diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index dd0f31885a..d0300700af 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -4,6 +4,7 @@ module ast import v.table +import v.util import strings pub fn (node &FnDecl) modname() string { @@ -105,6 +106,65 @@ pub fn (x &InfixExpr) str() string { return '${x.left.str()} $x.op.str() ${x.right.str()}' } +// 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 +// the format specifier must be appended, separated by a colon: +// '$z $z.b $z.c.x ${x[4]} ${z:8.3f} ${a:-20} ${a>b+2}' +// This method creates the format specifier (including the colon) or an empty +// string if none is needed and also returns (as bool) if the expression +// must be enclosed in braces. + +pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) { + mut res := []string{} + needs_fspec := lit.need_fmts[i] || lit.pluss[i] || (lit.fills[i] && lit.fwidths[i] >= 0) || lit.fwidths[i] != 0 || lit.precisions[i] != 0 + mut needs_braces := needs_fspec + 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 == `.` { + needs_braces = true + } + } + } + if !needs_braces { + mut sub_expr := lit.exprs[i] + for { + match sub_expr { + Ident { + break + } + SelectorExpr { + sub_expr = it.expr + continue + } + else { + needs_braces = true + break + } + } + } + } + if needs_fspec { + res << ':' + if lit.pluss[i] { + res << '+' + } + if lit.fills[i] && lit.fwidths[i] >= 0 { + res << '0' + } + if lit.fwidths[i] != 0 { + res << '${lit.fwidths[i]}' + } + if lit.precisions[i] != 0 { + res << '.${lit.precisions[i]}' + } + if lit.need_fmts[i] { + res << '${lit.fmts[i]:c}' + } + } + return res.join(''), needs_braces +} + // string representaiton of expr pub fn (x Expr) str() string { match x { @@ -157,31 +217,14 @@ pub fn (x Expr) str() string { for i, val in it.vals { res << val if i >= it.exprs.len { - continue + break } res << '$' - needs_fspec := it.need_fmts[i] || it.pluss[i] || (it.fills[i] && it.fwidths[i] >= 0) || it.fwidths[i] != 0 || it.precisions[i] != 0 - if needs_fspec || (it.exprs[i] !is Ident && it.exprs[i] !is SelectorExpr) { + fspec_str, needs_braces := it.get_fspec_braces(i) + if needs_braces { res << '{' res << it.exprs[i].str() - if needs_fspec { - res << ':' - if it.pluss[i] { - res << '+' - } - if it.fills[i] && it.fwidths[i] >= 0 { - res << '0' - } - if it.fwidths[i] != 0 { - res << '${it.fwidths[i]}' - } - if it.precisions[i] != 0 { - res << '.${it.precisions[i]}' - } - if it.need_fmts[i] { - res << '${it.fmts[i]:c}' - } - } + res << fspec_str res << '}' } else { res << it.exprs[i].str() diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 3a21cd35c7..3b43a53dbf 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -5,6 +5,7 @@ module fmt import v.ast import v.table +import v.util import strings const ( @@ -715,36 +716,19 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } } ast.StringInterLiteral { + // TODO: this code is very similar to ast.Expr.str() f.write("'") for i, val in it.vals { f.write(val) if i >= it.exprs.len { - continue + break } f.write('$') - needs_fspec := it.need_fmts[i] || it.pluss[i] || (it.fills[i] && it.fwidths[i] >= - 0) || it.fwidths[i] != 0 || it.precisions[i] != 0 - if needs_fspec || (it.exprs[i] !is ast.Ident && it.exprs[i] !is ast.SelectorExpr) { + fspec_str, needs_braces := it.get_fspec_braces(i) + if needs_braces { f.write('{') f.expr(it.exprs[i]) - if needs_fspec { - f.write(':') - if it.pluss[i] { - f.write('+') - } - if it.fills[i] && it.fwidths[i] >= 0 { - f.write('0') - } - if it.fwidths[i] != 0 { - f.write('${it.fwidths[i]}') - } - if it.precisions[i] != 0 { - f.write('.${it.precisions[i]}') - } - if it.need_fmts[i] { - f.write('${it.fmts[i]:c}') - } - } + f.write(fspec_str) f.write('}') } else { f.expr(it.exprs[i]) diff --git a/vlib/v/fmt/tests/string_interpolation_expected.vv b/vlib/v/fmt/tests/string_interpolation_expected.vv new file mode 100644 index 0000000000..8f0ebdca52 --- /dev/null +++ b/vlib/v/fmt/tests/string_interpolation_expected.vv @@ -0,0 +1,22 @@ +struct Aa { + xy int +} + +struct Bb { + a Aa +} + +struct Cc { + a []Aa +} + +fn main() { + st := Bb{Aa{5}} + ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} + aa := Aa{-13} + z := -14.75 + println('$st.a.xy ${ar.a[2].xy} $aa.xy $z') + 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}') +} diff --git a/vlib/v/fmt/tests/string_interpolation_input.vv b/vlib/v/fmt/tests/string_interpolation_input.vv new file mode 100644 index 0000000000..9c54e8dea4 --- /dev/null +++ b/vlib/v/fmt/tests/string_interpolation_input.vv @@ -0,0 +1,22 @@ +struct Aa { + xy int +} + +struct Bb { + a Aa +} + +struct Cc { + a []Aa +} + +fn main() { + st := Bb{Aa{5}} + ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} + aa := Aa{-13} + z := -14.75 + println('${st.a.xy} ${ar.a[2].xy} ${aa.xy} ${z}') + 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}') +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 3f2b7bd391..d7fcde4da5 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1177,7 +1177,6 @@ fn (mut p Parser) string_expr() ast.Expr { mut fill := false mut fmt := `_` // placeholder if p.tok.kind == .colon { - has_fmt = true p.next() // ${num:-2d} if p.tok.kind == .minus { @@ -1205,6 +1204,7 @@ fn (mut p Parser) string_expr() ast.Expr { if p.tok.kind == .name { if p.tok.lit.len == 1 { fmt = p.tok.lit[0] + has_fmt = true p.next() } else { p.error('format specifier may only be one letter')