X-Git-Url: https://git.ayoreis.com/zlox.git/blobdiff_plain/d85b42d6b5cfa46cdc86c4f8abaa4423a122fe06..60ca4a9e7c56ac47f67add12ec7d2edf5bd89e5a:/src/Interpreter.zig?ds=inline diff --git a/src/Interpreter.zig b/src/Interpreter.zig index d69b9a3..5b65b97 100644 --- a/src/Interpreter.zig +++ b/src/Interpreter.zig @@ -1,27 +1,72 @@ const std = @import("std"); const ArenaAllocator = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; const Environment = @import("Environment.zig"); const Interpreter = @This(); const Stmt = @import("stmt.zig").Stmt; const Expr = @import("expr.zig").Expr; const Token = @import("Token.zig"); -const Literal = Token.Literal; +const Object = @import("object.zig").Object; const lox = @import("main.zig"); +const LoxCallable = @import("LoxCallable.zig"); +const LoxFunction = @import("LoxFunction.zig"); +const Return = @import("Return.zig"); +const RuntimeError = @import("RuntimeError.zig"); arena: ArenaAllocator, allocator: Allocator, stdout: *std.Io.Writer, -environment: Environment, +globals: *Environment, +environment: *Environment, -pub const ErrorPayload = struct { token: Token, message: []const u8 }; +pub const ErrorPayload = union { + runtime_error: RuntimeError, + @"return": Return, +}; + +pub const Error = (Allocator.Error || std.Io.Writer.Error || error{ RuntimeError, Return }); + +fn clock_arity(ptr: *const anyopaque) u8 { + _ = ptr; + return 0; +} + +fn clock_call(ptr: *const anyopaque, interpreter: *Interpreter, arguments: []const Object, err_payload: *ErrorPayload) !Object { + _ = ptr; + _ = interpreter; + _ = arguments; + _ = err_payload; + return .{ .number = @as(f64, @floatFromInt(std.time.nanoTimestamp())) / 1_000_000_000 }; +} + +fn clock_format(ptr: *const anyopaque, writer: *std.Io.Writer) !void { + _ = ptr; + try writer.writeAll(""); +} + +pub fn init(child_allocator: Allocator, stdout: *std.Io.Writer) !Interpreter { + var arena: ArenaAllocator = .init(child_allocator); + const allocator = arena.allocator(); + + const globals = try allocator.create(Environment); + globals.* = .init(null); + + try globals.define(allocator, "clock", .{ .lox_callable = .{ + .ptr = undefined, + .vtable = &.{ + .arity = &clock_arity, + .call = &clock_call, + .format = &clock_format, + }, + } }); -pub fn init(child_allocator: Allocator, stdout: *std.Io.Writer) Interpreter { return .{ - .arena = .init(child_allocator), + .arena = arena, .allocator = undefined, .stdout = stdout, - .environment = .init(null), + .globals = globals, + .environment = globals, }; } @@ -35,33 +80,42 @@ pub fn interpret(self: *Interpreter, statements: []const *const Stmt) !void { for (statements) |statement| { var err_payload: ErrorPayload = undefined; self.execute(statement, &err_payload) catch |err| switch (err) { - error.RuntimeError => try lox.runtimeError(err_payload), + error.RuntimeError => { + try self.stdout.flush(); + try lox.runtimeError(err_payload.runtime_error); + }, else => return err, }; } } -fn evaluate(self: *Interpreter, expr: *const Expr, err_payload: *ErrorPayload) (Allocator.Error || error{RuntimeError})!Literal { +fn evaluate(self: *Interpreter, expr: *const Expr, err_payload: *ErrorPayload) Error!Object { return switch (expr.*) { + .assign => |assignment| self.visitAssignExpr(assignment, err_payload), .binary => |binary| self.visitBinaryExpr(binary, err_payload), .grouping => |grouping| self.visitGroupingExpr(grouping, err_payload), + .call => |call| self.visitCallExpr(call, err_payload), .literal => |literal| self.visitLiteralExpr(literal, err_payload), + .logical => |logical| self.visitLogicalExpr(logical, err_payload), .unary => |unary| self.visitUnaryExpr(unary, err_payload), .variable => |variable| self.visitVariableExpr(variable, err_payload), - .assignment => |assignment| self.visitAssignExpr(assignment, err_payload), }; } -fn execute(self: *Interpreter, stmt: *const Stmt, err_payload: *ErrorPayload) (Allocator.Error || std.Io.Writer.Error || error{RuntimeError})!void { +fn execute(self: *Interpreter, stmt: *const Stmt, err_payload: *ErrorPayload) Error!void { try switch (stmt.*) { .block => |block| self.visitBlockStmt(block, err_payload), .expression => |expression| self.visitExpressionStmt(expression, err_payload), + .function => |function| self.visitFunctionStmt(function, err_payload), + .@"if" => |@"if"| self.visitIfStmt(@"if", err_payload), .print => |print| self.visitPrintStmt(print, err_payload), + .@"return" => |@"return"| self.visitReturnStmt(@"return", err_payload), .@"var" => |@"var"| self.visitVarStmt(@"var", err_payload), + .@"while" => |@"while"| self.visitWhileStmt(@"while", err_payload), }; } -fn executeBlock(self: *Interpreter, statements: []const ?*const Stmt, environment: Environment, err_payload: *ErrorPayload) !void { +pub fn executeBlock(self: *Interpreter, statements: []const ?*const Stmt, environment: *Environment, err_payload: *ErrorPayload) !void { const previous = self.environment; defer self.environment = previous; @@ -75,21 +129,46 @@ fn executeBlock(self: *Interpreter, statements: []const ?*const Stmt, environmen } fn visitBlockStmt(self: *Interpreter, stmt: Stmt.Block, err_payload: *ErrorPayload) !void { - var copy = self.environment; - try self.executeBlock(stmt.statements, .init(©), err_payload); + const environment = try self.allocator.create(Environment); + environment.* = .init(self.environment); + try self.executeBlock(stmt.statements, environment, err_payload); } fn visitExpressionStmt(self: *Interpreter, stmt: Stmt.Expression, err_payload: *ErrorPayload) !void { _ = try self.evaluate(stmt.expression, err_payload); } +fn visitFunctionStmt(self: *Interpreter, stmt: Stmt.Function, err_payload: *ErrorPayload) !void { + _ = err_payload; + const function = try self.allocator.create(LoxFunction); + function.* = .init(stmt, self.environment); + try self.environment.define(self.allocator, stmt.name.lexeme, .{ .lox_callable = function.loxCallable() }); +} + +fn visitIfStmt(self: *Interpreter, stmt: Stmt.If, err_payload: *ErrorPayload) !void { + if (isTruthy(try self.evaluate(stmt.condition, err_payload))) { + try self.execute(stmt.then_branch, err_payload); + } else if (stmt.else_branch) |else_branch| { + try self.execute(else_branch, err_payload); + } +} + fn visitPrintStmt(self: *Interpreter, stmt: Stmt.Print, err_payload: *ErrorPayload) !void { const value = try self.evaluate(stmt.expression, err_payload); try self.stdout.print("{s}\n", .{try self.stringify(value)}); } +fn visitReturnStmt(self: *Interpreter, stmt: Stmt.Return, err_payload: *ErrorPayload) !void { + var value: Object = .{ .nil = {} }; + if (stmt.value != null) value = try self.evaluate(stmt.value.?, err_payload); + + err_payload.* = .{ .@"return" = .init(value) }; + return error.Return; +} + fn visitVarStmt(self: *Interpreter, stmt: Stmt.Var, err_payload: *ErrorPayload) !void { - var value: Literal = .nil; + var value: Object = .nil; + if (stmt.initializer) |initializer| { value = try self.evaluate(initializer, err_payload); } @@ -97,13 +176,19 @@ fn visitVarStmt(self: *Interpreter, stmt: Stmt.Var, err_payload: *ErrorPayload) try self.environment.define(self.allocator, stmt.name.lexeme, value); } -fn visitAssignExpr(self: *Interpreter, expr: Expr.Assignment, err_payload: *ErrorPayload) !Literal { +fn visitWhileStmt(self: *Interpreter, stmt: Stmt.While, err_payload: *ErrorPayload) !void { + while (isTruthy(try self.evaluate(stmt.condition, err_payload))) { + try self.execute(stmt.statement, err_payload); + } +} + +fn visitAssignExpr(self: *Interpreter, expr: Expr.Assign, err_payload: *ErrorPayload) !Object { const value = try self.evaluate(expr.value, err_payload); try self.environment.assign(self.allocator, expr.name, value, err_payload); return value; } -fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPayload) !Literal { +fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPayload) !Object { const left = try self.evaluate(expr.left, err_payload); const right = try self.evaluate(expr.right, err_payload); @@ -147,24 +232,63 @@ fn visitBinaryExpr(self: *Interpreter, expr: Expr.Binary, err_payload: *ErrorPay 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." }; + err_payload.* = .{ .runtime_error = .init(expr.operator, "Operands must be two numbers or two strings.") }; return error.RuntimeError; }, else => unreachable, }; } -fn visitGroupingExpr(self: *Interpreter, expr: Expr.Grouping, err_payload: *ErrorPayload) !Literal { +fn visitGroupingExpr(self: *Interpreter, expr: Expr.Grouping, err_payload: *ErrorPayload) !Object { return self.evaluate(expr.expression, err_payload); } -fn visitLiteralExpr(self: *Interpreter, expr: Expr.Literal, err_payload: *ErrorPayload) !Literal { +fn visitCallExpr(self: *Interpreter, expr: Expr.Call, err_payload: *ErrorPayload) !Object { + const callee = try self.evaluate(expr.callee, err_payload); + + var arguments: ArrayList(Object) = .empty; + + for (expr.arguments) |argument| { + try arguments.append(self.allocator, try self.evaluate(argument, err_payload)); + } + + if (callee != .lox_callable) { + err_payload.* = .{ .runtime_error = .init(expr.paren, "Can only call functions and classes.") }; + return error.RuntimeError; + } + + const function = callee.lox_callable; + + if (arguments.items.len != function.arity()) { + err_payload.* = .{ .runtime_error = .init( + expr.paren, + try std.fmt.allocPrint(self.allocator, "Expected {} arguments but got {}.", .{ function.arity(), arguments.items.len }), + ) }; + return error.RuntimeError; + } + + return function.call(self, try arguments.toOwnedSlice(self.allocator), err_payload); +} + +fn visitLiteralExpr(self: *Interpreter, expr: Expr.Literal, err_payload: *ErrorPayload) !Object { _ = self; _ = err_payload; return expr.value; } -fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPayload) !Literal { +fn visitLogicalExpr(self: *Interpreter, expr: Expr.Logical, err_payload: *ErrorPayload) !Object { + const left = try self.evaluate(expr.left, err_payload); + + if (expr.operator.type == .@"or") { + if (isTruthy(left)) return left; + } else { + if (!isTruthy(left)) return left; + } + + return try self.evaluate(expr.right, err_payload); +} + +fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPayload) !Object { const right = try self.evaluate(expr.right, err_payload); return switch (expr.operator.type) { @@ -177,43 +301,48 @@ fn visitUnaryExpr(self: *Interpreter, expr: Expr.Unary, err_payload: *ErrorPaylo }; } -fn visitVariableExpr(self: *Interpreter, expr: Expr.Variable, err_payload: *ErrorPayload) !Literal { +fn visitVariableExpr(self: *Interpreter, expr: Expr.Variable, err_payload: *ErrorPayload) !Object { return self.environment.get(self.allocator, expr.name, err_payload); } -fn checkNumberOperand(operator: Token, operand: Literal, err_payload: *ErrorPayload) !void { +fn checkNumberOperand(operator: Token, operand: Object, err_payload: *ErrorPayload) !void { if (operand == .number) return; - err_payload.* = .{ .token = operator, .message = "Operand must be a number." }; + err_payload.* = .{ .runtime_error = .init(operator, "Operand must be a number.") }; return error.RuntimeError; } -fn checkNumberOperands(operator: Token, left: Literal, right: Literal, err_payload: *ErrorPayload) !void { +fn checkNumberOperands(operator: Token, left: Object, right: Object, err_payload: *ErrorPayload) !void { if (left == .number and right == .number) return; - err_payload.* = .{ .token = operator, .message = "Operands must be numbers." }; + err_payload.* = .{ .runtime_error = .init(operator, "Operands must be numbers.") }; return error.RuntimeError; } -fn isTruthy(object: Literal) bool { +fn isTruthy(object: Object) bool { return switch (object) { .nil => false, + .boolean => object.boolean, else => true, }; } -fn isEqual(a: Literal, b: Literal) bool { +fn isEqual(a: Object, b: Object) bool { + if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; + 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, + .lox_callable => |a_lox_callable| a_lox_callable.ptr == b.lox_callable.ptr, }; } -fn stringify(self: *Interpreter, object: Literal) ![]const u8 { +fn stringify(self: *Interpreter, object: Object) ![]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", + .lox_callable => |lox_callable| std.fmt.allocPrint(self.allocator, "{f}", .{lox_callable}), }; }