v2 / vlib / v / fmt / fmt.v
3662 lines · 3493 sloc · 88.41 KB · 592deda7fd378512345e74a19956f468ced8ef82
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module fmt
5
6import os
7import strings
8import v.ast
9import v.util
10import v.pref
11
12const break_points = [0, 35, 60, 85, 93, 100]! // when to break a line depending on the penalty
13const max_len = break_points[break_points.len - 1]
14const bs = '\\'
15
16fn call_arg_spread_str(arg ast.CallArg) string {
17 return match arg.expr {
18 ast.ArrayDecompose {
19 decompose := arg.expr as ast.ArrayDecompose
20 '...${decompose.expr.str()}'
21 }
22 else {
23 arg.str()
24 }
25 }
26}
27
28@[minify]
29pub struct Fmt {
30pub:
31 pref &pref.Preferences = unsafe { nil }
32pub mut:
33 file ast.File
34 table &ast.Table = unsafe { nil }
35 is_debug bool
36 out strings.Builder
37 indent int
38 empty_line bool
39 line_len int // the current line length, Note: it counts \t as 4 spaces, and starts at 0 after f.writeln
40 buffering bool // disables line wrapping for exprs that will be analyzed later
41 par_level int // how many parentheses are put around the current expression
42 array_init_break []bool // line breaks after elements in hierarchy level of multi dimensional array
43 array_init_depth int // current level of hierarchy in array init
44 single_line_if bool
45 cur_mod string
46 import_pos int // position of the last import in the resulting string
47 mod2alias map[string]string // for `import time as t`, will contain: 'time'=>'t'
48 mod2syms map[string]string // import time { now } 'time.now'=>'now'
49 implied_import_str string // ​imports that the user's code uses but omitted to import explicitly
50 processed_imports []string
51 has_import_stmt bool
52 use_short_fn_args bool
53 single_line_fields bool // should struct fields be on a single line
54 in_lambda_depth int
55 inside_const bool
56 inside_unsafe bool
57 inside_comptime_if bool
58 is_assign bool
59 is_index_expr bool
60 is_mbranch_expr bool // match a { x...y { } }
61 is_struct_init bool
62 is_array_init bool
63 fn_scope &ast.Scope = unsafe { nil }
64 wsinfix_depth int
65 format_state FormatState
66 source_text string // can be set by `echo "println('hi')" | v fmt`, i.e. when processing source not from a file, but from stdin. In this case, it will contain the entire input text. You can use f.file.path otherwise, and read from that file.
67 global_processed_imports []string
68 branch_processed_imports []string
69 is_translated_module bool // @[translated]
70 is_c_function bool // C.func(...)
71}
72
73@[params]
74pub struct FmtOptions {
75pub:
76 source_text string
77}
78
79pub fn fmt(file ast.File, mut table ast.Table, pref_ &pref.Preferences, is_debug bool, options FmtOptions) string {
80 mut f := Fmt{
81 file: file
82 table: table
83 pref: pref_
84 is_debug: is_debug
85 out: strings.new_builder(1000)
86 }
87 f.source_text = options.source_text
88 f.process_file_imports(file)
89 // Compensate for indent increase of toplevel stmts done in `f.stmts()`.
90 f.indent--
91 f.stmts(file.stmts)
92 f.indent++
93 res := f.out.str().trim_space() + '\n'
94
95 // `implied_imports` should append to end of `import` block
96 if res.len == 1 {
97 return f.implied_import_str + '\n'
98 }
99 if res.len <= f.import_pos {
100 if f.implied_import_str.len > 0 {
101 return res + '\n' + f.implied_import_str + '\n'
102 }
103 return res
104 }
105 mut import_start_pos := f.import_pos
106 if f.import_pos == 0 && file.stmts.len > 1 {
107 // Check shebang.
108 stmt := file.stmts[1]
109 if stmt is ast.ExprStmt && stmt.expr is ast.Comment
110 && (stmt.expr as ast.Comment).text.starts_with('#!') {
111 import_start_pos = stmt.pos.len
112 }
113 }
114 if f.has_import_stmt || f.implied_import_str.len == 0 {
115 return res[..import_start_pos] + f.implied_import_str + res[import_start_pos..]
116 } else {
117 return res[..import_start_pos] + f.implied_import_str + '\n' + res[import_start_pos..]
118 }
119}
120
121/*
122// vfmt has a special type_to_str which calls Table.type_to_str, but does extra work.
123// Having it here and not in Table saves cpu cycles when not running the compiler in vfmt mode.
124pub fn (f &Fmt) type_to_str_using_aliases(typ ast.Type, import_aliases map[string]string) string {
125 mut s := f.type_to_str_using_aliases(typ, import_aliases)
126 if s.contains('Result') {
127 println('${s}')
128 }
129 return s
130}
131
132pub fn (f &Fmt) type_to_str(typ ast.Type) string {
133 return f.type_to_str(typ)
134}
135*/
136fn (f &Fmt) type_to_str_using_aliases(typ ast.Type, import_aliases map[string]string) string {
137 if f.table.new_int && typ == ast.int_type && (f.is_translated_module || f.is_c_function) {
138 return f.type_to_str_using_aliases(ast.i32_type, import_aliases)
139 }
140 return f.table.type_to_str_using_aliases(typ, import_aliases)
141}
142
143fn (f &Fmt) type_to_str(typ ast.Type) string {
144 if f.table.new_int && typ == ast.int_type && (f.is_translated_module || f.is_c_function) {
145 return 'i32'
146 }
147 return f.table.type_to_str(typ)
148}
149
150pub fn (mut f Fmt) process_file_imports(file &ast.File) {
151 mut sb := strings.new_builder(128)
152 for imp in file.implied_imports {
153 sb.writeln('import ${imp}')
154 }
155 f.implied_import_str = sb.str()
156
157 for imp in file.imports {
158 f.mod2alias[imp.mod] = imp.alias
159 f.mod2alias[imp.mod.all_after('${file.mod.name}.')] = imp.alias
160 for sym in imp.syms {
161 f.mod2alias['${imp.mod}.${sym.name}'] = sym.name
162 f.mod2alias['${imp.mod.all_after_last('.')}.${sym.name}'] = sym.name
163 f.mod2alias[sym.name] = sym.name
164 f.mod2syms['${imp.mod}.${sym.name}'] = sym.name
165 f.mod2syms['${imp.mod.all_after_last('.')}.${sym.name}'] = sym.name
166 f.mod2syms[sym.name] = sym.name
167 }
168 }
169}
170
171//=== Basic buffer write operations ===//
172
173pub fn (mut f Fmt) write(s string) {
174 if f.indent > 0 && f.empty_line {
175 f.write_indent()
176 }
177 f.out.write_string(s)
178 f.line_len += s.len
179 f.empty_line = false
180}
181
182pub fn (mut f Fmt) writeln(s string) {
183 if f.indent > 0 && f.empty_line && s != '' {
184 f.write_indent()
185 }
186 f.out.writeln(s)
187 f.empty_line = true
188 f.line_len = 0
189}
190
191fn (mut f Fmt) write_indent() {
192 f.out.write_string(util.tabs(f.indent))
193 f.line_len += f.indent * 4
194}
195
196pub fn (mut f Fmt) wrap_long_line(penalty_idx int, add_indent bool) bool {
197 if f.buffering {
198 return false
199 }
200 if penalty_idx > 0 && f.line_len <= break_points[penalty_idx] {
201 return false
202 }
203 if f.out.last() == ` ` {
204 f.out.go_back(1)
205 }
206 f.write('\n')
207 f.line_len = 0
208 if add_indent {
209 f.indent++
210 }
211 f.write_indent()
212 if add_indent {
213 f.indent--
214 }
215 return true
216}
217
218// When the removal action actually occurs, the string of the last line after the removal is returned
219pub fn (mut f Fmt) remove_new_line() string {
220 mut buffer := unsafe { &f.out }
221 mut i := 0
222 for i = buffer.len - 1; i >= 0; i-- {
223 if !buffer.byte_at(i).is_space() { // != `\n` {
224 break
225 }
226 }
227 if i == buffer.len - 1 {
228 return ''
229 }
230 buffer.go_back(buffer.len - i - 1)
231 f.empty_line = false
232 mut line_len := 0
233 mut last_line_str := []u8{}
234 for i = buffer.len - 1; i >= 0; i-- {
235 ch := buffer.byte_at(i)
236 if ch == `\n` {
237 break
238 }
239 line_len += if ch == `\t` { 4 } else { 1 }
240 last_line_str << ch
241 }
242 f.line_len = line_len
243 return last_line_str.reverse().bytestr()
244}
245
246//=== Specialized write methods ===//
247
248fn (mut f Fmt) write_language_prefix(lang ast.Language) {
249 match lang {
250 .c { f.write('C.') }
251 .js { f.write('JS.') }
252 .wasm { f.write('WASM.') }
253 else {}
254 }
255}
256
257fn (mut f Fmt) write_generic_types(gtypes []ast.Type) {
258 if gtypes.len > 0 {
259 f.write('[')
260 gtypes_string := gtypes.map(f.type_to_str(it)).join(', ')
261 f.write(gtypes_string)
262 f.write(']')
263 }
264}
265
266//=== Module handling helper methods ===//
267
268pub fn (mut f Fmt) set_current_module_name(cmodname string) {
269 f.cur_mod = cmodname
270 f.table.cmod_prefix = cmodname + '.'
271}
272
273fn (f &Fmt) get_modname_prefix(mname string) (string, string) {
274 // ./tests/proto_module_importing_vproto_keep.vv to know, why here is checked for ']' and '&'
275 if !mname.contains(']') && !mname.contains('&') {
276 return mname, ''
277 }
278 after_rbc := mname.all_after_last(']')
279 after_ref := mname.all_after_last('&')
280 modname := if after_rbc.len < after_ref.len { after_rbc } else { after_ref }
281 return modname, mname.trim_string_right(modname)
282}
283
284fn (mut f Fmt) is_external_name(name string) bool {
285 if name.len > 2 && name[0] == `C` && name[1] == `.` {
286 return true
287 }
288 if name.len > 3 && name[0] == `J` && name[1] == `S` && name[2] == `.` {
289 return true
290 }
291 return false
292}
293
294pub fn (mut f Fmt) no_cur_mod(typename string) string {
295 return util.no_cur_mod(typename, f.cur_mod)
296}
297
298// foo.bar.fn() => bar.fn()
299pub fn (mut f Fmt) short_module(name string) string {
300 if !name.contains('.') || name.starts_with('JS.') {
301 return name
302 }
303 if name in f.mod2syms {
304 return f.mod2syms[name]
305 }
306 if name.ends_with(']') {
307 generic_levels := name.trim_string_right(']').split('[')
308 mut res := '${f.short_module(generic_levels[0])}'
309 for i in 1 .. generic_levels.len {
310 genshorts := generic_levels[i].split(', ').map(f.short_module(it)).join(', ')
311 res += '[${genshorts}'
312 }
313 res += ']'
314 return res
315 }
316 vals := name.split('.')
317 if vals.len < 2 {
318 return name
319 }
320 idx := vals.len - 1
321 mname, tprefix := f.get_modname_prefix(vals[..idx].join('.'))
322 symname := vals.last()
323 mut aname := f.mod2alias[mname]
324 if aname == '' {
325 for _, v in f.mod2alias {
326 if v == mname {
327 aname = mname
328 break
329 }
330 }
331 }
332 if aname == '' {
333 return '${tprefix}${symname}'
334 }
335 return '${tprefix}${aname}.${symname}'
336}
337
338//=== Import-related methods ===//
339
340pub fn (mut f Fmt) import_stmt(imp ast.Import) {
341 f.has_import_stmt = true
342 if imp.mod in f.file.auto_imports && imp.mod !in f.file.used_imports {
343 // Skip hidden imports like preludes.
344 return
345 }
346 imp_stmt := f.imp_stmt_str(imp)
347 if imp_stmt in f.global_processed_imports
348 || (f.inside_comptime_if && imp_stmt in f.branch_processed_imports) {
349 // Skip duplicates.
350 f.import_comments(imp.next_comments)
351 return
352 }
353 if f.inside_comptime_if {
354 f.branch_processed_imports << imp_stmt
355 } else {
356 f.global_processed_imports << imp_stmt
357 }
358 if !f.format_state.is_vfmt_on {
359 original_imp_line :=
360 f.get_source_lines()#[imp.pos.line_nr..imp.pos.last_line + 1].join('\n')
361 // Same line comments(`imp.comments`) are included in the `original_imp_line`.
362 f.writeln(original_imp_line)
363 f.import_comments(imp.next_comments)
364 } else {
365 f.writeln('import ${imp_stmt}')
366 f.import_comments(imp.comments, same_line: true)
367 f.import_comments(imp.next_comments)
368 }
369 f.import_pos = f.out.len
370}
371
372pub fn (f &Fmt) imp_stmt_str(imp ast.Import) string {
373 // Format / remove unused selective import symbols
374 // E.g.: `import foo { Foo }` || `import foo as f { Foo }`
375 has_alias := imp.alias != imp.source_name.all_after_last('.')
376 mut suffix := if has_alias { ' as ${imp.alias}' } else { '' }
377 mut syms := imp.syms.map(it.name).filter(f.file.imported_symbols_used[it])
378 syms.sort()
379 if syms.len > 0 {
380 suffix += if imp.syms[0].pos.line_nr == imp.pos.line_nr {
381 ' { ' + syms.join(', ') + ' }'
382 } else {
383 ' {\n\t' + syms.join(',\n\t') + ',\n}'
384 }
385 }
386 return '${imp.source_name}${suffix}'
387}
388
389//=== Node helpers ===//
390
391fn (f &Fmt) should_insert_newline_before_node(node ast.Node, prev_node ast.Node) bool {
392 // No need to insert a newline if there is already one
393 if f.out.last_n(2) == '\n\n' {
394 return false
395 }
396 prev_line_nr := prev_node.pos().last_line
397 // The nodes are Stmts
398 if node is ast.Stmt && prev_node is ast.Stmt {
399 match prev_node {
400 // Force a newline after a block of HashStmts
401 ast.HashStmt {
402 if node !in [ast.HashStmt, ast.ExprStmt] {
403 return true
404 }
405 }
406 // Force a newline after function declarations
407 // The only exception is inside a block of no_body functions
408 ast.FnDecl {
409 if node !is ast.FnDecl || !prev_node.no_body {
410 return true
411 }
412 }
413 ast.SemicolonStmt {
414 return false
415 }
416 // Force a newline after struct declarations
417 ast.StructDecl {
418 return true
419 }
420 // Empty line after a block of type declarations
421 ast.TypeDecl {
422 if node !is ast.TypeDecl {
423 return true
424 }
425 }
426 // Force a newline after imports
427 ast.Import {
428 return node !is ast.Import
429 }
430 ast.ConstDecl {
431 mut is_comment_expr_stmt := false
432 if node is ast.ExprStmt {
433 expr_stmt := node
434 is_comment_expr_stmt = expr_stmt.expr is ast.Comment
435 }
436 if node !is ast.ConstDecl && !is_comment_expr_stmt {
437 return true
438 }
439 }
440 else {}
441 }
442
443 match node {
444 // Attributes are not respected in the stmts position, so this requires manual checking
445 ast.StructDecl, ast.EnumDecl, ast.FnDecl {
446 if node.attrs.len > 0 && node.attrs[0].pos.line_nr - prev_line_nr <= 1 {
447 return false
448 }
449 }
450 ast.Import {
451 return false
452 }
453 else {}
454 }
455 }
456 // The node shouldn't have a newline before
457 if node.pos().line_nr - prev_line_nr <= 1 {
458 return false
459 }
460 return true
461}
462
463pub fn (mut f Fmt) node_str(node ast.Node) string {
464 was_empty_line := f.empty_line
465 prev_line_len := f.line_len
466 pos := f.out.len
467 match node {
468 ast.Stmt { f.stmt(node) }
469 ast.Expr { f.expr(node) }
470 else { panic('´f.node_str()´ is not implemented for ${node}.') }
471 }
472
473 str := f.out.after(pos)
474 f.out.go_back_to(pos)
475 f.empty_line = was_empty_line
476 f.line_len = prev_line_len
477 return str
478}
479
480//=== General Stmt-related methods and helpers ===//
481
482pub fn (mut f Fmt) stmts(stmts []ast.Stmt) {
483 mut prev_stmt := ast.empty_stmt
484 f.indent++
485 for i, stmt in stmts {
486 if i > 0 && f.should_insert_newline_before_node(stmt, prev_stmt) {
487 f.out.writeln('')
488 }
489 f.stmt(stmt)
490 prev_stmt = stmt
491 }
492 f.indent--
493}
494
495pub fn (mut f Fmt) stmt(node ast.Stmt) {
496 if f.is_debug {
497 eprintln('stmt ${node.type_name():-20} | pos: ${node.pos.line_str()}')
498 }
499 match node {
500 ast.EmptyStmt, ast.NodeError {}
501 ast.AsmStmt {
502 f.asm_stmt(node)
503 }
504 ast.AssertStmt {
505 f.assert_stmt(node)
506 }
507 ast.AssignStmt {
508 f.assign_stmt(node)
509 }
510 ast.Block {
511 if node.is_unsafe {
512 f.inside_unsafe = true
513 f.block(node)
514 f.inside_unsafe = false
515 } else {
516 f.block(node)
517 }
518 }
519 ast.BranchStmt {
520 f.branch_stmt(node)
521 }
522 ast.ComptimeFor {
523 f.comptime_for(node)
524 }
525 ast.ConstDecl {
526 f.const_decl(node)
527 }
528 ast.DebuggerStmt {
529 f.debugger_stmt(node)
530 }
531 ast.DeferStmt {
532 f.defer_stmt(node)
533 }
534 ast.EnumDecl {
535 f.enum_decl(node)
536 }
537 ast.ExprStmt {
538 f.expr_stmt(node)
539 }
540 ast.FnDecl {
541 f.fn_decl(node)
542 }
543 ast.ForCStmt {
544 f.for_c_stmt(node)
545 }
546 ast.ForInStmt {
547 f.for_in_stmt(node)
548 }
549 ast.ForStmt {
550 f.for_stmt(node)
551 }
552 ast.GlobalDecl {
553 f.global_decl(node)
554 }
555 ast.GotoLabel {
556 f.goto_label(node)
557 }
558 ast.GotoStmt {
559 f.goto_stmt(node)
560 }
561 ast.HashStmt {
562 f.hash_stmt(node)
563 }
564 ast.Import {
565 f.import_stmt(node)
566 }
567 ast.InterfaceDecl {
568 f.interface_decl(node)
569 }
570 ast.Module {
571 f.module_stmt(node)
572 }
573 ast.Return {
574 f.return_stmt(node)
575 }
576 ast.SemicolonStmt {}
577 ast.SqlStmt {
578 f.sql_stmt(node)
579 }
580 ast.StructDecl {
581 f.struct_decl(node, false)
582 }
583 ast.TypeDecl {
584 f.type_decl(node)
585 }
586 }
587}
588
589fn stmt_is_single_line(stmt ast.Stmt) bool {
590 return match stmt {
591 ast.ExprStmt, ast.AssertStmt { expr_is_single_line(stmt.expr) }
592 ast.Return, ast.AssignStmt, ast.BranchStmt { true }
593 ast.SemicolonStmt { true }
594 else { false }
595 }
596}
597
598//=== General Expr-related methods and helpers ===//
599
600pub fn (mut f Fmt) expr(node_ ast.Expr) {
601 mut node := unsafe { node_ }
602 if f.is_debug {
603 eprintln('expr ${node.type_name():-20} | pos: ${node.pos().line_str()} | ${node.str()}')
604 }
605 match mut node {
606 ast.NodeError {}
607 ast.EmptyExpr {}
608 ast.AnonFn {
609 f.anon_fn(node)
610 }
611 ast.ArrayDecompose {
612 f.array_decompose(node)
613 }
614 ast.ArrayInit {
615 f.array_init(node)
616 }
617 ast.AsCast {
618 f.as_cast(node)
619 }
620 ast.Assoc {
621 f.assoc(node)
622 }
623 ast.AtExpr {
624 f.at_expr(node)
625 }
626 ast.BoolLiteral {
627 f.write(node.val.str())
628 }
629 ast.CallExpr {
630 f.call_expr(node)
631 }
632 ast.CastExpr {
633 f.cast_expr(node)
634 }
635 ast.ChanInit {
636 f.chan_init(mut node)
637 }
638 ast.CharLiteral {
639 f.char_literal(node)
640 }
641 ast.Comment {
642 f.comment(node, same_line: true)
643 }
644 ast.ComptimeCall {
645 f.comptime_call(node)
646 }
647 ast.ComptimeSelector {
648 f.comptime_selector(node)
649 }
650 ast.ConcatExpr {
651 f.concat_expr(node)
652 }
653 ast.CTempVar {
654 eprintln('ast.CTempVar of ${node.orig.str()} should be generated/used only in cgen')
655 }
656 ast.DumpExpr {
657 f.dump_expr(node)
658 }
659 ast.EnumVal {
660 f.enum_val(node)
661 }
662 ast.FloatLiteral {
663 f.write(node.val)
664 if node.val.ends_with('.') {
665 f.write('0')
666 }
667 }
668 ast.GoExpr {
669 f.go_expr(node)
670 }
671 ast.SpawnExpr {
672 f.spawn_expr(node)
673 }
674 ast.Ident {
675 f.ident(node)
676 }
677 ast.IfExpr {
678 f.if_expr(node)
679 }
680 ast.IfGuardExpr {
681 f.if_guard_expr(node)
682 }
683 ast.IndexExpr {
684 f.index_expr(node)
685 }
686 ast.InfixExpr {
687 f.infix_expr(node)
688 }
689 ast.IntegerLiteral {
690 f.write(node.val)
691 }
692 ast.LambdaExpr {
693 f.write('|')
694 for i, x in node.params {
695 f.expr(x)
696 if i < node.params.len - 1 {
697 f.write(', ')
698 }
699 }
700 f.write('| ')
701 f.expr(node.expr)
702 }
703 ast.Likely {
704 f.likely(node)
705 }
706 ast.LockExpr {
707 f.lock_expr(node)
708 }
709 ast.MapInit {
710 f.map_init(node)
711 }
712 ast.MatchExpr {
713 f.match_expr(node)
714 }
715 ast.None {
716 f.write('none')
717 }
718 ast.Nil {
719 f.write('nil')
720 }
721 ast.OffsetOf {
722 f.offset_of(node)
723 }
724 ast.OrExpr {
725 // shouldn't happen, an or expression is always linked to a call expr or index expr
726 panic('fmt: OrExpr should be linked to ast.CallExpr or ast.IndexExpr')
727 }
728 ast.ParExpr {
729 f.par_expr(node)
730 }
731 ast.PostfixExpr {
732 f.postfix_expr(node)
733 }
734 ast.PrefixExpr {
735 f.prefix_expr(node)
736 }
737 ast.RangeExpr {
738 f.range_expr(node)
739 }
740 ast.SelectExpr {
741 f.select_expr(node)
742 }
743 ast.SelectorExpr {
744 f.selector_expr(node)
745 }
746 ast.SizeOf {
747 f.size_of(node)
748 }
749 ast.IsRefType {
750 f.is_ref_type(node)
751 }
752 ast.SqlExpr {
753 f.sql_expr(node)
754 }
755 ast.SqlQueryDataExpr {
756 f.sql_query_data_expr(node)
757 }
758 ast.StringLiteral {
759 f.string_literal(node)
760 }
761 ast.StringInterLiteral {
762 f.string_inter_literal(node)
763 }
764 ast.StructInit {
765 f.struct_init(node)
766 }
767 ast.TypeNode {
768 f.type_expr(node)
769 }
770 ast.TypeOf {
771 f.type_of(node)
772 }
773 ast.UnsafeExpr {
774 f.inside_unsafe = true
775 f.unsafe_expr(node)
776 f.inside_unsafe = false
777 }
778 ast.ComptimeType {
779 match node.kind {
780 .unknown { f.write('\$unknown') }
781 .array { f.write('\$array') }
782 .array_dynamic { f.write('\$array_dynamic') }
783 .array_fixed { f.write('\$array_fixed') }
784 .struct { f.write('\$struct') }
785 .iface { f.write('\$interface') }
786 .map { f.write('\$map') }
787 .int { f.write('\$int') }
788 .float { f.write('\$float') }
789 .sum_type { f.write('\$sumtype') }
790 .enum { f.write('\$enum') }
791 .alias { f.write('\$alias') }
792 .function { f.write('\$function') }
793 .option { f.write('\$option') }
794 .shared { f.write('\$shared') }
795 .string { f.write('\$string') }
796 .pointer { f.write('\$pointer') }
797 .voidptr { f.write('\$voidptr') }
798 }
799 }
800 }
801}
802
803fn expr_is_single_line(expr ast.Expr) bool {
804 match expr {
805 ast.Comment, ast.IfExpr, ast.MapInit, ast.MatchExpr, ast.SqlQueryDataExpr {
806 return false
807 }
808 ast.AnonFn {
809 if !expr.decl.no_body {
810 return false
811 }
812 }
813 ast.StructInit {
814 if !expr.no_keys && (expr.init_fields.len > 0 || expr.pre_comments.len > 0) {
815 return false
816 }
817 }
818 ast.CallExpr {
819 if expr.or_block.stmts.len > 1 || expr.args.any(it.expr is ast.CallExpr
820 && it.expr.or_block.stmts.len > 1) {
821 return false
822 }
823 }
824 ast.ArrayInit {
825 for e in expr.exprs {
826 if !expr_is_single_line(e) {
827 return false
828 }
829 }
830 }
831 ast.ConcatExpr {
832 for e in expr.vals {
833 if !expr_is_single_line(e) {
834 return false
835 }
836 }
837 }
838 ast.StringLiteral {
839 return expr.pos.line_nr == expr.pos.last_line
840 }
841 ast.OrExpr {
842 if expr.stmts.len == 1 && stmt_is_single_line(expr.stmts[0]) {
843 stmt := expr.stmts[0]
844 if stmt is ast.ExprStmt && stmt.expr is ast.CallExpr
845 && (stmt.expr as ast.CallExpr).comments.len > 0 {
846 if comment := (stmt.expr as ast.CallExpr).comments[0] {
847 if !comment.is_multi {
848 return false
849 }
850 }
851 }
852 return true
853 }
854 return false
855 }
856 else {}
857 }
858
859 return true
860}
861
862fn (mut f Fmt) write_expr_list(exprs []ast.Expr) {
863 for i, expr in exprs {
864 f.expr(expr)
865 if i < exprs.len - 1 {
866 f.write(', ')
867 }
868 }
869}
870
871//=== Specific Stmt methods ===//
872
873pub fn (mut f Fmt) assert_stmt(node ast.AssertStmt) {
874 f.write('assert ')
875 mut expr := node.expr
876 expr = expr.remove_par()
877 f.expr(expr)
878 if node.extra !is ast.EmptyExpr {
879 f.write(', ')
880 f.expr(node.extra)
881 }
882 f.writeln('')
883}
884
885pub fn (mut f Fmt) assign_stmt(node ast.AssignStmt) {
886 for i, left in node.left {
887 f.expr(left)
888 if i < node.left.len - 1 {
889 f.write(', ')
890 }
891 }
892 f.is_assign = true
893 f.write(' ${node.op.str()} ')
894 right_start_pos := f.out.len
895 right_start_len := f.line_len
896 can_wrap_rhs := node.right.len == 1 && node.right[0] in [ast.CallExpr, ast.StructInit]
897 f.write_expr_list(node.right)
898 if can_wrap_rhs && !f.single_line_if && f.line_len > max_len {
899 right_str := f.out.after(right_start_pos)
900 if !right_str.contains('\n') {
901 f.out.go_back_to(right_start_pos)
902 f.line_len = right_start_len
903 if f.out.last() == ` ` {
904 f.out.go_back(1)
905 f.line_len--
906 }
907 f.writeln('')
908 f.indent++
909 f.write_expr_list(node.right)
910 f.indent--
911 }
912 }
913 if node.attr.name != '' {
914 f.write(' @[${node.attr.name}]')
915 }
916 f.comments(node.end_comments, has_nl: false, same_line: true, level: .keep)
917 if !f.single_line_if {
918 f.writeln('')
919 }
920 f.is_assign = false
921}
922
923pub fn (mut f Fmt) block(node ast.Block) {
924 if node.is_unsafe {
925 f.write('unsafe ')
926 }
927 f.write('{')
928 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
929 f.writeln('')
930 f.stmts(node.stmts)
931 }
932 f.writeln('}')
933}
934
935pub fn (mut f Fmt) debugger_stmt(node ast.DebuggerStmt) {
936 f.writeln('\$dbg;')
937}
938
939pub fn (mut f Fmt) branch_stmt(node ast.BranchStmt) {
940 f.writeln(node.str())
941}
942
943pub fn (mut f Fmt) comptime_for(node ast.ComptimeFor) {
944 f.write('\$for ${node.val_var} in ')
945 if node.typ != ast.void_type {
946 f.write(f.no_cur_mod(f.type_to_str_using_aliases(node.typ, f.mod2alias)))
947 } else {
948 f.expr(node.expr)
949 }
950 f.write('.${node.kind.str()} {')
951 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
952 f.writeln('')
953 f.stmts(node.stmts)
954 }
955 f.writeln('}')
956}
957
958pub fn (mut f Fmt) const_decl(node ast.ConstDecl) {
959 if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line {
960 // remove "const()"
961 return
962 }
963
964 f.attrs(node.attrs)
965 if !node.is_block {
966 if node.is_pub {
967 f.write('pub ')
968 }
969 }
970 f.inside_const = true
971 defer { f.inside_const = false }
972 if !node.is_block {
973 f.write('const ')
974 }
975 mut prev_field := if node.fields.len > 0 {
976 ast.Node(node.fields[0])
977 } else {
978 ast.Node(ast.NodeError{})
979 }
980 for fidx, field in node.fields {
981 if field.comments.len > 0 {
982 if f.should_insert_newline_before_node(ast.Expr(field.comments[0]), prev_field) {
983 f.writeln('')
984 }
985 f.comments(field.comments, same_line: true)
986 prev_field = ast.Expr(field.comments.last())
987 }
988 if node.is_block && f.should_insert_newline_before_node(field, prev_field) {
989 f.writeln('')
990 }
991 name := field.name.after('.')
992 if node.is_block {
993 // const() blocks are deprecated, prepend "const" before each value
994 if node.is_pub {
995 f.write('pub ')
996 }
997 f.write('const ')
998 }
999 if field.is_virtual_c {
1000 f.write('C.')
1001 }
1002 f.write('${name} ')
1003 if field.is_virtual_c {
1004 // f.typ(field.typ)
1005 f.write(f.type_to_str(field.typ))
1006 } else {
1007 f.write('= ')
1008 f.expr(field.expr)
1009 }
1010 f.comments(field.end_comments, same_line: true)
1011 if node.is_block && fidx < node.fields.len - 1 && node.fields.len > 1 {
1012 // old style grouped consts, converted to the new style ungrouped const
1013 f.writeln('')
1014 } else if node.end_comments.len > 0 {
1015 // Write out single line comments after const expr if present
1016 // E.g.: `const x = 1 // <comment>`
1017 if node.end_comments[0].text.contains('\n') {
1018 f.writeln('\n')
1019 }
1020 f.comments(node.end_comments, same_line: true, has_nl: false)
1021 }
1022 prev_field = field
1023 }
1024
1025 f.writeln('')
1026}
1027
1028fn (mut f Fmt) defer_stmt(node ast.DeferStmt) {
1029 f.write('defer')
1030 if node.mode == .function {
1031 f.write('(fn)')
1032 }
1033 if node.stmts.len == 0 {
1034 f.writeln(' {}')
1035 } else if node.stmts.len == 1 && node.pos.line_nr == node.pos.last_line
1036 && stmt_is_single_line(node.stmts[0]) {
1037 f.write(' { ')
1038 // the control stmts (return/break/continue...) print a newline inside them,
1039 // so, since this'll all be on one line, trim any possible whitespace
1040 str := f.node_str(node.stmts[0]).trim_space()
1041 // single_line := ' defer { ${str} }'
1042 // if single_line.len + f.line_len <= fmt.max_len {
1043 // f.write(single_line)
1044 // return
1045 //}
1046 f.write(str)
1047
1048 // f.stmt(node.stmts[0])
1049 f.writeln(' }')
1050 } else {
1051 f.writeln(' {')
1052 f.stmts(node.stmts)
1053 f.writeln('}')
1054 }
1055}
1056
1057pub fn (mut f Fmt) expr_stmt(node ast.ExprStmt) {
1058 f.comments(node.comments)
1059 f.expr(node.expr)
1060 if !f.single_line_if {
1061 f.writeln('')
1062 }
1063}
1064
1065pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) {
1066 f.attrs(node.attrs)
1067 if node.is_pub {
1068 f.write('pub ')
1069 }
1070 mut name := node.name.after('.')
1071 if node.typ != ast.int_type && node.typ != ast.invalid_type {
1072 senum_type := f.type_to_str_using_aliases(node.typ, f.mod2alias)
1073 name += ' as ${senum_type}'
1074 }
1075 if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line {
1076 f.writeln('enum ${name} {}\n')
1077 return
1078 }
1079 f.writeln('enum ${name} {')
1080 f.comments(node.comments, same_line: true, level: .indent)
1081
1082 mut value_align := new_field_align(use_break_line: true)
1083 mut attr_align := new_field_align(use_threshold: true)
1084 mut comment_align := new_field_align(use_threshold: true)
1085 for field in node.fields {
1086 if field.has_expr {
1087 value_align.add_info(field.name.len, field.pos.line_nr, field.has_break_line)
1088 }
1089 attrs_len := inline_attrs_len(field.attrs)
1090 if field.attrs.len > 0 {
1091 if field.has_expr {
1092 attr_align.add_info(field.expr.str().len + 2, field.pos.line_nr,
1093 field.has_break_line)
1094 } else {
1095 attr_align.add_info(field.name.len, field.pos.line_nr, field.has_break_line)
1096 }
1097 }
1098 if field.comments.len > 0 {
1099 if field.attrs.len > 0 {
1100 comment_align.add_info(attrs_len, field.pos.line_nr, field.has_break_line)
1101 } else if field.has_expr {
1102 comment_align.add_info(field.expr.str().len + 2, field.pos.line_nr,
1103 field.has_break_line)
1104 } else {
1105 comment_align.add_info(field.name.len, field.pos.line_nr, field.has_break_line)
1106 }
1107 }
1108 }
1109
1110 for i, field in node.fields {
1111 if i > 0 && field.has_prev_newline {
1112 f.writeln('')
1113 }
1114 if field.pre_comments.len > 0 {
1115 f.comments(field.pre_comments, has_nl: true, level: .indent)
1116 }
1117 f.write('\t${field.name}')
1118 if field.has_expr {
1119 f.write(' '.repeat(value_align.max_len(field.pos.line_nr) - field.name.len))
1120 f.write(' = ')
1121 f.expr(field.expr)
1122 }
1123 attrs_len := inline_attrs_len(field.attrs)
1124 if field.attrs.len > 0 {
1125 if field.has_expr {
1126 f.write(' '.repeat(attr_align.max_len(field.pos.line_nr) - field.expr.str().len - 1))
1127 } else {
1128 f.write(' '.repeat(attr_align.max_len(field.pos.line_nr) - field.name.len + 1))
1129 }
1130 f.single_line_attrs(field.attrs, same_line: true)
1131 }
1132 // f.comments(field.comments, same_line: true, has_nl: false, level: .indent)
1133 if field.comments.len > 0 {
1134 if field.attrs.len > 0 {
1135 f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - attrs_len + 1))
1136 } else if field.has_expr {
1137 f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - field.expr.str().len -
1138 1))
1139 } else {
1140 f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - field.name.len + 1))
1141 }
1142 f.comments(field.comments, same_line: true, has_nl: false)
1143 }
1144 f.writeln('')
1145 f.comments(field.next_comments, has_nl: true, level: .indent)
1146 }
1147 f.writeln('}')
1148}
1149
1150pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
1151 f.attrs(node.attrs)
1152 if node.name.starts_with('C.') {
1153 f.is_c_function = true
1154 }
1155 f.table.new_int_fmt_fix = f.table.new_int && (f.is_translated_module || f.is_c_function)
1156 f.write(f.table.stringify_fn_decl(&node, f.cur_mod, f.mod2alias, true))
1157 f.table.new_int_fmt_fix = false
1158 f.is_c_function = false
1159 // Handle trailing comments after fn header declarations
1160 if node.no_body && node.end_comments.len > 0 {
1161 first_comment := node.end_comments[0]
1162 if first_comment.text.contains('\n') {
1163 f.writeln('\n')
1164 } else {
1165 f.write(' ')
1166 }
1167 f.comment(first_comment)
1168 if node.end_comments.len > 1 {
1169 f.writeln('\n')
1170 comments := node.end_comments[1..]
1171 for i, comment in comments {
1172 f.comment(comment)
1173 if i != comments.len - 1 {
1174 f.writeln('\n')
1175 }
1176 }
1177 }
1178 }
1179 f.fn_body(node)
1180}
1181
1182pub fn (mut f Fmt) anon_fn(node ast.AnonFn) {
1183 f.table.new_int_fmt_fix = f.table.new_int && (f.is_translated_module || f.is_c_function)
1184 f.write(f.table.stringify_anon_decl(&node, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
1185 f.table.new_int_fmt_fix = false
1186 f.fn_body(node.decl)
1187}
1188
1189fn (mut f Fmt) fn_body(node ast.FnDecl) {
1190 prev_fn_scope := f.fn_scope
1191 f.fn_scope = node.scope
1192 defer { f.fn_scope = prev_fn_scope }
1193 if node.language == .v || (node.is_method && node.language == .js) {
1194 if !node.no_body {
1195 f.write(' {')
1196 pre_comments := node.comments.filter(it.pos.pos < node.name_pos.pos)
1197 body_comments := node.comments[pre_comments.len..]
1198 f.comments(body_comments, same_line: true)
1199 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
1200 if body_comments.len == 0 {
1201 f.writeln('')
1202 }
1203 f.stmts(node.stmts)
1204 }
1205 f.write('}')
1206 if node.end_comments.len > 0 {
1207 first_comment := node.end_comments[0]
1208 if first_comment.text.contains('\n') {
1209 f.writeln('\n')
1210 } else {
1211 f.write(' ')
1212 }
1213 f.comment(first_comment)
1214 if node.end_comments.len > 1 {
1215 f.writeln('\n')
1216 comments := node.end_comments[1..]
1217 for i, comment in comments {
1218 f.comment(comment)
1219 if i != comments.len - 1 {
1220 f.writeln('\n')
1221 }
1222 }
1223 }
1224 }
1225 }
1226 if !node.is_anon {
1227 f.writeln('')
1228 }
1229 } else {
1230 f.writeln('')
1231 }
1232}
1233
1234pub fn (mut f Fmt) for_c_stmt(node ast.ForCStmt) {
1235 if node.label.len > 0 {
1236 f.write('${node.label}: ')
1237 }
1238 init_comments := node.comments.filter(it.pos.pos < node.init.pos.pos)
1239 cond_comments := node.comments[init_comments.len..].filter(it.pos.pos < node.cond.pos().pos)
1240 inc_comments :=
1241 node.comments[(init_comments.len + cond_comments.len)..].filter(it.pos.pos < node.inc.pos.pos)
1242 after_inc_comments := node.comments[(init_comments.len + cond_comments.len + inc_comments.len)..]
1243 f.write('for ')
1244 if node.has_init {
1245 if init_comments.len > 0 {
1246 f.comments(init_comments)
1247 f.write(' ')
1248 }
1249 f.single_line_if = true // to keep all for ;; exprs on the same line
1250 f.stmt(node.init)
1251 f.single_line_if = false
1252 }
1253 f.write('; ')
1254 if cond_comments.len > 0 {
1255 f.comments(cond_comments)
1256 f.write(' ')
1257 }
1258 f.expr(node.cond)
1259 f.write('; ')
1260 if inc_comments.len > 0 {
1261 f.comments(inc_comments)
1262 f.write(' ')
1263 }
1264 f.stmt(node.inc)
1265 f.remove_new_line()
1266 if after_inc_comments.len > 0 {
1267 f.comments(after_inc_comments)
1268 }
1269 if f.out.len > 1 && !f.out.last_n(1)[0].is_space() {
1270 f.write(' ')
1271 }
1272 f.write('{')
1273 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
1274 f.writeln('')
1275 f.stmts(node.stmts)
1276 }
1277 f.writeln('}')
1278}
1279
1280pub fn (mut f Fmt) for_in_stmt(node ast.ForInStmt) {
1281 if node.label.len > 0 {
1282 f.write('${node.label}: ')
1283 }
1284 kv_comments := node.comments.filter(it.pos.pos < node.kv_pos.pos)
1285 cond_comments := node.comments[kv_comments.len..].filter(it.pos.pos < node.cond.pos().pos)
1286 after_comments := node.comments[(kv_comments.len + cond_comments.len)..]
1287 f.write('for ')
1288 if kv_comments.len > 0 {
1289 f.comments(kv_comments)
1290 f.write(' ')
1291 }
1292 if node.key_var != '' {
1293 f.write(node.key_var)
1294 }
1295 if node.val_var != '' {
1296 if node.key_var != '' {
1297 f.write(', ')
1298 }
1299 if node.val_is_mut {
1300 f.write('mut ')
1301 }
1302 f.write(node.val_var)
1303 }
1304 f.write(' in ')
1305 if cond_comments.len > 0 {
1306 f.comments(cond_comments)
1307 f.write(' ')
1308 }
1309 f.expr(node.cond)
1310 if node.is_range {
1311 f.write(' .. ')
1312 f.expr(node.high)
1313 }
1314 if after_comments.len > 0 {
1315 f.comments(after_comments)
1316 }
1317 if f.out.len > 1 && !f.out.last_n(1)[0].is_space() {
1318 f.write(' ')
1319 }
1320 f.write('{')
1321 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
1322 f.writeln('')
1323 f.stmts(node.stmts)
1324 }
1325 f.writeln('}')
1326}
1327
1328pub fn (mut f Fmt) for_stmt(node ast.ForStmt) {
1329 if node.label.len > 0 {
1330 f.write('${node.label}: ')
1331 }
1332 f.write('for ')
1333 if node.comments.len > 0 {
1334 f.comments(node.comments)
1335 }
1336 f.expr(node.cond)
1337 if f.out.len > 1 && !f.out.last_n(1)[0].is_space() {
1338 f.write(' ')
1339 }
1340 f.write('{')
1341 if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line {
1342 f.writeln('')
1343 f.stmts(node.stmts)
1344 }
1345 f.writeln('}')
1346}
1347
1348pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) {
1349 f.attrs(node.attrs)
1350 if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line {
1351 // remove "__global()"
1352 return
1353 }
1354 f.write('__global ')
1355 mut max := 0
1356 if node.is_block {
1357 f.writeln('(')
1358 f.indent++
1359 for field in node.fields {
1360 if field.name.len > max {
1361 max = field.name.len
1362 }
1363 }
1364 }
1365 for field in node.fields {
1366 f.comments(field.comments, same_line: true)
1367 if field.is_const {
1368 f.write('const ')
1369 }
1370 if field.is_volatile {
1371 f.write('volatile ')
1372 }
1373 f.write('${field.name} ')
1374 f.write(' '.repeat(max - field.name.len))
1375 if field.has_expr {
1376 f.write('= ')
1377 f.expr(field.expr)
1378 } else {
1379 f.write('${f.type_to_str_using_aliases(field.typ, f.mod2alias)}')
1380 }
1381 if node.is_block {
1382 f.writeln('')
1383 }
1384 }
1385 f.comments_after_last_field(node.end_comments)
1386 if node.is_block {
1387 f.indent--
1388 f.writeln(')')
1389 } else {
1390 f.writeln('')
1391 }
1392}
1393
1394pub fn (mut f Fmt) spawn_expr(node ast.SpawnExpr) {
1395 f.write('spawn ')
1396 f.call_expr(node.call_expr)
1397}
1398
1399pub fn (mut f Fmt) go_expr(node ast.GoExpr) {
1400 f.write('go ')
1401 f.call_expr(node.call_expr)
1402}
1403
1404pub fn (mut f Fmt) goto_label(node ast.GotoLabel) {
1405 f.writeln('${node.name}:')
1406}
1407
1408pub fn (mut f Fmt) goto_stmt(node ast.GotoStmt) {
1409 f.writeln('goto ${node.name}')
1410}
1411
1412pub fn (mut f Fmt) hash_stmt(node ast.HashStmt) {
1413 f.attrs(node.attrs)
1414 f.writeln('#${node.val}')
1415}
1416
1417pub fn (mut f Fmt) interface_decl(node ast.InterfaceDecl) {
1418 f.attrs(node.attrs)
1419 if node.is_pub {
1420 f.write('pub ')
1421 }
1422 f.write('interface ')
1423 f.write_language_prefix(node.language)
1424 name := node.name.after('.') // strip prepended module
1425 f.write(name)
1426 f.write_generic_types(node.generic_types)
1427 f.write(' {')
1428 if node.fields.len > 0 || node.methods.len > 0 || node.pos.line_nr < node.pos.last_line {
1429 f.writeln('')
1430 }
1431 f.comments_before_field(node.pre_comments)
1432 for embed in node.embeds {
1433 f.write('\t${embed.name}')
1434 f.comments(embed.comments, same_line: true, has_nl: false, level: .indent)
1435 f.writeln('')
1436 }
1437 immut_fields := if node.mut_pos < 0 { node.fields } else { node.fields[..node.mut_pos] }
1438 mut_fields := if node.mut_pos < 0 { []ast.StructField{} } else { node.fields[node.mut_pos..] }
1439
1440 mut immut_methods := node.methods.clone()
1441 mut mut_methods := []ast.FnDecl{}
1442 for i, method in node.methods {
1443 if method.params[0].is_mut {
1444 immut_methods = node.methods[..i].clone()
1445 mut_methods = node.methods[i..].clone()
1446 break
1447 }
1448 }
1449
1450 mut type_align := new_field_align(use_break_line: true)
1451 mut comment_align := new_field_align(use_threshold: true)
1452 mut default_expr_align := new_field_align(use_threshold: true)
1453 mut attr_align := new_field_align(use_threshold: true)
1454 mut field_types := []string{cap: node.fields.len}
1455
1456 // Calculate the alignments first
1457 f.calculate_alignment(node.fields, mut type_align, mut comment_align, mut default_expr_align, mut
1458 attr_align, mut field_types)
1459
1460 mut method_comment_align := new_field_align(use_threshold: true)
1461 for method in node.methods {
1462 end_comments := method.comments.filter(it.pos.pos > method.pos.pos)
1463 if end_comments.len > 0 {
1464 f.table.new_int_fmt_fix = f.table.new_int && (f.is_translated_module || f.is_c_function)
1465 method_str :=
1466 f.table.stringify_fn_decl(&method, f.cur_mod, f.mod2alias, false).all_after_first('fn ')
1467 f.table.new_int_fmt_fix = false
1468 method_comment_align.add_info(method_str.len, method.pos.line_nr, method.has_break_line)
1469 }
1470 }
1471
1472 // TODO: alignment, comments, etc.
1473 for field in immut_fields {
1474 if field.has_prev_newline {
1475 f.writeln('')
1476 }
1477 f.interface_field(field, mut type_align, mut comment_align)
1478 }
1479 for method in immut_methods {
1480 if method.has_prev_newline {
1481 f.writeln('')
1482 }
1483 f.interface_method(method, mut method_comment_align)
1484 }
1485 if mut_fields.len + mut_methods.len > 0 {
1486 f.writeln('mut:')
1487 for field in mut_fields {
1488 if field.has_prev_newline {
1489 f.writeln('')
1490 }
1491 f.interface_field(field, mut type_align, mut comment_align)
1492 }
1493 for method in mut_methods {
1494 if method.has_prev_newline {
1495 f.writeln('')
1496 }
1497 f.interface_method(method, mut method_comment_align)
1498 }
1499 }
1500 f.writeln('}\n')
1501}
1502
1503enum AlignState {
1504 plain
1505 has_attributes
1506 has_default_expression
1507 has_everything
1508}
1509
1510pub fn (mut f Fmt) calculate_alignment(fields []ast.StructField, mut type_align FieldAlign, mut comment_align FieldAlign,
1511 mut default_expr_align FieldAlign, mut attr_align FieldAlign, mut field_types []string) {
1512 // Calculate the alignments first
1513 mut prev_state := AlignState.plain
1514 for field in fields {
1515 ft := f.no_cur_mod(f.type_to_str_using_aliases(field.typ, f.mod2alias))
1516 // Handle anon structs recursively
1517 field_types << ft
1518 attrs_len := inline_attrs_len(field.attrs)
1519 end_pos := field.pos.pos + field.pos.len
1520 type_align.add_info(field.name.len, field.pos.line_nr, field.has_break_line)
1521 if field.has_default_expr {
1522 default_expr_align.add_info(ft.len, field.pos.line_nr, field.has_break_line)
1523 }
1524 if field.attrs.len > 0 {
1525 attr_align.add_info(ft.len, field.pos.line_nr, field.has_break_line)
1526 }
1527 for comment in field.comments {
1528 if comment.pos.pos >= end_pos {
1529 if comment.pos.line_nr == field.pos.line_nr {
1530 if field.attrs.len > 0 {
1531 if prev_state != AlignState.has_attributes {
1532 comment_align.add_new_info(attrs_len, comment.pos.line_nr)
1533 } else {
1534 comment_align.add_info(attrs_len, comment.pos.line_nr,
1535 field.has_break_line)
1536 }
1537 prev_state = AlignState.has_attributes
1538 } else if field.has_default_expr {
1539 if prev_state != AlignState.has_default_expression {
1540 comment_align.add_new_info(field.default_expr.str().len + 2,
1541 comment.pos.line_nr)
1542 } else {
1543 comment_align.add_info(field.default_expr.str().len + 2,
1544 comment.pos.line_nr, field.has_break_line)
1545 }
1546 prev_state = AlignState.has_default_expression
1547 } else {
1548 if prev_state != AlignState.has_everything {
1549 comment_align.add_new_info(ft.len, comment.pos.line_nr)
1550 } else {
1551 comment_align.add_info(ft.len, comment.pos.line_nr,
1552 field.has_break_line)
1553 }
1554 prev_state = AlignState.has_everything
1555 }
1556 }
1557 continue
1558 }
1559 }
1560 }
1561}
1562
1563pub fn (mut f Fmt) interface_field(field ast.StructField, mut type_align FieldAlign, mut comment_align FieldAlign) {
1564 ft := f.no_cur_mod(f.type_to_str_using_aliases(field.typ, f.mod2alias))
1565 mut pre_cmts, mut end_cmts, mut next_line_cmts := []ast.Comment{}, []ast.Comment{}, []ast.Comment{}
1566 for cmt in field.comments {
1567 match true {
1568 cmt.pos.pos < field.pos.pos { pre_cmts << cmt }
1569 cmt.pos.line_nr > field.pos.last_line { next_line_cmts << cmt }
1570 else { end_cmts << cmt }
1571 }
1572 }
1573 if pre_cmts.len > 0 {
1574 f.comments(pre_cmts, level: .indent)
1575 }
1576
1577 sym := f.table.sym(field.typ)
1578 if sym.info is ast.Struct {
1579 if sym.info.is_anon {
1580 f.write('\t${field.name} ')
1581 f.write_anon_struct_field_decl(field.typ, ast.StructDecl{ fields: sym.info.fields })
1582 } else {
1583 f.write('\t${field.name} ')
1584 }
1585 } else {
1586 f.write('\t${field.name} ')
1587 }
1588 if !(sym.info is ast.Struct && sym.info.is_anon) {
1589 f.write(' '.repeat(type_align.max_len(field.pos.line_nr) - field.name.len))
1590 f.write(ft)
1591 }
1592 if end_cmts.len > 0 {
1593 f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - ft.len + 1))
1594 f.comments(end_cmts, level: .indent)
1595 } else {
1596 f.writeln('')
1597 }
1598 if next_line_cmts.len > 0 {
1599 f.comments(next_line_cmts, level: .indent)
1600 }
1601}
1602
1603pub fn (mut f Fmt) interface_method(method ast.FnDecl, mut comment_align FieldAlign) {
1604 before_comments := method.comments.filter(it.pos.pos < method.pos.pos)
1605 end_comments := method.comments.filter(it.pos.pos > method.pos.pos)
1606 if before_comments.len > 0 {
1607 f.comments(before_comments, level: .indent)
1608 }
1609 f.write('\t')
1610 f.table.new_int_fmt_fix = f.table.new_int && (f.is_translated_module || f.is_c_function)
1611 method_str :=
1612 f.table.stringify_fn_decl(&method, f.cur_mod, f.mod2alias, false).all_after_first('fn ')
1613 f.table.new_int_fmt_fix = false
1614 f.write(method_str)
1615 if end_comments.len > 0 {
1616 f.write(' '.repeat(comment_align.max_len(method.pos.line_nr) - method_str.len + 1))
1617 f.comments(end_comments, level: .indent)
1618 } else {
1619 f.writeln('')
1620 }
1621 f.comments(method.next_comments, level: .indent)
1622}
1623
1624pub fn (mut f Fmt) module_stmt(mod ast.Module) {
1625 f.set_current_module_name(mod.name)
1626 if mod.is_skipped {
1627 return
1628 }
1629 f.is_translated_module = mod.attrs.any(it.name == 'translated')
1630 f.attrs(mod.attrs)
1631 f.writeln('module ${mod.short_name}\n')
1632 if f.import_pos == 0 {
1633 f.import_pos = f.out.len
1634 }
1635}
1636
1637pub fn (mut f Fmt) return_stmt(node ast.Return) {
1638 f.write('return')
1639 if node.exprs.len > 0 {
1640 f.write(' ')
1641 mut sum_len := 0
1642 // Loop over all return values. In normal returns this will only run once.
1643 for i, expr in node.exprs {
1644 pre_comments := node.comments[sum_len..].filter(it.pos.pos < expr.pos().pos)
1645 sum_len += pre_comments.len
1646 if pre_comments.len > 0 {
1647 f.comments(pre_comments)
1648 f.write(' ')
1649 }
1650 if expr is ast.ParExpr && expr.comments.len == 0 {
1651 f.expr(expr.expr)
1652 } else {
1653 f.expr(expr)
1654 }
1655 if i < node.exprs.len - 1 {
1656 f.write(', ')
1657 }
1658 }
1659 }
1660 if !f.single_line_if {
1661 f.writeln('')
1662 }
1663}
1664
1665pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) {
1666 f.write('sql ')
1667 f.expr(node.db_expr)
1668 f.writeln(' {')
1669
1670 for line in node.lines {
1671 f.comments(line.pre_comments, level: .indent)
1672 f.sql_stmt_line(line)
1673 f.comments(line.end_comments, level: .indent)
1674 }
1675 f.write('}')
1676 f.or_expr(node.or_expr)
1677 f.writeln('')
1678}
1679
1680pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) {
1681 sym := f.table.sym(node.table_expr.typ)
1682 mut table_name := sym.name
1683 if !table_name.starts_with('C.') && !table_name.starts_with('JS.') {
1684 table_name = f.no_cur_mod(f.short_module(sym.name)) // TODO: f.type_to_str?
1685 }
1686
1687 f.write('\t')
1688 match node.kind {
1689 .insert {
1690 f.writeln('insert ${node.object_var} into ${table_name}')
1691 }
1692 .upsert {
1693 f.writeln('upsert ${node.object_var} into ${table_name}')
1694 }
1695 .update {
1696 if node.is_dynamic {
1697 f.write('dynamic update ${table_name} set ')
1698 f.expr(node.update_data_expr)
1699 f.write(' ')
1700 f.write('where ')
1701 f.expr(node.where_expr)
1702 f.writeln('')
1703 } else {
1704 mut has_multiline_update_expr := false
1705 for expr in node.update_exprs {
1706 if f.node_str(expr).contains('\n') {
1707 has_multiline_update_expr = true
1708 break
1709 }
1710 }
1711 if has_multiline_update_expr {
1712 f.writeln('update ${table_name} set')
1713 // SQL block lines use a manual extra tab, so nested update values need two
1714 // formatter indent levels to stay visually nested.
1715 f.indent += 2
1716 for i, col in node.updated_columns {
1717 f.write('${col} = ')
1718 f.expr(node.update_exprs[i])
1719 if i < node.updated_columns.len - 1 {
1720 f.write(',')
1721 }
1722 f.writeln('')
1723 }
1724 f.indent -= 2
1725 } else {
1726 f.write('update ${table_name} set ')
1727 for i, col in node.updated_columns {
1728 f.write('${col} = ')
1729 f.expr(node.update_exprs[i])
1730 if i < node.updated_columns.len - 1 {
1731 f.write(', ')
1732 } else {
1733 f.write(' ')
1734 }
1735 f.wrap_long_line(3, true)
1736 }
1737 }
1738 if has_multiline_update_expr {
1739 f.write('\twhere ')
1740 } else {
1741 f.write('where ')
1742 }
1743 f.expr(node.where_expr)
1744 f.writeln('')
1745 }
1746 }
1747 .delete {
1748 f.write('delete from ${table_name} where ')
1749 f.expr(node.where_expr)
1750 f.writeln('')
1751 }
1752 .create {
1753 f.writeln('create table ${table_name}')
1754 }
1755 .drop {
1756 f.writeln('drop table ${table_name}')
1757 }
1758 }
1759}
1760
1761pub fn (mut f Fmt) type_decl(node ast.TypeDecl) {
1762 match node {
1763 ast.AliasTypeDecl { f.alias_type_decl(node) }
1764 ast.FnTypeDecl { f.fn_type_decl(node) }
1765 ast.SumTypeDecl { f.sum_type_decl(node) }
1766 }
1767
1768 f.writeln('')
1769}
1770
1771pub fn (mut f Fmt) alias_type_decl(node ast.AliasTypeDecl) {
1772 f.attrs(node.attrs)
1773 if node.is_pub {
1774 f.write('pub ')
1775 }
1776 // aliases of anon struct: `type Foo = struct {}`
1777 sym := f.table.sym(node.parent_type)
1778 if sym.info is ast.Struct {
1779 if sym.info.is_anon {
1780 f.write('type ${node.name} = ')
1781 f.struct_decl(ast.StructDecl{ fields: sym.info.fields }, true)
1782 f.comments(node.comments, has_nl: false)
1783 return
1784 }
1785 }
1786 ptype := f.type_to_str_using_aliases(node.parent_type, f.mod2alias)
1787 f.write('type ${node.name} = ${ptype}')
1788
1789 f.comments(node.comments, has_nl: false)
1790}
1791
1792pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) {
1793 f.attrs(node.attrs)
1794 if node.is_pub {
1795 f.write('pub ')
1796 }
1797 typ_sym := f.table.sym(node.typ)
1798 fn_typ_info := typ_sym.info as ast.FnType
1799 fn_info := fn_typ_info.func
1800 fn_name := f.no_cur_mod(node.name)
1801 mut generic_types_str := ''
1802 if node.generic_types.len > 0 {
1803 generic_names := node.generic_types.map(f.table.sym(it).name)
1804 generic_types_str = '[${generic_names.join(', ')}]'
1805 }
1806 f.write('type ${fn_name}${generic_types_str} = fn (')
1807 for i, arg in fn_info.params {
1808 if arg.is_mut {
1809 f.write(arg.typ.share().str() + ' ')
1810 }
1811 f.write(arg.name)
1812 mut s := f.no_cur_mod(f.type_to_str_using_aliases(arg.typ, f.mod2alias))
1813 if arg.is_mut {
1814 if s.starts_with('&') {
1815 s = s[1..]
1816 }
1817 s = s.trim_left('shared ')
1818 }
1819 is_last_arg := i == fn_info.params.len - 1
1820 should_add_type := true || is_last_arg
1821 || fn_info.params[i + 1].typ != arg.typ
1822 || (fn_info.is_variadic && i == fn_info.params.len - 2)
1823 if should_add_type {
1824 ns := if arg.name == '' { '' } else { ' ' }
1825 if fn_info.is_variadic && is_last_arg {
1826 f.write(ns + '...' + s)
1827 } else {
1828 f.write(ns + s)
1829 }
1830 }
1831 if !is_last_arg {
1832 f.write(', ')
1833 }
1834 }
1835 f.write(')')
1836 if fn_info.return_type.idx() != ast.void_type_idx {
1837 ret_str := f.no_cur_mod(f.type_to_str_using_aliases(fn_info.return_type, f.mod2alias))
1838 f.write(' ${ret_str}')
1839 } else if fn_info.return_type.has_flag(.option) {
1840 f.write(' ?')
1841 } else if fn_info.return_type.has_flag(.result) {
1842 f.write(' !')
1843 }
1844
1845 f.comments(node.comments, has_nl: false)
1846 f.writeln('')
1847}
1848
1849struct Variant {
1850 name string
1851 id int
1852}
1853
1854fn (mut f Fmt) sum_type_variant_comments(variant ast.TypeNode) {
1855 if variant.end_comments.len == 0 {
1856 return
1857 }
1858 mut same_line_comments := []ast.Comment{}
1859 mut follow_up_comments := []ast.Comment{}
1860 for comment in variant.end_comments {
1861 if comment.pos.line_nr == variant.pos.last_line {
1862 same_line_comments << comment
1863 } else {
1864 follow_up_comments << comment
1865 }
1866 }
1867 if same_line_comments.len > 0 {
1868 f.comments(same_line_comments, has_nl: false)
1869 }
1870 if follow_up_comments.len > 0 {
1871 f.writeln('')
1872 f.comments(follow_up_comments, has_nl: false, level: .indent)
1873 }
1874}
1875
1876pub fn (mut f Fmt) sum_type_decl(node ast.SumTypeDecl) {
1877 f.attrs(node.attrs)
1878 start_pos := f.out.len
1879 if node.is_pub {
1880 f.write('pub ')
1881 }
1882 f.write('type ${node.name}')
1883 f.write_generic_types(node.generic_types)
1884 f.write(' = ')
1885
1886 mut variants := []Variant{cap: node.variants.len}
1887 for i, variant in node.variants {
1888 variants << Variant{f.type_to_str_using_aliases(variant.typ, f.mod2alias), i}
1889 }
1890 // The first variant is now used as the default variant when doing `a:= Sumtype{}`, i.e. a change in semantics.
1891 // Sorting is disabled, because it is no longer a cosmetic change - it can change the default variant.
1892 // variants.sort(a.name < b.name)
1893
1894 mut separator := ' | '
1895 mut is_multiline := false
1896 // if line length is too long, put each type on its own line
1897 mut line_length := f.out.len - start_pos
1898 for variant in variants {
1899 // 3 = length of ' = ' or ' | '
1900 line_length += 3 + variant.name.len
1901 if line_length > max_len || (variant.id != node.variants.len - 1
1902 && node.variants[variant.id].end_comments.len > 0) {
1903 separator = '\n\t| '
1904 is_multiline = true
1905 break
1906 }
1907 }
1908
1909 for i, variant in variants {
1910 if i > 0 {
1911 f.write(separator)
1912 }
1913 f.write(variant.name)
1914 if node.variants[variant.id].end_comments.len > 0 && is_multiline {
1915 f.sum_type_variant_comments(node.variants[variant.id])
1916 }
1917 }
1918 if !is_multiline {
1919 f.comments(node.variants.last().end_comments,
1920 has_nl: false
1921 )
1922 }
1923}
1924
1925//=== Specific Expr methods ===//
1926
1927pub fn (mut f Fmt) array_decompose(node ast.ArrayDecompose) {
1928 f.write('...')
1929 f.expr(node.expr)
1930}
1931
1932pub fn (mut f Fmt) array_init(node ast.ArrayInit) {
1933 typed_fixed_literal := node.is_fixed && node.has_val && node.typ != 0
1934 && node.typ != ast.void_type
1935 if node.is_fixed && node.is_option {
1936 f.write('?')
1937 }
1938 if node.exprs.len == 0 && ((node.typ != 0 && node.typ != ast.void_type)
1939 || node.elem_type_expr !is ast.EmptyExpr) {
1940 // `x := []string{}`
1941 if node.alias_type != ast.void_type {
1942 f.write(f.type_to_str_using_aliases(node.alias_type, f.mod2alias))
1943 } else if node.elem_type_expr !is ast.EmptyExpr {
1944 f.write('[]')
1945 f.expr(node.elem_type_expr)
1946 } else {
1947 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
1948 }
1949 f.write('{')
1950 if node.has_len {
1951 f.write('len: ')
1952 f.expr(node.len_expr)
1953 if node.has_cap || node.has_init {
1954 f.write(', ')
1955 }
1956 }
1957 if node.has_cap {
1958 f.write('cap: ')
1959 f.expr(node.cap_expr)
1960 if node.has_init {
1961 f.write(', ')
1962 }
1963 }
1964 if node.has_init {
1965 f.write('init: ')
1966 old_is_array_init := f.is_array_init
1967 f.is_array_init = true
1968 f.expr(node.init_expr)
1969 f.is_array_init = old_is_array_init
1970 }
1971 f.write('}')
1972 return
1973 }
1974 if typed_fixed_literal && f.array_init_depth == 0 {
1975 fixed_literal_type := if node.literal_typ != ast.void_type {
1976 node.literal_typ
1977 } else {
1978 node.typ.clear_option_and_result()
1979 }
1980 f.write(f.type_to_str_using_aliases(fixed_literal_type, f.mod2alias))
1981 }
1982 // `[1,2,3]`
1983 f.write('[')
1984 mut inc_indent := false
1985 mut last_line_nr := node.pos.line_nr // to have the same newlines between array elements
1986 f.array_init_depth++
1987 if node.pre_cmnts.len > 0 {
1988 if node.pre_cmnts[0].pos.line_nr > last_line_nr {
1989 f.writeln('')
1990 }
1991 }
1992 for i, c in node.pre_cmnts {
1993 if i < node.pre_cmnts.len - 1 {
1994 if c.pos.last_line < node.pre_cmnts[i + 1].pos.line_nr {
1995 f.comment(c, level: .indent)
1996 f.writeln('')
1997 } else {
1998 f.comment(c, level: .indent)
1999 f.write(' ')
2000 }
2001 } else {
2002 next_line := if node.exprs.len > 0 {
2003 node.exprs[0].pos().line_nr
2004 } else {
2005 node.pos.last_line
2006 }
2007 if c.pos.last_line < next_line {
2008 f.comment(c, level: .indent)
2009 if node.exprs.len == 0 {
2010 f.writeln('')
2011 }
2012 } else {
2013 f.comment(c, level: .indent)
2014 if node.exprs.len > 0 {
2015 f.write(' ')
2016 }
2017 }
2018 }
2019 last_line_nr = c.pos.last_line
2020 }
2021 mut set_comma := false
2022 for i, expr in node.exprs {
2023 pos := expr.pos()
2024 if i == 0 {
2025 if f.array_init_depth > f.array_init_break.len {
2026 f.array_init_break << pos.line_nr > last_line_nr
2027 || f.line_len + expr.pos().len > break_points[3]
2028 }
2029 }
2030 mut line_break := f.array_init_break[f.array_init_depth - 1]
2031 mut penalty := if line_break { 0 } else { 4 }
2032 if penalty > 0 {
2033 if i == 0
2034 || node.exprs[i - 1] in [ast.ArrayInit, ast.StructInit, ast.MapInit, ast.CallExpr] {
2035 penalty--
2036 }
2037 if expr in [ast.ArrayInit, ast.StructInit, ast.MapInit, ast.CallExpr] {
2038 penalty--
2039 }
2040 }
2041 mut is_new_line := f.wrap_long_line(penalty, !inc_indent)
2042 if is_new_line && !inc_indent {
2043 f.indent++
2044 inc_indent = true
2045 }
2046 single_line_expr := expr_is_single_line(expr)
2047 if single_line_expr {
2048 mut estr := ''
2049 if !is_new_line && !f.buffering && f.line_len + expr.pos().len > max_len {
2050 if inc_indent {
2051 estr = f.node_str(expr)
2052 }
2053 f.writeln('')
2054 is_new_line = true
2055 if !inc_indent {
2056 f.indent++
2057 inc_indent = true
2058 f.write_indent()
2059 f.empty_line = false
2060 estr = f.node_str(expr)
2061 }
2062 if i == 0 {
2063 f.array_init_break[f.array_init_depth - 1] = true
2064 line_break = true
2065 }
2066 } else {
2067 estr = f.node_str(expr)
2068 }
2069 if !is_new_line && i > 0 {
2070 f.write(' ')
2071 }
2072 f.write(estr)
2073 } else {
2074 if !is_new_line && i > 0 {
2075 f.write(' ')
2076 }
2077 f.expr(expr)
2078 }
2079 mut last_comment_was_inline := false
2080 mut has_comments := node.ecmnts[i].len > 0
2081 if i < node.ecmnts.len && has_comments {
2082 expr_pos := expr.pos()
2083 for icmt, cmt in node.ecmnts[i] {
2084 if !set_comma && cmt.pos.pos > expr_pos.pos + expr_pos.len + 2 {
2085 if icmt > 0 {
2086 if last_comment_was_inline {
2087 f.write(',')
2088 set_comma = true
2089 }
2090 } else {
2091 f.write(',') // first comment needs a comma
2092 set_comma = true
2093 }
2094 }
2095 if cmt.pos.line_nr > expr_pos.last_line {
2096 f.writeln('')
2097 f.comment(cmt)
2098 } else {
2099 if !set_comma {
2100 f.write(',')
2101 set_comma = true
2102 }
2103 f.write(' ')
2104 f.comment(cmt)
2105 if !line_break {
2106 f.writeln('')
2107 }
2108 }
2109 }
2110 } else if i == node.exprs.len - 1 && !line_break {
2111 is_new_line = false
2112 }
2113
2114 mut put_comma := !set_comma
2115 if has_comments && !last_comment_was_inline {
2116 put_comma = false
2117 }
2118 if i == node.exprs.len - 1 {
2119 if is_new_line {
2120 if put_comma {
2121 f.write(',')
2122 }
2123 f.writeln('')
2124 }
2125 } else if put_comma {
2126 f.write(',')
2127 }
2128 last_line_nr = pos.last_line
2129 set_comma = false
2130 }
2131 f.array_init_depth--
2132 if f.array_init_depth == 0 {
2133 f.array_init_break = []
2134 }
2135 if inc_indent {
2136 f.indent--
2137 }
2138 f.write(']')
2139 // `[100]u8`
2140 if node.is_fixed {
2141 if node.has_val {
2142 if typed_fixed_literal {
2143 return
2144 }
2145 if node.from_to_fixed_size {
2146 f.write('.to_fixed_size()')
2147 } else {
2148 f.write('!')
2149 }
2150 return
2151 }
2152 f.write(f.type_to_str_using_aliases(node.elem_type, f.mod2alias))
2153 if node.has_init {
2154 f.write('{init: ')
2155 f.expr(node.init_expr)
2156 f.write('}')
2157 } else {
2158 f.write('{}')
2159 }
2160 }
2161}
2162
2163pub fn (mut f Fmt) as_cast(node ast.AsCast) {
2164 type_str := f.type_to_str_using_aliases(node.typ, f.mod2alias)
2165 f.expr(node.expr)
2166 f.write(' as ${type_str}')
2167}
2168
2169pub fn (mut f Fmt) assoc(node ast.Assoc) {
2170 f.writeln('{')
2171 f.indent++
2172 f.writeln('...${node.var_name}')
2173 for i, field in node.fields {
2174 f.write('${field}: ')
2175 f.expr(node.exprs[i])
2176 f.writeln('')
2177 }
2178 f.indent--
2179 f.write('}')
2180}
2181
2182pub fn (mut f Fmt) at_expr(node ast.AtExpr) {
2183 f.write(node.name)
2184}
2185
2186fn (mut f Fmt) write_static_method(_name string, short_name string) {
2187 if short_name.contains('.') {
2188 indx := short_name.index_('.') + 1
2189 f.write(short_name[0..indx] + short_name[indx..].replace('__static__', '.').capitalize())
2190 } else {
2191 f.write(short_name.replace('__static__', '.').capitalize())
2192 }
2193}
2194
2195pub fn (mut f Fmt) call_expr(node ast.CallExpr) {
2196 mut is_method_newline := false
2197 if node.is_method {
2198 if ast.builtin_array_generic_methods_no_sort_matcher.matches(node.name) {
2199 f.in_lambda_depth++
2200 defer(fn) { f.in_lambda_depth-- }
2201 }
2202 f.expr(node.left)
2203 is_method_newline = node.left.pos().last_line != node.name_pos.line_nr
2204 if is_method_newline {
2205 f.indent++
2206 f.writeln('')
2207 }
2208 f.write('.' + node.name)
2209 } else {
2210 f.write_language_prefix(node.language)
2211 if node.left is ast.AnonFn {
2212 f.anon_fn(node.left)
2213 } else if node.language != .v {
2214 f.write('${node.name.after_char(`.`)}')
2215 } else {
2216 name := f.short_module(node.name)
2217 if node.is_static_method {
2218 f.write_static_method(node.name, name)
2219 } else if node.is_paren_wrapped_call {
2220 f.write('(${name})')
2221 } else {
2222 f.write(name)
2223 }
2224 }
2225 }
2226 if node.mod == '' && node.name == '' {
2227 if node.left is ast.CallExpr {
2228 f.expr(node.left)
2229 } else {
2230 f.write(node.left.str())
2231 }
2232 }
2233 f.write_generic_call_if_require(node)
2234 f.write('(')
2235 f.call_args(node.args)
2236 f.write(')')
2237 f.or_expr(node.or_block)
2238 f.comments(node.comments, has_nl: false)
2239 if is_method_newline {
2240 f.indent--
2241 }
2242}
2243
2244fn (mut f Fmt) write_generic_call_if_require(node ast.CallExpr) {
2245 if node.concrete_types.len > 0 {
2246 f.write('[')
2247 for i, concrete_type in node.concrete_types {
2248 tsym := f.table.sym(concrete_type)
2249 if !f.write_anon_struct_type(concrete_type) {
2250 mut name := f.type_to_str_using_aliases(concrete_type, f.mod2alias)
2251 if tsym.language != .js && !tsym.name.starts_with('JS.') {
2252 name = f.short_module(name)
2253 } else if tsym.language == .js && !tsym.name.starts_with('JS.') {
2254 name = 'JS.' + name
2255 }
2256 if tsym.language == .c {
2257 name = 'C.' + name
2258 }
2259 f.write(name)
2260 }
2261 if i != node.concrete_types.len - 1 {
2262 f.write(', ')
2263 }
2264 }
2265 f.write(']')
2266 }
2267}
2268
2269pub fn (mut f Fmt) call_args(args []ast.CallArg) {
2270 old_single_line_fields_state := f.single_line_fields
2271 old_short_arg_state := f.use_short_fn_args
2272 f.single_line_fields = true
2273 f.use_short_fn_args = false
2274 defer {
2275 f.single_line_fields = old_single_line_fields_state
2276 f.use_short_fn_args = old_short_arg_state
2277 }
2278 for i, arg in args {
2279 pre_comments := arg.comments.filter(it.pos.pos < arg.expr.pos().pos)
2280 post_comments := arg.comments[pre_comments.len..]
2281 if pre_comments.len > 0 {
2282 f.comments(pre_comments)
2283 f.write(' ')
2284 }
2285 if i == args.len - 1 && arg.expr is ast.StructInit {
2286 if arg.expr.typ == ast.void_type {
2287 f.use_short_fn_args = true
2288 }
2289 }
2290 if arg.is_mut {
2291 f.write(arg.share.str() + ' ')
2292 }
2293 if i > 0 && !f.single_line_if && !f.use_short_fn_args && arg.expr !is ast.StructInit {
2294 arg_str := f.node_str(arg.expr)
2295 tail_len := if i < args.len - 1 { 2 } else { 1 }
2296 is_tiny_last_assign_arg := f.is_assign && i == args.len - 1 && arg_str.len <= 4
2297 if !is_tiny_last_assign_arg && !arg_str.contains('\n')
2298 && f.line_len + arg_str.len + tail_len > max_len {
2299 f.wrap_long_line(0, true)
2300 }
2301 }
2302 f.expr(arg.expr)
2303 if post_comments.len > 0 {
2304 f.comments(post_comments)
2305 f.write(' ')
2306 }
2307 if i < args.len - 1 {
2308 f.write(', ')
2309 }
2310 }
2311}
2312
2313pub fn (mut f Fmt) cast_expr(node ast.CastExpr) {
2314 typ := f.type_to_str_using_aliases(node.typ, f.mod2alias)
2315 if typ == 'voidptr' {
2316 // `voidptr(0)` => `nil`
2317 if node.expr is ast.IntegerLiteral {
2318 if node.expr.val == '0' {
2319 if f.inside_unsafe {
2320 f.write('nil')
2321 } else {
2322 f.write('unsafe { nil }')
2323 }
2324 return
2325 }
2326 }
2327 }
2328 f.write('${typ}(')
2329 f.expr(node.expr)
2330 if node.has_arg {
2331 f.write(', ')
2332 f.expr(node.arg)
2333 }
2334 f.write(')')
2335}
2336
2337pub fn (mut f Fmt) chan_init(mut node ast.ChanInit) {
2338 info := f.table.sym(node.typ).chan_info()
2339 if node.elem_type == 0 && node.typ > 0 {
2340 node.elem_type = info.elem_type
2341 }
2342 is_mut := info.is_mut
2343 el_typ := if is_mut {
2344 node.elem_type.set_nr_muls(node.elem_type.nr_muls() - 1)
2345 } else {
2346 node.elem_type
2347 }
2348 f.write('chan ')
2349 if is_mut {
2350 f.write('mut ')
2351 }
2352 f.write(f.type_to_str_using_aliases(el_typ, f.mod2alias))
2353 f.write('{')
2354 if node.has_cap {
2355 f.write('cap: ')
2356 f.expr(node.cap_expr)
2357 }
2358 f.write('}')
2359}
2360
2361pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) {
2362 if node.is_template {
2363 if node.kind == .html {
2364 if node.args.len == 1 && node.args[0].expr is ast.StringLiteral {
2365 f.write('\$veb.html(')
2366 f.expr(node.args[0].expr)
2367 f.write(')')
2368 } else {
2369 f.write('\$veb.html()')
2370 }
2371 } else {
2372 f.write('\$tmpl(')
2373 f.expr(node.args[0].expr)
2374 f.write(')')
2375 }
2376 } else {
2377 match true {
2378 node.kind == .embed_file {
2379 f.write('\$embed_file(')
2380 f.expr(node.args[0].expr)
2381 if node.embed_file.compression_type != 'none' {
2382 f.write(', .${node.embed_file.compression_type}')
2383 }
2384 f.write(')')
2385 }
2386 node.kind == .env {
2387 f.write("\$env('${node.args_var}')")
2388 }
2389 node.kind == .pkgconfig {
2390 f.write("\$pkgconfig('${node.args_var}')")
2391 }
2392 node.kind in [.compile_error, .compile_warn] {
2393 if node.args.len == 0 {
2394 if node.args_var.contains("'") {
2395 f.write('\$${node.method_name}("${node.args_var}")')
2396 } else {
2397 f.write("\$${node.method_name}('${node.args_var}')")
2398 }
2399 } else {
2400 f.write('\$${node.method_name}(')
2401 f.expr(node.args[0].expr)
2402 f.write(')')
2403 }
2404 }
2405 node.kind == .d {
2406 f.write("\$d('${node.args_var}', ")
2407 f.expr(node.args[0].expr)
2408 f.write(')')
2409 }
2410 node.kind == .res {
2411 if node.args_var != '' {
2412 f.write('\$res(${node.args_var})')
2413 } else {
2414 f.write('\$res()')
2415 }
2416 }
2417 node.kind in [.zero, .new] {
2418 f.write('\$${node.method_name}(')
2419 f.expr(node.args[0].expr)
2420 f.write(')')
2421 }
2422 else {
2423 inner_args := if node.args_var != '' {
2424 node.args_var
2425 } else {
2426 node.args.map(call_arg_spread_str).join(', ')
2427 }
2428 method_expr := if node.has_parens {
2429 '(${node.method_name}(${inner_args}))'
2430 } else {
2431 '${node.method_name}(${inner_args})'
2432 }
2433 f.expr(node.left)
2434 f.write('.$${method_expr}')
2435 f.or_expr(node.or_block)
2436 }
2437 }
2438 }
2439}
2440
2441pub fn (mut f Fmt) comptime_selector(node ast.ComptimeSelector) {
2442 f.expr(node.left)
2443 f.write('.\$(${node.field_expr})')
2444}
2445
2446pub fn (mut f Fmt) concat_expr(node ast.ConcatExpr) {
2447 for i, val in node.vals {
2448 if i != 0 {
2449 f.write(', ')
2450 }
2451 f.expr(val)
2452 }
2453}
2454
2455pub fn (mut f Fmt) dump_expr(node ast.DumpExpr) {
2456 f.write('dump(')
2457 f.expr(node.expr)
2458 f.write(')')
2459}
2460
2461pub fn (mut f Fmt) enum_val(node ast.EnumVal) {
2462 name := f.short_module(node.enum_name)
2463 f.write(name + '.' + node.val)
2464}
2465
2466pub fn (mut f Fmt) ident(node ast.Ident) {
2467 if node.info is ast.IdentVar {
2468 if node.comptime && node.name in ast.valid_comptime_not_user_defined {
2469 f.write(node.name)
2470 return
2471 }
2472 if node.info.is_mut {
2473 f.write(node.info.share.str() + ' ')
2474 }
2475 var_info := node.var_info()
2476 if var_info.is_static {
2477 f.write('static ')
2478 }
2479 if var_info.is_volatile {
2480 f.write('volatile ')
2481 }
2482 }
2483 f.write_language_prefix(node.language)
2484 if node.kind == .blank_ident {
2485 f.write('_')
2486 } else {
2487 mut is_local := false
2488 if f.fn_scope != unsafe { nil } {
2489 if _ := f.fn_scope.find_var(node.name) {
2490 is_local = true
2491 }
2492 }
2493 if !is_local && !node.name.contains('.') && !f.inside_const {
2494 if _ := f.file.global_scope.find_const('${f.cur_mod}.${node.name}') {
2495 const_name := node.name.all_after_last('.')
2496 f.write(const_name)
2497 if node.or_expr.kind == .block {
2498 f.or_expr(node.or_expr)
2499 }
2500 return
2501 }
2502 }
2503 name := f.short_module(node.name)
2504 if node.name.contains('__static__') {
2505 f.write_static_method(node.name, name)
2506 } else if f.is_array_init && name == 'it' {
2507 f.write('index')
2508 } else {
2509 f.write(name)
2510 }
2511 if node.concrete_types.len > 0 {
2512 f.write('[')
2513 for i, concrete_type in node.concrete_types {
2514 if !f.write_anon_struct_type(concrete_type) {
2515 typ_name := f.type_to_str_using_aliases(concrete_type, f.mod2alias)
2516 f.write(typ_name)
2517 }
2518 if i != node.concrete_types.len - 1 {
2519 f.write(', ')
2520 }
2521 }
2522 f.write(']')
2523 }
2524 if node.or_expr.kind == .propagate_option {
2525 f.write('?')
2526 } else if node.or_expr.kind == .block {
2527 f.or_expr(node.or_expr)
2528 }
2529 }
2530}
2531
2532pub fn (mut f Fmt) if_expr(node ast.IfExpr) {
2533 dollar := if node.is_comptime { '$' } else { '' }
2534 f.inside_comptime_if = node.is_comptime
2535 mut keep_single_line := node.branches.len == 1 && branch_is_single_line(node.branches[0])
2536 is_ternary := node.branches.len == 2 && node.has_else && branch_is_single_line(node.branches[0])
2537 && branch_is_single_line(node.branches[1]) && (node.is_expr || f.is_assign
2538 || f.inside_const || f.is_struct_init || f.single_line_fields)
2539 keep_single_line = keep_single_line || is_ternary
2540 f.single_line_if = keep_single_line
2541 start_pos := f.out.len
2542 start_len := f.line_len
2543 for {
2544 for i, branch in node.branches {
2545 f.branch_processed_imports.clear()
2546 mut sum_len := 0
2547 if i > 0 {
2548 // `else`, close previous branch
2549 if branch.comments.len > 0 {
2550 f.writeln('}')
2551 pre_comments := branch.comments.filter(it.pos.pos < branch.pos.pos)
2552 sum_len += pre_comments.len
2553 if pre_comments.len > 0 {
2554 f.comments(pre_comments)
2555 }
2556 } else {
2557 f.write('} ')
2558 }
2559 f.write('${dollar}else ')
2560 }
2561 if i < node.branches.len - 1 || !node.has_else {
2562 f.write('${dollar}if ')
2563 cur_pos := f.out.len
2564 pre_comments :=
2565 branch.comments[sum_len..].filter(it.pos.pos < branch.cond.pos().pos)
2566 sum_len += pre_comments.len
2567 post_comments := branch.comments[sum_len..]
2568 if pre_comments.len > 0 {
2569 f.comments(pre_comments)
2570 f.write(' ')
2571 }
2572 f.expr(branch.cond)
2573 if post_comments.len > 0 {
2574 f.comments(post_comments)
2575 f.write(' ')
2576 }
2577 cond_len := f.out.len - cur_pos
2578 is_cond_wrapped := cond_len > 0 && branch.cond in [ast.IfGuardExpr, ast.CallExpr]
2579 && f.out.last_n(cond_len).contains('\n')
2580 if is_cond_wrapped {
2581 f.writeln('')
2582 } else {
2583 f.write(' ')
2584 }
2585 }
2586 f.write('{')
2587 if keep_single_line {
2588 f.write(' ')
2589 } else {
2590 f.writeln('')
2591 }
2592 f.stmts(branch.stmts)
2593 if keep_single_line {
2594 f.write(' ')
2595 }
2596 }
2597 if keep_single_line && f.line_len > max_len && !f.buffering {
2598 keep_single_line = false
2599 f.single_line_if = false
2600 f.out.go_back_to(start_pos)
2601 f.line_len = start_len
2602 f.empty_line = start_len == 0
2603 continue
2604 }
2605 break
2606 }
2607 f.write('}')
2608 f.single_line_if = false
2609 f.inside_comptime_if = false
2610 if node.post_comments.len > 0 {
2611 if keep_single_line {
2612 f.comments(node.post_comments,
2613 has_nl: false
2614 same_line: true
2615 prev_line: node.branches.last().body_pos.last_line
2616 )
2617 } else {
2618 f.writeln('')
2619 f.comments(node.post_comments,
2620 has_nl: false
2621 prev_line: node.branches.last().body_pos.last_line
2622 )
2623 }
2624 }
2625}
2626
2627fn branch_is_single_line(b ast.IfBranch) bool {
2628 if b.stmts.len == 1 && b.comments.len == 0 && stmt_is_single_line(b.stmts[0])
2629 && b.pos.line_nr == b.stmts[0].pos.line_nr {
2630 return true
2631 }
2632 return false
2633}
2634
2635fn sql_query_data_item_is_single_line(item ast.SqlQueryDataItem) bool {
2636 return match item {
2637 ast.SqlQueryDataLeaf {
2638 item.pre_comments.len == 0 && item.end_comments.len == 0
2639 && item.pos.line_nr == item.pos.last_line && expr_is_single_line(item.expr)
2640 }
2641 ast.SqlQueryDataIf {
2642 false
2643 }
2644 }
2645}
2646
2647fn sql_query_data_branch_is_single_line(branch ast.SqlQueryDataBranch) bool {
2648 return branch.end_comments.len == 0 && branch.pos.line_nr == branch.pos.last_line
2649 && branch.items.len == 1 && sql_query_data_item_is_single_line(branch.items[0])
2650}
2651
2652fn sql_query_data_item_pre_comments(item ast.SqlQueryDataItem) []ast.Comment {
2653 return match item {
2654 ast.SqlQueryDataLeaf { item.pre_comments }
2655 ast.SqlQueryDataIf { item.pre_comments }
2656 }
2657}
2658
2659fn sql_query_data_item_end_comments(item ast.SqlQueryDataItem) []ast.Comment {
2660 return match item {
2661 ast.SqlQueryDataLeaf { item.end_comments }
2662 ast.SqlQueryDataIf { item.end_comments }
2663 }
2664}
2665
2666fn sql_query_data_item_last_line(item ast.SqlQueryDataItem) int {
2667 return match item {
2668 ast.SqlQueryDataLeaf { item.pos.last_line }
2669 ast.SqlQueryDataIf { item.pos.last_line }
2670 }
2671}
2672
2673pub fn (mut f Fmt) if_guard_expr(node ast.IfGuardExpr) {
2674 for i, var in node.vars {
2675 if var.is_mut {
2676 f.write('mut ')
2677 }
2678 f.write(var.name)
2679 if i != node.vars.len - 1 {
2680 f.write(', ')
2681 }
2682 }
2683 f.write(' := ')
2684 f.expr(node.expr)
2685}
2686
2687pub fn (mut f Fmt) index_expr(node ast.IndexExpr) {
2688 f.expr(node.left)
2689 if node.is_gated {
2690 f.write('#')
2691 }
2692 last_index_expr_state := f.is_index_expr
2693 f.is_index_expr = true
2694 f.write('[')
2695 parts := if node.indices.len > 0 { node.indices } else { [node.index] }
2696 for i, part in parts {
2697 if i > 0 {
2698 f.write(', ')
2699 }
2700 f.expr(part)
2701 }
2702 f.write(']')
2703 f.is_index_expr = last_index_expr_state
2704 if node.or_expr.kind != .absent {
2705 f.or_expr(node.or_expr)
2706 }
2707}
2708
2709pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) {
2710 buffering_save := f.buffering
2711 is_wrappable_additive_minus := node.op == .minus
2712 && (is_additive_infix(node.left) || is_additive_infix(node.right))
2713 if !f.buffering && (node.op in [.logical_or, .and, .plus] || is_wrappable_additive_minus) {
2714 f.buffering = true
2715 }
2716 is_assign_save := f.is_assign
2717 if node.op == .left_shift {
2718 f.is_assign = true // To write ternary if on a single line
2719 }
2720 start_pos := f.out.len
2721 start_len := f.line_len
2722 mut redundant_par := false
2723 if node.left is ast.ParExpr && node.op in [.and, .logical_or] {
2724 if node.left.expr is ast.InfixExpr {
2725 if node.left.expr.op !in [.and, .logical_or] {
2726 redundant_par = true
2727 f.expr(node.left.expr)
2728 }
2729 }
2730 }
2731 if !redundant_par {
2732 f.expr(node.left)
2733 }
2734 if node.before_op_comments.len > 0 {
2735 f.comments(node.before_op_comments)
2736 }
2737 is_one_val_array_init := node.op in [.key_in, .not_in] && node.right is ast.ArrayInit
2738 && node.right.exprs.len == 1
2739 is_and := node.op == .amp && f.node_str(node.right).starts_with('&')
2740 if is_one_val_array_init && !f.inside_comptime_if {
2741 // `var in [val]` => `var == val`
2742 op := if node.op == .key_in { ' == ' } else { ' != ' }
2743 f.write(op)
2744 } else if is_and {
2745 f.write(' && ')
2746 } else {
2747 f.write(' ${node.op.str()} ')
2748 }
2749 if node.after_op_comments.len > 0 {
2750 f.comments(node.after_op_comments)
2751 f.write(' ')
2752 }
2753 if is_one_val_array_init && !f.inside_comptime_if {
2754 // `var in [val]` => `var == val`
2755 f.expr((node.right as ast.ArrayInit).exprs[0])
2756 } else if is_and {
2757 f.write(f.node_str(node.right).trim_string_left('&'))
2758 } else {
2759 redundant_par = false
2760 if node.right is ast.ParExpr && node.op in [.and, .logical_or] {
2761 if node.right.expr is ast.InfixExpr {
2762 if node.right.expr.op !in [.and, .logical_or] {
2763 redundant_par = true
2764 f.expr(node.right.expr)
2765 }
2766 }
2767 }
2768 if !redundant_par {
2769 f.expr(node.right)
2770 }
2771 }
2772 if !buffering_save && f.buffering {
2773 f.buffering = false
2774 if !f.single_line_if && f.line_len > max_len {
2775 is_cond := node.op in [.and, .logical_or]
2776 f.wrap_infix(start_pos, start_len, is_cond)
2777 }
2778 }
2779 f.is_assign = is_assign_save
2780 f.or_expr(node.or_block)
2781}
2782
2783pub fn (mut f Fmt) wrap_infix(start_pos int, start_len int, is_cond bool) {
2784 cut_span := f.out.len - start_pos
2785 infix_str := f.out.cut_last(cut_span)
2786 if !infix_str.contains_any_substr(['&&', '||', '+', '-']) {
2787 f.write(infix_str)
2788 return
2789 }
2790 f.line_len = start_len
2791 if start_len == 0 {
2792 f.empty_line = true
2793 }
2794 conditions, penalties := split_up_infix(infix_str, false, is_cond)
2795 f.write_splitted_infix(conditions, penalties, false, is_cond)
2796}
2797
2798fn is_additive_infix(expr ast.Expr) bool {
2799 return match expr {
2800 ast.InfixExpr { expr.op in [.plus, .minus] }
2801 else { false }
2802 }
2803}
2804
2805fn split_up_infix(infix_str string, ignore_paren bool, is_cond_infix bool) ([]string, []int) {
2806 mut conditions := ['']
2807 mut penalties := [5]
2808 or_pen := if infix_str.contains('&&') { 3 } else { 5 }
2809 parts := infix_str.split(' ')
2810 mut inside_paren := false
2811 mut ind := 0
2812 for p in parts {
2813 if is_cond_infix && p in ['&&', '||'] {
2814 if inside_paren {
2815 conditions[ind] += '${p} '
2816 } else {
2817 pen := if p == '||' { or_pen } else { 5 }
2818 penalties << pen
2819 conditions << '${p} '
2820 ind++
2821 }
2822 } else if !is_cond_infix && p in ['+', '-'] {
2823 if inside_paren {
2824 conditions[ind] += '${p} '
2825 } else {
2826 penalties << 5
2827 conditions[ind] += '${p} '
2828 conditions << ''
2829 ind++
2830 }
2831 } else {
2832 conditions[ind] += '${p} '
2833 if ignore_paren {
2834 continue
2835 }
2836 if p.starts_with('(') {
2837 inside_paren = true
2838 } else if p.ends_with(')') {
2839 inside_paren = false
2840 }
2841 }
2842 }
2843 return conditions, penalties
2844}
2845
2846const wsinfix_depth_max = 10
2847
2848fn (mut f Fmt) write_splitted_infix(conditions []string, penalties []int, ignore_paren bool, is_cond bool) {
2849 f.wsinfix_depth++
2850 defer { f.wsinfix_depth-- }
2851 for i, cnd in conditions {
2852 c := cnd.trim_space()
2853 if f.line_len + c.len < break_points[penalties[i]] {
2854 if (i > 0 && i < conditions.len) || (ignore_paren && i == 0 && c.len > 5 && c[3] == `(`) {
2855 f.write(' ')
2856 }
2857 f.write(c)
2858 } else {
2859 is_paren_expr := (c[0] == `(` || (c.len > 5 && c[3] == `(`)) && c.ends_with(')')
2860 final_len := ((f.indent + 1) * 4) + c.len
2861 if f.wsinfix_depth > wsinfix_depth_max {
2862 // limit indefinite recursion, by just giving up splitting:
2863 f.write(c)
2864 continue
2865 }
2866 if final_len > max_len && is_paren_expr {
2867 conds, pens := split_up_infix(c, true, is_cond)
2868 f.write_splitted_infix(conds, pens, true, is_cond)
2869 continue
2870 }
2871 mut is_wrap_needed := true
2872 if i == 0 {
2873 last_line_str := f.remove_new_line().trim_space()
2874 if last_line_str in ['if', 'for'] {
2875 f.write(' ')
2876 is_wrap_needed = false
2877 }
2878 }
2879 if is_wrap_needed {
2880 f.writeln('')
2881 }
2882 f.indent++
2883 f.write(c)
2884 f.indent--
2885 }
2886 }
2887}
2888
2889pub fn (mut f Fmt) likely(node ast.Likely) {
2890 if node.is_likely {
2891 f.write('_likely_')
2892 } else {
2893 f.write('_unlikely_')
2894 }
2895 f.write('(')
2896 f.expr(node.expr)
2897 f.write(')')
2898}
2899
2900pub fn (mut f Fmt) lock_expr(node ast.LockExpr) {
2901 mut num_locked := 0
2902 mut num_rlocked := 0
2903 for is_rlock in node.is_rlock {
2904 if is_rlock {
2905 num_rlocked++
2906 } else {
2907 num_locked++
2908 }
2909 }
2910 if num_locked > 0 || num_rlocked == 0 {
2911 f.write('lock')
2912 if num_locked > 0 {
2913 f.write(' ')
2914 }
2915 mut n := 0
2916 for i, v in node.lockeds {
2917 if !node.is_rlock[i] {
2918 if n > 0 {
2919 f.write(', ')
2920 }
2921 f.expr(v)
2922 n++
2923 }
2924 }
2925 }
2926 if num_rlocked > 0 {
2927 if num_locked > 0 {
2928 f.write('; ')
2929 }
2930 f.write('rlock ')
2931 mut n := 0
2932 for i, v in node.lockeds {
2933 if node.is_rlock[i] {
2934 if n > 0 {
2935 f.write(', ')
2936 }
2937 f.expr(v)
2938 n++
2939 }
2940 }
2941 }
2942 f.writeln(' {')
2943 f.stmts(node.stmts)
2944 f.write('}')
2945}
2946
2947pub fn (mut f Fmt) map_init(node ast.MapInit) {
2948 if node.keys.len == 0 && !node.has_update_expr {
2949 if node.typ > ast.void_type {
2950 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
2951 }
2952 if node.pos.line_nr == node.pos.last_line {
2953 f.write('{}')
2954 } else {
2955 f.writeln('{')
2956 f.comments(node.pre_cmnts, level: .indent)
2957 f.write('}')
2958 }
2959 return
2960 }
2961 f.writeln('{')
2962 f.indent++
2963 f.comments(node.pre_cmnts)
2964 if node.has_update_expr {
2965 f.write('...')
2966 f.expr(node.update_expr)
2967 f.comments(node.update_expr_comments,
2968 prev_line: node.update_expr_pos.last_line
2969 has_nl: false
2970 )
2971 f.writeln('')
2972 }
2973 mut max_field_len := 0
2974 mut skeys := []string{}
2975 for key in node.keys {
2976 skey := f.node_str(key).trim_space()
2977 skeys << skey
2978 skey_len := utf8_str_visible_length(skey)
2979 if skey_len > max_field_len {
2980 max_field_len = skey_len
2981 }
2982 }
2983 for i, _ in node.keys {
2984 skey := skeys[i]
2985 f.write(skey)
2986 f.write(': ')
2987 skey_len := utf8_str_visible_length(skey)
2988 f.write(' '.repeat(max_field_len - skey_len))
2989 f.expr(node.vals[i])
2990 f.comments(node.comments[i], prev_line: node.vals[i].pos().last_line, has_nl: false)
2991 f.writeln('')
2992 }
2993 f.indent--
2994 f.write('}')
2995}
2996
2997fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool, is_comptime bool) {
2998 if !branch.is_else {
2999 // normal branch
3000 f.is_mbranch_expr = true
3001 for j, expr in branch.exprs {
3002 estr := f.node_str(expr).trim_space()
3003 if f.line_len + estr.len + 2 > max_len {
3004 f.remove_new_line()
3005 f.writeln('')
3006 }
3007 f.write(estr)
3008 if j < branch.exprs.len - 1 {
3009 f.write(', ')
3010 }
3011 if j < branch.ecmnts.len && branch.ecmnts[j].len > 0 {
3012 f.write(' ')
3013 f.comments(branch.ecmnts[j])
3014 }
3015 }
3016 f.is_mbranch_expr = false
3017 } else {
3018 // else branch
3019 if is_comptime {
3020 f.write('\$else')
3021 } else {
3022 f.write('else')
3023 }
3024 }
3025 if branch.stmts.len == 0 {
3026 f.writeln(' {}')
3027 } else {
3028 if single_line {
3029 f.write(' { ')
3030 } else if branch.ecmnts.len > 0 && branch.ecmnts.last().len > 0 {
3031 f.writeln('{')
3032 } else {
3033 f.writeln(' {')
3034 }
3035 f.stmts(branch.stmts)
3036 if single_line {
3037 f.remove_new_line()
3038 f.writeln(' }')
3039 } else {
3040 f.writeln('}')
3041 }
3042 }
3043 f.comments(branch.post_comments, same_line: true)
3044}
3045
3046pub fn (mut f Fmt) match_expr(node ast.MatchExpr) {
3047 dollar := if node.is_comptime { '$' } else { '' }
3048 cond, cond_or_expr := match_cond_with_trailing_or_expr(node.cond)
3049 f.write('${dollar}match ')
3050 f.expr(cond)
3051 f.writeln(' {')
3052 f.indent++
3053 f.comments(node.comments)
3054 mut single_line := true
3055 for branch in node.branches {
3056 if branch.stmts.len > 1 || branch.pos.line_nr < branch.pos.last_line {
3057 single_line = false
3058 break
3059 }
3060 if branch.stmts.len == 0 {
3061 continue
3062 }
3063 if !stmt_is_single_line(branch.stmts[0]) {
3064 single_line = false
3065 break
3066 }
3067 }
3068 mut else_idx := -1
3069 for i, branch in node.branches {
3070 if branch.is_else {
3071 else_idx = i
3072 continue
3073 }
3074 f.match_branch(branch, single_line, node.is_comptime)
3075 }
3076 if else_idx >= 0 {
3077 f.match_branch(node.branches[else_idx], single_line, node.is_comptime)
3078 }
3079 f.indent--
3080 f.write('}')
3081 f.or_expr(cond_or_expr)
3082}
3083
3084fn match_cond_with_trailing_or_expr(expr ast.Expr) (ast.Expr, ast.OrExpr) {
3085 match expr {
3086 ast.CallExpr {
3087 if expr.or_block.kind == .block {
3088 mut cond := expr
3089 or_expr := cond.or_block
3090 cond.or_block = ast.OrExpr{}
3091 return ast.Expr(cond), or_expr
3092 }
3093 }
3094 ast.Ident {
3095 if expr.or_expr.kind == .block {
3096 mut cond := expr
3097 or_expr := cond.or_expr
3098 cond.or_expr = ast.OrExpr{}
3099 return ast.Expr(cond), or_expr
3100 }
3101 }
3102 ast.IndexExpr {
3103 if expr.or_expr.kind == .block {
3104 mut cond := expr
3105 or_expr := cond.or_expr
3106 cond.or_expr = ast.OrExpr{}
3107 return ast.Expr(cond), or_expr
3108 }
3109 }
3110 ast.ParExpr {
3111 cond, or_expr := match_cond_with_trailing_or_expr(expr.expr)
3112 if or_expr.kind == .block {
3113 mut par_expr := expr
3114 par_expr.expr = cond
3115 return ast.Expr(par_expr), or_expr
3116 }
3117 }
3118 ast.PrefixExpr {
3119 if expr.op == .arrow && expr.or_block.kind == .block {
3120 mut cond := expr
3121 or_expr := cond.or_block
3122 cond.or_block = ast.OrExpr{}
3123 return ast.Expr(cond), or_expr
3124 }
3125 }
3126 ast.SelectorExpr {
3127 if expr.or_block.kind == .block {
3128 mut cond := expr
3129 or_expr := cond.or_block
3130 cond.or_block = ast.OrExpr{}
3131 return ast.Expr(cond), or_expr
3132 }
3133 }
3134 else {}
3135 }
3136
3137 return expr, ast.OrExpr{}
3138}
3139
3140pub fn (mut f Fmt) offset_of(node ast.OffsetOf) {
3141 f.write('__offsetof(${f.type_to_str_using_aliases(node.struct_type, f.mod2alias)}, ${node.field})')
3142}
3143
3144pub fn (mut f Fmt) or_expr(node ast.OrExpr) {
3145 match node.kind {
3146 .absent {}
3147 .block {
3148 if node.stmts.len == 0 {
3149 f.write(' or {')
3150 if node.pos.line_nr != node.pos.last_line {
3151 f.writeln('')
3152 }
3153 f.write('}')
3154 return
3155 } else if expr_is_single_line(node) {
3156 // the control stmts (return/break/continue...) print a newline inside them,
3157 // so, since this'll all be on one line, trim any possible whitespace
3158 str := f.node_str(node.stmts[0]).trim_space()
3159 single_line := ' or { ${str} }'
3160 if single_line.len + f.line_len <= max_len {
3161 f.write(single_line)
3162 return
3163 }
3164 }
3165 // Make it multiline if the blocks has at least two stmts
3166 // or a single line would be too long
3167 f.writeln(' or {')
3168 f.stmts(node.stmts)
3169 f.write('}')
3170 }
3171 .propagate_option {
3172 f.write('?')
3173 }
3174 .propagate_result {
3175 f.write('!')
3176 }
3177 }
3178}
3179
3180pub fn (mut f Fmt) par_expr(node ast.ParExpr) {
3181 mut expr := node.expr
3182 expr = expr.remove_par()
3183 requires_paren := expr !is ast.Ident || node.comments.len > 0
3184 if requires_paren {
3185 f.par_level++
3186 f.write('(')
3187 }
3188 pre_comments := node.comments.filter(it.pos.pos < expr.pos().pos)
3189 post_comments := node.comments[pre_comments.len..]
3190 if pre_comments.len > 0 {
3191 f.comments(pre_comments)
3192 f.write(' ')
3193 }
3194 f.expr(expr)
3195 if post_comments.len > 0 {
3196 f.comments(post_comments)
3197 f.write(' ')
3198 }
3199 if requires_paren {
3200 f.par_level--
3201 f.write(')')
3202 }
3203}
3204
3205pub fn (mut f Fmt) postfix_expr(node ast.PostfixExpr) {
3206 f.expr(node.expr)
3207 // `$if foo ?`
3208 if node.op == .question {
3209 f.write(' ?')
3210 } else {
3211 f.write('${node.op}')
3212 }
3213 if node.is_c2v_prefix {
3214 f.write('$')
3215 }
3216}
3217
3218pub fn (mut f Fmt) prefix_expr(node ast.PrefixExpr) {
3219 // !(a in b) => a !in b, !(a is b) => a !is b
3220 if node.op == .not && node.right is ast.ParExpr {
3221 if node.right.expr is ast.InfixExpr {
3222 if node.right.expr.op in [.key_in, .not_in, .key_is, .not_is]
3223 && node.right.expr.right !is ast.InfixExpr {
3224 f.expr(node.right.expr.left)
3225 match node.right.expr.op {
3226 .key_in { f.write(' !in ') }
3227 .not_in { f.write(' in ') }
3228 .key_is { f.write(' !is ') }
3229 .not_is { f.write(' is ') }
3230 else {}
3231 }
3232
3233 f.expr(node.right.expr.right)
3234 return
3235 }
3236 }
3237 }
3238 f.write(node.op.str())
3239 f.expr(node.right)
3240 f.or_expr(node.or_block)
3241}
3242
3243pub fn (mut f Fmt) range_expr(node ast.RangeExpr) {
3244 f.expr(node.low)
3245 if f.is_mbranch_expr && !f.is_index_expr {
3246 f.write('...')
3247 } else {
3248 f.write('..')
3249 }
3250 f.expr(node.high)
3251}
3252
3253pub fn (mut f Fmt) select_expr(node ast.SelectExpr) {
3254 f.writeln('select {')
3255 f.indent++
3256 for branch in node.branches {
3257 if branch.comment.text != '' {
3258 f.comment(branch.comment, same_line: true)
3259 f.writeln('')
3260 }
3261 if branch.is_else {
3262 f.write('else {')
3263 } else {
3264 f.single_line_if = true
3265 match branch.stmt {
3266 ast.ExprStmt { f.expr(branch.stmt.expr) }
3267 else { f.stmt(branch.stmt) }
3268 }
3269
3270 f.single_line_if = false
3271 f.write(' {')
3272 }
3273 if branch.stmts.len > 0 {
3274 f.writeln('')
3275 f.stmts(branch.stmts)
3276 }
3277 f.writeln('}')
3278 if branch.post_comments.len > 0 {
3279 f.comments(branch.post_comments, same_line: true)
3280 }
3281 }
3282 f.indent--
3283 f.write('}')
3284}
3285
3286pub fn (mut f Fmt) selector_expr(node ast.SelectorExpr) {
3287 // TODO(StunxFS): Even though we ignored the JS backend, the `v/gen/js/tests/js.v`
3288 // file was still formatted/transformed, so it is specifically ignored here. Fix this.
3289 if f.file.language != .js && node.expr is ast.StringLiteral && node.field_name == 'str'
3290 && !f.pref.backend.is_js()
3291 && !f.file.path.ends_with(os.join_path('v', 'gen', 'js', 'tests', 'js.v')) {
3292 f.write('c')
3293 f.expr(node.expr)
3294 return
3295 }
3296 f.expr(node.expr)
3297 f.write('.')
3298 f.write(node.field_name)
3299 f.or_expr(node.or_block)
3300}
3301
3302pub fn (mut f Fmt) size_of(node ast.SizeOf) {
3303 f.write('sizeof')
3304 if node.is_type && !node.guessed_type {
3305 // the new form was explicitly written in the source code; keep it:
3306 f.write('[')
3307 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3308 f.write(']()')
3309 return
3310 }
3311 if node.is_type {
3312 f.write('(')
3313 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3314 f.write(')')
3315 } else {
3316 f.write('(')
3317 f.expr(node.expr)
3318 f.write(')')
3319 }
3320}
3321
3322pub fn (mut f Fmt) is_ref_type(node ast.IsRefType) {
3323 f.write('isreftype')
3324 if node.is_type && !node.guessed_type {
3325 // the new form was explicitly written in the source code; keep it:
3326 f.write('[')
3327 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3328 f.write(']()')
3329 return
3330 }
3331 if node.is_type {
3332 f.write('(')
3333 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3334 f.write(')')
3335 } else {
3336 f.write('(')
3337 f.expr(node.expr)
3338 f.write(')')
3339 }
3340}
3341
3342pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) {
3343 // sql app.db { select from Contributor where repo == id && user == 0 }
3344 f.write('sql ')
3345 f.expr(node.db_expr)
3346 f.writeln(' {')
3347 f.write('\t')
3348 if node.is_dynamic {
3349 f.write('dynamic ')
3350 }
3351 if node.is_insert {
3352 f.write('insert ')
3353 } else {
3354 f.write('select ')
3355 }
3356 if node.has_distinct {
3357 f.write('distinct ')
3358 }
3359 sym := f.table.sym(node.table_expr.typ)
3360 mut table_name := sym.name
3361 if !table_name.starts_with('C.') && !table_name.starts_with('JS.') {
3362 table_name = f.no_cur_mod(f.short_module(sym.name)) // TODO: f.type_to_str?
3363 }
3364 if node.aggregate_kind != .none {
3365 match node.aggregate_kind {
3366 .count {
3367 f.write('count ')
3368 }
3369 .sum, .avg, .min, .max {
3370 f.write('${node.aggregate_kind}(${node.aggregate_field}) ')
3371 }
3372 .none {}
3373 }
3374 } else if node.requested_fields.len > 0 {
3375 for i, requested_field in node.requested_fields {
3376 f.write(requested_field.name)
3377 if i < node.requested_fields.len - 1 {
3378 f.write(', ')
3379 }
3380 }
3381 } else {
3382 for i, fd in node.fields {
3383 f.write(fd.name)
3384 if i < node.fields.len - 1 {
3385 f.write(', ')
3386 }
3387 }
3388 }
3389 if node.aggregate_kind == .none && (node.requested_fields.len > 0 || node.fields.len > 0) {
3390 f.write(' ')
3391 }
3392 if node.is_insert {
3393 f.write('${node.inserted_var} into ${table_name}')
3394 } else {
3395 f.write('from ${table_name}')
3396 }
3397 // Format JOIN clauses
3398 for join in node.joins {
3399 f.writeln('')
3400 f.write('\t')
3401 match join.kind {
3402 .inner { f.write('join ') }
3403 .left { f.write('left join ') }
3404 .right { f.write('right join ') }
3405 .full_outer { f.write('full outer join ') }
3406 }
3407
3408 join_sym := f.table.sym(join.table_expr.typ)
3409 mut join_table_name := join_sym.name
3410 if !join_table_name.starts_with('C.') && !join_table_name.starts_with('JS.') {
3411 join_table_name = f.no_cur_mod(f.short_module(join_sym.name))
3412 }
3413 f.write('${join_table_name} on ')
3414 f.expr(join.on_expr)
3415 }
3416 if node.has_where {
3417 f.write(' where ')
3418 f.expr(node.where_expr)
3419 }
3420 if node.has_order {
3421 f.write(' order by ')
3422 f.expr(node.order_expr)
3423 if node.has_desc {
3424 f.write(' desc')
3425 }
3426 }
3427 if node.has_limit {
3428 f.write(' limit ')
3429 f.expr(node.limit_expr)
3430 }
3431 if node.has_offset {
3432 f.write(' offset ')
3433 f.expr(node.offset_expr)
3434 }
3435 f.writeln('')
3436 f.write('}')
3437 f.or_expr(node.or_expr)
3438}
3439
3440pub fn (mut f Fmt) sql_query_data_expr(node ast.SqlQueryDataExpr) {
3441 if node.items.len == 0 && node.end_comments.len == 0 {
3442 f.write('{}')
3443 return
3444 }
3445 f.writeln('{')
3446 f.indent++
3447 f.sql_query_data_items(node.items, node.end_comments)
3448 f.indent--
3449 f.write('}')
3450}
3451
3452fn (mut f Fmt) sql_query_data_items(items []ast.SqlQueryDataItem, end_comments []ast.Comment) {
3453 for idx, item in items {
3454 f.sql_query_data_comment_lines(sql_query_data_item_pre_comments(item))
3455 f.sql_query_data_item(item)
3456 if idx < items.len - 1 || end_comments.len > 0 {
3457 f.write(',')
3458 }
3459 item_end_comments := sql_query_data_item_end_comments(item)
3460 if item_end_comments.len > 0 {
3461 if item_end_comments[0].pos.line_nr == sql_query_data_item_last_line(item) {
3462 f.comments(item_end_comments, same_line: true, has_nl: true, level: .keep)
3463 } else {
3464 f.writeln('')
3465 f.sql_query_data_comment_lines(item_end_comments)
3466 }
3467 } else {
3468 f.writeln('')
3469 }
3470 }
3471 f.sql_query_data_comment_lines(end_comments)
3472}
3473
3474fn (mut f Fmt) sql_query_data_comment_lines(comments []ast.Comment) {
3475 for comment in comments {
3476 f.comment(comment)
3477 f.writeln('')
3478 }
3479}
3480
3481fn (mut f Fmt) sql_query_data_item(item ast.SqlQueryDataItem) {
3482 match item {
3483 ast.SqlQueryDataLeaf {
3484 f.expr(item.expr)
3485 }
3486 ast.SqlQueryDataIf {
3487 for idx, branch in item.branches {
3488 if idx == 0 {
3489 f.write('if ')
3490 f.expr(branch.cond)
3491 f.write(' ')
3492 } else if branch.cond is ast.EmptyExpr {
3493 f.write('else ')
3494 } else {
3495 f.write('else if ')
3496 f.expr(branch.cond)
3497 f.write(' ')
3498 }
3499 f.sql_query_data_branch_items(branch.items, branch.end_comments,
3500 sql_query_data_branch_is_single_line(branch))
3501 if idx < item.branches.len - 1 {
3502 f.write(' ')
3503 }
3504 }
3505 }
3506 }
3507}
3508
3509fn (mut f Fmt) sql_query_data_branch_items(items []ast.SqlQueryDataItem, end_comments []ast.Comment, keep_single_line bool) {
3510 if items.len == 0 && end_comments.len == 0 {
3511 f.write('{}')
3512 return
3513 }
3514 if keep_single_line {
3515 start_pos := f.out.len
3516 start_len := f.line_len
3517 f.write('{ ')
3518 f.sql_query_data_item(items[0])
3519 f.write(' }')
3520 if !f.out.after(start_pos).contains('\n') && f.line_len <= max_len {
3521 return
3522 }
3523 f.out.go_back_to(start_pos)
3524 f.line_len = start_len
3525 f.empty_line = start_len == 0
3526 }
3527 f.writeln('{')
3528 f.indent++
3529 f.sql_query_data_items(items, end_comments)
3530 f.indent--
3531 f.write('}')
3532}
3533
3534pub fn (mut f Fmt) char_literal(node ast.CharLiteral) {
3535 if node.val == r"\'" {
3536 f.write("`'`")
3537 return
3538 }
3539 if node.val.len == 1 {
3540 clit := node.val[0]
3541 if clit < 32 || clit > 127 || clit == 92 || clit == 96 {
3542 f.write('`\\x${clit.hex()}`')
3543 return
3544 }
3545 }
3546 f.write('`${node.val}`')
3547}
3548
3549pub fn (mut f Fmt) string_literal(node ast.StringLiteral) {
3550 quote := if node.val.contains("'") && !node.val.contains('"') { '"' } else { "'" }
3551 if node.is_raw {
3552 f.write('r')
3553 } else if node.language == ast.Language.c {
3554 f.write('c')
3555 } else if node.language == ast.Language.js {
3556 f.write('js')
3557 }
3558 if node.is_raw {
3559 f.write('${quote}${node.val}${quote}')
3560 } else {
3561 unescaped_val := node.val.replace('${bs}${bs}', '\x01').replace_each([
3562 "${bs}'",
3563 "'",
3564 '${bs}"',
3565 '"',
3566 ])
3567 s := unescaped_val.replace_each(['\x01', '${bs}${bs}', quote, '${bs}${quote}'])
3568 f.write('${quote}${s}${quote}')
3569 }
3570}
3571
3572pub fn (mut f Fmt) string_inter_literal(node ast.StringInterLiteral) {
3573 mut quote := "'"
3574 for val in node.vals {
3575 if val.contains('\\"') {
3576 quote = '"'
3577 break
3578 }
3579 if val.contains("\\'") {
3580 quote = "'"
3581 break
3582 }
3583 if val.contains('"') {
3584 quote = "'"
3585 }
3586 if val.contains("'") {
3587 quote = '"'
3588 }
3589 }
3590 // TODO: this code is very similar to ast.Expr.str()
3591 // serkonda7: it can not fully be replaced tho as ´f.expr()´ and `ast.Expr.str()`
3592 // work too different for the various exprs that are interpolated
3593 f.write(quote)
3594 for i, val in node.vals {
3595 unescaped_val := val.replace('${bs}${bs}', '\x01').replace_each([
3596 "${bs}'",
3597 "'",
3598 '${bs}"',
3599 '"',
3600 ])
3601 s := unescaped_val.replace_each(['\x01', '${bs}${bs}', quote, '${bs}${quote}'])
3602 f.write('${s}')
3603 if i >= node.exprs.len {
3604 break
3605 }
3606 f.write('$')
3607 fspec_str := node.get_fspec(i)
3608
3609 f.write('{')
3610 f.expr(node.exprs[i])
3611 f.write(fspec_str)
3612 f.write('}')
3613 }
3614 f.write(quote)
3615}
3616
3617pub fn (mut f Fmt) type_expr(node ast.TypeNode) {
3618 if node.stmt == ast.empty_stmt {
3619 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3620 } else {
3621 f.struct_decl(ast.StructDecl{ fields: (node.stmt as ast.StructDecl).fields }, true)
3622 }
3623}
3624
3625pub fn (mut f Fmt) type_of(node ast.TypeOf) {
3626 f.write('typeof')
3627 if node.is_type {
3628 f.write('[')
3629 f.write(f.type_to_str_using_aliases(node.typ, f.mod2alias))
3630 f.write(']()')
3631 } else {
3632 f.write('(')
3633 f.expr(node.expr)
3634 f.write(')')
3635 }
3636}
3637
3638pub fn (mut f Fmt) unsafe_expr(node ast.UnsafeExpr) {
3639 single_line := node.pos.line_nr >= node.pos.last_line
3640 f.write('unsafe {')
3641 if single_line {
3642 f.write(' ')
3643 } else {
3644 f.writeln('')
3645 f.indent++
3646 f.empty_line = true
3647 }
3648 f.expr(node.expr)
3649 if single_line {
3650 f.write(' ')
3651 } else {
3652 f.writeln('')
3653 f.indent--
3654 }
3655 f.write('}')
3656}
3657
3658fn (mut f Fmt) trace[T](fbase string, x &T) {
3659 if f.file.path_base == fbase {
3660 println('> f.trace | ${fbase:-10s} | ${voidptr(x):16} | ${x}')
3661 }
3662}
3663