From: Ayo Reis Date: Wed, 25 Feb 2026 18:28:48 +0000 (+0000) Subject: Implement interpreter X-Git-Url: https://git.ayoreis.com/zlox.git/commitdiff_plain/e07eaf5e064b9f42a95b7cf47f7a0cb38bccd8ba Implement interpreter --- diff --git a/src/Interpreter.zig b/src/Interpreter.zig new file mode 100644 index 0000000..0f8c224 --- /dev/null +++ b/src/Interpreter.zig @@ -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", + }; +} diff --git a/src/expr.zig b/src/expr.zig index e862131..464f5ac 100644 --- a/src/expr.zig +++ b/src/expr.zig @@ -6,7 +6,7 @@ pub const Expr = union(enum) { literal: Literal, unary: Unary, - const Binary = struct { + pub const Binary = struct { 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 { @@ -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 { @@ -40,7 +40,7 @@ pub const Expr = union(enum) { } }; - const Unary = struct { + pub const Unary = struct { operator: Token, right: *const Expr, diff --git a/src/main.zig b/src/main.zig index a17dbd7..1f02d3e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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 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; @@ -35,19 +44,19 @@ fn runFile(allocator: Allocator, path: []const u8) !u8 { // 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.?); @@ -66,9 +75,8 @@ fn run(allocator: Allocator, source: []const u8) !void { // 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 { @@ -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); } } + +pub fn runtimeError(err: ErrorPayload) !void { + try stderr.print("{s}\n[line {}]\n", .{ err.message, err.token.line }); + try stderr.flush(); + hadRuntimeError = true; +}