1 const std = @import("std");
2 const ArenaAllocator = std.heap.ArenaAllocator;
3 const Allocator = std.mem.Allocator;
4 const Interpreter = @This();
5 const Expr = @import("expr.zig").Expr;
6 const Token = @import("Token.zig");
7 const Literal = Token.Literal;
8 const lox = @import("main.zig");
10 arena: ArenaAllocator,
12 stdout: *std.Io.Writer,
14 pub const ErrorPayload = struct { token: Token, message: []const u8 };
16 pub fn init(child_allocator: Allocator, stdout: *std.Io.Writer) Interpreter {
18 .arena = .init(child_allocator),
19 .allocator = undefined,
24 pub fn deinit(self: *Interpreter) void {
28 pub fn interpret(self: *Interpreter, expression: *const Expr) !void {
29 self.allocator = self.arena.allocator();
31 var err_payload: ErrorPayload = undefined;
32 if (self.evaluate(expression, &err_payload)) |value| {
33 const stringified = try self.stringify(value);
34 try self.stdout.print("{s}\n", .{stringified});
35 } else |err| switch (err) {
36 error.RuntimeError => try lox.runtimeError(err_payload),
41 fn evaluate(self: *Interpreter, expr: *const Expr, err_payload: *ErrorPayload) (Allocator.Error || error{RuntimeError})!Literal {
42 return switch (expr.*) {
43 .binary => |binary| self.visitBinaryExpr(binary, err_payload),
44 .grouping => |grouping| self.visitGroupingExpr(grouping, err_payload),
45 .literal => |literal| self.visitLiteralExpr(literal, err_payload),
46 .unary => |unary| self.visitUnaryExpr(unary, err_payload),
50 fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPayload) !Literal {
51 const left = try self.evaluate(expr.left, err_payload);
52 const right = try self.evaluate(expr.right, err_payload);
54 return switch (expr.operator.type) {
55 .bang_equal => .{ .boolean = !isEqual(left, right) },
56 .equal_equal => .{ .boolean = isEqual(left, right) },
58 try checkNumberOperands(expr.operator, left, right, err_payload);
59 return .{ .boolean = left.number > right.number };
62 try checkNumberOperands(expr.operator, left, right, err_payload);
63 return .{ .boolean = left.number >= right.number };
66 try checkNumberOperands(expr.operator, left, right, err_payload);
67 return .{ .boolean = left.number < right.number };
70 try checkNumberOperands(expr.operator, left, right, err_payload);
71 return .{ .boolean = left.number <= right.number };
74 try checkNumberOperands(expr.operator, left, right, err_payload);
75 return .{ .number = left.number - right.number };
78 try checkNumberOperands(expr.operator, left, right, err_payload);
79 return .{ .number = left.number / right.number };
82 try checkNumberOperands(expr.operator, left, right, err_payload);
83 return .{ .number = left.number * right.number };
86 if (left == .number and right == .number) {
87 return .{ .number = left.number + right.number };
90 if (left == .string and right == .string) {
91 return .{ .string = try std.mem.concat(self.allocator, u8, &.{ left.string, right.string }) };
94 err_payload.* = .{ .token = expr.operator, .message = "Operands must be two numbers or two strings." };
95 return error.RuntimeError;
101 fn visitGroupingExpr(self: *Interpreter, expr: Expr.Grouping, err_payload: *ErrorPayload) !Literal {
102 return self.evaluate(expr.expression, err_payload);
105 fn visitLiteralExpr(self: *Interpreter, expr: Expr.Literal, err_payload: *ErrorPayload) !Literal {
111 fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPayload) !Literal {
112 const right = try self.evaluate(expr.right, err_payload);
114 return switch (expr.operator.type) {
115 .bang => .{ .boolean = !isTruthy(right) },
117 try checkNumberOperand(expr.operator, right, err_payload);
118 return .{ .number = -right.number };
124 fn checkNumberOperand(operator: Token, operand: Literal, err_payload: *ErrorPayload) !void {
125 if (operand == .number) return;
126 err_payload.* = .{ .token = operator, .message = "Operand must be a number." };
127 return error.RuntimeError;
130 fn checkNumberOperands(operator: Token, left: Literal, right: Literal, err_payload: *ErrorPayload) !void {
131 if (left == .number and right == .number) return;
132 err_payload.* = .{ .token = operator, .message = "Operands must be numbers." };
133 return error.RuntimeError;
136 fn isTruthy(object: Literal) bool {
137 return switch (object) {
143 fn isEqual(a: Literal, b: Literal) bool {
145 .string => |a_string| b == .string and std.mem.eql(u8, a_string, b.string),
146 .number => |a_number| b == .number and a_number == b.number,
147 .boolean => |a_boolean| b == .boolean and a_boolean == b.boolean,
152 fn stringify(self: *Interpreter, object: Literal) ![]const u8 {
153 return switch (object) {
154 .string => |string| string,
155 .number => |number| std.fmt.allocPrint(self.allocator, "{}", .{number}),
156 .boolean => |boolean| std.fmt.allocPrint(self.allocator, "{}", .{boolean}),