342 lines
8.7 KiB
Zig
Executable File
342 lines
8.7 KiB
Zig
Executable File
const coral = @import("coral");
|
|
|
|
const tokens = @import("./tokens.zig");
|
|
|
|
const types = @import("./types.zig");
|
|
|
|
allocator: coral.io.Allocator,
|
|
arena: coral.arena.Stacking,
|
|
statements: StatementList,
|
|
error_message: []const u8,
|
|
|
|
pub const Expression = union (enum) {
|
|
nil_literal,
|
|
true_literal,
|
|
false_literal,
|
|
integer_literal: types.Integer,
|
|
float_literal: types.Float,
|
|
string_literal: []const u8,
|
|
array_literal: coral.list.Stack(Expression),
|
|
|
|
table_literal: coral.list.Stack(struct {
|
|
identifier: []const u8,
|
|
expression: Expression,
|
|
}),
|
|
|
|
grouped_expression: *Expression,
|
|
|
|
binary_operation: struct {
|
|
operator: tokens.Token,
|
|
lhs_expression: *Expression,
|
|
rhs_expression: *Expression,
|
|
},
|
|
|
|
unary_operation: struct {
|
|
operator: tokens.Token,
|
|
expression: *Expression,
|
|
},
|
|
};
|
|
|
|
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression;
|
|
|
|
const Self = @This();
|
|
|
|
pub const Statement = union (enum) {
|
|
return_expression: Expression,
|
|
return_nothing,
|
|
};
|
|
|
|
const StatementList = coral.list.Stack(Statement);
|
|
|
|
const UnaryOperation = enum {
|
|
boolean_negation,
|
|
numeric_negation,
|
|
};
|
|
|
|
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const tokens.Token) ExpressionParser {
|
|
return struct {
|
|
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
|
|
var expression = try parse_next(self, tokenizer);
|
|
|
|
{
|
|
const allocator = self.arena.as_allocator();
|
|
|
|
inline for (operators) |operator| {
|
|
if (tokenizer.current_token == coral.io.tag_of(operator)) {
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = true}),
|
|
"expected right-hand side of expression after `" ++ comptime operator.text() ++ "`");
|
|
|
|
expression = .{
|
|
.binary_operation = .{
|
|
.operator = operator,
|
|
.lhs_expression = try coral.io.allocate_one(allocator, expression),
|
|
.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
}.parse;
|
|
}
|
|
|
|
fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.ParseError!void {
|
|
if (condition) {
|
|
return;
|
|
}
|
|
|
|
return self.fail_syntax(error_message);
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
self.arena.clear_allocations();
|
|
self.statements.deinit(self.allocator);
|
|
}
|
|
|
|
fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError {
|
|
self.error_message = error_message;
|
|
|
|
return error.BadSyntax;
|
|
}
|
|
|
|
pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Self {
|
|
return Self{
|
|
.arena = .{
|
|
.base_allocator = allocator,
|
|
.min_page_size = 4096,
|
|
},
|
|
|
|
.allocator = allocator,
|
|
.statements = .{},
|
|
.error_message = "",
|
|
};
|
|
}
|
|
|
|
pub fn list_statements(self: Self) []const Statement {
|
|
return self.statements.values;
|
|
}
|
|
|
|
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void {
|
|
self.reset();
|
|
|
|
errdefer self.reset();
|
|
|
|
var has_not_returned_yet = true;
|
|
|
|
while (tokenizer.step(.{.include_newlines = false})) {
|
|
switch (tokenizer.current_token) {
|
|
.keyword_return => {
|
|
try self.check_syntax(has_not_returned_yet, "cannot return more than once per function scope");
|
|
|
|
try self.statements.push_one(self.allocator, get_statement: {
|
|
if (tokenizer.step(.{.include_newlines = true})) {
|
|
if (tokenizer.current_token != .newline) {
|
|
break: get_statement .{.return_expression = try self.parse_expression(tokenizer)};
|
|
}
|
|
|
|
if (tokenizer.step(.{.include_newlines = true})) {
|
|
try self.check_syntax(
|
|
tokenizer.current_token == .newline,
|
|
"expected end of declaration after return expression");
|
|
}
|
|
}
|
|
|
|
break: get_statement .return_nothing;
|
|
});
|
|
|
|
has_not_returned_yet = false;
|
|
},
|
|
|
|
else => return self.fail_syntax("invalid statement"),
|
|
}
|
|
}
|
|
}
|
|
|
|
const parse_comparison = binary_operation_parser(parse_term, &.{
|
|
.symbol_greater_than,
|
|
.symbol_greater_equals,
|
|
.symbol_less_than,
|
|
.symbol_less_equals
|
|
});
|
|
|
|
const parse_equality = binary_operation_parser(parse_comparison, &.{
|
|
.symbol_double_equals,
|
|
});
|
|
|
|
const parse_expression = binary_operation_parser(parse_equality, &.{
|
|
.symbol_plus,
|
|
.symbol_minus,
|
|
});
|
|
|
|
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
|
|
switch (tokenizer.current_token) {
|
|
.symbol_paren_left => {
|
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "expected an expression after `(`");
|
|
|
|
const expression = try self.parse_expression(tokenizer);
|
|
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_paren_right,
|
|
"expected a closing `)` after expression");
|
|
|
|
return Expression{.grouped_expression = try coral.io.allocate_one(self.arena.as_allocator(), expression)};
|
|
},
|
|
|
|
.integer => |value| {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return Expression{
|
|
.integer_literal = coral.utf8.parse_decimal(types.Integer, value, .{}) catch |parse_error| {
|
|
return self.fail_syntax(switch (parse_error) {
|
|
error.BadSyntax => "invalid integer literal",
|
|
error.IntOverflow => "integer literal is too big",
|
|
});
|
|
},
|
|
};
|
|
},
|
|
|
|
.real => |value| {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return Expression{
|
|
.float_literal = coral.utf8.parse_decimal(types.Float, value, .{}) catch |parse_error| {
|
|
return self.fail_syntax(switch (parse_error) {
|
|
error.BadSyntax => "invalid float literal",
|
|
});
|
|
},
|
|
};
|
|
},
|
|
|
|
.string => |value| {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return Expression{.string_literal = value};
|
|
},
|
|
|
|
.symbol_bracket_left => {
|
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of array literal");
|
|
|
|
var expression = Expression{.array_literal = .{}};
|
|
|
|
coral.debug.assert(expression == .array_literal);
|
|
|
|
const allocator = self.arena.as_allocator();
|
|
const array_average_maximum = 32;
|
|
|
|
try expression.array_literal.grow(allocator, array_average_maximum);
|
|
|
|
while (true) {
|
|
switch (tokenizer.current_token) {
|
|
.symbol_bracket_right => {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return expression;
|
|
},
|
|
|
|
else => {
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = false}),
|
|
"expected `]` or expression after `[`");
|
|
|
|
try expression.array_literal.push_one(allocator, try self.parse_expression(tokenizer));
|
|
},
|
|
}
|
|
}
|
|
},
|
|
|
|
.symbol_brace_left => {
|
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal");
|
|
|
|
var expression = Expression{.table_literal = .{}};
|
|
|
|
coral.debug.assert(expression == .table_literal);
|
|
|
|
const allocator = self.arena.as_allocator();
|
|
|
|
while (true) {
|
|
switch (tokenizer.current_token) {
|
|
.symbol_brace_right => {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return expression;
|
|
},
|
|
|
|
.local => |identifier| {
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_equals,
|
|
"expected `=` after identifier");
|
|
|
|
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end after `=`");
|
|
|
|
try expression.table_literal.push_one(allocator, .{
|
|
.identifier = identifier,
|
|
.expression = try self.parse_expression(tokenizer),
|
|
});
|
|
|
|
switch (tokenizer.current_token) {
|
|
.symbol_comma => _ = tokenizer.step(.{.include_newlines = false}),
|
|
|
|
.symbol_brace_right => {
|
|
_ = tokenizer.step(.{.include_newlines = false});
|
|
|
|
return expression;
|
|
},
|
|
|
|
else => return self.fail_syntax("expected `,` or `}` after expression"),
|
|
}
|
|
},
|
|
|
|
else => return self.fail_syntax("expected `}` or fields in table literal"),
|
|
}
|
|
}
|
|
},
|
|
|
|
.symbol_minus => {
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = false}),
|
|
"expected expression after numeric negation (`-`)");
|
|
|
|
return Expression{
|
|
.unary_operation = .{
|
|
.expression = try coral.io.allocate_one(
|
|
self.arena.as_allocator(),
|
|
try self.parse_factor(tokenizer)),
|
|
|
|
.operator = .symbol_minus,
|
|
},
|
|
};
|
|
},
|
|
|
|
.symbol_bang => {
|
|
try self.check_syntax(
|
|
tokenizer.step(.{.include_newlines = false}),
|
|
"expected expression after numeric negation (`!`)");
|
|
|
|
return Expression{
|
|
.unary_operation = .{
|
|
.expression = try coral.io.allocate_one(
|
|
self.arena.as_allocator(),
|
|
try self.parse_factor(tokenizer)),
|
|
|
|
.operator = .symbol_bang,
|
|
},
|
|
};
|
|
},
|
|
|
|
else => return self.fail_syntax("unexpected token in expression"),
|
|
}
|
|
}
|
|
|
|
const parse_term = binary_operation_parser(parse_factor, &.{
|
|
.symbol_asterisk,
|
|
.symbol_forward_slash,
|
|
});
|
|
|
|
pub fn reset(self: *Self) void {
|
|
self.statements.clear();
|
|
self.arena.clear_allocations();
|
|
}
|