diff options
| author | gingerBill <gingerBill@users.noreply.github.com> | 2026-02-17 11:11:56 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-17 11:11:56 +0000 |
| commit | a7ed7ccd0c392cfbb6cb2e486fc7c75b16791902 (patch) | |
| tree | 8a325415e65ad09cf81547b957753b99d867aaf9 | |
| parent | bfe1f234ec763fc79359f7614ae4cccab88780e7 (diff) | |
| parent | 58deab46a3e2423789f2af5e6ec7a4ef69ce810e (diff) | |
Merge pull request #6259 from odin-lang/bill/range-init
`for init; x in y {}` style loops (proof of concept)
| -rw-r--r-- | core/container/xar/freelist.odin | 154 | ||||
| -rw-r--r-- | core/container/xar/xar.odin | 100 | ||||
| -rw-r--r-- | core/math/linalg/general.odin | 1 | ||||
| -rw-r--r-- | core/odin/ast/ast.odin | 1 | ||||
| -rw-r--r-- | core/odin/ast/clone.odin | 1 | ||||
| -rw-r--r-- | core/odin/ast/walk.odin | 3 | ||||
| -rw-r--r-- | core/odin/parser/parser.odin | 14 | ||||
| -rw-r--r-- | src/check_stmt.cpp | 8 | ||||
| -rw-r--r-- | src/llvm_backend_stmt.cpp | 19 | ||||
| -rw-r--r-- | src/parser.cpp | 29 | ||||
| -rw-r--r-- | src/parser.hpp | 2 |
11 files changed, 297 insertions, 35 deletions
diff --git a/core/container/xar/freelist.odin b/core/container/xar/freelist.odin new file mode 100644 index 000000000..ab2895881 --- /dev/null +++ b/core/container/xar/freelist.odin @@ -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 +} diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index b2f1a5499..b200df0e6 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -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}
\ No newline at end of file diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 4a0150972..ea3a4e84a 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -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) } diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index ec22db434..2cee6e385 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -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, diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index b7501e6ca..df3e1df0d 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -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) diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index cba040875..24c90c13b 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -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) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index a18942e6b..643673c69 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -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 diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 8f17e66c4..bfa68ca78 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -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; diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 05ec10cda..98b45f646 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -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; diff --git a/src/parser.cpp b/src/parser.cpp index fe42739b8..e98832034 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -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) { diff --git a/src/parser.hpp b/src/parser.hpp index 1026433d0..d3527285d 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -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; \ |