diff --git a/examples/19-varargs.sx b/examples/19-varargs.sx index ccac756..02fedfe 100644 --- a/examples/19-varargs.sx +++ b/examples/19-varargs.sx @@ -2,14 +2,14 @@ sum :: (args: ..s32) -> s32 { result := 0; - for args { + for args: (it) { result = result + it; } result; } print_all :: (args: ..s32) { - for args { + for args: (it) { write(int_to_string(it)); write(" "); } @@ -26,7 +26,7 @@ main :: () -> s32 { write(int_to_string(sum(..arr))); write("\n"); - for arr { + for arr: (it) { write(int_to_string(it)); write(" "); } diff --git a/examples/20-any-varargs.sx b/examples/20-any-varargs.sx index 41d278c..c62da65 100644 --- a/examples/20-any-varargs.sx +++ b/examples/20-any-varargs.sx @@ -7,7 +7,7 @@ Point :: struct { // Print all arguments — accepts any type, dispatches via type-switch print_any :: (args: ..Any) { - for args { + for args: (it) { type := type_of(it); if type == { case int: write(int_to_string(cast(s32) it)); diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index e6c4d8a..4b7ff93 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -53,7 +53,7 @@ pair_add :: (a: $T, b: $U) -> s64 { typed_sum :: (args: ..s32) -> s32 { result := 0; - for args { result = result + it; } + for args: (it) { result = result + it; } result; } @@ -635,7 +635,7 @@ END; // For loop basic farr : [4]s32 = .[10, 20, 30, 40]; write("for:"); - for farr { + for farr: (it) { write(" "); write(int_to_string(it)); } @@ -643,29 +643,29 @@ END; // For with print write("for-print:"); - for farr { + for farr: (it) { print(" {}", it); } write("\n"); - // For with it_index + // For with index write("for-idx:"); - for farr { + for farr: (_, ix) { write(" "); - write(int_to_string(it_index)); + write(int_to_string(ix)); } write("\n"); // For with print two args write("for-2arg:"); - for farr { - print(" {}@{}", it, it_index); + for farr: (it, ix) { + print(" {}@{}", it, ix); } write("\n"); // For with break write("for-break:"); - for farr { + for farr: (it) { if it == 30 { break; } print(" {}", it); } @@ -673,7 +673,7 @@ END; // For with continue write("for-continue:"); - for farr { + for farr: (it) { if it == 20 { continue; } print(" {}", it); } @@ -682,15 +682,15 @@ END; // For on slice fsl : []s32 = .[10, 20, 30]; write("for-slice:"); - for fsl { + for fsl: (it) { print(" {}", it); } write("\n"); - // For on slice with it_index + // For on slice with index write("for-slice-idx:"); - for fsl { - print(" {}:{}", it_index, it); + for fsl: (it, ix) { + print(" {}:{}", ix, it); } write("\n"); @@ -698,19 +698,18 @@ END; nf_a : [2]s32 = .[0, 1]; nf_b : [2]s32 = .[0, 1]; write("for-nested:"); - for nf_a { - oa := it; - for nf_b { - print(" ({},{})", oa, it); + for nf_a: (oa) { + for nf_b: (ob) { + print(" ({},{})", oa, ob); } } write("\n"); - // For with break preserving it_index + // For with break preserving index fbi : [5]s32 = .[10, 20, 30, 40, 50]; fbi_idx := 0; - for fbi { - if it == 30 { fbi_idx = it_index; break; } + for fbi: (it, ix) { + if it == 30 { fbi_idx = ix; break; } } print("for-break-idx: {}\n", fbi_idx); diff --git a/specs.md b/specs.md index efeebca..00f2522 100644 --- a/specs.md +++ b/specs.md @@ -789,22 +789,23 @@ while i < 10 { ### For Loop ```sx -for iterable { - // `it` is the current element - // `it_index` is the current index (s64) - print("{it}\n"); -} +for iterable: (elem) { } // element alias (no copy) +for iterable: (elem, ix) { } // element + index +for iterable: (_, ix) { } // index only ``` -Iterates over arrays and slices. The loop body has two implicit variables: -- `it` — the current element value -- `it_index` — the current index (s64, starting at 0) +Iterates over arrays and slices. The capture clause after `:` binds loop variables: +- The first name is the element capture (non-reassignable alias into the array/slice) +- The optional second name is the index (s64, starting at 0, also non-reassignable) +- Use `_` to discard a capture + +The element capture is a direct alias — reads and field writes go to the original array element. Direct reassignment of the capture (`elem = x`) is a compile error. `break;` exits the loop. `continue;` skips to the next iteration. ```sx arr : [5]s32 = .[1, 2, 3, 4, 5]; -for arr { - if it_index == 2 { continue; } - print("{it}\n"); +for arr: (val, ix) { + if ix == 2 { continue; } + print("{}\n", val); } ``` @@ -1069,7 +1070,7 @@ assignment = lvalue ('=' | '+=' | '-=' | '*=' | '/=') expr lvalue = IDENT | postfix '.' IDENT expr = if_expr | match_expr | while_expr | for_expr | lambda | binary while_expr = 'while' expr block -for_expr = 'for' expr block +for_expr = 'for' expr ':' '(' IDENT [',' IDENT] ')' block binary = unary (binop unary)* unary = ('-' | '!' | 'xx' | 'cast' '(' type ')') postfix | postfix diff --git a/src/ast.zig b/src/ast.zig index fa2785a..3c0a874 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -349,6 +349,8 @@ pub const WhileExpr = struct { pub const ForExpr = struct { iterable: *Node, body: *Node, + capture_name: []const u8, + index_name: ?[]const u8 = null, }; pub const SpreadExpr = struct { diff --git a/src/codegen.zig b/src/codegen.zig index 2b95dc8..547ac82 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -296,6 +296,7 @@ pub const CodeGen = struct { const NamedValue = struct { ptr: c.LLVMValueRef, // alloca pointer ty: Type, // sx type + is_const: bool = false, }; /// Unified value lookup result — avoids sequential hash lookups at hot paths. @@ -2881,6 +2882,8 @@ pub const CodeGen = struct { return self.emitErrorFmt("undefined variable '{s}'", .{name}); const entry = lookup.asNamedValue() orelse return self.emitErrorFmt("cannot assign to constant '{s}'", .{name}); + if (entry.is_const) + return self.emitErrorFmt("cannot assign to '{s}'", .{name}); // Meta type reassignment: x = Vec4, x = f64, x = test if (entry.ty == .meta_type and asgn.op == .assign) { @@ -6345,15 +6348,17 @@ pub const CodeGen = struct { const elem_llvm_ty = self.typeToLLVM(elem_ty); - // Allocate it_index (s64) and it (element type) - const idx_alloca = self.buildEntryBlockAlloca(i64_type, "it_index"); + // Allocate index variable + const idx_alloca = self.buildEntryBlockAlloca(i64_type, "for_idx"); _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_type, 0, 0), idx_alloca); - const it_alloca = self.buildEntryBlockAlloca(elem_llvm_ty, "it"); - // Push scope and bind it, it_index + // Push scope and bind index if requested try self.pushScope(); - try self.named_values.put("it", .{ .ptr = it_alloca, .ty = elem_ty }); - try self.named_values.put("it_index", .{ .ptr = idx_alloca, .ty = Type.s(64) }); + if (for_expr.index_name) |idx_name| { + if (!std.mem.eql(u8, idx_name, "_")) { + try self.named_values.put(idx_name, .{ .ptr = idx_alloca, .ty = Type.s(64), .is_const = true }); + } + } // Create basic blocks const cond_bb = self.appendBB("for.cond"); @@ -6363,29 +6368,32 @@ pub const CodeGen = struct { self.br(cond_bb); - // Condition: it_index < len + // Condition: index < len self.positionAt(cond_bb); const cur_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "cur_idx"); const cond_val = self.icmp(c.LLVMIntSLT, cur_idx, len_val, "for_cond"); self.condBr(cond_val, body_bb, after_bb); - // Body: load it = iterable[it_index], then execute body + // Body: compute element GEP, bind capture, execute body self.positionAt(body_bb); const body_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "body_idx"); - if (is_slice) { - // Slice: GEP through data pointer - const gep = self.gepPointerElement(elem_llvm_ty, iter_ptr, body_idx, "for_elem"); - const elem_val = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "it_val"); - _ = c.LLVMBuildStore(self.builder, elem_val, it_alloca); - } else { - // Array: GEP with [0, idx] + const elem_gep = if (is_slice) + self.gepPointerElement(elem_llvm_ty, iter_ptr, body_idx, "for_elem") + else blk: { const arr_llvm_ty = self.typeToLLVM(iter_ty); const zero = c.LLVMConstInt(i64_type, 0, 0); var indices = [_]c.LLVMValueRef{ zero, body_idx }; - const gep = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, iter_ptr, &indices, 2, "for_elem"); - const elem_val = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "it_val"); - _ = c.LLVMBuildStore(self.builder, elem_val, it_alloca); + break :blk c.LLVMBuildGEP2(self.builder, arr_llvm_ty, iter_ptr, &indices, 2, "for_elem"); + }; + + if (!std.mem.eql(u8, for_expr.capture_name, "_")) { + // Alias mode: capture points directly to element in array/slice + try self.named_values.put(for_expr.capture_name, .{ + .ptr = elem_gep, + .ty = elem_ty, + .is_const = true, + }); } // Save and set loop context for break/continue @@ -6405,7 +6413,7 @@ pub const CodeGen = struct { self.br(incr_bb); } - // Increment it_index, then branch back to condition + // Increment index, then branch back to condition self.positionAt(incr_bb); const inc_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "inc_idx"); const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i64_type, 1, 0), "next_idx"); diff --git a/src/parser.zig b/src/parser.zig index 8af6f07..a8bd064 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1428,11 +1428,33 @@ pub const Parser = struct { self.advance(); // skip 'for' const iterable = try self.parseExpr(); + + // Expect ': (' capture clause + try self.expect(.colon); + try self.expect(.l_paren); + + // Capture variable name + if (self.current.tag != .identifier) return self.fail("expected capture variable name"); + const capture_name = self.tokenSlice(self.current); + self.advance(); + + // Optional ', index_name' + var index_name: ?[]const u8 = null; + if (self.current.tag == .comma) { + self.advance(); + if (self.current.tag != .identifier) return self.fail("expected index variable name"); + index_name = self.tokenSlice(self.current); + self.advance(); + } + + try self.expect(.r_paren); const body = try self.parseBlock(); return try self.createNode(start, .{ .for_expr = .{ .iterable = iterable, .body = body, + .capture_name = capture_name, + .index_name = index_name, } }); }