jsgen: object equality checks, optimise casting and start builtin implementation (#9068)
parent
5e0e44eb69
commit
65e888230a
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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)')
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
"
|
||||
)
|
Loading…
Reference in New Issue