expression parsing and basic tree-walk interpreter
This commit is contained in:
parent
f954a8276d
commit
5ed453a0fc
3 changed files with 585 additions and 76 deletions
181
vm/interp.jai
Normal file
181
vm/interp.jai
Normal 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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
// }
|
||||
|
|
|
|||
373
vm/parser.jai
373
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,32 +215,38 @@ 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;
|
||||
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 == {
|
||||
// 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);
|
||||
if ok == false return null;
|
||||
basic.assert(ok, "symbol"); // @errors
|
||||
|
||||
type_expr: *Node;
|
||||
value_expr: *Node;
|
||||
|
||||
// var sym int
|
||||
// var sym int = value
|
||||
// var sym = value
|
||||
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);
|
||||
if type_expr == null return null;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,24 +258,211 @@ parse_toplevel_declaration :: (p: *Parser) -> *Node {
|
|||
node.type_expr = type_expr;
|
||||
node.value_expr = value_expr;
|
||||
|
||||
if is_const {
|
||||
node.var_flags |= .immutable;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
parse_type_expression :: (p: *Parser) -> *Node {
|
||||
t, ok := expect_token(p, .symbol, .star);
|
||||
if ok == false return null;
|
||||
// print(expr)
|
||||
// print expr
|
||||
case .kw_print;
|
||||
expr := parse_expression(p);
|
||||
basic.assert(expr != null, "expected expression"); // @errors
|
||||
|
||||
if t.kind == {
|
||||
case .symbol;
|
||||
node := make_node(p, Node_Symbol);
|
||||
node.str = t.str;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -229,9 +523,24 @@ consume_token :: (p: *Parser) -> Token {
|
|||
if t.str == {
|
||||
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 "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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue