Implement 'break' and 'continue

This commit is contained in:
David Gonzalez Martin 2024-06-06 13:25:18 -06:00
parent f2b1066cc6
commit 56b6443c1f
3 changed files with 233 additions and 149 deletions

View File

@ -195,11 +195,9 @@ const Parser = struct{
fn parse_identifier(parser: *Parser, thread: *Thread, file: []const u8) u32 { fn parse_identifier(parser: *Parser, thread: *Thread, file: []const u8) u32 {
const identifier = parser.parse_raw_identifier(file); const identifier = parser.parse_raw_identifier(file);
if (identifier[0] != '"') { const keyword = parse_keyword(identifier);
const keyword = parse_keyword(identifier); if (keyword != ~(@as(u32, 0))) {
if (keyword != ~(@as(u32, 0))) { exit(1);
exit(1);
}
} }
const hash = intern_identifier(&thread.identifiers, identifier); const hash = intern_identifier(&thread.identifiers, identifier);
return hash; return hash;
@ -1405,16 +1403,20 @@ const Keyword = enum{
@"else", @"else",
@"for", @"for",
@"if", @"if",
@"break",
}; };
fn parse_keyword(identifier: []const u8) u32 { fn parse_keyword(identifier: []const u8) u32 {
inline for (@typeInfo(Keyword).Enum.fields) |keyword| { assert(identifier.len > 0);
if (byte_equal(identifier, keyword.name)) { if (identifier[0] != '"') {
return keyword.value; inline for (@typeInfo(Keyword).Enum.fields) |keyword| {
if (byte_equal(identifier, keyword.name)) {
return keyword.value;
}
} }
} else {
return ~@as(u32, 0);
} }
return ~@as(u32, 0);
} }
const Scope = struct { const Scope = struct {
@ -2656,10 +2658,16 @@ const Analyzer = struct{
current_function: *Function, current_function: *Function,
current_scope: *Scope, current_scope: *Scope,
exit_blocks: PinnedArray(*BasicBlock) = .{}, exit_blocks: PinnedArray(*BasicBlock) = .{},
loops: PinnedArray(LoopData) = .{},
return_block: ?*BasicBlock = null, return_block: ?*BasicBlock = null,
return_phi: ?*Phi = null, return_phi: ?*Phi = null,
}; };
const LoopData = struct {
break_block: *BasicBlock,
continue_block: *BasicBlock,
};
const brace_open = '{'; const brace_open = '{';
const brace_close = '}'; const brace_close = '}';
@ -3439,60 +3447,6 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
} }
switch (statement_start_ch) { switch (statement_start_ch) {
'r' => {
const identifier = parser.parse_raw_identifier(src);
if (byte_equal(identifier, "return")) {
parser.skip_space(src);
if (function.declaration.return_type.sema.id != .unresolved) {
const return_type = function.declaration.return_type;
const return_value = parser.parse_expression(analyzer, thread, file, return_type, .right);
parser.expect_character(src, ';');
if (analyzer.return_block) |return_block| {
const return_phi = analyzer.return_phi.?;
_ = return_phi.nodes.append(.{
.value = return_value,
.basic_block = analyzer.current_basic_block,
});
assert(analyzer.current_basic_block != return_block);
_ = emit_jump(analyzer, thread, return_block);
} else if (analyzer.exit_blocks.length > 0) {
const return_phi = thread.phis.append(.{
.instruction = .{
.id = .phi,
.value = .{
.sema = .{
.id = .instruction,
.thread = thread.get_index(),
.resolved = true,
},
},
},
.type = return_type,
});
analyzer.return_phi = return_phi;
const return_block = create_basic_block(thread);
analyzer.return_block = return_block;
_ = return_phi.nodes.append(.{
.value = return_value,
.basic_block = analyzer.current_basic_block,
});
_ = emit_jump(analyzer, thread, return_block);
} else {
build_return(thread, analyzer, return_value);
}
terminated = true;
} else {
exit(1);
}
} else {
parser.i = statement_start_ch_index;
}
},
// Local variable // Local variable
'>' => { '>' => {
parser.i += 1; parser.i += 1;
@ -3603,12 +3557,104 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
local_block.scope.declarations.put_no_clobber(local_name, &local_symbol.local_declaration.declaration); local_block.scope.declarations.put_no_clobber(local_name, &local_symbol.local_declaration.declaration);
}, },
'b' => {
const identifier = parser.parse_raw_identifier(src);
const break_kw = "break";
if (byte_equal(identifier, break_kw)) {
parser.skip_space(src);
parser.expect_character(src, ';');
if (analyzer.loops.length == 0) {
exit_with_error("break when no loop");
}
const inner_loop = analyzer.loops.const_slice()[analyzer.loops.length - 1];
_ = emit_jump(analyzer, thread, inner_loop.break_block);
terminated = true;
} else {
parser.i = statement_start_ch_index;
}
},
'c' => {
const identifier = parser.parse_raw_identifier(src);
const continue_kw = "continue";
if (byte_equal(identifier, continue_kw)) {
parser.skip_space(src);
parser.expect_character(src, ';');
if (analyzer.loops.length == 0) {
exit_with_error("continue when no loop");
}
const inner_loop = analyzer.loops.const_slice()[analyzer.loops.length - 1];
_ = emit_jump(analyzer, thread, inner_loop.continue_block);
terminated = true;
} else {
parser.i = statement_start_ch_index;
}
},
'i' => { 'i' => {
if (src[parser.i + 1] == 'f') { if (src[parser.i + 1] == 'f') {
const if_block = parser.parse_if_expression(analyzer, thread, file); const if_block = parser.parse_if_expression(analyzer, thread, file);
terminated = terminated or if_block.terminated; terminated = terminated or if_block.terminated;
} }
}, },
'r' => {
const identifier = parser.parse_raw_identifier(src);
if (byte_equal(identifier, "return")) {
parser.skip_space(src);
if (function.declaration.return_type.sema.id != .unresolved) {
const return_type = function.declaration.return_type;
const return_value = parser.parse_expression(analyzer, thread, file, return_type, .right);
parser.expect_character(src, ';');
if (analyzer.return_block) |return_block| {
const return_phi = analyzer.return_phi.?;
_ = return_phi.nodes.append(.{
.value = return_value,
.basic_block = analyzer.current_basic_block,
});
assert(analyzer.current_basic_block != return_block);
_ = emit_jump(analyzer, thread, return_block);
} else if (analyzer.exit_blocks.length > 0) {
const return_phi = thread.phis.append(.{
.instruction = .{
.id = .phi,
.value = .{
.sema = .{
.id = .instruction,
.thread = thread.get_index(),
.resolved = true,
},
},
},
.type = return_type,
});
analyzer.return_phi = return_phi;
const return_block = create_basic_block(thread);
analyzer.return_block = return_block;
_ = return_phi.nodes.append(.{
.value = return_value,
.basic_block = analyzer.current_basic_block,
});
_ = emit_jump(analyzer, thread, return_block);
} else {
build_return(thread, analyzer, return_value);
}
terminated = true;
} else {
exit(1);
}
} else {
parser.i = statement_start_ch_index;
}
},
'w' => { 'w' => {
const identifier = parser.parse_raw_identifier(src); const identifier = parser.parse_raw_identifier(src);
@ -3629,6 +3675,10 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
_ = emit_branch(analyzer, thread, condition, loop_body_block, loop_exit_block); _ = emit_branch(analyzer, thread, condition, loop_body_block, loop_exit_block);
analyzer.current_basic_block = loop_body_block; analyzer.current_basic_block = loop_body_block;
_ = analyzer.loops.append(.{
.continue_block = loop_header,
.break_block = loop_exit_block,
});
switch (src[parser.i]) { switch (src[parser.i]) {
'{' => { '{' => {
@ -3643,6 +3693,7 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
} }
analyzer.current_basic_block = loop_exit_block; analyzer.current_basic_block = loop_exit_block;
analyzer.loops.length -= 1;
} else { } else {
parser.i = statement_start_ch_index; parser.i = statement_start_ch_index;
} }
@ -3652,97 +3703,107 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
if (statement_start_ch_index == parser.i) { if (statement_start_ch_index == parser.i) {
if (is_identifier_char_start(statement_start_ch)) { if (is_identifier_char_start(statement_start_ch)) {
const identifier = parser.parse_identifier(thread, src); const raw_identifier = parser.parse_raw_identifier(src);
parser.skip_space(src); const keyword_index = parse_keyword(raw_identifier);
const is_keyword = keyword_index != ~@as(u32, 0);
if (is_keyword) {
const keyword: Keyword = @enumFromInt(keyword_index);
switch (keyword) {
else => |t| @panic(@tagName(t)),
}
} else {
const identifier = intern_identifier(&thread.identifiers, raw_identifier);
parser.skip_space(src);
const declaration_lookup = analyzer.current_scope.get_declaration(identifier) orelse unreachable; const declaration_lookup = analyzer.current_scope.get_declaration(identifier) orelse unreachable;
const operation_index = parser.i; const operation_index = parser.i;
const operation_ch = src[operation_index]; const operation_ch = src[operation_index];
var is_binary_operation = false; var is_binary_operation = false;
switch (operation_ch) { switch (operation_ch) {
'+', '-', '*', '/', '=' => { '+', '-', '*', '/', '=' => {
if (operation_ch != '=') { if (operation_ch != '=') {
if (src[parser.i + 1] == '=') { if (src[parser.i + 1] == '=') {
parser.i += 2; parser.i += 2;
is_binary_operation = true; is_binary_operation = true;
} else {
unreachable;
}
} else { } else {
unreachable; parser.i += 1;
} }
} else {
parser.i += 1;
}
parser.skip_space(src); parser.skip_space(src);
const declaration_value = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .right); const declaration_value = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .right);
const declaration_type = switch (declaration_lookup.declaration.*.id) { const declaration_type = switch (declaration_lookup.declaration.*.id) {
.argument => block: { .argument => block: {
const argument_declaration = declaration_lookup.declaration.*.get_payload(.argument); const argument_declaration = declaration_lookup.declaration.*.get_payload(.argument);
const argument_symbol = argument_declaration.to_symbol(); const argument_symbol = argument_declaration.to_symbol();
break :block argument_symbol.type; break :block argument_symbol.type;
},
.local => block: {
const local_declaration = declaration_lookup.declaration.*.get_payload(.local);
const local_symbol = local_declaration.to_symbol();
break :block local_symbol.type;
},
else => |t| @panic(@tagName(t)),
};
const right = parser.parse_expression(analyzer, thread, file, declaration_type, .right);
const source = switch (is_binary_operation) {
false => right,
true => block: {
const binary_operation_id: IntegerBinaryOperation.Id = switch (operation_ch) {
'+' => .add,
else => unreachable,
};
const binary_operation = thread.integer_binary_operations.append(.{
.id = binary_operation_id,
.instruction = .{
.id = .integer_binary_operation,
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = true,
.id = .instruction,
},
},
},
.left = declaration_value,
.right = right,
.type = declaration_type,
});
_ = analyzer.current_basic_block.instructions.append(&binary_operation.instruction);
break :block &binary_operation.instruction.value;
},
};
parser.skip_space(src);
parser.expect_character(src, ';');
const declaration_pointer = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .left);
const store = thread.stores.append(.{
.instruction = .{
.id = .store,
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = true,
.id = .instruction,
},
},
}, },
.destination = declaration_pointer, .local => block: {
.source = source, const local_declaration = declaration_lookup.declaration.*.get_payload(.local);
.alignment = declaration_type.alignment, const local_symbol = local_declaration.to_symbol();
.is_volatile = false, break :block local_symbol.type;
}); },
_ = analyzer.current_basic_block.instructions.append(&store.instruction); else => |t| @panic(@tagName(t)),
}, };
else => @panic((src.ptr + parser.i)[0..1]), const right = parser.parse_expression(analyzer, thread, file, declaration_type, .right);
const source = switch (is_binary_operation) {
false => right,
true => block: {
const binary_operation_id: IntegerBinaryOperation.Id = switch (operation_ch) {
'+' => .add,
else => unreachable,
};
const binary_operation = thread.integer_binary_operations.append(.{
.id = binary_operation_id,
.instruction = .{
.id = .integer_binary_operation,
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = true,
.id = .instruction,
},
},
},
.left = declaration_value,
.right = right,
.type = declaration_type,
});
_ = analyzer.current_basic_block.instructions.append(&binary_operation.instruction);
break :block &binary_operation.instruction.value;
},
};
parser.skip_space(src);
parser.expect_character(src, ';');
const declaration_pointer = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .left);
const store = thread.stores.append(.{
.instruction = .{
.id = .store,
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = true,
.id = .instruction,
},
},
},
.destination = declaration_pointer,
.source = source,
.alignment = declaration_type.alignment,
.is_volatile = false,
});
_ = analyzer.current_basic_block.instructions.append(&store.instruction);
},
else => @panic((src.ptr + parser.i)[0..1]),
}
} }
} else { } else {
exit_with_error("Unrecognized statement initial char"); exit_with_error("Unrecognized statement initial char");
@ -5513,5 +5574,3 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, return_
}, },
} }
} }

View File

@ -0,0 +1,12 @@
fn[cc(.c)] main[export]() s32 {
>i: s32 = 0;
>n: s32 = 5;
while (i < 10) {
i += 1;
if (i == n) {
break;
}
}
return i - n;
}

View File

@ -0,0 +1,13 @@
fn[cc(.c)] main[export]() s32 {
>i: s32 = 0;
>n: s32 = 0;
while (i < 10) {
i += 1;
if (n == 0) {
continue;
}
n += 1;
}
return n;
}