v/vlib/v/gen/js/fn.v

842 lines
19 KiB
V

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_optional := it.return_type.has_flag(.optional)
is_await := node.name == 'JS.await'
if call_return_is_optional {
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_optional {
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 := '`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('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_optional := it.return_type.has_flag(.optional)
if call_return_is_optional {
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_optional {
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 := '`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('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_optional := it.return_type.has_flag(.optional)
if call_return_is_optional {
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.resolve_generic_to_concrete(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 == .array {
if final_left_sym.kind == .array && 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 {
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 final_left_sym.kind == .array {
if it.name in special_array_methods {
g.gen_array_method_call(it)
return
}
}
}
if final_left_sym.kind == .array
&& node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', '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, false)
g.write('${name}(')
g.expr(it.left)
g.gen_deref_ptr(it.left_type)
g.write(',')
for i, arg in it.args {
g.expr(arg.expr)
if i != it.args.len - 1 {
g.write(', ')
}
}
g.write(')')
}
if call_return_is_optional {
// 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 := '`optional 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 && 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 js.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_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 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, false)
g.expr(it.left)
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
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 := '`optional 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_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, is_decl bool) string {
if types.len == 0 {
return before
}
mut name := before + '_T'
for typ in types {
name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.typ(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, true)
if name in js.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.typ(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 outputing 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.typ(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)
}
}
mut 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) {
if fun.has_gen {
return
}
fun.has_gen = 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, true)
if name in js.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 (')
mut 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
}