Merge pull request #6259 from odin-lang/bill/range-init

`for init; x in y {}` style loops (proof of concept)
This commit is contained in:
gingerBill
2026-02-17 11:11:56 +00:00
committed by GitHub
11 changed files with 297 additions and 35 deletions

View File

@@ -0,0 +1,154 @@
package container_xar
@(require) import "base:runtime"
Freelist_Array :: struct($T: typeid, $SHIFT: uint) where
0 < SHIFT,
SHIFT <= MAX_SHIFT,
size_of(T) >= size_of(^T) {
array: Array(T, SHIFT),
freelist: ^T,
}
freelist_init :: proc(x: ^$X/Freelist_Array($T, $SHIFT), allocator := context.allocator) {
init(&x.array, allocator)
x.freelist = nil
}
freelist_destroy :: proc(x: ^$X/Freelist_Array($T, $SHIFT)) {
destroy(&x.array)
x.freelist = nil
}
freelist_clear :: proc(x: ^$X/Freelist_Array($T, $SHIFT)) {
clear(&x.array)
x.freelist = nil
}
@(require_results)
freelist_push_with_index :: proc(x: ^$X/Freelist_Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, index: int, err: runtime.Allocator_Error) {
if x.freelist != nil {
slot := x.freelist
idx := freelist_index_of(x, slot)
x.freelist = (^^T)(slot)^
slot^ = value
return slot, idx, nil
}
idx := x.array.len
ptr = array_push_back_elem_and_get_ptr(&x.array, value, loc) or_return
return ptr, idx, nil
}
@(require_results)
freelist_push :: proc(x: ^$X/Freelist_Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: runtime.Allocator_Error) {
ptr, _, err = freelist_push_with_index(x, value, loc)
return
}
freelist_pop :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> T {
item := array_get_ptr(&x.array, index, loc)
result := item^
(^^T)(item)^ = x.freelist
x.freelist = item
return result
}
freelist_release :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int, loc := #caller_location) {
item := array_get_ptr(&x.array, index, loc)
(^^T)(item)^ = x.freelist
x.freelist = item
}
@(require_results)
freelist_linear_search :: proc(x: ^$X/Freelist_Array($T, $SHIFT), ptr: ^T) -> (index: int, found: bool) {
base := 0
for chunk, c in x.array.chunks {
if chunk == nil {
break
}
chunk_cap := 1 << (SHIFT + uint(c if c > 0 else 1) - 1)
ptr_addr := uintptr(ptr)
chunk_start_addr := uintptr(chunk)
chunk_end_addr := chunk_start_addr + uintptr(chunk_cap * size_of(T))
if chunk_start_addr <= ptr_addr && ptr_addr < chunk_end_addr {
offset := int(ptr_addr - chunk_start_addr) / size_of(T)
return base + offset, true
}
base += chunk_cap
}
return -1, false
}
@(require_results)
freelist_get :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> T {
return array_get(&x.array, index, loc)
}
@(require_results)
freelist_get_ptr :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> ^T {
return array_get_ptr(&x.array, index, loc)
}
freelist_set :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) {
array_set(&x.array, index, value, loc)
}
@(require_results)
freelist_len :: proc(x: $X/Freelist_Array($T, $SHIFT)) -> int {
return x.array.len
}
@(require_results)
freelist_cap :: proc(x: $X/Freelist_Array($T, $SHIFT)) -> int {
return array_cap(x.array)
}
@(require_results)
freelist_is_freed :: proc(x: ^$X/Freelist_Array($T, $SHIFT), #any_int index: int) -> bool {
ptr := array_get_ptr(&x.array, index)
current := x.freelist
for current != nil {
if current == ptr {
return true
}
current = (^^T)(current)^
}
return false
}
Freelist_Iterator :: struct($T: typeid, $SHIFT: uint) {
freelist_array: ^Freelist_Array(T, SHIFT),
idx: int,
}
freelist_iterator :: proc(x: ^$X/Freelist_Array($T, $SHIFT)) -> Freelist_Iterator(T, SHIFT) {
return {freelist_array = x, idx = 0}
}
@(require_results)
freelist_iterate_by_val :: proc(it: ^Freelist_Iterator($T, $SHIFT)) -> (val: T, idx: int, ok: bool) {
for it.idx < it.freelist_array.array.len {
if !freelist_is_freed(it.freelist_array, it.idx) {
val = array_get(&it.freelist_array.array, it.idx)
idx = it.idx
it.idx += 1
return val, idx, true
}
it.idx += 1
}
return
}
@(require_results)
freelist_iterate_by_ptr :: proc(it: ^Freelist_Iterator($T, $SHIFT)) -> (val: ^T, idx: int, ok: bool) {
for it.idx < it.freelist_array.array.len {
if !freelist_is_freed(it.freelist_array, it.idx) {
val = array_get_ptr(&it.freelist_array.array, it.idx)
idx = it.idx
it.idx += 1
return val, idx, true
}
it.idx += 1
}
return
}

