ranged for loop

This commit is contained in:
David Gonzalez Martin 2023-11-29 00:11:53 -06:00
parent 96a7c656e9
commit d5910989d1
7 changed files with 361 additions and 71 deletions

View File

@ -512,9 +512,11 @@ pub const Block = struct {
};
pub const Loop = struct {
pre: Value.Index,
condition: Value.Index,
body: Value.Index,
breaks: bool,
post: Value.Index,
reaches_end: bool,
pub const List = BlockList(@This());
pub const Index = List.Index;
@ -528,6 +530,12 @@ const Unresolved = struct {
pub const Assignment = struct {
destination: Value.Index,
source: Value.Index,
operation: Operation,
const Operation = enum {
none,
add,
};
pub const List = BlockList(@This());
pub const Index = List.Index;
@ -651,10 +659,14 @@ pub const FieldAccess = struct {
pub const Allocation = List.Allocation;
};
pub const Slice = struct {
sliceable: Value.Index,
pub const Range = struct {
start: Value.Index,
end: Value.Index,
};
pub const Slice = struct {
sliceable: Value.Index,
range: Range,
type: Type.Index,
pub const Access = struct {

View File

@ -130,7 +130,25 @@ pub const TranslationUnit = struct {
.value_index = declaration.init_value,
.type_index = declaration.type,
});
try list.append(allocator, ';');
}
fn writeAssignment(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, assignment_index: Compilation.Assignment.Index, function_return_type: Compilation.Type.Index, indentation: usize) !void {
const assignment = module.assignments.get(assignment_index);
const left_type = module.values.get(assignment.source).getType(module);
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = assignment.destination,
.type_index = left_type,
});
try list.append(allocator, ' ');
switch (assignment.operation) {
.none => {},
.add => try list.append(allocator, '+'),
}
try list.appendSlice(allocator, "= ");
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = assignment.source,
.type_index = Compilation.Type.Index.invalid,
});
}
fn writeBlock(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, block_index: Compilation.Block.Index, function_return_type: Compilation.Type.Index, old_indentation: usize) !void {
@ -146,19 +164,10 @@ pub const TranslationUnit = struct {
switch (statement.*) {
.declaration => |declaration_index| {
try unit.writeDeclaration(module, list, allocator, declaration_index, indentation);
try list.append(allocator, ';');
},
.assign => |assignment_index| {
const assignment = module.assignments.get(assignment_index);
const left_type = module.values.get(assignment.source).getType(module);
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = assignment.destination,
.type_index = left_type,
});
try list.appendSlice(allocator, " = ");
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = assignment.source,
.type_index = Compilation.Type.Index.invalid,
});
try unit.writeAssignment(module, list, allocator, assignment_index, function_return_type, indentation);
try list.append(allocator, ';');
},
.@"return" => |return_index| {
@ -245,6 +254,38 @@ pub const TranslationUnit = struct {
try unit.writeAssembly(module, list, allocator, assembly_block_index, indentation);
try list.append(allocator, ';');
},
.loop => |loop_index| {
const loop = module.loops.get(loop_index);
try list.appendSlice(allocator, "for (");
if (!loop.pre.invalid) {
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = loop.pre,
.type_index = Compilation.Type.Index.invalid,
});
}
try list.appendSlice(allocator, "; ");
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = loop.condition,
.type_index = Compilation.Type.boolean,
});
try list.appendSlice(allocator, "; ");
if (!loop.post.invalid) {
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = loop.post,
.type_index = Compilation.Type.Index.invalid,
});
}
try list.appendSlice(allocator, ") ");
try unit.writeValue(module, list, allocator, function_return_type, indentation, .{
.value_index = loop.body,
.type_index = Compilation.Type.Index.invalid,
});
},
else => |t| @panic(@tagName(t)),
}
@ -326,6 +367,7 @@ pub const TranslationUnit = struct {
=> {},
.@"struct" => {
try unit.writeDeclaration(module, &unit.global_variable_declarations, allocator, declaration_index, 0);
try unit.global_variable_declarations.append(allocator, ';');
try unit.global_variable_declarations.appendNTimes(allocator, '\n', 2);
},
else => |t| @panic(@tagName(t)),
@ -798,6 +840,12 @@ pub const TranslationUnit = struct {
_ = type_index;
const value = module.values.get(value_index);
switch (value.*) {
.declaration => |declaration_index| {
try unit.writeDeclaration(module, list, allocator, declaration_index, indentation);
},
.assign => |assignment_index| {
try unit.writeAssignment(module, list, allocator, assignment_index, function_return_type, indentation);
},
.integer => |integer| {
try list.writer(allocator).print("{}", .{integer.value});
},
@ -996,7 +1044,7 @@ pub const TranslationUnit = struct {
});
try list.appendSlice(allocator, ") + (");
try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{
.value_index = slice.start,
.value_index = slice.range.start,
.type_index = Compilation.Type.Index.invalid,
});
try list.appendSlice(allocator, "),\n");
@ -1009,16 +1057,16 @@ pub const TranslationUnit = struct {
switch (sliceable_type.*) {
.pointer => {
switch (slice.end.invalid) {
switch (slice.range.end.invalid) {
false => {
try list.append(allocator, '(');
try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{
.value_index = slice.end,
.value_index = slice.range.end,
.type_index = Compilation.Type.Index.invalid,
});
try list.appendSlice(allocator, ") - (");
try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{
.value_index = slice.start,
.value_index = slice.range.start,
.type_index = Compilation.Type.Index.invalid,
});
try list.appendSlice(allocator, ")\n");

View File

@ -50,6 +50,7 @@ pub const Token = packed struct(u64) {
fixed_keyword_align = 0x1b,
fixed_keyword_export = 0x1c,
fixed_keyword_cc = 0x1d,
fixed_keyword_for = 0x1e,
keyword_unsigned_integer = 0x1f,
keyword_signed_integer = 0x20,
bang = '!', // 0x21
@ -114,6 +115,7 @@ pub const FixedKeyword = enum {
@"align",
@"export",
cc,
@"for",
};
pub const Result = struct {

View File

@ -18,6 +18,7 @@ const Field = Compilation.Field;
const Function = Compilation.Function;
const Intrinsic = Compilation.Intrinsic;
const Loop = Compilation.Loop;
const Range = Compilation.Range;
const Scope = Compilation.Scope;
const ScopeType = Compilation.ScopeType;
const Slice = Compilation.Slice;
@ -106,7 +107,9 @@ const Analyzer = struct {
current_file: File.Index,
current_declaration: Declaration.Index = Declaration.Index.invalid,
payloads: ArrayList(Payload) = .{},
current_block: Block.Index = Block.Index.invalid,
maybe_count: usize = 0,
for_count: usize = 0,
fn getScopeSourceFile(analyzer: *Analyzer, scope_index: Scope.Index) []const u8 {
const scope = analyzer.module.scopes.get(scope_index);
@ -188,14 +191,20 @@ const Analyzer = struct {
});
const scope_index = new_scope.index;
var statements = ArrayList(Value.Index){};
const block_allocation = try analyzer.module.blocks.append(analyzer.allocator, .{
.statements = ArrayList(Value.Index){},
.reaches_end = true,
});
const block_index = block_allocation.index;
const previous_block = analyzer.current_block;
analyzer.current_block = block_index;
for (analyzer.payloads.items) |payload| {
const declaration_index = try analyzer.declarationCommon(scope_index, .local, payload.mutability, payload.name, payload.type, payload.value, null);
const statement_value_allocation = try analyzer.module.values.append(analyzer.allocator, .{
.declaration = declaration_index,
});
try statements.append(analyzer.allocator, statement_value_allocation.index);
try analyzer.module.blocks.get(block_index).statements.append(analyzer.allocator, statement_value_allocation.index);
}
analyzer.payloads.clearRetainingCapacity();
@ -215,25 +224,9 @@ const Analyzer = struct {
const statement_node = analyzer.getScopeNode(scope_index, statement_node_index);
logln(.sema, .node, "Trying to resolve statement of id {s}", .{@tagName(statement_node.id)});
const statement_value_index = switch (statement_node.id) {
.assign => (try analyzer.module.values.append(analyzer.allocator, try analyzer.processAssignment(scope_index, statement_node_index))).index,
.simple_while => blk: {
const loop_allocation = try analyzer.module.loops.append(analyzer.allocator, .{
.condition = Value.Index.invalid,
.body = Value.Index.invalid,
.breaks = false,
});
loop_allocation.ptr.condition = (try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, statement_node.left)).index;
loop_allocation.ptr.body = (try analyzer.unresolvedAllocate(scope_index, ExpectType.none, statement_node.right)).index;
// TODO: bool true
reaches_end = loop_allocation.ptr.breaks or unreachable;
const value_allocation = try analyzer.module.values.append(analyzer.allocator, .{
.loop = loop_allocation.index,
});
break :blk value_allocation.index;
},
.assign, .add_assign => (try analyzer.module.values.append(analyzer.allocator, try analyzer.processAssignment(scope_index, statement_node_index))).index,
.@"unreachable" => blk: {
reaches_end = false;
break :blk unreachable_index;
@ -300,7 +293,7 @@ const Analyzer = struct {
const if_else_value = try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index);
if (if_else_value.maybe_payload_declaration_index) |maybe_payload_declaration| {
try statements.append(analyzer.allocator, maybe_payload_declaration);
try analyzer.module.blocks.get(block_index).statements.append(analyzer.allocator, maybe_payload_declaration);
}
const branch = analyzer.module.branches.get(if_else_value.branch);
@ -328,7 +321,7 @@ const Analyzer = struct {
const if_expression = try analyzer.processIf(scope_index, expect_type, if_statement_node_index, payload_node_index);
if (if_expression.maybe_payload_declaration_value) |maybe_payload_declaration| {
try statements.append(analyzer.allocator, maybe_payload_declaration);
try analyzer.module.blocks.get(block_index).statements.append(analyzer.allocator, maybe_payload_declaration);
}
const branch = try analyzer.module.branches.append(analyzer.allocator, .{
@ -352,18 +345,24 @@ const Analyzer = struct {
const value = try analyzer.module.values.append(analyzer.allocator, assembly_value);
break :blk value.index;
},
.for_loop => blk: {
const loop_index = try analyzer.forLoop(scope_index, expect_type, statement_node_index);
const value = try analyzer.module.values.append(analyzer.allocator, .{
.loop = loop_index,
});
break :blk value.index;
},
else => |t| @panic(@tagName(t)),
};
try statements.append(analyzer.allocator, statement_value_index);
try analyzer.module.blocks.get(block_index).statements.append(analyzer.allocator, statement_value_index);
}
const block_allocation = try analyzer.module.blocks.append(analyzer.allocator, .{
.statements = statements,
.reaches_end = reaches_end,
});
analyzer.module.blocks.get(block_index).reaches_end = reaches_end;
return block_allocation.index;
analyzer.current_block = previous_block;
return block_index;
}
fn processAssemblyStatements(analyzer: *Analyzer, comptime architecture: type, scope_index: Scope.Index, assembly_statement_nodes: []const Node.Index) ![]Compilation.Assembly.Instruction.Index {
@ -796,6 +795,126 @@ const Analyzer = struct {
unreachable;
}
fn range(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Range {
const range_node = analyzer.getScopeNode(scope_index, node_index);
assert(range_node.id == .range);
const expect_type = ExpectType{
.type_index = Type.usize,
};
const range_start = try analyzer.unresolvedAllocate(scope_index, expect_type, range_node.left);
const range_end = try analyzer.unresolvedAllocate(scope_index, expect_type, range_node.right);
return Range{
.start = range_start.index,
.end = range_end.index,
};
}
fn forLoop(analyzer: *Analyzer, parent_scope_index: Scope.Index, expect_type: ExpectType, for_node_index: Node.Index) !Loop.Index {
const for_loop_node = analyzer.getScopeNode(parent_scope_index, for_node_index);
assert(for_loop_node.id == .for_loop);
const scope_allocation = try analyzer.allocateScope(.{
.token = for_loop_node.token,
.file = analyzer.module.scopes.get(parent_scope_index).file,
.parent = parent_scope_index,
});
const scope_index = scope_allocation.index;
const for_condition_node = analyzer.getScopeNode(scope_index, for_loop_node.left);
assert(for_condition_node.id == .for_condition);
const for_loop_element_node = analyzer.getScopeNode(scope_index, for_condition_node.left);
var pre = Value.Index.invalid;
const for_condition = switch (for_loop_element_node.id) {
.range => blk: {
const for_range = try analyzer.range(scope_index, for_condition_node.left);
const for_loop_payload_node = analyzer.getScopeNode(scope_index, for_condition_node.right);
const payload_name = switch (for_loop_payload_node.id) {
.node_list => b: {
const nodes = analyzer.getScopeNodeList(scope_index, for_loop_payload_node);
assert(nodes.items.len == 1);
const payload_node = analyzer.getScopeNode(scope_index, nodes.items[0]);
const result = analyzer.tokenIdentifier(scope_index, payload_node.token);
break :b result;
},
else => |t| @panic(@tagName(t)),
};
const declaration_index = try analyzer.declarationCommon(scope_index, .local, .@"var", payload_name, Type.usize, for_range.start, null);
const declaration_value = try analyzer.module.values.append(analyzer.allocator, .{
.declaration = declaration_index,
});
pre = declaration_value.index;
const binary_condition = try analyzer.module.binary_operations.append(analyzer.allocator, .{
.id = .compare_less_than,
.type = Type.boolean,
.left = try analyzer.doIdentifierString(scope_index, ExpectType{
.type_index = Type.usize,
}, payload_name),
.right = for_range.end,
});
const condition = try analyzer.module.values.append(analyzer.allocator, .{
.binary_operation = binary_condition.index,
});
break :blk condition.index;
},
else => |t| @panic(@tagName(t)),
};
const for_loop_body = try analyzer.unresolvedAllocate(scope_index, expect_type, for_loop_node.right);
var post = Value.Index.invalid;
switch (for_loop_element_node.id) {
.range => {
const for_condition_value = analyzer.module.values.get(for_condition);
switch (for_condition_value.*) {
.binary_operation => |binary_operation_index| {
const binary_operation = analyzer.module.binary_operations.get(binary_operation_index);
const left = binary_operation.left;
const right = try analyzer.module.values.append(analyzer.allocator, .{
.integer = .{
.value = 1,
.type = Type.usize,
.signedness = .unsigned,
},
});
const assignment = try analyzer.module.assignments.append(analyzer.allocator, .{
.operation = .add,
.destination = left,
.source = right.index,
});
const assignment_value = try analyzer.module.values.append(analyzer.allocator, .{
.assign = assignment.index,
});
post = assignment_value.index;
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
}
const reaches_end = switch (for_loop_body.ptr.*) {
.block => |block_index| analyzer.module.blocks.get(block_index).reaches_end,
else => |t| @panic(@tagName(t)),
};
const loop = try analyzer.module.loops.append(analyzer.allocator, .{
.pre = pre,
.condition = for_condition,
.body = for_loop_body.index,
.reaches_end = reaches_end,
.post = post,
});
return loop.index;
}
const If = struct {
maybe_payload_declaration_value: ?Value.Index,
expression: Value.Index,
@ -935,11 +1054,11 @@ const Analyzer = struct {
fn processAssignment(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value {
const node = analyzer.getScopeNode(scope_index, node_index);
assert(node.id == .assign);
assert(!node.left.invalid);
const left_node = analyzer.getScopeNode(scope_index, node.left);
switch (left_node.id) {
.discard => {
assert(node.id == .assign);
var result = Value{
.unresolved = .{
.node_index = node.right,
@ -964,6 +1083,11 @@ const Analyzer = struct {
const assignment = try analyzer.module.assignments.append(analyzer.allocator, .{
.destination = left.index,
.source = right.index,
.operation = switch (node.id) {
.assign => .none,
.add_assign => .add,
else => unreachable,
},
});
return Value{
@ -1734,15 +1858,10 @@ const Analyzer = struct {
.pointer => |pointer| pointer.element_type,
else => |t| @panic(@tagName(t)),
};
const slice_range_node = analyzer.getScopeNode(scope_index, node.right);
assert(slice_range_node.id == .slice_range);
const slice_range_start = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, slice_range_node.left);
const slice_range_end = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, slice_range_node.right);
const slice = try analyzer.module.slices.append(analyzer.allocator, .{
.sliceable = expression_to_slice.index,
.start = slice_range_start.index,
.end = slice_range_end.index,
.range = try analyzer.range(scope_index, node.right),
.type = try analyzer.getSliceType(.{
.element_type = element_type,
.@"const" = true,

View File

@ -165,7 +165,7 @@ pub const Node = struct {
if_payload,
discard,
slice,
slice_range,
range,
negation,
anonymous_container_literal,
const_single_pointer_type,
@ -175,6 +175,9 @@ pub const Node = struct {
assembly_register,
assembly_statement,
assembly_block,
for_condition,
for_loop,
add_assign,
};
};
@ -429,7 +432,7 @@ const Analyzer = struct {
return result;
}
fn block(analyzer: *Analyzer, options: Options) !Node.Index {
fn block(analyzer: *Analyzer, options: Options) anyerror!Node.Index {
const left_brace = try analyzer.expectToken(.left_brace);
var list = ArrayList(Node.Index){};
@ -448,6 +451,7 @@ const Analyzer = struct {
.fixed_keyword_while => try analyzer.whileExpression(options),
.fixed_keyword_switch => try analyzer.switchExpression(),
.fixed_keyword_if => try analyzer.ifExpression(),
.fixed_keyword_for => try analyzer.forExpression(),
.fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(),
.hash => blk: {
const intrinsic = try analyzer.compilerIntrinsic();
@ -620,23 +624,117 @@ const Analyzer = struct {
}
}
fn assignExpression(analyzer: *Analyzer) !Node.Index {
const expr = try analyzer.expression();
const expression_id: Node.Id = switch (analyzer.tokens[analyzer.token_i].id) {
.semicolon, .comma => return expr,
.equal => .assign,
fn forExpression(analyzer: *Analyzer) !Node.Index {
const token = try analyzer.expectToken(.fixed_keyword_for);
_ = try analyzer.expectToken(.left_parenthesis);
const expression_token = analyzer.token_i;
const first = try analyzer.expression();
const ForExpression = struct {
node_index: Node.Index,
expected_payload_count: usize,
};
const for_expression = switch (analyzer.tokens[analyzer.token_i].id) {
.period => switch (analyzer.tokens[analyzer.token_i + 1].id) {
.period => blk: {
analyzer.token_i += 2;
const second = try analyzer.expression();
break :blk ForExpression{
.node_index = try analyzer.addNode(.{
.id = .range,
.token = expression_token,
.left = first,
.right = second,
}),
.expected_payload_count = 1,
};
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
};
_ = try analyzer.expectToken(.right_parenthesis);
_ = try analyzer.expectToken(.vertical_bar);
var payload_nodes = ArrayList(Node.Index){};
while (analyzer.tokens[analyzer.token_i].id != .vertical_bar) {
const payload_identifier = try analyzer.expectToken(.identifier);
switch (analyzer.tokens[analyzer.token_i].id) {
.vertical_bar => {},
.comma => analyzer.token_i += 1,
else => |t| @panic(@tagName(t)),
}
try payload_nodes.append(analyzer.allocator, try analyzer.addNode(.{
.id = .identifier,
.token = payload_identifier,
.left = Node.Index.invalid,
.right = Node.Index.invalid,
}));
}
_ = try analyzer.expectToken(.vertical_bar);
if (payload_nodes.items.len != for_expression.expected_payload_count) {
unreachable;
}
const for_condition_node = try analyzer.addNode(.{
.id = .for_condition,
.token = token,
.left = for_expression.node_index,
.right = try analyzer.nodeList(payload_nodes),
});
const for_content_node = switch (analyzer.tokens[analyzer.token_i].id) {
.left_brace => try analyzer.block(.{
.is_comptime = false,
}),
else => blk: {
const for_content_expression = try analyzer.expression();
_ = try analyzer.expectToken(.semicolon);
break :blk for_content_expression;
},
};
const for_node = try analyzer.addNode(.{
.id = .for_loop,
.token = token,
.left = for_condition_node,
.right = for_content_node,
});
return for_node;
}
fn assignExpression(analyzer: *Analyzer) !Node.Index {
const left = try analyzer.expression();
const expression_token = analyzer.token_i;
const expression_id: Node.Id = switch (analyzer.tokens[expression_token].id) {
.semicolon, .comma => return left,
.equal => .assign,
.plus => switch (analyzer.tokens[analyzer.token_i + 1].id) {
.equal => blk: {
analyzer.token_i += 1;
break :blk .add_assign;
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
};
analyzer.token_i += 1;
const right = try analyzer.expression();
const node = Node{
.id = expression_id,
.token = blk: {
const token_i = analyzer.token_i;
analyzer.token_i += 1;
break :blk token_i;
},
.left = expr,
.right = try analyzer.expression(),
.token = expression_token,
.left = left,
.right = right,
};
logln(.parser, .assign, "assign:\nleft: {}.\nright: {}", .{ node.left, node.right });
@ -837,6 +935,7 @@ const Analyzer = struct {
.comma,
.fixed_keyword_const,
.fixed_keyword_var,
.fixed_keyword_return,
.identifier,
=> break,
else => blk: {
@ -854,7 +953,7 @@ const Analyzer = struct {
},
.plus => switch (next_token_id) {
.plus => unreachable,
.equal => unreachable,
.equal => break,
else => .add,
},
.minus => switch (next_token_id) {
@ -1470,7 +1569,7 @@ const Analyzer = struct {
.token = token,
.left = left,
.right = try analyzer.addNode(.{
.id = .slice_range,
.id = .range,
.token = token,
.left = index_expression,
.right = range_end_expression,

View File

@ -5,7 +5,7 @@ pub fn build(b: *std.Build) !void {
all = b.option(bool, "all", "All") orelse false;
const target = b.standardTargetOptions(.{});
const optimization = b.standardOptimizeOption(.{});
const use_llvm = b.option(bool, "use_llvm", "Use LLVM as the backend for generate the compiler binary") orelse false;
const use_llvm = b.option(bool, "use_llvm", "Use LLVM as the backend for generate the compiler binary") orelse true;
const exe = b.addExecutable(.{
.name = "nativity",
.root_source_file = .{ .path = "bootstrap/main.zig" },

10
test/foreach/main.nat Normal file
View File

@ -0,0 +1,10 @@
const main = fn () s32 {
var counter: s32 = 0;
const loop = 10;
for (0..loop) |_| {
counter += 1;
}
return loop - counter;
}