Compare commits
3 Commits
44792e5c19
...
1b166f04f8
Author | SHA1 | Date |
---|---|---|
|
1b166f04f8 | |
|
cb5d7c7798 | |
|
7236c0b43a |
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const token = @import("token.zig");
|
||||||
|
|
||||||
pub var hasError: bool = false;
|
pub var hasError: bool = false;
|
||||||
|
|
||||||
|
@ -6,7 +7,20 @@ pub fn err(line: u64, message: []const u8) void {
|
||||||
report(line, "", message);
|
report(line, "", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report(line: u64, where: []u8, message: []const u8) void {
|
pub fn errToken(tok: token.Token, message: []const u8) void {
|
||||||
|
if (tok.token_type == token.TokenType.EOF) {
|
||||||
|
report(tok.line, " at end", message);
|
||||||
|
} else {
|
||||||
|
var buffer = [_]u8{0} ** 100;
|
||||||
|
const where = std.fmt.bufPrint(&buffer, " at '{s}'", .{tok.lexeme}) catch {
|
||||||
|
std.debug.print("Format error: buffer empty", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
report(tok.line, where, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report(line: u64, where: []const u8, message: []const u8) void {
|
||||||
std.debug.print("[line {}] Error{s}: {s}\n", .{ line, where, message });
|
std.debug.print("[line {}] Error{s}: {s}\n", .{ line, where, message });
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,11 @@ pub const LiteralTag = enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const LiteralExpr = union(LiteralTag) {
|
pub const LiteralExpr = union(LiteralTag) {
|
||||||
number: u64,
|
number: f64,
|
||||||
string: []u8,
|
string: []const u8,
|
||||||
boolean: bool,
|
boolean: bool,
|
||||||
nil: void,
|
// FIXME: See if there is a way to make this void.
|
||||||
|
nil: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UnaryExpr = struct {
|
pub const UnaryExpr = struct {
|
||||||
|
|
21
src/main.zig
21
src/main.zig
|
@ -1,6 +1,9 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const scanner = @import("scanner.zig");
|
const scanner = @import("scanner.zig");
|
||||||
|
const expr = @import("expr.zig");
|
||||||
|
const parser = @import("parser.zig");
|
||||||
|
const runtime = @import("runtime.zig");
|
||||||
const err = @import("error.zig");
|
const err = @import("error.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
@ -56,8 +59,18 @@ fn runPrompt(alloc: std.mem.Allocator) !void {
|
||||||
fn run(allocator: std.mem.Allocator, bytes: []u8) !void {
|
fn run(allocator: std.mem.Allocator, bytes: []u8) !void {
|
||||||
var scan = scanner.Scanner.init(allocator, bytes);
|
var scan = scanner.Scanner.init(allocator, bytes);
|
||||||
defer scan.deinit();
|
defer scan.deinit();
|
||||||
std.debug.print("{any}\n", .{scan.scanTokens()});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error reporting
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
// TODO: Move to a separate file.
|
defer arena.deinit();
|
||||||
|
var alloc = arena.allocator();
|
||||||
|
var parse = parser.Parser{
|
||||||
|
.tokens = scan.scanTokens(),
|
||||||
|
.allocator = alloc,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expression = parse.parse();
|
||||||
|
if (err.hasError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime.interpret(expression);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const expr_zig = @import("expr.zig");
|
||||||
|
const Expr = expr_zig.Expr;
|
||||||
|
const BinaryExpr = expr_zig.BinaryExpr;
|
||||||
|
const UnaryExpr = expr_zig.UnaryExpr;
|
||||||
|
const LiteralExpr = expr_zig.LiteralExpr;
|
||||||
|
const GroupingExpr = expr_zig.GroupingExpr;
|
||||||
|
|
||||||
|
const token_zig = @import("token.zig");
|
||||||
|
const TokenType = token_zig.TokenType;
|
||||||
|
const Token = token_zig.Token;
|
||||||
|
|
||||||
|
const errors_zig = @import("error.zig");
|
||||||
|
const err = errors_zig.err;
|
||||||
|
const errToken = errors_zig.errToken;
|
||||||
|
|
||||||
|
const ParseError = error{
|
||||||
|
UnmatchedParen,
|
||||||
|
UnexpectedToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Parser = struct {
|
||||||
|
const Self = @This();
|
||||||
|
tokens: std.ArrayList(Token),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
current: u64 = 0,
|
||||||
|
|
||||||
|
pub fn parse(self: *Self) Expr {
|
||||||
|
var expr = self.expression() catch blk: {
|
||||||
|
break :blk &Expr{ .literal = LiteralExpr{
|
||||||
|
.nil = false,
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
return expr.*;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression(self: *Self) !*Expr {
|
||||||
|
return self.equality();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equality(self: *Self) !*Expr {
|
||||||
|
var expr = try self.comparison();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.peekType()) {
|
||||||
|
TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL => {
|
||||||
|
var old_expr = expr;
|
||||||
|
expr = try self.allocator.create(Expr);
|
||||||
|
expr.* = Expr{
|
||||||
|
.binary = BinaryExpr{
|
||||||
|
.operator = self.advance(),
|
||||||
|
.left = old_expr,
|
||||||
|
.right = try self.comparison(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comparison(self: *Self) !*Expr {
|
||||||
|
var expr = try self.term();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.peekType()) {
|
||||||
|
TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL => {
|
||||||
|
var old_expr = expr;
|
||||||
|
expr = try self.allocator.create(Expr);
|
||||||
|
expr.* = Expr{
|
||||||
|
.binary = BinaryExpr{
|
||||||
|
.operator = self.advance(),
|
||||||
|
.left = old_expr,
|
||||||
|
.right = try self.term(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn term(self: *Self) !*Expr {
|
||||||
|
var expr = try self.factor();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.peekType()) {
|
||||||
|
TokenType.PLUS, TokenType.MINUS => {
|
||||||
|
var old_expr = expr;
|
||||||
|
expr = try self.allocator.create(Expr);
|
||||||
|
expr.* = Expr{
|
||||||
|
.binary = BinaryExpr{
|
||||||
|
.operator = self.advance(),
|
||||||
|
.left = old_expr,
|
||||||
|
.right = try self.factor(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn factor(self: *Self) !*Expr {
|
||||||
|
var expr = try self.unary();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.peekType()) {
|
||||||
|
TokenType.STAR, TokenType.SLASH => {
|
||||||
|
var old_expr = expr;
|
||||||
|
expr = try self.allocator.create(Expr);
|
||||||
|
expr.* = Expr{
|
||||||
|
.binary = BinaryExpr{
|
||||||
|
.operator = self.advance(),
|
||||||
|
.left = old_expr,
|
||||||
|
.right = try self.unary(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary(self: *Self) !*Expr {
|
||||||
|
switch (self.peekType()) {
|
||||||
|
TokenType.BANG, TokenType.MINUS => {
|
||||||
|
var expr = try self.allocator.create(Expr);
|
||||||
|
expr.* = Expr{
|
||||||
|
.unary = UnaryExpr{
|
||||||
|
.operator = self.advance(),
|
||||||
|
.right = try self.unary(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return expr;
|
||||||
|
},
|
||||||
|
else => return self.primary(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn primary(self: *Self) error{ OutOfMemory, UnmatchedParen, UnexpectedToken }!*Expr {
|
||||||
|
var expr = try self.allocator.create(Expr);
|
||||||
|
const token = self.advance();
|
||||||
|
switch (token.token_type) {
|
||||||
|
TokenType.FALSE => expr.* = Expr{ .literal = LiteralExpr{ .boolean = false } },
|
||||||
|
TokenType.TRUE => expr.* = Expr{ .literal = LiteralExpr{ .boolean = true } },
|
||||||
|
TokenType.NIL => expr.* = Expr{ .literal = LiteralExpr{ .nil = false } },
|
||||||
|
TokenType.NUMBER => expr.* = Expr{ .literal = LiteralExpr{ .number = token.value.?.number } },
|
||||||
|
TokenType.STRING => expr.* = Expr{ .literal = LiteralExpr{ .string = token.value.?.string } },
|
||||||
|
TokenType.LEFT_PAREN => {
|
||||||
|
expr.* = Expr{
|
||||||
|
.grouping = GroupingExpr{ .expr = try self.expression() },
|
||||||
|
};
|
||||||
|
var next_token = self.advance();
|
||||||
|
if (next_token.token_type != TokenType.RIGHT_PAREN) {
|
||||||
|
errToken(next_token, "Unclosed left paren.");
|
||||||
|
return ParseError.UnmatchedParen;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
errToken(token, "Unexpected token.");
|
||||||
|
return ParseError.UnexpectedToken;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peekType(self: Self) TokenType {
|
||||||
|
return self.peek().token_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(self: Self) Token {
|
||||||
|
return self.tokens.items[self.current];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn previous(self: Self) Token {
|
||||||
|
// FIXME: Bounds check.
|
||||||
|
return self.tokens.items[self.current - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isAtEnd(self: Self) bool {
|
||||||
|
return self.peekType() == TokenType.EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(self: *Self) Token {
|
||||||
|
if (self.isAtEnd()) {
|
||||||
|
return self.peek();
|
||||||
|
}
|
||||||
|
self.current += 1;
|
||||||
|
return self.previous();
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,105 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const err = @import("error.zig");
|
||||||
|
const expr_zig = @import("expr.zig");
|
||||||
|
const Expr = expr_zig.Expr;
|
||||||
|
const LiteralExpr = expr_zig.LiteralExpr;
|
||||||
|
const LiteralTag = expr_zig.LiteralTag;
|
||||||
|
const token = @import("token.zig");
|
||||||
|
|
||||||
|
pub const RuntimeError = error{
|
||||||
|
TypeError,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn printLiteral(expr: LiteralExpr) void {
|
||||||
|
switch (expr) {
|
||||||
|
.number => |num| std.debug.print("{}\n", .{num}),
|
||||||
|
.string => |string| std.debug.print("{s}\n", .{string}),
|
||||||
|
.boolean => |boolean| std.debug.print("{}\n", .{boolean}),
|
||||||
|
.nil => std.debug.print("nil\n", .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret(expr: Expr) void {
|
||||||
|
var err_or_expr = evaluate(expr);
|
||||||
|
if (err_or_expr) |res| {
|
||||||
|
printLiteral(res);
|
||||||
|
} else |_| {
|
||||||
|
std.debug.print("Runtime Error.\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(expr: Expr) !LiteralExpr {
|
||||||
|
switch (expr) {
|
||||||
|
.literal => |literal| return literal,
|
||||||
|
.grouping => |grouping| return evaluate(grouping.expr.*),
|
||||||
|
.unary => |unary| {
|
||||||
|
var right = try evaluate(unary.right.*);
|
||||||
|
|
||||||
|
switch (unary.operator.token_type) {
|
||||||
|
.MINUS => switch (right) {
|
||||||
|
.number => |num| return LiteralExpr{ .number = -1 * num },
|
||||||
|
else => {
|
||||||
|
err.errToken(unary.operator, "Negating non-number");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.BANG => return LiteralExpr{
|
||||||
|
.boolean = !isTruthy(right),
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.binary => |binary| {
|
||||||
|
var left = try evaluate(binary.left.*);
|
||||||
|
var right = try evaluate(binary.right.*);
|
||||||
|
switch (binary.operator.token_type) {
|
||||||
|
.STAR => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number * right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.SLASH => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number / right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// FIXME: make this handle string concatenation.
|
||||||
|
.PLUS => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number + right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.MINUS => {
|
||||||
|
try checkNumberOperands(binary.operator, left, right);
|
||||||
|
return LiteralExpr{
|
||||||
|
.number = left.number - right.number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isTruthy(expr: LiteralExpr) bool {
|
||||||
|
return switch (expr) {
|
||||||
|
.nil => false,
|
||||||
|
.boolean => |*boolean| boolean.*,
|
||||||
|
else => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkNumberOperands(operator: token.Token, left: LiteralExpr, right: LiteralExpr) RuntimeError!void {
|
||||||
|
if (@as(LiteralTag, left) != .number) {
|
||||||
|
err.errToken(operator, "Left operand is non-numeric");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
}
|
||||||
|
if (@as(LiteralTag, right) != .number) {
|
||||||
|
err.errToken(operator, "Right operand is non-numeric");
|
||||||
|
return RuntimeError.TypeError;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ pub const Scanner = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Handle error.
|
// FIXME: Handle error.
|
||||||
self.tokens.append(token.Token{ .token_type = token.TokenType.EOF, .lexeme = "", .line = self.line }) catch {};
|
self.tokens.append(token.Token{ .token_type = token.TokenType.EOF, .lexeme = "", .line = self.line, .value = null }) catch {};
|
||||||
return self.tokens;
|
return self.tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ pub const Scanner = struct {
|
||||||
// Closing ".
|
// Closing ".
|
||||||
_ = self.advance();
|
_ = self.advance();
|
||||||
|
|
||||||
self.addToken(token.TokenType.STRING);
|
self.addString();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn number(self: *Scanner) void {
|
fn number(self: *Scanner) void {
|
||||||
|
@ -156,7 +156,7 @@ pub const Scanner = struct {
|
||||||
while (isDigit(self.peek())) _ = self.advance();
|
while (isDigit(self.peek())) _ = self.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addToken(token.TokenType.NUMBER);
|
self.addNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identifier(self: *Scanner) void {
|
fn identifier(self: *Scanner) void {
|
||||||
|
@ -207,8 +207,24 @@ pub const Scanner = struct {
|
||||||
return isDigit(char) or isAlpha(char);
|
return isDigit(char) or isAlpha(char);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addToken(self: *Scanner, token_type: token.TokenType) void {
|
fn addTokenInternal(self: *Scanner, token_type: token.TokenType, token_value: ?token.Token.Value) void {
|
||||||
// FIXME: Handle error.
|
// FIXME: Handle error.
|
||||||
self.tokens.append(token.Token{ .token_type = token_type, .lexeme = self.source[self.start..self.current], .line = self.line }) catch {};
|
self.tokens.append(token.Token{ .token_type = token_type, .lexeme = self.source[self.start..self.current], .line = self.line, .value = token_value }) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addToken(self: *Scanner, token_type: token.TokenType) void {
|
||||||
|
self.addTokenInternal(token_type, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addNumber(self: *Scanner) void {
|
||||||
|
// FIXME: Handle errors.
|
||||||
|
const float = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch 0;
|
||||||
|
self.addTokenInternal(token.TokenType.NUMBER, token.Token.Value{ .number = float });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addString(self: *Scanner) void {
|
||||||
|
self.addTokenInternal(token.TokenType.STRING, token.Token.Value{
|
||||||
|
.string = self.source[(self.start + 1)..(self.current - 1)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,6 +54,12 @@ pub const Token = struct {
|
||||||
token_type: TokenType,
|
token_type: TokenType,
|
||||||
lexeme: []const u8,
|
lexeme: []const u8,
|
||||||
line: u64,
|
line: u64,
|
||||||
|
value: ?Value,
|
||||||
|
|
||||||
|
pub const Value = union {
|
||||||
|
number: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
fn toString(self: *Token, alloc: std.mem.Allocator) ![]u8 {
|
fn toString(self: *Token, alloc: std.mem.Allocator) ![]u8 {
|
||||||
return std.fmt.allocPrint(alloc, "{} {} {}", .{ self.token_type, self.lexeme, self.line });
|
return std.fmt.allocPrint(alloc, "{} {} {}", .{ self.token_type, self.lexeme, self.line });
|
||||||
|
|
Loading…
Reference in New Issue