]> Repositories - zlox.git/blob - src/Interpreter.zig
Implement print and expression statements
[zlox.git] / src / Interpreter.zig
1 const std = @import("std");
2 const ArenaAllocator = std.heap.ArenaAllocator;
3 const Allocator = std.mem.Allocator;
4 const Interpreter = @This();
5 const Stmt = @import("stmt.zig").Stmt;
6 const Expr = @import("expr.zig").Expr;
7 const Token = @import("Token.zig");
8 const Literal = Token.Literal;
9 const lox = @import("main.zig");
10
11 arena: ArenaAllocator,
12 allocator: Allocator,
13 stdout: *std.Io.Writer,
14
15 pub const ErrorPayload = struct { token: Token, message: []const u8 };
16
17 pub fn init(child_allocator: Allocator, stdout: *std.Io.Writer) Interpreter {
18     return .{
19         .arena = .init(child_allocator),
20         .allocator = undefined,
21         .stdout = stdout,
22     };
23 }
24
25 pub fn deinit(self: *Interpreter) void {
26     self.arena.deinit();
27 }
28
29 pub fn interpret(self: *Interpreter, statements: []const *const Stmt) !void {
30     self.allocator = self.arena.allocator();
31
32     for (statements) |statement| {
33         var err_payload: ErrorPayload = undefined;
34         self.execute(statement, &err_payload) catch |err| switch (err) {
35             error.RuntimeError => try lox.runtimeError(err_payload),
36             else => return err,
37         };
38     }
39 }
40
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),
47     };
48 }
49
50 fn execute(self: *Interpreter, stmt: *const Stmt, err_payload: *ErrorPayload) !void {
51     try switch (stmt.*) {
52         .print => |print| self.visitPrintStmt(print, err_payload),
53         .expression => |expression| self.visitExpressionStmt(expression, err_payload),
54     };
55 }
56
57 fn visitExpressionStmt(self: *Interpreter, stmt: Stmt.Expression, err_payload: *ErrorPayload) !void {
58     _ = try self.evaluate(stmt.expression, err_payload);
59 }
60
61 fn visitPrintStmt(self: *Interpreter, stmt: Stmt.Print, err_payload: *ErrorPayload) !void {
62     const value = try self.evaluate(stmt.expression, err_payload);
63     try self.stdout.print("{s}\n", .{try self.stringify(value)});
64 }
65
66 fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPayload) !Literal {
67     const left = try self.evaluate(expr.left, err_payload);
68     const right = try self.evaluate(expr.right, err_payload);
69
70     return switch (expr.operator.type) {
71         .bang_equal => .{ .boolean = !isEqual(left, right) },
72         .equal_equal => .{ .boolean = isEqual(left, right) },
73         .greater => {
74             try checkNumberOperands(expr.operator, left, right, err_payload);
75             return .{ .boolean = left.number > right.number };
76         },
77         .greater_equal => {
78             try checkNumberOperands(expr.operator, left, right, err_payload);
79             return .{ .boolean = left.number >= right.number };
80         },
81         .less => {
82             try checkNumberOperands(expr.operator, left, right, err_payload);
83             return .{ .boolean = left.number < right.number };
84         },
85         .less_equal => {
86             try checkNumberOperands(expr.operator, left, right, err_payload);
87             return .{ .boolean = left.number <= right.number };
88         },
89         .minus => {
90             try checkNumberOperands(expr.operator, left, right, err_payload);
91             return .{ .number = left.number - right.number };
92         },
93         .slash => {
94             try checkNumberOperands(expr.operator, left, right, err_payload);
95             return .{ .number = left.number / right.number };
96         },
97         .star => {
98             try checkNumberOperands(expr.operator, left, right, err_payload);
99             return .{ .number = left.number * right.number };
100         },
101         .plus => {
102             if (left == .number and right == .number) {
103                 return .{ .number = left.number + right.number };
104             }
105
106             if (left == .string and right == .string) {
107                 return .{ .string = try std.mem.concat(self.allocator, u8, &.{ left.string, right.string }) };
108             }
109
110             err_payload.* = .{ .token = expr.operator, .message = "Operands must be two numbers or two strings." };
111             return error.RuntimeError;
112         },
113         else => unreachable,
114     };
115 }
116
117 fn visitGroupingExpr(self: *Interpreter, expr: Expr.Grouping, err_payload: *ErrorPayload) !Literal {
118     return self.evaluate(expr.expression, err_payload);
119 }
120
121 fn visitLiteralExpr(self: *Interpreter, expr: Expr.Literal, err_payload: *ErrorPayload) !Literal {
122     _ = self;
123     _ = err_payload;
124     return expr.value;
125 }
126
127 fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPayload) !Literal {
128     const right = try self.evaluate(expr.right, err_payload);
129
130     return switch (expr.operator.type) {
131         .bang => .{ .boolean = !isTruthy(right) },
132         .minus => {
133             try checkNumberOperand(expr.operator, right, err_payload);
134             return .{ .number = -right.number };
135         },
136         else => unreachable,
137     };
138 }
139
140 fn checkNumberOperand(operator: Token, operand: Literal, err_payload: *ErrorPayload) !void {
141     if (operand == .number) return;
142     err_payload.* = .{ .token = operator, .message = "Operand must be a number." };
143     return error.RuntimeError;
144 }
145
146 fn checkNumberOperands(operator: Token, left: Literal, right: Literal, err_payload: *ErrorPayload) !void {
147     if (left == .number and right == .number) return;
148     err_payload.* = .{ .token = operator, .message = "Operands must be numbers." };
149     return error.RuntimeError;
150 }
151
152 fn isTruthy(object: Literal) bool {
153     return switch (object) {
154         .nil => false,
155         else => true,
156     };
157 }
158
159 fn isEqual(a: Literal, b: Literal) bool {
160     return switch (a) {
161         .string => |a_string| b == .string and std.mem.eql(u8, a_string, b.string),
162         .number => |a_number| b == .number and a_number == b.number,
163         .boolean => |a_boolean| b == .boolean and a_boolean == b.boolean,
164         .nil => b == .nil,
165     };
166 }
167
168 fn stringify(self: *Interpreter, object: Literal) ![]const u8 {
169     return switch (object) {
170         .string => |string| string,
171         .number => |number| std.fmt.allocPrint(self.allocator, "{}", .{number}),
172         .boolean => |boolean| std.fmt.allocPrint(self.allocator, "{}", .{boolean}),
173         .nil => "nil",
174     };
175 }