View File

@@ -83,7 +83,7 @@ Initializes an exponential array with the given allocator.
- `x`: Pointer to the exponential array to initialize
- `allocator`: Allocator to use for chunk allocations (defaults to context.allocator)
*/
init :: proc(x: ^$X/Array($T, $SHIFT), allocator := context.allocator) {
array_init :: proc(x: ^$X/Array($T, $SHIFT), allocator := context.allocator) {
x^ = {allocator = allocator}
}
@@ -93,7 +93,7 @@ Frees all allocated chunks and resets the exponential array.
**Inputs**
- `x`: Pointer to the exponential array to destroy
*/
destroy :: proc(x: ^$X/Array($T, $SHIFT)) {
array_destroy :: proc(x: ^$X/Array($T, $SHIFT)) {
#reverse for c, i in x.chunks {
if c != nil {
n := 1 << (SHIFT + uint(i if i > 0 else 1) - 1)
@@ -108,19 +108,19 @@ destroy :: proc(x: ^$X/Array($T, $SHIFT)) {
Resets the array's length to zero without freeing memory.
Allocated chunks are retained for reuse.
*/
clear :: proc "contextless" (x: ^$X/Array($T, $SHIFT)) {
array_clear :: proc "contextless" (x: ^$X/Array($T, $SHIFT)) {
x.len = 0
}
// Returns the length of the exponential-array
@(require_results)
len :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int {
array_len :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int {
return x.len
}
// Returns the number of allocated elements
@(require_results)
cap :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int {
array_cap :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int {
#reverse for c, i in x.chunks {
if c != nil {
return 1 << (SHIFT + uint(i if i > 0 else 1))
@@ -160,7 +160,7 @@ Get a copy of the element at the specified index.
- a copy of the element
*/
@(require_results)
get :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: T) #no_bounds_check {
array_get :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: T) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, x.len)
chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index))
return x.chunks[chunk_idx][elem_idx]
@@ -199,7 +199,7 @@ Example:
}
*/
@(require_results)
get_ptr :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: ^T) #no_bounds_check {
array_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: ^T) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, x.len)
chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index))
return &x.chunks[chunk_idx][elem_idx]
@@ -207,7 +207,7 @@ get_ptr :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_lo
// No bounds checking
@(require_results)
get_ptr_unsafe :: proc "contextless" (x: ^$X/Array($T, $SHIFT), #any_int index: int) -> (val: ^T) #no_bounds_check {
array_get_ptr_unsafe :: proc "contextless" (x: ^$X/Array($T, $SHIFT), #any_int index: int) -> (val: ^T) #no_bounds_check {
chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index))
return &x.chunks[chunk_idx][elem_idx]
}
@@ -220,14 +220,15 @@ Set the element at the specified index to the given value.
- `index`: Position of the element (0-indexed)
- `value`: The value to set
*/
set :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) #no_bounds_check {
array_set :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, x.len)
chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index))
x.chunks[chunk_idx][elem_idx] = value
}
append :: proc{push_back_elem, push_back_elems}
push_back :: proc{push_back_elem, push_back_elems}
array_append :: proc{array_push_back_elem, array_push_back_elems}
array_push_back :: proc{array_push_back_elem, array_push_back_elems}
/*
Append an element to the end of the exponential array.
@@ -256,7 +257,7 @@ Example:
fmt.println(xar.get(&x, 1)) // world
}
*/
push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) {
array_push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) {
if x.allocator.procedure == nil {
// to minic `[dynamic]T` behaviour
x.allocator = context.allocator
@@ -283,14 +284,16 @@ Append multiple elements to the end of the exponential array.
- number of elements successfully added
- allocation error if chunk allocation failed (partial append possible)
*/
push_back_elems :: proc(x: ^$X/Array($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) {
array_push_back_elems :: proc(x: ^$X/Array($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) {
for value in values {
n += push_back_elem(x, value, loc) or_return
n += array_push_back_elem(x, value, loc) or_return
}
return
}
append_and_get_ptr :: push_back_elem_and_get_ptr
array_append_and_get_ptr :: array_push_back_elem_and_get_ptr
append_and_get_ptr :: array_push_back_elem_and_get_ptr
/*
Append an element and return a stable pointer to it.
This is useful when you need to initialize a complex struct in-place or
@@ -317,7 +320,7 @@ Example:
}
*/
@(require_results)
push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: runtime.Allocator_Error) {
array_push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: runtime.Allocator_Error) {
if x.allocator.procedure == nil {
// to minic `[dynamic]T` behaviour
x.allocator = context.allocator
@@ -336,7 +339,7 @@ push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #c
// `pop` will remove and return the end value of an exponential array `x` and reduces the length of the array by 1.
//
// Note: If the exponential array has no elements (`xar.len(x) == 0`), this procedure will panic.
pop :: proc(x: ^$X/Array($T, $SHIFT), loc := #caller_location) -> (val: T) {
array_pop :: proc(x: ^$X/Array($T, $SHIFT), loc := #caller_location) -> (val: T) {
assert(x.len > 0, loc=loc)
index := uint(x.len-1)
chunk_idx, elem_idx, _ := _meta_get(SHIFT, index)
@@ -347,7 +350,7 @@ pop :: proc(x: ^$X/Array($T, $SHIFT), loc := #caller_location) -> (val: T) {
// `pop_safe` trys to remove and return the end value of dynamic array `x` and reduces the length of the array by 1.
// If the operation is not possible, it will return false.
@(require_results)
pop_safe :: proc(x: ^$X/Array($T, $SHIFT)) -> (val: T, ok: bool) {
array_pop_safe :: proc(x: ^$X/Array($T, $SHIFT)) -> (val: T, ok: bool) {
if x.len == 0 {
return
}
@@ -389,16 +392,27 @@ pop_safe :: proc(x: ^$X/Array($T, $SHIFT)) -> (val: T, ok: bool) {
fmt.println(xar.get(&x, 1)) // 20
}
*/
unordered_remove :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) {
array_unordered_remove :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_location) {
runtime.bounds_check_error_loc(loc, index, x.len)
n := x.len-1
if index != n {
end := get(x, n)
set(x, index, end)
end := array_get(x, n)
array_set(x, index, end)
}
x.len -= 1
}
@(require_results)
array_linear_search :: proc(x: ^$X/Array($T, $SHIFT), elem: T) -> (index: int, found: bool) where intrinsics.type_is_comparable(T) {
it := array_iterator(x)
for val, i in array_iterate_by_val(it) {
if val == elem {
return i, true
}
}
return -1, flase
}
/*
Iterator state for traversing a `Xar`.
@@ -407,7 +421,7 @@ Fields:
- `xar`: Pointer to the exponential array being iterated
- `idx`: Current iteration index
*/
Iterator :: struct($T: typeid, $SHIFT: uint) {
Array_Iterator :: struct($T: typeid, $SHIFT: uint) {
xar: ^Array(T, SHIFT),
idx: int,
}
@@ -446,7 +460,7 @@ Output:
20
30
*/
iterator :: proc(xar: ^$X/Array($T, $SHIFT)) -> Iterator(T, SHIFT) {
array_iterator :: proc(xar: ^$X/Array($T, $SHIFT)) -> Array_Iterator(T, SHIFT) {
return {xar = auto_cast xar, idx = 0}
}
@@ -460,11 +474,12 @@ Advance the iterator and returns the next element.
- current element
- `true` if an element was returned, `false` if iteration is complete
*/
iterate_by_val :: proc(it: ^Iterator($T, $SHIFT)) -> (val: T, ok: bool) {
array_iterate_by_val :: proc(it: ^Array_Iterator($T, $SHIFT)) -> (val: T, idx: int, ok: bool) {
if it.idx >= it.xar.len {
return
}
val = get(it.xar, it.idx)
val = array_get(it.xar, it.idx)
idx = it.idx
it.idx += 1
return val, true
}
@@ -480,11 +495,42 @@ Advance the iterator and returns a pointer to the next element.
- pointer to the current element
- `true` if an element was returned, `false` if iteration is complete
*/
iterate_by_ptr :: proc(it: ^Iterator($T, $SHIFT)) -> (val: ^T, ok: bool) {
array_iterate_by_ptr :: proc(it: ^Array_Iterator($T, $SHIFT)) -> (val: ^T, idx: int, ok: bool) {
if it.idx >= it.xar.len {
return
}
val = get_ptr(it.xar, it.idx)
val = array_get_ptr(it.xar, it.idx)
idx = it.idx
it.idx += 1
return val, true
}
init :: proc{array_init, freelist_init}
destroy :: proc{array_destroy, freelist_destroy}
clear :: proc{array_clear, freelist_clear}
len :: proc{array_len, freelist_len}
cap :: proc{array_cap, freelist_cap}
get :: proc{array_get, freelist_get}
get_ptr_unsafe :: proc{array_get_ptr_unsafe}
get_ptr :: proc{array_get_ptr, freelist_get_ptr}
set :: proc{array_set, freelist_set}
append :: proc{array_push_back_elem, array_push_back_elems}
push_back :: proc{array_push_back_elem, array_push_back_elems}
push_back_elem :: proc{array_push_back_elem}
push_back_elems :: proc{array_push_back_elems}
push_back_elem_and_get_ptr:: proc{array_push_back_elem_and_get_ptr}
pop :: proc{array_pop, freelist_pop}
pop_safe :: proc{array_pop_safe}
unordered_remove :: proc{array_unordered_remove}
iterator :: proc{array_iterator, freelist_iterator}
iterate_by_val :: proc{array_iterate_by_val, freelist_iterate_by_val}
iterate_by_ptr :: proc{array_iterate_by_ptr, freelist_iterate_by_ptr}
push_with_index :: proc{freelist_push_with_index}
push :: proc{freelist_push}
release :: proc{freelist_release}
linear_search :: proc{array_linear_search, freelist_linear_search}
is_freed :: proc{freelist_is_freed}

View File

@@ -376,6 +376,7 @@ matrix_cast :: proc "contextless" (v: $A/matrix[$M, $N]$T, $Elem_Type: typeid) -
return
}
@(require_results) to_f16 :: #force_inline proc(v: $A/[$N]$T) -> [N]f16 { return array_cast(v, f16) }
@(require_results) to_f32 :: #force_inline proc(v: $A/[$N]$T) -> [N]f32 { return array_cast(v, f32) }
@(require_results) to_f64 :: #force_inline proc(v: $A/[$N]$T) -> [N]f64 { return array_cast(v, f64) }

View File

@@ -432,6 +432,7 @@ For_Stmt :: struct {
Range_Stmt :: struct {
using node: Stmt,
label: ^Expr, // possibly nil
init: ^Stmt,
for_pos: tokenizer.Pos,
vals: []^Expr,
in_pos: tokenizer.Pos,

View File

@@ -239,6 +239,7 @@ clone_node :: proc(node: ^Node) -> ^Node {
r.body = clone(r.body)
case ^Range_Stmt:
r.label = clone(r.label)
r.init = clone(r.init)
r.vals = clone(r.vals)
r.expr = clone(r.expr)
r.body = clone(r.body)

View File

@@ -222,6 +222,9 @@ walk :: proc(v: ^Visitor, node: ^Node) {
if n.label != nil {
walk(v, n.label)
}
if n.init != nil {
walk(v, n.init)
}
for val in n.vals {
if val != nil {
walk(v, val)

View File

@@ -881,7 +881,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
body: ^ast.Stmt
is_range := false
if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
general_conds: if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
prev_level := p.expr_level
defer p.expr_level = prev_level
p.expr_level = -1
@@ -929,6 +929,17 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
error(p, p.curr_tok.pos, "Expected ';', followed by a condition expression and post statement, got %s", tokenizer.tokens[p.curr_tok.kind])
} else {
if p.curr_tok.kind != .Semicolon {
if p.curr_tok.kind == .Ident {
next_token := peek_token(p)
if next_token.kind == .In || next_token.kind == .Comma {
cond = parse_simple_stmt(p, {.In})
as := cond.derived_stmt.(^ast.Assign_Stmt)
assert(as.op.kind == .In)
is_range = true
break general_conds
}
}
cond = parse_simple_stmt(p, nil)
}
@@ -967,6 +978,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
range_stmt := ast.new(ast.Range_Stmt, tok.pos, body)
range_stmt.for_pos = tok.pos
range_stmt.init = init
range_stmt.vals = vals
range_stmt.in_pos = assign_stmt.op.pos
range_stmt.expr = rhs

View File

@@ -1705,11 +1705,17 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
TEMPORARY_ALLOCATOR_GUARD();
u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
check_open_scope(ctx, node);
check_label(ctx, rs->label, node);
Operand init = {};
if (rs->init != nullptr) {
check_stmt(ctx, rs->init, mod_flags);
}
u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
auto vals = array_make<Type *>(temporary_allocator(), 0, 2);
auto entities = array_make<Entity *>(temporary_allocator(), 0, 2);
bool is_map = false;

View File

@@ -756,6 +756,10 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node,
lb_open_scope(p, scope);
if (rs->init != nullptr) {
lb_build_stmt(p, rs->init);
}
Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr;
Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr;
Type *val0_type = nullptr;
@@ -948,6 +952,10 @@ gb_internal void lb_build_range_tuple(lbProcedure *p, AstRangeStmt *rs, Scope *s
lb_open_scope(p, scope);
if (rs->init != nullptr) {
lb_build_stmt(p, rs->init);
}
lbBlock *loop = lb_create_block(p, "for.tuple.loop");
lb_emit_jump(p, loop);
lb_start_block(p, loop);
@@ -1002,6 +1010,9 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs
lb_open_scope(p, scope);
if (rs->init != nullptr) {
lb_build_stmt(p, rs->init);
}
Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr;
Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr;
@@ -1153,6 +1164,10 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc
lb_open_scope(p, scope);
if (rs->init != nullptr) {
lb_build_stmt(p, rs->init);
}
Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr;
Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr;
Type *val0_type = nullptr;
@@ -1352,6 +1367,10 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt *
lb_open_scope(p, scope); // Open scope here
if (rs->init != nullptr) {
lb_build_stmt(p, rs->init);
}
Ast *val0 = lb_strip_and_prefix(rs->val0);
Ast *val1 = lb_strip_and_prefix(rs->val1);
Type *val0_type = nullptr;

View File

@@ -353,12 +353,14 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
break;
case Ast_RangeStmt:
n->RangeStmt.label = clone_ast(n->RangeStmt.label, f);
n->RangeStmt.init = clone_ast(n->RangeStmt.init, f);
n->RangeStmt.vals = clone_ast_array(n->RangeStmt.vals, f);
n->RangeStmt.expr = clone_ast(n->RangeStmt.expr, f);
n->RangeStmt.body = clone_ast(n->RangeStmt.body, f);
break;
case Ast_UnrollRangeStmt:
n->UnrollRangeStmt.args = clone_ast_array(n->UnrollRangeStmt.args, f);
n->UnrollRangeStmt.init = clone_ast(n->UnrollRangeStmt.init, f);
n->UnrollRangeStmt.val0 = clone_ast(n->UnrollRangeStmt.val0, f);
n->UnrollRangeStmt.val1 = clone_ast(n->UnrollRangeStmt.val1, f);
n->UnrollRangeStmt.expr = clone_ast(n->UnrollRangeStmt.expr, f);
@@ -1055,9 +1057,10 @@ gb_internal Ast *ast_for_stmt(AstFile *f, Token token, Ast *init, Ast *cond, Ast
return result;
}
gb_internal Ast *ast_range_stmt(AstFile *f, Token token, Slice<Ast *> vals, Token in_token, Ast *expr, Ast *body) {
gb_internal Ast *ast_range_stmt(AstFile *f, Token token, Ast *init, Slice<Ast *> vals, Token in_token, Ast *expr, Ast *body) {
Ast *result = alloc_ast_node(f, Ast_RangeStmt);
result->RangeStmt.token = token;
result->RangeStmt.init = init;
result->RangeStmt.vals = vals;
result->RangeStmt.in_token = in_token;
result->RangeStmt.expr = expr;
@@ -1065,9 +1068,10 @@ gb_internal Ast *ast_range_stmt(AstFile *f, Token token, Slice<Ast *> vals, Toke
return result;
}
gb_internal Ast *ast_unroll_range_stmt(AstFile *f, Token unroll_token, Slice<Ast *> args, Token for_token, Ast *val0, Ast *val1, Token in_token, Ast *expr, Ast *body) {
gb_internal Ast *ast_unroll_range_stmt(AstFile *f, Token unroll_token, Ast *init, Slice<Ast *> args, Token for_token, Ast *val0, Ast *val1, Token in_token, Ast *expr, Ast *body) {
Ast *result = alloc_ast_node(f, Ast_UnrollRangeStmt);
result->UnrollRangeStmt.unroll_token = unroll_token;
result->UnrollRangeStmt.init = init;
result->UnrollRangeStmt.args = args;
result->UnrollRangeStmt.for_token = for_token;
result->UnrollRangeStmt.val0 = val0;
@@ -4883,7 +4887,7 @@ gb_internal Ast *parse_for_stmt(AstFile *f) {
body = parse_block_stmt(f, false);
}
return ast_range_stmt(f, token, {}, in_token, rhs, body);
return ast_range_stmt(f, token, init, {}, in_token, rhs, body);
}
if (f->curr_token.kind != Token_Semicolon) {
@@ -4898,9 +4902,20 @@ gb_internal Ast *parse_for_stmt(AstFile *f) {
cond = nullptr;
if (f->curr_token.kind == Token_OpenBrace || f->curr_token.kind == Token_do) {
syntax_error(f->curr_token, "Expected ';', followed by a condition expression and post statement, got %.*s", LIT(token_strings[f->curr_token.kind]));
syntax_error(f->curr_token, "Expected ';', followed by a condition expression and post statement, or 'x in y' style loop, got %.*s", LIT(token_strings[f->curr_token.kind]));
} else {
if (f->curr_token.kind != Token_Semicolon) {
if (f->curr_token.kind == Token_Ident) {
// for init; x in y { }
Token next_token = peek_token(f);
if (next_token.kind == Token_in || next_token.kind == Token_Comma) {
cond = parse_simple_stmt(f, StmtAllowFlag_In);
GB_ASSERT(cond->kind == Ast_AssignStmt && cond->AssignStmt.op.kind == Token_in);
is_range = true;
goto range_skip;
}
}
cond = parse_simple_stmt(f, StmtAllowFlag_None);
}
@@ -4918,6 +4933,7 @@ gb_internal Ast *parse_for_stmt(AstFile *f) {
}
}
range_skip:;
if (allow_token(f, Token_do)) {
body = parse_do_body(f, token, "the for statement");
@@ -4933,7 +4949,7 @@ gb_internal Ast *parse_for_stmt(AstFile *f) {
if (cond->AssignStmt.rhs.count > 0) {
rhs = cond->AssignStmt.rhs[0];
}
return ast_range_stmt(f, token, vals, in_token, rhs, body);
return ast_range_stmt(f, token, init, vals, in_token, rhs, body);
}
cond = convert_stmt_to_expr(f, cond, str_lit("boolean expression"));
@@ -5267,6 +5283,7 @@ gb_internal Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) {
}
Token for_token = expect_token(f, Token_for);
Ast *init = nullptr;
Ast *val0 = nullptr;
Ast *val1 = nullptr;
Token in_token = {};
@@ -5309,7 +5326,7 @@ gb_internal Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) {
if (bad_stmt) {
return ast_bad_stmt(f, unroll_token, f->curr_token);
}
return ast_unroll_range_stmt(f, unroll_token, slice_from_array(args), for_token, val0, val1, in_token, expr, body);
return ast_unroll_range_stmt(f, unroll_token, init, slice_from_array(args), for_token, val0, val1, in_token, expr, body);
}
gb_internal Ast *parse_stmt(AstFile *f) {

View File

@@ -587,6 +587,7 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
Scope *scope; \
Token token; \
Ast *label; \
Ast *init; \
Slice<Ast *> vals; \
Token in_token; \
Ast *expr; \
@@ -596,6 +597,7 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
AST_KIND(UnrollRangeStmt, "#unroll range statement", struct { \
Scope *scope; \
Token unroll_token; \
Ast *init; \
Slice<Ast *> args; \
Token for_token; \
Ast *val0; \