jsgen: object equality checks, optimise casting and start builtin implementation (#9068)

pull/9112/head
Leah Lundqvist 2021-03-04 14:02:16 +01:00 committed by GitHub
parent 5e0e44eb69
commit 65e888230a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 581 additions and 74 deletions

View File

@ -12,7 +12,9 @@ pub struct JS.String {
length JS.Number length JS.Number
} }
pub struct JS.Boolean {} pub struct JS.Boolean {}
pub struct JS.Array {} pub struct JS.Array {
length JS.Number
}
pub struct JS.Map {} pub struct JS.Map {}
// Type prototype functions // Type prototype functions
@ -22,12 +24,18 @@ fn (v JS.Boolean) toString() JS.String
fn (v JS.Array) toString() JS.String fn (v JS.Array) toString() JS.String
fn (v JS.Map) 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 // Top level functions
fn JS.eval(string) any fn JS.eval(string) any
fn JS.parseInt(string, f64) f64 fn JS.parseInt(string, f64) JS.Number
fn JS.parseFloat(string) f64 fn JS.parseFloat(string) JS.Number
fn JS.isNaN(f64) bool fn JS.isNaN(f64) bool
fn JS.isFinite(f64) bool fn JS.isFinite(f64) bool
fn JS.decodeURI(string) string fn JS.decodeURI(string) string
@ -84,3 +92,18 @@ fn JS.Math.tan(f64) f64
// JSON // JSON
fn JS.JSON.stringify(any) string fn JS.JSON.stringify(any) string
fn JS.JSON.parse(string) any 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

View File

@ -2,8 +2,162 @@ module builtin
pub struct string { pub struct string {
str JS.String str JS.String
len u32
} }
pub fn (s string) slice(a int, b int) string { pub fn (s string) slice(a int, b int) string {
return string(s.str.slice(a, b)) 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))
}

View File

