]> Repositories - zlox.git/commitdiff
Implement interpreter
authorAyo Reis <hey@ayoreis.com>
Wed, 25 Feb 2026 18:28:48 +0000 (18:28 +0000)
committerAyo Reis <hey@ayoreis.com>
Wed, 25 Feb 2026 18:28:48 +0000 (18:28 +0000)
src/Interpreter.zig [new file with mode: 0644]
src/expr.zig
src/main.zig

diff --git a/src/Interpreter.zig b/src/Interpreter.zig
new file mode 100644 (file)
index 0000000..0f8c224
--- /dev/null
@@ -0,0 +1,159 @@
+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",
+    };
+}
index e8621319d58fc2059b9fe11eea4eacb84598cd6a..464f5ac41709b96c6e18085cbbc91dce9430b54a 100644 (file)
@@ -6,7 +6,7 @@ pub const Expr = union(enum) {
     literal: Literal,
     unary: Unary,
 
     literal: Literal,
     unary: Unary,
 
-    const Binary = struct {
+    pub const Binary = struct {
         left: *const Expr,
         operator: Token,
         right: *const Expr,
         left: *const Expr,
         operator: Token,
         right: *const Expr,
@@ -20,7 +20,7 @@ pub const Expr = union(enum) {
         }
     };
 
         }
     };
 
-    const Grouping = struct {
+    pub const Grouping = struct {
         expression: *const Expr,
 
         pub fn init(expression: *const Expr) Grouping {
         expression: *const Expr,
 
         pub fn init(expression: *const Expr) Grouping {
@@ -30,7 +30,7 @@ pub const Expr = union(enum) {
         }
     };
 
         }
     };
 
-    const Literal = struct {
+    pub const Literal = struct {
         value: Token.Literal,
 
         pub fn init(value: Token.Literal) Literal {
         value: Token.Literal,
 
         pub fn init(value: Token.Literal) Literal {
@@ -40,7 +40,7 @@ pub const Expr = union(enum) {
         }
     };
 
         }
     };
 
-    const Unary = struct {
+    pub const Unary = struct {
         operator: Token,
         right: *const Expr,
 
         operator: Token,
         right: *const Expr,
 
index a17dbd73fe1b6e874ef360d0b4795c5b1b1220a9..1f02d3e7f89026fd72a6534bdf48eed06b9c7bc2 100644 (file)
@@ -3,26 +3,35 @@ const Allocator = std.mem.Allocator;
 const Scanner = @import("Scanner.zig");
 const Token = @import("Token.zig");
 const Parser = @import("Parser.zig");
 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 hadError = false;
+var hadRuntimeError = false;
 
 pub fn main() !u8 {
     var gpa: std.heap.DebugAllocator(.{}) = .init;
     defer _ = gpa.deinit();
     const allocator = gpa.allocator();
 
 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);
 
     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) {
     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.writeAll("Usage: zlox [script]\n");
+        try stdout.flush();
         return 64;
     } else if (args.len == 2) {
         return runFile(allocator, args[1]);
     } else {
         return 64;
     } else if (args.len == 2) {
         return runFile(allocator, args[1]);
     } else {
-        try runPrompt(allocator);
+        try runPrompt(allocator, stdout);
     }
 
     return 0;
     }
 
     return 0;
@@ -35,19 +44,19 @@ fn runFile(allocator: Allocator, path: []const u8) !u8 {
 
     // Indicate an error in the exit code.
     if (hadError) return 65;
 
     // Indicate an error in the exit code.
     if (hadError) return 65;
+    if (hadRuntimeError) return 70;
 
     return 0;
 }
 
 
     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 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("> ");
 
     while (true) {
         try stdout.writeAll("> ");
+        try stdout.flush();
         const line = try stdin.takeDelimiter('\n');
         if (line == null) break;
         try run(allocator, line.?);
         const line = try stdin.takeDelimiter('\n');
         if (line == null) break;
         try run(allocator, line.?);
@@ -66,9 +75,8 @@ fn run(allocator: Allocator, source: []const u8) !void {
     // Stop if there was a syntax error.
     if (hadError) return;
 
     // 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 {
 }
 
 pub fn scanError(line: u32, message: []const u8) !void {
@@ -92,3 +100,9 @@ pub fn parseError(allocator: Allocator, token: Token, message: []const u8) !void
         try report(token.line, try std.fmt.allocPrint(allocator, " at '{s}'", .{token.lexeme}), message);
     }
 }
         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;
+}