Add a primitive runtime interpreter.
This commit is contained in:
parent
cb5d7c7798
commit
1b166f04f8
|
@ -3,6 +3,7 @@ const std = @import("std");
|
||||||
const scanner = @import("scanner.zig");
|
const scanner = @import("scanner.zig");
|
||||||
const expr = @import("expr.zig");
|
const expr = @import("expr.zig");
|
||||||
const parser = @import("parser.zig");
|
const parser = @import("parser.zig");
|
||||||
|
const runtime = @import("runtime.zig");
|
||||||
const err = @import("error.zig");
|
const err = @import("error.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
@ -71,6 +72,5 @@ fn run(allocator: std.mem.Allocator, bytes: []u8) !void {
|
||||||
if (err.hasError) {
|
if (err.hasError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expr.AstPrint(expression);
|
runtime.interpret(expression);
|
||||||
std.debug.print("\n", .{});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const err = @import("error.zig");
|
||||||
|
const expr_zig = @import("expr.zig");
|
||||||
|
const Expr = expr_zig.Expr;
|
||||||
|
const LiteralExpr = expr_zig.LiteralExpr;
|
||||||
|
const LiteralTag = expr_zig.LiteralTag;
|
||||||
|
const token = @import("token.zig");
|
||||||
|
|
||||||
|
pub const RuntimeError = error{
|
||||||
|
TypeError,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn printLiteral(expr: LiteralExpr) void {
|
||||||
|
switch (expr) {
|
||||||
|
.number => |num| std.debug.print("{}\n", .{num}),
|
||||||
|
.string => |string| std.debug.print("{s}\n", .{string}),
|
||||||
|
.boolean => |boolean| std.debug.print("{}\n", .{boolean}),
|
||||||
|
.nil => std.debug.print("nil\n", .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret(expr: Expr) void {
|
||||||
|
var err_or_expr = evaluate(expr);
|
||||||
|
if (err_or_expr) |res| {
|
||||||
|
printLiteral(res);
|
||||||
|
} else |_| {
|
||||||
|
std.debug.print("Runtime Error.\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(expr: Expr) !LiteralExpr {
|
||||||
|
switch (expr) {
|
||||||
|
.literal => |literal| return literal,
|
||||||
|
.grouping => |grouping| return evaluate(grouping.expr.*),
|
||||||
|
.unary => |unary| {
|
||||||
|
var right = try evaluate(unary.right.*);
|
||||||
|
|
||||||
|
switch (unary.operator.token_type) {
|
||||||
|
.MINUS => switch (right) {
|
||||||
|
.number => |num| return LiteralExpr{ .number = -1 * num },
|
||||||
|
else => {
|
||||||
|
err.errToken(unary.operator, "Negating non-number");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.BANG => return LiteralExpr{
|
||||||
|
.boolean = !isTruthy(right),
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.binary => |binary| {
|
||||||
|
var left = try evaluate(binary.left.*);
|
||||||
|
var right = try evaluate(binary.right.*);
|
||||||
|
switch (binary.operator.token_type) {
|
||||||
|
.STAR => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number * right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.SLASH => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number / right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// FIXME: make this handle string concatenation.
|
||||||
|
.PLUS => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number + right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.MINUS => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number - right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isTruthy(expr: LiteralExpr) bool {
|
||||||
|
return switch (expr) {
|
||||||
|
.nil => false,
|
||||||
|
.boolean => |*boolean| boolean.*,
|
||||||
|
else => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkNumberOperands(operator: token.Token, left: LiteralExpr, right: LiteralExpr) RuntimeError!void {
|
||||||
|
if (@as(LiteralTag, left) != .number) {
|
||||||
|
err.errToken(operator, "Left operand is non-numeric");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
}
|
||||||
|
if (@as(LiteralTag, right) != .number) {
|
||||||
|
err.errToken(operator, "Right operand is non-numeric");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,7 +144,7 @@ pub const Scanner = struct {
|
||||||
// Closing ".
|
// Closing ".
|
||||||
_ = self.advance();
|
_ = self.advance();
|
||||||
|
|
||||||
self.addToken(token.TokenType.STRING);
|
self.addString();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn number(self: *Scanner) void {
|
fn number(self: *Scanner) void {
|
||||||
|
@ -221,4 +221,10 @@ pub const Scanner = struct {
|
||||||
const float = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch 0;
|
const float = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch 0;
|
||||||
self.addTokenInternal(token.TokenType.NUMBER, token.Token.Value{ .number = float });
|
self.addTokenInternal(token.TokenType.NUMBER, token.Token.Value{ .number = float });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn addString(self: *Scanner) void {
|
||||||
|
self.addTokenInternal(token.TokenType.STRING, token.Token.Value{
|
||||||
|
.string = self.source[(self.start + 1)..(self.current - 1)],
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue