aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2022-03-03 18:03:23 +0000
committergingerBill <bill@gingerbill.org>2022-03-03 18:03:23 +0000
commit52bb8524b10d39ead50de0f50d3b762a90e7c33a (patch)
tree23ac45b28e0e83eecb93dce5282704c392054042
parent11b08b4d861b8e6407730eafb5f245948c46c262 (diff)
Add basic reduce evaluation e.g. `+ 1 2 3`, `- 2 3`, `* 3 4`, `/ 3 4`
-rw-r--r--core/text/template/execute.odin218
-rw-r--r--core/text/template/scan/scan.odin2
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)