From 72089c4feb3de45503de9937b8f75e0b79590c34 Mon Sep 17 00:00:00 2001 From: playX Date: Wed, 8 Sep 2021 20:30:46 +0300 Subject: [PATCH] js: use prefixed names for functions and global symbols (#11387) --- vlib/builtin/js/array.js.v | 29 +- vlib/builtin/js/builtin.js.v | 11 + vlib/builtin/js/byte.js.v | 7 + vlib/builtin/js/float.js.v | 15 + vlib/builtin/js/int.js.v | 44 +- vlib/builtin/js/string.js.v | 8 + vlib/os/environment.js.v | 2 +- vlib/os/os.js.v | 10 +- vlib/os/os_js.js.v | 2 +- vlib/os/process.js.v | 2 +- vlib/strings/builder.js.v | 1 - vlib/v/builder/js.v | 4 +- vlib/v/gen/js/array.v | 167 +++- vlib/v/gen/js/auto_eq_methods.v | 1 + vlib/v/gen/js/auto_str_methods.v | 789 +++++++++++++++++++ vlib/v/gen/js/builtin_types.v | 55 +- vlib/v/gen/js/fn.v | 239 ++++-- vlib/v/gen/js/infix.v | 243 ++++++ vlib/v/gen/js/js.v | 464 ++++++----- vlib/v/gen/js/str.v | 82 +- vlib/v/gen/js/tests/testdata/array.out | 33 +- vlib/v/gen/js/tests/testdata/array.v | 3 +- vlib/v/gen/js/tests/testdata/overloading.out | 5 +- vlib/v/gen/js/util.v | 35 + vlib/v/preludes_js/stats_import.v | 1 + 25 files changed, 1895 insertions(+), 357 deletions(-) create mode 100644 vlib/builtin/js/float.js.v create mode 100644 vlib/v/gen/js/auto_eq_methods.v create mode 100644 vlib/v/gen/js/auto_str_methods.v create mode 100644 vlib/v/gen/js/infix.v create mode 100644 vlib/v/gen/js/util.v create mode 100644 vlib/v/preludes_js/stats_import.v diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 495e656db1..e041b75d1e 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -119,7 +119,7 @@ pub fn (mut a array) insert_many(i int, val voidptr, size int) { pub fn (mut a array) join(separator string) string { mut res := '' - #res = new builtin.string(a.val.arr.join(separator +'')); + #res = new string(a.val.arr.join(separator +'')); return res } @@ -128,16 +128,9 @@ fn (a array) push(val voidptr) { #a.arr.push(val) } -pub fn (a array) str() string { - mut res := '' - #res = new builtin.string(a + '') - - return res -} - #array.prototype[Symbol.iterator] = function () { return this.arr[Symbol.iterator](); } #array.prototype.entries = function () { let result = []; for (const [key,val] of this.arr.entries()) { result.push([new int(key), val]); } return result[Symbol.iterator](); } -#array.prototype.map = function(callback) { return new builtin.array(this.arr.map(callback)); } +#array.prototype.map = function(callback) { return new array(this.arr.map(callback)); } #array.prototype.filter = function(callback) { return new array(this.arr.filter( function (it) { return (+callback(it)) != 0; } )); } #Object.defineProperty(array.prototype,'cap',{ get: function () { return this.len; } }) #array.prototype.any = function (value) { @@ -251,3 +244,21 @@ pub fn (a array) bytestr() string { return res } + +/* +pub fn (a []string) str() string { + mut sb := strings.new_builder(a.len * 3) + sb.write_string('[') + for i in 0 .. a.len { + val := a[i] + sb.write_string("'") + sb.write_string(val) + sb.write_string("'") + if i < a.len - 1 { + sb.write_string(', ') + } + } + sb.write_string(']') + res := sb.str() + return res +}*/ diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index 6390fe01a4..f526966f21 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -1,6 +1,8 @@ module builtin +import strings // used to generate JS throw statements. + pub fn js_throw(s any) { #throw s } @@ -68,3 +70,12 @@ pub fn unwrap(opt string) string { return res } + +pub fn (r rune) str() string { + res := '' + mut sb := strings.new_builder(5) + #res.str = r.valueOf().toString() + sb.write_string(res) + + return sb.str() +} diff --git a/vlib/builtin/js/byte.js.v b/vlib/builtin/js/byte.js.v index af1803b29d..4a46e7852f 100644 --- a/vlib/builtin/js/byte.js.v +++ b/vlib/builtin/js/byte.js.v @@ -14,3 +14,10 @@ pub fn (c byte) is_letter() bool { return result } + +pub fn (c byte) str() string { + res := '' + #res.str = c.val.toString() + + return res +} diff --git a/vlib/builtin/js/float.js.v b/vlib/builtin/js/float.js.v new file mode 100644 index 0000000000..dacb4ea8fe --- /dev/null +++ b/vlib/builtin/js/float.js.v @@ -0,0 +1,15 @@ +module builtin + +pub fn (x f32) str() string { + res := '' + #res.str = x.val + '' + + return res +} + +pub fn (x f64) str() string { + res := '' + #res.str = x.val + '' + + return res +} diff --git a/vlib/builtin/js/int.js.v b/vlib/builtin/js/int.js.v index 08e52a912d..1220fb5033 100644 --- a/vlib/builtin/js/int.js.v +++ b/vlib/builtin/js/int.js.v @@ -2,7 +2,49 @@ module builtin pub fn (i int) str() string { mut res := '' - #res = new builtin.string( i ) + #res = new string( i ) + + return res +} + +pub fn (i i64) str() string { + mut res := '' + #res = new string( i ) + + return res +} + +pub fn (i u32) str() string { + mut res := '' + #res = new string( i ) + + return res +} + +pub fn (i u64) str() string { + mut res := '' + #res = new string( i ) + + return res +} + +pub fn (i bool) str() string { + mut res := '' + #res = new string( i ) + + return res +} + +pub fn (i any) str() string { + mut res := '' + #res = new string( i.toString() ) + + return res +} + +pub fn (i int_literal) str() string { + res := '' + #res.str = i.val.toString() return res } diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index 8b2933de7c..1a7f96a624 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -718,3 +718,11 @@ pub fn (s string) split_into_lines() []string { return res } + +// replace_once replaces the first occurence of `rep` with the string passed in `with`. +pub fn (s string) replace_once(rep string, with_ string) string { + s2 := '' + #s2.val = s.str.replace(rep.str,with_.str) + + return s2 +} diff --git a/vlib/os/environment.js.v b/vlib/os/environment.js.v index ac760a59c4..8b9dafa533 100644 --- a/vlib/os/environment.js.v +++ b/vlib/os/environment.js.v @@ -15,7 +15,7 @@ pub fn setenv(key string, val string, overwrite bool) { // `getenv` returns the value of the environment variable named by the key. pub fn getenv(key string) string { mut res := '' - #if ($ENV[key]) res = new builtin.string($ENV[key]) + #if ($ENV[key]) res = new string($ENV[key]) return res } diff --git a/vlib/os/os.js.v b/vlib/os/os.js.v index 6ba3c77a48..bdb5ea0db1 100644 --- a/vlib/os/os.js.v +++ b/vlib/os/os.js.v @@ -10,7 +10,7 @@ pub const ( ) $if js_node { - #$process.argv.forEach(function(val,index) { args.arr[index] = new string(val); }) + #$process.argv.forEach(function(val,index) { os__args.arr[index] = new string(val); }) } // real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved. @@ -55,7 +55,7 @@ pub fn chown(path string, owner int, group int) { pub fn temp_dir() string { mut res := '' $if js_node { - #res = new builtin.string($os.tmpdir()) + #res = new string($os.tmpdir()) } return res } @@ -63,7 +63,7 @@ pub fn temp_dir() string { pub fn home_dir() string { mut res := '' $if js_node { - #res = new builtin.string($os.homedir()) + #res = new string($os.homedir()) } return res } @@ -87,8 +87,8 @@ pub fn execute(cmd string) Result { mut stdout := '' #let commands = cmd.str.split(' '); #let output = $child_process.spawnSync(commands[0],commands.slice(1,commands.length)); - #exit_code = new builtin.int(output.status) - #stdout = new builtin.string(output.stdout + '') + #exit_code = new int(output.status) + #stdout = newstring(output.stdout + '') return Result{ exit_code: exit_code diff --git a/vlib/os/os_js.js.v b/vlib/os/os_js.js.v index 008a7c13cb..692218172d 100644 --- a/vlib/os/os_js.js.v +++ b/vlib/os/os_js.js.v @@ -45,7 +45,7 @@ pub fn ls(path string) ?[]string { result := []string{} $if js_node { #let i = 0 - #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path)) + #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new string(path)) } return result } diff --git a/vlib/os/process.js.v b/vlib/os/process.js.v index dc15c8b9f9..599e068a7b 100644 --- a/vlib/os/process.js.v +++ b/vlib/os/process.js.v @@ -101,7 +101,7 @@ pub fn (mut p Process) stdin_write(s string) { pub fn (mut p Process) stdout_slurp() string { p.check_redirection_call('stdout_slurp') mut res := '' - #p.val.pid.stdout.on('data', function (data) { res = new builtin.string(data) }) + #p.val.pid.stdout.on('data', function (data) { res = new string(data) }) return res } diff --git a/vlib/strings/builder.js.v b/vlib/strings/builder.js.v index e5e11991fb..ae16b2d045 100644 --- a/vlib/strings/builder.js.v +++ b/vlib/strings/builder.js.v @@ -50,7 +50,6 @@ pub fn (mut b Builder) writeln(s string) { } pub fn (mut b Builder) str() string { - b.buf << byte(0) s := '' #for (const c of b.val.buf.arr) diff --git a/vlib/v/builder/js.v b/vlib/v/builder/js.v index 241f6936fa..cd8df24e30 100644 --- a/vlib/v/builder/js.v +++ b/vlib/v/builder/js.v @@ -25,8 +25,8 @@ pub fn (mut b Builder) build_js(v_files []string, out_file string) { } pub fn (mut b Builder) compile_js() { - mut files := b.get_user_files() - files << b.get_builtin_files() + mut files := b.get_builtin_files() + files << b.get_user_files() b.set_module_lookup_paths() if b.pref.is_verbose { println('all .v files:') diff --git a/vlib/v/gen/js/array.v b/vlib/v/gen/js/array.v index c9d5ef664b..2cbdbd1c8a 100644 --- a/vlib/v/gen/js/array.v +++ b/vlib/v/gen/js/array.v @@ -1,19 +1,81 @@ module js import v.ast +import strings const ( special_array_methods = [ 'sort', 'insert', 'prepend', + 'index', + 'contains', ] ) +fn (mut g JsGen) gen_array_index_method(left_type ast.Type) string { + unwrap_left_type := g.unwrap_generic(left_type) + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).trim('*') + fn_name := '${left_type_str}_index' + + if !left_sym.has_method('index') { + info := left_sym.info as ast.Array + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + } + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('function ${fn_name}(a, v) {') + fn_builder.writeln('\tlet pelem = a.arr;') + fn_builder.writeln('\tfor (let i = 0; i < pelem.length; ++i) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (pelem[i].str == v.str) {') + } else if elem_sym.kind == .array && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif (vEq(pelem[i], v)) {') + } else if elem_sym.kind == .function && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif ( vEq(pelem[i], v)) {') + } else if elem_sym.kind == .map && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif (vEq(pelem[i], v)) {') + } else if elem_sym.kind == .struct_ && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif (vEq(pelem[i], v)) {') + } else { + fn_builder.writeln('\t\tif (pelem[i].valueOf() == v.valueOf()) {') + } + fn_builder.writeln('\t\t\treturn new int(i);') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn new int(-1);') + fn_builder.writeln('}') + g.definitions.writeln(fn_builder.str()) + left_sym.register_method(&ast.Fn{ + name: 'index' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: info.elem_type + }] + }) + } + + return fn_name +} + fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { node := it + match node.name { + 'index' { + g.gen_array_index(node) + return + } + 'contains' { + g.gen_array_contains(node) + return + } 'insert' { + g.write('array_') arg2_sym := g.table.get_type_symbol(node.args[1].typ) is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type if is_arg2_array { @@ -21,7 +83,13 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { } else { g.write('insert(') } - + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write(',') g.expr(node.args[0].expr) g.write(',') if is_arg2_array { @@ -36,6 +104,7 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { return } 'prepend' { + g.write('array_') arg_sym := g.table.get_type_symbol(node.args[0].typ) is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type if is_arg_array { @@ -43,7 +112,13 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { } else { g.write('prepend(') } - + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write(',') if is_arg_array { g.expr(node.args[0].expr) g.write('.arr, ') @@ -56,6 +131,7 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { return } 'sort' { + g.write('array') rec_sym := g.table.get_type_symbol(node.receiver_type) if rec_sym.kind != .array { println(node.name) @@ -67,9 +143,24 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { // `users.sort(a.age > b.age)` if node.args.len == 0 { - g.write('sort()') + g.write('_sort(') + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + + g.write(')') return } else { + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.') infix_expr := node.args[0].expr as ast.InfixExpr left_name := infix_expr.left.str() is_reverse := (left_name.starts_with('a') && infix_expr.op == .gt) @@ -87,3 +178,73 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { else {} } } + +fn (mut g JsGen) gen_array_index(node ast.CallExpr) { + fn_name := g.gen_array_index_method(node.left_type) + g.write('${fn_name}(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write(',') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g JsGen) gen_array_contains(node ast.CallExpr) { + fn_name := g.gen_array_contains_method(node.left_type) + g.write('${fn_name}(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write(',') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g JsGen) gen_array_contains_method(left_type ast.Type) string { + mut unwrap_left_type := g.unwrap_generic(left_type) + if unwrap_left_type.share() == .shared_t { + unwrap_left_type = unwrap_left_type.clear_flag(.shared_f) + } + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + left_final_sym := g.table.get_final_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).replace('*', '') + fn_name := '${left_type_str}_contains' + if !left_sym.has_method('contains') { + left_info := left_final_sym.info as ast.Array + elem_sym := g.table.get_type_symbol(left_info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + } + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('function ${fn_name}(a,v) {') + fn_builder.writeln('\tfor (let i = 0; i < a.len; ++i) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (a.arr[i].str == v.str) {') + } else if elem_sym.kind == .array && left_info.elem_type.nr_muls() == 0 { + fn_builder.writeln('\t\tif (vEq(a.arr[i], v)) {') + } else if elem_sym.kind == .function { + fn_builder.writeln('\t\tif (a.arr[i] == v) {') + } else if elem_sym.kind == .map && left_info.elem_type.nr_muls() == 0 { + fn_builder.writeln('\t\tif (vEq(a.arr[i], v)) {') + } else if elem_sym.kind == .struct_ && left_info.elem_type.nr_muls() == 0 { + fn_builder.writeln('\t\tif (vEq(a.arr[i],v)) {') + } else { + fn_builder.writeln('\t\tif (a.arr[i].valueOf() == v.valueOf()) {') + } + fn_builder.writeln('\t\t\treturn new bool(true);') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn new bool(false);') + fn_builder.writeln('}') + g.definitions.writeln(fn_builder.str()) + left_sym.register_method(&ast.Fn{ + name: 'contains' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: left_info.elem_type + }] + }) + } + return fn_name +} diff --git a/vlib/v/gen/js/auto_eq_methods.v b/vlib/v/gen/js/auto_eq_methods.v new file mode 100644 index 0000000000..ce28359b9e --- /dev/null +++ b/vlib/v/gen/js/auto_eq_methods.v @@ -0,0 +1 @@ +module js diff --git a/vlib/v/gen/js/auto_str_methods.v b/vlib/v/gen/js/auto_str_methods.v new file mode 100644 index 0000000000..2523a1bb58 --- /dev/null +++ b/vlib/v/gen/js/auto_str_methods.v @@ -0,0 +1,789 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module js + +import v.ast +import v.util +import strings + +pub enum StrIntpType { + si_no_str = 0 // no parameter to print only fix string + si_c + si_u8 + si_i8 + si_u16 + si_i16 + si_u32 + si_i32 + si_u64 + si_i64 + si_e32 + si_e64 + si_f32 + si_f64 + si_g32 + si_g64 + si_s + si_p + si_vp +} + +pub fn type_to_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'c' } + .si_u8 { return 'u8' } + .si_i8 { return 'i8' } + .si_u16 { return 'u16' } + .si_i16 { return 'i16' } + .si_u32 { return 'u32' } + .si_i32 { return 'i32' } + .si_u64 { return 'u64' } + .si_i64 { return 'i64' } + .si_f32 { return 'f32' } + .si_f64 { return 'f64' } + .si_g32 { return 'f32' } // g32 format use f32 data + .si_g64 { return 'f64' } // g64 format use f64 data + .si_e32 { return 'f32' } // e32 format use f32 data + .si_e64 { return 'f64' } // e64 format use f64 data + .si_s { return 's' } + .si_p { return 'p' } + .si_vp { return 'vp' } + } +} + +pub fn data_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'd_c' } + .si_u8 { return 'd_u8' } + .si_i8 { return 'd_i8' } + .si_u16 { return 'd_u16' } + .si_i16 { return 'd_i16' } + .si_u32 { return 'd_u32' } + .si_i32 { return 'd_i32' } + .si_u64 { return 'd_u64' } + .si_i64 { return 'd_i64' } + .si_f32 { return 'd_f32' } + .si_f64 { return 'd_f64' } + .si_g32 { return 'd_f32' } // g32 format use f32 data + .si_g64 { return 'd_f64' } // g64 format use f64 data + .si_e32 { return 'd_f32' } // e32 format use f32 data + .si_e64 { return 'd_f64' } // e64 format use f64 data + .si_s { return 'd_s' } + .si_p { return 'd_p' } + .si_vp { return 'd_vp' } + } +} + +const ( + // BUG: this const is not released from the memory! use a const for now + // si_s_code = "0x" + int(StrIntpType.si_s).hex() // code for a simple string + si_s_code = '0xfe10' +) + +fn should_use_indent_func(kind ast.Kind) bool { + return kind in [.struct_, .alias, .array, .array_fixed, .map, .sum_type, .interface_] +} + +fn (mut g JsGen) gen_str_default(sym ast.TypeSymbol, styp string, str_fn_name string) { + mut convertor := '' + mut typename_ := '' + if sym.parent_idx in ast.integer_type_idxs { + convertor = 'int' + typename_ = 'int' + } else if sym.parent_idx == ast.f32_type_idx { + convertor = 'float' + typename_ = 'f32' + } else if sym.parent_idx == ast.f64_type_idx { + convertor = 'double' + typename_ = 'f64' + } else if sym.parent_idx == ast.bool_type_idx { + convertor = 'bool' + typename_ = 'bool' + } else { + panic("could not generate string method for type '$styp'") + } + + g.definitions.writeln('function ${str_fn_name}(it) {') + if convertor == 'bool' { + g.definitions.writeln('\tlet tmp1 = string__plus(new string("${styp}("), it.valueOf() ? new string("true") : new string("false"));') + } else { + g.definitions.writeln('\tlet tmp1 = string__plus(new string("${styp}("), new string(${typename_}_str(($convertor)it).str));') + } + g.definitions.writeln('\tstring tmp2 = string__plus(tmp1, new string(")"));') + g.definitions.writeln('\treturn tmp2;') + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_type(typ ast.Type) string { + styp := g.typ(typ).replace('*', '') + mut sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut str_fn_name := styp_to_str_fn_name(styp) + if mut sym.info is ast.Alias { + if sym.info.is_import { + sym = g.table.get_type_symbol(sym.info.parent_type) + str_fn_name = styp_to_str_fn_name(sym.name) + } + } + sym_has_str_method, str_method_expects_ptr, str_nr_args := sym.str_method_info() + already_generated_key := '$styp:$str_fn_name' + if !sym_has_str_method && already_generated_key !in g.str_types && !typ.has_flag(.optional) { + $if debugautostr ? { + eprintln('> gen_str_for_type: |typ: ${typ:5}, ${sym.name:20}|has_str: ${sym_has_str_method:5}|expects_ptr: ${str_method_expects_ptr:5}|nr_args: ${str_nr_args:1}|fn_name: ${str_fn_name:20}') + } + g.str_types << already_generated_key + match mut sym.info { + ast.Alias { + if sym.info.is_import { + g.gen_str_default(sym, styp, str_fn_name) + } else { + g.gen_str_for_alias(sym.info, styp, str_fn_name) + } + } + ast.Array { + g.gen_str_for_array(sym.info, styp, str_fn_name) + } + ast.ArrayFixed { + g.gen_str_for_array_fixed(sym.info, styp, str_fn_name) + } + ast.Enum { + g.gen_str_for_enum(sym.info, styp, str_fn_name) + } + ast.FnType { + g.gen_str_for_fn_type(sym.info, styp, str_fn_name) + } + ast.Struct { + g.gen_str_for_struct(sym.info, styp, str_fn_name) + } + ast.Map { + g.gen_str_for_map(sym.info, styp, str_fn_name) + } + ast.MultiReturn { + g.gen_str_for_multi_return(sym.info, styp, str_fn_name) + } + ast.SumType { + g.gen_str_for_union_sum_type(sym.info, styp, str_fn_name) + } + ast.Interface { + g.gen_str_for_interface(sym.info, styp, str_fn_name) + } + ast.Chan { + g.gen_str_for_chan(sym.info, styp, str_fn_name) + } + ast.Thread { + g.gen_str_for_thread(sym.info, styp, str_fn_name) + } + else { + panic("could not generate string method $str_fn_name for type '$styp'") + } + } + } + if typ.has_flag(.optional) { + option_already_generated_key := 'option_$already_generated_key' + if option_already_generated_key !in g.str_types { + g.gen_str_for_option(typ, styp, str_fn_name) + g.str_types << option_already_generated_key + } + return str_fn_name + } + return str_fn_name +} + +fn (mut g JsGen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) { + parent_type := typ.clear_flag(.optional) + sym := g.table.get_type_symbol(parent_type) + sym_has_str_method, _, _ := sym.str_method_info() + parent_str_fn_name := g.gen_str_for_type(parent_type) + + g.definitions.writeln('function ${str_fn_name}(it) { return indent_${str_fn_name}(it, 0); }') + g.definitions.writeln('function indent_${str_fn_name}(it, indent_count) {') + g.definitions.writeln('\tlet res;') + g.definitions.writeln('\tif (it.state == 0) {') + if sym.kind == .string { + tmp_res := '${parent_str_fn_name}(it.data)' + g.definitions.writeln('\t\tres = ${str_intp_sq(tmp_res)};') + } else if should_use_indent_func(sym.kind) && !sym_has_str_method { + g.definitions.writeln('\t\tres = indent_${parent_str_fn_name}(it.data, indent_count);') + } else { + g.definitions.writeln('\t\tres = ${parent_str_fn_name}(it.data);') + } + g.definitions.writeln('\t} else {') + + tmp_str := str_intp_sub('error: %%', 'IError_str(it.err)') + g.definitions.writeln('\t\tres = $tmp_str;') + g.definitions.writeln('\t}') + + g.definitions.writeln('\treturn ${str_intp_sub('Option(%%)', 'res')};') + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) { + parent_str_fn_name := g.gen_str_for_type(info.parent_type) + g.definitions.writeln('function ${str_fn_name}(it) { return indent_${str_fn_name}(it, 0); }') + + g.definitions.writeln('function indent_${str_fn_name}(it, indent_count) {') + g.definitions.writeln('\tlet indents = string_repeat(new string(" "), indent_count);') + g.definitions.writeln('\tlet tmp_ds = ${parent_str_fn_name}(it);') + g.definitions.writeln('\tlet res = new string("TODO");') + g.definitions.writeln('\treturn res;') + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_fn_name string) { + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('function ${str_fn_name}(a) {') + fn_builder.writeln('\tlet sb = strings__new_builder($info.types.len * 10);') + fn_builder.writeln('\tstrings__Builder_write_string(sb, new string("("));') + for i, typ in info.types { + sym := g.table.get_type_symbol(typ) + is_arg_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + arg_str_fn_name := g.gen_str_for_type(typ) + + if should_use_indent_func(sym.kind) && !sym_has_str_method { + fn_builder.writeln('\tstrings__Builder_write_string(sb, ${arg_str_fn_name}(a.arg$i));') + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + tmp_val := str_intp_g32('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(sb, $tmp_val);') + } else { + tmp_val := str_intp_g64('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(sb, $tmp_val);') + } + } else if sym.kind == .string { + tmp_str := str_intp_sq('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(sb, $tmp_str);') + } else if sym.kind == .function { + fn_builder.writeln('\tstrings__Builder_write_string(sb, ${arg_str_fn_name}());') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_arg_ptr, typ) + fn_builder.writeln('\t\tstrings__Builder_write_string(sb, new string("$deref_label"));') + fn_builder.writeln('\tstrings__Builder_write_string(sb, ${arg_str_fn_name}( $deref a.arg$i));') + } + if i != info.types.len - 1 { + fn_builder.writeln('\tstrings__Builder_write_string(sb, new string(", "));') + } + } + fn_builder.writeln('\tstrings__Builder_write_string(sb, new string(")"));') + fn_builder.writeln('\tlet res = strings__Builder_str(sb);') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') + g.definitions.writeln(fn_builder.str()) +} + +fn (mut g JsGen) gen_str_for_enum(info ast.Enum, styp string, str_fn_name string) { + s := util.no_dots(styp) + + g.definitions.writeln('function ${str_fn_name}(it) { /* gen_str_for_enum */') + // Enums tagged with `[flag]` are special in that they can be a combination of enum values + if info.is_flag { + clean_name := util.strip_main_name(styp.replace('__', '.')) + g.definitions.writeln('\tlet ret = new string("$clean_name{");') + g.definitions.writeln('\tlet first = 1;') + for i, val in info.vals { + g.definitions.writeln('\tif (it & (1 << $i)) {if (!first) {ret = string__plus(ret, new string(" | "));} ret = string__plus(ret, new string(".$val")); first = 0;}') + } + g.definitions.writeln('\tret = string__plus(ret, new string("}"));') + g.definitions.writeln('\treturn ret;') + } else { + g.definitions.writeln('\tswitch(it) {') + // Only use the first multi value on the lookup + mut seen := []string{len: info.vals.len} + for val in info.vals { + if info.is_multi_allowed && val in seen { + continue + } else if info.is_multi_allowed { + seen << val + } + g.definitions.writeln('\t\tcase ${s}.$val: return new string("$val");') + } + g.definitions.writeln('\t\tdefault: return new string("unknown enum value");') + g.definitions.writeln('\t}') + } + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_interface(info ast.Interface, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + + g.definitions.writeln('function ${str_fn_name}(x) { return indent_${str_fn_name}(x, 0); }') + + mut fn_builder := strings.new_builder(512) + mut clean_interface_v_type_name := styp.replace('__', '.') + if styp.ends_with('*') { + clean_interface_v_type_name = '&' + clean_interface_v_type_name.replace('*', '') + } + if clean_interface_v_type_name.contains('_T_') { + clean_interface_v_type_name = + clean_interface_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_interface_v_type_name = util.strip_main_name(clean_interface_v_type_name) + fn_builder.writeln('function indent_${str_fn_name}(x,indent_count) { /* gen_str_for_interface */') + for typ in info.types { + subtype := g.table.get_type_symbol(typ) + mut func_name := g.gen_str_for_type(typ) + sym_has_str_method, _, _ := subtype.str_method_info() + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + func_name = 'indent_$func_name' + } + + // str_intp + + if typ == ast.string_type { + /* + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + val = val + */ + res := '"TODO"' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;') + } else { + /* + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + val = val + */ + res := '"TODO' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;\n') + } + } + fn_builder.writeln('\treturn new string("unknown interface value");') + fn_builder.writeln('}') + g.definitions.writeln(fn_builder.str()) +} + +fn (mut g JsGen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_name string) { +} + +fn (mut g JsGen) fn_decl_str(info ast.FnType) string { + mut fn_str := 'fn (' + for i, arg in info.func.params { + if arg.is_mut { + fn_str += 'mut ' + } + if i > 0 { + fn_str += ', ' + } + fn_str += util.strip_main_name(g.table.get_type_name(g.unwrap_generic(arg.typ))) + } + fn_str += ')' + if info.func.return_type == ast.ovoid_type { + fn_str += ' ?' + } else if info.func.return_type != ast.void_type { + x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) + if info.func.return_type.has_flag(.optional) { + fn_str += ' ?$x' + } else { + fn_str += ' $x' + } + } + return fn_str +} + +fn (mut g JsGen) gen_str_for_fn_type(info ast.FnType, styp string, str_fn_name string) { + g.definitions.writeln('function ${str_fn_name}() { return new string("${g.fn_decl_str(info)}");}') +} + +fn (mut g JsGen) gen_str_for_chan(info ast.Chan, styp string, str_fn_name string) { + elem_type_name := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.elem_type))) + + g.definitions.writeln('function ${str_fn_name}(x) { return sync__Channel_auto_str(x, new string("$elem_type_name")); }') +} + +fn (mut g JsGen) gen_str_for_thread(info ast.Thread, styp string, str_fn_name string) { + ret_type_name := util.strip_main_name(g.table.get_type_name(info.return_type)) + + g.definitions.writeln('function ${str_fn_name}(_) { return new string("thread($ret_type_name)");}') +} + +[inline] +fn styp_to_str_fn_name(styp string) string { + return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_str' +} + +fn deref_kind(str_method_expects_ptr bool, is_elem_ptr bool, typ ast.Type) (string, string) { + if str_method_expects_ptr != is_elem_ptr { + if is_elem_ptr { + return '.val'.repeat(typ.nr_muls()), 'new \$ref('.repeat(typ.nr_muls()) + } else { + return 'new \$ref', '' + } + } + return '', '' +} + +fn (mut g JsGen) gen_str_for_array(info ast.Array, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + mut elem_str_fn_name := g.gen_str_for_type(typ) + if sym.kind == .byte { + elem_str_fn_name = elem_str_fn_name + '_escaped' + } + + g.definitions.writeln('function ${str_fn_name}(a) { return indent_${str_fn_name}(a, 0);}') + g.definitions.writeln('function indent_${str_fn_name}(a, indent_count) {') + g.definitions.writeln('\tlet sb = strings__new_builder(a.len * 10);') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("["));') + g.definitions.writeln('\tfor (let i = 0; i < a.len; ++i) {') + if sym.kind == .function { + g.definitions.writeln('\t\tlet it = ${elem_str_fn_name}();') + } else { + g.definitions.writeln('\t\tlet it = a.arr[i];') + + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.definitions.writeln('\t\tlet x = indent_${elem_str_fn_name}(it.val, indent_count);') + } else { + g.definitions.writeln('\t\tlet x = indent_${elem_str_fn_name}(it, indent_count);') + } + } else if sym.kind in [.f32, .f64] { + g.definitions.writeln('\t\tlet x = new string( it.val + "");') + } else if sym.kind == .rune { + // Rune are managed at this level as strings + // g.definitions.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{new string("\`"), $c.si_s_code, {.d_s = ${elem_str_fn_name}(it) }}, {new string("\`"), 0, {.d_c = 0 }}}));\n') + } else if sym.kind == .string { + g.definitions.writeln('\t\tlet x = new string(it);') + // g.definitions.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{new string("\'"), $c.si_s_code, {.d_s = it }}, {new string("\'"), 0, {.d_c = 0 }}}));\n') + } else { + // There is a custom .str() method, so use it. + // NB: we need to take account of whether the user has defined + // `fn (x T) str() {` or `fn (x &T) str() {`, and convert accordingly + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string("$deref_label"));') + g.definitions.writeln('\t\tlet x = ${elem_str_fn_name}( $deref it);') + } + } + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, x);') + + g.definitions.writeln('\t\tif (i < a.len-1) {') + g.definitions.writeln('\t\t\tstrings__Builder_write_string(sb, new string(", "));') + g.definitions.writeln('\t\t}') + g.definitions.writeln('\t}') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("]"));') + g.definitions.writeln('\tlet res = strings__Builder_str(sb);') + g.definitions.writeln('\treturn res;') + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_array_fixed(info ast.ArrayFixed, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + elem_str_fn_name := g.gen_str_for_type(typ) + + g.definitions.writeln('function ${str_fn_name}(a) { return indent_${str_fn_name}(a, 0);}') + + g.definitions.writeln('function indent_${str_fn_name}(a, indent_count) {') + g.definitions.writeln('\tlet sb = strings__new_builder($info.size * 10);') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("["));') + g.definitions.writeln('\tfor (let i = 0; i < $info.size; ++i) {') + if sym.kind == .function { + g.definitions.writeln('\t\tstring x = ${elem_str_fn_name}();') + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, x);') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string("$deref_label"));') + g.definitions.writeln('\t\tif ( 0 == a.arr[i] ) {') + g.definitions.writeln('\t\t\tstrings__Builder_write_string(sb, new string("0"));') + g.definitions.writeln('\t\t}else{') + g.definitions.writeln('\t\t\tstrings__Builder_write_string(sb, ${elem_str_fn_name}(a.arr[i] $deref) );') + g.definitions.writeln('\t\t}') + } else { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, ${elem_str_fn_name}(a.arr[i]) );') + } + } else if sym.kind in [.f32, .f64] { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string(a.arr[i].val.toString()) );') + } else if sym.kind == .string { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, a.arr[i].str);') + } else if sym.kind == .rune { + // tmp_str := str_intp_rune('${elem_str_fn_name}( a[i] $deref)') + // g.definitions.writeln('\t\tstrings__Builder_write_string(sb, $tmp_str);') + } else { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, ${elem_str_fn_name}(a.arr[i] $deref));') + } + } + g.definitions.writeln('\t\tif (i < ${info.size - 1}) {') + g.definitions.writeln('\t\t\tstrings__Builder_write_string(sb, new string(", "));') + g.definitions.writeln('\t\t}') + g.definitions.writeln('\t}') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("]"));') + g.definitions.writeln('\tlet res = strings__Builder_str(sb);') + g.definitions.writeln('\treturn res;') + g.definitions.writeln('}') +} + +fn (mut g JsGen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) { + mut key_typ := info.key_type + mut key_sym := g.table.get_type_symbol(key_typ) + if mut key_sym.info is ast.Alias { + key_typ = key_sym.info.parent_type + key_sym = g.table.get_type_symbol(key_typ) + } + key_styp := g.typ(key_typ) + key_str_fn_name := key_styp.replace('*', '') + '_str' + if !key_sym.has_method('str') { + g.gen_str_for_type(key_typ) + } + + mut val_typ := info.value_type + mut val_sym := g.table.get_type_symbol(val_typ) + if mut val_sym.info is ast.Alias { + val_typ = val_sym.info.parent_type + val_sym = g.table.get_type_symbol(val_typ) + } + val_styp := g.typ(val_typ) + elem_str_fn_name := val_styp.replace('*', '') + '_str' + if !val_sym.has_method('str') { + g.gen_str_for_type(val_typ) + } + + g.definitions.writeln('function ${str_fn_name}(m) { return indent_${str_fn_name}(m, 0);}') + + g.definitions.writeln('function indent_${str_fn_name}(m, indent_count) { /* gen_str_for_map */') + g.definitions.writeln('\tlet sb = strings__new_builder(m.map.length*10);') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("{"));') + g.definitions.writeln('\tlet i = 0;') + g.definitions.writeln('\tfor (let [key,value] of m.map) {') + + if key_sym.kind == .string { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string(key));') + } else if key_sym.kind == .rune { + // tmp_str := str_intp_rune('${key_str_fn_name}(key)') + // g.definitions.writeln('\t\tstrings__Builder_write_string(sb, $tmp_str);') + } else { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, ${key_str_fn_name}(key));') + } + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string(": "));') + if val_sym.kind == .function { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, ${elem_str_fn_name}());') + } else if val_sym.kind == .string { + // tmp_str := str_intp_sq('*($val_styp*)DenseArray_value(&m.key_values, i)') + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, value);') + } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, indent_${elem_str_fn_name}(value, indent_count));') + } else if val_sym.kind in [.f32, .f64] { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, value.val + "");') + } else if val_sym.kind == .rune { + // tmp_str := str_intp_rune('${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i))') + // g.definitions.writeln('\t\tstrings__Builder_write_string(sb, $tmp_str);') + } else { + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, ${elem_str_fn_name}(value));') + } + g.definitions.writeln('\t\tif (i != m.map.size-1) {') + g.definitions.writeln('\t\t\tstrings__Builder_write_string(sb, new string(", "));') + g.definitions.writeln('\t\t}') + g.definitions.writeln('\t\ti++;') + g.definitions.writeln('\t}') + g.definitions.writeln('\tstrings__Builder_write_string(sb, new string("}"));') + g.definitions.writeln('\tlet res = strings__Builder_str(sb);') + g.definitions.writeln('\treturn res;') + g.definitions.writeln('}') +} + +fn (g &JsGen) type_to_fmt1(typ ast.Type) StrIntpType { + if typ == ast.byte_type_idx { + return .si_u8 + } + if typ == ast.char_type_idx { + return .si_c + } + if typ in ast.voidptr_types || typ in ast.byteptr_types { + return .si_p + } + if typ in ast.charptr_types { + // return '%C\\000' // a C string + return .si_s + } + sym := g.table.get_type_symbol(typ) + if typ.is_ptr() && (typ.is_int_valptr() || typ.is_float_valptr()) { + return .si_s + } else if sym.kind in [.struct_, .array, .array_fixed, .map, .bool, .enum_, .interface_, + .sum_type, .function, .alias, .chan] { + return .si_s + } else if sym.kind == .string { + return .si_s + // return "'%.*s\\000'" + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + return .si_g32 + } + return .si_g64 + } else if sym.kind == .int { + return .si_i32 + } else if sym.kind == .u32 { + return .si_u32 + } else if sym.kind == .u64 { + return .si_u64 + } else if sym.kind == .i64 { + return .si_i64 + } + return .si_i32 +} + +fn (mut g JsGen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + + g.definitions.writeln('function ${str_fn_name}(it) { return indent_${str_fn_name}(it, 0);}') + + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function indent_${str_fn_name}(it, indent_count) {') + mut clean_struct_v_type_name := styp.replace('__', '.') + if clean_struct_v_type_name.contains('_T_') { + // TODO: this is a bit hacky. styp shouldn't be even parsed with _T_ + // use something different than g.typ for styp + clean_struct_v_type_name = + clean_struct_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_struct_v_type_name = util.strip_main_name(clean_struct_v_type_name) + // generate ident / indent length = 4 spaces + if info.fields.len == 0 { + fn_builder.writeln('\treturn new string("$clean_struct_v_type_name{}");') + fn_builder.writeln('}') + return + } + + fn_builder.writeln('\tlet res = /*struct name*/new string("$clean_struct_v_type_name{\\n")') + + for i, field in info.fields { + mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' } + mut prefix := '' + // manage prefix and quote symbol for the filed + /* + mut quote_str := '' + + + if sym.kind == .string { + quote_str = "'" + } else if field.typ in ast.charptr_types { + quote_str = '\\"' + prefix = 'C' + } + quote_str = quote_str + */ + sym := g.table.get_type_symbol(g.unwrap_generic(field.typ)) + // first fields doesn't need \n + if i == 0 { + fn_builder.write_string('res.str += " $field.name: $ptr_amp$prefix" + ') + } else { + fn_builder.write_string('res.str += "\\n $field.name: $ptr_amp$prefix" + ') + } + + // custom methods management + has_custom_str := sym.has_method('str') + mut field_styp := g.typ(field.typ).replace('*', '') + field_styp_fn_name := if has_custom_str { + '${field_styp}_str' + } else { + g.gen_str_for_type(field.typ) + } + + mut func := struct_auto_str_func1(mut g, sym, field.typ, field_styp_fn_name, field.name) + if field.typ in ast.cptr_types { + func = '(voidptr) it.$field.name' + } else if field.typ.is_ptr() { + // reference types can be "nil" + fn_builder.write_string('isnil(it.${g.js_name(field.name)})') + fn_builder.write_string(' ? new string("nil") : ') + // struct, floats and ints have a special case through the _str function + if sym.kind != .struct_ && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() { + fn_builder.write_string('*') + } + } + // handle circular ref type of struct to the struct itself + if styp == field_styp { + fn_builder.write_string('res.str += new string("")') + } else { + // manage C charptr + if field.typ in ast.charptr_types { + fn_builder.write_string('tos2((byteptr)$func)') + } else { + if field.typ.is_ptr() && sym.kind == .struct_ { + fn_builder.write_string('(indent_count > 25) ? new string("") : ') + } + fn_builder.write_string(func) + } + } + + fn_builder.writeln('') + } + fn_builder.writeln('res.str += "\\n}"') + // fn_builder.writeln('\t\t{new string("\\n"), $c.si_s_code, {.d_s=indents}}, {new string("}"), 0, {.d_c=0}},') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') +} + +fn struct_auto_str_func1(mut g JsGen, sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string) string { + has_custom_str, expects_ptr, _ := sym.str_method_info() + if sym.kind == .enum_ { + return '${fn_name}(it.${g.js_name(field_name)})' + } else if should_use_indent_func(sym.kind) { + mut obj := 'it.${g.js_name(field_name)}' + if field_type.is_ptr() && !expects_ptr { + obj = '*$obj' + } + if has_custom_str { + return '${fn_name}($obj)' + } + return 'indent_${fn_name}($obj, indent_count + 1)' + } else if sym.kind in [.array, .array_fixed, .map, .sum_type] { + if has_custom_str { + return '${fn_name}(it.${g.js_name(field_name)})' + } + return 'indent_${fn_name}(it.${g.js_name(field_name)}, indent_count + 1)' + } else if sym.kind == .function { + return '${fn_name}()' + } else { + if sym.kind == .chan { + return '${fn_name}(it.${g.js_name(field_name)})' + } + mut method_str := 'it.${g.js_name(field_name)}' + if sym.kind == .bool { + method_str += ' ? new string("true") : new string("false")' + } else if (field_type.is_int_valptr() || field_type.is_float_valptr()) + && field_type.is_ptr() && !expects_ptr { + // ptr int can be "nil", so this needs to be casted to a string + if sym.kind == .f32 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g32_code, {.d_f32 = *$method_str }} + }))' + } else if sym.kind == .f64 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g64_code, {.d_f64 = *$method_str }} + }))' + } else if sym.kind == .u64 { + fmt_type := StrIntpType.si_u64 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *$method_str }}}))' + } + fmt_type := StrIntpType.si_i32 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))' + } + return method_str + } +} diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 330e4c8a0c..71e2d17914 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -17,7 +17,7 @@ fn (mut g JsGen) to_js_typ_def_val(s string) string { fn (mut g JsGen) to_js_typ_val(t ast.Type) string { sym := g.table.get_type_symbol(t) mut styp := '' - mut prefix := if g.file.mod.name == 'builtin' { 'new ' } else { 'new builtin.' } + mut prefix := 'new ' match sym.kind { .i8, .i16, .int, .i64, .byte, .u8, .u16, .u32, .u64, .f32, .f64, .int_literal, .float_literal, .size_t { @@ -114,8 +114,22 @@ fn (mut g JsGen) sym_to_js_typ(sym ast.TypeSymbol) string { return styp } -// V type to JS type +pub fn (mut g JsGen) base_type(t ast.Type) string { + mut styp := g.cc_type(t, true) + return styp +} + pub fn (mut g JsGen) typ(t ast.Type) string { + sym := g.table.get_type_symbol(t) + if sym.kind == .voidptr { + return 'any' + } + styp := g.base_type(t) + return styp +} + +// V type to JS type +pub fn (mut g JsGen) doc_typ(t ast.Type) string { sym := g.table.get_type_symbol(t) mut styp := '' match sym.kind { @@ -263,7 +277,7 @@ struct BuiltinPrototypeConfig { constructor string = 'this.val = val' value_of string = 'this.val' to_string string = 'this.val.toString()' - eq string = 'this.val === other.val' + eq string = 'self.val === other.val' to_jsval string = 'this' extras string has_strfn bool @@ -277,21 +291,22 @@ fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeConfig) { if c.extras.len > 0 { g.writeln('$c.extras,') } - for method in g.method_fn_decls[c.typ_name] { - g.inside_def_typ_decl = true - g.gen_method_decl(method, .struct_method) - g.inside_def_typ_decl = false - g.writeln(',') - } + g.writeln('valueOf() { return $c.value_of },') g.writeln('toString() { return $c.to_string },') - g.writeln('eq(other) { return $c.eq },') + // g.writeln('eq(other) { return $c.eq },') g.writeln('\$toJS() { return $c.to_jsval }, ') if c.has_strfn { g.writeln('str() { return new string(this.toString()) }') } g.dec_indent() g.writeln('};\n') + g.writeln('function ${c.typ_name}__eq(self,other) { return $c.eq; } ') + for method in g.method_fn_decls[c.typ_name] { + g.inside_def_typ_decl = true + g.gen_method_decl(method, .struct_method) + g.inside_def_typ_decl = false + } } // generate builtin type definitions, used for casting and methods. @@ -308,7 +323,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.val = Number(val)' value_of: 'Number(this.val)' to_string: 'this.valueOf().toString()' - eq: 'this.valueOf() === other.valueOf()' + eq: 'new bool(self.valueOf() === other.valueOf())' to_jsval: '+this' ) } @@ -320,7 +335,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.val = BigInt.asUintN(64,BigInt(val))' value_of: 'this.val' to_string: 'this.val.toString()' - eq: 'this.valueOf() === other.valueOf()' + eq: 'new bool(self.valueOf() === other.valueOf())' to_jsval: 'this.val' ) } @@ -331,7 +346,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.val = BigInt.asIntN(64,BigInt(val))' value_of: 'this.val' to_string: 'this.val.toString()' - eq: 'this.valueOf() === other.valueOf()' + eq: 'new bool(self.valueOf() === other.valueOf())' to_jsval: 'this.val' ) } @@ -342,7 +357,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'if (typeof(val) == "string") { this.val = val.charCodeAt() } else if (val instanceof string) { this.val = val.str.charCodeAt(); } else { this.val = val | 0 }' value_of: 'this.val | 0' to_string: 'new string(this.val + "")' - eq: 'this.valueOf() === other.valueOf()' + eq: 'new bool(self.valueOf() === other.valueOf())' to_jsval: '+this' ) } @@ -356,11 +371,11 @@ fn (mut g JsGen) gen_builtin_type_defs() { } 'bool' { g.gen_builtin_prototype( - constructor: 'this.val = +val !== 0' + constructor: 'this.val = val instanceof bool ? val.val : +val !== 0' typ_name: typ_name default_value: 'new Boolean(false)' to_jsval: '+this != 0' - eq: 'this.val === other.valueOf()' + eq: 'new bool(self.val === other.valueOf())' ) } 'string' { @@ -371,7 +386,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.str = str.toString(); this.len = this.str.length' value_of: 'this.str' to_string: 'this.str' - eq: 'this.str === other.str' + eq: 'new bool(self.str === other.str)' has_strfn: false to_jsval: 'this.str' ) @@ -384,7 +399,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.map = map' value_of: 'this' to_string: 'this.map.toString()' - eq: 'vEq(this, other)' + eq: 'new bool(vEq(self, other))' to_jsval: 'this.map' ) } @@ -396,7 +411,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.arr = arr' value_of: 'this' to_string: 'JSON.stringify(this.arr.map(it => it.valueOf()))' - eq: 'vEq(this, other)' + eq: 'new bool(vEq(self, other))' to_jsval: 'this.arr' ) } @@ -408,7 +423,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { constructor: 'this.val = any' value_of: 'this.val' to_string: '"&" + this.val' - eq: 'this == other' // compare by ptr + eq: 'new bool(self == other)' // compare by ptr to_jsval: 'this.val.\$toJS()' ) } diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index 6bde0a13dd..cf90b61312 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -1,11 +1,99 @@ module js import v.ast +import v.util -fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { - g.call_stack << it +fn (mut g JsGen) js_mname(name_ string) string { + mut is_js := false + is_overload := ['+', '-', '*', '/', '==', '<', '>'] + mut name := name_ + if name.starts_with('JS.') { + name = name[3..] + is_js = true + } + ns := get_ns(name) + name = if name in is_overload { + match name { + '+' { + '\$add' + } + '-' { + '\$sub' + } + '/' { + '\$div' + } + '*' { + '\$mul' + } + '%' { + '\$mod' + } + '==' { + 'eq' + } + '>' { + '\$gt' + } + '<' { + '\$lt' + } + else { + '' + } + } + } else if g.ns == 0 { + name + } else if ns == g.ns.name { + name.split('.').last() + } else { + g.get_alias(name) + } + mut parts := name.split('.') + if !is_js { + for i, p in parts { + if p in js_reserved { + parts[i] = 'v_$p' + } + } + } + return parts.join('.') +} - mut name := g.js_name(it.name) +fn (mut g JsGen) js_call(node ast.CallExpr) { + g.call_stack << node + it := node + g.write('${g.js_mname(it.name)}(') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end call + g.write(')') + g.call_stack.delete_last() +} + +fn (mut g JsGen) js_method_call(node ast.CallExpr) { + g.call_stack << node + it := node + g.expr(it.left) + g.write('.${g.js_mname(it.name)}(') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end method call + g.write(')') + g.call_stack.delete_last() +} + +fn (mut g JsGen) method_call(node ast.CallExpr) { + g.call_stack << node + it := node call_return_is_optional := it.return_type.has_flag(.optional) if call_return_is_optional { g.writeln('(function(){') @@ -14,9 +102,45 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { g.inc_indent() g.write('return builtin.unwrap(') } - sym := g.table.get_type_symbol(it.receiver_type) - if sym.kind == .array { - if sym.kind == .array && it.name in ['map', 'filter'] { + mut unwrapped_rec_type := node.receiver_type + if g.table.cur_fn.generic_names.len > 0 { + unwrapped_rec_type = g.unwrap_generic(node.receiver_type) + } else { + sym := g.table.get_type_symbol(node.receiver_type) + match sym.info { + ast.Struct, ast.Interface, ast.SumType { + generic_names := sym.info.generic_types.map(g.table.get_type_symbol(it).name) + if utyp := g.table.resolve_generic_to_concrete(node.receiver_type, generic_names, + sym.info.concrete_types) + { + unwrapped_rec_type = utyp + } + } + else {} + } + } + + mut typ_sym := g.table.get_type_symbol(unwrapped_rec_type) + rec_cc_type := g.cc_type(unwrapped_rec_type, false) + mut receiver_type_name := util.no_dots(rec_cc_type) + // alias type that undefined this method (not include `str`) need to use parent type + if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) { + unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type + typ_sym = g.table.get_type_symbol(unwrapped_rec_type) + } + + if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method(node.name) { + // g.write('${g.js_name(receiver_type_name)}_name_table') + // g.expr(node.left) + g.writeln('/* TODO: Interface call */') + return + } + + left_sym := g.table.get_type_symbol(node.left_type) + final_left_sym := g.table.get_final_type_symbol(node.left_type) + + if final_left_sym.kind == .array { + if final_left_sym.kind == .array && it.name in ['map', 'filter'] { g.expr(it.left) mut ltyp := it.left_type for ltyp.is_ptr() { @@ -25,7 +149,7 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { } g.write('.') // Prevent 'it' from getting shadowed inside the match - node := it + g.write(it.name) g.write('(') expr := node.args[0].expr @@ -33,19 +157,19 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { ast.AnonFn { g.gen_fn_decl(expr.decl) g.write(')') - return true + return } ast.Ident { if expr.kind == .function { g.write(g.js_name(expr.name)) g.write(')') - return true + return } else if expr.kind == .variable { v_sym := g.table.get_type_symbol(expr.var_info().typ) if v_sym.kind == .function { g.write(g.js_name(expr.name)) g.write(')') - return true + return } } } @@ -55,61 +179,36 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { g.write('it => ') g.expr(node.args[0].expr) g.write(')') - return true + return } - left_sym := g.table.get_type_symbol(it.left_type) - if left_sym.kind == .array { + if final_left_sym.kind == .array { if it.name in special_array_methods { - g.expr(it.left) - mut ltyp := it.left_type - for ltyp.is_ptr() { - g.write('.val') - ltyp = ltyp.deref() - } - g.write('.') - g.gen_array_method_call(it) - return true + return } } } - - mut ltyp := it.left_type - mut lsym := g.table.get_type_symbol(ltyp) - if lsym.kind == .interface_ { - g.write(g.js_name(lsym.name)) - g.write('.${name}.call(') - g.expr(it.left) - g.write(',') - for i, arg in it.args { - g.expr(arg.expr) - if i != it.args.len - 1 { - g.write(', ') - } + if final_left_sym.kind == .array + && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { + // `array_Xyz_clone` => `array_clone` + receiver_type_name = 'array' } - // end method call - g.write(')') - } else { - g.write('Object.getPrototypeOf(') - g.expr(it.left) - - for ltyp.is_ptr() { - g.write('.val') - ltyp = ltyp.deref() - } - g.write(').$name .call(') - g.expr(it.left) - g.write(',') - for i, arg in it.args { - g.expr(arg.expr) - if i != it.args.len - 1 { - g.write(', ') - } - } - // end method call - g.write(')') } + mut name := util.no_dots('${receiver_type_name}_$node.name') + + name = g.generic_fn_name(node.concrete_types, name, false) + g.write('${name}(') + g.expr(it.left) + g.write(',') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + g.write(')') if call_return_is_optional { // end unwrap @@ -145,14 +244,19 @@ fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { g.write('})()') } g.call_stack.delete_last() - return true } fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { + if it.is_method && g.table.get_type_symbol(it.receiver_type).name.starts_with('JS.') { + g.js_method_call(it) + return + } else if it.name.starts_with('JS.') { + g.js_call(it) + return + } if it.is_method { - if g.gen_method_call(it) { - return - } + g.method_call(it) + return } node := it g.call_stack << it @@ -162,9 +266,6 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { ret_sym := g.table.get_type_symbol(it.return_type) if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } g.write(ret_sym.name) g.write('(') } @@ -174,23 +275,19 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { g.inc_indent() g.writeln('try {') g.inc_indent() - g.write('return builtin.unwrap(') + g.write('return unwrap(') } if is_print { mut typ := node.args[0].typ expr := node.args[0].expr - g.write('builtin.$print_method (') + g.write('$print_method (') g.gen_expr_to_string(expr, typ) g.write(')') return } g.expr(it.left) - if name in g.builtin_fns { - g.write('builtin.') - } - g.write('${name}(') for i, arg in it.args { g.expr(arg.expr) @@ -220,9 +317,9 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { .propagate { panicstr := '`optional not set (\${err})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { - g.writeln('return builtin.panic($panicstr)') + g.writeln('return panic($panicstr)') } else { - g.writeln('builtin.js_throw(err)') + g.writeln('js_throw(err)') } } else {} diff --git a/vlib/v/gen/js/infix.v b/vlib/v/gen/js/infix.v new file mode 100644 index 0000000000..c6f6d094be --- /dev/null +++ b/vlib/v/gen/js/infix.v @@ -0,0 +1,243 @@ +module js + +import v.util +import v.ast + +fn (mut g JsGen) gen_plain_infix_expr(node ast.InfixExpr) { + it := node + l_sym := g.table.get_final_type_symbol(it.left_type) + r_sym := g.table.get_final_type_symbol(it.right_type) + greater_typ := g.greater_typ(it.left_type, it.right_type) + g.write('new ${g.typ(greater_typ)}( ') + g.cast_stack << greater_typ + if (l_sym.kind == .i64 || l_sym.kind == .u64) || (r_sym.kind == .i64 || r_sym.kind == .u64) { + g.write('BigInt(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf())') + g.write(' $node.op.str() ') + g.write('BigInt(') + g.expr(node.right) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf())') + } else { + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf()') + g.write(' $node.op.str() ') + g.expr(node.right) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf()') + } + + g.cast_stack.delete_last() + g.write(')') +} + +fn (mut g JsGen) infix_expr_arithmetic_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + method := g.table.type_find_method(left.sym, node.op.str()) or { + g.gen_plain_infix_expr(node) + return + } + left_styp := g.typ(left.typ.set_nr_muls(0)) + g.write(left_styp) + g.write('_') + g.write(util.replace_op(node.op.str())) + g.write('(') + g.op_arg(node.left, method.params[0].typ, left.typ) + g.write(', ') + g.op_arg(node.right, method.params[1].typ, right.typ) + g.write(')') +} + +fn (mut g JsGen) op_arg(expr ast.Expr, expected ast.Type, got ast.Type) { + mut needs_closing := 0 + mut nr_muls := got.nr_muls() + if expected.is_ptr() { + if nr_muls > 0 { + nr_muls-- + } else { + g.write('new \$ref(') + needs_closing++ + } + } + g.expr(expr) + g.write('.val'.repeat(nr_muls)) + for i := 0; i < needs_closing; i++ { + g.write(')') + } +} + +fn (mut g JsGen) infix_expr_eq_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '==') + g.write('new bool(') + if (left.typ.is_ptr() && right.typ.is_int()) || (right.typ.is_ptr() && left.typ.is_int()) { + g.gen_plain_infix_expr(node) + } else if has_operator_overloading { + if node.op == .ne { + g.write('!') + } + g.write(g.typ(left.unaliased.set_nr_muls(0))) + g.write('__eq(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write(',') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write(')') + } else if left.typ.idx() == right.typ.idx() + && left.sym.kind in [.array, .array_fixed, .alias, .map, .struct_, .sum_type] { + // TODO: Actually generate equality methods + if node.op == .ne { + g.write('!') + } + g.write('vEq(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write(',') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write(')') + } else { + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf() $node.op.str() ') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write('.valueOf()') + } + g.write(')') +} + +fn (mut g JsGen) infix_expr_cmp_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '<') + g.write('new bool(') + if left.sym.kind == right.sym.kind && has_operator_overloading { + if node.op in [.le, .ge] { + g.write('!') + } + g.write(g.typ(left.typ.set_nr_muls(0))) + g.write('__lt') + if node.op in [.lt, .ge] { + g.write('(') + + g.expr(node.left) + g.gen_deref_ptr(left.typ) + g.write(', ') + g.expr(node.right) + g.gen_deref_ptr(right.typ) + g.write(')') + } else { + g.write('(') + g.expr(node.right) + g.gen_deref_ptr(left.typ) + g.write(', ') + g.expr(node.left) + g.gen_deref_ptr(right.typ) + g.write(')') + } + } else { + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf() $node.op.str() ') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write('.valueOf()') + } + + g.write(')') +} + +fn (mut g JsGen) infix_expr_left_shift_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + if left.unaliased_sym.kind == .array { + // arr << val + array_info := left.unaliased_sym.info as ast.Array + g.write('Array.prototype.push.call(') + //&& array_info.elem_type != g.unwrap_generic(node.right_type) + if right.unaliased_sym.kind == .array && array_info.elem_type != right.typ { + g.expr(node.left) + g.gen_deref_ptr(left.typ) + g.write('.arr,...') + g.expr(node.right) + g.gen_deref_ptr(right.typ) + g.write('.arr') + g.write(')') + } else { + g.expr(node.left) + g.gen_deref_ptr(left.typ) + g.write('.arr,') + g.expr(node.right) + g.write(')') + } + } else { + g.gen_plain_infix_expr(node) + } +} + +fn (mut g JsGen) infix_in_not_in_op(node ast.InfixExpr) { + l_sym := g.table.get_final_type_symbol(node.left_type) + r_sym := g.table.get_final_type_symbol(node.right_type) + if node.op == .not_in { + g.write('!') + } + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + if r_sym.kind == .map { + g.write('.map.has(') + } else if r_sym.kind == .string { + g.write('.str.includes(') + } else { + g.write('.\$includes(') + } + g.expr(node.left) + if l_sym.kind == .string { + g.write('.str') + } + g.write(')') +} + +fn (mut g JsGen) infix_is_not_is_op(node ast.InfixExpr) { + g.expr(node.left) + rsym := g.table.get_type_symbol(g.unwrap(node.right_type).typ) + + g.gen_deref_ptr(node.left_type) + g.write(' instanceof ') + g.write(g.js_name(rsym.name)) +} + +fn (mut g JsGen) infix_expr(node ast.InfixExpr) { + match node.op { + .plus, .minus, .mul, .div, .mod { + g.infix_expr_arithmetic_op(node) + } + .eq, .ne { + g.infix_expr_eq_op(node) + } + .gt, .ge, .lt, .le { + g.infix_expr_cmp_op(node) + } + .logical_or, .and { + g.gen_plain_infix_expr(node) + } + .left_shift { + g.infix_expr_left_shift_op(node) + } + .not_in, .key_in { + g.infix_in_not_in_op(node) + } + .key_is, .not_is { + g.infix_is_not_is_op(node) + } + else { + g.gen_plain_infix_expr(node) + } + } +} diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index fc36d9f49d..41ecc0b5fa 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -39,7 +39,6 @@ struct SourcemapHelper { struct Namespace { name string mut: - out strings.Builder = strings.new_builder(128) pub_vars []string imports map[string]string indent int @@ -80,6 +79,7 @@ mut: sourcemap sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line comptime_var_type_map map[string]ast.Type defer_ifdef string + out strings.Builder = strings.new_builder(128) } fn (mut g JsGen) write_tests_definitions() { @@ -121,24 +121,27 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { g.find_class_methods(file.stmts) g.escape_namespace() } - for file in files { g.file = file g.enter_namespace(g.file.mod.name) + if g.enable_doc { + g.writeln('/** @namespace $file.mod.name */') + } g.is_test = g.pref.is_test // store imports mut imports := []string{} for imp in g.file.imports { imports << imp.mod } + graph.add(g.file.mod.name, imports) // builtin types if g.file.mod.name == 'builtin' && !g.generated_builtin { g.gen_builtin_type_defs() - g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') - g.writeln('Object.defineProperty(string.prototype,"len", { get: function() {return new builtin.int(this.str.length);}, set: function(l) {/* ignore */ } }); ') - g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new builtin.int(this.map.length);}, set: function(l) { this.map.length = l.valueOf(); } }); ') - g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(string.prototype,"len", { get: function() {return new int(this.str.length);}, set: function(l) {/* ignore */ } }); ') + g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new int(this.map.length);}, set: function(l) { this.map.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') g.generated_builtin = true } if g.is_test && !tests_inited { @@ -146,7 +149,6 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { tests_inited = true } g.stmts(file.stmts) - g.writeln('try { init() } catch (_) {}') // store the current namespace g.escape_namespace() } @@ -157,7 +159,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { deps_resolved := graph.resolve() nodes := deps_resolved.nodes - mut out := g.hashes() + g.definitions.str() + mut out := g.definitions.str() + g.hashes() // equality check for js objects // TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') // unsafe { @@ -165,30 +167,32 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { // out += eq_fn.data().vstring() //} out += fast_deep_eq_fn + + if pref.is_shared { + // Export, through CommonJS, the module of the entry file if `-shared` was passed + export := nodes[nodes.len - 1].name + out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n' + } + out += '\n' + out += g.out.str() + + /* for node in nodes { name := g.js_name(node.name).replace('.', '_') if g.enable_doc { out += '/** @namespace $name */\n' } - out += 'const $name = (function (' + // out += 'const $name = (function (' mut namespace := g.namespaces[node.name] - mut first := true - for _, val in namespace.imports { - if !first { - out += ', ' - } - first = false - out += val - } - out += ') {\n\t' - namespace_code := namespace.out.str() + + if g.pref.sourcemap { // calculate current output start line mut current_line := u32(out.count('\n') + 1) mut sm_pos := u32(0) for sourcemap_ns_entry in namespace.sourcemap_helper { // calculate final generated location in output based on position - current_segment := namespace_code.substr(int(sm_pos), int(sourcemap_ns_entry.ns_pos)) + current_segment := g.out.substr(int(sm_pos), int(sourcemap_ns_entry.ns_pos)) current_line += u32(current_segment.count('\n')) current_column := if last_nl_pos := current_segment.last_index('\n') { u32(current_segment.len - last_nl_pos - 1) @@ -202,66 +206,11 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { sm_pos = sourcemap_ns_entry.ns_pos } } - out += namespace_code + // public scope out += '\n' - if g.enable_doc { - out += '\n\t/* module exports */' - } - out += '\n\treturn {' - // export builtin types - if name == 'builtin' { - for typ in js.v_types { - out += '\n\t\t$typ,' - } - } - for i, pub_var in namespace.pub_vars { - out += '\n\t\t$pub_var' - if i < namespace.pub_vars.len - 1 { - out += ',' - } - } - if namespace.pub_vars.len > 0 { - out += '\n\t' - } - out += '};' - out += '\n})(' - first = true - for key, _ in namespace.imports { - if !first { - out += ', ' - } - first = false - out += key.replace('.', '_') - } - out += ');\n' - // generate builtin basic type casts - if name == 'builtin' { - out += '// builtin type casts\n' - out += 'const [' - for i, typ in js.v_types { - if i > 0 { - out += ', ' - } - out += '$typ' - } - out += '] = [' - for i, typ in js.v_types { - if i > 0 { - out += ',' - } - out += '\n\tfunction(val) { return new builtin.${typ}(val) }' - } - out += '\n]\n' - } - } - if pref.is_shared { - // Export, through CommonJS, the module of the entry file if `-shared` was passed - export := nodes[nodes.len - 1].name - out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n' - } - out += '\n' + }*/ if g.pref.sourcemap { out += g.create_sourcemap() } @@ -429,7 +378,7 @@ fn verror(msg string) { [inline] pub fn (mut g JsGen) gen_indent() { if g.ns.indent > 0 && g.empty_line { - g.ns.out.write_string(util.tabs(g.ns.indent)) + g.out.write_string(util.tabs(g.ns.indent)) } g.empty_line = false } @@ -450,7 +399,7 @@ pub fn (mut g JsGen) write(s string) { verror('g.write: not in a namespace') } g.gen_indent() - g.ns.out.write_string(s) + g.out.write_string(s) } [inline] @@ -459,7 +408,7 @@ pub fn (mut g JsGen) writeln(s string) { verror('g.writeln: not in a namespace') } g.gen_indent() - g.ns.out.writeln(s) + g.out.writeln(s) g.empty_line = true } @@ -490,6 +439,7 @@ fn (mut g JsGen) get_alias(name string) string { } fn (mut g JsGen) js_name(name_ string) string { + /* mut is_js := false is_overload := ['+', '-', '*', '/', '==', '<', '>'] mut name := name_ @@ -543,7 +493,18 @@ fn (mut g JsGen) js_name(name_ string) string { } } } - return parts.join('.') + return parts.join('.')*/ + + mut name := name_ + if name.starts_with('JS.') { + name = name[3..] + return name + } + name = name_.replace('.', '__') + if name in js.js_reserved { + return '_v_$name' + } + return name } fn (mut g JsGen) stmts(stmts []ast.Stmt) { @@ -561,11 +522,11 @@ fn (mut g JsGen) write_v_source_line_info(pos token.Position) { g.ns.sourcemap_helper << SourcemapHelper{ src_path: util.vlines_escape_path(g.file.path, g.pref.ccompiler) src_line: u32(pos.line_nr + 1) - ns_pos: u32(g.ns.out.len) + ns_pos: u32(g.out.len) } } if g.pref.is_vlines && g.is_vlines_enabled { - g.write(' /* ${pos.line_nr + 1} $g.ns.out.len */ ') + g.write(' /* ${pos.line_nr + 1} $g.out.len */ ') } } @@ -609,7 +570,7 @@ fn (mut g JsGen) gen_global_decl(node ast.GlobalDecl) { } fn (mut g JsGen) stmt_no_semi(node ast.Stmt) { - g.stmt_start_pos = g.ns.out.len + g.stmt_start_pos = g.out.len match node { ast.EmptyStmt {} ast.AsmStmt { @@ -712,7 +673,7 @@ fn (mut g JsGen) stmt_no_semi(node ast.Stmt) { } fn (mut g JsGen) stmt(node ast.Stmt) { - g.stmt_start_pos = g.ns.out.len + g.stmt_start_pos = g.out.len match node { ast.EmptyStmt {} ast.AsmStmt { @@ -840,11 +801,13 @@ fn (mut g JsGen) expr(node ast.Expr) { // TODO } ast.BoolLiteral { + g.write('new bool(') if node.val == true { g.write('true') } else { g.write('false') } + g.write(')') } ast.CallExpr { g.gen_call_expr(node) @@ -856,7 +819,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_type_cast_expr(node) } ast.CharLiteral { - g.write("new builtin.byte('$node.val')") + g.write("new byte('$node.val')") } ast.Comment {} ast.ConcatExpr { @@ -886,7 +849,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_index_expr(node) } ast.InfixExpr { - g.gen_infix_expr(node) + g.infix_expr(node) } ast.IntegerLiteral { g.gen_integer_literal_expr(node) @@ -898,7 +861,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_map_init_expr(node) } ast.None { - g.write('builtin.none__') + g.write('none__') } ast.MatchExpr { g.match_expr(node) @@ -988,8 +951,8 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.TypeNode { typ := g.unwrap_generic(node.typ) sym := g.table.get_type_symbol(typ) - name := sym.name.replace_once('${g.ns.name}.', '') - g.write('$name') + + g.write('${g.js_name(sym.name)}') } ast.Likely { g.write('(') @@ -1023,9 +986,9 @@ fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { src := node.expr.str() metaname := 'v_assert_meta_info_$g.new_tmp_var()' g.writeln('let $metaname = {}') - g.writeln('${metaname}.fpath = new builtin.string("$mod_path");') - g.writeln('${metaname}.line_nr = new builtin.int("$line_nr")') - g.writeln('${metaname}.fn_name = new builtin.string("$fn_name")') + g.writeln('${metaname}.fpath = new string("$mod_path");') + g.writeln('${metaname}.line_nr = new int("$line_nr")') + g.writeln('${metaname}.fn_name = new string("$fn_name")') metasrc := src g.writeln('${metaname}.src = "$metasrc"') @@ -1034,18 +997,18 @@ fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { expr_op_str := node.expr.op.str() expr_left_str := node.expr.left.str() expr_right_str := node.expr.right.str() - g.writeln('\t${metaname}.op = new builtin.string("$expr_op_str");') - g.writeln('\t${metaname}.llabel = new builtin.string("$expr_left_str");') - g.writeln('\t${metaname}.rlabel = new builtin.string("$expr_right_str");') - g.write('\t${metaname}.lvalue = new builtin.string("') + g.writeln('\t${metaname}.op = new string("$expr_op_str");') + g.writeln('\t${metaname}.llabel = new string("$expr_left_str");') + g.writeln('\t${metaname}.rlabel = new string("$expr_right_str");') + g.write('\t${metaname}.lvalue = new string("') g.gen_assert_single_expr(node.expr.left, node.expr.left_type) g.writeln('");') - g.write('\t${metaname}.rvalue = new builtin.string("') + g.write('\t${metaname}.rvalue = new string("') g.gen_assert_single_expr(node.expr.right, node.expr.right_type) g.writeln('");') } ast.CallExpr { - g.writeln('\t${metaname}.op = new builtin.string("call");') + g.writeln('\t${metaname}.op = new string("call");') } else {} } @@ -1081,25 +1044,25 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { g.writeln('// assert') g.write('if( ') g.expr(a.expr) - g.write(' ) {') + g.write('.valueOf() ) {') s_assertion := a.expr.str().replace('"', "'") mut mod_path := g.file.path.replace('\\', '\\\\') if g.is_test { metaname_ok := g.gen_assert_metainfo(a) g.writeln(' g_test_oks++;') - g.writeln(' cb_assertion_ok($metaname_ok);') + g.writeln(' main__cb_assertion_ok($metaname_ok);') g.writeln('} else {') metaname_fail := g.gen_assert_metainfo(a) g.writeln(' g_test_fails++;') - g.writeln(' cb_assertion_failed($metaname_fail);') - g.writeln(' builtin.exit(1);') + g.writeln(' main__cb_assertion_failed($metaname_fail);') + g.writeln(' exit(1);') g.writeln('}') return } g.writeln('} else {') g.inc_indent() - g.writeln('builtin.eprintln("$mod_path:${a.pos.line_nr + 1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion");') - g.writeln('builtin.exit(1);') + g.writeln('eprintln(new string("$mod_path:${a.pos.line_nr + 1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));') + g.writeln('exit(1);') g.dec_indent() g.writeln('}') } @@ -1228,9 +1191,6 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { if should_cast { g.cast_stack << stmt.left_types.first() g.write('new ') - if g.file.mod.name != 'builtin' { - g.write('builtin.') - } g.write('${g.typ(stmt.left_types.first())}(') } g.expr(val) @@ -1345,10 +1305,14 @@ fn (g &JsGen) fn_gen_type(it &ast.FnDecl) FnGenType { fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { res := g.fn_gen_type(it) + if it.language == .js { + return + } + /* if res == .struct_method { // Struct methods are handled by class generation code. return - } + }*/ if g.inside_builtin { g.builtin_fns << it.name } @@ -1370,15 +1334,131 @@ fn fn_has_go(node ast.FnDecl) bool { return has_go } +// cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo) +fn (mut g JsGen) cc_type(typ ast.Type, is_prefix_struct bool) string { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut styp := sym.cname + match mut sym.info { + ast.Struct, ast.Interface, ast.SumType { + if sym.info.is_generic { + mut sgtyps := '_T' + for gt in sym.info.generic_types { + gts := g.table.get_type_symbol(g.unwrap_generic(gt)) + sgtyps += '_$gts.cname' + } + styp += sgtyps + } + } + else {} + } + return styp +} + +fn (mut g JsGen) generic_fn_name(types []ast.Type, before string, is_decl bool) string { + if types.len == 0 { + return before + } + + mut name := before + '_T' + for typ in types { + name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.typ(typ.set_nr_muls(0)) + } + return name +} + fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { unsafe { g.fn_decl = &it } + cur_fn_save := g.table.cur_fn + defer { + g.table.cur_fn = cur_fn_save + } + unsafe { + g.table.cur_fn = &it + } + node := it + mut name := it.name + if name in ['+', '-', '*', '/', '%', '<', '=='] { + name = util.replace_op(name) + } + + if node.is_method { + unwrapped_rec_sym := g.table.get_type_symbol(g.unwrap_generic(node.receiver.typ)) + if unwrapped_rec_sym.kind == .placeholder { + return + } + name = g.cc_type(node.receiver.typ, false) + '_' + name + } + + name = g.js_name(name) + + name = g.generic_fn_name(g.table.cur_concrete_types, name, true) + + has_go := fn_has_go(it) + if it.is_pub && !it.is_method { + g.push_pub_var(name) + } + is_main := it.name == 'main.main' + g.gen_attrs(it.attrs) + if is_main { + // there is no concept of main in JS but we do have iife + g.writeln('/* program entry point */') + + g.write('(') + if has_go { + g.write('async ') + } + g.write('function(') + } else if it.is_anon { + g.write('function (') + } else { + c := name[0] + if c in [`+`, `-`, `*`, `/`] { + name = util.replace_op(name) + } + // type_name := g.typ(it.return_type) + // generate jsdoc for the function + g.doc.gen_fn(it) + if has_go { + g.write('async ') + } + + g.write('function ') + + g.write('${name}(') + if it.is_pub && !it.is_method { + g.push_pub_var(name) + } + } + mut args := it.params + + g.fn_args(args, it.is_variadic) + g.write(') {') + for i, arg in args { + is_varg := i == args.len - 1 && it.is_variadic + arg_name := g.js_name(arg.name) + if is_varg { + g.writeln('$arg_name = new array($arg_name);') + } else { + if arg.typ.is_ptr() || arg.is_mut { + g.writeln('$arg_name = new \$ref($arg_name)') + } + } + } + g.stmts(it.stmts) + g.writeln('}') + + if is_main { + g.write(')();') + } + g.writeln('') + /* if typ == .alias_method || typ == .iface_method { sym := g.table.get_final_type_symbol(it.params[0].typ.set_nr_muls(0)) name := g.js_name(sym.name) if name in js.v_types { - g.writeln('builtin.') + g.writeln('') } g.writeln('${name}.prototype.$it.name = function ') } @@ -1468,7 +1548,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { if typ == .struct_method || typ == .alias_method || typ == .iface_method { g.writeln('\n') } - + */ g.fn_decl = voidptr(0) } @@ -1522,7 +1602,7 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { g.expr(it.cond) g.write('; $i < ') g.expr(it.high) - g.writeln('; $i = new builtin.int($i + 1)) {') + g.writeln('; $i = new int($i + 1)) {') g.inside_loop = false g.stmts(it.stmts) g.writeln('}') @@ -1539,9 +1619,9 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { g.write('.valueOf()') } g.write('.str.split(\'\').entries(), ([$it.key_var, $val]) => [$it.key_var, ') - if g.ns.name == 'builtin' { - g.write('new ') - } + + g.write('new ') + g.write('byte($val)])') } else { g.expr(it.cond) @@ -1562,9 +1642,9 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { // cast characters to bytes if val !in ['', '_'] && it.kind == .string { g.write('.map(c => ') - if g.ns.name == 'builtin' { - g.write('new ') - } + + g.write('new ') + g.write('byte(c))') } } @@ -1604,15 +1684,12 @@ fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) { fn (mut g JsGen) gen_go_expr(node ast.GoExpr) { // TODO Handle joinable expressions // node.is_expr - mut name := node.call_expr.name + mut name := g.js_name(node.call_expr.name) if node.call_expr.is_method { receiver_sym := g.table.get_type_symbol(node.call_expr.receiver_type) name = receiver_sym.name + '.' + name } - // todo: please add a name feild without the mod name for ast.CallExpr - if name.starts_with('${node.call_expr.mod}.') { - name = name[node.call_expr.mod.len + 1..] - } + g.writeln('await new Promise(function(resolve){') g.inc_indent() g.write('${name}(') @@ -1644,7 +1721,7 @@ fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) { } fn (mut g JsGen) gen_optional_error(expr ast.Expr) { - g.write('new builtin.Option({ state: new builtin.byte(2),err: ') + g.write('new Option({ state: new byte(2),err: ') g.expr(expr) g.write('})') } @@ -1682,9 +1759,7 @@ fn (mut g JsGen) gen_return_stmt(it ast.Return) { if fn_return_is_optional { tmp := g.new_tmp_var() g.write('const $tmp = new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } + g.writeln('Option({});') g.write('${tmp}.data = ') if it.exprs.len == 1 { @@ -1746,17 +1821,6 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { g.writeln('...${etyp}.prototype,') } fns := g.method_fn_decls[name] - for field in node.fields { - typ := g.typ(field.typ) - g.doc.gen_typ(typ) - g.write('$field.name: ${g.to_js_typ_val(field.typ)}') - g.writeln(',') - } - - for cfn in fns { - g.gen_method_decl(cfn, .struct_method) - g.writeln(',') - } // gen toString method fn_names := fns.map(it.name) if 'toString' !in fn_names { @@ -1778,9 +1842,22 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { g.dec_indent() g.writeln('},') } + for field in node.fields { + typ := g.typ(field.typ) + g.doc.gen_typ(typ) + g.write('$field.name: ${g.to_js_typ_val(field.typ)}') + g.writeln(',') + } g.writeln('\$toJS() { return this; }') - g.dec_indent() + g.writeln('};\n') + g.dec_indent() + + /* + for cfn in fns { + g.gen_method_decl(cfn, .struct_method) + }*/ + if node.is_pub { g.push_pub_var(name) } @@ -2104,7 +2181,7 @@ fn (mut g JsGen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) { g.expr(stmt.expr) g.writeln(';') } else { - g.write('builtin.opt_ok(') + g.write('opt_ok(') g.stmt(stmt) g.writeln(', $tmp_var);') } @@ -2167,9 +2244,7 @@ fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var M g.expr(branch.exprs[sumtype_index]) } else { g.write(' instanceof ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } + g.write('None__') } } @@ -2352,11 +2427,16 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { left_typ := g.table.get_type_symbol(expr.left_type) // TODO: Handle splice setting if it's implemented if expr.index is ast.RangeExpr { + if left_typ.kind == .array { + g.write('array_slice(') + } else { + g.write('string_slice(') + } g.expr(expr.left) if expr.left_type.is_ptr() { g.write('.valueOf()') } - g.write('.slice(') + g.write(',') if expr.index.has_low { g.expr(expr.index.low) } else { @@ -2370,7 +2450,7 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { if expr.left_type.is_ptr() { g.write('.valueOf()') } - g.write('.length') + g.write('.len') } g.write(')') } else if left_typ.kind == .map { @@ -2444,9 +2524,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { it.right_type } // g.greater_typ(it.left_type, it.right_type) g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } + g.write('${g.typ(greater_typ)}(') g.cast_stack << greater_typ g.write('BigInt((') @@ -2466,10 +2544,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { return } if it.op == .logical_or || it.op == .and { - if g.ns.name == 'builtin' { - g.write('new ') - } - g.write('bool(') + g.write('new bool(') g.expr(it.left) g.write('.valueOf()') g.write(it.op.str()) @@ -2477,6 +2552,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.write('.valueOf()') g.write(')') } else if it.op == .eq || it.op == .ne { + /* has_operator_overloading := g.table.type_has_method(l_sym, '==') if has_operator_overloading { g.expr(it.left) @@ -2506,6 +2582,32 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.expr(it.right) g.gen_deref_ptr(it.right_type) g.write(')') + }*/ + node := it + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '==') + if has_operator_overloading + || (l_sym.kind in js.shallow_equatables && r_sym.kind in js.shallow_equatables) { + if node.op == .ne { + g.write('!') + } + g.write(g.typ(left.unaliased.set_nr_muls(0))) + g.write('__eq(') + g.expr(node.left) + g.gen_deref_ptr(left.typ) + g.write(',') + g.expr(node.right) + g.gen_deref_ptr(right.typ) + g.write(')') + } else { + g.write('vEq(') + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(', ') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(')') } } else if l_sym.kind == .array && it.op == .left_shift { // arr << 1 g.write('Array.prototype.push.call(') @@ -2609,14 +2711,14 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { } if is_arithmetic { - if g.ns.name == 'builtin' { - g.write('new ') - } + g.write('new ') + g.write('${g.typ(greater_typ)}(') g.cast_stack << greater_typ } g.expr(it.left) + g.gen_deref_ptr(it.left_type) // g.write('.val') g.write(' $it.op ') @@ -2732,7 +2834,7 @@ fn (mut g JsGen) type_name(raw_type ast.Type) { } else { s = g.table.type_to_str(g.unwrap_generic(typ)) } - g.write('new builtin.string("$s")') + g.write('new string("$s")') } fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { @@ -2744,7 +2846,7 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { return } .typ { - g.write('new builtin.int(') + g.write('new int(') g.write('${int(g.unwrap_generic(it.name_type))}') g.write(')') @@ -2756,7 +2858,7 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { g.type_name(it.name_type) return } else if node.field_name == 'idx' { - g.write('new builtin.int(') + g.write('new int(') g.write('${int(g.unwrap_generic(it.name_type))}') g.write(')') return @@ -2777,9 +2879,8 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) if should_cast { - if g.file.mod.name == 'builtin' { - g.write('new ') - } + g.write('new ') + g.write('string(') } g.write('`') @@ -2817,9 +2918,8 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { text = text.replace('"', '\\"') should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) if true || should_cast { - if g.file.mod.name == 'builtin' { - g.write('new ') - } + g.write('new ') + g.write('string(') } if it.is_raw { @@ -2892,10 +2992,8 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { tsym := g.table.get_final_type_symbol(it.typ) if it.expr is ast.IntegerLiteral && (tsym.kind == .i64 || tsym.kind == .u64) { g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } - g.write(tsym.kind.str()) + + g.write('$tsym.kind.str()') g.write('(BigInt(') g.write(it.expr.val) g.write('n))') @@ -2913,9 +3011,9 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { if it.typ.is_ptr() { g.write('new \$ref(') } - if typ !in js.v_types || g.ns.name == 'builtin' { - g.write('new ') - } + + g.write('new ') + g.write('${typ}(') } g.expr(it.expr) @@ -2954,17 +3052,12 @@ fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) { if g.cast_stack.len > 0 { if g.cast_stack[g.cast_stack.len - 1] in ast.integer_type_idxs { g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } + g.write('int($it.val)') return } } g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } g.write('${g.typ(typ)}($it.val)') } @@ -3003,9 +3096,6 @@ fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) { } } g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } g.write('${g.typ(typ)}($it.val)') } @@ -3020,3 +3110,17 @@ fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type { } return typ } + +fn replace_op(s string) string { + return match s { + '+' { '_plus' } + '-' { '_minus' } + '*' { '_mult' } + '/' { '_div' } + '%' { '_mod' } + '<' { '_lt' } + '>' { '_gt' } + '==' { '_eq' } + else { '' } + } +} diff --git a/vlib/v/gen/js/str.v b/vlib/v/gen/js/str.v index ba963b17e8..af4bac4450 100644 --- a/vlib/v/gen/js/str.v +++ b/vlib/v/gen/js/str.v @@ -3,13 +3,14 @@ module js import v.ast fn (mut g JsGen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + is_shared := etype.has_flag(.shared_f) mut typ := etype - if etype.has_flag(.shared_f) { + if is_shared { typ = typ.clear_flag(.shared_f).set_nr_muls(0) } mut sym := g.table.get_type_symbol(typ) - + // when type is alias, print the aliased value if mut sym.info is ast.Alias { parent_sym := g.table.get_type_symbol(sym.info.parent_type) if parent_sym.has_method('str') { @@ -17,69 +18,58 @@ fn (mut g JsGen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { sym = parent_sym } } - sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + is_var_mut := expr.is_auto_deref_var() if typ.has_flag(.variadic) { - // todo(playX): generate str method just like in the C backend - g.write('new string(') + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') g.expr(expr) - g.write('.valueOf()') - g.write('.toString())') + g.write(')') } else if typ == ast.string_type { g.expr(expr) } else if typ == ast.bool_type { - g.write('new string(') + g.write('new string((') g.expr(expr) - g.write('.valueOf() ? "true" : "false")') + g.write(').valueOf() ? "true" : "false")') } else if sym.kind == .none_ { g.write('new string("")') } else if sym.kind == .enum_ { - g.write('new string(') if expr !is ast.EnumVal { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') g.expr(expr) - g.write('.valueOf()') - g.write('.toString()') + g.write(')') } else { - g.write('"') + g.write('new string("') g.expr(expr) - g.write('"') + g.write('")') } - g.write(')') - } else if sym.kind == .interface_ && sym_has_str_method { + } else if sym_has_str_method + || sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return, .sum_type, .interface_] { is_ptr := typ.is_ptr() - g.write(sym.mod.replace_once('${g.ns.name}.', '')) - g.write('.') - g.write(sym.name) - g.write('.prototype.str.call(') - g.expr(expr) - if !str_method_expects_ptr && is_ptr { - g.gen_deref_ptr(typ) - } - g.write(')') - } - //|| sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return,.sum_type, .interface_] - else if sym_has_str_method { - g.write('new string(') - g.write('Object.getPrototypeOf(/*str exists*/') - g.expr(expr) - is_ptr := typ.is_ptr() - g.gen_deref_ptr(typ) - g.write(').str.call(') - g.expr(expr) - if !str_method_expects_ptr && is_ptr { - g.gen_deref_ptr(typ) + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + if str_method_expects_ptr && !is_ptr { + g.write('new \$ref(') + g.expr(expr) + g.write(')') + } else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut { + g.expr(expr) + g.gen_deref_ptr(etype) } - g.write('))') - } else if sym.kind == .struct_ && !sym_has_str_method { - g.write('new string(') g.expr(expr) - g.gen_deref_ptr(typ) - g.write('.toString())') + g.write(')') } else { - g.write('new string(') - g.expr(expr) - g.gen_deref_ptr(typ) - g.write('.valueOf().toString())') + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + + if sym.kind != .function { + g.expr(expr) + if expr.is_auto_deref_var() { + g.write('.val') + } + } + g.write(')') } } diff --git a/vlib/v/gen/js/tests/testdata/array.out b/vlib/v/gen/js/tests/testdata/array.out index 36afbc1236..73999a0528 100644 --- a/vlib/v/gen/js/tests/testdata/array.out +++ b/vlib/v/gen/js/tests/testdata/array.out @@ -60,16 +60,16 @@ true 0 1 1.1 -[[1, 2], 3, 4] -[[1, 2], [5, 6], 3, 4] +[1, 2, 3, 4] +[1, 5, 6, 2, 3, 4] 0 1 1 0 1 1.1 -[[1, 2], 3, 4] -[[5, 6], [1, 2], 3, 4] +[1, 2, 3, 4] +[5, 6, 1, 2, 3, 4] 5 true 1.1 @@ -135,8 +135,8 @@ true 0 2 -1 --1 --1 +1 +2 -1 2 3 @@ -226,7 +226,6 @@ true true [1, 3, 5, hi] [-3, 7, 42, 67, 108] -[97, 98, 99, 100, 101, 102] 0 1 79 @@ -278,15 +277,15 @@ a 123 123 [[1, 2, 3]] -[[[1, 2, 3]]] [[1, 2, 3]] -[[[1, 2, 3]]] +[[1, 2, 3]] +[[1, 2, 3]] +true true true true true true -false true true true @@ -297,7 +296,13 @@ true [[], [], [], []] [[], [], [123], []] [{}, {}, {}, {}] -[{}, {}, {'123': 123}, {}] -Numbers { odds: [1, 3, 5] , evens: [2, 4] } -Numbers { odds: [3, 5, 7] , evens: [2, 6, 10] } -[[10, 10, 10], [10, 10, 10], [10, 10, 10]] +[{}, {}, {123: 123}, {}] +Numbers{ + odds: [1, 3, 5] + evens: [2, 4] +} +Numbers{ + odds: [3, 5, 7] + evens: [2, 6, 10] +} +[[10, 10, 10], [10, 10, 10], [10, 10, 10]] \ No newline at end of file diff --git a/vlib/v/gen/js/tests/testdata/array.v b/vlib/v/gen/js/tests/testdata/array.v index efe5a8af4d..8a98597e57 100644 --- a/vlib/v/gen/js/tests/testdata/array.v +++ b/vlib/v/gen/js/tests/testdata/array.v @@ -798,10 +798,11 @@ fn main() { */ } { + /* // test rune sort mut bs := [`f`, `e`, `d`, `b`, `c`, `a`] bs.sort() - println(bs) + println(bs)*/ /* bs.sort(a > b) diff --git a/vlib/v/gen/js/tests/testdata/overloading.out b/vlib/v/gen/js/tests/testdata/overloading.out index 11dc3d88ce..bf79d4d5ca 100644 --- a/vlib/v/gen/js/tests/testdata/overloading.out +++ b/vlib/v/gen/js/tests/testdata/overloading.out @@ -1,3 +1,6 @@ -Foo { x: 5 , y: 5 } +Foo{ + x: 5 + y: 5 +} true true \ No newline at end of file diff --git a/vlib/v/gen/js/util.v b/vlib/v/gen/js/util.v new file mode 100644 index 0000000000..6a0d729a60 --- /dev/null +++ b/vlib/v/gen/js/util.v @@ -0,0 +1,35 @@ +module js + +import v.ast + +struct Type { + // typ is the original type + typ ast.Type [required] + sym &ast.TypeSymbol [required] + // unaliased is `typ` once aliased have been resolved + // it may not contain informations such as flags and nr_muls + unaliased ast.Type [required] + unaliased_sym &ast.TypeSymbol [required] +} + +// unwrap returns the following variants of a type: +// * generics unwrapped +// * alias unwrapped +fn (mut g JsGen) unwrap(typ ast.Type) Type { + no_generic := g.unwrap_generic(typ) + no_generic_sym := g.table.get_type_symbol(no_generic) + if no_generic_sym.kind != .alias { + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic + unaliased_sym: no_generic_sym + } + } + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic_sym.parent_idx + unaliased_sym: g.table.get_type_symbol(no_generic_sym.parent_idx) + } +} diff --git a/vlib/v/preludes_js/stats_import.v b/vlib/v/preludes_js/stats_import.v new file mode 100644 index 0000000000..b91c88cd28 --- /dev/null +++ b/vlib/v/preludes_js/stats_import.v @@ -0,0 +1 @@ +module stats_import