js: fix printing, make builtins for result and option types behave correctly (#11336)

pull/11353/head
playX 2021-08-30 20:47:18 +03:00 committed by GitHub
parent f33f216698
commit a9b705bfd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 382 additions and 244 deletions

View File

@ -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; } })

View File

@ -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
}

View File

@ -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' }

241
vlib/v/gen/js/fn.v 100644
View File

@ -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()
}

View File

@ -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(') ? ')

View File

@ -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("<none>")')
} 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())')
}
}