From 0121c8b4fd46e289cfd2221d6d281b6dc41325a6 Mon Sep 17 00:00:00 2001 From: playX Date: Wed, 18 Aug 2021 11:33:37 +0300 Subject: [PATCH] v.gen.js: fix method calls and other codegen parts, rand module compiles (#11205) --- vlib/builtin/js/array.js.v | 20 +- vlib/builtin/js/builtin.js.v | 32 +++ vlib/builtin/js/builtin.v | 28 --- vlib/rand/config/config.v | 13 ++ vlib/rand/rand.v | 13 +- vlib/time/parse.js.v | 24 +++ vlib/v/gen/js/builtin_types.v | 4 +- vlib/v/gen/js/js.v | 275 +++++++++++++++++++------ vlib/v/gen/js/tests/testdata/array.out | 22 +- 9 files changed, 301 insertions(+), 130 deletions(-) create mode 100644 vlib/rand/config/config.v create mode 100644 vlib/time/parse.js.v diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 46a3c9893b..7fc4084540 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -84,11 +84,11 @@ fn (a &array) set_len(i int) { } pub fn (mut a array) sort_with_compare(compare voidptr) { - #a.arr.sort(compare) + #a.val.arr.sort(compare) } pub fn (mut a array) sort() { - #a.arr.sort($sortComparator) + #a.val.arr.sort($sortComparator) } pub fn (a array) index(v string) int { @@ -110,16 +110,16 @@ pub fn (a array) slice(start int, end int) array { } pub fn (mut a array) insert(i int, val voidptr) { - #a.arr.splice(i,0,val) + #a.val.arr.splice(i,0,val) } pub fn (mut a array) insert_many(i int, val voidptr, size int) { - #a.arr.splice(i,0,...val.slice(0,+size)) + #a.val.arr.splice(i,0,...val.slice(0,+size)) } pub fn (mut a array) join(separator string) string { mut res := '' - #res = new builtin.string(a.arr.join(separator +'')); + #res = new builtin.string(a.val.arr.join(separator +'')); return res } @@ -164,7 +164,7 @@ pub fn (mut a array) delete(i int) { // delete_many deletes `size` elements beginning with index `i` pub fn (mut a array) delete_many(i int, size int) { - #a.arr.splice(i.valueOf(),size.valueOf()) + #a.val.arr.splice(i.valueOf(),size.valueOf()) } // prepend prepends one value to the array. @@ -186,7 +186,7 @@ pub fn (a array) reverse() array { } pub fn (mut a array) reverse_in_place() { - #a.arr.reverse() + #a.val.arr.reverse() } #array.prototype.$includes = function (elem) { return this.arr.find(function(e) { return vEq(elem,e); }) !== undefined;} @@ -195,7 +195,7 @@ pub fn (mut a array) reverse_in_place() { // resulting in a single output value. pub fn (a array) reduce(iter fn (int, int) int, accum_start int) int { mut accum_ := accum_start - #for (let i of a) { + #for (let i = 0;i < a.arr.length;i++) { #accum_ = iter(accum_, a.arr[i]) #} @@ -204,7 +204,7 @@ pub fn (a array) reduce(iter fn (int, int) int, accum_start int) int { pub fn (mut a array) pop() voidptr { mut res := voidptr(0) - #res = a.arr.pop() + #res = a.val.arr.pop() return res } @@ -237,5 +237,5 @@ pub fn (a array) contains(key voidptr) bool { // delete_last effectively removes last element of an array. pub fn (mut a array) delete_last() { - #a.arr.pop(); + #a.val.arr.pop(); } diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index d76346601e..4290f4b989 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -4,3 +4,35 @@ module builtin pub fn js_throw(s any) { #throw (s instanceof Error ? s : new Error(s)) } + +pub fn println(s any) { + $if js_freestanding { + #print(s.toString()) + } $else { + #console.log(s.toString()) + } +} + +pub fn print(s any) { + $if js_node { + #$process.stdout.write(s.toString()) + } $else { + panic('Cannot `print` in a browser, use `println` instead') + } +} + +pub fn eprintln(s any) { + $if js_freestanding { + #print(s.toString()) + } $else { + #console.error(s.toString()) + } +} + +pub fn eprint(s any) { + $if js_node { + #$process.stderr.write(s.toString()) + } $else { + panic('Cannot `eprint` in a browser, use `println` instead') + } +} diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index 86b60ad6e8..ce9093af98 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -6,34 +6,6 @@ module builtin fn (a any) toString() -pub fn println(s any) { - // Quickfix to properly print basic types - // TODO: Add proper detection code for this - JS.console.log(s.toString()) -} - -pub fn print(s any) { - // TODO - // $if js.node { - JS.process.stdout.write(s.toString()) - // } $else { - // panic('Cannot `print` in a browser, use `println` instead') - // } -} - -pub fn eprintln(s any) { - JS.console.error(s.toString()) -} - -pub fn eprint(s any) { - // TODO - // $if js.node { - JS.process.stderr.write(s.toString()) - // } $else { - // panic('Cannot `eprint` in a browser, use `eprintln` instead') - // } -} - // Exits the process in node, and halts execution in the browser // because `process.exit` is undefined. Workaround for not having // a 'real' way to exit in the browser. diff --git a/vlib/rand/config/config.v b/vlib/rand/config/config.v new file mode 100644 index 0000000000..b11e77cd6b --- /dev/null +++ b/vlib/rand/config/config.v @@ -0,0 +1,13 @@ +module config + +import rand.seed + +// PRNGConfigStruct is a configuration struct for creating a new instance of the default RNG. +// Note that the RNGs may have a different number of u32s required for seeding. The default +// generator WyRand used 64 bits, ie. 2 u32s so that is the default. In case your desired generator +// uses a different number of u32s, use the `seed.time_seed_array()` method with the correct +// number of u32s. +pub struct PRNGConfigStruct { +pub: + seed_ []u32 = seed.time_seed_array(2) +} diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index b88203b5dd..d790dd5824 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -3,18 +3,9 @@ // that can be found in the LICENSE file. module rand -import rand.seed +import rand.config import rand.wyrand -// PRNGConfigStruct is a configuration struct for creating a new instance of the default RNG. -// Note that the RNGs may have a different number of u32s required for seeding. The default -// generator WyRand used 64 bits, ie. 2 u32s so that is the default. In case your desired generator -// uses a different number of u32s, use the `seed.time_seed_array()` method with the correct -// number of u32s. -pub struct PRNGConfigStruct { - seed_ []u32 = seed.time_seed_array(2) -} - // PRNG is a common interface for all PRNGs that can be used seamlessly with the rand // modules's API. It defines all the methods that a PRNG (in the vlib or custom made) must // implement in order to ensure that _all_ functions can be used with the generator. @@ -52,7 +43,7 @@ fn init() { } // new_default returns a new instance of the default RNG. If the seed is not provided, the current time will be used to seed the instance. -pub fn new_default(config PRNGConfigStruct) &PRNG { +pub fn new_default(config config.PRNGConfigStruct) &PRNG { mut rng := &wyrand.WyRandRNG{} rng.seed(config.seed_) return rng diff --git a/vlib/time/parse.js.v b/vlib/time/parse.js.v new file mode 100644 index 0000000000..1303561a9b --- /dev/null +++ b/vlib/time/parse.js.v @@ -0,0 +1,24 @@ +module time + +// parse returns time from a date string. +// +// TODO(playX): JS Date expects iso8061 format of strings and other formats +// are implementation dependant, we probably want to implement parsing in JS. +pub fn parse(s string) Time { + mut res := Time{} + #let date = new Date(s.str) + #res.year.val = date.getFullYear() + #res.month.val = date.getMonth() + #res.day.val = date.getDay() + #res.hour.val = date.getHours() + #res.minute.val = date.getMinutes() + #res.second.val = date.getSeconds() + #res.microsecond.val = date.getMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +pub fn parse_iso8601(s string) ?Time { + return parse(s) +} diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 4234253d72..e9ce2fadaa 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -304,8 +304,8 @@ fn (mut g JsGen) gen_builtin_type_defs() { g.gen_builtin_prototype( typ_name: typ_name default_value: 'new Number(0)' - constructor: 'this.val = val | 0' - value_of: 'this.val | 0' + constructor: 'this.val = Number(val)' + value_of: 'Number(this.val)' to_string: 'this.valueOf().toString()' eq: 'this.valueOf() === other.valueOf()' to_jsval: '+this' diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 8e1e388324..f727a15792 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -308,7 +308,7 @@ pub fn (mut g JsGen) init() { g.definitions.writeln('"use strict";') g.definitions.writeln('') g.definitions.writeln('var \$global = (new Function("return this"))();') - g.definitions.writeln('function \$ref(value) { this.val = value; } ') + g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ') g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ') if g.pref.backend != .js_node { g.definitions.writeln('const \$process = {') @@ -736,17 +736,16 @@ fn (mut g JsGen) expr(node ast.Expr) { } ast.PrefixExpr { if node.op in [.amp, .mul] { - // C pointers/references: ignore them if node.op == .amp { - if !node.right_type.is_pointer() { - // kind of weird way to handle references but it allows us to access type methods easily. - g.write('(function(x) {') - g.write(' return { val: x, __proto__: Object.getPrototypeOf(x), valueOf: function() { return this.val; } }})( ') - g.expr(node.right) - g.write(')') - } else { - g.expr(node.right) - } + // if !node.right_type.is_pointer() { + // kind of weird way to handle references but it allows us to access type methods easily. + g.write('(function(x) {') + g.write(' return { val: x, __proto__: Object.getPrototypeOf(x), valueOf: function() { return this.val; } }})( ') + g.expr(node.right) + g.write(')') + //} else { + // g.expr(node.right) + // } } else { g.write('(') g.expr(node.right) @@ -1150,14 +1149,22 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { if args.len > 0 { g.write(', ') } - g.write('${it.params[0].name} = this') + if it.params[0].is_mut { + g.write('${it.params[0].name} = new \$ref(this)') + } else { + g.write('${it.params[0].name} = this') + } } g.writeln(') {') for i, arg in args { is_varg := i == args.len - 1 && it.is_variadic + name := g.js_name(arg.name) if is_varg { - name := g.js_name(arg.name) g.writeln('$name = new array($name);') + } else { + if arg.typ.is_ptr() || arg.is_mut { + g.writeln('$name = new \$ref($name)') + } } } @@ -1514,7 +1521,141 @@ fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) { g.write(']') } +fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { + 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 { + g.writeln('(function(){') + g.inc_indent() + g.writeln('try {') + g.inc_indent() + g.write('return builtin.unwrap(') + } + sym := g.table.get_type_symbol(it.receiver_type) + if sym.kind == .array { + if sym.kind == .array && it.name in ['map', 'filter'] { + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.') + // Prevent 'it' from getting shadowed inside the match + node := it + g.write(it.name) + g.write('(') + expr := node.args[0].expr + match expr { + ast.AnonFn { + g.gen_fn_decl(expr.decl) + g.write(')') + return true + } + ast.Ident { + if expr.kind == .function { + g.write(g.js_name(expr.name)) + g.write(')') + return true + } else if expr.kind == .variable { + v_sym := g.table.get_type_symbol(expr.var_info().typ) + if v_sym.kind == .function { + g.write(g.js_name(expr.name)) + g.write(')') + return true + } + } + } + else {} + } + + g.write('it => ') + g.expr(node.args[0].expr) + g.write(')') + return true + } + + left_sym := g.table.get_type_symbol(it.left_type) + if left_sym.kind == .array { + if it.name in special_array_methods { + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.') + + g.gen_array_method_call(it) + return true + } + } + } + // interfaces require dynamic dispatch. To obtain method table we use getPrototypeOf + g.write('Object.getPrototypeOf(') + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write(').$name .call(') + g.expr(it.left) + g.write(',') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end method call + g.write(')') + + if call_return_is_optional { + // end unwrap + g.writeln(')') + g.dec_indent() + // begin catch block + g.writeln('} catch(err) {') + g.inc_indent() + // gen or block contents + match it.or_block.kind { + .block { + if it.or_block.stmts.len > 1 { + g.stmts(it.or_block.stmts[..it.or_block.stmts.len - 1]) + } + g.write('return ') + g.stmt(it.or_block.stmts.last()) + } + .propagate { + panicstr := '`optional not set (\${err})`' + if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { + g.writeln('return builtin.panic($panicstr)') + } else { + g.writeln('builtin.js_throw(err)') + } + } + else {} + } + // end catch + g.dec_indent() + g.writeln('}') + // end anon fn + g.dec_indent() + g.write('})()') + } + g.call_stack.delete_last() + return true +} + fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { + if it.is_method { + if g.gen_method_call(it) { + return + } + } g.call_stack << it mut name := g.js_name(it.name) call_return_is_optional := it.return_type.has_flag(.optional) @@ -1525,59 +1666,13 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { g.inc_indent() g.write('return builtin.unwrap(') } + g.expr(it.left) - if it.is_method { // foo.bar.baz() - sym := g.table.get_type_symbol(it.receiver_type) - g.write('.') - if sym.kind == .array && it.name in ['map', 'filter'] { - // Prevent 'it' from getting shadowed inside the match - node := it - g.write(it.name) - g.write('(') - expr := node.args[0].expr - match expr { - ast.AnonFn { - g.gen_fn_decl(expr.decl) - g.write(')') - return - } - ast.Ident { - if expr.kind == .function { - g.write(g.js_name(expr.name)) - g.write(')') - return - } else if expr.kind == .variable { - v_sym := g.table.get_type_symbol(expr.var_info().typ) - if v_sym.kind == .function { - g.write(g.js_name(expr.name)) - g.write(')') - return - } - } - } - else {} - } - - g.write('it => ') - g.expr(node.args[0].expr) - g.write(')') - return - } - - left_sym := g.table.get_type_symbol(it.left_type) - if left_sym.kind == .array { - node := it - if node.name in special_array_methods { - g.gen_array_method_call(it) - return - } - } - } else { - if name in g.builtin_fns { - g.write('builtin.') - } + if name in g.builtin_fns { + g.write('builtin.') } + g.write('${name}(') for i, arg in it.args { g.expr(arg.expr) @@ -1782,6 +1877,14 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { } } +fn (mut g JsGen) gen_deref_ptr(ty ast.Type) { + mut t := ty + for t.is_ptr() { + g.write('.val') + t = t.deref() + } +} + 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) @@ -1804,10 +1907,12 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.cast_stack << greater_typ g.write('BigInt((') g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(').\$toJS())') g.write(' $it.op ') g.write('BigInt((') g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write(').\$toJS())') g.cast_stack.delete_last() g.write(')') @@ -1820,30 +1925,41 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { has_operator_overloading := g.table.type_has_method(l_sym, '==') if has_operator_overloading { g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write('.eq(') g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write(')') // Shallow equatables } else if l_sym.kind in js.shallow_equatables && r_sym.kind in js.shallow_equatables { // wrap left expr in parens so binary operations will work correctly. g.write('(') g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(')') g.write('.eq(') g.cast_stack << int(l_sym.kind) g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.cast_stack.delete_last() g.write(')') } else { g.write('vEq(') g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(', ') g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write(')') } } else if l_sym.kind == .array && it.op == .left_shift { // arr << 1 g.write('Array.prototype.push.call(') g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } g.write('.arr,') array_info := l_sym.info as ast.Array // arr << [1, 2] @@ -1854,6 +1970,12 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.write(')') } else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] { g.expr(it.right) + + mut ltyp := it.right_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } if r_sym.kind == .map { g.write('.map.has(') } else if r_sym.kind == .string { @@ -1868,6 +1990,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.write(')') } else if it.op in [.key_is, .not_is] { // foo is Foo g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(' instanceof ') g.write(g.typ(it.right_type)) } else if it.op in [.lt, .gt, .ge, .le] && g.table.type_has_method(l_sym, '<') @@ -1877,19 +2000,24 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { } if it.op in [.lt, .ge] { g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write('.\$lt (') g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write(')') } else { g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write('.\$lt (') g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(')') } } else { has_operator_overloading := g.table.type_has_method(l_sym, it.op.str()) if has_operator_overloading { g.expr(it.left) + g.gen_deref_ptr(it.left_type) name := match it.op.str() { '+' { '\$add' @@ -1913,6 +2041,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { } g.write('.$name (') g.expr(it.right) + g.gen_deref_ptr(it.right_type) g.write(')') } else { mut greater_typ := 0 @@ -1933,10 +2062,11 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { } g.expr(it.left) - + g.gen_deref_ptr(it.left_type) g.write(' $it.op ') g.expr(it.right) + g.gen_deref_ptr(it.right_type) if is_arithmetic { g.cast_stack.delete_last() @@ -2033,8 +2163,10 @@ fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) { fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { g.expr(it.expr) - if it.expr_type.is_ptr() { - g.write('.valueOf()') + mut ltyp := it.expr_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() } g.write('.$it.field_name') } @@ -2078,7 +2210,8 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { } fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { - text := it.val.replace("'", "\\'") + mut text := it.val.replace("'", "'") + text = text.replace('"', '\\"') should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) if true || should_cast { if g.file.mod.name == 'builtin' { @@ -2086,7 +2219,7 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { } g.write('string(') } - g.write("'$text'") + g.write("\"$text\"") if true || should_cast { g.write(')') } @@ -2166,6 +2299,9 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { g.cast_stack << it.typ typ := g.typ(it.typ) if !is_literal { + if it.typ.is_ptr() { + g.write('new \$ref(') + } if typ !in js.v_types || g.ns.name == 'builtin' { g.write('new ') } @@ -2177,6 +2313,9 @@ fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { } if !is_literal { g.write(')') + if it.typ.is_ptr() { + g.write(')') + } } g.cast_stack.delete_last() } diff --git a/vlib/v/gen/js/tests/testdata/array.out b/vlib/v/gen/js/tests/testdata/array.out index 4b7cad7a95..a7d0c13016 100644 --- a/vlib/v/gen/js/tests/testdata/array.out +++ b/vlib/v/gen/js/tests/testdata/array.out @@ -60,16 +60,16 @@ true 0 1 1.1 -[1, 2, 3, 4] -[1, 5, 6, 2, 3, 4] +[[1, 2], 3, 4] +[[1, 2], [5, 6], 3, 4] 0 1 1 0 1 1.1 -[1, 2, 3, 4] -[5, 6, 1, 2, 3, 4] +[[1, 2], 3, 4] +[[5, 6], [1, 2], 3, 4] 5 true 1.1 @@ -152,11 +152,11 @@ true true true true -0 -0 -0 -0 -0 +15 +20 +14 +-6 +-7 [2, 4, 6] [is, awesome] [2, 3, 4, 6, 8, 9, 10] @@ -278,9 +278,9 @@ a 123 123 [[1, 2, 3]] +[[[1, 2, 3]]] [[1, 2, 3]] -[[1, 2, 3]] -[[1, 2, 3]] +[[[1, 2, 3]]] true true true