mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 01:14:40 +00:00
878 lines
19 KiB
Odin
878 lines
19 KiB
Odin
package text_template
|
|
|
|
import "core:io"
|
|
import "core:reflect"
|
|
import "core:strings"
|
|
import "core:mem"
|
|
import "core:mem/virtual"
|
|
import "core:fmt"
|
|
|
|
import parse "parse"
|
|
|
|
Template :: struct {
|
|
tree: ^parse.Tree,
|
|
|
|
using config: Config,
|
|
}
|
|
|
|
Config :: struct {
|
|
flags: Flags,
|
|
allocator: mem.Allocator,
|
|
left_delim: string,
|
|
right_delim: string,
|
|
}
|
|
|
|
Flags :: distinct bit_set[Flag; u64]
|
|
Flag :: enum u64 {
|
|
Emit_Comments,
|
|
}
|
|
|
|
Parse_Error :: parse.Error
|
|
Execution_Error :: enum {
|
|
Accumulated_Errors,
|
|
|
|
Invalid_Node,
|
|
Invalid_Command,
|
|
Invalid_Value,
|
|
|
|
Undeclared_Variable,
|
|
Undefined_Function,
|
|
|
|
Invalid_Break,
|
|
Invalid_Continue,
|
|
|
|
Invalid_Argument_Count,
|
|
Invalid_Argument_Type,
|
|
Out_Of_Bounds_Access,
|
|
Division_by_Zero,
|
|
}
|
|
Error :: union {
|
|
io.Error,
|
|
Execution_Error,
|
|
}
|
|
|
|
create_from_string :: proc(input: string, config: Maybe(Config) = nil) -> (t: ^Template, err: Parse_Error) {
|
|
cfg, _ := config.?
|
|
if cfg.allocator.procedure == nil {
|
|
cfg.allocator = context.allocator
|
|
}
|
|
|
|
t = new(Template, cfg.allocator)
|
|
t.config = cfg
|
|
t.tree = nil
|
|
t.tree, err = parse.parse(input, t.left_delim, t.right_delim, .Emit_Comments in t.flags, t.allocator)
|
|
return
|
|
}
|
|
|
|
must :: proc(t: ^Template, err: Parse_Error) -> ^Template {
|
|
assert(t != nil)
|
|
assert(err == nil)
|
|
return t
|
|
}
|
|
|
|
destroy :: proc(t: ^Template) {
|
|
allocator := t.allocator
|
|
parse.destroy_tree(t.tree)
|
|
free(t, allocator)
|
|
}
|
|
|
|
execute :: proc(t: ^Template, w: io.Writer, data: any) -> Error {
|
|
s := State{
|
|
tmpl = t,
|
|
w = w,
|
|
}
|
|
context.allocator = virtual.arena_allocator(&s.arena)
|
|
defer free_all(context.allocator)
|
|
|
|
err := walk(&s, data,t.tree.root)
|
|
if err == nil && s.error_count > 0 {
|
|
err = .Accumulated_Errors
|
|
}
|
|
return err
|
|
}
|
|
|
|
|
|
State :: struct {
|
|
arena: virtual.Growing_Arena,
|
|
|
|
tmpl: ^Template,
|
|
w: io.Writer,
|
|
at: ^parse.Node,
|
|
vars: [dynamic]Variable,
|
|
depth: int,
|
|
error_count: int,
|
|
}
|
|
Variable :: struct {
|
|
name: string,
|
|
value: any,
|
|
}
|
|
|
|
walk :: proc(s: ^State, dot: any, node: ^parse.Node) -> Error {
|
|
s.at = node
|
|
|
|
switch n in node.variant {
|
|
case ^parse.Node_Comment:
|
|
// ignore
|
|
case ^parse.Node_Text:
|
|
io.write_string(s.w, n.text) or_return
|
|
return nil
|
|
case ^parse.Node_List:
|
|
for elem in n.nodes {
|
|
walk(s, dot, elem) or_return
|
|
}
|
|
return nil
|
|
case ^parse.Node_Action:
|
|
val := eval_pipeline(s, dot, n.pipe) or_return
|
|
if len(n.pipe.decl) == 0 {
|
|
print_value(s, n, val)
|
|
}
|
|
return nil
|
|
case ^parse.Node_Pipeline:
|
|
print_value(s, n, eval_pipeline(s, dot, n) or_return)
|
|
return nil
|
|
|
|
case ^parse.Node_Import:
|
|
panic("TODO Node_Import")
|
|
|
|
case ^parse.Node_If:
|
|
return walk_if_or_with(s, .If, dot, n.pipe, n.list, n.else_list)
|
|
case ^parse.Node_With:
|
|
return walk_if_or_with(s, .With, dot, n.pipe, n.list, n.else_list)
|
|
case ^parse.Node_For:
|
|
return walk_for(s, dot, n)
|
|
|
|
case ^parse.Node_Break:
|
|
return .Invalid_Break
|
|
case ^parse.Node_Continue:
|
|
return .Invalid_Continue
|
|
|
|
case ^parse.Node_Else,
|
|
^parse.Node_End,
|
|
^parse.Node_Nil,
|
|
^parse.Node_Bool,
|
|
^parse.Node_Number,
|
|
^parse.Node_String,
|
|
^parse.Node_Variable,
|
|
^parse.Node_Identifier,
|
|
^parse.Node_Operator,
|
|
^parse.Node_Dot,
|
|
^parse.Node_Field:
|
|
return .Invalid_Node
|
|
|
|
case ^parse.Node_Command:
|
|
return .Invalid_Node
|
|
case ^parse.Node_Chain:
|
|
return .Invalid_Node
|
|
}
|
|
|
|
return .Invalid_Node
|
|
}
|
|
|
|
mark_vars :: proc(s: ^State) -> int {
|
|
return len(s.vars)
|
|
}
|
|
pop_vars :: proc(s: ^State, n: int) {
|
|
resize(&s.vars, n)
|
|
}
|
|
|
|
@(private, deferred_in_out=pop_vars)
|
|
SCOPE :: proc(s: ^State) -> int {
|
|
return mark_vars(s)
|
|
}
|
|
walk_if_or_with :: proc(s: ^State, kind: enum{If, With}, dot: any,
|
|
pipe: ^parse.Node_Pipeline,
|
|
list: ^parse.Node_List,
|
|
else_list: ^parse.Node_List) -> Error {
|
|
SCOPE(s)
|
|
|
|
val := eval_pipeline(s, dot, pipe) or_return
|
|
truth, ok := is_true(val)
|
|
if !ok {
|
|
error(s, "if/with cannot use %v", val)
|
|
}
|
|
if truth {
|
|
if kind == .With {
|
|
return walk(s, val, list)
|
|
} else {
|
|
return walk(s, dot, list)
|
|
}
|
|
} else if else_list != nil {
|
|
return walk(s, dot, else_list)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
walk_for :: proc(s: ^State, dot: any, f: ^parse.Node_For) -> Error {
|
|
s.at = f
|
|
SCOPE(s)
|
|
|
|
val, _ := indirect(eval_pipeline(s, dot, f.pipe) or_return)
|
|
|
|
mark := mark_vars(s)
|
|
|
|
the_body :: proc(s: ^State, f: ^parse.Node_For, elem, index: any, mark: int) -> Error {
|
|
if len(f.pipe.decl) > 0 {
|
|
set_top_var(s, 1, index) or_return
|
|
}
|
|
if len(f.pipe.decl) > 1 {
|
|
set_top_var(s, 2, elem) or_return
|
|
}
|
|
defer pop_vars(s, mark)
|
|
return walk(s, elem, f.list)
|
|
}
|
|
|
|
original_id := val.id
|
|
ti := reflect.type_info_base(type_info_of(val.id))
|
|
val.id = ti.id
|
|
#partial switch info in ti.variant {
|
|
case reflect.Type_Info_Array,
|
|
reflect.Type_Info_Slice,
|
|
reflect.Type_Info_Dynamic_Array:
|
|
n := reflect.length(val)
|
|
if n == 0 {
|
|
break
|
|
}
|
|
for i in 0..<n {
|
|
the_body(s, f, reflect.index(val, i), i, mark) or_return
|
|
}
|
|
return nil
|
|
case reflect.Type_Info_Map:
|
|
if reflect.length(val) == 0 {
|
|
break
|
|
}
|
|
|
|
gs := reflect.type_info_base(info.generated_struct).variant.(reflect.Type_Info_Struct)
|
|
ed := reflect.type_info_base(gs.types[1]).variant.(reflect.Type_Info_Dynamic_Array)
|
|
entry_type := ed.elem.variant.(reflect.Type_Info_Struct)
|
|
key_offset := entry_type.offsets[2]
|
|
value_offset := entry_type.offsets[3]
|
|
entry_size := uintptr(ed.elem_size)
|
|
key_type := entry_type.types[2]
|
|
value_type := entry_type.types[3]
|
|
|
|
rm := (^mem.Raw_Map)(val.data)
|
|
|
|
data := uintptr(rm.entries.data)
|
|
for i in 0..<rm.entries.len {
|
|
key := any{rawptr(data + key_offset), key_type.id}
|
|
value := any{rawptr(data + value_offset), value_type.id}
|
|
|
|
the_body(s, f, key, value, mark) or_return
|
|
|
|
data += entry_size
|
|
}
|
|
return nil
|
|
|
|
// TODO
|
|
case:
|
|
error(s, "for cannot iterate over %v", original_id)
|
|
}
|
|
|
|
if f.else_list != nil {
|
|
return walk(s, dot, f.else_list)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
indirect :: proc(val: any) -> (v: any, is_nil: bool) {
|
|
v = val
|
|
for v != nil {
|
|
ti := reflect.type_info_base(type_info_of(v.id))
|
|
info, ok := ti.variant.(reflect.Type_Info_Pointer)
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
ptr := (^rawptr)(v.data)^
|
|
if ptr == nil {
|
|
return v, true
|
|
}
|
|
v = any{ptr, info.elem.id}
|
|
}
|
|
return v, false
|
|
}
|
|
|
|
|
|
error :: proc(s: ^State, format: string, args: ..any) {
|
|
s.error_count += 1
|
|
|
|
assert(s.at != nil)
|
|
|
|
// NOTE(bill): the line and column are recalculated
|
|
// each time here because errors are usually an early
|
|
// out for this execution system
|
|
|
|
pos := int(s.at.pos)
|
|
text := s.tmpl.tree.input[:pos]
|
|
col := strings.last_index(text, "\n")
|
|
if col < 0 {
|
|
col = pos
|
|
} else {
|
|
col += 1
|
|
col = pos - col
|
|
}
|
|
|
|
line := 1 + strings.count(text, "\n")
|
|
|
|
name := s.tmpl.tree.name
|
|
if name == "" {
|
|
name = "<input>"
|
|
}
|
|
fmt.eprintf("%s:%d:%d: ", name, line, col)
|
|
fmt.eprintf(format, ..args)
|
|
fmt.eprintln()
|
|
}
|
|
|
|
is_true :: proc(val: any) -> (truth, ok: bool) {
|
|
check_trivial :: proc(v: any) -> (bool, bool) {
|
|
data := reflect.as_bytes(v)
|
|
for v in data {
|
|
if v != 0 {
|
|
return true, true
|
|
}
|
|
}
|
|
return false, true
|
|
}
|
|
|
|
if val == nil {
|
|
return false, true
|
|
}
|
|
|
|
ti := reflect.type_info_base(type_info_of(val.id))
|
|
switch v in ti.variant {
|
|
case reflect.Type_Info_Named:
|
|
unreachable()
|
|
case reflect.Type_Info_Integer,
|
|
reflect.Type_Info_Rune,
|
|
reflect.Type_Info_Float,
|
|
reflect.Type_Info_Complex,
|
|
reflect.Type_Info_Quaternion,
|
|
reflect.Type_Info_Boolean,
|
|
reflect.Type_Info_Bit_Set,
|
|
reflect.Type_Info_Enum:
|
|
return check_trivial(val)
|
|
|
|
case reflect.Type_Info_String:
|
|
if v.is_cstring {
|
|
cstr := (^cstring)(val.data)^
|
|
if cstr == nil {
|
|
return false, true
|
|
}
|
|
return ([^]u8)(cstr)[0] != 0, true
|
|
}
|
|
str := (^string)(val.data)^
|
|
return len(str) > 0, true
|
|
|
|
case reflect.Type_Info_Any:
|
|
return false, false
|
|
case reflect.Type_Info_Type_Id:
|
|
return (^typeid)(val.data)^ != nil, true
|
|
case reflect.Type_Info_Pointer,
|
|
reflect.Type_Info_Multi_Pointer:
|
|
return (^rawptr)(val.data)^ != nil, true
|
|
|
|
case reflect.Type_Info_Procedure:
|
|
return
|
|
|
|
case reflect.Type_Info_Array:
|
|
return v.count > 0, true
|
|
case reflect.Type_Info_Enumerated_Array:
|
|
return v.count > 0, true
|
|
case reflect.Type_Info_Dynamic_Array:
|
|
a := (^mem.Raw_Dynamic_Array)(val.data)
|
|
return a.len > 0, true
|
|
case reflect.Type_Info_Slice:
|
|
a := (^mem.Raw_Slice)(val.data)
|
|
return a.len > 0, true
|
|
case reflect.Type_Info_Tuple:
|
|
return
|
|
case reflect.Type_Info_Struct:
|
|
// All structs are always non nil
|
|
return true, true
|
|
case reflect.Type_Info_Union:
|
|
return reflect.union_variant_typeid(val) != nil, true
|
|
case reflect.Type_Info_Map:
|
|
m := (^mem.Raw_Map)(val.data)
|
|
return m.entries.len > 0, true
|
|
case reflect.Type_Info_Simd_Vector:
|
|
return v.count > 0, true
|
|
case reflect.Type_Info_Relative_Pointer:
|
|
return check_trivial(val)
|
|
case reflect.Type_Info_Relative_Slice:
|
|
return check_trivial(val)
|
|
case reflect.Type_Info_Matrix:
|
|
return check_trivial(val)
|
|
}
|
|
return
|
|
}
|
|
|
|
|
|
eval_pipeline :: proc(s: ^State, dot: any, pipe: ^parse.Node_Pipeline) -> (value: any, err: Error) {
|
|
if pipe == nil {
|
|
return
|
|
}
|
|
s.at = pipe
|
|
value = nil
|
|
for cmd in pipe.cmds {
|
|
value = eval_command(s, dot, cmd, value) or_return
|
|
}
|
|
for var in pipe.decl {
|
|
if pipe.is_assign {
|
|
set_var(s, var.name, value) or_return
|
|
} else {
|
|
push_var(s, var.name, value)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
set_var :: proc(s: ^State, name: string, value: any) -> Error {
|
|
for i := mark_vars(s)-1; i >= 0; i -= 1 {
|
|
if s.vars[i].name == name {
|
|
s.vars[i].value = value
|
|
return nil
|
|
}
|
|
}
|
|
return .Undeclared_Variable
|
|
}
|
|
|
|
set_top_var :: proc(s: ^State, n: int, value: any) -> Error {
|
|
if len(s.vars) > 0 {
|
|
s.vars[len(s.vars)-n].value = value
|
|
return nil
|
|
}
|
|
return .Undeclared_Variable
|
|
}
|
|
|
|
push_var :: proc(s: ^State, name: string, value: any) {
|
|
append(&s.vars, Variable{name, value})
|
|
}
|
|
get_var :: proc(s: ^State, name: string) -> (value: any, err: Error) {
|
|
for i := mark_vars(s)-1; i >= 0; i -= 1 {
|
|
if s.vars[i].name == name {
|
|
return s.vars[i].value, nil
|
|
}
|
|
}
|
|
error(s, "undeclared variable $%s", name)
|
|
return nil, .Undeclared_Variable
|
|
|
|
}
|
|
|
|
eval_command :: proc(s: ^State, dot: any, cmd: ^parse.Node_Command, final: any) -> (value: any, err: Error) {
|
|
first_word := cmd.args[0]
|
|
#partial switch n in first_word.variant {
|
|
case ^parse.Node_Field:
|
|
s.at = n
|
|
return eval_fields(s, dot, n.idents)
|
|
case ^parse.Node_Chain:
|
|
return eval_chain(s, dot, n)
|
|
case ^parse.Node_Identifier:
|
|
return eval_function(s, dot, n.ident, cmd, final)
|
|
case ^parse.Node_Operator:
|
|
return eval_function(s, dot, n.value, cmd, final)
|
|
case ^parse.Node_Pipeline:
|
|
return eval_pipeline(s, dot, n)
|
|
case ^parse.Node_Variable:
|
|
s.at = n
|
|
return get_var(s, n.name)
|
|
}
|
|
s.at = first_word
|
|
#partial switch n in first_word.variant {
|
|
case ^parse.Node_Bool:
|
|
return new_any(n.ok), nil
|
|
case ^parse.Node_Dot:
|
|
return dot, nil
|
|
case ^parse.Node_Nil:
|
|
return nil, nil
|
|
case ^parse.Node_Number:
|
|
if i, ok := n.i.?; ok {
|
|
return new_any(i), nil
|
|
}
|
|
if u, ok := n.u.?; ok {
|
|
return new_any(u), nil
|
|
}
|
|
if f, ok := n.f.?; ok {
|
|
return new_any(f), nil
|
|
}
|
|
return eval_function(s, dot, n.text, cmd, final)
|
|
case ^parse.Node_String:
|
|
return new_any(n.text), nil
|
|
}
|
|
|
|
error(s, "cannot evaluate command %v", first_word.variant)
|
|
return nil, .Invalid_Command
|
|
}
|
|
|
|
eval_chain :: proc(s: ^State, dot: any, chain: ^parse.Node_Chain) -> (value: any, err: Error) {
|
|
s.at = chain
|
|
return eval_fields(s, eval_arg(s, dot, chain.node) or_return, chain.fields[:])
|
|
}
|
|
|
|
eval_fields :: proc(s: ^State, dot: any, idents: []string) -> (value: any, err: Error) {
|
|
value = dot
|
|
for ident in idents {
|
|
value = eval_field(s, value, ident) or_return
|
|
}
|
|
return
|
|
}
|
|
|
|
eval_field :: proc(s: ^State, dot: any, ident: string) -> (value: any, err: Error) {
|
|
if dot == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
ti := reflect.type_info_base(type_info_of(dot.id))
|
|
#partial switch info in ti.variant {
|
|
case reflect.Type_Info_Struct:
|
|
value = reflect.struct_field_value_by_name(dot, ident, true)
|
|
if value != nil {
|
|
return
|
|
}
|
|
case reflect.Type_Info_Pointer:
|
|
if dot.data != nil {
|
|
deref := (^rawptr)(dot.data)^
|
|
return eval_field(s, {deref, info.elem.id}, ident)
|
|
}
|
|
case reflect.Type_Info_Map:
|
|
key_type := reflect.type_info_base(info.key)
|
|
switch key_type.id {
|
|
case typeid_of(string), typeid_of(cstring):
|
|
gs := reflect.type_info_base(info.generated_struct).variant.(reflect.Type_Info_Struct)
|
|
ed := reflect.type_info_base(gs.types[1]).variant.(reflect.Type_Info_Dynamic_Array)
|
|
entry_type := ed.elem.variant.(reflect.Type_Info_Struct)
|
|
key_offset := entry_type.offsets[2]
|
|
value_offset := entry_type.offsets[3]
|
|
entry_size := uintptr(ed.elem_size)
|
|
|
|
rm := (^mem.Raw_Map)(dot.data)
|
|
|
|
data := uintptr(rm.entries.data)
|
|
for _ in 0..<rm.entries.len {
|
|
key: string
|
|
switch key_type.id {
|
|
case typeid_of(string):
|
|
key = (^string)(data + key_offset)^
|
|
case typeid_of(cstring):
|
|
key = string((^cstring)(data + key_offset)^)
|
|
}
|
|
|
|
if key == ident {
|
|
ptr := rawptr(data + value_offset)
|
|
return any{ptr, entry_type.types[3].id}, nil
|
|
}
|
|
|
|
data += entry_size
|
|
}
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
|
|
error(s, "cannot evaluate field %s in type %v", ident, dot.id)
|
|
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:]
|
|
|
|
switch name {
|
|
case "+", "-":
|
|
if len(cmd_args) < 1 {
|
|
error(s, "%q expects at least 1 argument", name)
|
|
return nil, .Invalid_Argument_Count
|
|
}
|
|
|
|
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
|
|
}
|
|
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]
|
|
if !ok {
|
|
error(s, "%q is not a defined function", name)
|
|
err = .Undefined_Function
|
|
return
|
|
}
|
|
|
|
if function == nil {
|
|
switch name {
|
|
case "and":
|
|
// TODO
|
|
case "or":
|
|
// TODO
|
|
case:
|
|
panic("unhandled built-in procedure")
|
|
}
|
|
}
|
|
|
|
n := len(cmd_args)
|
|
if final != nil {
|
|
n += 1
|
|
}
|
|
args_to_call := make([dynamic]any, 0, n)
|
|
for arg in cmd_args {
|
|
append(&args_to_call, eval_arg(s, dot, arg) or_return)
|
|
}
|
|
if final != nil {
|
|
append(&args_to_call, final)
|
|
}
|
|
|
|
return function(args_to_call[:])
|
|
}
|
|
|
|
eval_arg :: proc(s: ^State, dot: any, arg: ^parse.Node) -> (value: any, err: Error) {
|
|
s.at = arg
|
|
#partial switch n in arg.variant {
|
|
case ^parse.Node_Dot:
|
|
return dot, nil
|
|
case ^parse.Node_Nil:
|
|
return nil, nil
|
|
case ^parse.Node_Bool:
|
|
return new_any(n.ok), nil
|
|
case ^parse.Node_Number:
|
|
if i, ok := n.i.?; ok {
|
|
return new_any(i), nil
|
|
}
|
|
if u, ok := n.u.?; ok {
|
|
return new_any(u), nil
|
|
}
|
|
if f, ok := n.f.?; ok {
|
|
return new_any(f), nil
|
|
}
|
|
case ^parse.Node_String:
|
|
return new_any(n.text), nil
|
|
|
|
case ^parse.Node_Field:
|
|
return eval_fields(s, dot, n.idents)
|
|
case ^parse.Node_Variable:
|
|
return get_var(s, n.name)
|
|
case ^parse.Node_Pipeline:
|
|
return eval_pipeline(s, dot, n)
|
|
case ^parse.Node_Chain:
|
|
return eval_chain(s, dot, n)
|
|
}
|
|
return nil, .Invalid_Node
|
|
}
|
|
|
|
|
|
print_value :: proc(s: ^State, n: ^parse.Node, val: any) {
|
|
s.at = n
|
|
if val == nil {
|
|
io.write_string(s.w, "nil")
|
|
} else {
|
|
fmt.wprint(s.w, val)
|
|
}
|
|
} |