diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v index 43c6396eba..fb4f1c4772 100644 --- a/cmd/tools/vtest-cleancode.v +++ b/cmd/tools/vtest-cleancode.v @@ -47,6 +47,7 @@ const verify_known_failing_exceptions = [ 'vlib/builtin/int_test.v' /* special number formatting that should be tested */, // TODOs and unfixed vfmt bugs 'vlib/v/gen/js/tests/js.v', /* local `hello` fn, gets replaced with module `hello` aliased as `hl` */ + 'vlib/v/tests/string_new_interpolation_test.v', /* new string interpolation */ ] const vfmt_verify_list = [ diff --git a/vlib/v/eval/gen/infix_gen.v b/vlib/v/eval/gen/infix_gen.v index 8139b5b3fc..6b422d74bd 100644 --- a/vlib/v/eval/gen/infix_gen.v +++ b/vlib/v/eval/gen/infix_gen.v @@ -9,7 +9,7 @@ const ( module eval import v.token import v.ast -fn(e Eval)infix_expr(left Object,right Object,op token.Kind,expecting ast.Type)Object{match op{' +fn(e Eval)infix_expr(left Object,right Object,op token.Kind,expecting ast.Type)Object{ match op{' footer = "else{ e.error('unknown infix expression: \$op')}}return empty // should e.error before this anyway } " @@ -37,26 +37,26 @@ fn main() { b.write_string(header) for enm, op in comparison { - b.write_string('.$enm{match left{') + b.write_string('.$enm{ match left{') for ct in compound_types { - b.write_string('$ct {match right{') + b.write_string('$ct { match right{') for ct2 in compound_types { - b.write_string('$ct2{return left.val${op}right.val}') + b.write_string('$ct2{ return left.val${op}right.val}') } for lt2 in literal_types { - b.write_string('$lt2{return left.val${op}right}') + b.write_string('$lt2{ return left.val${op}right}') } b.write_string("else{ e.error('invalid operands to $op: $ct and \$right.type_name()')}}}") } for lt in literal_types { - b.write_string('$lt {match right{') + b.write_string('$lt { match right{') for ct2 in compound_types { - b.write_string('$ct2{return left${op}right.val}') + b.write_string('$ct2{ return left${op}right.val}') } for lt2 in literal_types { - b.write_string('$lt2{return left${op}right}') + b.write_string('$lt2{ return left${op}right}') } - b.write_string("else {e.error('invalid operands to $op: ") + b.write_string("else { e.error('invalid operands to $op: ") b.write_string(if lt == 'i64' { 'int' } else { 'float' }) b.write_string(" literal and \$right.type_name()')}}}") } @@ -66,12 +66,12 @@ fn main() { b.write_string("else { e.error('invalid operands to $op: \$left.type_name() and \$right.type_name()')}}}") } for math, op in math_ops { - b.write_string('.$math{match left{') + b.write_string('.$math{ match left{') for ct in compound_types { if op in ['<<', '>>'] && ct == 'Float' { continue } - b.write_string('$ct {match right{') + b.write_string('$ct { match right{') for ct2 in compound_types { if op in ['<<', '>>'] && ct2 == 'Float' { continue @@ -117,13 +117,13 @@ fn main() { continue } unsafe_start, unsafe_end := if op in ['<<', '>>'] { 'unsafe{', '}' } else { '', '' } - b.write_string('$lt2{if expecting in ast.signed_integer_type_idxs{ return Int{ i64(left)${op}i64(right),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{ return Uint{ u64(left)${op}u64(right),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left)${op}i64(right))$unsafe_end}') + b.write_string('$lt2{ if expecting in ast.signed_integer_type_idxs{ return Int{ i64(left)${op}i64(right),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{ return Uint{ u64(left)${op}u64(right),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left)${op}i64(right))$unsafe_end}') if op !in ['<<', '>>'] { b.write_string('else if expecting in ast.float_type_idxs{ return Float{ f64(left)${op}f64(right), i8(e.type_to_size(expecting))}}else if expecting==ast.float_literal_type_idx{ return f64(f64(left)${op}f64(right))}') } b.write_string(uk_expect_footer) } - b.write_string("else {e.error('invalid operands to $op: ") + b.write_string("else { e.error('invalid operands to $op: ") b.write_string(if lt == 'i64' { 'int' } else { 'float' }) b.write_string(" literal and \$right.type_name()')}}}") } diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index b65afc3d95..966186bc01 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -1214,7 +1214,7 @@ fn (mut s Scanner) ident_string() string { u_escapes_pos << s.pos - 1 } } - // ${var} (ignore in vfmt mode) (skip \$) + // '${var}' (ignore in vfmt mode) (skip \$) if prevc == `$` && c == `{` && !is_raw && s.count_symbol_before(s.pos - 2, scanner.backslash) % 2 == 0 { s.is_inside_string = true @@ -1223,7 +1223,7 @@ fn (mut s Scanner) ident_string() string { s.pos -= 2 break } - // $var + // '$var' if prevc == `$` && util.is_name_char(c) && !is_raw && s.count_symbol_before(s.pos - 2, scanner.backslash) % 2 == 0 { s.is_inside_string = true @@ -1231,7 +1231,7 @@ fn (mut s Scanner) ident_string() string { s.pos -= 2 break } - // {var} (ignore in vfmt mode) (skip \{) + // '{var}' (ignore in vfmt mode) (skip \{) if c == `{` && util.is_name_char(s.text[s.pos + 1]) && prevc != `$` && !is_raw && s.count_symbol_before(s.pos - 1, scanner.backslash) % 2 == 0 { // Detect certain strings with "{" that are not interpolation: @@ -1258,6 +1258,33 @@ fn (mut s Scanner) ident_string() string { break } } + // '{var1}{var2}' + if prevc == `{` && util.is_name_char(c) && s.text[s.pos - 2] !in [`$`, `{`] && !is_raw + && s.count_symbol_before(s.pos - 2, scanner.backslash) % 2 == 0 { + // Detect certain strings with "{" that are not interpolation: + // e.g. "{init: " (no "}" at the end) + mut is_valid_inter := true + for i := s.pos; i < s.text.len; i++ { + if s.text[i] == `}` { + // No } in this string, so it's not a valid `{x}` interpolation + break + } + if s.text[i] in [`=`, `:`, `\n`, s.inter_quote] { + // We reached the end of the line or string without reaching "}". + // Also if there's "=", there's no way it's a valid interpolation expression: + // e.g. `println("{a.b = 42}")` `println('{foo:bar}')` + is_valid_inter = false + break + } + } + if is_valid_inter { + s.is_inside_string = true + s.is_enclosed_inter = true + // so that s.pos points to $ at the next step + s.pos -= 2 + break + } + } if c != scanner.backslash { backslash_count = 0 } diff --git a/vlib/v/tests/string_new_interpolation_test.v b/vlib/v/tests/string_new_interpolation_test.v new file mode 100644 index 0000000000..418e15ad05 --- /dev/null +++ b/vlib/v/tests/string_new_interpolation_test.v @@ -0,0 +1,9 @@ +fn test_string_new_interpolation() { + a, b, c, d := 1, 2, 3, 4 + + println('{a}{b}{c}{d}') + assert '{a}{b}{c}{d}' == '1234' + + println('{a} {b} {c} {d}') + assert '{a} {b} {c} {d}' == '1 2 3 4' +}