--- /dev/null
+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",
+ };
+}
const Scanner = @import("Scanner.zig");
const Token = @import("Token.zig");
const Parser = @import("Parser.zig");
-const ast_printer = @import("ast_printer.zig");
+const Interpreter = @import("Interpreter.zig");
+const ErrorPayload = Interpreter.ErrorPayload;
+var interpreter: Interpreter = undefined;
var hadError = false;
+var hadRuntimeError = false;
pub fn main() !u8 {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
+
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
+ var stdout_buffer: [1024]u8 = undefined;
+ var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
+ const stdout = &stdout_writer.interface;
+ interpreter = Interpreter.init(allocator, stdout);
+ defer interpreter.deinit();
+
if (args.len > 2) {
- var stdout_writer = std.fs.File.stdout().writer(&.{});
- const stdout = &stdout_writer.interface;
try stdout.writeAll("Usage: zlox [script]\n");
+ try stdout.flush();
return 64;
} else if (args.len == 2) {
return runFile(allocator, args[1]);
} else {
- try runPrompt(allocator);
+ try runPrompt(allocator, stdout);
}
return 0;
// Indicate an error in the exit code.
if (hadError) return 65;
+ if (hadRuntimeError) return 70;
return 0;
}
-fn runPrompt(allocator: Allocator) !void {
+fn runPrompt(allocator: Allocator, stdout: *std.Io.Writer) !void {
var stdin_buffer: [1024]u8 = undefined;
var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer);
const stdin = &stdin_reader.interface;
- var stdout_writer = std.fs.File.stdout().writer(&.{});
- const stdout = &stdout_writer.interface;
while (true) {
try stdout.writeAll("> ");
+ try stdout.flush();
const line = try stdin.takeDelimiter('\n');
if (line == null) break;
try run(allocator, line.?);
// Stop if there was a syntax error.
if (hadError) return;
- const printed = try ast_printer.print(allocator, expression.?);
- defer allocator.free(printed);
- std.debug.print("{s}\n", .{printed});
+ try interpreter.interpret(expression.?);
+ try interpreter.stdout.flush();
}
pub fn scanError(line: u32, message: []const u8) !void {
try report(token.line, try std.fmt.allocPrint(allocator, " at '{s}'", .{token.lexeme}), message);
}
}
+
+pub fn runtimeError(err: ErrorPayload) !void {
+ try stderr.print("{s}\n[line {}]\n", .{ err.message, err.token.line });
+ try stderr.flush();
+ hadRuntimeError = true;
+}