diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index 1e27bb53e7..6f53ef257d 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -44,7 +44,7 @@ pub fn (err IError) str() string { return match err { None__ { 'none' } Error { err.msg } - else { '$err.type_name(): $err.msg' } + else { 'Error: $err.msg' } } } diff --git a/vlib/v/gen/js/auto_eq_methods.v b/vlib/v/gen/js/auto_eq_methods.v index ce28359b9e..2f7a59668c 100644 --- a/vlib/v/gen/js/auto_eq_methods.v +++ b/vlib/v/gen/js/auto_eq_methods.v @@ -1 +1,320 @@ module js + +import v.ast +import strings + +fn (mut g JsGen) gen_sumtype_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.sumtype_fn_definitions { + return ptr_styp + } + g.sumtype_fn_definitions << ptr_styp + info := left.sym.sumtype_info() + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('function ${ptr_styp}_sumtype_eq(a,b) {') + fn_builder.writeln('\tlet aProto = Object.getPrototypeOf(a);') + fn_builder.writeln('\tlet bProto = Object.getPrototypeOf(b);') + fn_builder.writeln('\tif (aProto !== bProto) { return new booL(false); }') + for typ in info.variants { + variant := g.unwrap(typ) + fn_builder.writeln('\tif (aProto == ${g.js_name(variant.sym.name)}) {') + if variant.sym.kind == .string { + fn_builder.writeln('\t\treturn new bool(a.str == b.str);') + } else if variant.sym.kind == .sum_type && !typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_sumtype_eq(a,b);') + } else if variant.sym.kind == .struct_ && !typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_struct_eq(a,b);') + } else if variant.sym.kind == .array && !typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(a,b);') + } else if variant.sym.kind == .array_fixed && !typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(a,b);') + } else if variant.sym.kind == .map && !typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_map_eq(a,b);') + } else if variant.sym.kind == .alias && !typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_alias_eq(a,b);') + } else if variant.sym.kind == .function { + fn_builder.writeln('\t\treturn new bool(a == b);') + } else { + fn_builder.writeln('\t\treturn new bool(vEq(a,b));') + } + fn_builder.writeln('\t}') + } + fn_builder.writeln('\treturn new bool(false);') + fn_builder.writeln('}') + g.definitions.writeln(fn_builder.str()) + return ptr_styp +} + +fn (mut g JsGen) gen_struct_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + fn_name := ptr_styp.replace('struct ', '') + if fn_name in g.struct_fn_definitions { + return fn_name + } + g.struct_fn_definitions << fn_name + info := left.sym.struct_info() + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function ${fn_name}_struct_eq(a,b) {') + + // overloaded + if left.sym.has_method('==') { + fn_builder.writeln('\treturn ${fn_name}__eq(a, b);') + fn_builder.writeln('}') + return fn_name + } + + fn_builder.write_string('\treturn new bool(') + if info.fields.len > 0 { + for i, field in info.fields { + if i > 0 { + fn_builder.write_string('\n\t\t&& ') + } + field_type := g.unwrap(field.typ) + field_name := g.js_name(field.name) + if field_type.sym.kind == .string { + fn_builder.write_string('a.${field_name}.str == b.${field_name}.str') + } else if field_type.sym.kind == .sum_type && !field.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_sumtype_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .struct_ && !field.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_struct_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .array && !field.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .array_fixed && !field.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .map && !field.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_map_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .alias && !field.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_alias_eq(a.$field_name, b.$field_name)') + } else if field_type.sym.kind == .function { + fn_builder.write_string('a.$field_name == b.$field_name') + } else { + // fallback to vEq for JS types or primitives. + fn_builder.write_string('vEq(a.$field_name,b.$field_name)') + } + } + } else { + fn_builder.write_string('true') + } + fn_builder.writeln(');') + fn_builder.writeln('}') + return fn_name +} + +fn (mut g JsGen) gen_alias_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.alias_fn_definitions { + return ptr_styp + } + g.alias_fn_definitions << ptr_styp + info := left.sym.info as ast.Alias + + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function ${ptr_styp}_alias_eq(a,b) {') + sym := g.table.get_type_symbol(info.parent_type) + if sym.kind == .string { + fn_builder.writeln('\treturn new bool(a.str == b.str);') + } else if sym.kind == .sum_type && !left.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_sumtype_eq(a, b);') + } else if sym.kind == .struct_ && !left.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_struct_eq(a, b);') + } else if sym.kind == .array && !left.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .array_fixed && !left.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .map && !left.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_map_eq(a, b);') + } else if sym.kind == .function { + fn_builder.writeln('\treturn new bool(a == b);') + } else { + fn_builder.writeln('\treturn new bool(vEq(a,b));') + } + fn_builder.writeln('}') + + return ptr_styp +} + +fn (mut g JsGen) gen_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem := g.unwrap(left.sym.array_info().elem_type) + + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function ${ptr_styp}_arr_eq(a,b) {') + fn_builder.writeln('\tif (a.arr.length != b.arr.length) {') + fn_builder.writeln('\t\treturn new bool(false);') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (let i = 0; i < a.len; ++i) {') + // compare every pair of elements of the two arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (a.arr[i].str != b.arr[i].str) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(a.arr[i],b.arr[i]).val) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (a.arr[i] != b.arr[i]) {') + } else { + fn_builder.writeln('\t\tif (!vEq(a.arr[i],b.arr[i])) {') + } + fn_builder.writeln('\t\t\treturn new bool(false);') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn new bool(true);') + fn_builder.writeln('}') + + return ptr_styp +} + +fn (mut g JsGen) gen_fixed_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem_info := left.sym.array_fixed_info() + elem := g.unwrap(elem_info.elem_type) + size := elem_info.size + + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function ${ptr_styp}_arr_eq(a,b) {') + fn_builder.writeln('\tfor (let i = 0; i < $size; ++i) {') + // compare every pair of elements of the two fixed arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (a.arr[i].str != b.arr[i].str) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(a.arr[i], b.arr[i])) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (a.arr[i] != b.arr[i]) {') + } else { + fn_builder.writeln('\t\tif (!vEq(a.arr[i],b.arr[i])) {') + } + fn_builder.writeln('\t\t\treturn new bool(false);') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn new bool(true);') + fn_builder.writeln('}') + + return ptr_styp +} + +fn (mut g JsGen) gen_map_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.map_fn_definitions { + return ptr_styp + } + g.map_fn_definitions << ptr_styp + value := g.unwrap(left.sym.map_info().value_type) + + mut fn_builder := strings.new_builder(512) + defer { + g.definitions.writeln(fn_builder.str()) + } + fn_builder.writeln('function ${ptr_styp}_map_eq(a,b) {') + fn_builder.writeln('\tif (a.map.size() != b.map.size()) {') + fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (let [key,value] of a.map) {') + fn_builder.writeln('\t\tif (!b.map.has(key)) { return new bool(false); }') + fn_builder.writeln('\t\tlet x = value; let y = b.map.get(key);') + kind := g.table.type_kind(value.typ) + if kind == .string { + fn_builder.writeln('\t\tif (x.str != y.str) {') + } else if kind == .sum_type { + eq_fn := g.gen_sumtype_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(x,y).val) {') + } else if kind == .struct_ { + eq_fn := g.gen_struct_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(x,y).val) {') + } else if kind == .array { + eq_fn := g.gen_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(x,y).val) {') + } else if kind == .array_fixed { + eq_fn := g.gen_fixed_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(x,y).val) {') + } else if kind == .map { + eq_fn := g.gen_map_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(x,y).val) {') + } else if kind == .alias { + eq_fn := g.gen_alias_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(x,y).val) {') + } else if kind == .function { + fn_builder.writeln('\t\tif (x !== y) {') + } else { + fn_builder.writeln('\t\tif (!vEq(x,y)) {') + } + fn_builder.writeln('\t\t\treturn new bool(false);') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn new bool(true);') + fn_builder.writeln('}') + + return ptr_styp +} diff --git a/vlib/v/gen/js/infix.v b/vlib/v/gen/js/infix.v index a0252d520e..d65157d347 100644 --- a/vlib/v/gen/js/infix.v +++ b/vlib/v/gen/js/infix.v @@ -91,17 +91,88 @@ fn (mut g JsGen) infix_expr_eq_op(node ast.InfixExpr) { 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('!') + match left.sym.kind { + .alias { + ptr_typ := g.gen_alias_equality_fn(left.typ) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_alias_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(')') + } + .array { + ptr_typ := g.gen_array_equality_fn(left.unaliased.clear_flag(.shared_f)) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_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(')') + } + .array_fixed { + ptr_typ := g.gen_fixed_array_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_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(')') + } + .map { + ptr_typ := g.gen_map_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_map_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(')') + } + .struct_ { + ptr_typ := g.gen_struct_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_struct_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(')') + } + .sum_type { + ptr_typ := g.gen_sumtype_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_sumtype_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 {} } - 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) diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 7a5e07c384..1256e5b726 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -51,36 +51,43 @@ mut: struct JsGen { pref &pref.Preferences mut: - table &ast.Table - definitions strings.Builder - ns &Namespace - namespaces map[string]&Namespace - doc &JsDoc - enable_doc bool - file &ast.File - tmp_count int - inside_ternary bool - inside_loop bool - inside_map_set bool // map.set(key, value) - inside_builtin bool - inside_if_optional bool - generated_builtin bool - inside_def_typ_decl bool - is_test bool - stmt_start_pos int - defer_stmts []ast.DeferStmt - fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 - str_types []string // types that need automatic str() generation - method_fn_decls map[string][]ast.FnDecl - builtin_fns []string // Functions defined in `builtin` - empty_line bool - cast_stack []ast.Type - call_stack []ast.CallExpr - is_vlines_enabled bool // is it safe to generate #line directives when -g is passed - 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) + table &ast.Table + definitions strings.Builder + ns &Namespace + namespaces map[string]&Namespace + doc &JsDoc + enable_doc bool + file &ast.File + tmp_count int + inside_ternary bool + inside_loop bool + inside_map_set bool // map.set(key, value) + inside_builtin bool + inside_if_optional bool + generated_builtin bool + inside_def_typ_decl bool + is_test bool + stmt_start_pos int + defer_stmts []ast.DeferStmt + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + str_types []string // types that need automatic str() generation + array_fn_definitions []string // array equality functions that have been defined + map_fn_definitions []string // map equality functions that have been defined + struct_fn_definitions []string // struct equality functions that have been defined + sumtype_fn_definitions []string // sumtype equality functions that have been defined + alias_fn_definitions []string // alias equality functions that have been defined + auto_fn_definitions []string // auto generated functions defination list + anon_fn_definitions []string // anon generated functions defination list + method_fn_decls map[string][]ast.FnDecl + builtin_fns []string // Functions defined in `builtin` + empty_line bool + cast_stack []ast.Type + call_stack []ast.CallExpr + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + 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() { diff --git a/vlib/v/gen/js/tests/testdata/array.out b/vlib/v/gen/js/tests/testdata/array.out index 73999a0528..d6f79a755a 100644 --- a/vlib/v/gen/js/tests/testdata/array.out +++ b/vlib/v/gen/js/tests/testdata/array.out @@ -196,34 +196,34 @@ true [1, 2, 3] [1, 2, 3] [[1, 2, 3], [4, 5, 6]] +false true true true true +false true +false true +false true +false true +false true +false true +false true +false true +false true +false true +false true -true -true -true -true -true -true -true -true -true -true -true -true -true +false [1, 3, 5, hi] [-3, 7, 42, 67, 108] 0