From ee25688b424090253aa61e63617ea4bace944ebd Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Sun, 1 Jun 2025 22:19:31 -0600 Subject: [PATCH 01/18] fix memory, add vm --- memory/module.jai | 4 +- vm/module.jai | 7 ++ vm/parser.jai | 297 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 vm/module.jai create mode 100644 vm/parser.jai diff --git a/memory/module.jai b/memory/module.jai index f3af797..576f955 100644 --- a/memory/module.jai +++ b/memory/module.jai @@ -63,12 +63,12 @@ align_to :: (ptr: int, align: int = Default_Align) -> int { } init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) { - init :: initializer_of(T); if custom_init != null { custom_init(ptr); } - #if init != null { + init :: initializer_of(T); + if init != null { inline init(ptr); } else { diff --git a/vm/module.jai b/vm/module.jai new file mode 100644 index 0000000..ee6c130 --- /dev/null +++ b/vm/module.jai @@ -0,0 +1,7 @@ +#module_parameters(RUN_TESTS := false); + +#scope_file; + +#if RUN_TESTS { + test :: #import "jc/test"; +} diff --git a/vm/parser.jai b/vm/parser.jai new file mode 100644 index 0000000..74164fb --- /dev/null +++ b/vm/parser.jai @@ -0,0 +1,297 @@ +Token :: struct { + kind: Kind; + str: string; + + Kind :: enum { + invalid; + end_of_file; + + symbol; + number; + string; + + kw_var; + kw_def; + kw_type; + kw_do; + kw_end; + + equal :: #char "="; + plus :: #char "+"; + minus :: #char "-"; + star :: #char "*"; + f_slash :: #char "/"; + b_slash :: #char "\\"; + + l_paren :: #char "("; + r_paren :: #char ")"; + l_square :: #char "["; + r_square :: #char "]"; + l_brace :: #char "{"; + r_brace :: #char "}"; + comma :: #char ","; + dot :: #char "."; + colon :: #char ":"; + semicolon :: #char ";"; + } +} + +Node :: struct { + kind: Kind; + + Kind :: enum { + invalid; + + decl_start; + var; + decl_end; + + expr_start; + symbol; + literal; + expr_end; + } +} + +Node_Var :: struct { + #as using n: Node; + n.kind = .var; + + symbol: *Node; + type_expr: *Node; + value_expr: *Node; +} + +Node_Symbol :: struct { + #as using n: Node; + n.kind = .symbol; + + str: string; +} + +Node_Literal :: struct { + #as using n: Node; + n.kind = .literal; + + union { + } +} + +Parser :: struct { + allocator: Allocator; + toplevel: [..]*Node; + + previous: Token; + filename: string; + source: string; + offset: int; +} + +init :: (p: *Parser, allocator: Allocator) { + p.allocator = allocator; + p.toplevel.allocator = allocator; +} + +parse_string :: (p: *Parser, source: string) -> bool { + p.source = source; + p.offset = 0; + + while !at_end(p) { + t := peek_token(p); + if t.kind == .invalid || t.kind == .end_of_file { + break; + } + + node := parse_toplevel_declaration(p); + if node == null break; + + array.append(*p.toplevel, node); + } + + return false; +} + + +#scope_file; + +parse_toplevel_declaration :: (p: *Parser) -> *Node { + t, ok := expect_token(p, .kw_var); + if ok == false return null; + + s:, ok = expect_token(p, .symbol); + if ok == false return null; + + type_expr: *Node; + value_expr: *Node; + + // var sym int + // var sym int = value + // var sym = value + + t = peek_token(p); + if t.kind == .equal { + consume_token(p); + value_expr = parse_expression(p); + } + else { + type_expr = parse_type_expression(p); + if type_expr == null return null; + + if peek_token(p).kind == .equal { + consume_token(p); + value_expr = parse_expression(p); + } + } + + symbol := make_node(p, Node_Symbol); + symbol.str = s.str; + + node := make_node(p, Node_Var); + node.symbol = symbol; + node.type_expr = type_expr; + node.value_expr = value_expr; + + return node; +} + +parse_type_expression :: (p: *Parser) -> *Node { + t, ok := expect_token(p, .symbol, .star); + if ok == false return null; + + if t.kind == { + case .symbol; + node := make_node(p, Node_Symbol); + node.str = t.str; + return node; + } + + return null; +} + +parse_expression :: (p: *Parser) -> *Node { + return null; +} + +make_node :: (p: *Parser, $T: Type) -> *T { + return mem.request_memory(T,, allocator = p.allocator); +} + +peek_token :: (p: *Parser) -> Token { + copy := p.*; + return consume_token(*copy); +} + +at_end :: (p: *Parser) -> bool { + return p.offset >= p.source.count; +} + +starts_symbol :: (c: u8) -> bool { + return (c >= "a" && c <= "z") || + (c >= "A" && c <= "Z") || + (c == "_"); +} +continues_symbol :: (c: u8) -> bool { + return starts_symbol(c) || (c >= "0" && c <= "9"); +} + +starts_number :: (c: u8) -> bool { + return (c >= "0" && c <= "9"); +} +continues_number :: (c: u8) -> bool { + return starts_number(c) || c == "."; +} + +consume_token :: (p: *Parser) -> Token { + if at_end(p) return .{ kind = .end_of_file }; + + c := p.source[p.offset]; + while !at_end(p) { + c = p.source[p.offset]; + if c == { + case " "; #through; + case "\n"; #through; + case "\t"; + p.offset += 1; + case; + break; + } + } + + if starts_symbol(c) { + t := Token.{ str = .{ data = p.source.data + p.offset } }; + while !at_end(p) { + c = p.source[p.offset]; + if !continues_symbol(c) break; + p.offset += 1; + } + + t.str.count = (p.source.data + p.offset) - t.str.data; + if t.str == { + case "var"; t.kind = .kw_var; + case "def"; t.kind = .kw_def; + case "do"; t.kind = .kw_do; + case "end"; t.kind = .kw_end; + case "type"; t.kind = .kw_type; + case; t.kind = .symbol; + } + + return t; + } + + if starts_number(c) { + t := Token.{ kind = .number, str = .{ data = p.source.data + p.offset } }; + while !at_end(p) { + c = p.source[p.offset]; + if !continues_number(c) break; + p.offset += 1; + } + + t.str.count = (p.source.data + p.offset) - t.str.data; + return t; + } + + if c == { + case "+"; #through; + case "-"; #through; + case "*"; #through; + case "/"; #through; + case "="; #through; + case "("; #through; + case ")"; #through; + case "["; #through; + case "]"; #through; + case "{"; #through; + case "}"; + s := string.{ data = p.source.data + p.offset, count = 1 }; + p.offset += 1; + return .{ kind = xx c, str = s }; + } + + s := string.{ data = p.source.data + p.offset, count = 1 }; + return .{ kind = .invalid, str = s }; +} + +expect_token :: (p: *Parser, kinds: ..Token.Kind) -> Token, bool { + t := consume_token(p); + for kinds if it == t.kind { + return t, true; + } + + return t, false; +} + +#run { + parser: Parser; + init(*parser, context.allocator); + + ok := parse_string(*parser, #string END + var x = 10 + var y = 20 + END); +} + +mem :: #import "jc/memory"; +array :: #import "jc/array"; + +basic :: #import "Basic"; // @future +strings :: #import "String"; // @future From f5b0a248ab3505e79f84cc13a2154f695a6786ac Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 13:32:13 -0600 Subject: [PATCH 02/18] fix memory and dynamic array --- array/dynamic_array.jai | 6 +++--- memory/module.jai | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/array/dynamic_array.jai b/array/dynamic_array.jai index ca2e780..2dc223b 100644 --- a/array/dynamic_array.jai +++ b/array/dynamic_array.jai @@ -20,9 +20,9 @@ append :: inline (arr: *[..]$T) -> *T { return basic.array_add(arr,, allocator = arr.allocator); } -resize :: inline (arr: *[..]$T, new_size: int) { - if new_size <= arr.allocated return; - basic.array_reserve(arr, new_size,, allocator = arr.allocator); +resize :: inline (arr: *[..]$T, new_count: int) { + if new_count <= arr.allocated return; + basic.array_reserve(arr, new_count,, allocator = arr.allocator); } remove_ordered :: inline (arr: *[..]$T, index: int, loc := #caller_location) #no_abc { diff --git a/memory/module.jai b/memory/module.jai index 576f955..6808174 100644 --- a/memory/module.jai +++ b/memory/module.jai @@ -67,9 +67,9 @@ init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) { custom_init(ptr); } - init :: initializer_of(T); - if init != null { - inline init(ptr); + initializer :: initializer_of(T); + #if initializer { + inline initializer(ptr); } else { memset(ptr, 0, size_of(T)); From f954a8276d4796500b6748481c3b83b1c485932a Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 19:21:18 -0600 Subject: [PATCH 03/18] improve kv --- kv/module.jai | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/kv/module.jai b/kv/module.jai index 9dcea6d..d0a8b72 100644 --- a/kv/module.jai +++ b/kv/module.jai @@ -25,7 +25,7 @@ init :: (kv: *Kv, allocator: Allocator) { } get :: (kv: *Kv, key: kv.Key) -> kv.Value, bool { - slot, ok := find_slot(kv, kv.hash_proc(key)); + slot, ok := find_slot(kv, get_hash(kv, key)); if !ok { return mem.zero_of(kv.Value), false; } @@ -34,7 +34,7 @@ get :: (kv: *Kv, key: kv.Key) -> kv.Value, bool { } set :: (kv: *Kv, key: kv.Key, value: kv.Value) { - hash := kv.hash_proc(key); + hash := get_hash(kv, key); slot, exists := find_slot(kv, hash); if !exists { slot = create_or_reuse_slot(kv); @@ -45,9 +45,14 @@ set :: (kv: *Kv, key: kv.Key, value: kv.Value) { slot.value = value; } +exists :: (kv: *Kv, key: kv.Key) -> bool { + _, exists := find_slot(kv, get_hash(kv, key)); + return exists; +} + // @note(judah): we use 'delete' instead of 'remove' because it's a keyword... delete :: (kv: *Kv, key: kv.Key) -> kv.Value, bool { - slot, ok, idx := find_slot(kv, kv.hash_proc(key)); + slot, ok, idx := find_slot(kv, get_hash(kv, key)); if !ok return mem.zero_of(kv.Value), false; last_value := slot.value; @@ -74,6 +79,12 @@ for_expansion :: (kv: *Kv, body: Code, flags: For_Flags) #expand { #scope_file; +get_hash :: inline (kv: *Kv, key: kv.Key) -> u32 { + hash := kv.hash_proc(key); + basic.assert(hash != kv.invalid_hash, "key % collided with invalid hash marker (%)", key, kv.invalid_hash); + return hash; +} + find_slot :: (kv: *Kv, hash: u32) -> *kv.Slot, bool, int { for * kv.slots if it.hash == hash { return it, true, it_index; @@ -122,6 +133,8 @@ mem :: #import "jc/memory"; array :: #import "jc/array"; hash :: #import "jc/hash"; +basic :: #import "Basic"; // @future + // ---------------------------------------------------------- // TESTS From 5ed453a0fcc9204b07e7118f8383919255073d8e Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 19:21:34 -0600 Subject: [PATCH 04/18] expression parsing and basic tree-walk interpreter --- vm/interp.jai | 181 +++++++++++++++++++++ vm/module.jai | 37 ++++- vm/parser.jai | 443 +++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 585 insertions(+), 76 deletions(-) create mode 100644 vm/interp.jai diff --git a/vm/interp.jai b/vm/interp.jai new file mode 100644 index 0000000..b41a9de --- /dev/null +++ b/vm/interp.jai @@ -0,0 +1,181 @@ +Interp :: struct { + allocator: Allocator; + + symbols: kv.Kv(string, *Interp_Value); + toplevel: []*Node; + // stack: [..]*Interp_Value; +} + +Interp_Value :: struct { + kind: Kind; + union { + b: bool; + i: s64; + u: u64; + f: float64; + s: string; + } + + Kind :: enum { + none; + nil; + bool; + int; + float; + string; + } +} + +init :: (i: *Interp, allocator: Allocator) { + // i.stack.allocator = allocator; + + value_nil = make_interp_value(i, .nil); + + value_true = make_interp_value(i, .bool); + value_true.b = true; + + value_false = make_interp_value(i, .bool); + value_false.b = false; +} + +interp_program :: (i: *Interp) { + for i.toplevel if it.kind == { + case .variable; + var := it.(*Node_Var); + sym := var.symbol.(*Node_Symbol); + basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring symbol '%'", sym.str); // @errors + + value := value_nil; + if var.value_expr != null { + value = interp_expr(i, var.value_expr); + basic.assert(value != null); // @errors + } + + kv.set(*i.symbols, sym.str, value); + + case .print; + print := it.(*Node_Print); + expr := interp_expr(i, print.expr); + basic.assert(expr != null); // @errors + + if expr.kind == { + case .none; basic.print("()"); + case .nil; basic.print("nil"); + case .bool; basic.print("%", expr.b); + case .int; basic.print("%", expr.i); + case .float; basic.print("%", expr.f); + case .string; basic.print("%", expr.s); + } + + basic.print("\n"); + + case; + basic.assert(false, "unhandled node kind: %", it.kind); // @errors + } +} + +interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { + if expr.kind == { + case .unary; + do_unop :: (code: Code) #expand { + if rhs.kind == { + case .int; + right := rhs.i; + res.i = #insert,scope() code; + case .float; + right := rhs.f; + res.f = #insert,scope() code; + case; + basic.assert(false, "cannot use unary operator '%' on values of type '%'", un.op, rhs.kind); + } + } + + un := expr.(*Node_Unary); + rhs := interp_expr(i, un.right); + res := make_interp_value(i, rhs.kind); + if un.op.kind == { + case .plus; do_unop(#code ifx right < 0 then -right else right); + case .minus; do_unop(#code -right); + case; basic.assert(false, "unhandled unary operator '%'", un.op.str); // @errors + } + + return res; + + case .binary; + bin := expr.(*Node_Binary); + lhs := interp_expr(i, bin.left); + rhs := interp_expr(i, bin.right); + basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors + + do_binop :: (code: Code) #expand { + if lhs.kind == { + case .int; + left := lhs.i; + right := rhs.i; + res.i = #insert,scope() code; + case .float; + left := lhs.f; + right := rhs.f; + res.f = #insert,scope() code; + case; + basic.assert(false, "cannot use binary operator '%' on values of type '%'", bin.op, lhs.kind); + } + } + + res := make_interp_value(i, lhs.kind); + if bin.op.kind == { + case .plus; do_binop(#code left + right); + case .minus; do_binop(#code left - right); + case .star; do_binop(#code left * right); + case .f_slash; + basic.assert(rhs.i != 0, "divide by zero"); // @errors + do_binop(#code left / right); + case .percent; + basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind); + res.i = lhs.i % rhs.i; + + case; basic.assert(false, "unhandled binary operator '%'", bin.op.str); + } + + return res; + + case .symbol; + sym := expr.(*Node_Symbol); + + value, ok := kv.get(*i.symbols, sym.str); + basic.assert(ok, "use of undeclared symbol '%'", sym.str); // @errors + return value; + + case .literal; + lit := expr.(*Node_Literal); + if lit.value_kind == { + case .int; + value := make_interp_value(i, .int); + value.i = lit.i; + return value; + + case .float; + value := make_interp_value(i, .float); + value.f = lit.f; + return value; + + case; basic.assert(false, "unhandled literal kind: %", lit.value_kind); // @errors + } + + case; basic.assert(false, "unhandled node kind: %", expr.kind); // @errors + } + + return null; +} + +#scope_file; + +value_nil: *Interp_Value; +value_true: *Interp_Value; +value_false: *Interp_Value; + +make_interp_value :: (i: *Interp, kind: Interp_Value.Kind) -> *Interp_Value { + value := mem.request_memory(Interp_Value,, allocator = i.allocator); + value.kind = kind; + return value; +} diff --git a/vm/module.jai b/vm/module.jai index ee6c130..eb96447 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -1,7 +1,38 @@ -#module_parameters(RUN_TESTS := false); +// #module_parameters(RUN_TESTS := false); + +#load "parser.jai"; +#load "interp.jai"; + +#scope_module; + +mem :: #import "jc/memory"; +array :: #import "jc/array"; +kv :: #import "jc/kv"; + +basic :: #import "Basic"; // @future +strings :: #import "String"; // @future #scope_file; -#if RUN_TESTS { - test :: #import "jc/test"; +#run { + parser: Parser; + init(*parser, context.allocator); + + ok := parse_string(*parser, #string END + var x = 10.0 + var y = 20.0 + + print x + print y + END); + + interp: Interp; + interp.toplevel = parser.toplevel; + init(*interp, context.allocator); + + interp_program(*interp); } + +// #if RUN_TESTS { +// test :: #import "jc/test"; +// } diff --git a/vm/parser.jai b/vm/parser.jai index 74164fb..58fc632 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -15,11 +15,29 @@ Token :: struct { kw_type; kw_do; kw_end; + kw_if; + kw_else; + kw_switch; + kw_case; + kw_for; + kw_in; + kw_loop; + kw_return; + kw_break; + kw_continue; + kw_goto; + kw_true; + kw_false; + + kw_print; equal :: #char "="; plus :: #char "+"; minus :: #char "-"; star :: #char "*"; + percent :: #char "%"; + bang :: #char "!"; + and :: #char "&"; f_slash :: #char "/"; b_slash :: #char "\\"; @@ -38,28 +56,65 @@ Token :: struct { Node :: struct { kind: Kind; + type: *Type_Info; Kind :: enum { invalid; + stmt_start; + print; + stmt_end; + decl_start; - var; + variable; decl_end; expr_start; + type; + unary; + binary; symbol; literal; expr_end; } } +Node_Print :: struct { + #as using n: Node; + n.kind = .print; + + expr: *Node; +} + Node_Var :: struct { #as using n: Node; - n.kind = .var; + n.kind = .variable; - symbol: *Node; - type_expr: *Node; + symbol: *Node; // always *Node_Symbol + type_expr: *Node; // always *Node_Type value_expr: *Node; + var_flags: Var_Flag; + + Var_Flag :: enum_flags { + immutable; // def + } +} + +Node_Unary :: struct { + #as using n: Node; + n.kind = .unary; + + op: Token; + right: *Node; +} + +Node_Binary :: struct { + #as using n: Node; + n.kind = .binary; + + op: Token; + left: *Node; + right: *Node; } Node_Symbol :: struct { @@ -73,7 +128,49 @@ Node_Literal :: struct { #as using n: Node; n.kind = .literal; + value_kind: Value_Kind; + value_flags: Value_Flag; + union { + i: s64; + u: u64; + f: float64; + b: bool; + s: string; + } + + Value_Kind :: enum { + int; + float; + bool; + string; + } + + Value_Flag :: enum_flags { + can_be_unsigned; + } +} + +Node_Type :: struct { + #as using n: Node; + n.kind = .type; + + resolved_type: *Type_Info; + type_kind: Type_Kind; + + union { + alias_target: *Node; + pointer_target: *Node_Type; + struct { + array_element: *Node_Type; + array_count: *Node; // can be null + }; + } + + Type_Kind :: enum { + alias; + pointer; + array; } } @@ -102,10 +199,14 @@ parse_string :: (p: *Parser, source: string) -> bool { break; } - node := parse_toplevel_declaration(p); + node := parse_toplevel(p); if node == null break; array.append(*p.toplevel, node); + + if node.kind == { + case .variable; + } } return false; @@ -114,61 +215,254 @@ parse_string :: (p: *Parser, source: string) -> bool { #scope_file; -parse_toplevel_declaration :: (p: *Parser) -> *Node { - t, ok := expect_token(p, .kw_var); - if ok == false return null; - - s:, ok = expect_token(p, .symbol); - if ok == false return null; - - type_expr: *Node; - value_expr: *Node; - - // var sym int - // var sym int = value - // var sym = value - - t = peek_token(p); - if t.kind == .equal { - consume_token(p); - value_expr = parse_expression(p); - } - else { - type_expr = parse_type_expression(p); - if type_expr == null return null; - - if peek_token(p).kind == .equal { - consume_token(p); - value_expr = parse_expression(p); - } - } - - symbol := make_node(p, Node_Symbol); - symbol.str = s.str; - - node := make_node(p, Node_Var); - node.symbol = symbol; - node.type_expr = type_expr; - node.value_expr = value_expr; - - return node; -} - -parse_type_expression :: (p: *Parser) -> *Node { - t, ok := expect_token(p, .symbol, .star); - if ok == false return null; +parse_toplevel :: (p: *Parser) -> *Node { + t, ok := expect_token(p, .kw_var, .kw_def, .kw_print); + basic.assert(ok, "var, def, print"); // @errors if t.kind == { - case .symbol; - node := make_node(p, Node_Symbol); - node.str = t.str; + // var sym type_expr + // var sym type_expr = expr + // var sym = expr + case .kw_var; #through; + case .kw_def; + s:, ok = expect_token(p, .symbol); + basic.assert(ok, "symbol"); // @errors + + type_expr: *Node; + value_expr: *Node; + + is_const := t.kind == .kw_def; + + t = peek_token(p); + if t.kind == .equal { + consume_token(p); + value_expr = parse_expression(p); + basic.assert(value_expr != null, "value expr"); // @errors + } + else { + type_expr = parse_type_expression(p); + basic.assert(type_expr != null, "type expr"); // @errors + + if peek_token(p).kind == .equal { + consume_token(p); + value_expr = parse_expression(p); + basic.assert(value_expr != null, "value expr"); // @errors + } + } + + symbol := make_node(p, Node_Symbol); + symbol.str = s.str; + + node := make_node(p, Node_Var); + node.symbol = symbol; + node.type_expr = type_expr; + node.value_expr = value_expr; + + if is_const { + node.var_flags |= .immutable; + } + + return node; + + // print(expr) + // print expr + case .kw_print; + expr := parse_expression(p); + basic.assert(expr != null, "expected expression"); // @errors + + node := make_node(p, Node_Print); + node.expr = expr; return node; } return null; } -parse_expression :: (p: *Parser) -> *Node { +parse_type_expression :: (p: *Parser) -> *Node_Type { + t, ok := expect_token(p, .symbol, .star, .l_square); + basic.assert(ok, "type expression"); // @errors + + if t.kind == { + case .star; + target := parse_type_expression(p); + basic.assert(target != null, "pointer target"); // @errors + + node := make_node(p, Node_Type); + node.type_kind = .pointer; + node.pointer_target = target; + return node; + + case .l_square; + node := make_node(p, Node_Type); + node.type_kind = .array; + + // slice + if peek_token(p).kind == .r_square { + consume_token(p); + + element := parse_type_expression(p); + basic.assert(element != null, "array element"); // @errors + + node.array_element = element; + } + else { + count := parse_expression(p); + basic.assert(count != null, "array count"); // @errors + + _, ok := expect_token(p, .r_square); + basic.assert(ok, "end of array type"); + + element := parse_type_expression(p); + basic.assert(element != null, "array element"); // @errors + + node.array_count = count; + node.array_element = element; + } + + return node; + + case .symbol; + symbol := make_node(p, Node_Symbol); + symbol.str = t.str; + + node := make_node(p, Node_Type); + node.type_kind = .alias; + node.alias_target = symbol; + return node; + } + + return null; +} + +parse_expression :: (p: *Parser, min_precedence := 1) -> *Node { + get_precedence :: inline (t: Token) -> int { + if t.kind == { + case .star; #through; + case .f_slash; #through; + case .percent; #through; + case .and; + return 4; + + case .plus; #through; + case .minus; + return 3; + + // case .equal_equal; #through; + // case .bang_equal; #through; + // case .less; #through; + // case .less_equal; #through; + // case .more; #through; + // case .more_equal; + // return 2; + } + + return 0; + } + + node := parse_expression_unary(p); + basic.assert(node != null, "expected expression"); // @errors + + while !at_end(p) { + op := peek_token(p); + prec := get_precedence(op); + if prec <= min_precedence break; + + op = consume_token(p); + + lhs := node; + rhs := parse_expression(p, prec); + basic.assert(rhs != null, "expected rhs"); // @errors + + new := make_node(p, Node_Binary); + new.op = op; + new.left = lhs; + new.right = rhs; + + node = new; + } + + return node; +} + +parse_expression_unary :: (p: *Parser) -> *Node { + op := peek_token(p); + if op.kind == { + case .plus; #through; + case .minus; + op = consume_token(p); + + node := parse_expression_unary(p); + basic.assert(node != null, "expected expr"); // @errors + + unary := make_node(p, Node_Unary); + unary.op = op; + unary.right = node; + return unary; + } + + return parse_expression_postfix(p); +} + +parse_expression_postfix :: (p: *Parser) -> *Node { + // @TODO + base := parse_expression_base(p); + basic.assert(base != null, "expected expression"); // @errors + return base; +} + +parse_expression_base :: (p: *Parser) -> *Node { + t, ok := expect_token(p, .kw_true, .kw_false, .number, .symbol, .l_paren); + basic.assert(ok, "expected expression, found '%'", t.str); // @errors + + if t.kind == { + case .kw_true; #through; + case .kw_false; + node := make_node(p, Node_Literal); + node.b = t.kind == .kw_true; + node.value_kind = .bool; + return node; + + case .symbol; + node := make_node(p, Node_Symbol); + node.str = t.str; + return node; + + case .number; + node := make_node(p, Node_Literal); + copy := t.str; + + if strings.contains(t.str, ".") { + node.value_kind = .float; + + value, ok := strings.parse_float(*copy); + basic.assert(ok, "malformed float '%'", t.str); // @errors + + node.f = value; + } + else { + node.value_kind = .int; + if t.str[0] == "-" { + node.value_flags |= .can_be_unsigned; + } + + value, ok := strings.parse_int(*copy); + basic.assert(ok, "malformed integer '%'", t.str); // @errors + + node.i = value; + } + + return node; + + case .l_paren; + node := parse_expression(p); + basic.assert(node != null, "expected expression"); // @errors + + _, ok := expect_token(p, .r_paren); + basic.assert(ok, "expected ')'"); // @errors + + return node; + } + return null; } @@ -227,12 +521,27 @@ consume_token :: (p: *Parser) -> Token { t.str.count = (p.source.data + p.offset) - t.str.data; if t.str == { - case "var"; t.kind = .kw_var; - case "def"; t.kind = .kw_def; - case "do"; t.kind = .kw_do; - case "end"; t.kind = .kw_end; - case "type"; t.kind = .kw_type; - case; t.kind = .symbol; + case "var"; t.kind = .kw_var; + case "def"; t.kind = .kw_def; + case "type"; t.kind = .kw_type; + case "do"; t.kind = .kw_do; + case "end"; t.kind = .kw_end; + case "if"; t.kind = .kw_if; + case "else"; t.kind = .kw_else; + case "switch"; t.kind = .kw_switch; + case "case"; t.kind = .kw_case; + case "for"; t.kind = .kw_for; + case "in"; t.kind = .kw_in; + case "loop"; t.kind = .kw_loop; + case "return"; t.kind = .kw_return; + case "break"; t.kind = .kw_break; + case "continue"; t.kind = .kw_continue; + case "goto"; t.kind = .kw_goto; + case "true"; t.kind = .kw_true; + case "false"; t.kind = .kw_false; + + case "print"; t.kind = .kw_print; + case; t.kind = .symbol; } return t; @@ -256,6 +565,9 @@ consume_token :: (p: *Parser) -> Token { case "*"; #through; case "/"; #through; case "="; #through; + case "%"; #through; + case "!"; #through; + case "&"; #through; case "("; #through; case ")"; #through; case "["; #through; @@ -280,18 +592,3 @@ expect_token :: (p: *Parser, kinds: ..Token.Kind) -> Token, bool { return t, false; } -#run { - parser: Parser; - init(*parser, context.allocator); - - ok := parse_string(*parser, #string END - var x = 10 - var y = 20 - END); -} - -mem :: #import "jc/memory"; -array :: #import "jc/array"; - -basic :: #import "Basic"; // @future -strings :: #import "String"; // @future From 85065a23e6985b12fd28798588b3d51ba644ab39 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 19:42:38 -0600 Subject: [PATCH 05/18] [vm] comments --- vm/module.jai | 7 +++++-- vm/parser.jai | 25 +++++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/vm/module.jai b/vm/module.jai index eb96447..79037b1 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -21,9 +21,12 @@ strings :: #import "String"; // @future ok := parse_string(*parser, #string END var x = 10.0 var y = 20.0 + var z = x + y * 2.0 / 3.0 - print x - print y + print x // 10 + print y // 20 + print z // 23.3 + // print z END); interp: Interp; diff --git a/vm/parser.jai b/vm/parser.jai index 58fc632..1a92a2a 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -203,10 +203,6 @@ parse_string :: (p: *Parser, source: string) -> bool { if node == null break; array.append(*p.toplevel, node); - - if node.kind == { - case .variable; - } } return false; @@ -217,7 +213,7 @@ parse_string :: (p: *Parser, source: string) -> bool { parse_toplevel :: (p: *Parser) -> *Node { t, ok := expect_token(p, .kw_var, .kw_def, .kw_print); - basic.assert(ok, "var, def, print"); // @errors + basic.assert(ok, "var, def, print, found '%'", t.str); // @errors if t.kind == { // var sym type_expr @@ -225,12 +221,12 @@ parse_toplevel :: (p: *Parser) -> *Node { // var sym = expr case .kw_var; #through; case .kw_def; + s:, ok = expect_token(p, .symbol); basic.assert(ok, "symbol"); // @errors type_expr: *Node; value_expr: *Node; - is_const := t.kind == .kw_def; t = peek_token(p); @@ -499,6 +495,8 @@ consume_token :: (p: *Parser) -> Token { if at_end(p) return .{ kind = .end_of_file }; c := p.source[p.offset]; + + // skip whitespace while !at_end(p) { c = p.source[p.offset]; if c == { @@ -511,6 +509,21 @@ consume_token :: (p: *Parser) -> Token { } } + // line comments + // @todo(judah): don't ignore these + if c == "/" && p.offset + 1 < p.source.count && p.source[p.offset + 1] == "/" { + p.offset += 2; + + while !at_end(p) { + c = p.source[p.offset]; + if c == "\n" break; + p.offset += 1; + } + + // @todo(judah): don't recurse + return consume_token(p); + } + if starts_symbol(c) { t := Token.{ str = .{ data = p.source.data + p.offset } }; while !at_end(p) { From 822f5c9ae4d90891d53689ffb945aa635a86d1ca Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 19:52:24 -0600 Subject: [PATCH 06/18] . --- vm/interp.jai | 8 +++----- vm/module.jai | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vm/interp.jai b/vm/interp.jai index b41a9de..648ad2e 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -3,7 +3,6 @@ Interp :: struct { symbols: kv.Kv(string, *Interp_Value); toplevel: []*Node; - // stack: [..]*Interp_Value; } Interp_Value :: struct { @@ -27,8 +26,6 @@ Interp_Value :: struct { } init :: (i: *Interp, allocator: Allocator) { - // i.stack.allocator = allocator; - value_nil = make_interp_value(i, .nil); value_true = make_interp_value(i, .bool); @@ -56,15 +53,16 @@ interp_program :: (i: *Interp) { case .print; print := it.(*Node_Print); expr := interp_expr(i, print.expr); - basic.assert(expr != null); // @errors + if expr == null continue; if expr.kind == { - case .none; basic.print("()"); + case .none; // do nothing case .nil; basic.print("nil"); case .bool; basic.print("%", expr.b); case .int; basic.print("%", expr.i); case .float; basic.print("%", expr.f); case .string; basic.print("%", expr.s); + case; basic.assert(false, "unhandled interp value kind: %", expr.kind); } basic.print("\n"); diff --git a/vm/module.jai b/vm/module.jai index 79037b1..fb4297b 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -5,11 +5,13 @@ #scope_module; +// exported to the entire module since we want these everywhere + mem :: #import "jc/memory"; array :: #import "jc/array"; kv :: #import "jc/kv"; -basic :: #import "Basic"; // @future +basic :: #import "Basic"; // @future strings :: #import "String"; // @future #scope_file; From 34939ef819815dd40221d8025bedca4c58d5111f Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 2 Jun 2025 20:08:33 -0600 Subject: [PATCH 07/18] [math] reorganized math tests --- math/mat.jai | 81 ++++++++++++ math/module.jai | 336 +----------------------------------------------- math/vec.jai | 141 +++++++++++++++++++- 3 files changed, 225 insertions(+), 333 deletions(-) diff --git a/math/mat.jai b/math/mat.jai index 66f6022..99b2cf2 100644 --- a/math/mat.jai +++ b/math/mat.jai @@ -400,3 +400,84 @@ inverse :: (m: Mat4) -> Mat4 { return transpose(r); } + + +#scope_file; + +#if RUN_TESTS #run { + test.run(basic.tprint("%: Mat2", UNITS), t => { + { + identity := m2_identity; + a: Mat2 = Mat2.{.[1, 2, 3, 4]}; + test.expect(t, a*identity == a); + } + { + rotator := rotation_mat2(from_rad(PI)); + v2 := v2f(1.0, 0.5); + v2 = rotator*v2; + test.expect(t, v2_eq(v2, v2f(-1.0, -0.5))); + v2 = rotator*v2; + test.expect(t, v2_eq(v2, v2f(1.0, 0.5))); + } + { + m := m2(1.0, 3.0, 2.0, 2.0); + det := determinate(m); + test.expect(t, det == -4); + } + { + rotator := rotation_mat2(from_rad(PI)); + inv_rotator := inverse(rotator); + v2 := v2f(0.1, 1.0); + + test.expect(t, inv_rotator*rotator == m2_identity); + test.expect(t, inv_rotator*(rotator*v2) == v2); + v2 = rotator*v2; + v2 = inv_rotator*v2; + } + }); + + test.run(basic.tprint("%: Mat4", UNITS), t => { + { + identity := m4_identity; + a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}; + test.expect(t, a*identity == a); + } + { + UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]}; + camera := v3f(1.0, 0.0, 1.0); + looking_at := v3f(0.0, 0.0, 0.0); + look_at := make_look_at(camera, looking_at, UP_VECTOR); + } + { + translator := translate(v3f(10.0, 5.0, 2.0)); + v3 := v3f(1.0, 2.0, 1.0); + v4 := v4f(v3, 1.0); + test.expect(t, v4 == v4f(1.0, 2.0, 1.0, 1.0)); + test.expect(t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0)); + } + { + rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); + v4 := v4f(1.0, 0.5, 0.1, 1.0); + v4 = rotator*v4; + test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); + v4 = rotator*v4; + test.expect(t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0))); + } + { + rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI)); + rotator_y := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); + camera_rotator := rotator_x*rotator_y; + det := determinate(camera_rotator); + } + { + rotator := rotation_mat4(v3f(0.0, 0.0, 1.0), from_rad(PI)); + inv_rotator := inverse(rotator); + v4 := v4f(0.1, 1.0, 0.0, 1.0); + + test.expect(t, inv_rotator*rotator == m4_identity); + + v4 = rotator*v4; + v4 = inv_rotator*v4; + } + }); +} diff --git a/math/module.jai b/math/module.jai index b1d50e2..4bc54d4 100644 --- a/math/module.jai +++ b/math/module.jai @@ -25,333 +25,15 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64); #scope_module; -#if RUN_TESTS #run { - test :: #import "jc/test"; - - vec2_tests(); - vec3_tests(); - vec4_tests(); - vecn_tests(); - mat2_tests(); - mat4_tests(); - quat_tests(); -} - -#scope_file; - -meta :: #import "jc/meta"; +meta :: #import "jc/meta"; +math :: #import "Math"; // @future basic :: #import "Basic"; // @future -math :: #import "Math"; - -test :: #import "jc/test"; - -vec2_tests :: () { - dot_print("Vec2 % tests", UNITS); - t: test.T; - { - a: Vec2 = v2f(0.0, 1.0); - b: Vec2 = v2f(1.0, 2.0); - test.expect(*t, a + b == v2f(1.0, 3.0)); - test.expect(*t, b - a == v2f(1.0, 1.0)); - test.expect(*t, a*b == v2f(0.0, 2.0)); - test.expect(*t, a/b == v2f(0.0, 0.5)); - } - - { - a: Vec(2, int) = v2i(2, 1); - b: Vec(2, int) = v2i(1, 0); - test.expect(*t, a + b == v2i(3, 1)); - test.expect(*t, b - a == v2i(-1, -1)); - test.expect(*t, a*b == v2i(2, 0)); - test.expect(*t, b/a == v2i(0, 0)); - } - { - a: Vec2 = v2f(2.3, -4.1); - b: Vec2 = v2f(1.0, 3.6); - c := min(a, b); - test.expect(*t, c == v2f(1.0, -4.1)); - c = max(a, b); - test.expect(*t, c == v2f(2.3, 3.6)); - } +#if RUN_TESTS { + test :: #import "jc/test"; } -vec3_tests :: () { - dot_print("Vec3 % tests", UNITS); - t: test.T; - { - a: Vec3 = v3f(0.0, 1.0, 2.0); - b: Vec3 = v3f(1.0, 2.0, 3.0); - test.expect(*t, a + b == v3f(1.0, 3.0, 5.0)); - test.expect(*t, b - a == v3f(1.0, 1.0, 1.0)); - test.expect(*t, a*b == v3f(0.0, 2.0, 6.0)); - test.expect(*t, a/b == v3f(0.0, 0.5, 0.66666667)); - - a = v3f(1.0, 1.0, 0.0); - b = v3f(1.0, 0.0, 0.0); - test.expect(*t, reflect(a, b) == v3f(1.0, -1.0, 0.0)); - test.expect(*t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0)); - test.expect(*t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); - - a = v3f(1.0, 0.0, 0.0); - b = v3f(0.0, 1.0, 0.0); - test.expect(*t, cross(a, b) == v3f(0.0, 0.0, 1.0)); - } - { - a: Vec3 = v3f(2.3, 4.1, 9.0); - b: Vec3 = v3f(1.0, -3.6, 5.0); - c := min(a, b); - test.expect(*t, c == v3f(1.0, -3.6, 5.0)); - c = max(a, b); - test.expect(*t, c == v3f(2.3, 4.1, 9.0)); - } -} - -vec4_tests :: () { - dot_print("Vec4 % tests", UNITS); - t: test.T; - { - a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0); - b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0); - test.expect(*t, a + b == v4f(6.25, 3.0, 5.0, 2.0)); - test.expect(*t, b - a == v4f(1.75, 1.0, 1.0, 0.0)); - test.expect(*t, a*b == v4f(9.0, 2.0, 6.0, 1.0)); - test.expect(*t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); - } -} - -vecn_tests :: () { - dot_print("VecN % tests", UNITS); - t: test.T; - { - a: Vec(16, float); - b: Vec(16, float); - for *a { - it.* = xx it_index; - } - for *b { - it.* = xx(it_index + 1); - } - test.expect(*t, a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]}); - test.expect(*t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}); - test.expect(*t, a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]}); - - test.expect(*t, min(a, b) == a); - test.expect(*t, max(a, b) == b); - test.expect(*t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]}); - test.expect(*t, min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]}); - test.expect(*t, clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]}); - - a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]}; - basic.print(">> a1: %\n", a1); - basic.print(">> ceil(a1): %\n", ceil(a1)); - basic.print(">> floor(a1): %\n", floor(a1)); - test.expect(*t, ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]}); - test.expect(*t, floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]}); - - test.expect(*t, dot(a, b) == 1360.0); - test.expect(*t, abs(a) == a); - c := a; - for *c { // Check making every other component negative - if it_index%2 == 0 - it.* = -it.*; - } - test.expect(*t, abs(c) == a); - test.expect(*t, float_eq(length(normalize(a)), 1)); - test.expect(*t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}); - test.expect(*t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}); - test.expect(*t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); - } -} - -mat2_tests :: () { - dot_print("Mat2 % tests", UNITS); - t: test.T; - { - identity := m2_identity; - a: Mat2 = Mat2.{.[1, 2, 3, 4]}; - test.expect(*t, a*identity == a); - } - { - basic.print("rotation matrix\n"); - rotator := rotation_mat2(from_rad(PI)); - v2 := v2f(1.0, 0.5); - basic.print("rotating: %\n", v2); - v2 = rotator*v2; - basic.print("after rotate: %\n", v2); - test.expect(*t, v2_eq(v2, v2f(-1.0, -0.5))); - v2 = rotator*v2; - basic.print("rotate back: %\n", v2); - test.expect(*t, v2_eq(v2, v2f(1.0, 0.5))); - } - { - basic.print("determinate\n"); - m := m2(1.0, 3.0, 2.0, 2.0); - basic.print("m2: %\n", m); - det := determinate(m); - basic.print("determinate: %\n", det); - test.expect(*t, det == -4); - } - { - basic.print("inverse test\n"); - rotator := rotation_mat2(from_rad(PI)); - inv_rotator := inverse(rotator); - v2 := v2f(0.1, 1.0); - basic.print(" : %\n", rotator); - basic.print("inv: %\n", inv_rotator); - - basic.print("inv_mat*mat = %\n", inv_rotator*rotator); - test.expect(*t, inv_rotator*rotator == m2_identity); - - test.expect(*t, inv_rotator*(rotator*v2) == v2); - basic.print("v2 : %\n", v2); - v2 = rotator*v2; - basic.print("rot : %\n", v2); - v2 = inv_rotator*v2; - basic.print("inv : %\n", v2); - } -} - -mat4_tests :: () { - dot_print("Mat4 % tests", UNITS); - t: test.T; - { - identity := m4_identity; - a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}; - test.expect(*t, a*identity == a); - } - { - UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]}; - camera := v3f(1.0, 0.0, 1.0); - looking_at := v3f(0.0, 0.0, 0.0); - look_at := make_look_at(camera, looking_at, UP_VECTOR); - basic.print("lookat matrix: %\n", look_at); - basic.print("lookat from 0,1,0: %\n", look_at*v4f(0.0, 1.0, 0.0, 1.0)); - } - { - translator := translate(v3f(10.0, 5.0, 2.0)); - v3 := v3f(1.0, 2.0, 1.0); - v4 := v4f(v3, 1.0); - test.expect(*t, v4 == v4f(1.0, 2.0, 1.0, 1.0)); - test.expect(*t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0)); - } - { - basic.print("rotation matrix\n"); - rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); - v4 := v4f(1.0, 0.5, 0.1, 1.0); - basic.print("rotating: %\n", v4); - v4 = rotator*v4; - basic.print("after rotate: %\n", v4); - test.expect(*t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); - v4 = rotator*v4; - basic.print("rotate back: %\n", v4); - test.expect(*t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0))); - } - { - rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI)); - rotator_y := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); - camera_rotator := rotator_x*rotator_y; - basic.print("big rotator: %\n", camera_rotator); - det := determinate(camera_rotator); - basic.print("determinate: %\n", det); - } - { - basic.print("inverse test\n"); - rotator := rotation_mat4(v3f(0.0, 0.0, 1.0), from_rad(PI)); - inv_rotator := inverse(rotator); - v4 := v4f(0.1, 1.0, 0.0, 1.0); - basic.print(" : %\n", rotator); - basic.print("inv: %\n", inv_rotator); - - basic.print("inv_mat*mat = %\n", inv_rotator*rotator); - test.expect(*t, inv_rotator*rotator == m4_identity); - - basic.print("v4 : %\n", v4); - v4 = rotator*v4; - basic.print("rot : %\n", v4); - v4 = inv_rotator*v4; - basic.print("inv : %\n", v4); - } -} - -quat_tests :: () { - dot_print("Quaternion % tests", UNITS); - t: test.T; - { - qi := quat_identity; - basic.print("qi lensq: %\n", length_squared(qi)); - basic.print("qi len : %\n", length(qi)); - q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); - q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0)); - basic.print("q : %\n", q); - basic.print("q2 : %\n", q2); - basic.print("dot : %\n", dot(q, q2)); - basic.print("mul : %\n", q*q2); - qc := conjugate(q); - basic.print("conjugate : %\n", qc); - inv_q := inverse(q); - basic.print("inverse : %\n", inv_q); - test.expect(*t, q*inv_q == qi); - - q1 := quat(2, 0, 0, 0); - q2 = quat(1, 1, -1, 0); - basic.print("q1: %\n", q1); - basic.print("q2: %\n", q2); - basic.print("q1*q2: %\n", q1*q2); - basic.print("dot(q2,q2): %\n", dot(q2, q2)); - basic.print("dot(q1,q2): %\n", dot(q1, q2)); - basic.print("q1*q2*conj(q1): %\n", q1*q2*conjugate(q1)); - c := q1*q2*conjugate(q1); - test.expect(*t, float_eq(c.w, 0.0)); - - q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); - p := v3f(2.0, 0.0, 0.0); - basic.print("q: %\n", q); - basic.print("p: %\n", p); - c1 := q*quat(p, 0)*conjugate(q); - basic.print("q*quat(p, 0)*conjugate(q): %\n", c1); - c2 := q*p; - basic.print("q*p : %\n", c2); - test.expect(*t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); - test.expect(*t, v3_eq(c1.xyz, c2)); - - basic.print("quaternion to matrix rotation\n"); - q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); - basic.print("q: %\n", q); - m := rotation_mat4(q); - print_mat4(m); - p1 := v4f(2.0, 0.0, 0.0, 1.0); - basic.print("p : %\n", p1); - basic.print("p': %\n", m*p1); - test.expect(*t, v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0))); - - q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); - q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0)); - perc := 0.5; - q = slerp(q1, q2, perc); - basic.print("q1: %\n", q1); - basic.print("q2: %\n", q2); - basic.print("slerp q1, q2, %: %\n", perc, q); - - q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); - print_mat4(rotation_mat4(q)); - } -} - -dot_print :: (fmt: string, args: ..Any, width: int = 30) { - dots := "................................................................."; - str := basic.tprint(fmt, args); - dots.count = width - str.count; - basic.print("%0%", str, dots); - basic.print("\n"); -} - -check :: (cond: bool, loc := #caller_location) -> bool { - if !cond { - basic.print("\tTest failed at %:%\n", loc.fully_pathed_filename, loc.line_number); - } - return cond; -} +// @temp(judah): move these to the right files v2_eq :: (a: Vec2, b: Vec2) -> bool { return float_eq(a.x, b.x) && float_eq(a.y, b.y); @@ -367,13 +49,7 @@ v4_eq :: (a: Vec4, b: Vec4) -> bool { // Smallest difference where a float is basically that value EPSILON :: 0.001; + float_eq :: (f: float, with: float) -> bool { return f > with - EPSILON && f < with + EPSILON; } - -print_mat4 :: (m: Mat4) { - basic.print("| % % % % |\n", m._00, m._01, m._02, m._03); - basic.print("| % % % % |\n", m._10, m._11, m._12, m._13); - basic.print("| % % % % |\n", m._20, m._21, m._22, m._23); - basic.print("| % % % % |\n", m._30, m._31, m._32, m._33); -} diff --git a/math/vec.jai b/math/vec.jai index 0753c63..ba71d26 100644 --- a/math/vec.jai +++ b/math/vec.jai @@ -640,6 +640,141 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 { #scope_file -meta :: #import "jc/meta"; -math :: #import "Math"; // @future -basic :: #import "Basic"; // @future +#if RUN_TESTS #run,stallable { + test.run(basic.tprint("%: Vec2", UNITS), t => { + { + a: Vec2 = v2f(0.0, 1.0); + b: Vec2 = v2f(1.0, 2.0); + + test.expect(t, a + b == v2f(1.0, 3.0)); + test.expect(t, b - a == v2f(1.0, 1.0)); + test.expect(t, a*b == v2f(0.0, 2.0)); + test.expect(t, a/b == v2f(0.0, 0.5)); + } + + { + a: Vec(2, int) = v2i(2, 1); + b: Vec(2, int) = v2i(1, 0); + test.expect(t, a + b == v2i(3, 1)); + test.expect(t, b - a == v2i(-1, -1)); + test.expect(t, a*b == v2i(2, 0)); + test.expect(t, b/a == v2i(0, 0)); + } + { + a: Vec2 = v2f(2.3, -4.1); + b: Vec2 = v2f(1.0, 3.6); + c := min(a, b); + test.expect(t, c == v2f(1.0, -4.1)); + c = max(a, b); + test.expect(t, c == v2f(2.3, 3.6)); + } + }); + + test.run(basic.tprint("%: Vec3", UNITS), t => { + { + a: Vec3 = v3f(0.0, 1.0, 2.0); + b: Vec3 = v3f(1.0, 2.0, 3.0); + test.expect(t, a + b == v3f(1.0, 3.0, 5.0)); + test.expect(t, b - a == v3f(1.0, 1.0, 1.0)); + test.expect(t, a*b == v3f(0.0, 2.0, 6.0)); + test.expect(t, a/b == v3f(0.0, 0.5, 0.66666667)); + + a = v3f(1.0, 1.0, 0.0); + b = v3f(1.0, 0.0, 0.0); + test.expect(t, reflect(a, b) == v3f(1.0, -1.0, 0.0)); + test.expect(t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0)); + test.expect(t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); + + a = v3f(1.0, 0.0, 0.0); + b = v3f(0.0, 1.0, 0.0); + test.expect(t, cross(a, b) == v3f(0.0, 0.0, 1.0)); + } + { + a: Vec3 = v3f(2.3, 4.1, 9.0); + b: Vec3 = v3f(1.0, -3.6, 5.0); + c := min(a, b); + test.expect(t, c == v3f(1.0, -3.6, 5.0)); + c = max(a, b); + test.expect(t, c == v3f(2.3, 4.1, 9.0)); + } + }); + + test.run(basic.tprint("%: Vec4", UNITS), t => { + a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0); + b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0); + test.expect(t, a + b == v4f(6.25, 3.0, 5.0, 2.0)); + test.expect(t, b - a == v4f(1.75, 1.0, 1.0, 0.0)); + test.expect(t, a*b == v4f(9.0, 2.0, 6.0, 1.0)); + test.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); + }); + + test.run(basic.tprint("%: VecN", UNITS), t => { + a: Vec(16, float); + b: Vec(16, float); + for *a { + it.* = xx it_index; + } + for *b { + it.* = xx(it_index + 1); + } + test.expect(t, a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]}); + test.expect(t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}); + test.expect(t, a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]}); + + test.expect(t, min(a, b) == a); + test.expect(t, max(a, b) == b); + test.expect(t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]}); + test.expect(t, min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]}); + test.expect(t, clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]}); + + a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]}; + test.expect(t, ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]}); + test.expect(t, floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]}); + + test.expect(t, dot(a, b) == 1360.0); + test.expect(t, abs(a) == a); + + c := a; + for *c { // Check making every other component negative + if it_index%2 == 0 then it.* = -it.*; + } + + test.expect(t, abs(c) == a); + test.expect(t, float_eq(length(normalize(a)), 1)); + test.expect(t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}); + test.expect(t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}); + test.expect(t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); + }); + + test.run(basic.tprint("%: Quat", UNITS), t => { + qi := quat_identity; + q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); + q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0)); + qc := conjugate(q); + inv_q := inverse(q); + test.expect(t, q*inv_q == qi); + + q1 := quat(2, 0, 0, 0); + q2 = quat(1, 1, -1, 0); + c := q1*q2*conjugate(q1); + test.expect(t, float_eq(c.w, 0.0)); + + q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); + p := v3f(2.0, 0.0, 0.0); + c1 := q*quat(p, 0)*conjugate(q); + c2 := q*p; + test.expect(t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); + test.expect(t, v3_eq(c1.xyz, c2)); + + q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); + m := rotation_mat4(q); + p1 := v4f(2.0, 0.0, 0.0, 1.0); + test.expect(t, v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0))); + + q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); + q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0)); + perc := 0.5; + q = slerp(q1, q2, perc); + q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); + }); +} From d11da492060ea9cebb94ff9b700af8fc2c746ddb Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Tue, 3 Jun 2025 11:38:04 -0600 Subject: [PATCH 08/18] add 074 --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 148eba9..a0f84bb 100644 --- a/TODO +++ b/TODO @@ -69,6 +69,7 @@ 070 [x, bug] figure out why [meta] `for_expansion` procs aren't usable when import is namespaced 071 [math] basic shape collision detection (circle, rect, line) 072 [timestep] create drop-in fixed timestep module + 074 [hash] fix murmur32 not properly hashing pointers (it takes a pointer to a local which causes collisions) *** DONE *** From 93e59df67696cc12c28f71754a824c306750cd64 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Tue, 3 Jun 2025 11:38:19 -0600 Subject: [PATCH 09/18] use correct overload for murmur32 --- hash/murmur.jai | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hash/murmur.jai b/hash/murmur.jai index 2da4c50..5463131 100644 --- a/hash/murmur.jai +++ b/hash/murmur.jai @@ -8,10 +8,7 @@ murmur32 :: inline (s: string, seed: u32 = MurMur_Seed) -> u32 { } murmur32 :: inline (x: $T, seed: u32 = MurMur_Seed) -> u32 { - d: []u8 = ---; - d.data = cast(*u8)*x; - d.count = size_of(T); - return murmur32(d.data, d.count, seed); + return murmur32(*x, size_of(T), seed); } murmur32 :: (key: *void, len: int, seed: u32 = MurMur_Seed) -> u32 { From dc04c0463b1f399b8afba11294e11f395af18cec Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Tue, 3 Jun 2025 11:39:00 -0600 Subject: [PATCH 10/18] [vm] procedure call parsing / fake procedure calls --- vm/interp.jai | 20 ++++++++++++++++ vm/module.jai | 2 ++ vm/parser.jai | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/vm/interp.jai b/vm/interp.jai index 648ad2e..ce755fc 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -74,6 +74,26 @@ interp_program :: (i: *Interp) { interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { if expr.kind == { + case .procedure_call; + call := expr.(*Node_Procedure_Call); + args := call.all_arguments; + + // @temp + sym := call.call_expr.(*Node_Symbol); + basic.assert(sym.kind == .symbol); + if sym.str == { + case "add"; + basic.assert(args.count == 2, "mismatched number of arguments; require 2, given %", args.count); // @errors + + node := mem.request_memory(Node_Binary,, allocator = basic.temp); + node.op = .{ kind = .plus, str = "+" }; + node.left = args[0]; + node.right = args[1]; + return interp_expr(i, node); + } + + return value_nil; + case .unary; do_unop :: (code: Code) #expand { if rhs.kind == { diff --git a/vm/module.jai b/vm/module.jai index fb4297b..267b041 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -24,10 +24,12 @@ strings :: #import "String"; // @future var x = 10.0 var y = 20.0 var z = x + y * 2.0 / 3.0 + var w = add(x, y) print x // 10 print y // 20 print z // 23.3 + print w // 30 // print z END); diff --git a/vm/parser.jai b/vm/parser.jai index 1a92a2a..98f11a3 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -73,6 +73,7 @@ Node :: struct { type; unary; binary; + procedure_call; symbol; literal; expr_end; @@ -86,6 +87,15 @@ Node_Print :: struct { expr: *Node; } +Node_Procedure_Call :: struct { + #as using n: Node; + n.kind = .procedure_call; + + call_expr: *Node; + named_arguments: kv.Kv(*Node, *Node); + all_arguments: [..]*Node; +} + Node_Var :: struct { #as using n: Node; n.kind = .variable; @@ -221,7 +231,6 @@ parse_toplevel :: (p: *Parser) -> *Node { // var sym = expr case .kw_var; #through; case .kw_def; - s:, ok = expect_token(p, .symbol); basic.assert(ok, "symbol"); // @errors @@ -403,6 +412,56 @@ parse_expression_postfix :: (p: *Parser) -> *Node { // @TODO base := parse_expression_base(p); basic.assert(base != null, "expected expression"); // @errors + + t := peek_token(p); + if t.kind == { + case .l_paren; // procedure calls + consume_token(p); + + node := make_node(p, Node_Procedure_Call); + node.call_expr = base; + + array.init(*node.all_arguments, p.allocator); + kv.init(*node.named_arguments, p.allocator); + + while !at_end(p) { + t = peek_token(p); + if t.kind == .r_paren break; + + arg_or_name := parse_expression(p); + basic.assert(arg_or_name != null, "expected expression in procedure call"); // @errors + + if peek_token(p).kind == .colon { + consume_token(p); + basic.assert(arg_or_name.kind == .symbol, "expected symbol for named argument"); // @errors + basic.assert(!kv.exists(*node.named_arguments, arg_or_name), "duplicate named argument '%'", arg_or_name.(*Node_Symbol).str); // @errors + + value := parse_expression(p); + basic.assert(value != null, "expected expression after ':'"); // @errors + array.append(*node.all_arguments, value); + kv.set(*node.named_arguments, arg_or_name, value); + } + else { + array.append(*node.all_arguments, arg_or_name); + } + + t = peek_token(p); + if t.kind == { + case .comma; + consume_token(p); + continue; + case .r_paren; + break; + case; + basic.assert(false, "expected ',' or ')' but found '%'", t.str); + } + } + + _, ok := expect_token(p, .r_paren); + basic.assert(ok, "expected ')'"); // @errors + return node; + } + return base; } @@ -581,6 +640,10 @@ consume_token :: (p: *Parser) -> Token { case "%"; #through; case "!"; #through; case "&"; #through; + case ","; #through; + case "."; #through; + case ":"; #through; + case ";"; #through; case "("; #through; case ")"; #through; case "["; #through; From 08769c7a6d4195fcd3402a9e7b07542e6e4d405f Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Tue, 3 Jun 2025 21:43:11 -0600 Subject: [PATCH 11/18] [vm] return statements and start of procedure parsing --- vm/module.jai | 3 ++ vm/parser.jai | 132 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/vm/module.jai b/vm/module.jai index 267b041..4140cfb 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -21,6 +21,9 @@ strings :: #import "String"; // @future init(*parser, context.allocator); ok := parse_string(*parser, #string END + fn add(x, y) do return x + y end + fn sub(x, y) do return x - y end + var x = 10.0 var y = 20.0 var z = x + y * 2.0 / 3.0 diff --git a/vm/parser.jai b/vm/parser.jai index 98f11a3..f0b01c7 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -13,6 +13,7 @@ Token :: struct { kw_var; kw_def; kw_type; + kw_fn; kw_do; kw_end; kw_if; @@ -61,12 +62,16 @@ Node :: struct { Kind :: enum { invalid; + block; + stmt_start; print; + return_; stmt_end; decl_start; variable; + procedure; decl_end; expr_start; @@ -184,6 +189,50 @@ Node_Type :: struct { } } +/* +fn add(x int, y int) int do return x + y end +*/ + +Node_Procedure :: struct { + #as using n: Node; + n.kind = .procedure; + + header: *Node_Procedure_Header; + + args: [..]Node_Argument; + rets: [..]Node_Argument; + body: *Node_Block; + flags: Flag; + + Flag :: enum_flags { + inline_; + } +} + +Node_Argument :: struct { + symbol: *Node_Symbol; + type: *Node_Type; + value: *Node; // always an expression, can be null +} + +Node_Procedure_Header :: struct { + args: [..]*Node_Type; + rets: [..]*Node_Type; +} + +Node_Block :: struct { + #as using n: Node; + n.kind = .block; + body: [..]*Node; +} + +Node_Return :: struct { + #as using n: Node; + n.kind = .return_; + + values: [..]*Node; +} + Parser :: struct { allocator: Allocator; toplevel: [..]*Node; @@ -210,9 +259,7 @@ parse_string :: (p: *Parser, source: string) -> bool { } node := parse_toplevel(p); - if node == null break; - - array.append(*p.toplevel, node); + if node != null array.append(*p.toplevel, node); } return false; @@ -222,7 +269,7 @@ parse_string :: (p: *Parser, source: string) -> bool { #scope_file; parse_toplevel :: (p: *Parser) -> *Node { - t, ok := expect_token(p, .kw_var, .kw_def, .kw_print); + t, ok := expect_token(p, .kw_var, .kw_def, .kw_fn, .kw_print, .kw_do, .kw_return); basic.assert(ok, "var, def, print, found '%'", t.str); // @errors if t.kind == { @@ -269,6 +316,22 @@ parse_toplevel :: (p: *Parser) -> *Node { return node; + // return + // return expr0, ..exprN + case .kw_return; + node := make_node(p, Node_Return); + array.init(*node.values, p.allocator); + + prev_offset := p.offset; + expr := parse_expression(p); + if expr == null { + p.offset = prev_offset; + return node; + } + + array.append(*node.values, expr); + return node; + // print(expr) // print expr case .kw_print; @@ -278,11 +341,71 @@ parse_toplevel :: (p: *Parser) -> *Node { node := make_node(p, Node_Print); node.expr = expr; return node; + + // fn symbol(arg0, ..argN) do end + case .kw_fn; + symbol, ok := expect_token(p, .symbol); + basic.assert(ok, "expected name for procedure"); // @errors @todo(judah): lambdas + + t, ok = expect_token(p, .l_paren); + basic.assert(ok, "expected '(' but found '%'", t.str); // @errors + + while !at_end(p) { + t = peek_token(p); + if t.kind == .r_paren break; + + expr := parse_expression(p); + basic.assert(expr != null); // @errors + + t = peek_token(p); + if t.kind == { + case .comma; + consume_token(p); + continue; + case .r_paren; + break; + case; + basic.assert(false, "expected ',' or ')' but found '%'", t.str); + } + } + + _, ok = expect_token(p, .r_paren); + basic.assert(ok, "expected ')'"); // @errors + + block := parse_block(p); + basic.assert(block != null, "expected block"); // @errors + + node := make_node(p, Node_Procedure); + + return null; } return null; } +parse_block :: (p: *Parser) -> *Node_Block { + t, ok := expect_token(p, .kw_do); + basic.assert(ok, "expected 'do' found '%'", t.str); // @errors + + block := make_node(p, Node_Block); + array.init(*block.body, p.allocator); + + while !at_end(p) { + t = peek_token(p); + if t.kind == .kw_end break; + + node := parse_toplevel(p); + basic.assert(node != null); // @errors + + array.append(*block.body, node); + } + + t, ok = expect_token(p, .kw_end); + basic.assert(ok, "expected 'end' found '%'", t.str); // @errors + + return block; +} + parse_type_expression :: (p: *Parser) -> *Node_Type { t, ok := expect_token(p, .symbol, .star, .l_square); basic.assert(ok, "type expression"); // @errors @@ -596,6 +719,7 @@ consume_token :: (p: *Parser) -> Token { case "var"; t.kind = .kw_var; case "def"; t.kind = .kw_def; case "type"; t.kind = .kw_type; + case "fn"; t.kind = .kw_fn; case "do"; t.kind = .kw_do; case "end"; t.kind = .kw_end; case "if"; t.kind = .kw_if; From 7e685785bea435a46ad412bc1640e620d9ba4568 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Wed, 4 Jun 2025 15:35:08 -0600 Subject: [PATCH 12/18] . --- vm/parser.jai | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/vm/parser.jai b/vm/parser.jai index f0b01c7..d8537b8 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -85,22 +85,6 @@ Node :: struct { } } -Node_Print :: struct { - #as using n: Node; - n.kind = .print; - - expr: *Node; -} - -Node_Procedure_Call :: struct { - #as using n: Node; - n.kind = .procedure_call; - - call_expr: *Node; - named_arguments: kv.Kv(*Node, *Node); - all_arguments: [..]*Node; -} - Node_Var :: struct { #as using n: Node; n.kind = .variable; @@ -189,27 +173,23 @@ Node_Type :: struct { } } -/* -fn add(x int, y int) int do return x + y end -*/ - Node_Procedure :: struct { #as using n: Node; n.kind = .procedure; header: *Node_Procedure_Header; - args: [..]Node_Argument; - rets: [..]Node_Argument; + args: [..]Node_Parameter; + rets: [..]Node_Parameter; body: *Node_Block; flags: Flag; Flag :: enum_flags { - inline_; + must_inline; } } -Node_Argument :: struct { +Node_Parameter :: struct { symbol: *Node_Symbol; type: *Node_Type; value: *Node; // always an expression, can be null @@ -220,6 +200,22 @@ Node_Procedure_Header :: struct { rets: [..]*Node_Type; } +Node_Print :: struct { + #as using n: Node; + n.kind = .print; + + expr: *Node; +} + +Node_Procedure_Call :: struct { + #as using n: Node; + n.kind = .procedure_call; + + call_expr: *Node; + named_arguments: kv.Kv(*Node, *Node); + all_arguments: [..]*Node; +} + Node_Block :: struct { #as using n: Node; n.kind = .block; From b1a9e84d8b0a46ea64e8c1ee667ef156102bdb93 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Wed, 4 Jun 2025 22:59:22 -0600 Subject: [PATCH 13/18] [vm] more procedure things --- vm/interp.jai | 42 +++++++++++++++----- vm/module.jai | 4 +- vm/parser.jai | 106 +++++++++++++++++++++++++++++++------------------- 3 files changed, 101 insertions(+), 51 deletions(-) diff --git a/vm/interp.jai b/vm/interp.jai index ce755fc..0c9d27c 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -13,6 +13,8 @@ Interp_Value :: struct { u: u64; f: float64; s: string; + p: *void; + proc: *Node_Procedure; } Kind :: enum { @@ -22,6 +24,8 @@ Interp_Value :: struct { int; float; string; + pointer; + procedure; } } @@ -39,7 +43,7 @@ interp_program :: (i: *Interp) { for i.toplevel if it.kind == { case .variable; var := it.(*Node_Var); - sym := var.symbol.(*Node_Symbol); + sym := var.symbol; basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring symbol '%'", sym.str); // @errors value := value_nil; @@ -50,6 +54,15 @@ interp_program :: (i: *Interp) { kv.set(*i.symbols, sym.str, value); + case .procedure; + proc := it.(*Node_Procedure); + sym := proc.symbol; + basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring procedure '%'", sym.str); + + value := make_interp_value(i, .procedure); + value.proc = proc; + kv.set(*i.symbols, sym.str, value); + case .print; print := it.(*Node_Print); expr := interp_expr(i, print.expr); @@ -81,18 +94,27 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { // @temp sym := call.call_expr.(*Node_Symbol); basic.assert(sym.kind == .symbol); - if sym.str == { - case "add"; - basic.assert(args.count == 2, "mismatched number of arguments; require 2, given %", args.count); // @errors - node := mem.request_memory(Node_Binary,, allocator = basic.temp); - node.op = .{ kind = .plus, str = "+" }; - node.left = args[0]; - node.right = args[1]; - return interp_expr(i, node); + value, ok := kv.get(*i.symbols, sym.str); + basic.assert(ok, "procedure didn't exists '%'", sym.str); + basic.assert(value.kind == .procedure, "attempt to call non procedure '%'", sym.str); + + result := value_nil; + proc := value.proc; + + // @todo(judah): check arity, create scope, map args to locals, exec + for expr: proc.block.body { + if expr.kind == .return_ { + ret := expr.(*Node_Return); + if ret.values.count != 0 { + result = interp_expr(i, ret.values[0]); + } + + break; + } } - return value_nil; + return result; case .unary; do_unop :: (code: Code) #expand { diff --git a/vm/module.jai b/vm/module.jai index 4140cfb..ee9e815 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -24,8 +24,8 @@ strings :: #import "String"; // @future fn add(x, y) do return x + y end fn sub(x, y) do return x - y end - var x = 10.0 - var y = 20.0 + var x = 11.0 + var y = 22.0 var z = x + y * 2.0 / 3.0 var w = add(x, y) diff --git a/vm/parser.jai b/vm/parser.jai index d8537b8..460a8c3 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -89,8 +89,8 @@ Node_Var :: struct { #as using n: Node; n.kind = .variable; - symbol: *Node; // always *Node_Symbol - type_expr: *Node; // always *Node_Type + symbol: *Node_Symbol; + type_expr: *Node_Type; value_expr: *Node; var_flags: Var_Flag; @@ -158,18 +158,23 @@ Node_Type :: struct { type_kind: Type_Kind; union { - alias_target: *Node; + name_target: *Node_Symbol; pointer_target: *Node_Type; struct { array_element: *Node_Type; array_count: *Node; // can be null }; + struct { + procedure_arguments: [..]*Node_Type; + procedure_returns: [..]*Node_Type; + }; } Type_Kind :: enum { - alias; + named; pointer; array; + procedure; } } @@ -177,29 +182,25 @@ Node_Procedure :: struct { #as using n: Node; n.kind = .procedure; - header: *Node_Procedure_Header; + arguments: [..]Parameter; + returns: [..]Parameter; - args: [..]Node_Parameter; - rets: [..]Node_Parameter; - body: *Node_Block; - flags: Flag; + symbol: *Node_Symbol; // can be null + head: *Node_Type; // will always be of type .procedure + block: *Node_Block; + flags: Flag; Flag :: enum_flags { must_inline; } } -Node_Parameter :: struct { +Parameter :: struct { symbol: *Node_Symbol; type: *Node_Type; value: *Node; // always an expression, can be null } -Node_Procedure_Header :: struct { - args: [..]*Node_Type; - rets: [..]*Node_Type; -} - Node_Print :: struct { #as using n: Node; n.kind = .print; @@ -211,14 +212,15 @@ Node_Procedure_Call :: struct { #as using n: Node; n.kind = .procedure_call; - call_expr: *Node; + call_expr: *Node; named_arguments: kv.Kv(*Node, *Node); - all_arguments: [..]*Node; + all_arguments: [..]*Node; } Node_Block :: struct { #as using n: Node; n.kind = .block; + body: [..]*Node; } @@ -277,7 +279,7 @@ parse_toplevel :: (p: *Parser) -> *Node { s:, ok = expect_token(p, .symbol); basic.assert(ok, "symbol"); // @errors - type_expr: *Node; + type_expr: *Node_Type; value_expr: *Node; is_const := t.kind == .kw_def; @@ -316,7 +318,6 @@ parse_toplevel :: (p: *Parser) -> *Node { // return expr0, ..exprN case .kw_return; node := make_node(p, Node_Return); - array.init(*node.values, p.allocator); prev_offset := p.offset; expr := parse_expression(p); @@ -338,7 +339,7 @@ parse_toplevel :: (p: *Parser) -> *Node { node.expr = expr; return node; - // fn symbol(arg0, ..argN) do end + // fn symbol(arg0, ..argN) do ... end case .kw_fn; symbol, ok := expect_token(p, .symbol); basic.assert(ok, "expected name for procedure"); // @errors @todo(judah): lambdas @@ -346,12 +347,20 @@ parse_toplevel :: (p: *Parser) -> *Node { t, ok = expect_token(p, .l_paren); basic.assert(ok, "expected '(' but found '%'", t.str); // @errors + node := make_node(p, Node_Procedure); + node.symbol = make_node(p, Node_Symbol); + node.symbol.str = symbol.str; + while !at_end(p) { t = peek_token(p); if t.kind == .r_paren break; - expr := parse_expression(p); - basic.assert(expr != null); // @errors + sym, ok := expect_token(p, .symbol); + basic.assert(ok, "expected symbol"); // @errors + + arg := array.append(*node.arguments); + arg.symbol = make_node(p, Node_Symbol); + arg.symbol.str = sym.str; t = peek_token(p); if t.kind == { @@ -368,12 +377,10 @@ parse_toplevel :: (p: *Parser) -> *Node { _, ok = expect_token(p, .r_paren); basic.assert(ok, "expected ')'"); // @errors - block := parse_block(p); - basic.assert(block != null, "expected block"); // @errors + node.block = parse_block(p); + basic.assert(node.block != null, "expected block"); // @errors - node := make_node(p, Node_Procedure); - - return null; + return node; } return null; @@ -384,7 +391,6 @@ parse_block :: (p: *Parser) -> *Node_Block { basic.assert(ok, "expected 'do' found '%'", t.str); // @errors block := make_node(p, Node_Block); - array.init(*block.body, p.allocator); while !at_end(p) { t = peek_token(p); @@ -411,14 +417,12 @@ parse_type_expression :: (p: *Parser) -> *Node_Type { target := parse_type_expression(p); basic.assert(target != null, "pointer target"); // @errors - node := make_node(p, Node_Type); - node.type_kind = .pointer; + node := make_node(p, .pointer); node.pointer_target = target; return node; case .l_square; - node := make_node(p, Node_Type); - node.type_kind = .array; + node := make_node(p, .array); // slice if peek_token(p).kind == .r_square { @@ -449,9 +453,8 @@ parse_type_expression :: (p: *Parser) -> *Node_Type { symbol := make_node(p, Node_Symbol); symbol.str = t.str; - node := make_node(p, Node_Type); - node.type_kind = .alias; - node.alias_target = symbol; + node := make_node(p, .named); + node.name_target = symbol; return node; } @@ -540,9 +543,6 @@ parse_expression_postfix :: (p: *Parser) -> *Node { node := make_node(p, Node_Procedure_Call); node.call_expr = base; - array.init(*node.all_arguments, p.allocator); - kv.init(*node.named_arguments, p.allocator); - while !at_end(p) { t = peek_token(p); if t.kind == .r_paren break; @@ -641,7 +641,35 @@ parse_expression_base :: (p: *Parser) -> *Node { } make_node :: (p: *Parser, $T: Type) -> *T { - return mem.request_memory(T,, allocator = p.allocator); + node := mem.request_memory(T,, allocator = p.allocator); + + #if T == { // nodes that require initialization + case Node_Block; + array.init(*node.body, p.allocator); + case Node_Return; + array.init(*node.values, p.allocator); + case Node_Procedure_Call; + array.init(*node.all_arguments, p.allocator); + kv.init(*node.named_arguments, p.allocator); + case Node_Procedure; + array.init(*node.arguments, p.allocator); + array.init(*node.returns, p.allocator); + } + + return node; +} + +make_node :: (p: *Parser, $type_kind: Node_Type.Type_Kind) -> *Node_Type { + type := mem.request_memory(Node_Type,, allocator = p.allocator); + type.type_kind = type_kind; + + #if type_kind == { + case .procedure; + array.init(*type.procedure_arguments, p.allocator); + array.init(*type.procedure_returns, p.allocator); + } + + return type; } peek_token :: (p: *Parser) -> Token { From 3bfa0679aed1c266db9102ad4d3c91ae61e45e24 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Thu, 26 Jun 2025 13:43:00 -0600 Subject: [PATCH 14/18] basic procedures --- bytes/buffer.jai | 45 ++++++++ bytes/module.jai | 9 ++ jc.jai | 1 + thirdparty/luajit/x/module.jai | 190 +++++++++++++++++++++++++++++++++ vm/interp.jai | 64 ++++++++--- vm/module.jai | 9 +- 6 files changed, 294 insertions(+), 24 deletions(-) create mode 100644 bytes/buffer.jai create mode 100644 bytes/module.jai create mode 120000 jc.jai create mode 100644 thirdparty/luajit/x/module.jai diff --git a/bytes/buffer.jai b/bytes/buffer.jai new file mode 100644 index 0000000..a18c672 --- /dev/null +++ b/bytes/buffer.jai @@ -0,0 +1,45 @@ +Buffer :: struct { + allocator: Allocator; + + data: [..]u8; + count: int; +} + +append :: inline (buf: *Buffer, ptr: *void, count: int) { + free_space := ensure_buffer_has_room(buf, count); + memcpy(free_space, ptr, count); + buf.count += count; +} + +append :: inline (buf: *Buffer, data: []u8) { + append(buf, data.data, data.count); +} + +append :: inline (buf: *Buffer, ptr: *$T) { + append(buf, ptr, size_of(T)); +} + +reset :: inline (buf: *Buffer) { + buf.count = 0; +} + +#scope_file; + +array :: #import "jc/array"; + +ensure_buffer_has_room :: (buf: *Buffer, count: int) -> *u8 { + ptr := buf.data.data + buf.count; + if buf.count + count >= buf.data.allocated { + array.resize(*buf.data, buf.data.allocated * 2); + ptr = buf.data.data + buf.count; + buf.data.count = buf.data.allocated; + } + + return ptr; +} + +#if RUN_TESTS #run { + test.run("basic operations", t => { + buf: Buffer; + }); +} diff --git a/bytes/module.jai b/bytes/module.jai new file mode 100644 index 0000000..4c3db65 --- /dev/null +++ b/bytes/module.jai @@ -0,0 +1,9 @@ +#module_parameters(RUN_TESTS := false); + +#load "buffer.jai"; + +#scope_module; + +#if RUN_TESTS { + test :: #import "jc/test"; +} diff --git a/jc.jai b/jc.jai new file mode 120000 index 0000000..501958c --- /dev/null +++ b/jc.jai @@ -0,0 +1 @@ +jc.jai/ \ No newline at end of file diff --git a/thirdparty/luajit/x/module.jai b/thirdparty/luajit/x/module.jai new file mode 100644 index 0000000..1eb5cda --- /dev/null +++ b/thirdparty/luajit/x/module.jai @@ -0,0 +1,190 @@ +#module_parameters(STATIC := true); + +c_context: #Context; + +#scope_export; + +using #import,file "../module.jai"(STATIC = STATIC); + +expose :: (L: *State, proc: $T, $caller_code := #caller_code, $loc := #caller_location) +#modify { + return T.(*Type_Info).type == .PROCEDURE, "expose must take a procedure"; +} #expand { + #insert -> string { + info := T.(*Type_Info_Procedure); + code := compiler_get_nodes(caller_code); + call := code.(*Code_Procedure_Call); + + proc := call.arguments_sorted[1].(*Code_Ident); + basic.assert(proc.kind == .IDENT, "must be an identifier", loc = loc); + + body_builder: basic.String_Builder; + args_builder: basic.String_Builder; + call_builder: basic.String_Builder; + rets_builder: basic.String_Builder; + + if info.return_types.count != 0 { + basic.append(*call_builder, "\t\t\t\t"); + + for info.return_types { + basic.print_to_builder(*call_builder, "r%", it_index); + if it_index < info.return_types.count -1 { + basic.append(*call_builder, ", "); + } + } + + basic.append(*call_builder, " := "); + } + + basic.append(*call_builder, "proc("); + + for < info.argument_types { + index := -(it_index + 1); + basic.print_to_builder(*args_builder, "\t\t\ta% := %;\n", it_index, gen_stack_pull(it, index)); + + basic.print_to_builder(*call_builder, "a%", it_index); + if it_index > 0 { + basic.append(*call_builder, ", "); + } + } + + basic.append(*call_builder, ");"); + + for info.return_types { + basic.print_to_builder(*rets_builder, "\t\t\t\t%;", gen_stack_push(it, it_index)); + if it_index < info.return_types.count - 1 { + basic.append(*rets_builder, "\n"); + } + } + + return basic.sprint(#string END + lua_name :: "%1"; + lua_wrapper :: (L: *State) -> s32 #c_call { +%2 + push_context c_context { +%3 +%4 + } + + return %5; + } + END, + proc.name, + basic.builder_to_string(*args_builder), + basic.builder_to_string(*call_builder), + basic.builder_to_string(*rets_builder), + info.return_types.count, + ); + } + + info := T.(*Type_Info_Procedure); + pushcclosure(L, lua_wrapper, info.argument_types.count.(s32)); + setfield(L, LUA_GLOBALSINDEX, lua_name); +} + + +#scope_file; + +gen_stack_pull :: (info: *Type_Info, index: int) -> string { + if info.type == { + case .BOOL; + return basic.tprint("get_lua_bool(L, %)", index); + case .INTEGER; + return basic.tprint("tointeger(L, %)", index); + case .FLOAT; + return basic.tprint("tonumber(L, %)", index); + case .STRING; + return basic.tprint("get_lua_string(L, %)", index); + case .STRUCT; + return basic.tprint("get_lua_table(L, %, %)", info.(*Type_Info_Struct).name, index); + + case; + basic.assert(false, "% (%)", info.type, index); + } + + return ""; +} + +gen_stack_push :: (info: *Type_Info, index: int) -> string { + if info.type == { + case .BOOL; + return basic.tprint("pushboolean(L, r%.(s32))", index); + case .INTEGER; + return basic.tprint("pushinteger(L, r%)", index); + case; + basic.assert(false, "% (%)", info.type, index); + } + + return ""; +} + +get_lua_string :: inline (L: *State, index: s32) -> string #c_call { + len: u64; + ptr := tolstring(L, index, *len); + return string.{ data = ptr, count = xx len }; +} + +get_lua_bool :: inline (L: *State, index: s32) -> bool #c_call { + return toboolean(L, index) == 1; +} + +get_lua_table :: (L: *State, $T: Type, index: s32) -> T #c_call { + push_context c_context { + return get_lua_table(L, T.(*Type_Info_Struct), index).(*T).*; + } +} + +temp_storage: [4096]u8; +temp_offset: int; + +get_lua_table :: (L: *State, info: *Type_Info_Struct, index: s32) -> *void { + res := temp_storage.data + temp_offset; + memset(res, 0, info.runtime_size); + + temp_offset = (temp_offset + info.runtime_size) % temp_storage.count; + + for info.members { + defer settop(L, -2); + pushlstring(L, it.name.data, xx it.name.count); + rawget(L, -2); + + vp: *void; + if it.type.type == { + case .BOOL; + v := get_lua_bool(L, -1); + vp = *v; + case .INTEGER; + v := tointeger(L, -1); + if it.type.runtime_size == { + case 1; vp = *(v.(u8, no_check)); + case 2; vp = *(v.(u16, no_check)); + case 4; vp = *(v.(u32, no_check)); + case 8; vp = *(v.(u64, no_check)); + } + case .FLOAT; + v := tonumber(L, -1); + if it.type.runtime_size == { + case 4; vp = *(v.(float32, no_check)); + case 8; vp = *(v.(float64, no_check)); + } + case .STRING; + v := get_lua_string(L, -1); + vp = *v; + case .STRUCT; + v := get_lua_table(L, it.type.(*Type_Info_Struct), -1); + vp = *v; + + case; + basic.assert(false, "% (%)", info.type, index); + } + + memcpy(res + it.offset_in_bytes, vp, it.type.runtime_size); + } + + return res; +} + + +basic :: #import "Basic"; // @future + +#import "Compiler"; diff --git a/vm/interp.jai b/vm/interp.jai index 0c9d27c..cbc9a12 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -1,8 +1,13 @@ Interp :: struct { allocator: Allocator; - symbols: kv.Kv(string, *Interp_Value); toplevel: []*Node; + global: *Interp_Scope; +} + +Interp_Scope :: struct { + parent: *Interp_Scope; + bindings: kv.Kv(string, *Interp_Value); } Interp_Value :: struct { @@ -37,35 +42,39 @@ init :: (i: *Interp, allocator: Allocator) { value_false = make_interp_value(i, .bool); value_false.b = false; + + i.global = make_scope(null,, allocator = allocator); } interp_program :: (i: *Interp) { + scope := i.global; + for i.toplevel if it.kind == { case .variable; var := it.(*Node_Var); sym := var.symbol; - basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring symbol '%'", sym.str); // @errors + basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring symbol '%'", sym.str); // @errors value := value_nil; if var.value_expr != null { - value = interp_expr(i, var.value_expr); + value = interp_expr(i, var.value_expr, i.global); basic.assert(value != null); // @errors } - kv.set(*i.symbols, sym.str, value); + kv.set(*scope.bindings, sym.str, value); case .procedure; proc := it.(*Node_Procedure); sym := proc.symbol; - basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring procedure '%'", sym.str); + basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring procedure '%'", sym.str); value := make_interp_value(i, .procedure); value.proc = proc; - kv.set(*i.symbols, sym.str, value); + kv.set(*scope.bindings, sym.str, value); case .print; print := it.(*Node_Print); - expr := interp_expr(i, print.expr); + expr := interp_expr(i, print.expr, i.global); if expr == null continue; if expr.kind == { @@ -85,7 +94,7 @@ interp_program :: (i: *Interp) { } } -interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { +interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { if expr.kind == { case .procedure_call; call := expr.(*Node_Procedure_Call); @@ -95,19 +104,24 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { sym := call.call_expr.(*Node_Symbol); basic.assert(sym.kind == .symbol); - value, ok := kv.get(*i.symbols, sym.str); - basic.assert(ok, "procedure didn't exists '%'", sym.str); + value := find_symbol(scope, sym.str); + basic.assert(value != null, "procedure didn't exists '%'", sym.str); basic.assert(value.kind == .procedure, "attempt to call non procedure '%'", sym.str); result := value_nil; proc := value.proc; + basic.assert(proc.arguments.count == args.count, "argument mismatch. expected %, given %", proc.arguments.count, args.count); // @errors + + proc_scope := make_scope(scope); + for proc.arguments { + kv.set(*proc_scope.bindings, it.symbol.str, interp_expr(i, args[it_index], scope)); + } - // @todo(judah): check arity, create scope, map args to locals, exec for expr: proc.block.body { if expr.kind == .return_ { ret := expr.(*Node_Return); if ret.values.count != 0 { - result = interp_expr(i, ret.values[0]); + result = interp_expr(i, ret.values[0], proc_scope); } break; @@ -131,7 +145,7 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { } un := expr.(*Node_Unary); - rhs := interp_expr(i, un.right); + rhs := interp_expr(i, un.right, scope); res := make_interp_value(i, rhs.kind); if un.op.kind == { case .plus; do_unop(#code ifx right < 0 then -right else right); @@ -143,8 +157,8 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { case .binary; bin := expr.(*Node_Binary); - lhs := interp_expr(i, bin.left); - rhs := interp_expr(i, bin.right); + lhs := interp_expr(i, bin.left, scope); + rhs := interp_expr(i, bin.right, scope); basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors do_binop :: (code: Code) #expand { @@ -182,8 +196,8 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { case .symbol; sym := expr.(*Node_Symbol); - value, ok := kv.get(*i.symbols, sym.str); - basic.assert(ok, "use of undeclared symbol '%'", sym.str); // @errors + value := find_symbol(scope, sym.str); + basic.assert(value != null, "use of undeclared symbol '%'", sym.str); // @errors return value; case .literal; @@ -214,6 +228,22 @@ value_nil: *Interp_Value; value_true: *Interp_Value; value_false: *Interp_Value; +find_symbol :: (scope: *Interp_Scope, symbol: string, all_the_way_up := true) -> *Interp_Value { + value, ok := kv.get(*scope.bindings, symbol); + if !ok && (all_the_way_up && scope.parent != null) { + return find_symbol(scope.parent, symbol, true); + } + + return value; +} + +make_scope :: (parent: *Interp_Scope) -> *Interp_Scope { + scope := mem.request_memory(Interp_Scope); + scope.parent = parent; + kv.init(*scope.bindings, context.allocator); + return scope; +} + make_interp_value :: (i: *Interp, kind: Interp_Value.Kind) -> *Interp_Value { value := mem.request_memory(Interp_Value,, allocator = i.allocator); value.kind = kind; diff --git a/vm/module.jai b/vm/module.jai index ee9e815..21f78f9 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -26,14 +26,9 @@ strings :: #import "String"; // @future var x = 11.0 var y = 22.0 - var z = x + y * 2.0 / 3.0 - var w = add(x, y) - print x // 10 - print y // 20 - print z // 23.3 - print w // 30 - // print z + print add(x, y) + print add(x+1.0, y) END); interp: Interp; From 3f81359ab15e3f61e21008c5b1198ae5a13e5b73 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Thu, 26 Jun 2025 21:09:03 -0600 Subject: [PATCH 15/18] improvements --- vm/interp.jai | 62 ++++++++++++++++++++++++++++++++++++++++++--------- vm/module.jai | 6 ++--- vm/parser.jai | 58 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 105 insertions(+), 21 deletions(-) diff --git a/vm/interp.jai b/vm/interp.jai index cbc9a12..0b1a15f 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -20,6 +20,7 @@ Interp_Value :: struct { s: string; p: *void; proc: *Node_Procedure; + val: *Interp_Value; } Kind :: enum { @@ -31,6 +32,7 @@ Interp_Value :: struct { string; pointer; procedure; + value; } } @@ -47,24 +49,44 @@ init :: (i: *Interp, allocator: Allocator) { } interp_program :: (i: *Interp) { - scope := i.global; + for i.toplevel { + interp_statement(i, it, i.global); + } +} - for i.toplevel if it.kind == { +interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { + if stmt.kind == { case .variable; - var := it.(*Node_Var); + var := stmt.(*Node_Var); sym := var.symbol; basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring symbol '%'", sym.str); // @errors value := value_nil; if var.value_expr != null { - value = interp_expr(i, var.value_expr, i.global); + value = interp_expr(i, var.value_expr, scope); basic.assert(value != null); // @errors } kv.set(*scope.bindings, sym.str, value); + case .assign; + assign := stmt.(*Node_Assign); + + src := interp_expr(i, assign.src, scope); + basic.assert(src != null); // @errors + + dst := interp_lvalue(i, assign.dst, scope); + basic.assert(dst != null); // @errors + basic.assert(dst.kind == .value); // @errors + + // @todo: typechecking + if assign.op.kind == { + case .equal; + dst.val.* = src.*; + } + case .procedure; - proc := it.(*Node_Procedure); + proc := stmt.(*Node_Procedure); sym := proc.symbol; basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring procedure '%'", sym.str); @@ -73,9 +95,9 @@ interp_program :: (i: *Interp) { kv.set(*scope.bindings, sym.str, value); case .print; - print := it.(*Node_Print); - expr := interp_expr(i, print.expr, i.global); - if expr == null continue; + print := stmt.(*Node_Print); + expr := interp_expr(i, print.expr, scope); + if expr == null return; if expr.kind == { case .none; // do nothing @@ -90,10 +112,27 @@ interp_program :: (i: *Interp) { basic.print("\n"); case; - basic.assert(false, "unhandled node kind: %", it.kind); // @errors + interp_expr(i, stmt, scope); + // basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors } } +interp_lvalue :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { + if expr.kind == { + case .symbol; + sym := expr.(*Node_Symbol); + lval := find_symbol(scope, sym.str); + oval := make_interp_value(i, .value); + oval.val = lval; + return oval; + + case; + basic.assert(false, "unable to get lvalue from %", expr.kind); + } + + return null; +} + interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { if expr.kind == { case .procedure_call; @@ -102,7 +141,7 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value // @temp sym := call.call_expr.(*Node_Symbol); - basic.assert(sym.kind == .symbol); + basic.assert(sym.kind == .symbol, "%", sym.kind); value := find_symbol(scope, sym.str); basic.assert(value != null, "procedure didn't exists '%'", sym.str); @@ -126,6 +165,9 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value break; } + else { + interp_statement(i, expr, proc_scope); + } } return result; diff --git a/vm/module.jai b/vm/module.jai index 21f78f9..324dcfe 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -21,14 +21,14 @@ strings :: #import "String"; // @future init(*parser, context.allocator); ok := parse_string(*parser, #string END - fn add(x, y) do return x + y end + fn add(a, b) do return a + b end fn sub(x, y) do return x - y end var x = 11.0 var y = 22.0 - print add(x, y) - print add(x+1.0, y) + add(1.0, 1.0) + // print add(x+1.0, y) END); interp: Interp; diff --git a/vm/parser.jai b/vm/parser.jai index 460a8c3..1f0ca5b 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -67,6 +67,7 @@ Node :: struct { stmt_start; print; return_; + assign; stmt_end; decl_start; @@ -99,6 +100,15 @@ Node_Var :: struct { } } +Node_Assign :: struct { + #as using n: Node; + n.kind = .assign; + + op: Token; + dst: *Node; + src: *Node; +} + Node_Unary :: struct { #as using n: Node; n.kind = .unary; @@ -256,7 +266,7 @@ parse_string :: (p: *Parser, source: string) -> bool { break; } - node := parse_toplevel(p); + node := parse_statement(p); if node != null array.append(*p.toplevel, node); } @@ -266,17 +276,17 @@ parse_string :: (p: *Parser, source: string) -> bool { #scope_file; -parse_toplevel :: (p: *Parser) -> *Node { - t, ok := expect_token(p, .kw_var, .kw_def, .kw_fn, .kw_print, .kw_do, .kw_return); - basic.assert(ok, "var, def, print, found '%'", t.str); // @errors - +parse_statement :: (p: *Parser) -> *Node { + t := peek_token(p); if t.kind == { // var sym type_expr // var sym type_expr = expr // var sym = expr case .kw_var; #through; case .kw_def; - s:, ok = expect_token(p, .symbol); + consume_token(p); + + s, ok := expect_token(p, .symbol); basic.assert(ok, "symbol"); // @errors type_expr: *Node_Type; @@ -317,6 +327,8 @@ parse_toplevel :: (p: *Parser) -> *Node { // return // return expr0, ..exprN case .kw_return; + consume_token(p); + node := make_node(p, Node_Return); prev_offset := p.offset; @@ -332,6 +344,8 @@ parse_toplevel :: (p: *Parser) -> *Node { // print(expr) // print expr case .kw_print; + consume_token(p); + expr := parse_expression(p); basic.assert(expr != null, "expected expression"); // @errors @@ -341,6 +355,8 @@ parse_toplevel :: (p: *Parser) -> *Node { // fn symbol(arg0, ..argN) do ... end case .kw_fn; + consume_token(p); + symbol, ok := expect_token(p, .symbol); basic.assert(ok, "expected name for procedure"); // @errors @todo(judah): lambdas @@ -381,9 +397,35 @@ parse_toplevel :: (p: *Parser) -> *Node { basic.assert(node.block != null, "expected block"); // @errors return node; + + // do ... end + case .kw_do; + return parse_block(p); } - return null; + return parse_simple_statement(p); +} + +parse_simple_statement :: (p: *Parser) -> *Node { + dst := parse_expression(p); + basic.assert(dst != null, "expected expression for simple statement"); + + t := peek_token(p); + if t.kind == { + case .equal; + consume_token(p); + + src := parse_expression(p); + basic.assert(src != null, "expected right-hand side of assignment"); + + node := make_node(p, Node_Assign); + node.op = t; + node.dst = dst; + node.src = src; + return node; + } + + return dst; } parse_block :: (p: *Parser) -> *Node_Block { @@ -396,7 +438,7 @@ parse_block :: (p: *Parser) -> *Node_Block { t = peek_token(p); if t.kind == .kw_end break; - node := parse_toplevel(p); + node := parse_statement(p); basic.assert(node != null); // @errors array.append(*block.body, node); From 1d82f6f5499ebb66bae73cbd9185bc83f805ea38 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Fri, 27 Jun 2025 15:39:24 -0600 Subject: [PATCH 16/18] . --- vm/module.jai | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/vm/module.jai b/vm/module.jai index 324dcfe..4992ae4 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -21,14 +21,21 @@ strings :: #import "String"; // @future init(*parser, context.allocator); ok := parse_string(*parser, #string END - fn add(a, b) do return a + b end - fn sub(x, y) do return x - y end + fn add(l, r) do return l + r end + fn sub(l, r) do return l - r end + fn mul(l, r) do return l * r end + fn div(l, r) do return l / r end - var x = 11.0 + var x = 21.0 var y = 22.0 - add(1.0, 1.0) - // print add(x+1.0, y) + x = x + 1.0 / 2.0 + print x + + x = add(x, div(1.0, 2.0)) + print x + + print add(x, y) END); interp: Interp; From d58f238fc9731b21910003ef4a05cad1d7d59ece Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Fri, 27 Jun 2025 22:56:59 -0600 Subject: [PATCH 17/18] comparison and assignment operators --- vm/interp.jai | 61 +++++++++++++++++------- vm/module.jai | 8 +++- vm/parser.jai | 128 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 160 insertions(+), 37 deletions(-) diff --git a/vm/interp.jai b/vm/interp.jai index 0b1a15f..6c1762a 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -63,7 +63,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { value := value_nil; if var.value_expr != null { - value = interp_expr(i, var.value_expr, scope); + value = interp_expression(i, var.value_expr, scope); basic.assert(value != null); // @errors } @@ -72,7 +72,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { case .assign; assign := stmt.(*Node_Assign); - src := interp_expr(i, assign.src, scope); + src := interp_expression(i, assign.src, scope); basic.assert(src != null); // @errors dst := interp_lvalue(i, assign.dst, scope); @@ -80,10 +80,8 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { basic.assert(dst.kind == .value); // @errors // @todo: typechecking - if assign.op.kind == { - case .equal; - dst.val.* = src.*; - } + basic.assert(assign.op.kind == .equal, "unexpected assign op: %", assign.op.kind); + dst.val.* = src.*; case .procedure; proc := stmt.(*Node_Procedure); @@ -96,7 +94,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { case .print; print := stmt.(*Node_Print); - expr := interp_expr(i, print.expr, scope); + expr := interp_expression(i, print.expr, scope); if expr == null return; if expr.kind == { @@ -111,8 +109,15 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) { basic.print("\n"); + case .assert; + assert := stmt.(*Node_Assert); + expr := interp_expression(i, assert.expr, scope); + basic.assert(expr != null, "runtime assertion failed"); + basic.assert(expr.kind == .bool, "non-boolean expression given to assert"); + basic.assert(expr.b, "runtime assertion failed"); + case; - interp_expr(i, stmt, scope); + interp_expression(i, stmt, scope); // basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors } } @@ -133,7 +138,7 @@ interp_lvalue :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Valu return null; } -interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { +interp_expression :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { if expr.kind == { case .procedure_call; call := expr.(*Node_Procedure_Call); @@ -153,14 +158,14 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value proc_scope := make_scope(scope); for proc.arguments { - kv.set(*proc_scope.bindings, it.symbol.str, interp_expr(i, args[it_index], scope)); + kv.set(*proc_scope.bindings, it.symbol.str, interp_expression(i, args[it_index], scope)); } for expr: proc.block.body { if expr.kind == .return_ { ret := expr.(*Node_Return); if ret.values.count != 0 { - result = interp_expr(i, ret.values[0], proc_scope); + result = interp_expression(i, ret.values[0], proc_scope); } break; @@ -187,7 +192,7 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value } un := expr.(*Node_Unary); - rhs := interp_expr(i, un.right, scope); + rhs := interp_expression(i, un.right, scope); res := make_interp_value(i, rhs.kind); if un.op.kind == { case .plus; do_unop(#code ifx right < 0 then -right else right); @@ -199,8 +204,8 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value case .binary; bin := expr.(*Node_Binary); - lhs := interp_expr(i, bin.left, scope); - rhs := interp_expr(i, bin.right, scope); + lhs := interp_expression(i, bin.left, scope); + rhs := interp_expression(i, bin.right, scope); basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors do_binop :: (code: Code) #expand { @@ -220,16 +225,38 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value res := make_interp_value(i, lhs.kind); if bin.op.kind == { - case .plus; do_binop(#code left + right); - case .minus; do_binop(#code left - right); - case .star; do_binop(#code left * right); + case .plus; do_binop(#code left + right); + case .minus; do_binop(#code left - right); + case .star; do_binop(#code left * right); + case .f_slash; basic.assert(rhs.i != 0, "divide by zero"); // @errors do_binop(#code left / right); + case .percent; basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind); res.i = lhs.i % rhs.i; + // @todo: typechecking + case .equal_equal; + res.kind = .bool; + res.b = lhs.i == rhs.i; + case .bang_equal; + res.kind = .bool; + res.b = lhs.i != rhs.i; + case .less; + res.kind = .bool; + res.b = lhs.i < rhs.i; + case .less_equal; + res.kind = .bool; + res.b = lhs.i <= rhs.i; + case .more; + res.kind = .bool; + res.b = lhs.i > rhs.i; + case .more_equal; + res.kind = .bool; + res.b = lhs.i >= rhs.i; + case; basic.assert(false, "unhandled binary operator '%'", bin.op.str); } diff --git a/vm/module.jai b/vm/module.jai index 4992ae4..3ffe284 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -28,6 +28,7 @@ strings :: #import "String"; // @future var x = 21.0 var y = 22.0 + var z = x + y x = x + 1.0 / 2.0 print x @@ -35,7 +36,12 @@ strings :: #import "String"; // @future x = add(x, div(1.0, 2.0)) print x - print add(x, y) + print x == x + print x == y + print x == z + + assert x == y + assert x != z END); interp: Interp; diff --git a/vm/parser.jai b/vm/parser.jai index 1f0ca5b..293b178 100644 --- a/vm/parser.jai +++ b/vm/parser.jai @@ -31,6 +31,23 @@ Token :: struct { kw_false; kw_print; + kw_assert; + + equal_equal; // == + bang_equal; // != + and_equal; // &= + or_equal; // |= + less_equal; // <= + more_equal; // >= + + plus_equal; // += + minus_equal; // -= + star_equal; // *= + f_slash_equal; // /= + percent_equal; // %= + + and_and_equal; // &&= + or_or_equal; // ||= equal :: #char "="; plus :: #char "+"; @@ -39,8 +56,11 @@ Token :: struct { percent :: #char "%"; bang :: #char "!"; and :: #char "&"; + or :: #char "|"; f_slash :: #char "/"; b_slash :: #char "\\"; + less :: #char "<"; + more :: #char ">"; l_paren :: #char "("; r_paren :: #char ")"; @@ -52,6 +72,7 @@ Token :: struct { dot :: #char "."; colon :: #char ":"; semicolon :: #char ";"; + } } @@ -66,6 +87,7 @@ Node :: struct { stmt_start; print; + assert; return_; assign; stmt_end; @@ -218,6 +240,13 @@ Node_Print :: struct { expr: *Node; } +Node_Assert :: struct { + #as using n: Node; + n.kind = .assert; + + expr: *Node; +} + Node_Procedure_Call :: struct { #as using n: Node; n.kind = .procedure_call; @@ -353,6 +382,18 @@ parse_statement :: (p: *Parser) -> *Node { node.expr = expr; return node; + // assert(cond) + // assert cond + case .kw_assert; + consume_token(p); + + expr := parse_expression(p); + basic.assert(expr != null, "expected expression"); // @errors + + node := make_node(p, Node_Assert); + node.expr = expr; + return node; + // fn symbol(arg0, ..argN) do ... end case .kw_fn; consume_token(p); @@ -412,7 +453,12 @@ parse_simple_statement :: (p: *Parser) -> *Node { t := peek_token(p); if t.kind == { - case .equal; + case .equal; #through; + case .plus_equal; #through; + case .minus_equal; #through; + case .star_equal; #through; + case .f_slash_equal; #through; + case .percent_equal; consume_token(p); src := parse_expression(p); @@ -421,7 +467,29 @@ parse_simple_statement :: (p: *Parser) -> *Node { node := make_node(p, Node_Assign); node.op = t; node.dst = dst; - node.src = src; + + if t.kind == .equal { + node.src = src; + } + // transform these into 'dst = dst op src' + else { + bin := make_node(p, Node_Binary); + bin.left = dst; + bin.right = src; + bin.op = t; + + if t.kind == { + case .plus_equal; bin.op.kind = .plus; + case .minus_equal; bin.op.kind = .minus; + case .star_equal; bin.op.kind = .star; + case .f_slash_equal; bin.op.kind = .f_slash; + case .percent_equal; bin.op.kind = .percent; + } + + node.op.kind = .equal; + node.src = bin; + } + return node; } @@ -516,13 +584,13 @@ parse_expression :: (p: *Parser, min_precedence := 1) -> *Node { case .minus; return 3; - // case .equal_equal; #through; - // case .bang_equal; #through; - // case .less; #through; - // case .less_equal; #through; - // case .more; #through; - // case .more_equal; - // return 2; + case .equal_equal; #through; + case .bang_equal; #through; + case .less; #through; + case .less_equal; #through; + case .more; #through; + case .more_equal; + return 2; } return 0; @@ -802,8 +870,9 @@ consume_token :: (p: *Parser) -> Token { case "true"; t.kind = .kw_true; case "false"; t.kind = .kw_false; - case "print"; t.kind = .kw_print; - case; t.kind = .symbol; + case "print"; t.kind = .kw_print; + case "assert"; t.kind = .kw_assert; + case; t.kind = .symbol; } return t; @@ -821,15 +890,36 @@ consume_token :: (p: *Parser) -> Token { return t; } + with_optional_equal :: (current_kind: Token.Kind, equal_kind: Token.Kind) -> Token #expand { + token := Token.{ + kind = current_kind, + str = string.{ data = `p.source.data + `p.offset, count = 1 }, + }; + + `p.offset += 1; + if `p.offset < `p.source.count && `p.source[`p.offset] == #char "=" { + `p.offset += 1; + + token.kind = equal_kind; + token.str.count = 2; + } + + return token; + } + if c == { - case "+"; #through; - case "-"; #through; - case "*"; #through; - case "/"; #through; - case "="; #through; - case "%"; #through; - case "!"; #through; - case "&"; #through; + case "+"; return with_optional_equal(c.(Token.Kind), .plus_equal); + case "-"; return with_optional_equal(c.(Token.Kind), .minus_equal); + case "*"; return with_optional_equal(c.(Token.Kind), .star_equal); + case "/"; return with_optional_equal(c.(Token.Kind), .f_slash_equal); + case "="; return with_optional_equal(c.(Token.Kind), .equal_equal); + case "%"; return with_optional_equal(c.(Token.Kind), .percent_equal); + case "!"; return with_optional_equal(c.(Token.Kind), .bang_equal); + case "&"; return with_optional_equal(c.(Token.Kind), .and_equal); + case "|"; return with_optional_equal(c.(Token.Kind), .or_equal); + case "<"; return with_optional_equal(c.(Token.Kind), .less_equal); + case ">"; return with_optional_equal(c.(Token.Kind), .more_equal); + case ","; #through; case "."; #through; case ":"; #through; From 5038517d72c2b3bed9e6224bea66bbb14230ee77 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Sat, 28 Jun 2025 20:54:33 -0600 Subject: [PATCH 18/18] random --- vm/module.jai | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/vm/module.jai b/vm/module.jai index 3ffe284..e99e3f9 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -42,6 +42,47 @@ strings :: #import "String"; // @future assert x == y assert x != z + + // def ( + // Add = poly[T] proc(x T, y T) T do return x + y end + // Sub = poly[T] proc(x T, y T) T do return x - y end + // Mul = poly[T] proc(x T, y T) T do return x * y end + // Div = poly[T] proc(x T, y T) T do return x / y end + // ) + + // def ( + // Addi = Add[int] + // Addf = Add[float] + // Subi = Sub[int] + // Subf = Sub[float] + // Muli = Mul[int] + // Mulf = Mul[float] + // Divi = Div[int] + // Divf = Div[float] + // ) + + // def Foo = struct { + // x int = 1 + // y int = 2 + // z int = 3 + // } + + // def Value = union { + // i int + // f float + // b bool + // } + + // def Kind = enum { + // a + // b + // c + // d + // } + + // var foo = Foo{ x = 10, y = 20, z = 30 } + // var val = Value{ f = 3.14 } + // var kind = Kind.a END); interp: Interp;