From 3b6e66db0df71f4bf3d1a154bc48652ff323ec35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Thu, 18 Jun 2020 18:48:23 +0200 Subject: [PATCH] fmt: further fixes for string interpolation and builtin macros --- cmd/tools/vdoc.v | 2 +- cmd/tools/vfmt.v | 2 +- vlib/v/ast/str.v | 14 ++++++- vlib/v/fmt/fmt.v | 37 ++++++++++++++++--- vlib/v/fmt/fmt_keep_test.v | 6 ++- vlib/v/fmt/fmt_test.v | 6 ++- vlib/v/fmt/fmt_vlib_test.v | 6 ++- .../tests/string_interpolation_expected.vv | 10 +++++ .../v/fmt/tests/string_interpolation_input.vv | 10 +++++ vlib/v/parser/parser.v | 6 +-- vlib/v/scanner/scanner.v | 15 +++++--- vlib/v/scanner/scanner_test.v | 2 +- vlib/v/util/scanning.v | 6 --- 13 files changed, 90 insertions(+), 32 deletions(-) diff --git a/cmd/tools/vdoc.v b/cmd/tools/vdoc.v index 24b9022235..30df2d5a4c 100644 --- a/cmd/tools/vdoc.v +++ b/cmd/tools/vdoc.v @@ -185,7 +185,7 @@ fn html_highlight(code string, tb &table.Table) string { } else { tok.lit } return if typ in [.unone, .name] { lit } else { '$lit' } } - s := scanner.new_scanner(code, .parse_comments) + s := scanner.new_scanner(code, .parse_comments, false) mut tok := s.scan() mut next_tok := s.scan() mut buf := strings.new_builder(200) diff --git a/cmd/tools/vfmt.v b/cmd/tools/vfmt.v index b3fa461c75..af6b95c289 100644 --- a/cmd/tools/vfmt.v +++ b/cmd/tools/vfmt.v @@ -140,7 +140,7 @@ fn main() { fn (foptions &FormatOptions) format_file(file string) { mut prefs := pref.new_preferences() - prefs.is_fmt = util.is_fmt() + prefs.is_fmt = true if foptions.is_verbose { eprintln('vfmt2 running fmt.fmt over file: $file') } diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 7e508309ec..b57df39a8c 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -107,7 +107,8 @@ pub fn (x &InfixExpr) str() string { } // Expressions in string interpolations may have to be put in braces if they -// are non-trivial or if a format specification is given. In the latter case +// are non-trivial, if they would interfere with the next character or if a +// format specification is given. In the latter case // the format specifier must be appended, separated by a colon: // '$z $z.b $z.c.x ${x[4]} ${z:8.3f} ${a:-20} ${a>b+2}' // This method creates the format specifier (including the colon) or an empty @@ -121,7 +122,7 @@ pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) { if !needs_braces { if i+1 < lit.vals.len && lit.vals[i+1].len > 0 { next_char := lit.vals[i+1][0] - if util.is_func_char(next_char) || next_char == `.` { + if util.is_func_char(next_char) || next_char == `.` || next_char == `(` { needs_braces = true } } @@ -131,6 +132,15 @@ pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) { for { match sub_expr as sx { Ident { + if sx.name[0] == `@` { + needs_braces = true + } + break + } + CallExpr { + if sx.args.len != 0 { + needs_braces = true + } break } SelectorExpr { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 3b43a53dbf..cda90819bd 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -5,7 +5,6 @@ module fmt import v.ast import v.table -import v.util import strings const ( @@ -29,6 +28,7 @@ pub mut: file ast.File did_imports bool is_assign bool + is_inside_interp bool auto_imports []string // automatically inserted imports that the user forgot to specify import_pos int // position of the imports in the resulting string for later autoimports insertion used_imports []string // to remove unused imports @@ -619,8 +619,12 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } ast.InfixExpr { f.expr(it.left) - f.write(' $it.op.str() ') - f.wrap_long_line() + if f.is_inside_interp { + f.write('$it.op.str()') + } else { + f.write(' $it.op.str() ') + f.wrap_long_line() + } f.expr(it.right) } ast.IndexExpr { @@ -717,7 +721,19 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } ast.StringInterLiteral { // TODO: this code is very similar to ast.Expr.str() - f.write("'") + mut contains_single_quote := false + for val in it.vals { + if val.contains("'") { + contains_single_quote = true + break + } + } + if contains_single_quote { + f.write('"') + } else { + f.write("'") + } + f.is_inside_interp = true for i, val in it.vals { f.write(val) if i >= it.exprs.len { @@ -734,7 +750,12 @@ pub fn (mut f Fmt) expr(node ast.Expr) { f.expr(it.exprs[i]) } } - f.write("'") + f.is_inside_interp = false + if contains_single_quote { + f.write('"') + } else { + f.write("'") + } } ast.StructInit { f.struct_init(it) @@ -782,7 +803,11 @@ pub fn (mut f Fmt) call_args(args []ast.CallArg) { } f.expr(arg.expr) if i < args.len - 1 { - f.write(', ') + if f.is_inside_interp { + f.write(',') + } else { + f.write(', ') + } } } } diff --git a/vlib/v/fmt/fmt_keep_test.v b/vlib/v/fmt/fmt_keep_test.v index 57f6dada68..2083d11373 100644 --- a/vlib/v/fmt/fmt_keep_test.v +++ b/vlib/v/fmt/fmt_keep_test.v @@ -44,8 +44,10 @@ fn test_fmt() { continue } table := table.new_table() - file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{}, &ast.Scope{ - parent: 0 + file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{ + is_fmt: true + }, &ast.Scope{ + parent: 0 }) result_ocontent := fmt.fmt(file_ast, table, false) if expected_ocontent != result_ocontent { diff --git a/vlib/v/fmt/fmt_test.v b/vlib/v/fmt/fmt_test.v index 3ccbcad3a8..32787c0702 100644 --- a/vlib/v/fmt/fmt_test.v +++ b/vlib/v/fmt/fmt_test.v @@ -44,8 +44,10 @@ fn test_fmt() { continue } table := table.new_table() - file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{}, &ast.Scope{ - parent: 0 + file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{ + is_fmt: true + }, &ast.Scope{ + parent: 0 }) result_ocontent := fmt.fmt(file_ast, table, false) if expected_ocontent != result_ocontent { diff --git a/vlib/v/fmt/fmt_vlib_test.v b/vlib/v/fmt/fmt_vlib_test.v index 5610da8faf..8c50442c3b 100644 --- a/vlib/v/fmt/fmt_vlib_test.v +++ b/vlib/v/fmt/fmt_vlib_test.v @@ -43,8 +43,10 @@ fn test_vlib_fmt() { continue } table := table.new_table() - file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{}, &ast.Scope{ - parent: 0 + file_ast := parser.parse_file(ipath, table, .parse_comments, &pref.Preferences{ + is_fmt: true + }, &ast.Scope{ + parent: 0 }) result_ocontent := fmt.fmt(file_ast, table, false) if expected_ocontent != result_ocontent { diff --git a/vlib/v/fmt/tests/string_interpolation_expected.vv b/vlib/v/fmt/tests/string_interpolation_expected.vv index 8f0ebdca52..9855d2edca 100644 --- a/vlib/v/fmt/tests/string_interpolation_expected.vv +++ b/vlib/v/fmt/tests/string_interpolation_expected.vv @@ -10,6 +10,14 @@ struct Cc { a []Aa } +fn (c &Cc) f() int { + return c.a[0].xy +} + +fn (c &Cc) g(k, l int) int { + return c.a[k].xy + l +} + fn main() { st := Bb{Aa{5}} ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} @@ -19,4 +27,6 @@ fn main() { println('$st.a.xy${ar.a[2].xy}$aa.xy$z') println('${st.a.xy}ya ${ar.a[2].xy}X2 ${aa.xy}.b ${z}3') println('${z:-5} ${z:+5.3} ${z:+09.3f} ${z:-7.2} ${z:+09} ${z:08.3f}') + println('$ar.f() ${ar.g(1,2)} ${ar.a}() ${z}(') + println('${z>12.3*z-3} ${@VEXE} ${4*5}') } diff --git a/vlib/v/fmt/tests/string_interpolation_input.vv b/vlib/v/fmt/tests/string_interpolation_input.vv index 9c54e8dea4..31b109d7ed 100644 --- a/vlib/v/fmt/tests/string_interpolation_input.vv +++ b/vlib/v/fmt/tests/string_interpolation_input.vv @@ -10,6 +10,14 @@ struct Cc { a []Aa } +fn (c &Cc) f() int { + return c.a[0].xy +} + +fn (c &Cc) g(k, l int) int { + return c.a[k].xy+l +} + fn main() { st := Bb{Aa{5}} ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} @@ -19,4 +27,6 @@ fn main() { println('${st.a.xy}${ar.a[2].xy}${aa.xy}${z}') println('${st.a.xy}ya ${ar.a[2].xy}X2 ${aa.xy}.b ${z}3') println('${z:-5} ${z:+5.3} ${z:+09.3f} ${z:-07.2} ${z:+009} ${z:008.3f}') + println('${ar.f()} ${ar.g(1, 2)} ${ar.a}() ${z}(') + println('${z > 12.3 * z - 3} ${@VEXE} ${4 * 5}') } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index a4a81d7b58..1d9405d729 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -60,7 +60,7 @@ mut: // for tests pub fn parse_stmt(text string, table &table.Table, scope &ast.Scope) ast.Stmt { - s := scanner.new_scanner(text, .skip_comments) + s := scanner.new_scanner(text, .skip_comments, false) mut p := Parser{ scanner: s table: table @@ -77,7 +77,7 @@ pub fn parse_stmt(text string, table &table.Table, scope &ast.Scope) ast.Stmt { } pub fn parse_text(text string, b_table &table.Table, pref &pref.Preferences, scope, global_scope &ast.Scope) ast.File { - s := scanner.new_scanner(text, .skip_comments) + s := scanner.new_scanner(text, .skip_comments, pref.is_fmt) mut p := Parser{ scanner: s table: b_table @@ -100,7 +100,7 @@ pub fn parse_file(path string, b_table &table.Table, comments_mode scanner.Comme // panic(err) // } mut p := Parser{ - scanner: scanner.new_scanner_file(path, comments_mode) + scanner: scanner.new_scanner_file(path, comments_mode, pref.is_fmt) comments_mode: comments_mode table: b_table file_name: path diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index aa72459d6d..3cd4163e8d 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -94,7 +94,7 @@ pub enum CommentsMode { } // new scanner from file. -pub fn new_scanner_file(file_path string, comments_mode CommentsMode) &Scanner { +pub fn new_scanner_file(file_path string, comments_mode CommentsMode, is_fmt bool) &Scanner { if !os.exists(file_path) { verror("$file_path doesn't exist") } @@ -102,20 +102,20 @@ pub fn new_scanner_file(file_path string, comments_mode CommentsMode) &Scanner { verror(err) return voidptr(0) } - mut s := new_scanner(raw_text, comments_mode) // .skip_comments) + mut s := new_scanner(raw_text, comments_mode, is_fmt) // .skip_comments) // s.init_fmt() s.file_path = file_path return s } // new scanner from string. -pub fn new_scanner(text string, comments_mode CommentsMode) &Scanner { +pub fn new_scanner(text string, comments_mode CommentsMode, is_fmt bool) &Scanner { s := &Scanner{ text: text is_print_line_on_error: true is_print_colored_error: true is_print_rel_paths_on_error: true - is_fmt: util.is_fmt() + is_fmt: is_fmt comments_mode: comments_mode } return s @@ -883,6 +883,9 @@ fn (mut s Scanner) text_scan() token.Token { `@` { s.pos++ name := s.ident_name() + if s.is_fmt { + return s.new_token(.name, '@' + name, name.len+1) + } // @FN => will be substituted with the name of the current V function // @MOD => will be substituted with the name of the current V module // @STRUCT => will be substituted with the name of the current V struct @@ -1204,14 +1207,14 @@ fn (mut s Scanner) ident_string() string { s.error('0 character in a string literal') } // ${var} (ignore in vfmt mode) - if c == `{` && prevc == `$` && !is_raw && !s.is_fmt && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { + if c == `{` && prevc == `$` && !is_raw && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { s.is_inside_string = true // so that s.pos points to $ at the next step s.pos -= 2 break } // $var - if util.is_name_char(c) && prevc == `$` && !s.is_fmt && !is_raw && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { + if util.is_name_char(c) && prevc == `$` && !is_raw && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { s.is_inside_string = true s.is_inter_start = true s.pos -= 2 diff --git a/vlib/v/scanner/scanner_test.v b/vlib/v/scanner/scanner_test.v index 34dc113cee..1ecf25bc42 100644 --- a/vlib/v/scanner/scanner_test.v +++ b/vlib/v/scanner/scanner_test.v @@ -45,7 +45,7 @@ fn fn_name_mod_level_high_order(cb fn(int)) { } fn scan_kinds(text string) []token.Kind { - mut scanner := new_scanner(text, .skip_comments) + mut scanner := new_scanner(text, .skip_comments, false) mut token_kinds := []token.Kind{} for { tok := scanner.scan() diff --git a/vlib/v/util/scanning.v b/vlib/v/util/scanning.v index 9fc7e7e39b..072f7145d0 100644 --- a/vlib/v/util/scanning.v +++ b/vlib/v/util/scanning.v @@ -1,7 +1,5 @@ module util -import os - [inline] pub fn is_name_char(c byte) bool { return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` @@ -43,7 +41,3 @@ pub fn good_type_name(s string) bool { pub fn cescaped_path(s string) string { return s.replace('\\', '\\\\') } - -pub fn is_fmt() bool { - return os.executable().contains('vfmt') -}