module js import v.ast import v.util import strings pub const builtin_functions = ['print', 'println', 'eprint', 'eprintln', 'isnil', 'panic', 'exit'] fn (mut g JsGen) js_mname(name_ string) string { mut is_js := false is_overload := ['+', '-', '*', '/', '==', '<', '>'] mut name := name_ if name.starts_with('JS.') { name = name[3..] is_js = true } ns := get_ns(name) name = if name in is_overload { match name { '+' { '\$add' } '-' { '\$sub' } '/' { '\$div' } '*' { '\$mul' } '%' { '\$mod' } '==' { 'eq' } '>' { '\$gt' } '<' { '\$lt' } else { '' } } } else if unsafe { g.ns == 0 } { name } else if ns == g.ns.name { name.split('.').last() } else { g.get_alias(name) } mut parts := name.split('.') if !is_js { for i, p in parts { if p in js_reserved { parts[i] = 'v_${p}' } } } return parts.join('.') } fn (mut g JsGen) js_call(node ast.CallExpr) { g.call_stack << node it := node call_return_is_option := it.return_type.has_flag(.option) is_await := node.name == 'JS.await' if call_return_is_option { if is_await { g.writeln('await (async function () {') } else { g.writeln('(function () {') } g.writeln('try {') g.writeln('let tmp = ') } if it.is_ctor_new { g.write('new ') } if is_await { g.write('await (') g.expr(it.args[0].expr) g.write(').promise') } else { g.write('${g.js_mname(it.name)}(') for i, arg in it.args { g.expr(arg.expr) if i != it.args.len - 1 { g.write(', ') } } // end call g.write(')') } if call_return_is_option { g.write(';\n') prev_inside_or := g.inside_or g.inside_or = true defer { g.inside_or = prev_inside_or } g.writeln('if (tmp === null) throw "none";') g.writeln('return tmp;') 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_option { panicstr := '`option not set (\${err + ""})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic(${panicstr})') } else { g.writeln('throw new Option({ state: new u8(2), err: error(new string(${panicstr})) });') } } else {} } // end catch g.dec_indent() g.writeln('}') g.writeln('})()') } g.call_stack.delete_last() } fn (mut g JsGen) js_method_call(node ast.CallExpr) { g.call_stack << node it := node call_return_is_option := it.return_type.has_flag(.option) if call_return_is_option { g.writeln('(function () {') g.writeln('try {') g.writeln('let tmp = ') } if it.is_ctor_new { g.write('new ') } g.expr(it.left) g.write('.${g.js_mname(it.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_option { prev_inside_or := g.inside_or g.inside_or = true defer { g.inside_or = prev_inside_or } g.write(';\n') g.writeln('if (tmp === null) throw "none";') g.writeln('return tmp;') 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_option { panicstr := '`option not set (\${err + ""})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic(${panicstr})') } else { g.writeln('throw new option({ state: new u8(2), err: error(new string(${panicstr})) });') } } else {} } // end catch g.dec_indent() g.writeln('}') g.writeln('})()') } g.call_stack.delete_last() } fn (mut g JsGen) method_call(node ast.CallExpr) { g.call_stack << node it := node if it.name == 'str' { g.gen_expr_to_string(node.left, node.left_type) return } is_async := node.name == 'wait' && g.table.sym(node.receiver_type).name.starts_with('Promise<') call_return_is_option := it.return_type.has_flag(.option) if call_return_is_option { if is_async { g.writeln('(async function (){') } else { g.writeln('(function(){') } g.inc_indent() g.writeln('try {') g.inc_indent() g.write('return unwrap(') } if node.name == 'str' { mut rec_type := node.receiver_type if rec_type.has_flag(.shared_f) { rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0) } g.get_str_fn(rec_type) } mut unwrapped_rec_type := node.receiver_type if unsafe { g.fn_decl != 0 } && g.fn_decl.generic_names.len > 0 { // in generic fn unwrapped_rec_type = g.unwrap_generic(node.receiver_type) } else { // in non-generic fn sym := g.table.sym(node.receiver_type) match sym.info { ast.Struct, ast.Interface, ast.SumType { generic_names := sym.info.generic_types.map(g.table.sym(it).name) // see comment at top of vlib/v/gen/c/utils.v mut muttable := unsafe { &ast.Table(g.table) } if utyp := muttable.convert_generic_type(node.receiver_type, generic_names, sym.info.concrete_types) { unwrapped_rec_type = utyp } } else {} } } mut typ_sym := g.table.sym(unwrapped_rec_type) rec_cc_type := g.cc_type(unwrapped_rec_type, false) mut receiver_type_name := util.no_dots(rec_cc_type) // alias type that undefined this method (not include `str`) need to use parent type if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) { unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type typ_sym = g.table.sym(unwrapped_rec_type) } if typ_sym.kind == .interface && (typ_sym.info as ast.Interface).defines_method(node.name) { g.expr(it.left) g.gen_deref_ptr(it.left_type) g.write('.${it.name}(') for i, arg in it.args { g.expr(arg.expr) if i != it.args.len - 1 { g.write(', ') } } g.write(')') return } left_sym := g.table.sym(node.left_type) final_left_sym := g.table.final_sym(node.left_type) if final_left_sym.kind == .map && it.name in special_map_methods { g.gen_map_method_call(it) return } else if final_left_sym.kind == .array { if it.name in ['map', 'filter'] { g.expr(it.left) mut ltyp := it.left_type for ltyp.is_ptr() { g.write('.valueOf()') ltyp = ltyp.deref() } g.write('.') // Prevent 'it' from getting shadowed inside the match 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 && expr.info is ast.IdentVar { v_sym := g.table.sym(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 } if it.name in special_array_methods { g.gen_array_method_call(it) return } if node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop_left', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { // `array_Xyz_clone` => `array_clone` receiver_type_name = 'array' } } } if is_async { g.write('await ') g.expr(it.left) g.write('.promise') } else { mut name := util.no_dots('${receiver_type_name}_${node.name}') name = g.generic_fn_name(node.concrete_types, name) g.write('${name}(') g.expr_with_expected_type(it.left, unwrapped_rec_type) g.gen_deref_ptr(it.left_type) g.write(',') for i, arg in it.args { expected_arg_type := if i < it.expected_arg_types.len { it.expected_arg_types[i] } else { arg.typ } g.expr_with_expected_type(arg.expr, expected_arg_type) if i != it.args.len - 1 { g.write(', ') } } g.write(')') } if call_return_is_option { // end unwrap g.writeln(')') g.dec_indent() prev_inside_or := g.inside_or g.inside_or = true defer { g.inside_or = prev_inside_or } // 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_option { panicstr := '`option not set (\${err.valueOf().msg})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic(${panicstr})') } else { g.writeln('js_throw(err)') } } else {} } // end catch g.dec_indent() g.writeln('}') // end anon fn g.dec_indent() g.write('})()') } g.call_stack.delete_last() } fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { if it.should_be_skipped { return } if it.is_method && (it.is_field || g.table.sym(it.receiver_type).name.starts_with('JS.')) { g.js_method_call(it) return } else if it.name.starts_with('JS.') { g.js_call(it) return } if it.is_method { g.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'] if name in builtin_functions { name = 'builtin__${name}' } print_method := name ret_sym := g.table.sym(it.return_type) if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { g.write('new ') g.write(ret_sym.name) g.write('(') } call_return_is_option := it.return_type.has_flag(.option) if call_return_is_option { g.writeln('(function(){') g.inc_indent() g.writeln('try {') g.inc_indent() g.write('return unwrap(') } if is_print { mut typ := node.args[0].typ expr := node.args[0].expr g.write('${print_method} (') g.gen_expr_to_string(expr, typ) g.write(')') return } name = g.generic_fn_name(node.concrete_types, name) g.expr(it.left) g.write('${name}(') for i, arg in it.args { expected_arg_type := if i < it.expected_arg_types.len { it.expected_arg_types[i] } else { arg.typ } g.expr_with_expected_type(arg.expr, expected_arg_type) if i != it.args.len - 1 { g.write(', ') } } // end method call g.write(')') if call_return_is_option { // end unwrap prev_inside_or := g.inside_or g.inside_or = true defer { g.inside_or = prev_inside_or } 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_option { panicstr := '`option not set (\${err.valueOf().msg})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic(${panicstr})') } else { g.writeln('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() } 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.sym(it.params[0].typ).kind == .alias { return .alias_method } else if it.is_method && g.table.sym(it.params[0].typ).kind == .interface { return .iface_method } else if it.is_method || it.no_body { return .struct_method } else { return .function } } fn (mut g JsGen) is_used_by_main(node ast.FnDecl) bool { mut is_used_by_main := true if g.pref.skip_unused { fkey := node.fkey() is_used_by_main = g.table.used_features.used_fns[fkey] $if trace_skip_unused_fns ? { println('> is_used_by_main: ${is_used_by_main} | node.name: ${node.name} | fkey: ${fkey} | node.is_method: ${node.is_method}') } if !is_used_by_main { $if trace_skip_unused_fns_in_js_code ? { g.writeln('// trace_skip_unused_fns_in_js_code, ${node.name}, fkey: ${fkey}') } } } else { $if trace_skip_unused_fns_in_js_code ? { g.writeln('// trace_skip_unused_fns_in_js_code, ${node.name}, fkey: ${node.fkey()}') } } return is_used_by_main } fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { res := g.fn_gen_type(it) if it.language == .js && it.no_body { for attr in it.attrs { match attr.name { 'wasm_import' { mut x := g.wasm_export[attr.arg] or { []string{} } x << it.name g.wasm_import[attr.arg] = x } else {} } } return } if g.inside_builtin { g.builtin_fns << it.name } if !g.is_used_by_main(it) { return } if it.should_be_skipped { return } cur_fn_decl := g.fn_decl g.fn_decl = unsafe { &it } g.gen_method_decl(it, res) g.fn_decl = cur_fn_decl } fn fn_has_go(node ast.FnDecl) bool { mut has_go := false for stmt in node.stmts { if stmt is ast.ExprStmt { if stmt.expr is ast.GoExpr { has_go = true break } } } return has_go } fn (mut g JsGen) generic_fn_name(types []ast.Type, before string) string { if types.len == 0 { return before } mut name := before + '_T' for typ in types { name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.styp(typ.set_nr_muls(0)) } return name } fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { node := it if node.generic_names.len > 0 && g.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion // loop thru each generic type and generate a function for concrete_types in g.table.fn_generic_types[node.fkey()] { if g.pref.is_verbose { syms := concrete_types.map(g.table.sym(it)) the_type := syms.map(it.name).join(', ') println('gen fn `${node.name}` for type `${the_type}`') } g.cur_concrete_types = concrete_types g.gen_method_decl(node, typ) } g.cur_concrete_types = [] return } cur_fn_decl := g.fn_decl unsafe { g.fn_decl = &it } cur_fn_save := g.table.cur_fn defer { g.table.cur_fn = cur_fn_save } unsafe { g.table.cur_fn = &it } mut name := it.name if name in ['+', '-', '*', '**', '/', '%', '<', '==', '[]', '[]='] { name = util.replace_op(name) } if node.is_method { name = g.cc_type(node.receiver.typ, false) + '_' + name } name = g.js_name(name) name = g.generic_fn_name(g.cur_concrete_types, name) if name in builtin_functions { name = 'builtin__${name}' } if it.is_pub && !it.is_method { g.push_pub_var(name) } if it.language == .js && it.is_method { g.writeln('${g.styp(it.receiver.typ)}.prototype.${it.name} = ') } mut has_go := fn_has_go(it) || it.has_await for attr in it.attrs { if attr.name == 'async' { if g.pref.output_es5 { verror('cannot use @[async] attribute when outputting ES5 source code') } has_go = true break } } is_main := it.name == 'main.main' g.gen_attrs(it.attrs) if is_main { // there is no concept of main in JS but we do have iife g.writeln('/* program entry point */') if !g.pref.output_es5 { // main function is always async g.write('async ') } g.write('function js_main(') } else if it.is_anon { g.write('function (') } else { c := name[0] if c in [`+`, `-`, `*`, `/`] { name = util.replace_op(name) } // type_name := g.styp(it.return_type) // generate jsdoc for the function g.doc.gen_fn(it) if has_go && !g.pref.output_es5 { g.write('async ') } g.write('function ') g.write('${name}(') if it.is_pub && !it.is_method { g.push_pub_var(name) } } args := it.params g.fn_args(args, it.is_variadic) g.writeln(') {') for i, arg in args { is_varg := i == args.len - 1 && it.is_variadic arg_name := g.js_name(arg.name) if is_varg { g.writeln('${arg_name} = new array(new array_buffer({arr: ${arg_name},len: new int(${arg_name}.length),index_start: new int(0)}));') } else { asym := g.table.sym(arg.typ) if asym.kind != .interface && asym.language != .js { if arg.typ.is_ptr() || arg.is_mut { g.writeln('${arg_name} = new \$ref(${arg_name})') } } } } g.inc_indent() g.writeln('try {') g.inc_indent() g.stmts(it.stmts) g.dec_indent() g.writeln('} catch (e) { ') g.writeln('\tif (e instanceof ReturnException) { return e.val; } ') g.writeln('\tthrow e;') g.writeln('}') g.dec_indent() g.writeln('}') if is_main { // g.write(')') } g.writeln('') for attr in it.attrs { match attr.name { 'export' { g.writeln('globalThis.${attr.arg} = ${g.js_name(it.name)};') } 'wasm_export' { mut x := g.wasm_export[attr.arg] or { []string{} } g.write('function \$wasm${g.js_name(it.name)}(') g.fn_args(args, it.is_variadic) g.writeln(') {') g.write('\treturn ${name} (') for i, arg in args { is_varg := i == args.len - 1 && it.is_variadic arg_name := g.js_name(arg.name) if is_varg { g.write('...${arg_name}') } else { g.gen_cast_tmp(arg_name, arg.typ) } if i != args.len - 1 { g.write(',') } } g.writeln(').valueOf();') g.writeln('}') x << it.name g.wasm_export[attr.arg] = x } 'wasm_import' { mut x := g.wasm_export[attr.arg] or { []string{} } x << name g.wasm_import[attr.arg] = x } else {} } } g.fn_decl = cur_fn_decl } fn (mut g JsGen) fn_args(args []ast.Param, is_variadic bool) { for i, arg in args { name := g.js_name(arg.name) is_varg := i == args.len - 1 && is_variadic if is_varg { g.write('...${name}') } else { g.write(name) } // if its not the last argument if i < args.len - 1 { g.write(', ') } } } fn (mut g JsGen) gen_anon_fn(mut fun ast.AnonFn) { mut fn_name := fun.decl.name if fun.decl.generic_names.len > 0 { fn_name = g.generic_fn_name(g.cur_concrete_types, fn_name) } if fun.has_gen[fn_name] { return } fun.has_gen[fn_name] = true it := fun.decl cur_fn_decl := g.fn_decl unsafe { g.fn_decl = &it } cur_fn_save := g.table.cur_fn defer { g.table.cur_fn = cur_fn_save } unsafe { g.table.cur_fn = &it } mut name := it.name if name in ['+', '-', '*', '**', '/', '%', '<', '==', '[]', '[]='] { name = util.replace_op(name) } g.writeln('(function () { ') mut inherited2copy := map[string]string{} for inherited in fun.inherited_vars { if !inherited.is_mut { copy := g.copy_val(inherited.typ, inherited.name) inherited2copy[inherited.name] = copy } } name = g.js_name(name) name = g.generic_fn_name(g.table.cur_concrete_types, name) if name in builtin_functions { name = 'builtin__${name}' } if it.is_pub && !it.is_method { g.push_pub_var(name) } g.gen_attrs(it.attrs) g.write('return function (') args := it.params g.fn_args(args, it.is_variadic) g.writeln(') {') g.inc_indent() for i, arg in args { is_varg := i == args.len - 1 && it.is_variadic arg_name := g.js_name(arg.name) if is_varg { g.writeln('${arg_name} = new array(new array_buffer({arr: ${arg_name},len: new int(${arg_name}.length),index_start: new int(0)}));') } else { asym := g.table.sym(arg.typ) if arg.typ.is_ptr() || (arg.is_mut && asym.kind != .interface && asym.language != .js) { g.writeln('${arg_name} = new \$ref(${arg_name})') } } } for inherited in fun.inherited_vars { if !inherited.is_mut { g.writeln('let ${inherited.name} = ${inherited2copy[inherited.name]};') } } g.stmts(it.stmts) g.dec_indent() g.writeln('}})()') g.fn_decl = cur_fn_decl }