expression parsing and basic tree-walk interpreter

This commit is contained in:
Judah Caruso 2025-06-02 19:21:34 -06:00
parent f954a8276d
commit 5ed453a0fc
3 changed files with 585 additions and 76 deletions

181
vm/interp.jai Normal file
View file

@ -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;
}

View file

@ -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; #scope_file;
#if RUN_TESTS { #run {
test :: #import "jc/test"; 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";
// }

View file

@ -15,11 +15,29 @@ Token :: struct {
kw_type; kw_type;
kw_do; kw_do;
kw_end; 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 "="; equal :: #char "=";
plus :: #char "+"; plus :: #char "+";
minus :: #char "-"; minus :: #char "-";
star :: #char "*"; star :: #char "*";
percent :: #char "%";
bang :: #char "!";
and :: #char "&";
f_slash :: #char "/"; f_slash :: #char "/";
b_slash :: #char "\\"; b_slash :: #char "\\";
@ -38,28 +56,65 @@ Token :: struct {
Node :: struct { Node :: struct {
kind: Kind; kind: Kind;
type: *Type_Info;
Kind :: enum { Kind :: enum {
invalid; invalid;
stmt_start;
print;
stmt_end;
decl_start; decl_start;
var; variable;
decl_end; decl_end;
expr_start; expr_start;
type;
unary;
binary;
symbol; symbol;
literal; literal;
expr_end; expr_end;
} }
} }
Node_Print :: struct {
#as using n: Node;
n.kind = .print;
expr: *Node;
}
Node_Var :: struct { Node_Var :: struct {
#as using n: Node; #as using n: Node;
n.kind = .var; n.kind = .variable;
symbol: *Node; symbol: *Node; // always *Node_Symbol
type_expr: *Node; type_expr: *Node; // always *Node_Type
value_expr: *Node; 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 { Node_Symbol :: struct {
@ -73,7 +128,49 @@ Node_Literal :: struct {
#as using n: Node; #as using n: Node;
n.kind = .literal; n.kind = .literal;
value_kind: Value_Kind;
value_flags: Value_Flag;
union { 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; break;
} }
node := parse_toplevel_declaration(p); node := parse_toplevel(p);
if node == null break; if node == null break;
array.append(*p.toplevel, node); array.append(*p.toplevel, node);
if node.kind == {
case .variable;
}
} }
return false; return false;
@ -114,32 +215,38 @@ parse_string :: (p: *Parser, source: string) -> bool {
#scope_file; #scope_file;
parse_toplevel_declaration :: (p: *Parser) -> *Node { parse_toplevel :: (p: *Parser) -> *Node {
t, ok := expect_token(p, .kw_var); t, ok := expect_token(p, .kw_var, .kw_def, .kw_print);
if ok == false return null; basic.assert(ok, "var, def, print"); // @errors
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); s:, ok = expect_token(p, .symbol);
if ok == false return null; basic.assert(ok, "symbol"); // @errors
type_expr: *Node; type_expr: *Node;
value_expr: *Node; value_expr: *Node;
// var sym int is_const := t.kind == .kw_def;
// var sym int = value
// var sym = value
t = peek_token(p); t = peek_token(p);
if t.kind == .equal { if t.kind == .equal {
consume_token(p); consume_token(p);
value_expr = parse_expression(p); value_expr = parse_expression(p);
basic.assert(value_expr != null, "value expr"); // @errors
} }
else { else {
type_expr = parse_type_expression(p); type_expr = parse_type_expression(p);
if type_expr == null return null; basic.assert(type_expr != null, "type expr"); // @errors
if peek_token(p).kind == .equal { if peek_token(p).kind == .equal {
consume_token(p); consume_token(p);
value_expr = parse_expression(p); value_expr = parse_expression(p);
basic.assert(value_expr != null, "value expr"); // @errors
} }
} }
@ -151,24 +258,211 @@ parse_toplevel_declaration :: (p: *Parser) -> *Node {
node.type_expr = type_expr; node.type_expr = type_expr;
node.value_expr = value_expr; node.value_expr = value_expr;
return node; if is_const {
node.var_flags |= .immutable;
} }
parse_type_expression :: (p: *Parser) -> *Node { return node;
t, ok := expect_token(p, .symbol, .star);
if ok == false return null;
if t.kind == { // print(expr)
case .symbol; // print expr
node := make_node(p, Node_Symbol); case .kw_print;
node.str = t.str; expr := parse_expression(p);
basic.assert(expr != null, "expected expression"); // @errors
node := make_node(p, Node_Print);
node.expr = expr;
return node; return node;
} }
return null; 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; return null;
} }
@ -229,9 +523,24 @@ consume_token :: (p: *Parser) -> Token {
if t.str == { if t.str == {
case "var"; t.kind = .kw_var; case "var"; t.kind = .kw_var;
case "def"; t.kind = .kw_def; case "def"; t.kind = .kw_def;
case "type"; t.kind = .kw_type;
case "do"; t.kind = .kw_do; case "do"; t.kind = .kw_do;
case "end"; t.kind = .kw_end; case "end"; t.kind = .kw_end;
case "type"; t.kind = .kw_type; 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; case; t.kind = .symbol;
} }
@ -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;
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; 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