zloxi/src/runtime.zig

106 lines
3.4 KiB
Zig

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