Implement Control Flow Statements in Kym #37
|
@ -14,8 +14,12 @@ options = {
|
|||
[42] = "42",
|
||||
}
|
||||
|
||||
options["foo"] = "rab"
|
||||
options[42] = "24"
|
||||
if pos.y > 0 do
|
||||
options["foo"] = "rab"
|
||||
options[42] = "24"
|
||||
|
||||
@print("Conditional hit!")
|
||||
end
|
||||
|
||||
@print(options.title)
|
||||
@print(options["foo"])
|
||||
|
|
|
@ -61,6 +61,9 @@ pub const RuntimeEnv = struct {
|
|||
clt,
|
||||
cge,
|
||||
cle,
|
||||
|
||||
jt: u32,
|
||||
jf: u32,
|
||||
};
|
||||
|
||||
const OpcodeList = coral.list.Stack(Opcode);
|
||||
|
@ -265,6 +268,22 @@ pub const RuntimeEnv = struct {
|
|||
}
|
||||
},
|
||||
|
||||
.@"if" => |@"if"| {
|
||||
try self.compile_expression(chunk, @"if".condition_expression);
|
||||
try chunk.opcodes.push_one(.{.jf = 0});
|
||||
|
||||
const jump_opcode = &chunk.opcodes.values[chunk.opcodes.values.len - 1];
|
||||
|
||||
for (@"if".block_statements.values) |block_statement| {
|
||||
try self.compile_statement(chunk, block_statement);
|
||||
}
|
||||
|
||||
coral.debug.assert(jump_opcode.* == .jf);
|
||||
coral.debug.assert(chunk.opcodes.values.len < coral.math.max_int(@typeInfo(u32).Int));
|
||||
|
||||
jump_opcode.jf = @intCast(chunk.opcodes.values.len);
|
||||
},
|
||||
|
||||
.expression => |expression| try self.compile_expression(chunk, expression),
|
||||
}
|
||||
}
|
||||
|
@ -288,10 +307,10 @@ pub const RuntimeEnv = struct {
|
|||
}
|
||||
};
|
||||
|
||||
fn compile(self: *Chunk, ast: Ast) RuntimeError!void {
|
||||
fn compile(self: *Chunk, statements: *const Ast.Statement.List) RuntimeError!void {
|
||||
var unit = CompilationUnit{};
|
||||
|
||||
for (ast.list_statements()) |statement| {
|
||||
for (statements.values) |statement| {
|
||||
try unit.compile_statement(self, statement);
|
||||
}
|
||||
}
|
||||
|
@ -322,8 +341,10 @@ pub const RuntimeEnv = struct {
|
|||
|
||||
defer coral.debug.assert(self.env.frames.pop() != null);
|
||||
|
||||
for (self.opcodes.values) |opcode| {
|
||||
switch (opcode) {
|
||||
var opcode_cursor = @as(u32, 0);
|
||||
|
||||
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
|
||||
switch (self.opcodes.values[opcode_cursor]) {
|
||||
.push_nil => try self.env.locals.push_one(null),
|
||||
.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)),
|
||||
|
@ -791,6 +812,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 +1088,18 @@ pub const RuntimeEnv = struct {
|
|||
defer self.allocator.deallocate(file_data);
|
||||
|
||||
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);
|
||||
|
||||
defer chunk.free();
|
||||
|
||||
try chunk.compile(ast);
|
||||
var ast = Ast.make(self.allocator, file_name);
|
||||
|
||||
defer ast.free();
|
||||
|
||||
try chunk.compile(ast.parse(file_data) catch |parse_error| return switch (parse_error) {
|
||||
error.BadSyntax => self.raise(error.BadSyntax, ast.error_message()),
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
});
|
||||
|
||||
return chunk.execute();
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ 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,
|
||||
parsed_statements: Statement.List,
|
||||
has_returned: bool,
|
||||
|
||||
pub const Expression = union (enum) {
|
||||
nil_literal,
|
||||
|
@ -108,9 +109,14 @@ pub const Statement = union (enum) {
|
|||
expression: ?Expression,
|
||||
},
|
||||
|
||||
@"if": struct {
|
||||
condition_expression: Expression,
|
||||
block_statements: List,
|
||||
},
|
||||
|
||||
expression: Expression,
|
||||
|
||||
const List = coral.list.Stack(Statement);
|
||||
pub const List = coral.list.Stack(Statement);
|
||||
};
|
||||
|
||||
const UnaryOperator = enum {
|
||||
|
@ -159,19 +165,20 @@ pub fn error_message(self: Self) []const coral.io.Byte {
|
|||
}
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
self.arena.free();
|
||||
self.statements.free();
|
||||
self.parsed_statements.free();
|
||||
self.error_buffer.free();
|
||||
self.arena.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),
|
||||
.parsed_statements = Statement.List.make(allocator),
|
||||
.tokenizer = .{.source = ""},
|
||||
.allocator = allocator,
|
||||
.name = ast_name,
|
||||
.has_returned = false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -185,58 +192,19 @@ fn report(self: *Self, message: []const coral.io.Byte) ParseError {
|
|||
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 {
|
||||
pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!*const Statement.List {
|
||||
self.tokenizer = .{.source = data};
|
||||
self.has_returned = false;
|
||||
|
||||
var has_returned = false;
|
||||
self.parsed_statements.free();
|
||||
|
||||
self.tokenizer.skip_newlines();
|
||||
self.parsed_statements = try self.parse_statements();
|
||||
|
||||
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");
|
||||
if (self.tokenizer.token == .keyword_end) {
|
||||
return self.report("unexpected `end` without matching `do` block");
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
return &self.parsed_statements;
|
||||
}
|
||||
|
||||
const parse_additive = binary_operation_parser(parse_equality, &.{
|
||||
|
@ -561,6 +529,69 @@ fn parse_factor(self: *Self) ParseError!Expression {
|
|||
return expression;
|
||||
}
|
||||
|
||||
fn parse_statements(self: *Self) ParseError!Statement.List {
|
||||
var statements = Statement.List.make(self.arena.as_allocator());
|
||||
|
||||
self.tokenizer.skip_newlines();
|
||||
|
||||
while (true) {
|
||||
try statements.push_one(parse_statement: {
|
||||
switch (self.tokenizer.token) {
|
||||
.end, .keyword_end => return statements,
|
||||
|
||||
.keyword_return => {
|
||||
if (self.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");
|
||||
}
|
||||
|
||||
self.has_returned = true;
|
||||
|
||||
break: parse_statement .{.@"return" = .{.expression = null}};
|
||||
},
|
||||
|
||||
.keyword_if => {
|
||||
self.tokenizer.step();
|
||||
|
||||
const condition_expression = try self.parse_expression();
|
||||
|
||||
if (self.tokenizer.token != .keyword_do) {
|
||||
return self.report("expected `do` block after if statement");
|
||||
}
|
||||
|
||||
self.tokenizer.step();
|
||||
|
||||
const if_statement = Statement{
|
||||
.@"if" = .{
|
||||
.block_statements = try self.parse_statements(),
|
||||
.condition_expression = condition_expression,
|
||||
},
|
||||
};
|
||||
|
||||
if (self.tokenizer.token != .keyword_end) {
|
||||
return self.report("expected `end` after block");
|
||||
}
|
||||
|
||||
self.tokenizer.skip_newlines();
|
||||
|
||||
break: parse_statement if_statement;
|
||||
},
|
||||
|
||||
else => break: parse_statement .{.expression = try self.parse_expression()},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const parse_term = binary_operation_parser(parse_factor, &.{
|
||||
.multiplication,
|
||||
.divsion,
|
||||
|
|
|
@ -38,6 +38,9 @@ pub const Token = union(enum) {
|
|||
keyword_return,
|
||||
keyword_self,
|
||||
keyword_const,
|
||||
keyword_if,
|
||||
keyword_do,
|
||||
keyword_end,
|
||||
|
||||
pub fn text(self: Token) []const coral.io.Byte {
|
||||
return switch (self) {
|
||||
|
@ -79,6 +82,9 @@ pub const Token = union(enum) {
|
|||
.keyword_true => "true",
|
||||
.keyword_return => "return",
|
||||
.keyword_self => "self",
|
||||
.keyword_if => "if",
|
||||
.keyword_do => "do",
|
||||
.keyword_end => "end",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -173,9 +179,17 @@ pub const Tokenizer = struct {
|
|||
}
|
||||
},
|
||||
|
||||
'n' => {
|
||||
if (coral.io.ends_with(identifier, "il")) {
|
||||
self.token = .keyword_nil;
|
||||
'd' => {
|
||||
if (coral.io.ends_with(identifier, "o")) {
|
||||
self.token = .keyword_do;
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
'e' => {
|
||||
if (coral.io.ends_with(identifier, "nd")) {
|
||||
self.token = .keyword_end;
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -189,9 +203,17 @@ pub const Tokenizer = struct {
|
|||
}
|
||||
},
|
||||
|
||||
't' => {
|
||||
if (coral.io.ends_with(identifier, "rue")) {
|
||||
self.token = .keyword_true;
|
||||
'i' => {
|
||||
if (coral.io.ends_with(identifier, "f")) {
|
||||
self.token = .keyword_if;
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
'n' => {
|
||||
if (coral.io.ends_with(identifier, "il")) {
|
||||
self.token = .keyword_nil;
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -213,6 +235,14 @@ pub const Tokenizer = struct {
|
|||
}
|
||||
},
|
||||
|
||||
't' => {
|
||||
if (coral.io.ends_with(identifier, "rue")) {
|
||||
self.token = .keyword_true;
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue