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