From a4eb5b63565ab742d400f5a5f06212ad9406defe Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 28 Jun 2022 09:40:47 +0300 Subject: [PATCH] gen: basic initial work on the Go backend --- cmd/tools/builders/golang_builder.v | 7 + cmd/v/v.v | 4 + vlib/v/builder/golangbuilder/golangbuilder.v | 41 + vlib/v/checker/checker.v | 8 +- vlib/v/gen/golang/align.v | 59 + vlib/v/gen/golang/attrs.v | 64 + vlib/v/gen/golang/comments.v | 142 ++ vlib/v/gen/golang/golang.v | 2361 ++++++++++++++++++ vlib/v/gen/golang/struct.v | 283 +++ vlib/v/gen/golang/tests/simple.vv | 43 + vlib/v/gen/golang/tests/simple.vv.out | 2 + vlib/v/pref/pref.v | 2 + 12 files changed, 3012 insertions(+), 4 deletions(-) create mode 100644 cmd/tools/builders/golang_builder.v create mode 100644 vlib/v/builder/golangbuilder/golangbuilder.v create mode 100644 vlib/v/gen/golang/align.v create mode 100644 vlib/v/gen/golang/attrs.v create mode 100644 vlib/v/gen/golang/comments.v create mode 100644 vlib/v/gen/golang/golang.v create mode 100644 vlib/v/gen/golang/struct.v create mode 100644 vlib/v/gen/golang/tests/simple.vv create mode 100644 vlib/v/gen/golang/tests/simple.vv.out diff --git a/cmd/tools/builders/golang_builder.v b/cmd/tools/builders/golang_builder.v new file mode 100644 index 000000000..d2b7edf1a --- /dev/null +++ b/cmd/tools/builders/golang_builder.v @@ -0,0 +1,7 @@ +module main + +import v.builder.golangbuilder + +fn main() { + golangbuilder.start() +} diff --git a/cmd/v/v.v b/cmd/v/v.v index bd57f1959..5b3cfbd93 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -176,5 +176,9 @@ fn rebuild(prefs &pref.Preferences) { .interpret { util.launch_tool(prefs.is_verbose, 'builders/interpret_builder', os.args[1..]) } + .golang { + println('using Go WIP backend...') + util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..]) + } } } diff --git a/vlib/v/builder/golangbuilder/golangbuilder.v b/vlib/v/builder/golangbuilder/golangbuilder.v new file mode 100644 index 000000000..79cae393e --- /dev/null +++ b/vlib/v/builder/golangbuilder/golangbuilder.v @@ -0,0 +1,41 @@ +module golangbuilder + +import os +import v.pref +import v.util +import v.builder +import v.gen.golang + +pub fn start() { + mut args_and_flags := util.join_env_vflags_and_os_args()[1..] + prefs, _ := pref.parse_args([], args_and_flags) + builder.compile('build', prefs, compile_golang) +} + +pub fn compile_golang(mut b builder.Builder) { + // v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare')) + files := [b.pref.path] + b.set_module_lookup_paths() + build_golang(mut b, files, b.pref.out_name) +} + +pub fn build_golang(mut b builder.Builder, v_files []string, out_file string) { + if b.pref.os == .windows { + if !b.pref.is_shared && b.pref.build_mode != .build_module + && !b.pref.out_name.ends_with('.exe') { + b.pref.out_name += '.exe' + } + } + mut nvf := []string{} + for vf in v_files { + if os.is_dir(vf) { + nvf << b.v_files_from_dir(vf) + } else { + nvf << vf + } + } + b.front_and_middle_stages(nvf) or { return } + util.timing_start('Golang GEN') + b.stats_lines, b.stats_bytes = golang.gen(b.parsed_files, b.table, out_file, b.pref) + util.timing_measure('Golang GEN') +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index a9cabea07..110eced6b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1765,12 +1765,12 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if c.ct_cond_stack.len > 0 { node.ct_conds = c.ct_cond_stack.clone() } - if c.pref.backend.is_js() { - if !c.file.path.ends_with('.js.v') { - c.error('hash statements are only allowed in backend specific files such "x.js.v"', + if c.pref.backend.is_js() || c.pref.backend == .golang { + if !c.file.path.ends_with('.js.v') && !c.file.path.ends_with('.go.v') { + c.error('hash statements are only allowed in backend specific files such "x.js.v" and "x.go.v"', node.pos) } - if c.mod == 'main' { + if c.mod == 'main' && c.pref.backend != .golang { c.error('hash statements are not allowed in the main module. Place them in a separate module.', node.pos) } diff --git a/vlib/v/gen/golang/align.v b/vlib/v/gen/golang/align.v new file mode 100644 index 000000000..0dc62ffde --- /dev/null +++ b/vlib/v/gen/golang/align.v @@ -0,0 +1,59 @@ +// Copyright (c) 2019-2022 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 golang + +import v.mathutil + +const struct_field_align_threshold = 8 + +struct AlignInfo { +mut: + line_nr int + max_len int + max_type_len int +} + +/* +[params] +struct AddInfoConfig { + use_threshold bool +} + +fn (mut infos []AlignInfo) add_new_info(len int, type_len int, line int) { + infos << AlignInfo{ + line_nr: line + max_len: len + max_type_len: type_len + } +} + +[direct_array_access] +fn (mut infos []AlignInfo) add_info(len int, type_len int, line int, cfg AddInfoConfig) { + if infos.len == 0 { + infos.add_new_info(len, type_len, line) + return + } + i := infos.len - 1 + if line - infos[i].line_nr > 1 { + infos.add_new_info(len, type_len, line) + return + } + if cfg.use_threshold { + len_diff := mathutil.abs(infos[i].max_len - len) + + mathutil.abs(infos[i].max_type_len - type_len) + + if len_diff >= fmt.struct_field_align_threshold { + infos.add_new_info(len, type_len, line) + return + } + } + infos[i].line_nr = line + if len > infos[i].max_len { + infos[i].max_len = len + } + if type_len > infos[i].max_type_len { + infos[i].max_type_len = type_len + } +} +*/ diff --git a/vlib/v/gen/golang/attrs.v b/vlib/v/gen/golang/attrs.v new file mode 100644 index 000000000..ec7e2af66 --- /dev/null +++ b/vlib/v/gen/golang/attrs.v @@ -0,0 +1,64 @@ +// Copyright (c) 2019-2022 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 golang + +import v.ast + +pub fn (mut f Gen) attrs(attrs []ast.Attr) { + mut sorted_attrs := attrs.clone() + // Sort the attributes. The ones with arguments come first + sorted_attrs.sort_with_compare(fn (a &ast.Attr, b &ast.Attr) int { + d := b.arg.len - a.arg.len + return if d != 0 { d } else { compare_strings(b.arg, a.arg) } + }) + for i, attr in sorted_attrs { + if attr.arg.len == 0 { + f.single_line_attrs(sorted_attrs[i..]) + break + } + f.writeln('[$attr]') + } +} + +[params] +pub struct AttrsOptions { + inline bool +} + +pub fn (mut f Gen) single_line_attrs(attrs []ast.Attr, options AttrsOptions) { + if attrs.len == 0 { + return + } + mut sorted_attrs := attrs.clone() + sorted_attrs.sort(a.name < b.name) + if options.inline { + f.write(' ') + } + f.write('[') + for i, attr in sorted_attrs { + if i > 0 { + f.write('; ') + } + f.write('$attr') + } + f.write(']') + if !options.inline { + f.writeln('') + } +} + +fn inline_attrs_len(attrs []ast.Attr) int { + if attrs.len == 0 { + return 0 + } + mut n := 2 // ' ['.len + for i, attr in attrs { + if i > 0 { + n += 2 // '; '.len + } + n += '$attr'.len + } + n++ // ']'.len + return n +} diff --git a/vlib/v/gen/golang/comments.v b/vlib/v/gen/golang/comments.v new file mode 100644 index 000000000..d6724d181 --- /dev/null +++ b/vlib/v/gen/golang/comments.v @@ -0,0 +1,142 @@ +// Copyright (c) 2019-2022 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 golang + +import v.ast + +pub enum CommentsLevel { + keep + indent +} + +// CommentsOptions defines the way comments are going to be written +// - has_nl: adds an newline at the end of a list of comments +// - inline: line comments will be on the same line as the last statement +// - level: either .keep (don't indent), or .indent (increment indentation) +// - iembed: a /* ... */ block comment used inside expressions; // comments the whole line +// - prev_line: the line number of the previous token to save linebreaks +[minify; params] +pub struct CommentsOptions { + has_nl bool = true + inline bool + level CommentsLevel + iembed bool + prev_line int = -1 +} + +pub fn (mut f Gen) comment(node ast.Comment, options CommentsOptions) { + // Shebang in .vsh files + if node.text.starts_with('#!') { + f.writeln(node.text) + return + } + if options.level == .indent { + f.indent++ + } + if options.iembed { + x := node.text.trim_left('\x01').trim_space() + if x.contains('\n') { + f.writeln('/*') + f.writeln(x) + f.write('*/') + } else { + f.write('/* $x */') + } + } else if !node.text.contains('\n') { + is_separate_line := !options.inline || node.text.starts_with('\x01') + mut s := node.text.trim_left('\x01').trim_right(' ') + mut out_s := '//' + if s != '' { + if is_char_alphanumeric(s[0]) { + out_s += ' ' + } + out_s += s + } + if !is_separate_line && f.indent > 0 { + f.remove_new_line() // delete the generated \n + f.write(' ') + } + f.write(out_s) + } else { + lines := node.text.trim_space().split_into_lines() + start_break := is_char_alphanumeric(node.text[0]) || node.text[0].is_space() + end_break := is_char_alphanumeric(node.text.trim('\t').bytes().last()) + || node.text.bytes().last().is_space() + f.write('/*') + if start_break { + f.writeln('') + } + for line in lines { + f.writeln(line.trim_right(' ')) + f.empty_line = false + } + if end_break { + f.empty_line = true + } else { + f.remove_new_line() + } + f.write('*/') + } + if options.level == .indent { + f.indent-- + } +} + +pub fn (mut f Gen) comments(comments []ast.Comment, options CommentsOptions) { + mut prev_line := options.prev_line + for i, c in comments { + if options.prev_line > -1 && ((c.pos.line_nr > prev_line && f.out.last_n(1) != '\n') + || (c.pos.line_nr > prev_line + 1 && f.out.last_n(2) != '\n\n')) { + f.writeln('') + } + if !f.out.last_n(1)[0].is_space() { + f.write(' ') + } + f.comment(c, options) + if !options.iembed && (i < comments.len - 1 || options.has_nl) { + f.writeln('') + } + prev_line = c.pos.last_line + } +} + +pub fn (mut f Gen) comments_before_field(comments []ast.Comment) { + // They behave the same as comments after the last field. This alias is just for clarity. + f.comments_after_last_field(comments) +} + +pub fn (mut f Gen) comments_after_last_field(comments []ast.Comment) { + for comment in comments { + f.indent++ + f.empty_line = true + f.comment(comment, inline: true) + f.writeln('') + f.indent-- + } +} + +pub fn (mut f Gen) import_comments(comments []ast.Comment, options CommentsOptions) { + if comments.len == 0 { + return + } + if options.inline { + f.remove_new_line(imports_buffer: true) + } + for c in comments { + ctext := c.text.trim_left('\x01') + if ctext == '' { + continue + } + mut out_s := if options.inline { ' ' } else { '' } + '//' + if is_char_alphanumeric(ctext[0]) { + out_s += ' ' + } + out_s += ctext + f.out_imports.writeln(out_s) + } +} + +fn is_char_alphanumeric(c u8) bool { + return c.is_letter() || c.is_digit() +} diff --git a/vlib/v/gen/golang/golang.v b/vlib/v/gen/golang/golang.v new file mode 100644 index 000000000..15d25985e --- /dev/null +++ b/vlib/v/gen/golang/golang.v @@ -0,0 +1,2361 @@ +// Copyright (c) 2019-2022 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 golang + +import strings +import v.ast +import v.util +import v.pref +import os + +const ( + bs = '\\' + // when to break a line dependant on penalty + max_len = [0, 35, 60, 85, 93, 100] +) + +pub struct Gen { +pub mut: + table &ast.Table + pref &pref.Preferences + // is_debug bool + out strings.Builder + out_imports strings.Builder + indent int + empty_line bool + line_len int // the current line length, Note: it counts \t as 4 spaces, and starts at 0 after f.writeln + buffering bool // disables line wrapping for exprs that will be analyzed later + par_level int // how many parentheses are put around the current expression + single_line_if bool + cur_mod string + did_imports bool + is_assign bool + is_struct_init bool + auto_imports []string // automatically inserted imports that the user forgot to specify + import_pos int // position of the imports in the resulting string for later autoimports insertion + used_imports []string // to remove unused imports + import_syms_used map[string]bool // to remove unused import symbols. + mod2alias map[string]string // for `import time as t`, will contain: 'time'=>'t' + mod2syms map[string]string // import time { now } 'time.now'=>'now' + use_short_fn_args bool + single_line_fields bool // should struct fields be on a single line + it_name string // the name to replace `it` with + in_lambda_depth int + inside_const bool + is_mbranch_expr bool // match a { x...y { } } + fn_scope &ast.Scope = voidptr(0) + wsinfix_depth int + nlines int +} + +pub fn gen(files []&ast.File, table &ast.Table, out_file string, pref &pref.Preferences) (int, int) { + mut g := Gen{ + table: table + pref: pref + // is_debug: is_debug + out: strings.new_builder(1000) + out_imports: strings.new_builder(200) + } + for file in files { + if file.errors.len > 0 { + g.error(file.errors[0].str()) + } + g.stmts(file.stmts) + } + os.write_file(out_file, g.out.str()) or { panic(err) } + os.system('go fmt "$out_file"') + return g.nlines, 0 // g.buf.len +} + +pub fn (mut f Gen) process_file_imports(file &ast.File) { + for imp in file.imports { + f.mod2alias[imp.mod] = imp.alias + for sym in imp.syms { + f.mod2alias['${imp.mod}.$sym.name'] = sym.name + f.mod2alias['${imp.mod.all_after_last('.')}.$sym.name'] = sym.name + f.mod2alias[sym.name] = sym.name + f.mod2syms['${imp.mod}.$sym.name'] = sym.name + f.mod2syms['${imp.mod.all_after_last('.')}.$sym.name'] = sym.name + f.mod2syms[sym.name] = sym.name + f.import_syms_used[sym.name] = false + } + } + f.auto_imports = file.auto_imports +} + +//=== Basic buffer write operations ===// + +pub fn (mut f Gen) write(s string) { + if f.indent > 0 && f.empty_line { + f.write_indent() + } + f.out.write_string(s) + f.line_len += s.len + f.empty_line = false +} + +pub fn (mut f Gen) writeln(s string) { + if f.indent > 0 && f.empty_line && s.len > 0 { + f.write_indent() + } + f.out.writeln(s) + f.empty_line = true + f.line_len = 0 +} + +fn (mut f Gen) write_indent() { + f.out.write_string(util.tabs(f.indent)) + f.line_len += f.indent * 4 +} + +pub fn (mut f Gen) wrap_long_line(penalty_idx int, add_indent bool) bool { + if f.buffering { + return false + } + if f.out[f.out.len - 1] == ` ` { + f.out.go_back(1) + } + f.write('\n') + f.line_len = 0 + if add_indent { + f.indent++ + } + f.write_indent() + if add_indent { + f.indent-- + } + return true +} + +[params] +pub struct RemoveNewLineConfig { + imports_buffer bool // Work on f.out_imports instead of f.out +} + +pub fn (mut f Gen) remove_new_line(cfg RemoveNewLineConfig) { + mut buffer := if cfg.imports_buffer { unsafe { &f.out_imports } } else { unsafe { &f.out } } + mut i := 0 + for i = buffer.len - 1; i >= 0; i-- { + if !buffer.byte_at(i).is_space() { // != `\n` { + break + } + } + buffer.go_back(buffer.len - i - 1) + f.empty_line = false +} + +//=== Specialized write methods ===// + +fn (mut f Gen) write_language_prefix(lang ast.Language) { + match lang { + .c { f.write('C.') } + .js { f.write('JS.') } + else {} + } +} + +fn (mut f Gen) write_generic_types(gtypes []ast.Type) { + if gtypes.len > 0 { + f.write('<') + gtypes_string := gtypes.map(f.table.type_to_str(it)).join(', ') + f.write(gtypes_string) + f.write('>') + } +} + +//=== Module handling helper methods ===// + +pub fn (mut f Gen) set_current_module_name(cmodname string) { + f.cur_mod = cmodname + f.table.cmod_prefix = cmodname + '.' +} + +fn (f Gen) get_modname_prefix(mname string) (string, string) { + // ./tests/proto_module_importing_vproto_keep.vv to know, why here is checked for ']' and '&' + if !mname.contains(']') && !mname.contains('&') { + return mname, '' + } + after_rbc := mname.all_after_last(']') + after_ref := mname.all_after_last('&') + modname := if after_rbc.len < after_ref.len { after_rbc } else { after_ref } + return modname, mname.trim_string_right(modname) +} + +fn (mut f Gen) is_external_name(name string) bool { + if name.len > 2 && name[0] == `C` && name[1] == `.` { + return true + } + if name.len > 3 && name[0] == `J` && name[1] == `S` && name[2] == `.` { + return true + } + return false +} + +pub fn (mut f Gen) no_cur_mod(typename string) string { + return util.no_cur_mod(typename, f.cur_mod) +} + +// foo.bar.fn() => bar.fn() +pub fn (mut f Gen) short_module(name string) string { + if !name.contains('.') || name.starts_with('JS.') { + return name + } + if name in f.mod2syms { + return f.mod2syms[name] + } + if name.ends_with('>') { + generic_levels := name.trim_string_right('>').split('<') + mut res := '${f.short_module(generic_levels[0])}' + for i in 1 .. generic_levels.len { + genshorts := generic_levels[i].split(', ').map(f.short_module(it)).join(', ') + res += '<$genshorts' + } + res += '>' + return res + } + vals := name.split('.') + if vals.len < 2 { + return name + } + idx := vals.len - 1 + mname, tprefix := f.get_modname_prefix(vals[..idx].join('.')) + symname := vals[vals.len - 1] + mut aname := f.mod2alias[mname] + if aname == '' { + for _, v in f.mod2alias { + if v == mname { + aname = mname + break + } + } + } + if aname == '' { + return '$tprefix$symname' + } + return '$tprefix${aname}.$symname' +} + +//=== Import-related methods ===// + +pub fn (mut f Gen) mark_types_import_as_used(typ ast.Type) { + sym := f.table.sym(typ) + if sym.info is ast.Map { + map_info := sym.map_info() + f.mark_types_import_as_used(map_info.key_type) + f.mark_types_import_as_used(map_info.value_type) + return + } + if sym.info is ast.GenericInst { + for concrete_typ in sym.info.concrete_types { + f.mark_types_import_as_used(concrete_typ) + } + } + name := sym.name.split('<')[0] // take `Type` from `Type` + f.mark_import_as_used(name) +} + +// `name` is a function (`foo.bar()`) or type (`foo.Bar{}`) +pub fn (mut f Gen) mark_import_as_used(name string) { + parts := name.split('.') + last := parts.last() + if last in f.import_syms_used { + f.import_syms_used[last] = true + } + if parts.len == 1 { + return + } + mod := parts[0..parts.len - 1].join('.') + if mod in f.used_imports { + return + } + f.used_imports << mod +} + +pub fn (mut f Gen) imports(imports []ast.Import) { + if f.did_imports || imports.len == 0 { + return + } + f.did_imports = true + mut num_imports := 0 + mut already_imported := map[string]bool{} + + for imp in imports { + if imp.mod !in f.used_imports { + // TODO bring back once only unused imports are removed + // continue + } + if imp.mod in f.auto_imports && imp.mod !in f.used_imports { + continue + } + import_text := 'import ${f.imp_stmt_str(imp)}' + if already_imported[import_text] { + continue + } + already_imported[import_text] = true + f.out_imports.writeln(import_text) + f.import_comments(imp.comments, inline: true) + f.import_comments(imp.next_comments) + num_imports++ + } + if num_imports > 0 { + f.out_imports.writeln('') + } +} + +pub fn (f Gen) imp_stmt_str(imp ast.Import) string { + mod := if imp.mod.len == 0 { imp.alias } else { imp.mod } + is_diff := imp.alias != mod && !mod.ends_with('.' + imp.alias) + mut imp_alias_suffix := if is_diff { ' as $imp.alias' } else { '' } + mut syms := imp.syms.map(it.name).filter(f.import_syms_used[it]) + syms.sort() + if syms.len > 0 { + imp_alias_suffix += if imp.syms[0].pos.line_nr == imp.pos.line_nr { + ' { ' + syms.join(', ') + ' }' + } else { + ' {\n\t' + syms.join(',\n\t') + ',\n}' + } + } + return '$mod$imp_alias_suffix' +} + +//=== Node helpers ===// + +fn (f Gen) should_insert_newline_before_node(node ast.Node, prev_node ast.Node) bool { + // No need to insert a newline if there is already one + if f.out.last_n(2) == '\n\n' { + return false + } + prev_line_nr := prev_node.pos().last_line + // The nodes are Stmts + if node is ast.Stmt && prev_node is ast.Stmt { + stmt := node + prev_stmt := prev_node + // Force a newline after a block of HashStmts + if prev_stmt is ast.HashStmt && stmt !is ast.HashStmt && stmt !is ast.ExprStmt { + return true + } + // Force a newline after function declarations + // The only exception is inside an block of no_body functions + if prev_stmt is ast.FnDecl { + if stmt !is ast.FnDecl || !prev_stmt.no_body { + return true + } + } + // Force a newline after struct declarations + if prev_stmt is ast.StructDecl { + return true + } + // Empty line after an block of type declarations + if prev_stmt is ast.TypeDecl && stmt !is ast.TypeDecl { + return true + } + // Imports are handled special hence they are ignored here + if stmt is ast.Import || prev_stmt is ast.Import { + return false + } + // Attributes are not respected in the stmts position, so this requires manual checking + if stmt is ast.StructDecl { + if stmt.attrs.len > 0 && stmt.attrs[0].pos.line_nr - prev_line_nr <= 1 { + return false + } + } + if stmt is ast.FnDecl { + if stmt.attrs.len > 0 && stmt.attrs[0].pos.line_nr - prev_line_nr <= 1 { + return false + } + } + } + // The node shouldn't have a newline before + if node.pos().line_nr - prev_line_nr <= 1 { + return false + } + return true +} + +pub fn (mut f Gen) node_str(node ast.Node) string { + was_empty_line := f.empty_line + prev_line_len := f.line_len + pos := f.out.len + match node { + ast.Stmt { f.stmt(node) } + ast.Expr { f.expr(node) } + else { panic('´f.node_str()´ is not implemented for ${node}.') } + } + str := f.out.after(pos) + f.out.go_back_to(pos) + f.empty_line = was_empty_line + f.line_len = prev_line_len + return str +} + +//=== General Stmt-related methods and helpers ===// + +pub fn (mut f Gen) stmts(stmts []ast.Stmt) { + mut prev_stmt := if stmts.len > 0 { stmts[0] } else { ast.empty_stmt() } + f.indent++ + for stmt in stmts { + if !f.pref.building_v && f.should_insert_newline_before_node(stmt, prev_stmt) { + f.out.writeln('') + } + f.stmt(stmt) + prev_stmt = stmt + } + f.indent-- +} + +pub fn (mut f Gen) stmt(node ast.Stmt) { + // if f.is_debug { + // eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}') + //} + match node { + ast.EmptyStmt, ast.NodeError {} + ast.AsmStmt { + // f.asm_stmt(node) + } + ast.AssertStmt { + f.assert_stmt(node) + } + ast.AssignStmt { + f.assign_stmt(node) + } + ast.Block { + f.block(node) + } + ast.BranchStmt { + f.branch_stmt(node) + } + ast.ComptimeFor { + f.comptime_for(node) + } + ast.ConstDecl { + f.const_decl(node) + } + ast.DeferStmt { + f.defer_stmt(node) + } + ast.EnumDecl { + f.enum_decl(node) + } + ast.ExprStmt { + f.expr_stmt(node) + } + ast.FnDecl { + f.fn_decl(node) + } + ast.ForCStmt { + f.for_c_stmt(node) + } + ast.ForInStmt { + f.for_in_stmt(node) + } + ast.ForStmt { + f.for_stmt(node) + } + ast.GlobalDecl { + f.global_decl(node) + } + ast.GotoLabel { + f.goto_label(node) + } + ast.GotoStmt { + f.goto_stmt(node) + } + ast.HashStmt { + f.hash_stmt(node) + } + ast.Import { + // Imports are handled after the file is formatted, to automatically add necessary modules + // Just remember the position of the imports for now + f.import_pos = f.out.len + } + ast.InterfaceDecl { + f.interface_decl(node) + } + ast.Module { + f.module_stmt(node) + } + ast.Return { + f.return_stmt(node) + } + ast.SqlStmt { + f.sql_stmt(node) + } + ast.StructDecl { + f.struct_decl(node) + } + ast.TypeDecl { + f.type_decl(node) + } + } +} + +fn stmt_is_single_line(stmt ast.Stmt) bool { + return match stmt { + ast.ExprStmt, ast.AssertStmt { expr_is_single_line(stmt.expr) } + ast.Return, ast.AssignStmt, ast.BranchStmt { true } + else { false } + } +} + +//=== General Expr-related methods and helpers ===// + +pub fn (mut f Gen) expr(node_ ast.Expr) { + mut node := unsafe { node_ } + // if f.is_debug { + // eprintln('expr: ${node.pos():-42} | node: ${node.type_name():-20} | $node.str()') + //} + match mut node { + ast.NodeError {} + ast.EmptyExpr {} + ast.AnonFn { + f.anon_fn(node) + } + ast.ArrayDecompose { + f.array_decompose(node) + } + ast.ArrayInit { + f.array_init(node) + } + ast.AsCast { + f.as_cast(node) + } + ast.Assoc { + f.assoc(node) + } + ast.AtExpr { + f.at_expr(node) + } + ast.BoolLiteral { + f.write(node.val.str()) + } + ast.CallExpr { + f.call_expr(node) + } + ast.CastExpr { + f.cast_expr(node) + } + ast.ChanInit { + f.chan_init(mut node) + } + ast.CharLiteral { + f.char_literal(node) + } + ast.Comment { + f.comment(node, inline: true) + } + ast.ComptimeCall { + f.comptime_call(node) + } + ast.ComptimeSelector { + f.comptime_selector(node) + } + ast.ConcatExpr { + f.concat_expr(node) + } + ast.CTempVar { + eprintln('ast.CTempVar of $node.orig.str() should be generated/used only in cgen') + } + ast.DumpExpr { + f.dump_expr(node) + } + ast.EnumVal { + f.enum_val(node) + } + ast.FloatLiteral { + f.write(node.val) + if node.val.ends_with('.') { + f.write('0') + } + } + ast.GoExpr { + f.go_expr(node) + } + ast.Ident { + f.ident(node) + } + ast.IfExpr { + f.if_expr(node) + } + ast.IfGuardExpr { + f.if_guard_expr(node) + } + ast.IndexExpr { + f.index_expr(node) + } + ast.InfixExpr { + f.infix_expr(node) + } + ast.IntegerLiteral { + f.write(node.val) + } + ast.Likely { + f.likely(node) + } + ast.LockExpr { + f.lock_expr(node) + } + ast.MapInit { + f.map_init(node) + } + ast.MatchExpr { + f.match_expr(node) + } + ast.None { + f.write('none') + } + ast.OffsetOf { + f.offset_of(node) + } + ast.OrExpr { + // shouldn't happen, an or expression is always linked to a call expr or index expr + panic('fmt: OrExpr should be linked to ast.CallExpr or ast.IndexExpr') + } + ast.ParExpr { + f.par_expr(node) + } + ast.PostfixExpr { + f.postfix_expr(node) + } + ast.PrefixExpr { + f.prefix_expr(node) + } + ast.RangeExpr { + f.range_expr(node) + } + ast.SelectExpr { + f.select_expr(node) + } + ast.SelectorExpr { + f.selector_expr(node) + } + ast.SizeOf { + f.size_of(node) + } + ast.IsRefType { + f.is_ref_type(node) + } + ast.SqlExpr { + f.sql_expr(node) + } + ast.StringLiteral { + f.string_literal(node) + } + ast.StringInterLiteral { + f.string_inter_literal(node) + } + ast.StructInit { + f.struct_init(node) + } + ast.TypeNode { + f.type_expr(node) + } + ast.TypeOf { + f.type_of(node) + } + ast.UnsafeExpr { + f.unsafe_expr(node) + } + ast.ComptimeType { + match node.kind { + .array { f.write('\$Array') } + .struct_ { f.write('\$Struct') } + .iface { f.write('\$Interface') } + .map_ { f.write('\$Map') } + .int { f.write('\$Int') } + .float { f.write('\$Float') } + .sum_type { f.write('\$Sumtype') } + .enum_ { f.write('\$Enum') } + } + } + } +} + +fn expr_is_single_line(expr ast.Expr) bool { + match expr { + ast.Comment, ast.IfExpr, ast.MapInit, ast.MatchExpr { + return false + } + ast.AnonFn { + if !expr.decl.no_body { + return false + } + } + ast.StructInit { + if !expr.is_short && (expr.fields.len > 0 || expr.pre_comments.len > 0) { + return false + } + } + ast.CallExpr { + if expr.or_block.stmts.len > 1 { + return false + } + } + ast.ArrayInit { + if expr.exprs.len > 0 { + return expr_is_single_line(expr.exprs[0]) + } + } + ast.ConcatExpr { + for e in expr.vals { + if !expr_is_single_line(e) { + return false + } + } + } + ast.StringLiteral { + return expr.pos.line_nr == expr.pos.last_line + } + else {} + } + return true +} + +//=== Specific Stmt methods ===// + +pub fn (mut f Gen) assert_stmt(node ast.AssertStmt) { + f.write('assert ') + mut expr := node.expr + for expr is ast.ParExpr { + expr = (expr as ast.ParExpr).expr + } + f.expr(expr) + f.writeln('') +} + +pub fn (mut f Gen) assign_stmt(node ast.AssignStmt) { + for i, left in node.left { + f.expr(left) + if i < node.left.len - 1 { + f.write(', ') + } + } + f.is_assign = true + f.write(' $node.op.str() ') + for i, val in node.right { + f.expr(val) + if i < node.right.len - 1 { + f.write(', ') + } + } + f.comments(node.end_comments, has_nl: false, inline: true, level: .keep) + if !f.single_line_if { + f.writeln('') + } + f.is_assign = false +} + +pub fn (mut f Gen) block(node ast.Block) { + if node.is_unsafe { + f.write('unsafe ') + } + f.write('{') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Gen) branch_stmt(node ast.BranchStmt) { + f.writeln(node.str()) +} + +pub fn (mut f Gen) comptime_for(node ast.ComptimeFor) { + typ := f.no_cur_mod(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write('\$for $node.val_var in ${typ}.$node.kind.str() {') + f.mark_types_import_as_used(node.typ) + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +struct ConstAlignInfo { +mut: + max int + last_idx int +} + +pub fn (mut f Gen) const_decl(node ast.ConstDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('const ()\n') + return + } + f.inside_const = true + defer { + f.inside_const = false + } + f.write('const ') + mut align_infos := []ConstAlignInfo{} + if node.is_block { + f.writeln('(') + mut info := ConstAlignInfo{} + for i, field in node.fields { + if field.name.len > info.max { + info.max = field.name.len + } + if !expr_is_single_line(field.expr) { + info.last_idx = i + align_infos << info + info = ConstAlignInfo{} + } + } + info.last_idx = node.fields.len + align_infos << info + f.indent++ + } else { + align_infos << ConstAlignInfo{0, 1} + } + mut prev_field := if node.fields.len > 0 { + ast.Node(node.fields[0]) + } else { + ast.Node(ast.NodeError{}) + } + mut align_idx := 0 + for i, field in node.fields { + if i > align_infos[align_idx].last_idx { + align_idx++ + } + if field.comments.len > 0 { + if f.should_insert_newline_before_node(ast.Expr(field.comments[0]), prev_field) { + f.writeln('') + } + f.comments(field.comments, inline: true) + prev_field = ast.Expr(field.comments.last()) + } + if node.is_block && f.should_insert_newline_before_node(field, prev_field) { + f.writeln('') + } + name := field.name.after('.') + f.write('$name ') + f.write(strings.repeat(` `, align_infos[align_idx].max - field.name.len)) + f.write('= ') + f.expr(field.expr) + f.comments(field.end_comments, inline: true) + if node.is_block && field.end_comments.len == 0 { + f.writeln('') + } else { + // Write out single line comments after const expr if present + // E.g.: `const x = 1 // ` + if node.end_comments.len > 0 && node.end_comments[0].text.contains('\n') { + f.writeln('\n') + } + f.comments(node.end_comments, inline: true) + } + prev_field = field + } + + if node.is_block { + f.comments_after_last_field(node.end_comments) + } else if node.end_comments.len == 0 { + // If no single line comments after the const expr is present + f.writeln('') + } + if node.is_block { + f.indent-- + f.writeln(')\n') + } else { + f.writeln('') + } +} + +pub fn (mut f Gen) defer_stmt(node ast.DeferStmt) { + f.write('defer {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Gen) expr_stmt(node ast.ExprStmt) { + f.comments(node.comments) + f.expr(node.expr) + if !f.single_line_if { + f.writeln('') + } +} + +pub fn (mut f Gen) enum_decl(node ast.EnumDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + name := node.name.after('.') + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('enum $name {}\n') + return + } + f.writeln('enum $name {') + f.comments(node.comments, inline: true, level: .indent) + for field in node.fields { + f.write('\t$field.name') + if field.has_expr { + f.write(' = ') + f.expr(field.expr) + } + f.comments(field.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + f.comments(field.next_comments, inline: false, has_nl: true, level: .indent) + } + f.writeln('}\n') +} + +pub fn (mut f Gen) fn_decl(node ast.FnDecl) { + f.attrs(node.attrs) + f.write(node.stringify(f.table, f.cur_mod, f.mod2alias).replace('fn ', 'func ')) + f.fn_body(node) +} + +pub fn (mut f Gen) anon_fn(node ast.AnonFn) { + f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.fn_body(node.decl) +} + +fn (mut f Gen) fn_body(node ast.FnDecl) { + prev_fn_scope := f.fn_scope + f.fn_scope = node.scope + defer { + f.fn_scope = prev_fn_scope + } + if node.language == .v { + if !node.no_body { + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + if node.comments.len == 0 { + f.writeln('') + } + f.stmts(node.stmts) + } + f.write('}') + } + if !node.is_anon { + f.writeln('') + } + } else { + f.writeln('') + } +} + +pub fn (mut f Gen) for_c_stmt(node ast.ForCStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + if node.has_init { + f.single_line_if = true // to keep all for ;; exprs on the same line + f.stmt(node.init) + f.single_line_if = false + } + f.write('; ') + f.expr(node.cond) + f.write('; ') + f.stmt(node.inc) + f.remove_new_line() + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Gen) for_in_stmt(node ast.ForInStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + if node.key_var != '' { + f.write(node.key_var) + } + if node.val_var != '' { + if node.key_var != '' { + f.write(', ') + } + if node.val_is_mut { + f.write('mut ') + } + f.write(node.val_var) + } + f.write(' := range ') + f.expr(node.cond) + if node.is_range { + f.write(' .. ') + f.expr(node.high) + } + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Gen) for_stmt(node ast.ForStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + f.expr(node.cond) + if !node.is_inf { + f.write(' ') + } + f.write('{') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Gen) global_decl(node ast.GlobalDecl) { + f.attrs(node.attrs) + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('__global ()') + return + } + f.write('__global ') + mut max := 0 + // mut has_assign := false + if node.is_block { + f.writeln('(') + f.indent++ + for field in node.fields { + if field.name.len > max { + max = field.name.len + } + // if field.has_expr { + // has_assign = true + //} + } + } + for field in node.fields { + f.comments(field.comments, inline: true) + f.write('$field.name ') + f.write(strings.repeat(` `, max - field.name.len)) + if field.has_expr { + f.write('= ') + f.expr(field.expr) + } else { + f.write('${f.table.type_to_str_using_aliases(field.typ, f.mod2alias)}') + } + if node.is_block { + f.writeln('') + } + f.mark_types_import_as_used(field.typ) + } + f.comments_after_last_field(node.end_comments) + if node.is_block { + f.indent-- + f.writeln(')') + } else { + f.writeln('') + } +} + +pub fn (mut f Gen) go_expr(node ast.GoExpr) { + f.write('go ') + f.call_expr(node.call_expr) +} + +pub fn (mut f Gen) goto_label(node ast.GotoLabel) { + f.writeln('$node.name:') +} + +pub fn (mut f Gen) goto_stmt(node ast.GotoStmt) { + f.writeln('goto $node.name') +} + +pub fn (mut f Gen) hash_stmt(node ast.HashStmt) { + f.writeln(node.val) +} + +pub fn (mut f Gen) interface_decl(node ast.InterfaceDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + f.write('interface ') + f.write_language_prefix(node.language) + name := node.name.after('.') // strip prepended module + f.write(name) + f.write_generic_types(node.generic_types) + f.write(' {') + if node.fields.len > 0 || node.methods.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + } + f.comments_before_field(node.pre_comments) + for embed in node.embeds { + f.write('\t$embed.name') + f.comments(embed.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + } + immut_fields := if node.mut_pos < 0 { node.fields } else { node.fields[..node.mut_pos] } + mut_fields := if node.mut_pos < 0 { []ast.StructField{} } else { node.fields[node.mut_pos..] } + + mut immut_methods := node.methods + mut mut_methods := []ast.FnDecl{} + for i, method in node.methods { + if method.params[0].is_mut { + immut_methods = node.methods[..i] + mut_methods = node.methods[i..] + break + } + } + + // TODO: alignment, comments, etc. + for field in immut_fields { + f.interface_field(field) + } + for method in immut_methods { + f.interface_method(method) + } + if mut_fields.len + mut_methods.len > 0 { + f.writeln('mut:') + for field in mut_fields { + f.interface_field(field) + } + for method in mut_methods { + f.interface_method(method) + } + } + f.writeln('}\n') +} + +pub fn (mut f Gen) interface_field(field ast.StructField) { + mut ft := f.no_cur_mod(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) + end_pos := field.pos.pos + field.pos.len + before_comments := field.comments.filter(it.pos.pos < field.pos.pos) + between_comments := field.comments[before_comments.len..].filter(it.pos.pos < end_pos) + after_type_comments := field.comments[(before_comments.len + between_comments.len)..] + f.write('\t$field.name $ft') + if after_type_comments.len > 0 { + f.comments(after_type_comments, level: .indent) + } else { + f.writeln('') + } + f.mark_types_import_as_used(field.typ) +} + +pub fn (mut f Gen) interface_method(method ast.FnDecl) { + f.write('\t') + f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn ')) + f.comments(method.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + f.comments(method.next_comments, inline: false, has_nl: true, level: .indent) + for param in method.params { + f.mark_types_import_as_used(param.typ) + } + f.mark_types_import_as_used(method.return_type) +} + +pub fn (mut f Gen) module_stmt(mod ast.Module) { + f.set_current_module_name(mod.name) + if mod.is_skipped { + return + } + f.attrs(mod.attrs) + f.writeln('package $mod.short_name\n') + if f.import_pos == 0 { + f.import_pos = f.out.len + } +} + +pub fn (mut f Gen) return_stmt(node ast.Return) { + f.comments(node.comments) + f.write('return') + if node.exprs.len > 0 { + f.write(' ') + // Loop over all return values. In normal returns this will only run once. + for i, expr in node.exprs { + if expr is ast.ParExpr { + f.expr(expr.expr) + } else { + f.expr(expr) + } + if i < node.exprs.len - 1 { + f.write(', ') + } + } + } + f.writeln('') +} + +pub fn (mut f Gen) sql_stmt(node ast.SqlStmt) { + f.write('sql ') + f.expr(node.db_expr) + f.writeln(' {') + + for line in node.lines { + f.sql_stmt_line(line) + } + + f.writeln('}') +} + +pub fn (mut f Gen) sql_stmt_line(node ast.SqlStmtLine) { + table_name := util.strip_mod_name(f.table.sym(node.table_expr.typ).name) + f.mark_types_import_as_used(node.table_expr.typ) + f.write('\t') + match node.kind { + .insert { + f.writeln('insert $node.object_var_name into $table_name') + } + .update { + f.write('update $table_name set ') + for i, col in node.updated_columns { + f.write('$col = ') + f.expr(node.update_exprs[i]) + if i < node.updated_columns.len - 1 { + f.write(', ') + } else { + f.write(' ') + } + f.wrap_long_line(3, true) + } + f.write('where ') + f.expr(node.where_expr) + f.writeln('') + } + .delete { + f.write('delete from $table_name where ') + f.expr(node.where_expr) + f.writeln('') + } + .create { + f.writeln('create table $table_name') + } + .drop { + f.writeln('drop table $table_name') + } + } +} + +pub fn (mut f Gen) type_decl(node ast.TypeDecl) { + match node { + ast.AliasTypeDecl { f.alias_type_decl(node) } + ast.FnTypeDecl { f.fn_type_decl(node) } + ast.SumTypeDecl { f.sum_type_decl(node) } + } + f.writeln('') +} + +pub fn (mut f Gen) alias_type_decl(node ast.AliasTypeDecl) { + if node.is_pub { + f.write('pub ') + } + ptype := f.table.type_to_str_using_aliases(node.parent_type, f.mod2alias) + f.write('type $node.name = $ptype') + + f.comments(node.comments, has_nl: false) + f.mark_types_import_as_used(node.parent_type) +} + +pub fn (mut f Gen) fn_type_decl(node ast.FnTypeDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + typ_sym := f.table.sym(node.typ) + fn_typ_info := typ_sym.info as ast.FnType + fn_info := fn_typ_info.func + fn_name := f.no_cur_mod(node.name) + f.write('type $fn_name = fn (') + for i, arg in fn_info.params { + if arg.is_mut { + f.write(arg.typ.share().str() + ' ') + } + f.write(arg.name) + f.mark_types_import_as_used(arg.typ) + mut s := f.no_cur_mod(f.table.type_to_str_using_aliases(arg.typ, f.mod2alias)) + if arg.is_mut { + if s.starts_with('&') { + s = s[1..] + } + } + is_last_arg := i == fn_info.params.len - 1 + should_add_type := true || is_last_arg + || fn_info.params[i + 1].typ != arg.typ + || (fn_info.is_variadic && i == fn_info.params.len - 2) + if should_add_type { + ns := if arg.name == '' { '' } else { ' ' } + if fn_info.is_variadic && is_last_arg { + f.write(ns + '...' + s) + } else { + f.write(ns + s) + } + } + if !is_last_arg { + f.write(', ') + } + } + f.write(')') + if fn_info.return_type.idx() != ast.void_type_idx { + f.mark_types_import_as_used(fn_info.return_type) + ret_str := f.no_cur_mod(f.table.type_to_str_using_aliases(fn_info.return_type, + f.mod2alias)) + f.write(' $ret_str') + } else if fn_info.return_type.has_flag(.optional) { + f.write(' ?') + } else if fn_info.return_type.has_flag(.result) { + f.write(' !') + } + + f.comments(node.comments, has_nl: false) + f.writeln('') +} + +pub fn (mut f Gen) sum_type_decl(node ast.SumTypeDecl) { + f.attrs(node.attrs) + start_pos := f.out.len + if node.is_pub { + f.write('pub ') + } + f.write('type $node.name') + f.write_generic_types(node.generic_types) + f.write(' = ') + + mut sum_type_names := []string{cap: node.variants.len} + for variant in node.variants { + sum_type_names << f.table.type_to_str_using_aliases(variant.typ, f.mod2alias) + f.mark_types_import_as_used(variant.typ) + } + sum_type_names.sort() + + separator := ' | ' + for i, name in sum_type_names { + if i > 0 { + f.write(separator) + } + f.write(name) + } +} + +//=== Specific Expr methods ===// + +pub fn (mut f Gen) array_decompose(node ast.ArrayDecompose) { + f.write('...') + f.expr(node.expr) +} + +pub fn (mut f Gen) array_init(node ast.ArrayInit) { + if node.exprs.len == 0 && node.typ != 0 && node.typ != ast.void_type { + // `x := []string{}` + f.mark_types_import_as_used(node.typ) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write('{') + if node.has_len { + f.write('len: ') + f.expr(node.len_expr) + if node.has_cap || node.has_default { + f.write(', ') + } + } + if node.has_cap { + f.write('cap: ') + f.expr(node.cap_expr) + if node.has_default { + f.write(', ') + } + } + if node.has_default { + f.write('init: ') + f.expr(node.default_expr) + } + f.write('}') + return + } + // `[1,2,3]` + sym := f.table.sym(node.typ) + f.write('$sym.name{') + mut inc_indent := false + mut last_line_nr := node.pos.line_nr // to have the same newlines between array elements + if node.pre_cmnts.len > 0 { + if node.pre_cmnts[0].pos.line_nr > last_line_nr { + f.writeln('') + } + } + mut set_comma := false + for i, expr in node.exprs { + pos := expr.pos() + f.expr(expr) + f.write(',') + } + f.write('}') + // `[100]u8` + if node.is_fixed { + if node.has_val { + f.write('!') + return + } + f.write(f.table.type_to_str_using_aliases(node.elem_type, f.mod2alias)) + if node.has_default { + f.write('{init: ') + f.expr(node.default_expr) + f.write('}') + } else { + f.write('{}') + } + } +} + +pub fn (mut f Gen) as_cast(node ast.AsCast) { + f.mark_types_import_as_used(node.typ) + type_str := f.table.type_to_str_using_aliases(node.typ, f.mod2alias) + f.expr(node.expr) + f.write(' as $type_str') +} + +pub fn (mut f Gen) assoc(node ast.Assoc) { + f.writeln('{') + f.indent++ + f.writeln('...$node.var_name') + for i, field in node.fields { + f.write('$field: ') + f.expr(node.exprs[i]) + f.writeln('') + } + f.indent-- + f.write('}') +} + +pub fn (mut f Gen) at_expr(node ast.AtExpr) { + f.write(node.name) +} + +pub fn (mut f Gen) call_expr(node ast.CallExpr) { + for arg in node.args { + f.comments(arg.comments) + } + mut is_method_newline := false + if node.is_method { + if node.name in ['map', 'filter', 'all', 'any'] { + f.in_lambda_depth++ + defer { + f.in_lambda_depth-- + } + } + f.expr(node.left) + is_method_newline = node.left.pos().last_line != node.name_pos.line_nr + if is_method_newline { + f.indent++ + f.writeln('') + } + f.write('.' + node.name) + } else { + f.write_language_prefix(node.language) + if node.left is ast.AnonFn { + f.anon_fn(node.left) + } else if node.language != .v { + f.write('${node.name.after_char(`.`)}') + } else { + mut name := f.short_module(node.name) + f.mark_import_as_used(name) + f.write('$name') + } + } + if node.mod == '' && node.name == '' { + f.write(node.left.str()) + } + f.write_generic_call_if_require(node) + f.write('(') + f.call_args(node.args) + f.write(')') + f.or_expr(node.or_block) + f.comments(node.comments, has_nl: false) + if is_method_newline { + f.indent-- + } +} + +fn (mut f Gen) write_generic_call_if_require(node ast.CallExpr) { + if node.concrete_types.len > 0 { + f.write('<') + for i, concrete_type in node.concrete_types { + mut name := f.table.type_to_str_using_aliases(concrete_type, f.mod2alias) + tsym := f.table.sym(concrete_type) + if tsym.language != .js && !tsym.name.starts_with('JS.') { + name = f.short_module(name) + } else if tsym.language == .js && !tsym.name.starts_with('JS.') { + name = 'JS.' + name + } + f.write(name) + f.mark_import_as_used(name) + if i != node.concrete_types.len - 1 { + f.write(', ') + } + } + f.write('>') + } +} + +pub fn (mut f Gen) call_args(args []ast.CallArg) { + f.single_line_fields = true + old_short_arg_state := f.use_short_fn_args + f.use_short_fn_args = false + defer { + f.single_line_fields = false + f.use_short_fn_args = old_short_arg_state + } + for i, arg in args { + if i == args.len - 1 && arg.expr is ast.StructInit { + if arg.expr.typ == ast.void_type { + f.use_short_fn_args = true + } + } + if arg.is_mut { + f.write(arg.share.str() + ' ') + } + if i > 0 && !f.single_line_if { + f.wrap_long_line(3, true) + } + f.expr(arg.expr) + if i < args.len - 1 { + f.write(', ') + } + } +} + +pub fn (mut f Gen) cast_expr(node ast.CastExpr) { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias) + '(') + f.mark_types_import_as_used(node.typ) + f.expr(node.expr) + if node.has_arg { + f.write(', ') + f.expr(node.arg) + } + f.write(')') +} + +pub fn (mut f Gen) chan_init(mut node ast.ChanInit) { + info := f.table.sym(node.typ).chan_info() + if node.elem_type == 0 && node.typ > 0 { + node.elem_type = info.elem_type + } + is_mut := info.is_mut + el_typ := if is_mut { + node.elem_type.set_nr_muls(node.elem_type.nr_muls() - 1) + } else { + node.elem_type + } + f.write('chan ') + if is_mut { + f.write('mut ') + } + f.write(f.table.type_to_str_using_aliases(el_typ, f.mod2alias)) + f.write('{') + if node.has_cap { + f.write('cap: ') + f.expr(node.cap_expr) + } + f.write('}') +} + +pub fn (mut f Gen) comptime_call(node ast.ComptimeCall) { + if node.is_vweb { + if node.method_name == 'html' { + f.write('\$vweb.html()') + } else { + f.write("\$tmpl('$node.args_var')") + } + } else { + if node.is_embed { + if node.embed_file.compression_type == 'none' { + f.write("\$embed_file('$node.embed_file.rpath')") + } else { + f.write("\$embed_file('$node.embed_file.rpath', .$node.embed_file.compression_type)") + } + } else if node.is_env { + f.write("\$env('$node.args_var')") + } else if node.is_pkgconfig { + f.write("\$pkgconfig('$node.args_var')") + } else { + inner_args := if node.args_var != '' { + node.args_var + } else { + node.args.map(it.str()).join(', ') + } + method_expr := if node.has_parens { + '(${node.method_name}($inner_args))' + } else { + '${node.method_name}($inner_args)' + } + f.write('${node.left}.$$method_expr') + } + } +} + +pub fn (mut f Gen) comptime_selector(node ast.ComptimeSelector) { + f.write('${node.left}.\$($node.field_expr)') +} + +pub fn (mut f Gen) concat_expr(node ast.ConcatExpr) { + for i, val in node.vals { + if i != 0 { + f.write(', ') + } + f.expr(val) + } +} + +pub fn (mut f Gen) dump_expr(node ast.DumpExpr) { + f.write('dump(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Gen) enum_val(node ast.EnumVal) { + name := f.short_module(node.enum_name) + f.write(name + '.' + node.val) + f.mark_import_as_used(name) +} + +pub fn (mut f Gen) ident(node ast.Ident) { + if node.info is ast.IdentVar { + if node.info.is_mut { + f.write(node.info.share.str() + ' ') + } + var_info := node.var_info() + if var_info.is_static { + f.write('static ') + } + if var_info.is_volatile { + f.write('volatile ') + } + } + f.write_language_prefix(node.language) + if node.name == 'it' && f.it_name != '' && f.in_lambda_depth == 0 { // allow `it` in lambdas + f.write(f.it_name) + } else if node.kind == .blank_ident { + f.write('_') + } else { + mut is_local := false + if !isnil(f.fn_scope) { + if _ := f.fn_scope.find_var(node.name) { + is_local = true + } + } + name := f.short_module(node.name) + f.write(name) + f.mark_import_as_used(name) + } +} + +pub fn (mut f Gen) if_expr(node ast.IfExpr) { + dollar := if node.is_comptime { '$' } else { '' } + mut is_ternary := node.branches.len == 2 && node.has_else + && branch_is_single_line(node.branches[0]) && branch_is_single_line(node.branches[1]) + && (node.is_expr || f.is_assign || f.is_struct_init || f.single_line_fields) + f.single_line_if = is_ternary + start_pos := f.out.len + start_len := f.line_len + for { + for i, branch in node.branches { + if i == 0 { + // first `if` + f.comments(branch.comments) + } else { + // `else`, close previous branch + if branch.comments.len > 0 { + f.writeln('}') + f.comments(branch.comments) + } else { + f.write('} ') + } + f.write('${dollar}else ') + } + if i < node.branches.len - 1 || !node.has_else { + f.write('${dollar}if ') + cur_pos := f.out.len + f.expr(branch.cond) + cond_len := f.out.len - cur_pos + is_cond_wrapped := cond_len > 0 && branch.cond in [ast.IfGuardExpr, ast.CallExpr] + && f.out.last_n(cond_len).contains('\n') + if is_cond_wrapped { + f.writeln('') + } else { + f.write(' ') + } + } + f.write('{') + if is_ternary { + f.write(' ') + } else { + f.writeln('') + } + f.stmts(branch.stmts) + if is_ternary { + f.write(' ') + } + } + break + } + f.write('}') + f.single_line_if = false + if node.post_comments.len > 0 { + f.writeln('') + f.comments(node.post_comments, + has_nl: false + prev_line: node.branches.last().body_pos.last_line + ) + } +} + +fn branch_is_single_line(b ast.IfBranch) bool { + if b.stmts.len == 1 && b.comments.len == 0 && stmt_is_single_line(b.stmts[0]) + && b.pos.line_nr == b.stmts[0].pos.line_nr { + return true + } + return false +} + +pub fn (mut f Gen) if_guard_expr(node ast.IfGuardExpr) { + for i, var in node.vars { + if var.is_mut { + f.write('mut ') + } + f.write(var.name) + if i != node.vars.len - 1 { + f.write(', ') + } + } + f.write(' := ') + f.expr(node.expr) +} + +pub fn (mut f Gen) index_expr(node ast.IndexExpr) { + f.expr(node.left) + if node.index is ast.RangeExpr { + if node.index.is_gated { + f.write('#') + } + } + f.write('[') + f.expr(node.index) + f.write(']') + if node.or_expr.kind != .absent { + f.or_expr(node.or_expr) + } +} + +pub fn (mut f Gen) infix_expr(node ast.InfixExpr) { + buffering_save := f.buffering + if !f.buffering && node.op in [.logical_or, .and, .plus] { + f.buffering = true + } + is_assign_save := f.is_assign + if node.op == .left_shift { + f.is_assign = true // To write ternary if on a single line + } + start_pos := f.out.len + start_len := f.line_len + f.expr(node.left) + is_one_val_array_init := node.op in [.key_in, .not_in] && node.right is ast.ArrayInit + && (node.right as ast.ArrayInit).exprs.len == 1 + if is_one_val_array_init { + // `var in [val]` => `var == val` + op := if node.op == .key_in { ' == ' } else { ' != ' } + f.write(op) + } else { + f.write(' $node.op.str() ') + } + if is_one_val_array_init { + // `var in [val]` => `var == val` + f.expr((node.right as ast.ArrayInit).exprs[0]) + } else { + f.expr(node.right) + } + if !buffering_save && f.buffering { + f.buffering = false + } + f.is_assign = is_assign_save + f.or_expr(node.or_block) +} + +pub fn (mut f Gen) wrap_infix(start_pos int, start_len int, is_cond bool) { + cut_span := f.out.len - start_pos + infix_str := f.out.cut_last(cut_span) + if !infix_str.contains_any_substr(['&&', '||', '+']) { + f.write(infix_str) + return + } + f.line_len = start_len + if start_len == 0 { + f.empty_line = true + } + conditions, penalties := split_up_infix(infix_str, false, is_cond) + f.write_splitted_infix(conditions, penalties, false, is_cond) +} + +fn split_up_infix(infix_str string, ignore_paren bool, is_cond_infix bool) ([]string, []int) { + mut conditions := [''] + mut penalties := [5] + or_pen := if infix_str.contains('&&') { 3 } else { 5 } + parts := infix_str.split(' ') + mut inside_paren := false + mut ind := 0 + for p in parts { + if is_cond_infix && p in ['&&', '||'] { + if inside_paren { + conditions[ind] += '$p ' + } else { + pen := if p == '||' { or_pen } else { 5 } + penalties << pen + conditions << '$p ' + ind++ + } + } else if !is_cond_infix && p == '+' { + penalties << 5 + conditions[ind] += '$p ' + conditions << '' + ind++ + } else { + conditions[ind] += '$p ' + if ignore_paren { + continue + } + if p.starts_with('(') { + inside_paren = true + } else if p.ends_with(')') { + inside_paren = false + } + } + } + return conditions, penalties +} + +const wsinfix_depth_max = 10 + +fn (mut f Gen) write_splitted_infix(conditions []string, penalties []int, ignore_paren bool, is_cond bool) { + f.wsinfix_depth++ + defer { + f.wsinfix_depth-- + } + for i, cnd in conditions { + c := cnd.trim_space() + is_paren_expr := (c[0] == `(` || (c.len > 5 && c[3] == `(`)) && c.ends_with(')') + final_len := ((f.indent + 1) * 4) + c.len + if i == 0 { + f.remove_new_line() + } + f.writeln('') + f.indent++ + f.write(c) + f.indent-- + } +} + +pub fn (mut f Gen) likely(node ast.Likely) { + if node.is_likely { + f.write('_likely_') + } else { + f.write('_unlikely_') + } + f.write('(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Gen) lock_expr(node ast.LockExpr) { + mut num_locked := 0 + mut num_rlocked := 0 + for is_rlock in node.is_rlock { + if is_rlock { + num_rlocked++ + } else { + num_locked++ + } + } + if num_locked > 0 || num_rlocked == 0 { + f.write('lock ') + mut n := 0 + for i, v in node.lockeds { + if !node.is_rlock[i] { + if n > 0 { + f.write(', ') + } + f.expr(v) + n++ + } + } + } + if num_rlocked > 0 { + if num_locked > 0 { + f.write('; ') + } + f.write('rlock ') + mut n := 0 + for i, v in node.lockeds { + if node.is_rlock[i] { + if n > 0 { + f.write(', ') + } + f.expr(v) + n++ + } + } + } + f.writeln(' {') + f.stmts(node.stmts) + f.write('}') +} + +pub fn (mut f Gen) map_init(node ast.MapInit) { + if node.keys.len == 0 { + if node.typ > ast.void_type { + sym := f.table.sym(node.typ) + info := sym.info as ast.Map + f.mark_types_import_as_used(info.key_type) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } + if node.pos.line_nr == node.pos.last_line { + f.write('{}') + } else { + f.writeln('{') + f.comments(node.pre_cmnts, level: .indent) + f.write('}') + } + return + } + f.writeln('{') + f.indent++ + f.comments(node.pre_cmnts) + mut max_field_len := 0 + mut skeys := []string{} + for key in node.keys { + skey := f.node_str(key).trim_space() + skeys << skey + if skey.len > max_field_len { + max_field_len = skey.len + } + } + for i, key in node.keys { + skey := skeys[i] + f.write(skey) + f.write(': ') + f.write(strings.repeat(` `, max_field_len - skey.len)) + f.expr(node.vals[i]) + if key is ast.EnumVal && skey.starts_with('.') { + // enforce the use of `,` for maps with short enum keys, otherwise there is ambiguity + // when the values are struct values, and the code will no longer parse properly + f.write(',') + } + f.comments(node.comments[i], prev_line: node.vals[i].pos().last_line, has_nl: false) + f.writeln('') + } + f.indent-- + f.write('}') +} + +fn (mut f Gen) match_branch(branch ast.MatchBranch, single_line bool) { + if !branch.is_else { + // normal branch + f.is_mbranch_expr = true + for j, expr in branch.exprs { + estr := f.node_str(expr).trim_space() + f.write(estr) + if j < branch.ecmnts.len && branch.ecmnts[j].len > 0 { + f.write(' ') + f.comments(branch.ecmnts[j], iembed: true) + } + if j < branch.exprs.len - 1 { + f.write(', ') + } + } + f.is_mbranch_expr = false + } else { + // else branch + f.write('else') + } + if branch.stmts.len == 0 { + f.writeln(' {}') + } else { + if single_line { + f.write(' { ') + } else { + f.writeln(' {') + } + f.stmts(branch.stmts) + if single_line { + f.remove_new_line() + f.writeln(' }') + } else { + f.writeln('}') + } + } + f.comments(branch.post_comments, inline: true) +} + +pub fn (mut f Gen) match_expr(node ast.MatchExpr) { + f.write('match ') + f.expr(node.cond) + if node.cond is ast.Ident { + f.it_name = node.cond.name + } + f.writeln(' {') + f.indent++ + f.comments(node.comments) + mut single_line := true + for branch in node.branches { + if branch.stmts.len > 1 || branch.pos.line_nr < branch.pos.last_line { + single_line = false + break + } + if branch.stmts.len == 0 { + continue + } + if !stmt_is_single_line(branch.stmts[0]) { + single_line = false + break + } + } + mut else_idx := -1 + for i, branch in node.branches { + if branch.is_else { + else_idx = i + continue + } + f.match_branch(branch, single_line) + } + if else_idx >= 0 { + f.match_branch(node.branches[else_idx], single_line) + } + f.indent-- + f.write('}') + f.it_name = '' +} + +pub fn (mut f Gen) offset_of(node ast.OffsetOf) { + f.write('__offsetof(${f.table.type_to_str_using_aliases(node.struct_type, f.mod2alias)}, $node.field)') + f.mark_types_import_as_used(node.struct_type) +} + +pub fn (mut f Gen) or_expr(node ast.OrExpr) { + match node.kind { + .absent {} + .block { + if node.stmts.len == 0 { + f.write(' or {') + if node.pos.line_nr != node.pos.last_line { + f.writeln('') + } + f.write('}') + return + } else if node.stmts.len == 1 && stmt_is_single_line(node.stmts[0]) { + // the control stmts (return/break/continue...) print a newline inside them, + // so, since this'll all be on one line, trim any possible whitespace + str := f.node_str(node.stmts[0]).trim_space() + single_line := ' or { $str }' + } + // Make it multiline if the blocks has at least two stmts + // or a single line would be too long + f.writeln(' or {') + f.stmts(node.stmts) + f.write('}') + } + .propagate_option { + f.write('?') + } + .propagate_result { + f.write('!') + } + } +} + +pub fn (mut f Gen) par_expr(node ast.ParExpr) { + requires_paren := node.expr !is ast.Ident + if requires_paren { + f.par_level++ + f.write('(') + } + mut expr := node.expr + for mut expr is ast.ParExpr { + expr = expr.expr + } + f.expr(expr) + if requires_paren { + f.par_level-- + f.write(')') + } +} + +pub fn (mut f Gen) postfix_expr(node ast.PostfixExpr) { + f.expr(node.expr) + // `$if foo ?` + if node.op == .question { + f.write(' ?') + } else { + f.write('$node.op') + } + if node.is_c2v_prefix { + f.write('$') + } +} + +pub fn (mut f Gen) prefix_expr(node ast.PrefixExpr) { + // !(a in b) => a !in b, !(a is b) => a !is b + if node.op == .not && node.right is ast.ParExpr { + if node.right.expr is ast.InfixExpr { + if node.right.expr.op in [.key_in, .not_in, .key_is, .not_is] + && node.right.expr.right !is ast.InfixExpr { + f.expr(node.right.expr.left) + if node.right.expr.op == .key_in { + f.write(' !in ') + } else if node.right.expr.op == .not_in { + f.write(' in ') + } else if node.right.expr.op == .key_is { + f.write(' !is ') + } else if node.right.expr.op == .not_is { + f.write(' is ') + } + f.expr(node.right.expr.right) + return + } + } + } + f.write(node.op.str()) + f.expr(node.right) + f.or_expr(node.or_block) +} + +pub fn (mut f Gen) range_expr(node ast.RangeExpr) { + f.expr(node.low) + if f.is_mbranch_expr { + f.write('...') + } else { + f.write('..') + } + f.expr(node.high) +} + +pub fn (mut f Gen) select_expr(node ast.SelectExpr) { + f.writeln('select {') + f.indent++ + for branch in node.branches { + if branch.comment.text != '' { + f.comment(branch.comment, inline: true) + f.writeln('') + } + if branch.is_else { + f.write('else {') + } else { + f.single_line_if = true + match branch.stmt { + ast.ExprStmt { f.expr(branch.stmt.expr) } + else { f.stmt(branch.stmt) } + } + f.single_line_if = false + f.write(' {') + } + if branch.stmts.len > 0 { + f.writeln('') + f.stmts(branch.stmts) + } + f.writeln('}') + if branch.post_comments.len > 0 { + f.comments(branch.post_comments, inline: true) + } + } + f.indent-- + f.write('}') +} + +pub fn (mut f Gen) selector_expr(node ast.SelectorExpr) { + f.expr(node.expr) + f.write('.') + f.write(node.field_name) +} + +pub fn (mut f Gen) size_of(node ast.SizeOf) { + f.write('sizeof(') + if node.is_type { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } else { + f.expr(node.expr) + } + f.write(')') +} + +pub fn (mut f Gen) is_ref_type(node ast.IsRefType) { + f.write('isreftype(') + if node.is_type { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } else { + f.expr(node.expr) + } + f.write(')') +} + +pub fn (mut f Gen) sql_expr(node ast.SqlExpr) { + // sql app.db { select from Contributor where repo == id && user == 0 } + f.write('sql ') + f.expr(node.db_expr) + f.writeln(' {') + f.write('\tselect ') + table_name := util.strip_mod_name(f.table.sym(node.table_expr.typ).name) + if node.is_count { + f.write('count ') + } else { + for i, fd in node.fields { + f.write(fd.name) + if i < node.fields.len - 1 { + f.write(', ') + } + } + } + f.write('from $table_name') + if node.has_where { + f.write(' where ') + f.expr(node.where_expr) + } + if node.has_order { + f.write(' order by ') + f.expr(node.order_expr) + if node.has_desc { + f.write(' desc') + } + } + if node.has_limit { + f.write(' limit ') + f.expr(node.limit_expr) + } + if node.has_offset { + f.write(' offset ') + f.expr(node.offset_expr) + } + f.writeln('') + f.write('}') +} + +pub fn (mut f Gen) char_literal(node ast.CharLiteral) { + if node.val == r"\'" { + f.write("`'`") + return + } + if node.val.len == 1 { + clit := node.val[0] + if clit < 32 || clit > 127 || clit == 92 || clit == 96 { + f.write('`\\x$clit.hex()`') + return + } + } + f.write('`$node.val`') +} + +pub fn (mut f Gen) string_literal(node ast.StringLiteral) { + use_double_quote := true + if node.is_raw { + f.write('r') + } else if node.language == ast.Language.c { + f.write('c') + } + if node.is_raw { + if use_double_quote { + f.write('"$node.val"') + } else { + f.write("'$node.val'") + } + } else { + unescaped_val := node.val.replace('$golang.bs$golang.bs', '\x01').replace_each([ + "$golang.bs'", + "'", + '$golang.bs"', + '"', + ]) + if use_double_quote { + s := unescaped_val.replace_each(['\x01', '$golang.bs$golang.bs', '"', '$golang.bs"']) + f.write('"$s"') + } else { + s := unescaped_val.replace_each(['\x01', '$golang.bs$golang.bs', "'", "$golang.bs'"]) + f.write("'$s'") + } + } +} + +pub fn (mut f Gen) string_inter_literal(node ast.StringInterLiteral) { + mut quote := "'" + for val in node.vals { + if val.contains('\\"') { + quote = '"' + break + } + if val.contains("\\'") { + quote = "'" + break + } + if val.contains('"') { + quote = "'" + } + if val.contains("'") { + quote = '"' + } + } + // TODO: this code is very similar to ast.Expr.str() + // serkonda7: it can not fully be replaced tho as ´f.expr()´ and `ast.Expr.str()` + // work too different for the various exprs that are interpolated + f.write(quote) + for i, val in node.vals { + f.write(val) + if i >= node.exprs.len { + break + } + f.write('$') + fspec_str, needs_braces := node.get_fspec_braces(i) + if needs_braces { + f.write('{') + f.expr(node.exprs[i]) + f.write(fspec_str) + f.write('}') + } else { + f.expr(node.exprs[i]) + } + } + f.write(quote) +} + +pub fn (mut f Gen) type_expr(node ast.TypeNode) { + f.mark_types_import_as_used(node.typ) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) +} + +pub fn (mut f Gen) type_of(node ast.TypeOf) { + f.write('typeof(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Gen) unsafe_expr(node ast.UnsafeExpr) { + single_line := node.pos.line_nr >= node.pos.last_line + f.write('unsafe {') + if single_line { + f.write(' ') + } else { + f.writeln('') + f.indent++ + f.empty_line = true + } + f.expr(node.expr) + if single_line { + f.write(' ') + } else { + f.writeln('') + f.indent-- + } + f.write('}') +} + +fn (mut f Gen) trace(fbase string, message string) { + // if f.file.path_base == fbase { + // println('> f.trace | ${fbase:-10s} | $message') + //} +} + +pub fn (mut g Gen) error(s string) { + util.verror('golang backend error', s) +} diff --git a/vlib/v/gen/golang/struct.v b/vlib/v/gen/golang/struct.v new file mode 100644 index 000000000..2376c4389 --- /dev/null +++ b/vlib/v/gen/golang/struct.v @@ -0,0 +1,283 @@ +// Copyright (c) 2019-2022 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 golang + +import strings +import v.ast +import v.mathutil as mu + +pub fn (mut f Gen) struct_decl(node ast.StructDecl) { + f.attrs(node.attrs) + f.write('type ') + if node.is_pub { + f.write('pub ') + } + f.write_language_prefix(node.language) + name := node.name.after('.') // strip prepended module + f.write(name) + if node.is_union { + f.write('union ') + } else { + f.write(' struct ') + } + f.write_generic_types(node.generic_types) + if node.fields.len == 0 && node.embeds.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln(' {}') + return + } + mut field_aligns := []AlignInfo{} + mut comment_aligns := []AlignInfo{} + mut default_expr_aligns := []AlignInfo{} + mut field_types := []string{cap: node.fields.len} + for i, field in node.fields { + ft := f.no_cur_mod(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) + field_types << ft + attrs_len := inline_attrs_len(field.attrs) + end_pos := field.pos.pos + field.pos.len + mut comments_len := 0 // Length of comments between field name and type + // field_aligns.add_info(comments_len + field.name.len, ft.len, field.pos.line_nr) + if field.has_default_expr { + // default_expr_aligns.add_info(attrs_len, field_types[i].len, field.pos.line_nr, + // use_threshold: true) + } + } + f.writeln(' {') + for embed in node.embeds { + f.mark_types_import_as_used(embed.typ) + styp := f.table.type_to_str_using_aliases(embed.typ, f.mod2alias) + + pre_comments := embed.comments.filter(it.pos.pos < embed.pos.pos) + comments := embed.comments[pre_comments.len..] + + f.comments_before_field(pre_comments) + if comments.len == 0 { + f.writeln('\t$styp') + } else { + f.write('\t$styp') + f.comments(comments, level: .indent) + } + } + mut field_align_i := 0 + mut comment_align_i := 0 + mut default_expr_align_i := 0 + mut inc_indent := false // for correct indents with multi line default exprs + for i, field in node.fields { + if i == node.mut_pos { + f.writeln('mut:') + } else if i == node.pub_pos { + f.writeln('pub:') + } else if i == node.pub_mut_pos { + f.writeln('pub mut:') + } else if i == node.global_pos { + f.writeln('__global:') + } else if i == node.module_pos { + f.writeln('module:') + } else if i > 0 { + // keep one empty line between fields (exclude one after mut:, pub:, ...) + mut before_last_line := node.fields[i - 1].pos.line_nr + if node.fields[i - 1].comments.len > 0 { + before_last_line = mu.max(before_last_line, node.fields[i - 1].comments.last().pos.last_line) + } + if node.fields[i - 1].has_default_expr { + before_last_line = mu.max(before_last_line, node.fields[i - 1].default_expr.pos().last_line) + } + + mut next_first_line := field.pos.line_nr + if field.comments.len > 0 { + next_first_line = mu.min(next_first_line, field.comments[0].pos.line_nr) + } + if next_first_line - before_last_line > 1 { + f.writeln('') + } + } + end_pos := field.pos.pos + field.pos.len + before_comments := field.comments.filter(it.pos.pos < field.pos.pos) + between_comments := field.comments[before_comments.len..].filter(it.pos.pos < end_pos) + after_type_comments := field.comments[(before_comments.len + between_comments.len)..] + // Handle comments before the field + f.comments_before_field(before_comments) + volatile_prefix := if field.is_volatile { 'volatile ' } else { '' } + f.write('\t$volatile_prefix$field.name ') + // Handle comments between field name and type + before_len := f.line_len + f.comments(between_comments, iembed: true, has_nl: false) + comments_len := f.line_len - before_len + // mut field_align := field_aligns[field_align_i] + // if field_align.line_nr < field.pos.line_nr { + // field_align_i++ + // field_align = field_aligns[field_align_i] + //} + // f.write(strings.repeat(` `, field_align.max_len - field.name.len - comments_len)) + f.write(field_types[i]) + f.mark_types_import_as_used(field.typ) + attrs_len := inline_attrs_len(field.attrs) + has_attrs := field.attrs.len > 0 + if has_attrs { + // f.write(strings.repeat(` `, field_align.max_type_len - field_types[i].len)) + f.single_line_attrs(field.attrs, inline: true) + } + if field.has_default_expr { + mut align := default_expr_aligns[default_expr_align_i] + if align.line_nr < field.pos.line_nr { + default_expr_align_i++ + align = default_expr_aligns[default_expr_align_i] + } + pad_len := align.max_len - attrs_len + align.max_type_len - field_types[i].len + f.write(strings.repeat(` `, pad_len)) + f.write(' = ') + if !expr_is_single_line(field.default_expr) { + f.indent++ + inc_indent = true + } + f.expr(field.default_expr) + if inc_indent { + f.indent-- + inc_indent = false + } + } + // Handle comments after field type + if after_type_comments.len > 0 { + if after_type_comments[0].pos.line_nr > field.pos.line_nr { + f.writeln('') + } else { + if !field.has_default_expr { + mut align := comment_aligns[comment_align_i] + if align.line_nr < field.pos.line_nr { + comment_align_i++ + align = comment_aligns[comment_align_i] + } + pad_len := align.max_len - attrs_len + align.max_type_len - field_types[i].len + f.write(strings.repeat(` `, pad_len)) + } + f.write(' ') + } + f.comments(after_type_comments, level: .indent) + } else { + f.writeln('') + } + } + f.comments_after_last_field(node.end_comments) + f.writeln('}\n') +} + +pub fn (mut f Gen) struct_init(node ast.StructInit) { + struct_init_save := f.is_struct_init + f.is_struct_init = true + defer { + f.is_struct_init = struct_init_save + } + + type_sym := f.table.sym(node.typ) + // f.write('') + mut name := type_sym.name + if !name.starts_with('C.') && !name.starts_with('JS.') { + name = f.no_cur_mod(f.short_module(type_sym.name)) // TODO f.type_to_str? + } + if name == 'void' { + name = '' + } + if node.fields.len == 0 && !node.has_update_expr { + // `Foo{}` on one line if there are no fields or comments + if node.pre_comments.len == 0 { + f.write('$name{}') + } else { + f.writeln('$name{') + f.comments(node.pre_comments, inline: true, has_nl: true, level: .indent) + f.write('}') + } + f.mark_import_as_used(name) + } else if node.is_short { + // `Foo{1,2,3}` (short syntax ) + f.write('$name{') + f.mark_import_as_used(name) + if node.has_update_expr { + f.write('...') + f.expr(node.update_expr) + f.write(', ') + } + for i, field in node.fields { + f.expr(field.expr) + if i < node.fields.len - 1 { + f.write(', ') + } + } + f.write('}') + } else { + use_short_args := f.use_short_fn_args && !node.has_update_expr + f.use_short_fn_args = false + mut single_line_fields := f.single_line_fields + f.single_line_fields = false + if node.pos.line_nr < node.pos.last_line || node.pre_comments.len > 0 { + single_line_fields = false + } + if !use_short_args { + f.write('$name{') + f.mark_import_as_used(name) + if single_line_fields { + f.write(' ') + } + } + fields_start := f.out.len + fields_loop: for { + if !single_line_fields { + if use_short_args && f.out[f.out.len - 1] == ` ` { + // v Remove space at tail of line + // f(a, b, c, \n + // f1: 0\n + // f2: 1\n + // ) + f.out.go_back(1) + } + f.writeln('') + f.indent++ + } + f.comments(node.pre_comments, inline: true, has_nl: true, level: .keep) + if node.has_update_expr { + f.write('...') + f.expr(node.update_expr) + if single_line_fields { + if node.fields.len > 0 { + f.write(', ') + } + } else { + f.writeln('') + } + f.comments(node.update_expr_comments, inline: true, has_nl: true, level: .keep) + } + for i, field in node.fields { + f.write('$field.name: ') + f.expr(field.expr) + f.comments(field.comments, inline: true, has_nl: false, level: .indent) + if single_line_fields { + if i < node.fields.len - 1 { + f.write(', ') + } + } else { + f.writeln('') + } + f.comments(field.next_comments, inline: false, has_nl: true, level: .keep) + if single_line_fields && (field.comments.len > 0 + || field.next_comments.len > 0 + || !expr_is_single_line(field.expr) + || f.line_len > max_len.last()) { + single_line_fields = false + f.out.go_back_to(fields_start) + f.line_len = fields_start + f.remove_new_line() + continue fields_loop + } + } + break + } + if !single_line_fields { + f.indent-- + } + if !use_short_args { + if single_line_fields { + f.write(' ') + } + f.write('}') + } + } +} diff --git a/vlib/v/gen/golang/tests/simple.vv b/vlib/v/gen/golang/tests/simple.vv new file mode 100644 index 000000000..32ebe09e0 --- /dev/null +++ b/vlib/v/gen/golang/tests/simple.vv @@ -0,0 +1,43 @@ +module main + +#import "fmt" + +//fn println(x any){ +fn println(x string){ +#fmt.Println(x) +} + +struct Foo { + x int + s string +} + +fn is_prime(x int) bool { + for i := 2; i <= x/2; i++ { + if x % i == 0 { + return false + } + } + + return true +} + +fn main() { + x := 10 + if x % 2 == 0 { + println('even') + } + //println(x.str()) + println('hello world!') + + foo := Foo{} + q := foo.x + _ = q + w := q>2 && 3