From 65e888230ad665aa08ab3ef9ef3277537ada6396 Mon Sep 17 00:00:00 2001 From: Leah Lundqvist Date: Thu, 4 Mar 2021 14:02:16 +0100 Subject: [PATCH] jsgen: object equality checks, optimise casting and start builtin implementation (#9068) --- vlib/builtin/js/jsfns.js.v | 31 +++- vlib/builtin/js/string.v | 154 +++++++++++++++++ vlib/v/gen/js/builtin_types.v | 92 ++++++++-- vlib/v/gen/js/fast_deep_equal.js | 66 ++++++++ vlib/v/gen/js/js.v | 240 +++++++++++++++++++++------ vlib/v/gen/js/temp_fast_deep_equal.v | 72 ++++++++ 6 files changed, 581 insertions(+), 74 deletions(-) create mode 100644 vlib/v/gen/js/fast_deep_equal.js create mode 100644 vlib/v/gen/js/temp_fast_deep_equal.v diff --git a/vlib/builtin/js/jsfns.js.v b/vlib/builtin/js/jsfns.js.v index 677d6aa960..c0dc1f292f 100644 --- a/vlib/builtin/js/jsfns.js.v +++ b/vlib/builtin/js/jsfns.js.v @@ -12,7 +12,9 @@ pub struct JS.String { length JS.Number } pub struct JS.Boolean {} -pub struct JS.Array {} +pub struct JS.Array { + length JS.Number +} pub struct JS.Map {} // Type prototype functions @@ -22,12 +24,18 @@ fn (v JS.Boolean) toString() JS.String fn (v JS.Array) toString() JS.String fn (v JS.Map) toString() JS.String -fn (v JS.String) slice(a int, b int) JS.String +// Hack for "`[]JS.String` is not a struct" when returning arr.length or arr.len +// TODO: Fix []JS.String not a struct error +fn native_str_arr_len(arr []JS.String) int { + len := 0 + #len = arr.length + return len +} // Top level functions fn JS.eval(string) any -fn JS.parseInt(string, f64) f64 -fn JS.parseFloat(string) f64 +fn JS.parseInt(string, f64) JS.Number +fn JS.parseFloat(string) JS.Number fn JS.isNaN(f64) bool fn JS.isFinite(f64) bool fn JS.decodeURI(string) string @@ -84,3 +92,18 @@ fn JS.Math.tan(f64) f64 // JSON fn JS.JSON.stringify(any) string fn JS.JSON.parse(string) any + +// String +fn (v JS.String) slice(a int, b int) JS.String +fn (v JS.String) split(dot JS.String) []JS.String +fn (s JS.String) indexOf(needle JS.String) int +fn (s JS.String) lastIndexOf(needle JS.String) int + +fn (s JS.String) charAt(i int) JS.String +fn (s JS.String) charCodeAt(i int) byte +fn (s JS.String) toUpperCase() JS.String +fn (s JS.String) toLowerCase() JS.String +fn (s JS.String) concat(a JS.String) JS.String +fn (s JS.String) includes(substr JS.String) bool +fn (s JS.String) ends_with(substr JS.String) bool +fn (s JS.String) starts_with(substr JS.String) bool diff --git a/vlib/builtin/js/string.v b/vlib/builtin/js/string.v index 51ff40a13d..7602ca5a86 100644 --- a/vlib/builtin/js/string.v +++ b/vlib/builtin/js/string.v @@ -2,8 +2,162 @@ module builtin pub struct string { str JS.String + len u32 } pub fn (s string) slice(a int, b int) string { return string(s.str.slice(a, b)) } + +pub fn (s string) after(dot string) string { + return string(s.str.slice(s.str.lastIndexOf(dot.str) + 1, int(s.str.length))) +} + + +pub fn (s string) after_char(dot byte) string { + // TODO: Implement after byte + return s +} + +pub fn (s string) all_after(dot string) string { + return string(s.str.slice(s.str.indexOf(dot.str) + 1, int(s.str.length))) +} + +// why does this exist? +pub fn (s string) all_after_last(dot string) string { + return s.after(dot) +} + +pub fn (s string) all_before(dot string) string { + return string(s.str.slice(0, s.str.indexOf(dot.str))) +} + +pub fn (s string) all_before_last(dot string) string { + return string(s.str.slice(0, s.str.lastIndexOf(dot.str))) +} + +pub fn (s string) bool() bool { + return s == 'true' +} + +pub fn (s string) split(dot string) []string { + return s.str.split(dot.str).map(string(it)) +} + +pub fn (s string) bytes() []byte { + sep := '' + return s.str.split(sep.str).map(it.charCodeAt(0)) +} + +pub fn (s string) capitalize() string { + part := string(s.str.slice(1, int(s.str.length))) + return string(s.str.charAt(0).toUpperCase().concat(part.str)) +} + +pub fn (s string) clone() string { + return string(s.str) +} + +pub fn (s string) contains(substr string) bool { + return s.str.includes(substr.str) +} + +pub fn (s string) contains_any(chars string) bool { + sep := '' + for x in chars.str.split(sep.str) { + if s.str.includes(x) { + return true + } + } + return false +} + +pub fn (s string) contains_any_substr(chars []string) bool { + for x in chars { + if s.str.includes(x.str) { + return true + } + } + return false +} + +pub fn (s string) count(substr string) int { + // TODO: "error: `[]JS.String` is not a struct" when returning arr.length or arr.len + arr := s.str.split(substr.str) + return native_str_arr_len(arr) +} + +pub fn (s string) ends_with(p string) bool { + return s.str.ends_with(p.str) +} + +pub fn (s string) starts_with(p string) bool { + return s.str.starts_with(p.str) +} + +pub fn (s string) fields() []string { + return []// s.str.split() +} + +pub fn (s string) find_between(start string, end string) string { + return string(s.str.slice(s.str.indexOf(start.str), s.str.indexOf(end.str) + 1)) +} + +// unnecessary in the JS backend, implemented for api parity. +pub fn (s string) free () {} + +pub fn (s string) hash () int { + mut h := u32(0) + if h == 0 && s.len > 0 { + for c in s { + h = h * 31 + u32(c) + } + } + return int(h) +} + +// int returns the value of the string as an integer `'1'.int() == 1`. +pub fn (s string) int() int { + return int(JS.parseInt(s)) +} + +// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`. +pub fn (s string) i64() i64 { + return i64(JS.parseInt(s)) +} + +// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`. +pub fn (s string) i8() i8 { + return i8(JS.parseInt(s)) +} + +// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`. +pub fn (s string) i16() i16 { + return i16(JS.parseInt(s)) +} + +// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`. +pub fn (s string) f32() f32 { + // return C.atof(charptr(s.str)) + return f32(JS.parseFloat(s)) +} + +// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`. +pub fn (s string) f64() f64 { + return f64(JS.parseFloat(s)) +} + +// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`. +pub fn (s string) u16() u16 { + return u16(JS.parseInt(s)) +} + +// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`. +pub fn (s string) u32() u32 { + return u32(JS.parseInt(s)) +} + +// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`. +pub fn (s string) u64() u64 { + return u64(JS.parseInt(s)) +} \ No newline at end of file diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 4a1bfe4a1d..6e8b6c167e 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -210,23 +210,36 @@ fn (mut g JsGen) struct_typ(s string) string { return styp + '["prototype"]' } +struct BuiltinPrototypeCongig { + typ_name string + val_name string = 'val' + default_value string + constructor string = 'this.val = val' + value_of string = 'this.val' + to_string string = 'this.val.toString()' + eq string = 'this.val === other.val' + extras string +} + // ugly arguments but not sure a config struct would be worth it -fn (mut g JsGen) gen_builtin_prototype(typ_name string, val_name string, default_value string, constructor string, value_of string, to_string string, extras string) { - g.writeln('function ${typ_name}($val_name = ${default_value}) { $constructor }') - g.writeln('${typ_name}.prototype = {') +fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeCongig) { + g.writeln('function ${c.typ_name}(${c.val_name} = ${c.default_value}) { ${c.constructor} }') + g.writeln('${c.typ_name}.prototype = {') g.inc_indent() - g.writeln('val: $default_value,') - if extras.len > 0 { - g.writeln('$extras,') + g.writeln('val: ${c.default_value},') + if c.extras.len > 0 { + g.writeln('${c.extras},') } - for method in g.method_fn_decls[typ_name] { + for method in g.method_fn_decls[c.typ_name] { g.inside_def_typ_decl = true g.gen_method_decl(method) g.inside_def_typ_decl = false g.writeln(',') } - g.writeln('valueOf() { return $value_of },') - g.writeln('toString() { return $to_string }') + g.writeln('valueOf() { return ${c.value_of} },') + g.writeln('toString() { return ${c.to_string} },') + g.writeln('eq(other) { return ${c.eq} },') + g.writeln('str() { return new string(this.toString()) }') g.dec_indent() g.writeln('};\n') } @@ -237,24 +250,71 @@ fn (mut g JsGen) gen_builtin_type_defs() { for typ_name in v_types { // TODO: JsDoc match typ_name { - 'i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'int_literal', 'size_t' { + 'i8', 'i16', 'int', 'i64', 'u16', 'u32', 'u64', 'int_literal', 'size_t' { // TODO: Bounds checking - g.gen_builtin_prototype(typ_name, 'val', 'new Number(0)', 'this.val = val | 0;', 'this.val | 0', '(this.val | 0).toString()', '') + g.gen_builtin_prototype({ + typ_name: typ_name + default_value: 'new Number(0)' + constructor: 'this.val = val | 0' + value_of: 'this.val | 0' + to_string: 'this.valueOf().toString()' + eq: 'this.valueOf() === other.valueOf()' + }) + } + 'byte' { + g.gen_builtin_prototype({ + typ_name: typ_name + default_value: 'new Number(0)' + constructor: 'this.val = typeof(val) == "string" ? val.charCodeAt() : (val | 0)' + value_of: 'this.val | 0' + to_string: 'String.fromCharCode(this.val)' + eq: 'this.valueOf() === other.valueOf()' + }) } 'f32', 'f64', 'float_literal' { - g.gen_builtin_prototype(typ_name, 'val', 'new Number(0)', 'this.val = val;', 'this.val', 'this.val.toString()', '') + g.gen_builtin_prototype({ + typ_name: typ_name + default_value: 'new Number(0)' + }) } 'bool' { - g.gen_builtin_prototype(typ_name, 'val', 'new Boolean(false)', 'this.val = val;', 'this.val', 'this.val.toString()', '') + g.gen_builtin_prototype({ + typ_name: typ_name + default_value: 'new Boolean(false)' + }) } 'string' { - g.gen_builtin_prototype(typ_name, 'str', 'new String("")', 'this.str = str;', 'this.str', 'this.str.toString()', 'get length() { return this.str.length }') + g.gen_builtin_prototype({ + typ_name: typ_name + val_name: 'str' + default_value: 'new String("")' + constructor: 'this.str = str.toString(); this.len = this.str.length' + value_of: 'this.str' + to_string: 'this.str' + eq: 'this.str === other.str' + }) } 'map' { - g.gen_builtin_prototype(typ_name, 'val', 'new Map()', 'this.val = val;', 'this.val', 'this.val.toString()', '') + g.gen_builtin_prototype({ + typ_name: typ_name + val_name: 'map' + default_value: 'new Map()' + constructor: 'this.map = map' + value_of: 'this.map' + to_string: 'this.map.toString()' + eq: 'vEq(this, other)' + }) } 'array' { - g.gen_builtin_prototype(typ_name, 'val', 'new Array()', 'this.val = val;', 'this.val', 'this.val.toString()', '') + g.gen_builtin_prototype({ + typ_name: typ_name + val_name: 'arr' + default_value: 'new Array()' + constructor: 'this.arr = arr' + value_of: 'this.arr' + to_string: 'JSON.stringify(this.arr.map(it => it.valueOf()))' + eq: 'vEq(this, other)' + }) } else {} } diff --git a/vlib/v/gen/js/fast_deep_equal.js b/vlib/v/gen/js/fast_deep_equal.js new file mode 100644 index 0000000000..4a0296cb9a --- /dev/null +++ b/vlib/v/gen/js/fast_deep_equal.js @@ -0,0 +1,66 @@ +// https://www.npmjs.com/package/fast-deep-equal - 3/3/2021 +const envHasBigInt64Array = typeof BigInt64Array !== 'undefined'; +function vEq(a, b) { + if (a === b) return true; + + if (a && b && typeof a == 'object' && typeof b == 'object') { + if (a.constructor !== b.constructor) return false; + + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (!vEq(a[i], b[i])) return false; + return true; + } + + + if ((a instanceof Map) && (b instanceof Map)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + for (i of a.entries()) + if (!vEq(i[1], b.get(i[0]))) return false; + return true; + } + + if ((a instanceof Set) && (b instanceof Set)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + return true; + } + + if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (a[i] !== b[i]) return false; + return true; + } + + + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + + for (i = length; i-- !== 0;) + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + + for (i = length; i-- !== 0;) { + var key = keys[i]; + + if (!vEq(a[key], b[key])) return false; + } + + return true; + } + + // true if both NaN, false otherwise + return a!==a && b!==b; +}; \ No newline at end of file diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index b21d1af046..ad4b474076 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -19,6 +19,8 @@ const ( // used to generate type structs v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', 'int_literal', 'float_literal', 'size_t', 'bool', 'string', 'map', 'array'] + shallow_equatables = [table.Kind.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .int_literal, + .float_literal, .size_t, .bool, .string] tabs = ['', '\t', '\t\t', '\t\t\t', '\t\t\t\t', '\t\t\t\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t'] ) @@ -58,6 +60,8 @@ mut: method_fn_decls map[string][]ast.FnDecl builtin_fns []string // Functions defined in `builtin` empty_line bool + cast_stack []table.Type + call_stack []ast.CallExpr } pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string { @@ -109,6 +113,13 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string deps_resolved := graph.resolve() nodes := deps_resolved.nodes mut out := g.hashes() + g.definitions.str() + // equality check for js objects + // TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') + //unsafe { + // mut eq_fn := $embed_file('fast_deep_equal.js') + // out += eq_fn.data().vstring() + //} + out += fast_deep_eq_fn for node in nodes { name := g.js_name(node.name).replace('.', '_') if g.enable_doc { @@ -478,7 +489,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.write('${styp}.$node.val') } ast.FloatLiteral { - g.write('${g.typ(table.Type(table.f32_type))}($node.val)') + g.gen_float_literal_expr(node) } ast.GoExpr { // TODO @@ -499,7 +510,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_infix_expr(node) } ast.IntegerLiteral { - g.write('${g.typ(table.Type(table.int_type))}($node.val)') + g.gen_integer_literal_expr(node) } ast.LockExpr { g.gen_lock_expr(node) @@ -555,11 +566,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_string_inter_literal(node) } ast.StringLiteral { - text := node.val.replace("'", "\\'") - if g.file.mod.name == 'builtin' { - g.write('new ') - } - g.write("string('$text')") + g.gen_string_literal(node) } ast.StructInit { // TODO: once generic fns/unwrap_generic is implemented @@ -681,7 +688,18 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) { g.write(')') } else { g.write(' $op ') + // TODO: Multiple types?? + should_cast := g.table.type_kind(stmt.left_types.first()) in shallow_equatables + if should_cast { + g.cast_stack << stmt.left_types.first() + if g.file.mod.name == 'builtin' { g.write('new ') } + g.write('${g.typ(stmt.left_types.first())}(') + } g.expr(val) + if should_cast { + g.write(')') + g.cast_stack.delete_last() + } } if g.inside_loop { g.write('; ') @@ -826,12 +844,11 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { args = args[1..] } g.fn_args(args, it.is_variadic) - g.writeln(') {') if it.is_method { - g.inc_indent() - g.writeln('const ${it.params[0].name} = this;') - g.dec_indent() + if args.len > 0 { g.write(', ') } + g.write('${it.params[0].name} = this') } + g.writeln(') {') g.stmts(it.stmts) g.write('}') if is_main { @@ -904,12 +921,19 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { g.inside_loop = true g.write('for (let $i = 0; $i < ') g.expr(it.cond) - g.writeln('.length; ++$i) {') + g.writeln('.len; ++$i) {') g.inside_loop = false if val !in ['', '_'] { g.write('\tconst $val = ') + if it.kind == .string { + if g.file.mod.name == 'builtin' { g.write('new ') } + g.write('byte(') + } g.expr(it.cond) - g.writeln('[$i];') + g.write(if it.kind == .array { '.arr' } else if it.kind == .string { '.str' } else { '.val' }) + g.write('[$i]') + if it.kind == .string { g.write(')') } + g.writeln(';') } g.stmts(it.stmts) g.writeln('}') @@ -1113,6 +1137,7 @@ fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) { } fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { + g.call_stack << it mut name := g.js_name(it.name) call_return_is_optional := it.return_type.has_flag(.optional) if call_return_is_optional { @@ -1206,6 +1231,7 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { g.dec_indent() g.write('})()') } + g.call_stack.delete_last() } fn (mut g JsGen) gen_ident(node ast.Ident) { @@ -1331,7 +1357,9 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { // TODO Does this cover all cases? g.expr(expr.left) g.write('[') + g.cast_stack << table.int_type_idx g.expr(expr.index) + g.cast_stack.delete_last() g.write(']') } } @@ -1339,19 +1367,34 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { l_sym := g.table.get_type_symbol(it.left_type) r_sym := g.table.get_type_symbol(it.right_type) - if l_sym.kind == .array && it.op == .left_shift { // arr << 1 + + is_not := it.op in [.not_in, .not_is, .ne] + if is_not { g.write('!(') } + + if it.op == .eq || it.op == .ne { + // Shallow equatables + if l_sym.kind in shallow_equatables && r_sym.kind in shallow_equatables { + g.expr(it.left) + g.write('.eq(') + g.expr(it.right) + g.write(')') + } else { + g.write('vEq(') + g.expr(it.left) + g.write(', ') + g.expr(it.right) + g.write(')') + } + } else if l_sym.kind == .array && it.op == .left_shift { // arr << 1 g.expr(it.left) g.write('.push(') + // arr << [1, 2] if r_sym.kind == .array { g.write('...') } - // arr << [1, 2] g.expr(it.right) g.write(')') } else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] { - if it.op == .not_in { - g.write('!(') - } g.expr(it.right) g.write(if r_sym.kind == .map { '.has(' @@ -1361,51 +1404,35 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { '.includes(' }) g.expr(it.left) - if l_sym.kind == .string { - g.write('.str') - } + if l_sym.kind == .string { g.write('.str') } g.write(')') - if it.op == .not_in { - g.write(')') - } } else if it.op in [.key_is, .not_is] { // foo is Foo - if it.op == .not_is { - g.write('!(') - } g.expr(it.left) g.write(' instanceof ') g.write(g.typ(it.right_type)) - if it.op == .not_is { - g.write(')') - } } else { - both_are_int := int(it.left_type) in table.integer_type_idxs - && int(it.right_type) in table.integer_type_idxs is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod] - if is_arithmetic { - g.write('${g.typ(g.greater_typ(it.left_type, it.right_type))}(') - } - if it.op == .div && both_are_int { - g.write('((') + needs_cast := it.left_type != it.right_type + + if is_arithmetic && needs_cast { + greater_typ := g.greater_typ(it.left_type, it.right_type) + if g.ns.name == 'builtin' { + g.write('new ') + } + g.write('${g.typ(greater_typ)}(') + g.cast_stack << greater_typ } g.expr(it.left) - // in js == is non-strict & === is strict, always do strict - if it.op == .eq { - g.write(' === ') - } else if it.op == .ne { - g.write(' !== ') - } else { - g.write(' $it.op ') - } + g.write(' ${it.op} ') g.expr(it.right) - // Int division: 2.5 -> 2 by appending |0 - if it.op == .div && both_are_int { - g.write(')|0)') - } - if is_arithmetic { + + if is_arithmetic && needs_cast { + g.cast_stack.delete_last() g.write(')') } } + + if is_not { g.write(')') } } fn (mut g JsGen) greater_typ(left table.Type, right table.Type) table.Type { @@ -1489,10 +1516,14 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { } fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { - if g.file.mod.name == 'builtin' { - g.write('new ') + should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == table.string_type_idx) + if should_cast { + if g.file.mod.name == 'builtin' { + g.write('new ') + } + g.write('string(') } - g.write('string(`') + g.write('`') for i, val in it.vals { escaped_val := val.replace('`', '\\`') g.write(escaped_val) @@ -1516,7 +1547,25 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { } g.write('}') } - g.write('`)') + g.write('`') + if should_cast { + g.write(')') + } +} + +fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { + text := it.val.replace("'", "\\'") + should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == table.string_type_idx) + if should_cast { + if g.file.mod.name == 'builtin' { + g.write('new ') + } + g.write("string(") + } + g.write("'$text'") + if should_cast { + g.write(')') + } } fn (mut g JsGen) gen_struct_init(it ast.StructInit) { @@ -1571,6 +1620,13 @@ fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) { fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { is_literal := ((it.expr is ast.IntegerLiteral && it.typ in table.integer_type_idxs) || (it.expr is ast.FloatLiteral && it.typ in table.float_type_idxs)) + // Skip cast if type is the same as the parrent caster + if g.cast_stack.len > 0 && is_literal { + if it.typ == g.cast_stack[g.cast_stack.len - 1] { + return + } + } + g.cast_stack << it.typ typ := g.typ(it.typ) if !is_literal { if typ !in js.v_types || g.ns.name == 'builtin' { @@ -1585,4 +1641,80 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { if !is_literal { g.write(')') } + g.cast_stack.delete_last() +} + +fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) { + typ := table.Type(table.int_type) + + // Don't wrap integers for use in JS.foo functions. + // TODO: call.language always seems to be "v", parser bug? + if g.call_stack.len > 0 { + call := g.call_stack[g.call_stack.len - 1] + //if call.language == .js { + for t in call.args { + if t.expr is ast.IntegerLiteral { + if t.expr == it { + g.write(it.val) + return + } + } + } + //} + } + + // Skip cast if type is the same as the parrent caster + if g.cast_stack.len > 0 { + if g.cast_stack[g.cast_stack.len - 1] in table.integer_type_idxs { + g.write('$it.val') + return + } + } + + if g.ns.name == 'builtin' { + g.write('new ') + } + + g.write('${g.typ(typ)}($it.val)') +} + +fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) { + typ := table.Type(table.f32_type) + + // Don't wrap integers for use in JS.foo functions. + // TODO: call.language always seems to be "v", parser bug? + if g.call_stack.len > 0 { + call := g.call_stack[g.call_stack.len - 1] + //if call.language == .js { + for i, t in call.args { + if t.expr is ast.FloatLiteral { + if t.expr == it { + if call.expected_arg_types[i] in table.integer_type_idxs { + g.write(int(it.val.f64()).str()) + } else { + g.write(it.val) + } + return + } + } + } + //} + } + + // Skip cast if type is the same as the parrent caster + if g.cast_stack.len > 0 { + if g.cast_stack[g.cast_stack.len - 1] in table.float_type_idxs { + g.write('$it.val') + return + } else if g.cast_stack[g.cast_stack.len - 1] in table.integer_type_idxs { + g.write(int(it.val.f64()).str()) + return + } + } + + if g.ns.name == 'builtin' { + g.write('new ') + } + + g.write('${g.typ(typ)}($it.val)') } diff --git a/vlib/v/gen/js/temp_fast_deep_equal.v b/vlib/v/gen/js/temp_fast_deep_equal.v new file mode 100644 index 0000000000..fcd1d089a6 --- /dev/null +++ b/vlib/v/gen/js/temp_fast_deep_equal.v @@ -0,0 +1,72 @@ +module js + +// TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') +const ( + fast_deep_eq_fn = "// https://www.npmjs.com/package/fast-deep-equal - 3/3/2021 +const envHasBigInt64Array = typeof BigInt64Array !== 'undefined'; +function vEq(a, b) { + if (a === b) return true; + + if (a && b && typeof a == 'object' && typeof b == 'object') { + if (a.constructor !== b.constructor) return false; + + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (!vEq(a[i], b[i])) return false; + return true; + } + + + if ((a instanceof Map) && (b instanceof Map)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + for (i of a.entries()) + if (!vEq(i[1], b.get(i[0]))) return false; + return true; + } + + if ((a instanceof Set) && (b instanceof Set)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + return true; + } + + if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (a[i] !== b[i]) return false; + return true; + } + + + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + + for (i = length; i-- !== 0;) + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + + for (i = length; i-- !== 0;) { + var key = keys[i]; + + if (!vEq(a[key], b[key])) return false; + } + + return true; + } + + // true if both NaN, false otherwise + return a!==a && b!==b; +}; +" +) \ No newline at end of file