1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00
v/vlib/v/ast/str.v

732 lines
17 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module ast
import v.util
import strings
// get_name returns the real name for the function declaration
pub fn (f &FnDecl) get_name() string {
if f.is_static_type_method {
return f.name.all_after_last('__static__')
} else {
return f.name
}
}
// get_name returns the real name for the function calling
pub fn (f &CallExpr) get_name() string {
if f.name != '' && f.name.all_after_last('.')[0].is_capital() && f.name.contains('__static__') {
return f.name.replace('__static__', '.')
} else {
return f.name
}
}
pub fn (node &FnDecl) modname() string {
if node.mod != '' {
return node.mod
}
mut pamod := node.name.all_before_last('.')
if pamod == node.name.after('.') {
pamod = if node.is_builtin { 'builtin' } else { 'main' }
}
return pamod
}
// fkey returns a unique name of the function/method.
// it is used in table.used_fns and v.markused.
pub fn (node &FnDecl) fkey() string {
if node.is_method {
return '${int(node.receiver.typ)}.${node.name}'
}
return node.name
}
pub fn (node &Fn) fkey() string {
if node.is_method {
return '${int(node.receiver_type)}.${node.name}'
}
return node.name
}
pub fn (node &CallExpr) fkey() string {
if node.is_method {
return '${int(node.receiver_type)}.${node.name}'
}
return node.name
}
// These methods are used only by vfmt, vdoc, and for debugging.
pub fn (node &AnonFn) stringify_anon_decl(t &Table, cur_mod string, m2a map[string]string) string {
mut f := strings.new_builder(30)
f.write_string('fn ')
if node.inherited_vars.len > 0 {
f.write_string('[')
for i, var in node.inherited_vars {
if i > 0 {
f.write_string(', ')
}
if var.is_shared {
f.write_string('shared ')
} else if var.is_atomic {
f.write_string('atomic ')
} else if var.is_mut {
f.write_string('mut ')
}
f.write_string(var.name)
}
f.write_string('] ')
}
stringify_fn_after_name(node.decl, mut f, t, cur_mod, m2a)
return f.str()
}
pub fn (node &FnDecl) stringify_fn_decl(t &Table, cur_mod string, m2a map[string]string) string {
mut f := strings.new_builder(30)
if node.is_pub {
f.write_string('pub ')
}
f.write_string('fn ')
if node.is_method {
f.write_string('(')
mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
cur_mod)
if node.rec_mut {
f.write_string(node.receiver.typ.share().str() + ' ')
styp = styp[1..] // remove &
}
f.write_string(node.receiver.name + ' ')
styp = util.no_cur_mod(styp, cur_mod)
if node.params[0].is_auto_rec {
styp = styp.trim('&')
}
f.write_string(styp + ') ')
} else if node.is_static_type_method {
mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
cur_mod)
f.write_string(styp + '.')
}
mut name := if !node.is_method && node.language == .v {
node.name.all_after_last('.')
} else {
node.name
}
if node.is_static_type_method {
name = name.after('__static__')
}
f.write_string(name)
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] {
f.write_string(' ')
}
stringify_fn_after_name(node, mut f, t, cur_mod, m2a)
return f.str()
}
fn stringify_fn_after_name(node &FnDecl, mut f strings.Builder, t &Table, cur_mod string, m2a map[string]string) {
mut add_para_types := true
if node.generic_names.len > 0 {
if node.is_method {
sym := t.sym(node.params[0].typ)
if sym.info is Struct {
generic_names := sym.info.generic_types.map(t.sym(it).name)
if generic_names == node.generic_names {
add_para_types = false
}
}
}
if add_para_types {
f.write_string('[')
for i, gname in node.generic_names {
is_last := i == node.generic_names.len - 1
f.write_string(gname)
if !is_last {
f.write_string(', ')
}
}
f.write_string(']')
}
}
f.write_string('(')
for i, arg in node.params {
// skip receiver
// if (node.is_method || node.is_interface) && i == 0 {
if node.is_method && i == 0 {
continue
}
if arg.is_hidden {
continue
}
is_last_arg := i == node.params.len - 1
is_type_only := arg.name == ''
should_add_type := true // is_last_arg || is_type_only || node.params[i + 1].typ != arg.typ ||
// (node.is_variadic && i == node.params.len - 2)
if arg.is_mut {
f.write_string(arg.typ.share().str() + ' ')
}
f.write_string(arg.name)
arg_sym := t.sym(arg.typ)
if arg_sym.kind == .struct_ && (arg_sym.info as Struct).is_anon {
f.write_string(' struct {')
struct_ := arg_sym.info as Struct
for field in struct_.fields {
f.write_string(' ${field.name} ${t.type_to_str(field.typ)}')
if field.has_default_expr {
f.write_string(' = ${field.default_expr}')
}
}
if struct_.fields.len > 0 {
f.write_string(' ')
}
f.write_string('}')
} else {
mut s := t.type_to_str(arg.typ.clear_flag(.shared_f))
if arg.is_mut {
if s.starts_with('&') && ((!arg_sym.is_number() && arg_sym.kind != .bool)
|| node.language != .v) {
s = s[1..]
}
}
s = util.no_cur_mod(s, cur_mod)
s = shorten_full_name_based_on_aliases(s, m2a)
if should_add_type {
if !is_type_only {
f.write_string(' ')
}
if node.is_variadic && is_last_arg {
f.write_string('...')
}
f.write_string(s)
}
}
if !is_last_arg {
f.write_string(', ')
}
}
f.write_string(')')
if node.return_type != void_type {
sreturn_type := util.no_cur_mod(t.type_to_str(node.return_type), cur_mod)
short_sreturn_type := shorten_full_name_based_on_aliases(sreturn_type, m2a)
f.write_string(' ${short_sreturn_type}')
}
}
struct StringifyModReplacement {
mod string
alias string
weight int
}
fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) string {
if m2a.len == 0 || -1 == input.index_u8(`.`) {
// a simple typename, like `string` or `[]bool`; no module aliasings apply,
// (or there just are not any mappings)
return input
}
// Shorten the full names to their aliases, but replace the longer mods first, so that:
// `import user.project`
// `import user.project.routes`
// will lead to replacing `user.project.routes` first to `routes`, NOT `user.project.routes` to `project.routes`.
// Also take into account the nesting level, so `a.e.c.d` will be shortened before `a.xyz.b`, even though they are the same length.
mut replacements := []StringifyModReplacement{cap: m2a.len}
for mod, alias in m2a {
if mod == alias {
// for vlib modules like `import strings` -> mod: `strings` | alias: `strings`
// ... which is the same, so no replacements are needed
continue
}
if !input.contains(mod) {
continue
}
replacements << StringifyModReplacement{
mod: mod
alias: alias
weight: mod.count('.') * 100 + mod.len
}
}
if replacements.len == 0 {
return input
}
//
mut res := input
if replacements.len > 1 {
replacements.sort(a.weight > b.weight)
}
for r in replacements {
if -1 == res.index_u8(`.`) {
// there are no remaining module parts left in the type name, it is a local one after all
break
}
if !res.contains(r.mod) {
// nothing to replace as well (just minimises modifications and string clonings)
continue
}
// r.mod: `v.token` | r.alias: `xyz` | res: `v.token.Abc` -> `xyz.Abc`
// r.mod: `v.ast` | r.alias: `ast` | res: `v.ast.AliasTypeDecl` -> `ast.AliasTypeDecl`
// r.mod: `v.ast` | r.alias: `ast` | res: `[]v.ast.InterfaceEmbedding` -> `[]ast.InterfaceEmbedding`
res = res.replace(r.mod, r.alias)
}
return res
}
// Expressions in string interpolations may have to be put in braces if they
// are non-trivial, if they would interfere with the next character or if a
// format specification is given. In the latter case
// the format specifier must be appended, separated by a colon:
// '$z $z.b $z.c.x ${x[4]} ${z:8.3f} ${a:-20} ${a>b+2}'
// This method creates the format specifier (including the colon) or an empty
// string if none is needed and also returns (as bool) if the expression
// must be enclosed in braces.
pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) {
mut res := []string{}
needs_fspec := lit.need_fmts[i] || lit.pluss[i]
|| (lit.fills[i] && lit.fwidths[i] >= 0) || lit.fwidths[i] != 0
|| lit.precisions[i] != 987698
mut needs_braces := needs_fspec
sx := lit.exprs[i].str()
if sx.contains(r'"') || sx.contains(r"'") {
needs_braces = true
}
if !needs_braces {
if i + 1 < lit.vals.len && lit.vals[i + 1].len > 0 {
next_char := lit.vals[i + 1][0]
if util.is_func_char(next_char) || next_char == `.` || next_char == `(` {
needs_braces = true
}
}
}
if !needs_braces {
mut sub_expr := lit.exprs[i]
for {
match mut sub_expr {
Ident {
if sub_expr.name[0] == `@` {
needs_braces = true
}
break
}
else {
needs_braces = true
break
}
}
}
}
if needs_fspec {
res << ':'
if lit.pluss[i] {
res << '+'
}
if lit.fills[i] && lit.fwidths[i] >= 0 {
res << '0'
}
if lit.fwidths[i] != 0 {
res << '${lit.fwidths[i]}'
}
if lit.precisions[i] != 987698 {
res << '.${lit.precisions[i]}'
}
if lit.need_fmts[i] {
res << '${lit.fmts[i]:c}'
}
}
return res.join(''), needs_braces
}
// string representation of expr
pub fn (x Expr) str() string {
match x {
AnonFn {
return 'anon_fn'
}
ComptimeType {
return x.str()
}
DumpExpr {
return 'dump(${x.expr.str()})'
}
ArrayInit {
mut fields := []string{}
if x.has_len {
fields << 'len: ${x.len_expr.str()}'
}
if x.has_cap {
fields << 'cap: ${x.cap_expr.str()}'
}
if x.has_default {
fields << 'init: ${x.default_expr.str()}'
}
if fields.len > 0 {
return '[]T{${fields.join(', ')}}'
} else {
return x.exprs.str()
}
}
AsCast {
return '${x.expr.str()} as ${global_table.type_to_str(x.typ)}'
}
AtExpr {
return '${x.val}'
}
CTempVar {
return x.orig.str()
}
BoolLiteral {
return x.val.str()
}
CastExpr {
return '${x.typname}(${x.expr.str()})'
}
CallExpr {
sargs := args2str(x.args)
propagate_suffix := if x.or_block.kind == .propagate_option {
'?'
} else if x.or_block.kind == .propagate_result {
'!'
} else {
''
}
if x.is_method {
return '${x.left.str()}.${x.name}(${sargs})${propagate_suffix}'
}
if x.name.starts_with('${x.mod}.') {
return util.strip_main_name('${x.get_name()}(${sargs})${propagate_suffix}')
}
if x.mod == '' && x.name == '' {
return x.left.str() + '(${sargs})${propagate_suffix}'
}
if x.name.contains('.') {
return '${x.get_name()}(${sargs})${propagate_suffix}'
}
if x.name.contains('__static__') {
return '${x.mod}.${x.get_name()}(${sargs})${propagate_suffix}'
}
return '${x.mod}.${x.get_name()}(${sargs})${propagate_suffix}'
}
CharLiteral {
return '`${x.val}`'
}
Comment {
if x.is_multi {
lines := x.text.split_into_lines()
return '/* ${lines.len} lines comment */'
} else {
text := x.text.trim('\x01').trim_space()
return '´// ${text}´'
}
}
ComptimeSelector {
return '${x.left}.$(${x.field_expr})'
}
ConcatExpr {
return x.vals.map(it.str()).join(',')
}
EnumVal {
return '.${x.val}'
}
FloatLiteral, IntegerLiteral {
return x.val.clone()
}
GoExpr {
return 'go ${x.call_expr}'
}
SpawnExpr {
return 'spawn ${x.call_expr}'
}
Ident {
return x.name.clone()
}
IfExpr {
mut parts := []string{}
dollar := if x.is_comptime { '$' } else { '' }
for i, branch in x.branches {
if i != 0 {
parts << ' } ${dollar}else '
}
if i < x.branches.len - 1 || !x.has_else {
parts << ' ${dollar}if ' + branch.cond.str() + ' { '
} else if x.has_else && i == x.branches.len - 1 {
parts << '{ '
}
for stmt in branch.stmts {
parts << stmt.str()
}
}
parts << ' }'
return parts.join('')
}
IndexExpr {
return '${x.left.str()}[${x.index.str()}]'
}
InfixExpr {
return '${x.left.str()} ${x.op.str()} ${x.right.str()}'
}
MapInit {
mut pairs := []string{}
for ik, kv in x.keys {
mv := x.vals[ik].str()
pairs << '${kv}: ${mv}'
}
return 'map{ ${pairs.join(' ')} }'
}
Nil {
return 'nil'
}
ParExpr {
return '(${x.expr})'
}
PostfixExpr {
if x.op == .question {
return '${x.expr} ?'
}
return '${x.expr}${x.op}'
}
PrefixExpr {
return x.op.str() + x.right.str()
}
RangeExpr {
mut s := '..'
if x.has_low {
s = '${x.low} ' + s
}
if x.has_high {
s = s + ' ${x.high}'
}
return s
}
SelectExpr {
return 'ast.SelectExpr'
}
SelectorExpr {
propagate_suffix := if x.or_block.kind == .propagate_option {
'?'
} else if x.or_block.kind == .propagate_result {
'!'
} else {
''
}
return '${x.expr.str()}.${x.field_name}${propagate_suffix}'
}
SizeOf {
if x.is_type {
return 'sizeof(${global_table.type_to_str(x.typ)})'
}
return 'sizeof(${x.expr.str()})'
}
OffsetOf {
return '__offsetof(${global_table.type_to_str(x.struct_type)}, ${x.field})'
}
StringInterLiteral {
mut res := strings.new_builder(50)
res.write_string("'")
for i, val in x.vals {
res.write_string(val)
if i >= x.exprs.len {
break
}
res.write_string('$')
fspec_str, needs_braces := x.get_fspec_braces(i)
if needs_braces {
res.write_string('{')
res.write_string(x.exprs[i].str())
res.write_string(fspec_str)
res.write_string('}')
} else {
res.write_string(x.exprs[i].str())
}
}
res.write_string("'")
return res.str()
}
StringLiteral {
return "'${x.val}'"
}
TypeNode {
return 'TypeNode(${global_table.type_str(x.typ)})'
}
TypeOf {
if x.is_type {
return 'typeof[${global_table.type_to_str(x.typ)}]()'
}
return 'typeof(${x.expr.str()})'
}
Likely {
return '_likely_(${x.expr.str()})'
}
UnsafeExpr {
return 'unsafe { ${x.expr} }'
}
None {
return 'none'
}
IsRefType {
if x.is_type {
return 'isreftype(${global_table.type_to_str(x.typ)})'
}
return 'isreftype(${x.expr.str()})'
}
IfGuardExpr {
mut s := ''
for i, var in x.vars {
s += var.name
if i != x.vars.len - 1 {
s += ', '
}
}
return s + ' := ' + x.expr.str()
}
StructInit {
sname := global_table.sym(x.typ).name
return '${sname}{....}'
}
ArrayDecompose {
return 'ast.ArrayDecompose'
}
Assoc {
return 'ast.Assoc'
}
ChanInit {
return 'ast.ChanInit'
}
ComptimeCall {
return 'ast.ComptimeCall'
}
EmptyExpr {
return 'ast.EmptyExpr'
}
LockExpr {
return 'ast.LockExpr'
}
MatchExpr {
return 'ast.MatchExpr'
}
NodeError {
return 'ast.NodeError'
}
OrExpr {
return 'ast.OrExpr'
}
SqlExpr {
return 'ast.SqlExpr'
}
}
return '[unhandled expr type ${x.type_name()}]'
}
pub fn (a CallArg) str() string {
if a.is_mut {
return 'mut ${a.expr.str()}'
}
return '${a.expr.str()}'
}
pub fn args2str(args []CallArg) string {
mut res := []string{}
for a in args {
res << a.str()
}
return res.join(', ')
}
pub fn (node &BranchStmt) str() string {
mut s := '${node.kind}'
if node.label.len > 0 {
s += ' ${node.label}'
}
return s
}
pub fn (node Stmt) str() string {
match node {
AssertStmt {
return 'assert ${node.expr}'
}
AssignStmt {
mut out := ''
for i, left in node.left {
if left is Ident {
var_info := left.var_info()
if var_info.is_mut {
out += 'mut '
}
}
out += left.str()
if i < node.left.len - 1 {
out += ','
}
}
out += ' ${node.op.str()} '
for i, val in node.right {
out += val.str()
if i < node.right.len - 1 {
out += ','
}
}
return out
}
BranchStmt {
return node.str()
}
ConstDecl {
fields := node.fields.map(field_to_string)
return 'const (${fields.join(' ')})'
}
ExprStmt {
return node.expr.str()
}
FnDecl {
return 'fn ${node.name}( ${node.params.len} params ) { ${node.stmts.len} stmts }'
}
EnumDecl {
return 'enum ${node.name} { ${node.fields.len} fields }'
}
ForStmt {
if node.is_inf {
return 'for {'
}
return 'for ${node.cond} {'
}
Module {
return 'module ${node.name}'
}
Import {
mut out := 'import ${node.mod}'
if node.alias.len > 0 {
out += ' as ${node.alias}'
}
return out
}
Return {
mut out := 'return'
for i, val in node.exprs {
out += ' ${val}'
if i < node.exprs.len - 1 {
out += ','
}
}
return out
}
StructDecl {
return 'struct ${node.name} { ${node.fields.len} fields }'
}
else {
return '[unhandled stmt str type: ${node.type_name()} ]'
}
}
}
fn field_to_string(f ConstField) string {
x := f.name.trim_string_left(f.mod + '.')
return '${x} = ${f.expr}'
}
pub fn (e ComptimeForKind) str() string {
match e {
.methods { return 'methods' }
.fields { return 'fields' }
.attributes { return 'attributes' }
.values { return 'values' }
}
}