Merge pull request 'Implement Control Flow Statements in Kym' (#37) from kym-control-flow into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #37
This commit is contained in:
commit
1e3f676698
|
@ -1,24 +1,23 @@
|
||||||
|
|
||||||
# Test comment.
|
i = 0
|
||||||
|
|
||||||
pos = @vec3(10, 20, 0.3)
|
while i < 5:
|
||||||
coord = pos.xz
|
@print("hello, world")
|
||||||
|
|
||||||
options = {
|
i = i + 1
|
||||||
.title = "Afterglow",
|
end
|
||||||
|
|
||||||
|
if i > 6:
|
||||||
|
@print("`i` greater than `6`")
|
||||||
|
elif i == 4:
|
||||||
|
@print("`i` is equal to `4`")
|
||||||
|
else:
|
||||||
|
@print("i'unno")
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
.title = "Game",
|
||||||
.width = 1280,
|
.width = 1280,
|
||||||
.height = 800,
|
.height = 800,
|
||||||
.tick_rate = 60,
|
.tick_rate = 60,
|
||||||
|
|
||||||
["foo"] = "bar",
|
|
||||||
[42] = "42",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options["foo"] = "rab"
|
|
||||||
options[42] = "24"
|
|
||||||
|
|
||||||
@print(options.title)
|
|
||||||
@print(options["foo"])
|
|
||||||
@print(options[42])
|
|
||||||
|
|
||||||
return options
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const Ast = @import("./kym/Ast.zig");
|
const ast = @import("./kym/ast.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ pub const RuntimeEnv = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Opcode = union (enum) {
|
const Opcode = union (enum) {
|
||||||
|
pop,
|
||||||
push_nil,
|
push_nil,
|
||||||
push_true,
|
push_true,
|
||||||
push_false,
|
push_false,
|
||||||
|
@ -61,6 +62,9 @@ pub const RuntimeEnv = struct {
|
||||||
clt,
|
clt,
|
||||||
cge,
|
cge,
|
||||||
cle,
|
cle,
|
||||||
|
|
||||||
|
jt: u32,
|
||||||
|
jf: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const OpcodeList = coral.list.Stack(Opcode);
|
const OpcodeList = coral.list.Stack(Opcode);
|
||||||
|
@ -69,7 +73,7 @@ pub const RuntimeEnv = struct {
|
||||||
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
|
||||||
local_identifiers_count: u8 = 0,
|
local_identifiers_count: u8 = 0,
|
||||||
|
|
||||||
fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: Ast.Expression) RuntimeError!void {
|
fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: ast.Expression) RuntimeError!void {
|
||||||
const number_format = coral.utf8.DecimalFormat{
|
const number_format = coral.utf8.DecimalFormat{
|
||||||
.delimiter = "_",
|
.delimiter = "_",
|
||||||
.positive_prefix = .none,
|
.positive_prefix = .none,
|
||||||
|
@ -132,9 +136,9 @@ pub const RuntimeEnv = struct {
|
||||||
.subtraction => .sub,
|
.subtraction => .sub,
|
||||||
.multiplication => .mul,
|
.multiplication => .mul,
|
||||||
.divsion => .div,
|
.divsion => .div,
|
||||||
.greater_equals_comparison => .eql,
|
.greater_equals_comparison => .cge,
|
||||||
.greater_than_comparison => .cgt,
|
.greater_than_comparison => .cgt,
|
||||||
.equals_comparison => .cge,
|
.equals_comparison => .eql,
|
||||||
.less_than_comparison => .clt,
|
.less_than_comparison => .clt,
|
||||||
.less_equals_comparison => .cle,
|
.less_equals_comparison => .cle,
|
||||||
});
|
});
|
||||||
|
@ -200,21 +204,23 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
.local_get => |local_get| {
|
.local_get => |local_get| {
|
||||||
try chunk.opcodes.push_one(.{
|
try chunk.opcodes.push_one(.{
|
||||||
.push_local = self.resolve_local(local_get) orelse {
|
.push_local = self.resolve_local(local_get.identifier) orelse {
|
||||||
return chunk.env.raise(error.OutOfMemory, "undefined local");
|
return chunk.env.raise(error.OutOfMemory, "undefined local");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
.local_set => |local_set| {
|
.local_set => |local_set| {
|
||||||
if (self.resolve_local(local_set)) |index| {
|
try self.compile_expression(chunk, local_set.value_expression.*);
|
||||||
|
|
||||||
|
if (self.resolve_local(local_set.identifier)) |index| {
|
||||||
try chunk.opcodes.push_one(.{.local_set = index});
|
try chunk.opcodes.push_one(.{.local_set = index});
|
||||||
} else {
|
} else {
|
||||||
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
|
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
|
||||||
return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals");
|
return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.local_identifiers_buffer[self.local_identifiers_count] = local_set;
|
self.local_identifiers_buffer[self.local_identifiers_count] = local_set.identifier;
|
||||||
self.local_identifiers_count += 1;
|
self.local_identifiers_count += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -255,17 +261,62 @@ pub const RuntimeEnv = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_statement(self: *CompilationUnit, chunk: *Chunk, statement: Ast.Statement) RuntimeError!void {
|
fn compile_statement(self: *CompilationUnit, chunk: *Chunk, statement: ast.Statement) RuntimeError!void {
|
||||||
switch (statement) {
|
switch (statement) {
|
||||||
.@"return" => |@"return"| {
|
.@"return" => |@"return"| {
|
||||||
if (@"return".expression) |expression| {
|
if (@"return") |expression| {
|
||||||
try self.compile_expression(chunk, expression);
|
try self.compile_expression(chunk, expression);
|
||||||
} else {
|
} else {
|
||||||
try chunk.opcodes.push_one(.push_nil);
|
try chunk.opcodes.push_one(.push_nil);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.expression => |expression| try self.compile_expression(chunk, expression),
|
.block => |block| {
|
||||||
|
for (block.values) |block_statement| {
|
||||||
|
try self.compile_statement(chunk, block_statement);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"while" => |@"while"| {
|
||||||
|
try self.compile_expression(chunk, @"while".condition_expression);
|
||||||
|
try chunk.opcodes.push_one(.{.jf = 0});
|
||||||
|
|
||||||
|
const origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1));
|
||||||
|
|
||||||
|
for (@"while".block_statements.values) |block_statement| {
|
||||||
|
try self.compile_statement(chunk, block_statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.opcodes.values[origin_index].jf = @intCast(chunk.opcodes.values.len - 1);
|
||||||
|
|
||||||
|
try self.compile_expression(chunk, @"while".condition_expression);
|
||||||
|
try chunk.opcodes.push_one(.{.jt = origin_index});
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"if" => |@"if"| {
|
||||||
|
try self.compile_expression(chunk, @"if".condition_expression);
|
||||||
|
try chunk.opcodes.push_one(.{.jf = 0});
|
||||||
|
|
||||||
|
const origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1));
|
||||||
|
|
||||||
|
for (@"if".block_statements.values) |block_statement| {
|
||||||
|
try self.compile_statement(chunk, block_statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.opcodes.values[origin_index].jf = @intCast(chunk.opcodes.values.len - 1);
|
||||||
|
|
||||||
|
if (@"if".else_statement) |else_statement| {
|
||||||
|
try self.compile_statement(chunk, else_statement.*);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.expression => |expression| {
|
||||||
|
try self.compile_expression(chunk, expression);
|
||||||
|
|
||||||
|
if (expression == .invoke) {
|
||||||
|
try chunk.opcodes.push_one(.pop);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,10 +339,10 @@ pub const RuntimeEnv = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn compile(self: *Chunk, ast: Ast) RuntimeError!void {
|
fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void {
|
||||||
var unit = CompilationUnit{};
|
var unit = CompilationUnit{};
|
||||||
|
|
||||||
for (ast.list_statements()) |statement| {
|
for (statements) |statement| {
|
||||||
try unit.compile_statement(self, statement);
|
try unit.compile_statement(self, statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,8 +373,16 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
defer coral.debug.assert(self.env.frames.pop() != null);
|
defer coral.debug.assert(self.env.frames.pop() != null);
|
||||||
|
|
||||||
for (self.opcodes.values) |opcode| {
|
var opcode_cursor = @as(u32, 0);
|
||||||
switch (opcode) {
|
|
||||||
|
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
|
||||||
|
switch (self.opcodes.values[opcode_cursor]) {
|
||||||
|
.pop => {
|
||||||
|
if (try self.pop_local()) |ref| {
|
||||||
|
self.env.discard(ref);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
.push_nil => try self.env.locals.push_one(null),
|
.push_nil => try self.env.locals.push_one(null),
|
||||||
.push_true => try self.env.locals.push_one(try self.env.new_boolean(true)),
|
.push_true => try self.env.locals.push_one(try self.env.new_boolean(true)),
|
||||||
.push_false => try self.env.locals.push_one(try self.env.new_boolean(false)),
|
.push_false => try self.env.locals.push_one(try self.env.new_boolean(false)),
|
||||||
|
@ -337,6 +396,10 @@ pub const RuntimeEnv = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
.push_local => |push_local| {
|
.push_local => |push_local| {
|
||||||
|
if (push_local >= self.env.locals.values.len) {
|
||||||
|
return self.env.raise(error.IllegalState, "invalid local");
|
||||||
|
}
|
||||||
|
|
||||||
if (self.env.locals.values[push_local]) |local| {
|
if (self.env.locals.values[push_local]) |local| {
|
||||||
try self.env.locals.push_one(try self.env.acquire(local));
|
try self.env.locals.push_one(try self.env.acquire(local));
|
||||||
} else {
|
} else {
|
||||||
|
@ -401,15 +464,11 @@ pub const RuntimeEnv = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
.object_get => {
|
.object_get => {
|
||||||
const index = (try self.pop_local()) orelse {
|
const index = try self.env.expect(try self.pop_local());
|
||||||
return self.env.raise(error.TypeMismatch, "nil is not a valid index");
|
|
||||||
};
|
|
||||||
|
|
||||||
defer self.env.discard(index);
|
defer self.env.discard(index);
|
||||||
|
|
||||||
const indexable = (try self.pop_local()) orelse {
|
const indexable = try self.env.expect(try self.pop_local());
|
||||||
return self.env.raise(error.TypeMismatch, "nil is not a valid indexable");
|
|
||||||
};
|
|
||||||
|
|
||||||
defer self.env.discard(indexable);
|
defer self.env.discard(indexable);
|
||||||
|
|
||||||
|
@ -791,6 +850,29 @@ pub const RuntimeEnv = struct {
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.jf => |jf| {
|
||||||
|
const condition = (try self.pop_local()) orelse {
|
||||||
|
opcode_cursor = jf;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.env.discard(condition);
|
||||||
|
|
||||||
|
if (!condition.is_truthy()) {
|
||||||
|
opcode_cursor = jf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.jt => |jt| {
|
||||||
|
const condition = (try self.pop_local()) orelse continue;
|
||||||
|
|
||||||
|
self.env.discard(condition);
|
||||||
|
|
||||||
|
if (condition.is_truthy()) {
|
||||||
|
opcode_cursor = jt;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1044,20 +1126,18 @@ pub const RuntimeEnv = struct {
|
||||||
defer self.allocator.deallocate(file_data);
|
defer self.allocator.deallocate(file_data);
|
||||||
|
|
||||||
const file_name = file_path.to_string() orelse "<script>";
|
const file_name = file_path.to_string() orelse "<script>";
|
||||||
var ast = Ast.make(self.allocator, file_name);
|
|
||||||
|
|
||||||
defer ast.free();
|
|
||||||
|
|
||||||
ast.parse(file_data) catch |parse_error| return switch (parse_error) {
|
|
||||||
error.BadSyntax => self.raise(error.BadSyntax, ast.error_message()),
|
|
||||||
error.OutOfMemory => error.OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
var chunk = try Chunk.make(self, file_name);
|
var chunk = try Chunk.make(self, file_name);
|
||||||
|
|
||||||
defer chunk.free();
|
defer chunk.free();
|
||||||
|
|
||||||
try chunk.compile(ast);
|
var ast_tree = ast.Tree.make(self.allocator, file_name);
|
||||||
|
|
||||||
|
defer ast_tree.free();
|
||||||
|
|
||||||
|
try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) {
|
||||||
|
error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()),
|
||||||
|
error.OutOfMemory => error.OutOfMemory,
|
||||||
|
});
|
||||||
|
|
||||||
return chunk.execute();
|
return chunk.execute();
|
||||||
}
|
}
|
||||||
|
@ -1512,6 +1592,21 @@ pub const RuntimeRef = opaque {
|
||||||
.dynamic => true,
|
.dynamic => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn typename(self: *const RuntimeRef) []const coral.io.Byte {
|
||||||
|
return switch (self.object().payload) {
|
||||||
|
.false => "false",
|
||||||
|
.true => "true",
|
||||||
|
.float => "float",
|
||||||
|
.fixed => "fixed",
|
||||||
|
.symbol => "symbol",
|
||||||
|
.vector2 => "vector2",
|
||||||
|
.vector3 => "vector3",
|
||||||
|
.syscall => "syscall",
|
||||||
|
.string => "string",
|
||||||
|
.dynamic => "dynamic",
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef;
|
pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef;
|
||||||
|
|
|
@ -1,567 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const tokens = @import("./tokens.zig");
|
|
||||||
|
|
||||||
name: []const coral.io.Byte,
|
|
||||||
allocator: coral.io.Allocator,
|
|
||||||
arena: coral.arena.Stacking,
|
|
||||||
statements: Statement.List,
|
|
||||||
error_buffer: coral.list.ByteStack,
|
|
||||||
tokenizer: tokens.Tokenizer,
|
|
||||||
|
|
||||||
pub const Expression = union (enum) {
|
|
||||||
nil_literal,
|
|
||||||
true_literal,
|
|
||||||
false_literal,
|
|
||||||
builtin: []const coral.io.Byte,
|
|
||||||
number_literal: []const coral.io.Byte,
|
|
||||||
string_literal: []const coral.io.Byte,
|
|
||||||
symbol_literal: []const coral.io.Byte,
|
|
||||||
table_literal: TableLiteral,
|
|
||||||
grouped_expression: *Expression,
|
|
||||||
local_get: []const coral.io.Byte,
|
|
||||||
local_set: []const coral.io.Byte,
|
|
||||||
|
|
||||||
field_get: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
},
|
|
||||||
|
|
||||||
field_set: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
value_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
subscript_get: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
subscript_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
subscript_set: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
subscript_expression: *Expression,
|
|
||||||
value_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
binary_operation: struct {
|
|
||||||
operator: BinaryOperator,
|
|
||||||
lhs_expression: *Expression,
|
|
||||||
rhs_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
unary_operation: struct {
|
|
||||||
operator: UnaryOperator,
|
|
||||||
expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
invoke: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
argument_expressions: List,
|
|
||||||
},
|
|
||||||
|
|
||||||
pub const BinaryOperator = enum {
|
|
||||||
addition,
|
|
||||||
subtraction,
|
|
||||||
multiplication,
|
|
||||||
divsion,
|
|
||||||
equals_comparison,
|
|
||||||
greater_than_comparison,
|
|
||||||
greater_equals_comparison,
|
|
||||||
less_than_comparison,
|
|
||||||
less_equals_comparison,
|
|
||||||
|
|
||||||
fn token(self: BinaryOperator) tokens.Token {
|
|
||||||
return switch (self) {
|
|
||||||
.addition => .symbol_plus,
|
|
||||||
.subtraction => .symbol_minus,
|
|
||||||
.multiplication => .symbol_asterisk,
|
|
||||||
.divsion => .symbol_forward_slash,
|
|
||||||
.equals_comparison => .symbol_double_equals,
|
|
||||||
.greater_than_comparison => .symbol_greater_than,
|
|
||||||
.greater_equals_comparison => .symbol_greater_equals,
|
|
||||||
.less_than_comparison => .symbol_less_than,
|
|
||||||
.less_equals_comparison => .symbol_less_equals,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TableLiteral = coral.list.Stack(struct {
|
|
||||||
key_expression: Expression,
|
|
||||||
value_expression: Expression,
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const List = coral.list.Stack(Expression);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ExpressionParser = fn (self: *Self) ParseError!Expression;
|
|
||||||
|
|
||||||
pub const ParseError = error {
|
|
||||||
OutOfMemory,
|
|
||||||
BadSyntax,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Statement = union (enum) {
|
|
||||||
@"return": struct {
|
|
||||||
expression: ?Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
expression: Expression,
|
|
||||||
|
|
||||||
const List = coral.list.Stack(Statement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnaryOperator = enum {
|
|
||||||
boolean_negation,
|
|
||||||
numeric_negation,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn binary_operation_parser(
|
|
||||||
comptime parse_next: ExpressionParser,
|
|
||||||
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
|
||||||
|
|
||||||
const BinaryOperationParser = struct {
|
|
||||||
fn parse(self: *Self) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
var expression = try parse_next(self);
|
|
||||||
|
|
||||||
inline for (operators) |operator| {
|
|
||||||
const token = comptime operator.token();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == coral.io.tag_of(token)) {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected other half of expression after `" ++ comptime token.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)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return BinaryOperationParser.parse;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_message(self: Self) []const coral.io.Byte {
|
|
||||||
return self.error_buffer.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(self: *Self) void {
|
|
||||||
self.arena.free();
|
|
||||||
self.statements.free();
|
|
||||||
self.error_buffer.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make(allocator: coral.io.Allocator, ast_name: []const coral.io.Byte) Self {
|
|
||||||
return Self{
|
|
||||||
.arena = coral.arena.Stacking.make(allocator, 4096),
|
|
||||||
.error_buffer = coral.list.ByteStack.make(allocator),
|
|
||||||
.statements = Statement.List.make(allocator),
|
|
||||||
.tokenizer = .{.source = ""},
|
|
||||||
.allocator = allocator,
|
|
||||||
.name = ast_name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report(self: *Self, message: []const coral.io.Byte) ParseError {
|
|
||||||
coral.utf8.print_formatted(coral.list.stack_as_writer(&self.error_buffer), "{name}@{line}: {message}", .{
|
|
||||||
.name = self.name,
|
|
||||||
.line = self.tokenizer.lines_stepped,
|
|
||||||
.message = message,
|
|
||||||
}) catch return error.OutOfMemory;
|
|
||||||
|
|
||||||
return error.BadSyntax;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_statements(self: Self) []const Statement {
|
|
||||||
return self.statements.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void {
|
|
||||||
self.tokenizer = .{.source = data};
|
|
||||||
|
|
||||||
var has_returned = false;
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try self.statements.push_one(parse_statement: {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.end => return,
|
|
||||||
|
|
||||||
.keyword_return => {
|
|
||||||
if (has_returned) {
|
|
||||||
return self.report("multiple returns in function scope but expected only one");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
|
||||||
break: parse_statement .{
|
|
||||||
.@"return" = .{
|
|
||||||
.expression = try self.parse_expression(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
|
||||||
return self.report("expected end or newline after return statement");
|
|
||||||
}
|
|
||||||
|
|
||||||
has_returned = true;
|
|
||||||
|
|
||||||
break: parse_statement .{
|
|
||||||
.@"return" = .{
|
|
||||||
.expression = null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
break: parse_statement .{
|
|
||||||
.expression = try self.parse_expression()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse_additive = binary_operation_parser(parse_equality, &.{
|
|
||||||
.addition,
|
|
||||||
.subtraction,
|
|
||||||
});
|
|
||||||
|
|
||||||
const parse_comparison = binary_operation_parser(parse_term, &.{
|
|
||||||
.greater_than_comparison,
|
|
||||||
.greater_equals_comparison,
|
|
||||||
.less_than_comparison,
|
|
||||||
.less_equals_comparison
|
|
||||||
});
|
|
||||||
|
|
||||||
const parse_equality = binary_operation_parser(parse_comparison, &.{
|
|
||||||
.equals_comparison,
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn parse_expression(self: *Self) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
const expression = try parse_additive(self);
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .symbol_equals) {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected assignment after `=`");
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (expression) {
|
|
||||||
.local_get => |local_get| .{.local_set = local_get},
|
|
||||||
|
|
||||||
.field_get => |field_get| .{
|
|
||||||
.field_set = .{
|
|
||||||
.object_expression = field_get.object_expression,
|
|
||||||
.identifier = field_get.identifier,
|
|
||||||
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.subscript_get => |subscript_get| .{
|
|
||||||
.subscript_set = .{
|
|
||||||
.object_expression = subscript_get.object_expression,
|
|
||||||
.subscript_expression = subscript_get.subscript_expression,
|
|
||||||
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
else => self.report("expected local or field on left-hand side of expression"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_factor(self: *Self) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
|
|
||||||
var expression = @as(Expression, parse: {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_paren_left => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected an expression after `(`");
|
|
||||||
}
|
|
||||||
|
|
||||||
const expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_paren_right) {
|
|
||||||
return self.report("expected a closing `)` after expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_nil => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .nil_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_true => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .true_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_false => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .false_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.number => |value| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.number_literal = value};
|
|
||||||
},
|
|
||||||
|
|
||||||
.string => |value| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.string_literal = value};
|
|
||||||
},
|
|
||||||
|
|
||||||
.identifier => |identifier| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.local_get = identifier};
|
|
||||||
},
|
|
||||||
|
|
||||||
.builtin => |builtin| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.builtin = builtin};
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_brace_left => {
|
|
||||||
var table_literal = Expression.TableLiteral.make(allocator);
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_period => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |identifier| identifier,
|
|
||||||
else => return self.report("expected identifier after `.`"),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after symbol");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
try table_literal.push_one(.{
|
|
||||||
.value_expression = try self.parse_expression(),
|
|
||||||
.key_expression = .{.symbol_literal = identifier},
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => self.tokenizer.skip_newlines(),
|
|
||||||
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `,` or `}` after expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bracket_left => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const subscript_expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_bracket_right) {
|
|
||||||
return self.report("expected `]` after subscript expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after `]`");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
try table_literal.push_one(.{
|
|
||||||
.value_expression = try self.parse_expression(),
|
|
||||||
.key_expression = subscript_expression,
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => self.tokenizer.skip_newlines(),
|
|
||||||
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `,` or `}` after expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `}` or fields in table literal"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_minus => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected expression after numeric negation (`-`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
break: parse .{
|
|
||||||
.unary_operation = .{
|
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
||||||
.operator = .numeric_negation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bang => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected expression after boolean negation (`!`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
break: parse .{
|
|
||||||
.unary_operation = .{
|
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
||||||
.operator = .boolean_negation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("unexpected token in expression"),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_period => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.field_get = .{
|
|
||||||
.identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |field_identifier| field_identifier,
|
|
||||||
else => return self.report("expected identifier after `.`"),
|
|
||||||
},
|
|
||||||
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bracket_left => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.subscript_get = .{
|
|
||||||
.subscript_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_bracket_right) {
|
|
||||||
return self.report("expected `]` subscript expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_paren_left => {
|
|
||||||
var argument_expressions = Expression.List.make(allocator);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
|
|
||||||
else => {
|
|
||||||
try argument_expressions.push_one(try self.parse_expression());
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => continue,
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
else => return self.report("expected `,` or `)` after function argument expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.invoke = .{
|
|
||||||
.argument_expressions = argument_expressions,
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse_term = binary_operation_parser(parse_factor, &.{
|
|
||||||
.multiplication,
|
|
||||||
.divsion,
|
|
||||||
});
|
|
|
@ -0,0 +1,677 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
pub const BinaryOperator = enum {
|
||||||
|
addition,
|
||||||
|
subtraction,
|
||||||
|
multiplication,
|
||||||
|
divsion,
|
||||||
|
equals_comparison,
|
||||||
|
greater_than_comparison,
|
||||||
|
greater_equals_comparison,
|
||||||
|
less_than_comparison,
|
||||||
|
less_equals_comparison,
|
||||||
|
|
||||||
|
fn builder(comptime build_next: ExpressionBuilder, comptime operators: []const BinaryOperator) ExpressionBuilder {
|
||||||
|
const Builder = struct {
|
||||||
|
fn build(self: *Tree) ParseError!Expression {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
var expression = try build_next(self);
|
||||||
|
|
||||||
|
inline for (operators) |operator| {
|
||||||
|
const token = @as(tokens.Token, switch (operator) {
|
||||||
|
.addition => .symbol_plus,
|
||||||
|
.subtraction => .symbol_minus,
|
||||||
|
.multiplication => .symbol_asterisk,
|
||||||
|
.divsion => .symbol_forward_slash,
|
||||||
|
.equals_comparison => .symbol_double_equals,
|
||||||
|
.greater_than_comparison => .symbol_greater_than,
|
||||||
|
.greater_equals_comparison => .symbol_greater_equals,
|
||||||
|
.less_than_comparison => .symbol_less_than,
|
||||||
|
.less_equals_comparison => .symbol_less_equals,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (self.tokenizer.token == coral.io.tag_of(token)) {
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report(
|
||||||
|
"expected other half of expression after `" ++
|
||||||
|
comptime token.text() ++
|
||||||
|
"`");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove once Zig has fixed struct self-reassignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.binary_operation = .{
|
||||||
|
.operator = operator,
|
||||||
|
.lhs_expression = unnecessary_temp,
|
||||||
|
.rhs_expression = try coral.io.allocate_one(allocator, try build_next(self)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Builder.build;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Expression = union (enum) {
|
||||||
|
nil_literal,
|
||||||
|
true_literal,
|
||||||
|
false_literal,
|
||||||
|
builtin: []const coral.io.Byte,
|
||||||
|
number_literal: []const coral.io.Byte,
|
||||||
|
string_literal: []const coral.io.Byte,
|
||||||
|
symbol_literal: []const coral.io.Byte,
|
||||||
|
table_literal: TableLiteral,
|
||||||
|
grouped_expression: *Expression,
|
||||||
|
|
||||||
|
local_get: struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
},
|
||||||
|
|
||||||
|
local_set: struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
value_expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
field_get: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
},
|
||||||
|
|
||||||
|
field_set: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
value_expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
subscript_get: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
subscript_expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
subscript_set: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
subscript_expression: *Expression,
|
||||||
|
value_expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
binary_operation: struct {
|
||||||
|
operator: BinaryOperator,
|
||||||
|
lhs_expression: *Expression,
|
||||||
|
rhs_expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
unary_operation: struct {
|
||||||
|
operator: UnaryOperator,
|
||||||
|
expression: *Expression,
|
||||||
|
},
|
||||||
|
|
||||||
|
invoke: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
argument_expressions: List,
|
||||||
|
},
|
||||||
|
|
||||||
|
const List = coral.list.Stack(Expression);
|
||||||
|
|
||||||
|
const TableLiteral = coral.list.Stack(struct {
|
||||||
|
key_expression: Expression,
|
||||||
|
value_expression: Expression,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExpressionBuilder = fn (self: *Tree) ParseError!Expression;
|
||||||
|
|
||||||
|
pub const ParseError = error {
|
||||||
|
OutOfMemory,
|
||||||
|
BadSyntax,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Statement = union (enum) {
|
||||||
|
@"return": ?Expression,
|
||||||
|
|
||||||
|
@"if": struct {
|
||||||
|
condition_expression: Expression,
|
||||||
|
block_statements: List,
|
||||||
|
else_statement: ?*Statement,
|
||||||
|
},
|
||||||
|
|
||||||
|
@"while": struct {
|
||||||
|
condition_expression: Expression,
|
||||||
|
block_statements: List,
|
||||||
|
},
|
||||||
|
|
||||||
|
block: List,
|
||||||
|
expression: Expression,
|
||||||
|
|
||||||
|
const List = coral.list.Stack(Statement);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Tree = struct {
|
||||||
|
name: []const coral.io.Byte,
|
||||||
|
allocator: coral.io.Allocator,
|
||||||
|
arena: coral.arena.Stacking,
|
||||||
|
error_buffer: coral.list.ByteStack,
|
||||||
|
tokenizer: tokens.Tokenizer,
|
||||||
|
parsed_statements: Statement.List,
|
||||||
|
has_returned: bool,
|
||||||
|
|
||||||
|
pub fn error_message(self: Tree) []const coral.io.Byte {
|
||||||
|
return self.error_buffer.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(self: *Tree) void {
|
||||||
|
self.parsed_statements.free();
|
||||||
|
self.error_buffer.free();
|
||||||
|
self.arena.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make(allocator: coral.io.Allocator, ast_name: []const coral.io.Byte) Tree {
|
||||||
|
return .{
|
||||||
|
.arena = coral.arena.Stacking.make(allocator, 4096),
|
||||||
|
.error_buffer = coral.list.ByteStack.make(allocator),
|
||||||
|
.parsed_statements = Statement.List.make(allocator),
|
||||||
|
.tokenizer = .{.source = ""},
|
||||||
|
.allocator = allocator,
|
||||||
|
.name = ast_name,
|
||||||
|
.has_returned = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(self: *Tree, data: []const coral.io.Byte) ParseError![]const Statement {
|
||||||
|
self.free();
|
||||||
|
|
||||||
|
self.tokenizer = .{.source = data};
|
||||||
|
self.has_returned = false;
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
while (self.tokenizer.token != .end) {
|
||||||
|
try self.parsed_statements.push_one(try self.parse_statement());
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.parsed_statements.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse_additive = BinaryOperator.builder(parse_equality, &.{
|
||||||
|
.addition,
|
||||||
|
.subtraction,
|
||||||
|
});
|
||||||
|
|
||||||
|
fn parse_branch(self: *Tree) ParseError!Statement {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
|
||||||
|
defer self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
const condition_expression = try self.parse_expression();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_colon) {
|
||||||
|
return self.report("expected `:` after `if` statement condition");
|
||||||
|
}
|
||||||
|
|
||||||
|
var statements = Statement.List.make(allocator);
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.keyword_end => {
|
||||||
|
return .{
|
||||||
|
.@"if" = .{
|
||||||
|
.condition_expression = condition_expression,
|
||||||
|
.block_statements = statements,
|
||||||
|
.else_statement = null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_else => {
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_colon) {
|
||||||
|
return self.report("expected `:` after `if` statement condition");
|
||||||
|
}
|
||||||
|
|
||||||
|
var else_statements = Statement.List.make(allocator);
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
while (self.tokenizer.token != .keyword_end) {
|
||||||
|
try else_statements.push_one(try self.parse_statement());
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.@"if" = .{
|
||||||
|
.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}),
|
||||||
|
.condition_expression = condition_expression,
|
||||||
|
.block_statements = statements,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_elif => {
|
||||||
|
return .{
|
||||||
|
.@"if" = .{
|
||||||
|
.else_statement = try coral.io.allocate_one(allocator, try self.parse_branch()),
|
||||||
|
.condition_expression = condition_expression,
|
||||||
|
.block_statements = statements,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => try statements.push_one(try self.parse_statement()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse_comparison = BinaryOperator.builder(parse_term, &.{
|
||||||
|
.greater_than_comparison,
|
||||||
|
.greater_equals_comparison,
|
||||||
|
.less_than_comparison,
|
||||||
|
.less_equals_comparison
|
||||||
|
});
|
||||||
|
|
||||||
|
const parse_equality = BinaryOperator.builder(parse_comparison, &.{
|
||||||
|
.equals_comparison,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn parse_expression(self: *Tree) ParseError!Expression {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
const expression = try self.parse_additive();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .symbol_equals) {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report("expected assignment after `=`");
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (expression) {
|
||||||
|
.local_get => |local_get| .{
|
||||||
|
.local_set = .{
|
||||||
|
.identifier = local_get.identifier,
|
||||||
|
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.field_get => |field_get| .{
|
||||||
|
.field_set = .{
|
||||||
|
.object_expression = field_get.object_expression,
|
||||||
|
.identifier = field_get.identifier,
|
||||||
|
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.subscript_get => |subscript_get| .{
|
||||||
|
.subscript_set = .{
|
||||||
|
.object_expression = subscript_get.object_expression,
|
||||||
|
.subscript_expression = subscript_get.subscript_expression,
|
||||||
|
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
else => self.report("expected local or field on left-hand side of expression"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_factor(self: *Tree) ParseError!Expression {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
|
||||||
|
var expression = @as(Expression, parse: {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_paren_left => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report("expected an expression after `(`");
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = try self.parse_expression();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_paren_right) {
|
||||||
|
return self.report("expected a closing `)` after expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_nil => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .nil_literal;
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_true => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .true_literal;
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_false => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .false_literal;
|
||||||
|
},
|
||||||
|
|
||||||
|
.number => |value| {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.number_literal = value};
|
||||||
|
},
|
||||||
|
|
||||||
|
.string => |value| {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.string_literal = value};
|
||||||
|
},
|
||||||
|
|
||||||
|
.identifier => |identifier| {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.local_get = .{.identifier = identifier}};
|
||||||
|
},
|
||||||
|
|
||||||
|
.builtin => |builtin| {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.builtin = builtin};
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_brace_left => {
|
||||||
|
var table_literal = Expression.TableLiteral.make(allocator);
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_brace_right => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.table_literal = table_literal};
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_period => {
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
const identifier = switch (self.tokenizer.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return self.report("expected identifier after `.`"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_equals) {
|
||||||
|
return self.report("expected `=` after symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
try table_literal.push_one(.{
|
||||||
|
.value_expression = try self.parse_expression(),
|
||||||
|
.key_expression = .{.symbol_literal = identifier},
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_comma => self.tokenizer.skip_newlines(),
|
||||||
|
|
||||||
|
.symbol_brace_right => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.table_literal = table_literal};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return self.report("expected `,` or `}` after expression"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bracket_left => {
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
const subscript_expression = try self.parse_expression();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_bracket_right) {
|
||||||
|
return self.report("expected `]` after subscript expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_equals) {
|
||||||
|
return self.report("expected `=` after `]`");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
try table_literal.push_one(.{
|
||||||
|
.value_expression = try self.parse_expression(),
|
||||||
|
.key_expression = subscript_expression,
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_comma => self.tokenizer.skip_newlines(),
|
||||||
|
|
||||||
|
.symbol_brace_right => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.table_literal = table_literal};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return self.report("expected `,` or `}` after expression"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return self.report("expected `}` or fields in table literal"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_minus => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report("expected expression after numeric negation (`-`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
break: parse .{
|
||||||
|
.unary_operation = .{
|
||||||
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
||||||
|
.operator = .numeric_negation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bang => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report("expected expression after boolean negation (`!`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
break: parse .{
|
||||||
|
.unary_operation = .{
|
||||||
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
||||||
|
.operator = .boolean_negation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return self.report("unexpected token in expression"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_period => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.field_get = .{
|
||||||
|
.identifier = switch (self.tokenizer.token) {
|
||||||
|
.identifier => |field_identifier| field_identifier,
|
||||||
|
else => return self.report("expected identifier after `.`"),
|
||||||
|
},
|
||||||
|
|
||||||
|
.object_expression = unnecessary_temp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bracket_left => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.subscript_get = .{
|
||||||
|
.subscript_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
||||||
|
.object_expression = unnecessary_temp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_bracket_right) {
|
||||||
|
return self.report("expected `]` subscript expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_paren_left => {
|
||||||
|
var argument_expressions = Expression.List.make(allocator);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
|
||||||
|
else => {
|
||||||
|
try argument_expressions.push_one(try self.parse_expression());
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_comma => continue,
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
else => return self.report("expected `,` or `)` after function argument expression"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.invoke = .{
|
||||||
|
.argument_expressions = argument_expressions,
|
||||||
|
.object_expression = unnecessary_temp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_statement(self: *Tree) ParseError!Statement {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.keyword_return => {
|
||||||
|
defer self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.has_returned) {
|
||||||
|
return self.report("multiple returns in lambda scope but expected only one");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
||||||
|
return .{.@"return" = try self.parse_expression()};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
||||||
|
return self.report("expected end or newline after return statement");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.has_returned = true;
|
||||||
|
|
||||||
|
return .{.@"return" = null};
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_while => {
|
||||||
|
defer self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
const condition_expression = try self.parse_expression();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_colon) {
|
||||||
|
return self.report("expected `:` after `while` statement");
|
||||||
|
}
|
||||||
|
|
||||||
|
var statements = Statement.List.make(allocator);
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
while (self.tokenizer.token != .keyword_end) {
|
||||||
|
try statements.push_one(try self.parse_statement());
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.@"while" = .{
|
||||||
|
.block_statements = statements,
|
||||||
|
.condition_expression = condition_expression,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_if => return self.parse_branch(),
|
||||||
|
else => return .{.expression = try self.parse_expression()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse_term = BinaryOperator.builder(parse_factor, &.{
|
||||||
|
.multiplication,
|
||||||
|
.divsion,
|
||||||
|
});
|
||||||
|
|
||||||
|
fn report(self: *Tree, message: []const coral.io.Byte) ParseError {
|
||||||
|
coral.utf8.print_formatted(coral.list.stack_as_writer(&self.error_buffer), "{name}@{line}: {message}", .{
|
||||||
|
.name = self.name,
|
||||||
|
.line = self.tokenizer.lines_stepped,
|
||||||
|
.message = message,
|
||||||
|
}) catch return error.OutOfMemory;
|
||||||
|
|
||||||
|
return error.BadSyntax;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UnaryOperator = enum {
|
||||||
|
boolean_negation,
|
||||||
|
numeric_negation,
|
||||||
|
};
|
|
@ -21,7 +21,7 @@ pub const Token = union(enum) {
|
||||||
symbol_bracket_left,
|
symbol_bracket_left,
|
||||||
symbol_bracket_right,
|
symbol_bracket_right,
|
||||||
symbol_period,
|
symbol_period,
|
||||||
symbol_lambda,
|
symbol_colon,
|
||||||
symbol_less_than,
|
symbol_less_than,
|
||||||
symbol_less_equals,
|
symbol_less_equals,
|
||||||
symbol_greater_than,
|
symbol_greater_than,
|
||||||
|
@ -38,6 +38,12 @@ pub const Token = union(enum) {
|
||||||
keyword_return,
|
keyword_return,
|
||||||
keyword_self,
|
keyword_self,
|
||||||
keyword_const,
|
keyword_const,
|
||||||
|
keyword_if,
|
||||||
|
keyword_do,
|
||||||
|
keyword_end,
|
||||||
|
keyword_while,
|
||||||
|
keyword_else,
|
||||||
|
keyword_elif,
|
||||||
|
|
||||||
pub fn text(self: Token) []const coral.io.Byte {
|
pub fn text(self: Token) []const coral.io.Byte {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
@ -62,7 +68,7 @@ pub const Token = union(enum) {
|
||||||
.symbol_bracket_left => "[",
|
.symbol_bracket_left => "[",
|
||||||
.symbol_bracket_right => "]",
|
.symbol_bracket_right => "]",
|
||||||
.symbol_period => ".",
|
.symbol_period => ".",
|
||||||
.symbol_lambda => "=>",
|
.symbol_colon => ":",
|
||||||
.symbol_less_than => "<",
|
.symbol_less_than => "<",
|
||||||
.symbol_less_equals => "<=",
|
.symbol_less_equals => "<=",
|
||||||
.symbol_greater_than => ">",
|
.symbol_greater_than => ">",
|
||||||
|
@ -79,6 +85,12 @@ pub const Token = union(enum) {
|
||||||
.keyword_true => "true",
|
.keyword_true => "true",
|
||||||
.keyword_return => "return",
|
.keyword_return => "return",
|
||||||
.keyword_self => "self",
|
.keyword_self => "self",
|
||||||
|
.keyword_if => "if",
|
||||||
|
.keyword_do => "do",
|
||||||
|
.keyword_end => "end",
|
||||||
|
.keyword_while => "while",
|
||||||
|
.keyword_elif => "elif",
|
||||||
|
.keyword_else => "else",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -86,7 +98,7 @@ pub const Token = union(enum) {
|
||||||
pub const Tokenizer = struct {
|
pub const Tokenizer = struct {
|
||||||
source: []const coral.io.Byte,
|
source: []const coral.io.Byte,
|
||||||
lines_stepped: usize = 1,
|
lines_stepped: usize = 1,
|
||||||
token: Token = .end,
|
token: Token = .newline,
|
||||||
|
|
||||||
pub fn skip_newlines(self: *Tokenizer) void {
|
pub fn skip_newlines(self: *Tokenizer) void {
|
||||||
self.step();
|
self.step();
|
||||||
|
@ -173,9 +185,29 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'n' => {
|
'd' => {
|
||||||
if (coral.io.ends_with(identifier, "il")) {
|
if (coral.io.ends_with(identifier, "o")) {
|
||||||
self.token = .keyword_nil;
|
self.token = .keyword_do;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'e' => {
|
||||||
|
if (coral.io.ends_with(identifier, "lse")) {
|
||||||
|
self.token = .keyword_else;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coral.io.ends_with(identifier, "lif")) {
|
||||||
|
self.token = .keyword_elif;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coral.io.ends_with(identifier, "nd")) {
|
||||||
|
self.token = .keyword_end;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -189,9 +221,17 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
't' => {
|
'i' => {
|
||||||
if (coral.io.ends_with(identifier, "rue")) {
|
if (coral.io.ends_with(identifier, "f")) {
|
||||||
self.token = .keyword_true;
|
self.token = .keyword_if;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'n' => {
|
||||||
|
if (coral.io.ends_with(identifier, "il")) {
|
||||||
|
self.token = .keyword_nil;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -213,6 +253,22 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
't' => {
|
||||||
|
if (coral.io.ends_with(identifier, "rue")) {
|
||||||
|
self.token = .keyword_true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'w' => {
|
||||||
|
if (coral.io.ends_with(identifier, "hile")) {
|
||||||
|
self.token = .keyword_while;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +394,13 @@ pub const Tokenizer = struct {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
':' => {
|
||||||
|
self.token = .symbol_colon;
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
'=' => {
|
'=' => {
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
|
|
||||||
|
@ -350,13 +413,6 @@ pub const Tokenizer = struct {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
'>' => {
|
|
||||||
cursor += 1;
|
|
||||||
self.token = .symbol_lambda;
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue