js: add initial support for optional types, IfGuardExpr codegen for `if` (#11332)
parent
985fe85de2
commit
61ac7b671d
|
@ -44,3 +44,9 @@ pub fn exit(c int) {
|
||||||
JS.process.exit(c)
|
JS.process.exit(c)
|
||||||
js_throw('exit($c)')
|
js_throw('exit($c)')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn opt_ok(data voidptr, option Option) {
|
||||||
|
#option.state = 0
|
||||||
|
#option.err = none__
|
||||||
|
#option.data = data
|
||||||
|
}
|
||||||
|
|
|
@ -19,11 +19,6 @@ pub fn panic(s string) {
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Option {
|
|
||||||
state byte
|
|
||||||
err Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// IError holds information about an error instance
|
// IError holds information about an error instance
|
||||||
pub interface IError {
|
pub interface IError {
|
||||||
msg string
|
msg string
|
||||||
|
@ -37,7 +32,12 @@ pub:
|
||||||
code int
|
code int
|
||||||
}
|
}
|
||||||
|
|
||||||
const none__ = IError(&None__{})
|
pub const none__ = IError(&None__{})
|
||||||
|
|
||||||
|
pub struct Option {
|
||||||
|
state byte
|
||||||
|
err IError = none__
|
||||||
|
}
|
||||||
|
|
||||||
struct None__ {
|
struct None__ {
|
||||||
msg string
|
msg string
|
||||||
|
@ -66,7 +66,6 @@ pub fn (o Option) str() string {
|
||||||
return 'Option{ error: "$o.err" }'
|
return 'Option{ error: "$o.err" }'
|
||||||
}
|
}
|
||||||
|
|
||||||
[if trace_error ?]
|
|
||||||
fn trace_error(x string) {
|
fn trace_error(x string) {
|
||||||
eprintln('> ${@FN} | $x')
|
eprintln('> ${@FN} | $x')
|
||||||
}
|
}
|
||||||
|
@ -75,7 +74,7 @@ fn trace_error(x string) {
|
||||||
// Example: `if ouch { return error('an error occurred') }`
|
// Example: `if ouch { return error('an error occurred') }`
|
||||||
[inline]
|
[inline]
|
||||||
pub fn error(message string) IError {
|
pub fn error(message string) IError {
|
||||||
trace_error(message)
|
// trace_error(message)
|
||||||
return &Error{
|
return &Error{
|
||||||
msg: message
|
msg: message
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ mut:
|
||||||
inside_loop bool
|
inside_loop bool
|
||||||
inside_map_set bool // map.set(key, value)
|
inside_map_set bool // map.set(key, value)
|
||||||
inside_builtin bool
|
inside_builtin bool
|
||||||
|
inside_if_optional bool
|
||||||
generated_builtin bool
|
generated_builtin bool
|
||||||
inside_def_typ_decl bool
|
inside_def_typ_decl bool
|
||||||
is_test bool
|
is_test bool
|
||||||
|
@ -891,12 +892,12 @@ fn (mut g JsGen) expr(node ast.Expr) {
|
||||||
ast.MapInit {
|
ast.MapInit {
|
||||||
g.gen_map_init_expr(node)
|
g.gen_map_init_expr(node)
|
||||||
}
|
}
|
||||||
|
ast.None {
|
||||||
|
g.write('builtin.none__')
|
||||||
|
}
|
||||||
ast.MatchExpr {
|
ast.MatchExpr {
|
||||||
g.match_expr(node)
|
g.match_expr(node)
|
||||||
}
|
}
|
||||||
ast.None {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
ast.OrExpr {
|
ast.OrExpr {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
@ -1309,7 +1310,7 @@ fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
|
||||||
|
|
||||||
fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
|
fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
|
||||||
g.expr(it.expr)
|
g.expr(it.expr)
|
||||||
if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary {
|
if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary && !g.inside_if_optional {
|
||||||
g.writeln(';')
|
g.writeln(';')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1623,12 +1624,43 @@ fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) {
|
||||||
g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }')
|
g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut g JsGen) gen_optional_error(expr ast.Expr) {
|
||||||
|
g.write('new builtin.Option({ state: new builtin.byte(2),err: ')
|
||||||
|
g.expr(expr)
|
||||||
|
g.write('})')
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut g JsGen) gen_return_stmt(it ast.Return) {
|
fn (mut g JsGen) gen_return_stmt(it ast.Return) {
|
||||||
if it.exprs.len == 0 {
|
node := it
|
||||||
// Returns nothing
|
// sym := g.table.get_type_symbol(g.fn_decl.return_type)
|
||||||
g.writeln('return;')
|
fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional)
|
||||||
|
if node.exprs.len == 0 {
|
||||||
|
if fn_return_is_optional {
|
||||||
|
g.writeln('return {}')
|
||||||
|
} else {
|
||||||
|
g.writeln('return;')
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fn_return_is_optional {
|
||||||
|
optional_none := node.exprs[0] is ast.None
|
||||||
|
ftyp := g.typ(node.types[0])
|
||||||
|
mut is_regular_option := ftyp == 'Option'
|
||||||
|
if optional_none || is_regular_option || node.types[0] == ast.error_type_idx {
|
||||||
|
if !isnil(g.fn_decl) && g.fn_decl.is_test {
|
||||||
|
test_error_var := g.new_tmp_var()
|
||||||
|
g.writeln('let $test_error_var = "TODO";')
|
||||||
|
g.writeln('return $test_error_var;')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.write('return ')
|
||||||
|
g.gen_optional_error(it.exprs[0])
|
||||||
|
g.writeln(';')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g.write('return ')
|
g.write('return ')
|
||||||
if it.exprs.len == 1 {
|
if it.exprs.len == 1 {
|
||||||
g.expr(it.exprs[0])
|
g.expr(it.exprs[0])
|
||||||
|
@ -2240,11 +2272,29 @@ fn (mut g JsGen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) {
|
||||||
}
|
}
|
||||||
for i, stmt in stmts {
|
for i, stmt in stmts {
|
||||||
if i == stmts.len - 1 && tmp_var != '' {
|
if i == stmts.len - 1 && tmp_var != '' {
|
||||||
g.write('$tmp_var = ')
|
if g.inside_if_optional {
|
||||||
g.stmt(stmt)
|
if stmt is ast.ExprStmt {
|
||||||
g.writeln('')
|
if stmt.typ == ast.error_type_idx || stmt.expr is ast.None {
|
||||||
|
g.writeln('${tmp_var}.state = 2;')
|
||||||
|
g.write('${tmp_var}.err = ')
|
||||||
|
g.expr(stmt.expr)
|
||||||
|
g.writeln(';')
|
||||||
|
} else {
|
||||||
|
g.write('builtin.opt_ok(')
|
||||||
|
g.stmt(stmt)
|
||||||
|
g.writeln(', $tmp_var);')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.write('$tmp_var = ')
|
||||||
|
g.stmt(stmt)
|
||||||
|
g.writeln('')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
g.stmt(stmt)
|
g.stmt(stmt)
|
||||||
|
if g.inside_if_optional && stmt is ast.ExprStmt {
|
||||||
|
g.writeln(';')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if g.inside_ternary && i < stmts.len - 1 {
|
if g.inside_ternary && i < stmts.len - 1 {
|
||||||
g.write(',')
|
g.write(',')
|
||||||
|
@ -2308,16 +2358,58 @@ fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var M
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut g JsGen) need_tmp_var_in_if(node ast.IfExpr) bool {
|
||||||
|
if node.is_expr && g.inside_ternary {
|
||||||
|
if node.typ.has_flag(.optional) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for branch in node.branches {
|
||||||
|
if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if branch.stmts.len == 1 {
|
||||||
|
if branch.stmts[0] is ast.ExprStmt {
|
||||||
|
stmt := branch.stmts[0] as ast.ExprStmt
|
||||||
|
if stmt.expr is ast.CallExpr {
|
||||||
|
if stmt.expr.is_method {
|
||||||
|
left_sym := g.table.get_type_symbol(stmt.expr.receiver_type)
|
||||||
|
if left_sym.kind in [.array, .array_fixed, .map] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
||||||
if node.is_comptime {
|
if node.is_comptime {
|
||||||
g.comp_if(node)
|
g.comp_if(node)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
type_sym := g.table.get_type_symbol(node.typ)
|
// For simpe if expressions we can use C's `?:`
|
||||||
// one line ?:
|
// `if x > 0 { 1 } else { 2 }` => `(x > 0) ? (1) : (2)`
|
||||||
if node.is_expr && node.branches.len >= 2 && node.has_else && type_sym.kind != .void {
|
// For if expressions with multiple statements or another if expression inside, it's much
|
||||||
// `x := if a > b { } else if { } else { }`
|
// easier to use a temp var, than do C tricks with commas, introduce special vars etc
|
||||||
|
// (as it used to be done).
|
||||||
|
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
|
||||||
|
needs_tmp_var := g.need_tmp_var_in_if(node)
|
||||||
|
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
|
||||||
|
|
||||||
|
if needs_tmp_var {
|
||||||
|
if node.typ.has_flag(.optional) {
|
||||||
|
g.inside_if_optional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
g.writeln('let $tmp; /* if prepend */')
|
||||||
|
} else if node.is_expr || g.inside_ternary {
|
||||||
g.write('(')
|
g.write('(')
|
||||||
|
prev := g.inside_ternary
|
||||||
g.inside_ternary = true
|
g.inside_ternary = true
|
||||||
for i, branch in node.branches {
|
for i, branch in node.branches {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -2326,60 +2418,100 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
||||||
if i < node.branches.len - 1 || !node.has_else {
|
if i < node.branches.len - 1 || !node.has_else {
|
||||||
g.write('(')
|
g.write('(')
|
||||||
g.expr(branch.cond)
|
g.expr(branch.cond)
|
||||||
g.write(')')
|
g.write(').valueOf()')
|
||||||
g.write('.valueOf()')
|
|
||||||
g.write(' ? ')
|
g.write(' ? ')
|
||||||
}
|
}
|
||||||
g.stmts(branch.stmts)
|
g.stmts(branch.stmts)
|
||||||
}
|
}
|
||||||
g.inside_ternary = false
|
g.inside_ternary = prev
|
||||||
g.write(')')
|
g.write(')')
|
||||||
} else {
|
return
|
||||||
// mut is_guard = false
|
}
|
||||||
for i, branch in node.branches {
|
|
||||||
if i == 0 {
|
mut is_guard := false
|
||||||
match branch.cond {
|
mut guard_idx := 0
|
||||||
ast.IfGuardExpr {
|
mut guard_vars := []string{}
|
||||||
// TODO optionals
|
|
||||||
|
for i, branch in node.branches {
|
||||||
|
cond := branch.cond
|
||||||
|
if cond is ast.IfGuardExpr {
|
||||||
|
if !is_guard {
|
||||||
|
is_guard = true
|
||||||
|
guard_idx = i
|
||||||
|
guard_vars = []string{len: node.branches.len}
|
||||||
|
}
|
||||||
|
if cond.expr !is ast.IndexExpr && cond.expr !is ast.PrefixExpr {
|
||||||
|
var_name := g.new_tmp_var()
|
||||||
|
guard_vars[i] = var_name
|
||||||
|
g.writeln('let $var_name;')
|
||||||
|
} else {
|
||||||
|
guard_vars[i] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, branch in node.branches {
|
||||||
|
if i > 0 {
|
||||||
|
g.write('} else ')
|
||||||
|
}
|
||||||
|
// if last branch is `else {`
|
||||||
|
if i == node.branches.len - 1 && node.has_else {
|
||||||
|
g.writeln('{')
|
||||||
|
// define `err` only for simple `if val := opt {...} else {`
|
||||||
|
if is_guard && guard_idx == i - 1 {
|
||||||
|
cvar_name := guard_vars[guard_idx]
|
||||||
|
g.writeln('\tlet err = ${cvar_name}.err;')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match branch.cond {
|
||||||
|
ast.IfGuardExpr {
|
||||||
|
mut var_name := guard_vars[i]
|
||||||
|
mut short_opt := false
|
||||||
|
if var_name == '' {
|
||||||
|
short_opt = true // we don't need a further tmp, so use the one we'll get later
|
||||||
|
var_name = g.new_tmp_var()
|
||||||
|
guard_vars[i] = var_name // for `else`
|
||||||
|
g.tmp_count--
|
||||||
|
g.writeln('if (${var_name}.state == 0) {')
|
||||||
|
} else {
|
||||||
|
g.write('if ($var_name = ')
|
||||||
|
g.expr(branch.cond.expr)
|
||||||
|
g.writeln(', ${var_name}.state == 0) {')
|
||||||
}
|
}
|
||||||
else {
|
if short_opt || branch.cond.var_name != '_' {
|
||||||
g.write('if (')
|
if short_opt {
|
||||||
if '$branch.cond' == 'js' {
|
cond_var_name := if branch.cond.var_name == '_' {
|
||||||
g.write('true')
|
'_dummy_${g.tmp_count + 1}'
|
||||||
|
} else {
|
||||||
|
branch.cond.var_name
|
||||||
|
}
|
||||||
|
g.write('\tlet $cond_var_name = ')
|
||||||
|
g.expr(branch.cond.expr)
|
||||||
|
g.writeln(';')
|
||||||
} else {
|
} else {
|
||||||
g.write('(')
|
g.writeln('\tlet $branch.cond.var_name = ${var_name}.data;')
|
||||||
g.expr(branch.cond)
|
|
||||||
g.write(')')
|
|
||||||
g.write('.valueOf()')
|
|
||||||
}
|
}
|
||||||
g.writeln(') {')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if i < node.branches.len - 1 || !node.has_else {
|
else {
|
||||||
g.write('} else if (')
|
g.write('if ((')
|
||||||
g.write('(')
|
g.expr(branch.cond)
|
||||||
g.expr(branch.cond)
|
g.writeln(').valueOf()) {')
|
||||||
g.write(')')
|
}
|
||||||
g.write('.valueOf()')
|
|
||||||
g.writeln(') {')
|
|
||||||
} else if i == node.branches.len - 1 && node.has_else {
|
|
||||||
/*
|
|
||||||
if is_guard {
|
|
||||||
//g.writeln('} if (!$guard_ok) { /* else */')
|
|
||||||
} else {
|
|
||||||
*/
|
|
||||||
g.writeln('} else {')
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if needs_tmp_var {
|
||||||
|
g.stmts_with_tmp_var(branch.stmts, tmp)
|
||||||
|
} else {
|
||||||
g.stmts(branch.stmts)
|
g.stmts(branch.stmts)
|
||||||
}
|
}
|
||||||
/*
|
}
|
||||||
if is_guard {
|
g.writeln('}')
|
||||||
g.write('}')
|
if needs_tmp_var {
|
||||||
}
|
g.write('$tmp')
|
||||||
*/
|
}
|
||||||
g.writeln('}')
|
if node.typ.has_flag(.optional) {
|
||||||
g.writeln('')
|
g.inside_if_optional = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue