diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index ac0f2321a1..495e656db1 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -136,7 +136,7 @@ pub fn (a array) str() string { } #array.prototype[Symbol.iterator] = function () { return this.arr[Symbol.iterator](); } -#array.prototype.entries = function () { return this.arr.entries(); } +#array.prototype.entries = function () { let result = []; for (const [key,val] of this.arr.entries()) { result.push([new int(key), val]); } return result[Symbol.iterator](); } #array.prototype.map = function(callback) { return new builtin.array(this.arr.map(callback)); } #array.prototype.filter = function(callback) { return new array(this.arr.filter( function (it) { return (+callback(it)) != 0; } )); } #Object.defineProperty(array.prototype,'cap',{ get: function () { return this.len; } }) diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index 80654160ad..f64da0070c 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -2,36 +2,36 @@ module builtin // used to generate JS throw statements. pub fn js_throw(s any) { - #throw (s instanceof Error ? s : new Error(s)) + #throw s } -pub fn println(s any) { +pub fn println(s string) { $if js_freestanding { - #print(s.toString()) + #print(s.str) } $else { - #console.log(s.toString()) + #console.log(s.str) } } -pub fn print(s any) { +pub fn print(s string) { $if js_node { - #$process.stdout.write(s.toString()) + #$process.stdout.write(s.str) } $else { panic('Cannot `print` in a browser, use `println` instead') } } -pub fn eprintln(s any) { +pub fn eprintln(s string) { $if js_freestanding { - #print(s.toString()) + #print(s.str) } $else { - #console.error(s.toString()) + #console.error(s.str) } } -pub fn eprint(s any) { +pub fn eprint(s string) { $if js_node { - #$process.stderr.write(s.toString()) + #$process.stderr.write(s.str) } $else { panic('Cannot `eprint` in a browser, use `println` instead') } @@ -50,3 +50,12 @@ fn opt_ok(data voidptr, option Option) { #option.err = none__ #option.data = data } + +pub fn unwrap(opt string) string { + mut o := Option{} + #o = opt + if o.state != 0 { + js_throw(o.err) + } + return opt +} diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index 71e42c65f1..7c82d1ad98 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -6,14 +6,6 @@ module builtin fn (a any) toString() -pub fn unwrap(opt any) any { - o := &Option(opt) - if o.state != 0 { - js_throw(o.err) - } - return opt -} - pub fn panic(s string) { eprintln('V panic: $s') exit(1) @@ -32,13 +24,6 @@ pub: code int } -pub const none__ = IError(&None__{}) - -pub struct Option { - state byte - err IError = none__ -} - struct None__ { msg string code int @@ -48,6 +33,13 @@ fn (_ None__) str() string { return 'none' } +pub const none__ = IError(None__{'', 0}) + +pub struct Option { + state byte + err IError = none__ +} + pub fn (err IError) str() string { return match err { None__ { 'none' } diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v new file mode 100644 index 0000000000..6bde0a13dd --- /dev/null +++ b/vlib/v/gen/js/fn.v @@ -0,0 +1,241 @@ +module js + +import v.ast + +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 + } + } + } + + mut ltyp := it.left_type + mut lsym := g.table.get_type_symbol(ltyp) + if lsym.kind == .interface_ { + g.write(g.js_name(lsym.name)) + 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(')') + } else { + g.write('Object.getPrototypeOf(') + g.expr(it.left) + + 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 + } + } + node := it + g.call_stack << it + mut name := g.js_name(it.name) + is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic'] + print_method := name + ret_sym := g.table.get_type_symbol(it.return_type) + if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write(ret_sym.name) + g.write('(') + } + 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(') + } + if is_print { + mut typ := node.args[0].typ + + expr := node.args[0].expr + g.write('builtin.$print_method (') + g.gen_expr_to_string(expr, typ) + g.write(')') + return + } + g.expr(it.left) + + if name in g.builtin_fns { + g.write('builtin.') + } + + g.write('${name}(') + 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('})()') + } + if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { + g.write(')') + } + g.call_stack.delete_last() +} diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 90a02b26a3..d35feed7c9 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -406,6 +406,12 @@ pub fn (mut g JsGen) init() { g.definitions.writeln('const \$process = process;') } g.definitions.writeln('function alias(value) { return value; } ') + + g.definitions.writeln('function \$v_fmt(value) { let res = ""; + if (Object.getPrototypeOf(s).hasOwnProperty("str") && typeof s.str == "function") res = s.str().str + else res = s.toString() + return res + } ') } pub fn (g JsGen) hashes() string { @@ -1323,11 +1329,14 @@ enum FnGenType { function struct_method alias_method + iface_method } fn (g &JsGen) fn_gen_type(it &ast.FnDecl) FnGenType { if it.is_method && g.table.get_type_symbol(it.params[0].typ).kind == .alias { return .alias_method + } else if it.is_method && g.table.get_type_symbol(it.params[0].typ).kind == .interface_ { + return .iface_method } else if it.is_method || it.no_body { return .struct_method } else { @@ -1366,7 +1375,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { unsafe { g.fn_decl = &it } - if typ == .alias_method { + if typ == .alias_method || typ == .iface_method { sym := g.table.get_final_type_symbol(it.params[0].typ.set_nr_muls(0)) name := g.js_name(sym.name) if name in js.v_types { @@ -1443,13 +1452,17 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { } g.stmts(it.stmts) - g.write('}') + g.writeln('}') + if is_main { g.write(')();') + } else if typ != .struct_method { + // g.write(';') } - if typ == .struct_method || typ == .alias_method { + if typ == .struct_method || typ == .alias_method || typ == .iface_method { g.writeln('\n') } + g.fn_decl = voidptr(0) } @@ -1620,7 +1633,7 @@ fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) { // This is a hack to make the interface's type accessible outside its namespace // TODO: interfaces are always `pub`? name := g.js_name(it.name) - g.push_pub_var('/** @type $name */\n\t\t$name: undefined') + g.push_pub_var('/** @type $name */\n\t\t$name') g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }') } @@ -1828,217 +1841,6 @@ 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) - ret_sym := g.table.get_type_symbol(it.return_type) - if it.language == .js && ret_sym.name in js.v_types && ret_sym.name != 'void' { - g.write('new ') - if g.ns.name != 'builtin' { - g.write('builtin.') - } - g.write(ret_sym.name) - g.write('(') - } - 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(') - } - - g.expr(it.left) - - if name in g.builtin_fns { - g.write('builtin.') - } - - g.write('${name}(') - 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('})()') - } - if it.language == .js && ret_sym.name in js.v_types && ret_sym.name != 'void' { - g.write(')') - } - g.call_stack.delete_last() -} - fn (mut g JsGen) gen_ident(node ast.Ident) { mut name := g.js_name(node.name) if node.kind == .blank_ident || name in ['', '_'] { @@ -2337,8 +2139,17 @@ fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var M if sym.kind == .sum_type { g.write(' instanceof ') g.expr(branch.exprs[sumtype_index]) - } else { - panic('TODO: Generate match for interfaces') + } else if sym.kind == .interface_ { + if branch.exprs[sumtype_index] is ast.TypeNode { + g.write(' instanceof ') + g.expr(branch.exprs[sumtype_index]) + } else { + g.write(' instanceof ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write('None__') + } } if is_expr && tmp_var.len == 0 { g.write(') ? ') diff --git a/vlib/v/gen/js/str.v b/vlib/v/gen/js/str.v new file mode 100644 index 0000000000..ba963b17e8 --- /dev/null +++ b/vlib/v/gen/js/str.v @@ -0,0 +1,85 @@ +module js + +import v.ast + +fn (mut g JsGen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + mut typ := etype + if etype.has_flag(.shared_f) { + typ = typ.clear_flag(.shared_f).set_nr_muls(0) + } + + mut sym := g.table.get_type_symbol(typ) + + if mut sym.info is ast.Alias { + parent_sym := g.table.get_type_symbol(sym.info.parent_type) + if parent_sym.has_method('str') { + typ = sym.info.parent_type + sym = parent_sym + } + } + + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + if typ.has_flag(.variadic) { + // todo(playX): generate str method just like in the C backend + g.write('new string(') + g.expr(expr) + g.write('.valueOf()') + g.write('.toString())') + } else if typ == ast.string_type { + g.expr(expr) + } else if typ == ast.bool_type { + g.write('new string(') + g.expr(expr) + g.write('.valueOf() ? "true" : "false")') + } else if sym.kind == .none_ { + g.write('new string("")') + } else if sym.kind == .enum_ { + g.write('new string(') + if expr !is ast.EnumVal { + g.expr(expr) + g.write('.valueOf()') + g.write('.toString()') + } else { + g.write('"') + g.expr(expr) + g.write('"') + } + g.write(')') + } else if sym.kind == .interface_ && sym_has_str_method { + is_ptr := typ.is_ptr() + g.write(sym.mod.replace_once('${g.ns.name}.', '')) + g.write('.') + g.write(sym.name) + g.write('.prototype.str.call(') + g.expr(expr) + if !str_method_expects_ptr && is_ptr { + g.gen_deref_ptr(typ) + } + g.write(')') + } + //|| sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return,.sum_type, .interface_] + else if sym_has_str_method { + g.write('new string(') + g.write('Object.getPrototypeOf(/*str exists*/') + g.expr(expr) + is_ptr := typ.is_ptr() + g.gen_deref_ptr(typ) + g.write(').str.call(') + g.expr(expr) + if !str_method_expects_ptr && is_ptr { + g.gen_deref_ptr(typ) + } + + g.write('))') + } else if sym.kind == .struct_ && !sym_has_str_method { + g.write('new string(') + g.expr(expr) + g.gen_deref_ptr(typ) + g.write('.toString())') + } else { + g.write('new string(') + g.expr(expr) + g.gen_deref_ptr(typ) + g.write('.valueOf().toString())') + } +}