const std = @import("std"); const ArenaAllocator = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; const Interpreter = @This(); const Expr = @import("expr.zig").Expr; const Token = @import("Token.zig"); const Literal = Token.Literal; const lox = @import("main.zig"); arena: ArenaAllocator, allocator: Allocator, stdout: *std.Io.Writer, pub const ErrorPayload = struct { token: Token, message: []const u8 }; pub fn init(child_allocator: Allocator, stdout: *std.Io.Writer) Interpreter { return .{ .arena = .init(child_allocator), .allocator = undefined, .stdout = stdout, }; } pub fn deinit(self: *Interpreter) void { self.arena.deinit(); } pub fn interpret(self: *Interpreter, expression: *const Expr) !void { self.allocator = self.arena.allocator(); var err_payload: ErrorPayload = undefined; if (self.evaluate(expression, &err_payload)) |value| { const stringified = try self.stringify(value); try self.stdout.print("{s}\n", .{stringified}); } else |err| switch (err) { error.RuntimeError => try lox.runtimeError(err_payload), else => return err, } } fn evaluate(self: *Interpreter, expr: *const Expr, err_payload: *ErrorPayload) (Allocator.Error || error{RuntimeError})!Literal { return switch (expr.*) { .binary => |binary| self.visitBinaryExpr(binary, err_payload), .grouping => |grouping| self.visitGroupingExpr(grouping, err_payload), .literal => |literal| self.visitLiteralExpr(literal, err_payload), .unary => |unary| self.visitUnaryExpr(unary, err_payload), }; } fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPayload) !Literal { const left = try self.evaluate(expr.left, err_payload); const right = try self.evaluate(expr.right, err_payload); return switch (expr.operator.type) { .bang_equal => .{ .boolean = !isEqual(left, right) }, .equal_equal => .{ .boolean = isEqual(left, right) }, .greater => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .boolean = left.number > right.number }; }, .greater_equal => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .boolean = left.number >= right.number }; }, .less => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .boolean = left.number < right.number }; }, .less_equal => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .boolean = left.number <= right.number }; }, .minus => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .number = left.number - right.number }; }, .slash => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .number = left.number / right.number }; }, .star => { try checkNumberOperands(expr.operator, left, right, err_payload); return .{ .number = left.number * right.number }; }, .plus => { if (left == .number and right == .number) { return .{ .number = left.number + right.number }; } if (left == .string and right == .string) { return .{ .string = try std.mem.concat(self.allocator, u8, &.{ left.string, right.string }) }; } err_payload.* = .{ .token = expr.operator, .message = "Operands must be two numbers or two strings." }; return error.RuntimeError; }, else => unreachable, }; } fn visitGroupingExpr(self: *Interpreter, expr: Expr.Grouping, err_payload: *ErrorPayload) !Literal { return self.evaluate(expr.expression, err_payload); } fn visitLiteralExpr(self: *Interpreter, expr: Expr.Literal, err_payload: *ErrorPayload) !Literal { _ = self; _ = err_payload; return expr.value; } fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPayload) !Literal { const right = try self.evaluate(expr.right, err_payload); return switch (expr.operator.type) { .bang => .{ .boolean = !isTruthy(right) }, .minus => { try checkNumberOperand(expr.operator, right, err_payload); return .{ .number = -right.number }; }, else => unreachable, }; } fn checkNumberOperand(operator: Token, operand: Literal, err_payload: *ErrorPayload) !void { if (operand == .number) return; err_payload.* = .{ .token = operator, .message = "Operand must be a number." }; return error.RuntimeError; } fn checkNumberOperands(operator: Token, left: Literal, right: Literal, err_payload: *ErrorPayload) !void { if (left == .number and right == .number) return; err_payload.* = .{ .token = operator, .message = "Operands must be numbers." }; return error.RuntimeError; } fn isTruthy(object: Literal) bool { return switch (object) { .nil => false, else => true, }; } fn isEqual(a: Literal, b: Literal) bool { return switch (a) { .string => |a_string| b == .string and std.mem.eql(u8, a_string, b.string), .number => |a_number| b == .number and a_number == b.number, .boolean => |a_boolean| b == .boolean and a_boolean == b.boolean, .nil => b == .nil, }; } fn stringify(self: *Interpreter, object: Literal) ![]const u8 { return switch (object) { .string => |string| string, .number => |number| std.fmt.allocPrint(self.allocator, "{}", .{number}), .boolean => |boolean| std.fmt.allocPrint(self.allocator, "{}", .{boolean}), .nil => "nil", }; }