From 52bb8524b10d39ead50de0f50d3b762a90e7c33a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 3 Mar 2022 18:03:23 +0000 Subject: Add basic reduce evaluation e.g. `+ 1 2 3`, `- 2 3`, `* 3 4`, `/ 3 4` --- core/text/template/execute.odin | 218 +++++++++++++++++++++++++++++++++++++- core/text/template/scan/scan.odin | 2 +- 2 files changed, 214 insertions(+), 6 deletions(-) diff --git a/core/text/template/execute.odin b/core/text/template/execute.odin index c594daed8..c4b1291c3 100644 --- a/core/text/template/execute.odin +++ b/core/text/template/execute.odin @@ -44,6 +44,7 @@ Execution_Error :: enum { Invalid_Argument_Count, Invalid_Argument_Type, Out_Of_Bounds_Access, + Division_by_Zero, } Error :: union { io.Error, @@ -571,6 +572,201 @@ eval_field :: proc(s: ^State, dot: any, ident: string) -> (value: any, err: Erro return nil, .Invalid_Value } +Operator :: enum { + Add, + Sub, + Mul, + Div, +} + +Value :: union { + bool, + i64, + f64, + string, +} + +get_value :: proc(x: any) -> Value { + if v, ok := x.(Value); ok { + return v + } + + id := reflect.typeid_core(x.id) + #partial switch reflect.type_kind(id) { + case .Integer, .Rune: + if v, ok := reflect.as_i64(x); ok { return v } + case .Float: + if v, ok := reflect.as_f64(x); ok { return v } + case .String: + if v, ok := reflect.as_string(x); ok { return v } + case .Boolean: + if v, ok := reflect.as_bool(x); ok { return v } + } + return nil +} + +promote_values :: proc(lhs, rhs: any) -> (x, y: Value, err: Error) { + a := get_value(lhs) + b := get_value(rhs) + if a == nil || b == nil { + err = .Invalid_Argument_Type + return + } + switch va in a { + case bool: + switch vb in b { + case bool: + return va, vb, nil + case i64: + return i64(va), vb, nil + case f64: + return f64(i64(va)), vb, nil + case string: + err = .Invalid_Argument_Type + return + } + case i64: + switch vb in b { + case bool: + return va, i64(vb), nil + case i64: + return va, vb, nil + case f64: + return f64(va), vb, nil + case string: + err = .Invalid_Argument_Type + return + } + case f64: + switch vb in b { + case bool: + return va, f64(i64(vb)), nil + case i64: + return va, f64(vb), nil + case f64: + return va, vb, nil + case string: + err = .Invalid_Argument_Type + return + } + case string: + switch vb in b { + case bool, i64, f64: + err = .Invalid_Argument_Type + return + case string: + return va, vb, nil + } + } + + err = .Invalid_Argument_Type + return +} + +eval_reduce :: proc(s: ^State, dot: any, op: Operator, args: []^parse.Node) -> (value: Value, err: Error) { + if len(args) < 1 { + return nil, .Invalid_Argument_Count + } + + acc := get_value(eval_arg(s, dot, args[0]) or_return) + if acc == nil { + return nil, .Invalid_Argument_Type + } + + + if len(args) == 1 { + if op == .Add { + return acc, nil + } + + switch v in acc { + case bool, string: + // none + case i64: + if op == .Sub { + return -v, nil + } + case f64: + if op == .Sub { + return -v, nil + } + } + return nil, .Invalid_Argument_Type + } + + for arg in args[1:] { + elem := eval_arg(s, dot, arg) or_return + lhs, rhs := promote_values(acc, elem) or_return + + switch x in lhs { + case bool: + y := rhs.(bool) + switch op { + case .Add: + acc = i64(x) + i64(y) + case .Sub: + acc = i64(x) - i64(y) + case .Mul: + acc = i64(x) * i64(y) + case .Div: + if !y { + err = .Division_by_Zero + return + } + acc = f64(i64(x)) / f64(i64(y)) + } + case i64: + y := rhs.(i64) + switch op { + case .Add: + acc = x + y + case .Sub: + acc = x - y + case .Mul: + acc = x * y + case .Div: + if y == 0 { + err = .Division_by_Zero + return + } + acc = f64(x) / f64(y) + } + case f64: + y := rhs.(f64) + switch op { + case .Add: + acc = x + y + case .Sub: + acc = x - y + case .Mul: + acc = x * y + case .Div: + if y == 0 { + err = .Division_by_Zero + return + } + acc = x / y + } + case string: + y := rhs.(string) + if op != .Add { + err = .Invalid_Argument_Type + return + } + acc = strings.concatenate({x, y}) + } + + } + + switch v in acc { + case bool: return v, nil + case i64: return v, nil + case f64: return v, nil + case string: return v, nil + } + return nil, .Invalid_Argument_Type +} + eval_function :: proc(s: ^State, dot: any, name: string, cmd: ^parse.Node_Command, final: any) -> (value: any, err: Error) { cmd_args := cmd.args[1:] @@ -580,16 +776,28 @@ eval_function :: proc(s: ^State, dot: any, name: string, cmd: ^parse.Node_Comman error(s, "%q expects at least 1 argument", name) return nil, .Invalid_Argument_Count } - // TODO - return nil, nil - case "*": + res := eval_reduce(s, dot, .Add if name == "+" else .Sub, cmd_args) or_return + switch v in res { + case bool: return new_any(v), nil + case i64: return new_any(v), nil + case f64: return new_any(v), nil + case string: return new_any(v), nil + } + return nil, .Invalid_Argument_Type + case "*", "/": if len(cmd_args) < 1 { error(s, "%q expects at least 2 arguments, got %d", name, len(cmd_args)) return nil, .Invalid_Argument_Count } - // TODO - return nil, nil + res := eval_reduce(s, dot, .Mul if name == "*" else .Div, cmd_args) or_return + switch v in res { + case bool: return new_any(v), nil + case i64: return new_any(v), nil + case f64: return new_any(v), nil + case string: return new_any(v), nil + } + return nil, .Invalid_Argument_Type } function, ok := builtin_funcs[name] diff --git a/core/text/template/scan/scan.odin b/core/text/template/scan/scan.odin index 375e5ef5e..a0af3c1d0 100644 --- a/core/text/template/scan/scan.odin +++ b/core/text/template/scan/scan.odin @@ -495,7 +495,7 @@ step :: proc(s: ^Scanner, state: Scan_State) -> Scan_State { case '0' <= r && r <= '9': backup(s) return .Number - case r == '+', r == '-', r == '*': + case r == '+', r == '-', r == '*', r == '/': emit(s, .Operator) case is_alpha_numeric(r): backup(s) -- cgit v1.2.3