fmt: further fixes for string interpolation and builtin macros

pull/5415/head^2
Uwe Krüger 2020-06-18 18:48:23 +02:00 committed by GitHub
parent f526754535
commit 3b6e66db0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 90 additions and 32 deletions

View File

@ -185,7 +185,7 @@ fn html_highlight(code string, tb &table.Table) string {
} else { tok.lit }
return if typ in [.unone, .name] { lit } else { '<span class="token $typ">$lit</span>' }
}
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)

View File

@ -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')
}

View File

@ -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 {

View File

@ -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(', ')
}
}
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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}')
}

View File

@ -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}')
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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')
}