js: equality method generation for V types (#11503)

pull/11518/head
playX 2021-09-16 07:07:48 +03:00 committed by GitHub
parent d00808660f
commit 7d1776b84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 451 additions and 54 deletions

View File

@ -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' }
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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() {

View File

@ -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