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