diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 8c3121a040..833a0fd595 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1098,6 +1098,7 @@ fn (mut g JsGen) gen_assign_expr(it ast.AssignExpr) { tmp_var := g.new_tmp_var() g.write('const $tmp_var = ') g.expr(it.val) + return } // NB: The expr has to go *before* inside_map_set as it's defined there @@ -1326,10 +1327,9 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { } fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { - // TODO Implement `tos3` - g.write('tos3(`') + g.write('`') for i, val in it.vals { - escaped_val := val.replace_each(['`', '\`', '\r\n', '\n']) + escaped_val := val.replace('`', '\\`') g.write(escaped_val) if i >= it.exprs.len { continue @@ -1338,39 +1338,19 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { sfmt := it.expr_fmts[i] g.write('\${') if sfmt.len > 0 { - fspec := sfmt[sfmt.len - 1] - if fspec == `s` && it.expr_types[i] == table.string_type { - g.expr(expr) - g.write('.str') - } else { - g.expr(expr) - } - } else if it.expr_types[i] == table.string_type { - // `name.str` + // TODO: Handle formatting g.expr(expr) - g.write('.str') - } else if it.expr_types[i] == table.bool_type { - // `expr ? "true" : "false"` - g.expr(expr) - g.write(' ? "true" : "false"') } else { sym := g.table.get_type_symbol(it.expr_types[i]) - match sym.kind { - .struct_ { - g.expr(expr) - if sym.has_method('str') { - g.write('.str()') - } - } - else { - g.expr(expr) - } + g.expr(expr) + if sym.kind == .struct_ && sym.has_method('str') { + g.write('.str()') } } g.write('}') } - g.write('`)') + g.write('`') } fn (mut g JsGen) gen_struct_init(it ast.StructInit) { diff --git a/vlib/v/gen/js/tests/interp.js b/vlib/v/gen/js/tests/interp.js new file mode 100644 index 0000000000..e118984bd6 --- /dev/null +++ b/vlib/v/gen/js/tests/interp.js @@ -0,0 +1,260 @@ +// V_COMMIT_HASH 8a24d7d +// V_CURRENT_COMMIT_HASH 123d788 +// Generated by the V compiler + +"use strict"; + +/** @namespace builtin */ +const builtin = (function () { + /** + * @function + * @param {any} s + * @returns {void} + */ + function println(s) { + console.log(s); + } + + /** + * @function + * @param {any} s + * @returns {void} + */ + function print(s) { + process.stdout.write(s); + } + + /* module exports */ + return { + println, + print + }; +})(); + +/** @namespace main */ +const main = (function () { + /** + * @function + * @param {string} s1 + * @param {string} s2 + * @returns {void} + */ + function test_fn(s1, s2) { + builtin.print((s1 === s2 ? "true" : "false")); + builtin.print("\t=> "); + builtin.println(`"${s1}", "${s2}"`); + } + + /** + * @function + * @returns {void} + */ + function simple_string_interpolation() { + /** @type {string} */ + const a = "Hello"; + /** @type {string} */ + const b = "World"; + /** @type {string} */ + const res = `${a} ${b}`; + test_fn(res, "Hello World"); + } + + /** + * @function + * @returns {void} + */ + function mixed_string_interpolation() { + /** @type {number} */ + const num = 7; + /** @type {string} */ + const str = "abc"; + /** @type {string} */ + const s1 = `number=${num}`; + test_fn(s1, "number=7"); + /** @type {string} */ + const s2 = `string=${str}`; + test_fn(s2, "string=abc"); + /** @type {string} */ + const s3 = `a: ${num} | b: ${str}`; + test_fn(s3, "a: 7 | b: abc"); + } + + /** + * @function + * @returns {void} + */ + function formatted_string_interpolation() { + /** @type {string} */ + const x = "abc"; + /** @type {string} */ + const axb = `a:${x}:b`; + test_fn(axb, "a:abc:b"); + /** @type {string} */ + const x_10 = `a:${x}:b`; + /** @type {string} */ + const x10_ = `a:${x}:b`; + test_fn(x_10, "a: abc:b"); + test_fn(x10_, "a:abc :b"); + /** @type {number} */ + const i = 23; + /** @type {string} */ + const si_right = `${i}`; + /** @type {string} */ + const si__left = `${i}`; + test_fn(si_right, " 23"); + test_fn(si__left, "23 "); + } + + /** + * @function + * @returns {void} + */ + function implicit_str() { + /** @type {number} */ + const i = 42; + test_fn(`int ${i}`, "int 42"); + test_fn(`${i}`, "42"); + /** @type {boolean} */ + const check = `${i}` === "42"; + /** @type {string} */ + const text = `${i}` + "42"; + test_fn(text, "4242"); + } + + /** + * @function + * @returns {void} + */ + function string_interpolation_percent_escaping() { + /** @type {string} */ + const test = "hello"; + /** @type {string} */ + const hello = "world"; + /** @type {string} */ + const x = `%.*s${hello}${test} |${hello}|`; + test_fn(x, "%.*sworldhello |world |"); + } + + /** + * @function + * @returns {void} + */ + function string_interpolation_string_prefix() { + /** @type {string} */ + const r = "r"; + /** @type {string} */ + const rr = `${r}${r}`; + test_fn(rr, "rr"); + /** @type {string} */ + const c = "c"; + /** @type {string} */ + const cc = `${c}${c}`; + test_fn(cc, "cc"); + /** @type {string} */ + const js = "js"; + /** @type {string} */ + const jsjs = `${js}${js}`; + test_fn(jsjs, "jsjs"); + } + + /** + * @function + * @returns {void} + */ + function interpolation_string_prefix_expr() { + /** @type {number} */ + const r = 1; + /** @type {number} */ + const c = 2; + /** @type {number} */ + const js = 1; + test_fn(`>${3 + r}<`, ">4<"); + test_fn(`${r === js} ${js}`, "true 1"); + test_fn(`>${js + c} ${js + r === c}<`, ">3 true<"); + } + + /** + * @function + * @returns {void} + */ + function utf8_string_interpolation() { + /** @type {string} */ + const a = "à-côté"; + /** @type {string} */ + const st = "Sträßle"; + /** @type {string} */ + const m = "10€"; + test_fn(`${a} ${st} ${m}`, "à-côté Sträßle 10€"); + /** @type {string} */ + const zz = `>${a}< >${st}< >${m}<-`; + /** @type {string} */ + const zz_expected = "> à-côté< >Sträßle < > 10€<-"; + test_fn(zz, zz_expected); + /** @type {string} */ + const e = "€"; + test_fn(`100.00 ${e}`, "100.00 €"); + /** @type {string} */ + const m2 = "Москва́"; + /** @type {string} */ + const d = "Antonín Dvořák"; + test_fn(`:${m2}:${d}:`, ": Москва́:Antonín Dvořák :"); + /** @type {string} */ + const g = "Πελοπόννησος"; + test_fn(`>${g}<`, ">Πελοπόννησος <"); + } + + /** + * @constructor + * @param {{v1?: number, v2?: number}} init + */ + function Sss({ v1 = 0, v2 = 0 }) { + this.v1 = v1 + this.v2 = v2 + }; + Sss.prototype = { + /** @type {number} */ + v1: 0, + /** @type {number} */ + v2: 0, + /** + * @function + * @returns {string} + */ + str() { + const s = this; + return `[${s.v1}, ${s.v2}]`; + } + }; + + + /** + * @function + * @returns {void} + */ + function string_interpolation_str_evaluation() { + /** @type {Sss} */ + let x = new Sss({ + v1: 17, + v2: 13.455893 + }); + test_fn(`${x.str()}`, "[17, 13.456]"); + } + + /* program entry point */ + (function() { + simple_string_interpolation(); + mixed_string_interpolation(); + formatted_string_interpolation(); + implicit_str(); + string_interpolation_percent_escaping(); + string_interpolation_string_prefix(); + interpolation_string_prefix_expr(); + utf8_string_interpolation(); + string_interpolation_str_evaluation(); + })(); + + /* module exports */ + return {}; +})(); + + diff --git a/vlib/v/gen/js/tests/interp.v b/vlib/v/gen/js/tests/interp.v new file mode 100644 index 0000000000..b5f59bc3f4 --- /dev/null +++ b/vlib/v/gen/js/tests/interp.v @@ -0,0 +1,188 @@ +fn test_fn(s1, s2 string) { + print(if s1 == s2 {'true'} else {'false'}) + print('\t=> ') + println('"$s1", "$s2"') +} + +fn simple_string_interpolation() { + a := 'Hello' + b := 'World' + res := '$a $b' + test_fn(res, 'Hello World') +} + +fn mixed_string_interpolation() { + num := 7 + str := 'abc' + s1 := 'number=$num' + test_fn(s1, 'number=7') + s2 := 'string=$str' + test_fn(s2, 'string=abc') + s3 := 'a: $num | b: $str' + test_fn(s3, 'a: 7 | b: abc') +} + +fn formatted_string_interpolation() { + x := 'abc' + axb := 'a:$x:b' + test_fn(axb, 'a:abc:b') + x_10 := 'a:${x:10s}:b' + x10_ := 'a:${x:-10s}:b' + test_fn(x_10, 'a: abc:b') + test_fn(x10_, 'a:abc :b') + i := 23 + si_right := '${i:10d}' + si__left := '${i:-10d}' + test_fn(si_right, ' 23') + test_fn(si__left, '23 ') +} + +/* +excape_dollar_in_string() +fn excape_dollar_in_string() { + i := 42 + test_fn('($i)', '(42)') + println('(\$i)'.contains('i') && !'(\$i)'.contains('42')) + println(!'(\\$i)'.contains('i') && '(\\$i)'.contains('42') && '(\\$i)'.contains('\\')) + println('(\\\$i)'.contains('i') && !'(\\\$i)'.contains('42') && '(\\$i)'.contains('\\')) + println(!'(\\\\$i)'.contains('i') && '(\\\\$i)'.contains('42') && '(\\\\$i)'.contains('\\\\')) + test_fn('(${i})', '(42)') + println('(\${i})'.contains('i') && !'(\${i})'.contains('42')) + println(!'(\\${i})'.contains('i') && '(\\${i})'.contains('42') && '(\\${i})'.contains('\\')) + println('(\\\${i})'.contains('i') && !'(\\\${i})'.contains('42') && '(\\${i})'.contains('\\')) + println(!'(\\\\${i})'.contains('i') && '(\\\\${i})'.contains('42') && '(\\\\${i})'.contains('\\\\')) + test_fn(i, 42) +} +*/ + +fn implicit_str() { + i := 42 + test_fn('int $i', 'int 42') + test_fn('$i', '42') + check := '$i' == '42' + //println(check) + text := '$i' + '42' + test_fn(text, '4242') +} + +fn string_interpolation_percent_escaping() { + test := 'hello' + hello := 'world' + x := '%.*s$hello$test |${hello:-30s}|' + test_fn(x, '%.*sworldhello |world |') +} + +fn string_interpolation_string_prefix() { + // `r`, `c` and `js` are also used as a string prefix. + r := 'r' + rr := '$r$r' + test_fn(rr, 'rr') + c := 'c' + cc := '$c$c' + test_fn(cc, 'cc') + js := 'js' + jsjs := '$js$js' + test_fn(jsjs, 'jsjs') +} + +fn interpolation_string_prefix_expr() { + r := 1 + c := 2 + js := 1 + test_fn('>${3+r}<', '>4<') + test_fn('${r == js} $js', 'true 1') + test_fn('>${js+c} ${js+r==c}<', '>3 true<') +} + +/* +inttypes_string_interpolation() +fn inttypes_string_interpolation() { + c := i8(-103) + uc := byte(217) + uc2 := byte(13) + s := i16(-23456) + us := u16(54321) + i := -1622999040 + ui := u32(3421958087) + vp := voidptr(ui) + bp := byteptr(15541149836) + l := i64(-7694555558525237396) + ul := u64(17234006112912956370) + test_fn('$s $us', '-23456 54321') + test_fn('$ui $i', '3421958087 -1622999040') + test_fn('$l $ul', '-7694555558525237396 17234006112912956370') + test_fn('>${s:11}:${us:-13}<', '> -23456:54321 <') + test_fn('0x${ul:-19x}:${l:22d}', '0xef2b7d4001165bd2 : -7694555558525237396') + test_fn('${c:5}${uc:-7}x', ' -103217 x') + test_fn('${c:x}:${uc:x}:${uc2:02X}', '99:d9:0D') + test_fn('${s:X}:${us:x}:${u16(uc):04x}', 'A460:d431:00d9') + test_fn('${i:x}:${ui:X}:${int(s):x}', '9f430000:CBF6EFC7:ffffa460') + test_fn('${l:x}:${ul:X}', '9537727cad98876c:EF2B7D4001165BD2') + // default pointer format is platform dependent, so try a few + println("platform pointer format: '${vp:p}:$bp'") + test_fn('${vp:p}:$bp', '0xcbf6efc7:0x39e53208c' || + '${vp:p}:$bp' == 'CBF6EFC7:39E53208C' || + '${vp:p}:$bp' == 'cbf6efc7:39e53208c' || + '${vp:p}:$bp' == '00000000CBF6EFC7:000000039E53208C') +} +*/ + +fn utf8_string_interpolation() { + a := 'à-côté' + st := 'Sträßle' + m := '10€' + test_fn('$a $st $m', 'à-côté Sträßle 10€') + zz := '>${a:10}< >${st:-8}< >${m:5}<-' + zz_expected := '> à-côté< >Sträßle < > 10€<-' + //println(' zz: $zz') + //println('zz_expected: $zz_expected') + test_fn(zz, zz_expected) + // e := '\u20AC' // Eurosign doesn' work with MSVC and tcc + e := '€' + test_fn('100.00 $e', '100.00 €') + m2 := 'Москва́' // cyrillic а́: combination of U+0430 and U+0301, UTF-8: d0 b0 cc 81 + d := 'Antonín Dvořák' // latin á: U+00E1, UTF-8: c3 a1 + test_fn(':${m2:7}:${d:-15}:', ': Москва́:Antonín Dvořák :') + g := 'Πελοπόννησος' + test_fn('>${g:-13}<', '>Πελοπόννησος <') +} + +struct Sss { + v1 int + v2 f64 +} + +fn (s Sss) str() string { + return '[${s.v1}, ${s.v2:.3f}]' +} + +fn string_interpolation_str_evaluation() { + mut x := Sss{17, 13.455893} + test_fn('$x', '[17, 13.456]') +} + +/* +string_interpolation_with_negative_format_width_should_compile_and_run_without_segfaulting() +fn string_interpolation_with_negative_format_width_should_compile_and_run_without_segfaulting() { + // discovered during debugging VLS + i := 3 + input := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' + println('---------------------------------------------------------------------------------------------') + println('+60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():60} | $input') + println('-60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():-60} | $input') + println('---------------------------------------------------------------------------------------------') + println(true) +} +*/ + +fn main() { + simple_string_interpolation() + mixed_string_interpolation() + formatted_string_interpolation() + implicit_str() + string_interpolation_percent_escaping() + string_interpolation_string_prefix() + interpolation_string_prefix_expr() + utf8_string_interpolation() + string_interpolation_str_evaluation() +} \ No newline at end of file diff --git a/vlib/v/gen/js/tests/js.js b/vlib/v/gen/js/tests/js.js index ef2c001925..1cd2ab78a8 100644 --- a/vlib/v/gen/js/tests/js.js +++ b/vlib/v/gen/js/tests/js.js @@ -1,5 +1,5 @@ -// V_COMMIT_HASH 808975f -// V_CURRENT_COMMIT_HASH 564545d +// V_COMMIT_HASH 8a24d7d +// V_CURRENT_COMMIT_HASH 123d788 // Generated by the V compiler "use strict"; @@ -214,10 +214,10 @@ const main = (function (hl) { /** @type {string} */ const v_debugger = "JS keywords"; /** @type {string} */ - const v_await = v_super + ": " + v_debugger; + const v_await = `${v_super}: ${v_debugger}`; /** @type {string} */ let v_finally = "implemented"; - console.log(v_await, v_finally); + builtin.println(`${v_await} ${v_finally}`); /** @type {number} */ const dun = i_am_a_const * 20; /** @type {string} */ @@ -226,7 +226,7 @@ const main = (function (hl) { } for (let i = 0; i < "hello".length; ++i) { - let x = "hello"[i]; + const x = "hello"[i]; } for (let x = 1; x < 10; ++x) { @@ -235,7 +235,7 @@ const main = (function (hl) { /** @type {number[]} */ const arr = [1, 2, 3, 4, 5]; for (let _tmp6 = 0; _tmp6 < arr.length; ++_tmp6) { - let i = arr[_tmp6]; + const i = arr[_tmp6]; } /** @type {Map} */ @@ -255,7 +255,7 @@ const main = (function (hl) { /** @type {(number: number) => void} */ const fn_in_var = function (number) { - builtin.println(tos3(`number: ${number}`)); + builtin.println(`number: ${number}`); }; hl.v_debugger(); anon_consumer(hl.excited(), function (message) { @@ -294,7 +294,7 @@ const main = (function (hl) { */ function hello(game_on, ...dummy) { for (let _tmp7 = 0; _tmp7 < dummy.length; ++_tmp7) { - let dd = dummy[_tmp7]; + const dd = dummy[_tmp7]; /** @type {string} */ const l = dd; } diff --git a/vlib/v/gen/js/tests/js.v b/vlib/v/gen/js/tests/js.v index 07b7ac3eff..6ee557034f 100644 --- a/vlib/v/gen/js/tests/js.v +++ b/vlib/v/gen/js/tests/js.v @@ -47,7 +47,7 @@ fn main() { c.a.update('another update') println(c) - _ := "done" + _ = "done" { _ = "block" } @@ -57,10 +57,10 @@ fn main() { debugger := 'JS keywords' // TODO: Implement interpolation - await := super + ': ' + debugger + await := '$super: $debugger' mut finally := 'implemented' - JS.console.log(await, finally) + println('$await $finally') dun := i_am_a_const * 20 dunn := hl.hello // External constant