feat: execute Bro-compatible integer range loops

This commit is contained in:
2026-10-10 01:47:30 +02:00
parent d51ca57c9e
commit cf39481bfc
16 changed files with 1416 additions and 16 deletions
+84 -5
View File
@@ -18,7 +18,7 @@ Storage :: struct {
@hide:file
LoopTargets :: struct {
guard ir.BlockId
continue_target ir.BlockId
exit ir.BlockId
}
@@ -104,7 +104,7 @@ lower_type proc(type typecheck.Type) ir.Type {
.string: .string
.void: .void
.function: .function
.none, .invalid: .invalid
.none, .invalid, .range: .invalid
}
}
@@ -267,6 +267,19 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! mem.AllocError
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative)
}
.while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
.for_stmt: {
loop :: ast.for_stmt(state.tree, statement)
function := &state.module.functions.items[state.function]
slot :: u32(function.locals.items.len)
try arraylist.append(&function.locals, ir.Type.int)
state.storage[usize(statement)] = Storage{ kind = .local, index = slot }
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
if (loop.index) |index| {
state.storage[usize(index)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
try arraylist.append(&function.locals, ir.Type.int)
}
try predeclare_locals(state, loop.body)
}
else: {}
}
}
@@ -365,7 +378,7 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
span = ast.node_span(state.tree, loop.condition),
}
previous_loop :: state.loop
state.loop = LoopTargets{ guard = guard, exit = exit }
state.loop = LoopTargets{ continue_target = guard, exit = exit }
defer state.loop = previous_loop
state.block = body
try lower_block(state, loop.body, true)
@@ -378,18 +391,80 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
state.terminated = false
}
@hide:file
lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
loop :: ast.for_stmt(state.tree, id)
if (ast.expr_kind(state.tree, loop.iterable) != .range) {
_ = try lower_expression(state, loop.iterable)
if (!state.terminated) _ = trap_type(state, id)
return
}
interval :: ast.range_expr(state.tree, loop.iterable)
start :: try lower_expression(state, interval.start)
if (state.terminated) return
end :: try lower_expression(state, interval.end)
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
slot :: state.storage[usize(id)].index
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = start, slot = slot, span = interval.span })
origin :: state.block
condition :: try add_block(state)
body :: try add_block(state)
update :: try add_block(state)
increment :: try add_block(state)
exit :: try add_block(state)
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
kind = .jump, target = condition, span = loop.span,
}
state.block = condition
current :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
comparison ir.Op :: if (interval.inclusive) .less_equal else .less
condition_value :: try emit(state, ir.Instruction{ op = comparison, type = .bool, left = current, right = end, span = interval.span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .branch, value = condition_value, target = body, alternative = exit, span = interval.span,
}
previous_loop :: state.loop
state.loop = LoopTargets{ continue_target = update, exit = exit }
defer state.loop = previous_loop
state.block = body
if (loop.capture) |capture| _ = trap_binding(state, capture)
if (!state.terminated) try lower_block(state, loop.body, true)
if (!state.terminated) {
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = update, span = loop.span,
}
}
state.block = update
state.terminated = false
last :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span })
# continue reaches this guard too, so an inclusive maximum never adds one.
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
}
state.block = increment
one :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, interval.span)
next :: try emit(state, ir.Instruction{ op = .add, type = .int, left = last, right = one, span = interval.span })
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = next, slot = slot, span = interval.span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = condition, span = loop.span,
}
state.block = exit
state.terminated = false
}
@hide:file
lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
match ast.stmt_kind(tree, id) {
.if_stmt: try lower_if(state, id)
.while_stmt: try lower_while(state, id)
.for_stmt: try lower_for(state, id)
.break_stmt, .continue_stmt: {
if (trap_type(state, id)) return
if (state.loop) |targets| {
target := targets.exit
# continue restarts the whole guard, before any short-circuit branches.
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.guard
# each loop supplies its own guard or guarded-update entry.
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = target, span = ast.node_span(tree, id),
}
@@ -491,6 +566,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
invalid :: ast.invalid(tree, id)
trap(state, invalid.diagnostic, invalid.span)
}
.range: {
# ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable
}
.literal_int, .literal_float: {
if (trap_type(state, id)) return ir.no_value
return try lower_number(state, id)