From 14b33baa3b42b880d8f4ddfdbdb5f648d654db8d Mon Sep 17 00:00:00 2001 From: Tim Basel Date: Thu, 20 Jan 2022 07:40:16 +0100 Subject: [PATCH] transformer: refactor + apply transform to (hopefully) all nodes (#13216) --- vlib/v/ast/ast.v | 147 ++-- vlib/v/checker/checker.v | 8 +- vlib/v/checker/for.v | 4 +- vlib/v/checker/if.v | 4 +- vlib/v/checker/match.v | 2 +- vlib/v/parser/parse_type.v | 8 +- vlib/v/transformer/index_state.v | 123 +++ vlib/v/transformer/transformer.v | 1272 +++++++++++++++--------------- 8 files changed, 853 insertions(+), 715 deletions(-) create mode 100644 vlib/v/transformer/index_state.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 2e7d874905..cec47ad192 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -151,10 +151,10 @@ pub: // Stand-alone expression in a statement list. pub struct ExprStmt { pub: - expr Expr pos token.Position comments []Comment pub mut: + expr Expr is_expr bool typ Type } @@ -290,11 +290,11 @@ pub struct ConstField { pub: mod string name string - expr Expr // the value expr of field; everything after `=` is_pub bool is_markused bool // an explict `[markused]` tag; the const will NOT be removed by `-skip-unused`, no matter what pos token.Position pub mut: + expr Expr // the value expr of field; everything after `=` typ Type // the type of the const field, it can be any type in V comments []Comment // comments before current const field // the comptime_expr_value field is filled by the checker, when it has enough @@ -387,11 +387,11 @@ pub mut: pub struct StructInitEmbed { pub: - expr Expr pos token.Position comments []Comment next_comments []Comment pub mut: + expr Expr name string typ Type expected_type Type @@ -791,10 +791,10 @@ pub mut: // ++, -- pub struct PostfixExpr { pub: - op token.Kind - expr Expr - pos token.Position + op token.Kind + pos token.Position pub mut: + expr Expr auto_locked string } @@ -812,10 +812,10 @@ pub mut: pub struct IndexExpr { pub: - pos token.Position - index Expr // [0], RangeExpr [start..end] or map[key] - or_expr OrExpr + pos token.Position pub mut: + index Expr // [0], RangeExpr [start..end] or map[key] + or_expr OrExpr left Expr left_type Type // array, map, fixed array is_setter bool @@ -831,10 +831,10 @@ pub struct IfExpr { pub: is_comptime bool tok_kind token.Kind - left Expr // `a` in `a := if ...` pos token.Position post_comments []Comment pub mut: + left Expr // `a` in `a := if ...` branches []IfBranch // includes all `else if` branches is_expr bool typ Type @@ -844,11 +844,11 @@ pub mut: pub struct IfBranch { pub: - cond Expr pos token.Position body_pos token.Position comments []Comment pub mut: + cond Expr pkg_exist bool stmts []Stmt scope &Scope @@ -856,16 +856,17 @@ pub mut: pub struct UnsafeExpr { pub: + pos token.Position +pub mut: expr Expr - pos token.Position } pub struct LockExpr { pub: - stmts []Stmt is_rlock []bool pos token.Position pub mut: + stmts []Stmt lockeds []Expr // `x`, `y.z` in `lock x, y.z {` comments []Comment is_expr bool @@ -876,11 +877,11 @@ pub mut: pub struct MatchExpr { pub: tok_kind token.Kind - cond Expr - branches []MatchBranch pos token.Position comments []Comment // comments before the first branch pub mut: + cond Expr + branches []MatchBranch is_expr bool // returns a value return_type Type cond_type Type // type of `x` in `match x {` @@ -913,13 +914,14 @@ pub mut: pub struct SelectBranch { pub: - stmt Stmt // `a := <-ch` or `ch <- a` - stmts []Stmt // right side pos token.Position comment Comment // comment above `select {` is_else bool is_timeout bool post_comments []Comment +pub mut: + stmt Stmt // `a := <-ch` or `ch <- a` + stmts []Stmt // right side } pub enum ComptimeForKind { @@ -942,11 +944,11 @@ pub mut: pub struct ForStmt { pub: - cond Expr - stmts []Stmt is_inf bool // `for {}` pos token.Position pub mut: + cond Expr + stmts []Stmt label string // `label: for {` scope &Scope } @@ -974,16 +976,16 @@ pub mut: pub struct ForCStmt { pub: - init Stmt // i := 0; has_init bool - cond Expr // i < 10; has_cond bool - inc Stmt // i++; i += 2 has_inc bool is_multi bool // for a,b := 0,1; a < 10; a,b = a+b, a {...} - stmts []Stmt pos token.Position pub mut: + init Stmt // i := 0; + cond Expr // i < 10; + inc Stmt // i++; i += 2 + stmts []Stmt label string // `label: for {` scope &Scope } @@ -1031,10 +1033,10 @@ pub mut: // `expr as Ident` pub struct AsCast { pub: - expr Expr // from expr: `expr` in `expr as Ident` - typ Type // to type - pos token.Position + typ Type // to type + pos token.Position pub mut: + expr Expr // from expr: `expr` in `expr as Ident` expr_type Type // from type } @@ -1124,8 +1126,9 @@ pub mut: // `(3+4)` pub struct ParExpr { pub: + pos token.Position +pub mut: expr Expr - pos token.Position } pub struct GoExpr { @@ -1152,20 +1155,20 @@ pub struct ArrayInit { pub: pos token.Position // `[]` in []Type{} position elem_type_pos token.Position // `Type` in []Type{} position - exprs []Expr // `[expr, expr]` or `[expr]Type{}` for fixed array - ecmnts [][]Comment // optional iembed comments after each expr + ecmnts [][]Comment // optional iembed comments after each expr pre_cmnts []Comment is_fixed bool has_val bool // fixed size literal `[expr, expr]!` mod string - len_expr Expr // len: expr - cap_expr Expr // cap: expr - default_expr Expr // init: expr has_len bool has_cap bool has_default bool has_it bool // true if temp variable it is used pub mut: + exprs []Expr // `[expr, expr]` or `[expr]Type{}` for fixed array + len_expr Expr // len: expr + cap_expr Expr // cap: expr + default_expr Expr // init: expr expr_types []Type // [Dog, Cat] // also used for interface_types elem_type Type // element type default_type Type // default value type @@ -1174,19 +1177,19 @@ pub mut: pub struct ArrayDecompose { pub: - expr Expr - pos token.Position + pos token.Position pub mut: + expr Expr expr_type Type arg_type Type } pub struct ChanInit { pub: - pos token.Position - cap_expr Expr - has_cap bool + pos token.Position + has_cap bool pub mut: + cap_expr Expr typ Type elem_type Type } @@ -1194,11 +1197,11 @@ pub mut: pub struct MapInit { pub: pos token.Position - keys []Expr - vals []Expr comments [][]Comment // comments after key-value pairs pre_cmnts []Comment // comments before the first key-value pair pub mut: + keys []Expr + vals []Expr typ Type key_type Type value_type Type @@ -1207,18 +1210,18 @@ pub mut: // s[10..20] pub struct RangeExpr { pub: - low Expr - high Expr has_high bool has_low bool pos token.Position is_gated bool // #[] gated array +pub mut: + low Expr + high Expr } pub struct CastExpr { -pub: - arg Expr // `n` in `string(buf, n)` pub mut: + arg Expr // `n` in `string(buf, n)` typ Type // `string` expr Expr // `buf` in `string(buf, n)` and `&Type(buf)` typname string // `&Type` in `&Type(buf)` @@ -1457,9 +1460,9 @@ pub struct Assoc { pub: var_name string fields []string - exprs []Expr pos token.Position pub mut: + exprs []Expr typ Type scope &Scope } @@ -1467,19 +1470,19 @@ pub mut: pub struct SizeOf { pub: is_type bool - expr Expr // checker uses this to set typ pos token.Position pub mut: - typ Type + expr Expr // checker uses this to set typ + typ Type } pub struct IsRefType { pub: is_type bool - expr Expr // checker uses this to set typ pos token.Position pub mut: - typ Type + expr Expr // checker uses this to set typ + typ Type } pub struct OffsetOf { @@ -1491,24 +1494,25 @@ pub: pub struct Likely { pub: - expr Expr pos token.Position is_likely bool // false for _unlikely_ +pub mut: + expr Expr } pub struct TypeOf { pub: - expr Expr - pos token.Position + pos token.Position pub mut: + expr Expr expr_type Type } pub struct DumpExpr { pub: - expr Expr - pos token.Position + pos token.Position pub mut: + expr Expr expr_type Type cname string // filled in the checker } @@ -1542,12 +1546,12 @@ pub mut: pub struct ComptimeSelector { pub: has_parens bool // if $() is used, for vfmt - left Expr - field_expr Expr pos token.Position pub mut: - left_type Type - typ Type + left Expr + left_type Type + field_expr Expr + typ Type } pub struct ComptimeCall { @@ -1614,21 +1618,21 @@ pub mut: pub struct SqlExpr { pub: - typ Type - is_count bool - db_expr Expr // `db` in `sql db {` - has_where bool - has_offset bool - offset_expr Expr - has_order bool - order_expr Expr - has_desc bool - is_array bool - pos token.Position - has_limit bool - limit_expr Expr + typ Type + is_count bool + has_where bool + has_order bool + has_limit bool + has_offset bool + has_desc bool + is_array bool + pos token.Position pub mut: + db_expr Expr // `db` in `sql db {` where_expr Expr + order_expr Expr + limit_expr Expr + offset_expr Expr table_expr TypeNode fields []StructField sub_structs map[int]SqlExpr @@ -1780,9 +1784,10 @@ pub fn (stmt Stmt) check_c_expr() ? { pub struct CTempVar { pub: name string // the name of the C temporary variable; used by g.expr(x) - orig Expr // the original expression, which produced the C temp variable; used by x.str() typ Type // the type of the original expression is_ptr bool // whether the type is a pointer +pub mut: + orig Expr // the original expression, which produced the C temp variable; used by x.str() } pub fn (node Node) position() token.Position { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0f4e4c5a2e..f9b77b10cd 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1821,7 +1821,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { node.typ = c.expr(node.expr) c.expected_type = ast.void_type mut or_typ := ast.void_type - match node.expr { + match mut node.expr { ast.IndexExpr { if node.expr.or_expr.kind != .absent { node.is_expr = true @@ -1837,7 +1837,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { else {} } if !c.pref.is_repl && (c.stmt_level == 1 || (c.stmt_level > 1 && !c.is_last_stmt)) { - if node.expr is ast.InfixExpr { + if mut node.expr is ast.InfixExpr { if node.expr.op == .left_shift { left_sym := c.table.final_sym(node.expr.left_type) if left_sym.kind != .array { @@ -2422,7 +2422,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type { c.error('expected `string` instead of `$expr_sym.name` (e.g. `field.name`)', node.field_expr.position()) } - if node.field_expr is ast.SelectorExpr { + if mut node.field_expr is ast.SelectorExpr { left_pos := node.field_expr.expr.position() if c.comptime_fields_type.len == 0 { c.error('compile time field access can only be used when iterating over `T.fields`', @@ -3025,7 +3025,7 @@ pub fn (mut c Checker) ident(mut node ast.Ident) ast.Type { c.inside_const = false c.mod = old_c_mod - if obj.expr is ast.CallExpr { + if mut obj.expr is ast.CallExpr { if obj.expr.or_block.kind != .absent { typ = typ.clear_flag(.optional) } diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index 6365a9ae57..e483b47b8b 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -147,10 +147,10 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { if !node.is_inf && typ.idx() != ast.bool_type_idx && !c.pref.translated { c.error('non-bool used as for condition', node.pos) } - if node.cond is ast.InfixExpr { + if mut node.cond is ast.InfixExpr { infix := node.cond if infix.op == .key_is { - if infix.left in [ast.Ident, ast.SelectorExpr] && infix.right is ast.TypeNode { + if infix.right is ast.TypeNode && infix.left in [ast.Ident, ast.SelectorExpr] { is_variable := if mut infix.left is ast.Ident { infix.left.kind == .variable } else { diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 8b943b63a5..974e307a2f 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -50,7 +50,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { if node.is_comptime { // Skip checking if needed // smartcast field type on comptime if mut comptime_field_name := '' - if branch.cond is ast.InfixExpr { + if mut branch.cond is ast.InfixExpr { if branch.cond.op == .key_is { if branch.cond.right !is ast.TypeNode { c.error('invalid `\$if` condition: expected a type', branch.cond.right.position()) @@ -106,7 +106,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { } } else if c.pref.output_cross_c { mut is_freestanding_block := false - if branch.cond is ast.Ident { + if mut branch.cond is ast.Ident { if branch.cond.name == 'freestanding' { is_freestanding_block = true } diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index da91d24ffd..134673396d 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -8,7 +8,7 @@ import strings pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { node.is_expr = c.expected_type != ast.void_type node.expected_type = c.expected_type - if node.cond is ast.ParExpr && !c.pref.translated { + if mut node.cond is ast.ParExpr && !c.pref.translated { c.error('unnecessary `()` in `match` condition, use `match expr {` instead of `match (expr) {`.', node.cond.pos) } diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index cad497d01d..a0331e7c60 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -27,13 +27,13 @@ pub fn (mut p Parser) parse_array_type(expecting token.Kind) ast.Type { } ast.Ident { mut show_non_const_error := false - if const_field := p.table.global_scope.find_const('${p.mod}.$size_expr.name') { - if const_field.expr is ast.IntegerLiteral { + if mut const_field := p.table.global_scope.find_const('${p.mod}.$size_expr.name') { + if mut const_field.expr is ast.IntegerLiteral { fixed_size = const_field.expr.val.int() } else { - if const_field.expr is ast.InfixExpr { + if mut const_field.expr is ast.InfixExpr { mut t := transformer.new_transformer(p.pref) - folded_expr := t.infix_expr(const_field.expr) + folded_expr := t.infix_expr(mut const_field.expr) if folded_expr is ast.IntegerLiteral { fixed_size = folded_expr.val.int() diff --git a/vlib/v/transformer/index_state.v b/vlib/v/transformer/index_state.v new file mode 100644 index 0000000000..a45005447a --- /dev/null +++ b/vlib/v/transformer/index_state.v @@ -0,0 +1,123 @@ +module transformer + +struct KeyVal { + key string + value int +} + +[if debug_bounds_checking ?] +fn debug_bounds_checking(str string) { + println(str) +} + +// IndexState is used to track the index analysis performed when parsing the code +// `IndexExpr` nodes are annotated with `is_direct`, indicating that the array index can be safely directly accessed. + +// The c_gen code check will handle this annotation and perform this direct memory access. The following cases are considered valid for this optimisation: +// 1. the array size is known and has a `len` larger than the index requested +// 2. the array was previously accessed with a higher value which would have reported the issue already +// 3. the array was created from a range expression a := range[10..13] and the offset'ed indexes are safe + +// Current limitations: +// * any function using break/continue or goto/label stopped from being optimised as soon as the relevant AST nodes are found as the code can not be ensured to be sequential +// * `enum` and `const` indexes are not optimised (they could probably be looked up) +// * for loops with multiple var in their init and/or inc are not analysed +// * mut array are not analysed as their size can be reduced, but self-assignment in a single line + +pub struct IndexState { +mut: + // max_index has the biggest array index accessed for then named array + // so if a[2] was set or read, it will be 2 + // A new array with no .len will recorded as -1 (accessing a[0] would be invalid) + // the value -2 is used to indicate that the array should not be analysed + // this is used for a mut array + max_index map[string]int + // We need to snapshot when entering `if` and `for` blocks and restore on exit + // as the statements may not be run. This is managed by indent() & unindent(). + saved_disabled []bool + saved_key_vals [][]KeyVal +pub mut: + // on encountering goto/break/continue statements we stop any analysis + // for the current function (as the code is not linear anymore) + disabled bool + level int +} + +// we are remembering the last array accessed and checking if the value is safe +// the node is updated with this information which can then be used by the code generators +fn (mut i IndexState) safe_access(key string, new int) bool { + $if no_bounds_checking { + return false + } + if i.disabled { + return false + } + old := i.max_index[key] or { + debug_bounds_checking('$i.level ${key}.len = $new') + i.max_index[key] = new + return false + } + if new > old { + if old < -1 { + debug_bounds_checking('$i.level $key[$new] unsafe (mut array)') + return false + } + debug_bounds_checking('$i.level $key[$new] unsafe (index was $old)') + i.max_index[key] = new + return false + } + debug_bounds_checking('$i.level $key[$new] safe (index is $old)') + return true +} + +// safe_offset returns for a previvous array what was the highest +// offset we ever accessed for that identifier +fn (mut i IndexState) safe_offset(key string) int { + $if no_bounds_checking { + return -2 + } + if i.disabled { + return -2 + } + return i.max_index[key] or { -1 } +} + +// indent is used for when encountering new code blocks (if, for and functions) +// The code analysis needs to take into consideration blocks of code which +// may not run at runtime (if/for) and therefore even if a new maximum for an +// index access is found on an if branch it can not be used within the parent +// code. The same is true with for blocks. indent() snapshot the current state, +// to allow restoration with unindent() +// Also within a function, analysis must be `disabled` when goto or break are +// encountered as the code flow is then not lineear, and only restart when a +// new function analysis is started. +[if !no_bounds_checking] +fn (mut i IndexState) indent(is_function bool) { + mut kvs := []KeyVal{cap: i.max_index.len} + for k, v in i.max_index { + kvs << KeyVal{k, v} + } + i.saved_disabled << i.disabled + i.saved_key_vals << kvs + if is_function { + i.disabled = false + } + i.level += 1 +} + +// restoring the data as it was before the if/for/unsafe block +[if !no_bounds_checking] +fn (mut i IndexState) unindent() { + i.level -= 1 + mut keys := []string{cap: i.max_index.len} + for k, _ in i.max_index { + keys << k + } + for k in keys { + i.max_index.delete(k) + } + for saved in i.saved_key_vals.pop() { + i.max_index[saved.key] = saved.value + } + i.disabled = i.saved_disabled.pop() +} diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index 623c654a91..018e93a73b 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -4,128 +4,6 @@ import v.pref import v.ast import v.util -struct KeyVal { - key string - value int -} - -[if debug_bounds_checking ?] -fn debug_bounds_checking(str string) { - println(str) -} - -// IndexState is used to track the index analysis performed when parsing the code -// `IndexExpr` nodes are annotated with `is_direct`, indicating that the array index can be safely directly accessed. - -// The c_gen code check will handle this annotation and perform this direct memory access. The following cases are considered valid for this optimisation: -// 1. the array size is known and has a `len` larger than the index requested -// 2. the array was previously accessed with a higher value which would have reported the issue already -// 3. the array was created from a range expression a := range[10..13] and the offset'ed indexes are safe - -// Current limitations: -// * any function using break/continue or goto/label stopped from being optimised as soon as the relevant AST nodes are found as the code can not be ensured to be sequential -// * `enum` and `const` indexes are not optimised (they could probably be looked up) -// * for loops with multiple var in their init and/or inc are not analysed -// * mut array are not analysed as their size can be reduced, but self-assignment in a single line - -pub struct IndexState { -mut: - // max_index has the biggest array index accessed for then named array - // so if a[2] was set or read, it will be 2 - // A new array with no .len will recorded as -1 (accessing a[0] would be invalid) - // the value -2 is used to indicate that the array should not be analysed - // this is used for a mut array - max_index map[string]int - // We need to snapshot when entering `if` and `for` blocks and restore on exit - // as the statements may not be run. This is managed by indent() & unindent(). - saved_disabled []bool - saved_key_vals [][]KeyVal -pub mut: - // on encountering goto/break/continue statements we stop any analysis - // for the current function (as the code is not linear anymore) - disabled bool - level int -} - -// we are remembering the last array accessed and checking if the value is safe -// the node is updated with this information which can then be used by the code generators -fn (mut i IndexState) safe_access(key string, new int) bool { - $if no_bounds_checking { - return false - } - if i.disabled { - return false - } - old := i.max_index[key] or { - debug_bounds_checking('$i.level ${key}.len = $new') - i.max_index[key] = new - return false - } - if new > old { - if old < -1 { - debug_bounds_checking('$i.level $key[$new] unsafe (mut array)') - return false - } - debug_bounds_checking('$i.level $key[$new] unsafe (index was $old)') - i.max_index[key] = new - return false - } - debug_bounds_checking('$i.level $key[$new] safe (index is $old)') - return true -} - -// safe_offset returns for a previvous array what was the highest -// offset we ever accessed for that identifier -fn (mut i IndexState) safe_offset(key string) int { - $if no_bounds_checking { - return -2 - } - if i.disabled { - return -2 - } - return i.max_index[key] or { -1 } -} - -// indent is used for when encountering new code blocks (if, for and functions) -// The code analysis needs to take into consideration blocks of code which -// may not run at runtime (if/for) and therefore even if a new maximum for an -// index access is found on an if branch it can not be used within the parent -// code. The same is true with for blocks. indent() snapshot the current state, -// to allow restoration with unindent() -// Also within a function, analysis must be `disabled` when goto or break are -// encountered as the code flow is then not lineear, and only restart when a -// new function analysis is started. -[if !no_bounds_checking] -fn (mut i IndexState) indent(is_function bool) { - mut kvs := []KeyVal{cap: i.max_index.len} - for k, v in i.max_index { - kvs << KeyVal{k, v} - } - i.saved_disabled << i.disabled - i.saved_key_vals << kvs - if is_function { - i.disabled = false - } - i.level += 1 -} - -// restoring the data as it was before the if/for/unsafe block -[if !no_bounds_checking] -fn (mut i IndexState) unindent() { - i.level -= 1 - mut keys := []string{cap: i.max_index.len} - for k, _ in i.max_index { - keys << k - } - for k in keys { - i.max_index.delete(k) - } - for saved in i.saved_key_vals.pop() { - i.max_index[saved.key] = saved.value - } - i.disabled = i.saved_disabled.pop() -} - pub struct Transformer { pref &pref.Preferences pub mut: @@ -226,14 +104,14 @@ pub fn (mut t Transformer) find_mut_self_assign(node ast.AssignStmt) { // even if mutable we can be sure than `a[1] = a[2] is safe } -pub fn (mut t Transformer) find_assert_len(node ast.InfixExpr) { +pub fn (mut t Transformer) find_assert_len(mut node ast.InfixExpr) ast.Expr { if !t.pref.is_prod { - return + return node } - right := node.right + right := t.expr(mut node.right) match right { ast.IntegerLiteral { - left := node.left + left := t.expr(mut node.left) if left is ast.SelectorExpr { len := right.val.int() if left.field_name == 'len' { @@ -253,7 +131,7 @@ pub fn (mut t Transformer) find_assert_len(node ast.InfixExpr) { } } ast.SelectorExpr { - left := node.left + left := t.expr(mut node.left) if left is ast.IntegerLiteral { len := left.val.int() if right.field_name == 'len' { @@ -274,6 +152,7 @@ pub fn (mut t Transformer) find_assert_len(node ast.InfixExpr) { } else {} } + return node } pub fn (mut t Transformer) check_safe_array(mut node ast.IndexExpr) { @@ -327,19 +206,18 @@ pub fn (mut t Transformer) check_safe_array(mut node ast.IndexExpr) { } } -pub fn (mut t Transformer) stmt(mut node ast.Stmt) { +pub fn (mut t Transformer) stmt(mut node ast.Stmt) ast.Stmt { match mut node { ast.EmptyStmt {} ast.NodeError {} ast.AsmStmt {} ast.AssertStmt { - expr := node.expr - match expr { + match mut node.expr { ast.InfixExpr { - t.find_assert_len(expr) + node.expr = t.find_assert_len(mut node.expr) } else { - t.expr(expr) + node.expr = t.expr(mut node.expr) } } } @@ -348,16 +226,16 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) { t.find_new_range(node) t.find_mut_self_assign(node) for mut right in node.right { - right = t.expr(right) + right = t.expr(mut right) } - for left in node.left { - t.expr(left) + for mut left in node.left { + left = t.expr(mut left) } } ast.Block { t.index.indent(false) for mut stmt in node.stmts { - t.stmt(mut stmt) + stmt = t.stmt(mut stmt) } t.index.unindent() } @@ -366,252 +244,102 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) { // we can not rely on sequential scanning and need to cancel all index optimisation t.index.disabled = true } - ast.ComptimeFor {} - ast.ConstDecl { - for mut field in node.fields { - expr := t.expr(field.expr) - field = ast.ConstField{ - ...(*field) - expr: expr - } + ast.ComptimeFor { + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) + } + } + ast.ConstDecl { + for mut field in node.fields { + field.expr = t.expr(mut field.expr) + } + } + ast.DeferStmt { + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) } } - ast.DeferStmt {} ast.EnumDecl {} ast.ExprStmt { - expr := node.expr - node = &ast.ExprStmt{ - ...node - expr: match mut expr { - ast.IfExpr { - t.if_expr(mut expr) - } - ast.MatchExpr { - t.match_expr(mut expr) - } - else { - t.expr(expr) - } + // TODO: check if this can be handled in `t.expr` + node.expr = match mut node.expr { + ast.IfExpr { + t.expr_stmt_if_expr(mut node.expr) + } + ast.MatchExpr { + t.expr_stmt_match_expr(mut node.expr) + } + else { + t.expr(mut node.expr) } } } ast.FnDecl { t.index.indent(true) for mut stmt in node.stmts { - t.stmt(mut stmt) + stmt = t.stmt(mut stmt) } t.index.unindent() } ast.ForCStmt { - // TODO we do not optimise array access for multi init - // for a,b := 0,1; a < 10; a,b = a+b, a {...} - - // https://github.com/vlang/v/issues/12782 - // if node.has_init && !node.is_multi { - // mut init := node.init - // t.stmt(mut init) - // } - - if node.has_cond { - t.expr(node.cond) - } - - t.index.indent(false) - for mut stmt in node.stmts { - t.stmt(mut stmt) - } - t.index.unindent() - - // https://github.com/vlang/v/issues/12782 - // if node.has_inc && !node.is_multi { - // mut inc := node.inc - // t.stmt(mut inc) - // } + return t.for_c_stmt(mut node) } ast.ForInStmt { // indexes access within the for itself are not optimised (yet) t.index.indent(false) for mut stmt in node.stmts { - t.stmt(mut stmt) + stmt = t.stmt(mut stmt) } t.index.unindent() } ast.ForStmt { - cond_expr := t.expr(node.cond) - - node = &ast.ForStmt{ - ...node - cond: cond_expr - } - match node.cond { - ast.BoolLiteral { - if !(node.cond as ast.BoolLiteral).val { // for false { ... } should be eleminated - node = &ast.EmptyStmt{} - } - } - else { - if !node.is_inf { - t.index.indent(false) - for mut stmt in node.stmts { - t.stmt(mut stmt) - } - t.index.unindent() - } - } + return t.for_stmt(mut node) + } + ast.GlobalDecl { + for mut field in node.fields { + field.expr = t.expr(mut field.expr) } } - ast.GlobalDecl {} ast.GotoLabel {} ast.GotoStmt { // we can not rely on sequential scanning and need to cancel all index optimisation t.index.disabled = true } - ast.HashStmt {} + ast.HashStmt { + for mut cond in node.ct_conds { + cond = t.expr(mut cond) + } + } ast.Import {} - ast.InterfaceDecl {} + ast.InterfaceDecl { + for mut field in node.fields { + field.default_expr = t.expr(mut field.default_expr) + } + } ast.Module {} ast.Return { for mut expr in node.exprs { - expr = t.expr(expr) + expr = t.expr(mut expr) } } ast.SqlStmt {} - ast.StructDecl {} + ast.StructDecl { + for mut field in node.fields { + field.default_expr = t.expr(mut field.default_expr) + } + } ast.TypeDecl {} } + return node } -pub fn (mut t Transformer) expr(node ast.Expr) ast.Expr { - match mut node { - ast.CallExpr { - for arg in node.args { - t.expr(arg.expr) - } - return node - } - ast.InfixExpr { - return t.infix_expr(node) - } - ast.OrExpr { - for mut stmt in node.stmts { - t.stmt(mut stmt) - } - return node - } - ast.IndexExpr { - t.check_safe_array(mut node) - mut index := ast.IndexExpr{ - ...node - index: t.expr(node.index) - } - return index - } - ast.IfExpr { - for mut branch in node.branches { - branch = ast.IfBranch{ - ...(*branch) - cond: t.expr(branch.cond) - } - t.index.indent(false) - for i, mut stmt in branch.stmts { - t.stmt(mut stmt) - - if i == branch.stmts.len - 1 { - if stmt is ast.ExprStmt { - expr := (stmt as ast.ExprStmt).expr - - match expr { - ast.IfExpr { - if expr.branches.len == 1 { - branch.stmts.pop() - branch.stmts << expr.branches[0].stmts - break - } - } - ast.MatchExpr { - if expr.branches.len == 1 { - branch.stmts.pop() - branch.stmts << expr.branches[0].stmts - break - } - } - else {} - } - } - } - } - t.index.unindent() - } - // where we place the result of the if when a := if ... - t.expr(node.left) - return node - } - ast.MatchExpr { - node = ast.MatchExpr{ - ...node - cond: t.expr(node.cond) - } - for mut branch in node.branches { - for mut expr in branch.exprs { - expr = t.expr(expr) - } - t.index.indent(false) - for i, mut stmt in branch.stmts { - t.stmt(mut stmt) - - if i == branch.stmts.len - 1 { - if stmt is ast.ExprStmt { - expr := (stmt as ast.ExprStmt).expr - - match expr { - ast.IfExpr { - if expr.branches.len == 1 { - branch.stmts.pop() - branch.stmts << expr.branches[0].stmts - break - } - } - ast.MatchExpr { - if expr.branches.len == 1 { - branch.stmts.pop() - branch.stmts << expr.branches[0].stmts - break - } - } - else {} - } - } - } - } - t.index.unindent() - } - return node - } - ast.AnonFn { - return node - } - ast.PostfixExpr { - return node - } - ast.PrefixExpr { - return node - } - ast.Likely { - return node - } - else { - return node - } +pub fn (mut t Transformer) expr_stmt_if_expr(mut node ast.IfExpr) ast.Expr { + mut stop_index, mut unreachable_branches := -1, []int{cap: node.branches.len} + if node.is_comptime { + return node } -} - -pub fn (mut t Transformer) if_expr(mut original ast.IfExpr) ast.Expr { - mut stop_index, mut unreachable_branches := -1, []int{cap: original.branches.len} - if original.is_comptime { - return *original - } - for i, mut branch in original.branches { - cond := t.expr(branch.cond) + for i, mut branch in node.branches { + cond := t.expr(mut branch.cond) branch = ast.IfBranch{ ...(*branch) cond: cond @@ -632,32 +360,29 @@ pub fn (mut t Transformer) if_expr(mut original ast.IfExpr) ast.Expr { } if stop_index != -1 { unreachable_branches = unreachable_branches.filter(it < stop_index) - original.branches = original.branches[..stop_index + 1] + node.branches = node.branches[..stop_index + 1] } for unreachable_branches.len != 0 { - original.branches.delete(unreachable_branches.pop()) + node.branches.delete(unreachable_branches.pop()) } + /* + FIXME: optimization causes cgen error `g.expr(): unhandled EmptyExpr` if original.branches.len == 0 { // no remain branches to walk through return ast.EmptyExpr{} - } - if original.branches.len == 1 && original.branches[0].cond.type_name() == 'unknown v.ast.Expr' { - original.branches[0] = &ast.IfBranch{ - ...original.branches[0] - cond: ast.BoolLiteral{ - val: true - } + }*/ + if node.branches.len == 1 && node.branches[0].cond.type_name() == 'unknown v.ast.Expr' { + node.branches[0].cond = ast.BoolLiteral{ + val: true } } - return *original + return node } -pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { - cond, mut terminate := t.expr(original.cond), false - original = ast.MatchExpr{ - ...(*original) - cond: cond - } - for mut branch in original.branches { +pub fn (mut t Transformer) expr_stmt_match_expr(mut node ast.MatchExpr) ast.Expr { + mut terminate := false + cond := t.expr(mut node.cond) + node.cond = cond + for mut branch in node.branches { if branch.is_else { t.index.indent(false) for mut stmt in branch.stmts { @@ -668,17 +393,14 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { } for mut expr in branch.exprs { - expr = t.expr(expr) + expr = t.expr(mut expr) - match cond { + match mut cond { ast.BoolLiteral { if expr is ast.BoolLiteral { if cond.val == (expr as ast.BoolLiteral).val { branch.exprs = [expr] - original = ast.MatchExpr{ - ...(*original) - branches: [branch] - } + node.branches = [branch] terminate = true } } @@ -687,10 +409,7 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { if expr is ast.IntegerLiteral { if cond.val.int() == (expr as ast.IntegerLiteral).val.int() { branch.exprs = [expr] - original = ast.MatchExpr{ - ...(*original) - branches: [branch] - } + node.branches = [branch] terminate = true } } @@ -699,10 +418,7 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { if expr is ast.FloatLiteral { if cond.val.f32() == (expr as ast.FloatLiteral).val.f32() { branch.exprs = [expr] - original = ast.MatchExpr{ - ...(*original) - branches: [branch] - } + node.branches = [branch] terminate = true } } @@ -711,10 +427,7 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { if expr is ast.StringLiteral { if cond.val == (expr as ast.StringLiteral).val { branch.exprs = [expr] - original = ast.MatchExpr{ - ...(*original) - branches: [branch] - } + node.branches = [branch] terminate = true } } @@ -725,7 +438,7 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { t.index.indent(false) for mut stmt in branch.stmts { - t.stmt(mut stmt) + stmt = t.stmt(mut stmt) } t.index.unindent() @@ -733,267 +446,564 @@ pub fn (mut t Transformer) match_expr(mut original ast.MatchExpr) ast.Expr { break } } - return *original + return node } -pub fn (mut t Transformer) infix_expr(original ast.InfixExpr) ast.Expr { - mut node := original - node.left = t.expr(node.left) - node.right = t.expr(node.right) - mut pos := node.left.position() - pos.extend(node.pos) - pos.extend(node.right.position()) - left_node := node.left - right_node := node.right - match left_node { +pub fn (mut t Transformer) for_c_stmt(mut node ast.ForCStmt) ast.Stmt { + // TODO we do not optimise array access for multi init + // for a,b := 0,1; a < 10; a,b = a+b, a {...} + + if node.has_init && !node.is_multi { + node.init = t.stmt(mut node.init) + } + + if node.has_cond { + node.cond = t.expr(mut node.cond) + } + + t.index.indent(false) + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) + } + t.index.unindent() + + if node.has_inc && !node.is_multi { + node.inc = t.stmt(mut node.inc) + } + return node +} + +pub fn (mut t Transformer) for_stmt(mut node ast.ForStmt) ast.Stmt { + node.cond = t.expr(mut node.cond) + match node.cond { ast.BoolLiteral { - match right_node { - ast.BoolLiteral { - match node.op { - .eq { - return ast.BoolLiteral{ - val: left_node.val == right_node.val - } - } - .ne { - return ast.BoolLiteral{ - val: left_node.val != right_node.val - } - } - .and { - return ast.BoolLiteral{ - val: left_node.val && right_node.val - } - } - .logical_or { - return ast.BoolLiteral{ - val: left_node.val || right_node.val - } - } - else { - return node - } - } - } - else { - return node - } - } - } - ast.StringLiteral { - match right_node { - ast.StringLiteral { - match node.op { - .eq { - return ast.BoolLiteral{ - val: left_node.val == right_node.val - } - } - .ne { - return ast.BoolLiteral{ - val: left_node.val != right_node.val - } - } - .plus { - return if t.pref.backend == .c { ast.Expr(ast.StringLiteral{ - val: util.smart_quote(left_node.val, left_node.is_raw) + util.smart_quote(right_node.val, right_node.is_raw) - pos: pos - }) } else { ast.Expr(node) } - } - else { - return node - } - } - } - else { - return node - } - } - } - ast.IntegerLiteral { - match right_node { - ast.IntegerLiteral { - left_val := left_node.val.int() - right_val := right_node.val.int() - match node.op { - .eq { - return ast.BoolLiteral{ - val: left_node.val == right_node.val - } - } - .ne { - return ast.BoolLiteral{ - val: left_node.val != right_node.val - } - } - .gt { - return ast.BoolLiteral{ - val: left_node.val > right_node.val - } - } - .ge { - return ast.BoolLiteral{ - val: left_node.val >= right_node.val - } - } - .lt { - return ast.BoolLiteral{ - val: left_node.val < right_node.val - } - } - .le { - return ast.BoolLiteral{ - val: left_node.val <= right_node.val - } - } - .plus { - return ast.IntegerLiteral{ - val: (left_val + right_val).str() - pos: pos - } - } - .mul { - return ast.IntegerLiteral{ - val: (left_val * right_val).str() - pos: pos - } - } - .minus { - return ast.IntegerLiteral{ - val: (left_val - right_val).str() - pos: pos - } - } - .div { - return ast.IntegerLiteral{ - val: (left_val / right_val).str() - pos: pos - } - } - .mod { - return ast.IntegerLiteral{ - val: (left_val % right_val).str() - pos: pos - } - } - .xor { - return ast.IntegerLiteral{ - val: (left_val ^ right_val).str() - pos: pos - } - } - .pipe { - return ast.IntegerLiteral{ - val: (left_val | right_val).str() - pos: pos - } - } - .amp { - return ast.IntegerLiteral{ - val: (left_val & right_val).str() - pos: pos - } - } - .left_shift { - return ast.IntegerLiteral{ - val: (u32(left_val) << right_val).str() - pos: pos - } - } - .right_shift { - return ast.IntegerLiteral{ - val: (left_val >> right_val).str() - pos: pos - } - } - .unsigned_right_shift { - return ast.IntegerLiteral{ - val: (left_val >>> right_val).str() - pos: pos - } - } - else { - return node - } - } - } - else { - return node - } - } - } - ast.FloatLiteral { - match right_node { - ast.FloatLiteral { - left_val := left_node.val.f32() - right_val := right_node.val.f32() - match node.op { - .eq { - return ast.BoolLiteral{ - val: left_node.val == right_node.val - } - } - .ne { - return ast.BoolLiteral{ - val: left_node.val != right_node.val - } - } - .gt { - return ast.BoolLiteral{ - val: left_node.val > right_node.val - } - } - .ge { - return ast.BoolLiteral{ - val: left_node.val >= right_node.val - } - } - .lt { - return ast.BoolLiteral{ - val: left_node.val < right_node.val - } - } - .le { - return ast.BoolLiteral{ - val: left_node.val <= right_node.val - } - } - .plus { - return ast.FloatLiteral{ - val: (left_val + right_val).str() - pos: pos - } - } - .mul { - return ast.FloatLiteral{ - val: (left_val * right_val).str() - pos: pos - } - } - .minus { - return ast.FloatLiteral{ - val: (left_val - right_val).str() - pos: pos - } - } - .div { - return ast.FloatLiteral{ - val: (left_val / right_val).str() - pos: pos - } - } - else { - return node - } - } - } - else { - return node - } + if !(node.cond as ast.BoolLiteral).val { // for false { ... } should be eleminated + return ast.EmptyStmt{} } } else { - return node + if !node.is_inf { + t.index.indent(false) + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) + } + t.index.unindent() + } } } + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) + } + return node +} + +pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr { + match mut node { + ast.AnonFn { + node.decl = t.stmt(mut node.decl) as ast.FnDecl + } + ast.ArrayDecompose { + node.expr = t.expr(mut node.expr) + } + ast.ArrayInit { + for mut expr in node.exprs { + expr = t.expr(mut expr) + } + node.len_expr = t.expr(mut node.len_expr) + node.cap_expr = t.expr(mut node.cap_expr) + node.default_expr = t.expr(mut node.default_expr) + } + ast.AsCast { + node.expr = t.expr(mut node.expr) + } + ast.CTempVar { + node.orig = t.expr(mut node.orig) + } + ast.CallExpr { + node.left = t.expr(mut node.left) + for mut arg in node.args { + arg.expr = t.expr(mut arg.expr) + } + node.or_block = t.expr(mut node.or_block) as ast.OrExpr + } + ast.CastExpr { + node.arg = t.expr(mut node.arg) + node.expr = t.expr(mut node.expr) + } + ast.ChanInit { + node.cap_expr = t.expr(mut node.cap_expr) + } + ast.ComptimeCall { + for mut arg in node.args { + arg.expr = t.expr(mut arg.expr) + } + } + ast.ComptimeSelector { + node.left = t.expr(mut node.left) + node.field_expr = t.expr(mut node.field_expr) + } + ast.ConcatExpr { + for mut val in node.vals { + val = t.expr(mut val) + } + } + ast.DumpExpr { + node.expr = t.expr(mut node.expr) + } + ast.GoExpr { + node.call_expr = t.expr(mut node.call_expr) as ast.CallExpr + } + ast.IfExpr { + return t.if_expr(mut node) + } + ast.IfGuardExpr { + node.expr = t.expr(mut node.expr) + } + ast.IndexExpr { + t.check_safe_array(mut node) + node.left = t.expr(mut node.left) + node.index = t.expr(mut node.index) + node.or_expr = t.expr(mut node.or_expr) as ast.OrExpr + } + ast.InfixExpr { + return t.infix_expr(mut node) + } + ast.IsRefType { + node.expr = t.expr(mut node.expr) + } + ast.Likely { + node.expr = t.expr(mut node.expr) + } + ast.LockExpr { + for mut stmt in node.stmts { + stmt = t.stmt(mut stmt) + } + for mut locked in node.lockeds { + locked = t.expr(mut locked) + } + } + ast.MapInit { + for mut key in node.keys { + key = t.expr(mut key) + } + for mut val in node.vals { + val = t.expr(mut val) + } + } + ast.MatchExpr { + return t.match_expr(mut node) + } + ast.OrExpr { + for mut stmt in node.stmts { + t.stmt(mut stmt) + } + } + ast.ParExpr { + node.expr = t.expr(mut node.expr) + } + ast.PostfixExpr { + node.expr = t.expr(mut node.expr) + } + ast.PrefixExpr { + node.right = t.expr(mut node.right) + node.or_block = t.expr(mut node.or_block) as ast.OrExpr + } + ast.RangeExpr { + node.low = t.expr(mut node.low) + node.high = t.expr(mut node.high) + } + ast.SelectExpr { + for mut branch in node.branches { + branch.stmt = t.stmt(mut branch.stmt) + for mut stmt in branch.stmts { + stmt = t.stmt(mut stmt) + } + } + } + ast.SelectorExpr { + node.expr = t.expr(mut node.expr) + } + ast.SizeOf { + node.expr = t.expr(mut node.expr) + } + ast.SqlExpr { + return t.sql_expr(mut node) + } + ast.StructInit { + node.update_expr = t.expr(mut node.update_expr) + for mut field in node.fields { + field.expr = t.expr(mut field.expr) + } + for mut embed in node.embeds { + embed.expr = t.expr(mut embed.expr) + } + } + ast.UnsafeExpr { + node.expr = t.expr(mut node.expr) + } + else {} + } + return node +} + +pub fn (mut t Transformer) call_expr(mut node ast.CallExpr) ast.Expr { + for mut arg in node.args { + arg.expr = t.expr(mut arg.expr) + } + return node +} + +pub fn (mut t Transformer) infix_expr(mut node ast.InfixExpr) ast.Expr { + node.left = t.expr(mut node.left) + node.right = t.expr(mut node.right) + + mut pos := node.left.position() + pos.extend(node.pos) + pos.extend(node.right.position()) + + if t.pref.is_debug { + return node + } else { + match mut node.left { + ast.BoolLiteral { + match mut node.right { + ast.BoolLiteral { + match node.op { + .eq { + return ast.BoolLiteral{ + val: node.left.val == node.right.val + } + } + .ne { + return ast.BoolLiteral{ + val: node.left.val != node.right.val + } + } + .and { + return ast.BoolLiteral{ + val: node.left.val && node.right.val + } + } + .logical_or { + return ast.BoolLiteral{ + val: node.left.val || node.right.val + } + } + else {} + } + } + else {} + } + } + ast.StringLiteral { + match mut node.right { + ast.StringLiteral { + match node.op { + .eq { + return ast.BoolLiteral{ + val: node.left.val == node.right.val + } + } + .ne { + return ast.BoolLiteral{ + val: node.left.val != node.right.val + } + } + .plus { + return if t.pref.backend == .c { ast.Expr(ast.StringLiteral{ + val: util.smart_quote(node.left.val, node.left.is_raw) + util.smart_quote(node.right.val, node.right.is_raw) + pos: pos + }) } else { ast.Expr(node) } + } + else {} + } + } + else {} + } + } + ast.IntegerLiteral { + match mut node.right { + ast.IntegerLiteral { + left_val := node.left.val.i64() + right_val := node.right.val.i64() + + match node.op { + .eq { + return ast.BoolLiteral{ + val: left_val == right_val + } + } + .ne { + return ast.BoolLiteral{ + val: left_val != right_val + } + } + .gt { + return ast.BoolLiteral{ + val: left_val > right_val + } + } + .ge { + return ast.BoolLiteral{ + val: left_val >= right_val + } + } + .lt { + return ast.BoolLiteral{ + val: left_val < right_val + } + } + .le { + return ast.BoolLiteral{ + val: left_val <= right_val + } + } + .plus { + return ast.IntegerLiteral{ + val: (left_val + right_val).str() + pos: pos + } + } + .mul { + return ast.IntegerLiteral{ + val: (left_val * right_val).str() + pos: pos + } + } + .minus { + // HACK: prevent folding of `min_i64` values in `math` module + if left_val == -9223372036854775807 && right_val == 1 { + return node + } + + return ast.IntegerLiteral{ + val: (left_val - right_val).str() + pos: pos + } + } + .div { + return ast.IntegerLiteral{ + val: (left_val / right_val).str() + pos: pos + } + } + .mod { + return ast.IntegerLiteral{ + val: (left_val % right_val).str() + pos: pos + } + } + .xor { + return ast.IntegerLiteral{ + val: (left_val ^ right_val).str() + pos: pos + } + } + .pipe { + return ast.IntegerLiteral{ + val: (left_val | right_val).str() + pos: pos + } + } + .amp { + return ast.IntegerLiteral{ + val: (left_val & right_val).str() + pos: pos + } + } + .left_shift { + return ast.IntegerLiteral{ + val: (u32(left_val) << right_val).str() + pos: pos + } + } + .right_shift { + return ast.IntegerLiteral{ + val: (left_val >> right_val).str() + pos: pos + } + } + .unsigned_right_shift { + return ast.IntegerLiteral{ + val: (left_val >>> right_val).str() + pos: pos + } + } + else {} + } + } + else {} + } + } + ast.FloatLiteral { + match mut node.right { + ast.FloatLiteral { + left_val := node.left.val.f32() + right_val := node.right.val.f32() + match node.op { + .eq { + return ast.BoolLiteral{ + val: left_val == right_val + } + } + .ne { + return ast.BoolLiteral{ + val: left_val != right_val + } + } + .gt { + return ast.BoolLiteral{ + val: left_val > right_val + } + } + .ge { + return ast.BoolLiteral{ + val: left_val >= right_val + } + } + .lt { + return ast.BoolLiteral{ + val: left_val < right_val + } + } + .le { + return ast.BoolLiteral{ + val: left_val <= right_val + } + } + .plus { + return ast.FloatLiteral{ + val: (left_val + right_val).str() + pos: pos + } + } + .mul { + return ast.FloatLiteral{ + val: (left_val * right_val).str() + pos: pos + } + } + .minus { + return ast.FloatLiteral{ + val: (left_val - right_val).str() + pos: pos + } + } + .div { + return ast.FloatLiteral{ + val: (left_val / right_val).str() + pos: pos + } + } + else {} + } + } + else {} + } + } + else {} + } + return node + } +} + +pub fn (mut t Transformer) if_expr(mut node ast.IfExpr) ast.Expr { + for mut branch in node.branches { + branch.cond = t.expr(mut branch.cond) + + t.index.indent(false) + for i, mut stmt in branch.stmts { + stmt = t.stmt(mut stmt) + + if i == branch.stmts.len - 1 { + if stmt is ast.ExprStmt { + expr := (stmt as ast.ExprStmt).expr + + match expr { + ast.IfExpr { + if expr.branches.len == 1 { + branch.stmts.pop() + branch.stmts << expr.branches[0].stmts + break + } + } + ast.MatchExpr { + if expr.branches.len == 1 { + branch.stmts.pop() + branch.stmts << expr.branches[0].stmts + break + } + } + else {} + } + } + } + } + t.index.unindent() + } + // where we place the result of the if when a := if ... + node.left = t.expr(mut node.left) + return node +} + +pub fn (mut t Transformer) match_expr(mut node ast.MatchExpr) ast.Expr { + node.cond = t.expr(mut node.cond) + for mut branch in node.branches { + for mut expr in branch.exprs { + expr = t.expr(mut expr) + } + t.index.indent(false) + for i, mut stmt in branch.stmts { + stmt = t.stmt(mut stmt) + + if i == branch.stmts.len - 1 { + if stmt is ast.ExprStmt { + expr := (stmt as ast.ExprStmt).expr + + match expr { + ast.IfExpr { + if expr.branches.len == 1 { + branch.stmts.pop() + branch.stmts << expr.branches[0].stmts + break + } + } + ast.MatchExpr { + if expr.branches.len == 1 { + branch.stmts.pop() + branch.stmts << expr.branches[0].stmts + break + } + } + else {} + } + } + } + } + t.index.unindent() + } + return node +} + +pub fn (mut t Transformer) sql_expr(mut node ast.SqlExpr) ast.Expr { + node.db_expr = t.expr(mut node.db_expr) + if node.has_where { + node.where_expr = t.expr(mut node.where_expr) + } + if node.has_order { + node.order_expr = t.expr(mut node.order_expr) + } + if node.has_limit { + node.limit_expr = t.expr(mut node.limit_expr) + } + if node.has_offset { + node.offset_expr = t.expr(mut node.offset_expr) + } + for mut field in node.fields { + field.default_expr = t.expr(mut field.default_expr) + } + for _, mut sub_struct in node.sub_structs { + sub_struct = t.expr(mut sub_struct) as ast.SqlExpr + } + return node }