@ -210,23 +210,36 @@ fn (mut g JsGen) struct_typ(s string) string {
return styp + '["prototype"]' 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 // 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) { fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeCongig) {
g.writeln('function ${typ_name}($val_name = ${default_value}) { $constructor }') g.writeln('function ${c.typ_name}(${c.val_name} = ${c.default_value}) { ${c.constructor} }')
g.writeln('${typ_name}.prototype = {') g.writeln('${c.typ_name}.prototype = {')
g.inc_indent() g.inc_indent()
g.writeln('val: $default_value,') g.writeln('val: ${c.default_value},')
if extras.len > 0 { if c.extras.len > 0 {
g.writeln('$extras,') 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.inside_def_typ_decl = true
g.gen_method_decl(method) g.gen_method_decl(method)
g.inside_def_typ_decl = false g.inside_def_typ_decl = false
g.writeln(',') g.writeln(',')
} }
g.writeln('valueOf() { return $value_of },') g.writeln('valueOf() { return ${c.value_of} },')
g.writeln('toString() { return $to_string }') 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.dec_indent()
g.writeln('};\n') g.writeln('};\n')
} }
@ -237,24 +250,71 @@ fn (mut g JsGen) gen_builtin_type_defs() {
for typ_name in v_types { for typ_name in v_types {
// TODO: JsDoc // TODO: JsDoc
match typ_name { 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 // 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' { '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' { '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' { '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' { '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' { '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 {} else {}
} }

View File

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

View File

@ -19,6 +19,8 @@ const (
// used to generate type structs // used to generate type structs
v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', 'int_literal', v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', 'int_literal',
'float_literal', 'size_t', 'bool', 'string', 'map', 'array'] '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', 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'] '\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 method_fn_decls map[string][]ast.FnDecl
builtin_fns []string // Functions defined in `builtin` builtin_fns []string // Functions defined in `builtin`
empty_line bool empty_line bool
cast_stack []table.Type
call_stack []ast.CallExpr
} }
pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string { 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() deps_resolved := graph.resolve()
nodes := deps_resolved.nodes nodes := deps_resolved.nodes
mut out := g.hashes() + g.definitions.str() 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 { for node in nodes {
name := g.js_name(node.name).replace('.', '_') name := g.js_name(node.name).replace('.', '_')
if g.enable_doc { if g.enable_doc {
@ -478,7 +489,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.write('${styp}.$node.val') g.write('${styp}.$node.val')
} }
ast.FloatLiteral { ast.FloatLiteral {
g.write('${g.typ(table.Type(table.f32_type))}($node.val)') g.gen_float_literal_expr(node)
} }
ast.GoExpr { ast.GoExpr {
// TODO // TODO
@ -499,7 +510,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_infix_expr(node) g.gen_infix_expr(node)
} }
ast.IntegerLiteral { ast.IntegerLiteral {
g.write('${g.typ(table.Type(table.int_type))}($node.val)') g.gen_integer_literal_expr(node)
} }
ast.LockExpr { ast.LockExpr {
g.gen_lock_expr(node) g.gen_lock_expr(node)
@ -555,11 +566,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_string_inter_literal(node) g.gen_string_inter_literal(node)
} }
ast.StringLiteral { ast.StringLiteral {
text := node.val.replace("'", "\\'") g.gen_string_literal(node)
if g.file.mod.name == 'builtin' {
g.write('new ')
}
g.write("string('$text')")
} }
ast.StructInit { ast.StructInit {
// TODO: once generic fns/unwrap_generic is implemented // TODO: once generic fns/unwrap_generic is implemented
@ -681,7 +688,18 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
g.write(')') g.write(')')
} else { } else {
g.write(' $op ') 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) g.expr(val)
if should_cast {
g.write(')')
g.cast_stack.delete_last()
}
} }
if g.inside_loop { if g.inside_loop {
g.write('; ') g.write('; ')
@ -826,12 +844,11 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
args = args[1..] args = args[1..]
} }
g.fn_args(args, it.is_variadic) g.fn_args(args, it.is_variadic)
g.writeln(') {')
if it.is_method { if it.is_method {
g.inc_indent() if args.len > 0 { g.write(', ') }
g.writeln('const ${it.params[0].name} = this;') g.write('${it.params[0].name} = this')
g.dec_indent()
} }
g.writeln(') {')
g.stmts(it.stmts) g.stmts(it.stmts)
g.write('}') g.write('}')
if is_main { if is_main {
@ -904,12 +921,19 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) {
g.inside_loop = true g.inside_loop = true
g.write('for (let $i = 0; $i < ') g.write('for (let $i = 0; $i < ')
g.expr(it.cond) g.expr(it.cond)
g.writeln('.length; ++$i) {') g.writeln('.len; ++$i) {')
g.inside_loop = false g.inside_loop = false
if val !in ['', '_'] { if val !in ['', '_'] {
g.write('\tconst $val = ') 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.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.stmts(it.stmts)
g.writeln('}') 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) { fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
g.call_stack << it
mut name := g.js_name(it.name) mut name := g.js_name(it.name)
call_return_is_optional := it.return_type.has_flag(.optional) call_return_is_optional := it.return_type.has_flag(.optional)
if call_return_is_optional { if call_return_is_optional {
@ -1206,6 +1231,7 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
g.dec_indent() g.dec_indent()
g.write('})()') g.write('})()')
} }
g.call_stack.delete_last()
} }
fn (mut g JsGen) gen_ident(node ast.Ident) { 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? // TODO Does this cover all cases?
g.expr(expr.left) g.expr(expr.left)
g.write('[') g.write('[')
g.cast_stack << table.int_type_idx
g.expr(expr.index) g.expr(expr.index)
g.cast_stack.delete_last()
g.write(']') 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) { fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
l_sym := g.table.get_type_symbol(it.left_type) l_sym := g.table.get_type_symbol(it.left_type)
r_sym := g.table.get_type_symbol(it.right_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.expr(it.left)
g.write('.push(') g.write('.push(')
// arr << [1, 2]
if r_sym.kind == .array { if r_sym.kind == .array {
g.write('...') g.write('...')
} }
// arr << [1, 2]
g.expr(it.right) g.expr(it.right)
g.write(')') g.write(')')
} else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] { } 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.expr(it.right)
g.write(if r_sym.kind == .map { g.write(if r_sym.kind == .map {
'.has(' '.has('
@ -1361,51 +1404,35 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
'.includes(' '.includes('
}) })
g.expr(it.left) g.expr(it.left)
if l_sym.kind == .string { if l_sym.kind == .string { g.write('.str') }
g.write('.str')
}
g.write(')') g.write(')')
if it.op == .not_in {
g.write(')')
}
} else if it.op in [.key_is, .not_is] { // foo is Foo } else if it.op in [.key_is, .not_is] { // foo is Foo
if it.op == .not_is {
g.write('!(')
}
g.expr(it.left) g.expr(it.left)
g.write(' instanceof ') g.write(' instanceof ')
g.write(g.typ(it.right_type)) g.write(g.typ(it.right_type))
if it.op == .not_is {
g.write(')')
}
} else { } 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] is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod]
if is_arithmetic { needs_cast := it.left_type != it.right_type
g.write('${g.typ(g.greater_typ(it.left_type, it.right_type))}(')
} if is_arithmetic && needs_cast {
if it.op == .div && both_are_int { greater_typ := g.greater_typ(it.left_type, it.right_type)
g.write('((') if g.ns.name == 'builtin' {
g.write('new ')
}
g.write('${g.typ(greater_typ)}(')
g.cast_stack << greater_typ
} }
g.expr(it.left) g.expr(it.left)
// in js == is non-strict & === is strict, always do strict g.write(' ${it.op} ')
if it.op == .eq {
g.write(' === ')
} else if it.op == .ne {
g.write(' !== ')
} else {
g.write(' $it.op ')
}
g.expr(it.right) g.expr(it.right)
// Int division: 2.5 -> 2 by appending |0
if it.op == .div && both_are_int { if is_arithmetic && needs_cast {
g.write(')|0)') g.cast_stack.delete_last()
}
if is_arithmetic {
g.write(')') g.write(')')
} }
} }
if is_not { g.write(')') }
} }
fn (mut g JsGen) greater_typ(left table.Type, right table.Type) table.Type { 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) { fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) {
if g.file.mod.name == 'builtin' { should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == table.string_type_idx)
g.write('new ') 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 { for i, val in it.vals {
escaped_val := val.replace('`', '\\`') escaped_val := val.replace('`', '\\`')
g.write(escaped_val) 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('`)') 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) { 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) { 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) 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)) || (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) typ := g.typ(it.typ)
if !is_literal { if !is_literal {
if typ !in js.v_types || g.ns.name == 'builtin' { 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 { if !is_literal {
g.write(')') 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)')
} }

View File

@ -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;
};
"
)