multiple assign
This commit is contained in:
@@ -8,16 +8,12 @@ quick_sort :: (items: []$T) {
|
|||||||
while j < hi {
|
while j < hi {
|
||||||
if items[j] < pivot {
|
if items[j] < pivot {
|
||||||
i += 1;
|
i += 1;
|
||||||
tmp := items[i];
|
items[i], items[j] = items[j], items[i];
|
||||||
items[i] = items[j];
|
|
||||||
items[j] = tmp;
|
|
||||||
}
|
}
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
tmp := items[i];
|
items[i], items[hi] = items[hi], items[i];
|
||||||
items[i] = items[hi];
|
|
||||||
items[hi] = tmp;
|
|
||||||
i;
|
i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1012,5 +1012,32 @@ END;
|
|||||||
print("flags-explicit: {}\n", wf);
|
print("flags-explicit: {}\n", wf);
|
||||||
print("flags-explicit-raw: {}\n", cast(s64) wf);
|
print("flags-explicit-raw: {}\n", cast(s64) wf);
|
||||||
|
|
||||||
|
// --- Multi-target assignment (swap) ---
|
||||||
|
print("--- swap ---\n");
|
||||||
|
|
||||||
|
// Variable swap
|
||||||
|
{
|
||||||
|
sa := 10;
|
||||||
|
sb := 20;
|
||||||
|
sa, sb = sb, sa;
|
||||||
|
print("var swap: {} {}\n", sa, sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array element swap
|
||||||
|
{
|
||||||
|
sarr : [3]s64 = .[1, 2, 3];
|
||||||
|
sarr[0], sarr[2] = sarr[2], sarr[0];
|
||||||
|
print("arr swap: {} {}\n", sarr[0], sarr[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3-way rotation
|
||||||
|
{
|
||||||
|
ra := 1;
|
||||||
|
rb := 2;
|
||||||
|
rc := 3;
|
||||||
|
ra, rb, rc = rc, ra, rb;
|
||||||
|
print("3-way: {} {} {}\n", ra, rb, rc);
|
||||||
|
}
|
||||||
|
|
||||||
print("=== DONE ===\n");
|
print("=== DONE ===\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,12 @@ quick_sort :: (items: []$T) {
|
|||||||
while j < hi {
|
while j < hi {
|
||||||
if items[j] < pivot {
|
if items[j] < pivot {
|
||||||
i += 1;
|
i += 1;
|
||||||
tmp := items[i];
|
items[i], items[j] = items[j], items[i];
|
||||||
items[i] = items[j];
|
|
||||||
items[j] = tmp;
|
|
||||||
}
|
}
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
tmp := items[i];
|
items[i], items[hi] = items[hi], items[i];
|
||||||
items[i] = items[hi];
|
|
||||||
items[hi] = tmp;
|
|
||||||
i;
|
i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
specs.md
4
specs.md
@@ -849,6 +849,7 @@ Statements are terminated by `;`.
|
|||||||
|
|
||||||
- **Declaration**: `name :: value;` / `name := value;`
|
- **Declaration**: `name :: value;` / `name := value;`
|
||||||
- **Assignment**: `name = value;` / `name += value;` (and other compound assignments). Also supports field targets: `obj.field = value;`
|
- **Assignment**: `name = value;` / `name += value;` (and other compound assignments). Also supports field targets: `obj.field = value;`
|
||||||
|
- **Multi-target assignment**: `a, b = b, a;` — all RHS values are evaluated before any stores, enabling swaps without temporaries. Target count must equal value count. Only plain `=` is supported (no compound operators). Each target must be a valid lvalue (variable, field, index, dereference).
|
||||||
- **Expression statement**: `expr;` — evaluates the expression (last in a block = return value)
|
- **Expression statement**: `expr;` — evaluates the expression (last in a block = return value)
|
||||||
- **Return**: `return expr;` — returns from the enclosing function with the given value. `return;` returns void.
|
- **Return**: `return expr;` — returns from the enclosing function with the given value. `return;` returns void.
|
||||||
- **Break**: `break;` — exits a match arm or while loop
|
- **Break**: `break;` — exits a match arm or while loop
|
||||||
@@ -1059,7 +1060,7 @@ field_group = IDENT (',' IDENT)* ':' type ('=' expr)? ';'
|
|||||||
params = param (',' param)*
|
params = param (',' param)*
|
||||||
param = IDENT ':' type
|
param = IDENT ':' type
|
||||||
block = '{' stmt* '}'
|
block = '{' stmt* '}'
|
||||||
stmt = decl | assignment ';' | return_stmt | defer_stmt | insert_stmt
|
stmt = decl | assignment ';' | multi_assign ';' | return_stmt | defer_stmt | insert_stmt
|
||||||
| break_stmt | continue_stmt | expr ';'
|
| break_stmt | continue_stmt | expr ';'
|
||||||
return_stmt = 'return' expr? ';'
|
return_stmt = 'return' expr? ';'
|
||||||
break_stmt = 'break' ';'
|
break_stmt = 'break' ';'
|
||||||
@@ -1067,6 +1068,7 @@ continue_stmt = 'continue' ';'
|
|||||||
defer_stmt = 'defer' expr ';'
|
defer_stmt = 'defer' expr ';'
|
||||||
insert_stmt = '#insert' expr ';'
|
insert_stmt = '#insert' expr ';'
|
||||||
assignment = lvalue ('=' | '+=' | '-=' | '*=' | '/=') expr
|
assignment = lvalue ('=' | '+=' | '-=' | '*=' | '/=') expr
|
||||||
|
multi_assign = lvalue (',' lvalue)+ '=' expr (',' expr)+
|
||||||
lvalue = IDENT | postfix '.' IDENT
|
lvalue = IDENT | postfix '.' IDENT
|
||||||
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
|
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
|
||||||
while_expr = 'while' expr block
|
while_expr = 'while' expr block
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub const Node = struct {
|
|||||||
const_decl: ConstDecl,
|
const_decl: ConstDecl,
|
||||||
var_decl: VarDecl,
|
var_decl: VarDecl,
|
||||||
assignment: Assignment,
|
assignment: Assignment,
|
||||||
|
multi_assign: MultiAssign,
|
||||||
enum_decl: EnumDecl,
|
enum_decl: EnumDecl,
|
||||||
struct_decl: StructDecl,
|
struct_decl: StructDecl,
|
||||||
struct_literal: StructLiteral,
|
struct_literal: StructLiteral,
|
||||||
@@ -227,6 +228,11 @@ pub const Assignment = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const MultiAssign = struct {
|
||||||
|
targets: []const *Node,
|
||||||
|
values: []const *Node,
|
||||||
|
};
|
||||||
|
|
||||||
pub const EnumDecl = struct {
|
pub const EnumDecl = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
variant_names: []const []const u8,
|
variant_names: []const []const u8,
|
||||||
|
|||||||
@@ -2447,6 +2447,9 @@ pub const CodeGen = struct {
|
|||||||
.assignment => |asgn| {
|
.assignment => |asgn| {
|
||||||
return self.genAssignment(asgn);
|
return self.genAssignment(asgn);
|
||||||
},
|
},
|
||||||
|
.multi_assign => |ma| {
|
||||||
|
return self.genMultiAssign(ma);
|
||||||
|
},
|
||||||
.return_stmt => |rs| {
|
.return_stmt => |rs| {
|
||||||
// Evaluate return value first, then emit all defers, then return
|
// Evaluate return value first, then emit all defers, then return
|
||||||
if (rs.value) |val_node| {
|
if (rs.value) |val_node| {
|
||||||
@@ -2945,6 +2948,65 @@ pub const CodeGen = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn genMultiAssign(self: *CodeGen, ma: ast.MultiAssign) !c.LLVMValueRef {
|
||||||
|
const n = ma.targets.len;
|
||||||
|
|
||||||
|
// Phase 1: Evaluate ALL RHS values into temporaries.
|
||||||
|
// This ensures correctness for aliased swaps (a, b = b, a).
|
||||||
|
const tmp_ptrs = try self.allocator.alloc(c.LLVMValueRef, n);
|
||||||
|
const target_types = try self.allocator.alloc(Type, n);
|
||||||
|
|
||||||
|
for (0..n) |i| {
|
||||||
|
target_types[i] = self.inferType(ma.targets[i]);
|
||||||
|
const llvm_ty = self.typeToLLVM(target_types[i]);
|
||||||
|
const val = try self.genExprAsType(ma.values[i], target_types[i]);
|
||||||
|
const tmp = self.buildEntryBlockAlloca(llvm_ty, "swap_tmp");
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||||
|
tmp_ptrs[i] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Load temporaries and store to each target.
|
||||||
|
for (0..n) |i| {
|
||||||
|
const llvm_ty = self.typeToLLVM(target_types[i]);
|
||||||
|
const val = c.LLVMBuildLoad2(self.builder, llvm_ty, tmp_ptrs[i], "swap_load");
|
||||||
|
try self.storeToLvalue(ma.targets[i], val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storeToLvalue(self: *CodeGen, target: *Node, val: c.LLVMValueRef) !void {
|
||||||
|
// Deref assignment: p.* = val
|
||||||
|
if (target.data == .deref_expr) {
|
||||||
|
const de = target.data.deref_expr;
|
||||||
|
const ptr_val = try self.genExpr(de.operand);
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, ptr_val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier assignment: x = val (with const check)
|
||||||
|
if (target.data == .identifier) {
|
||||||
|
const name = target.data.identifier.name;
|
||||||
|
const lookup = self.lookupValue(name) orelse
|
||||||
|
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});
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, entry.ptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field access and index expressions — use genAddressOf to get the target pointer
|
||||||
|
if (target.data == .field_access or target.data == .index_expr) {
|
||||||
|
const ptr = try self.genAddressOf(target);
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, ptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.emitError("multi-assign target must be a variable, field, index, or dereference expression");
|
||||||
|
}
|
||||||
|
|
||||||
fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef {
|
fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef {
|
||||||
const fa = asgn.target.data.field_access;
|
const fa = asgn.target.data.field_access;
|
||||||
|
|
||||||
@@ -3276,6 +3338,9 @@ pub const CodeGen = struct {
|
|||||||
.assignment => |asgn| {
|
.assignment => |asgn| {
|
||||||
return self.genAssignment(asgn);
|
return self.genAssignment(asgn);
|
||||||
},
|
},
|
||||||
|
.multi_assign => |ma| {
|
||||||
|
return self.genMultiAssign(ma);
|
||||||
|
},
|
||||||
.return_stmt => |rs| {
|
.return_stmt => |rs| {
|
||||||
if (rs.value) |val_node| {
|
if (rs.value) |val_node| {
|
||||||
const raw_val = try self.genExpr(val_node);
|
const raw_val = try self.genExpr(val_node);
|
||||||
|
|||||||
@@ -834,6 +834,12 @@ pub const Parser = struct {
|
|||||||
return self.parseTypedBinding(name, start);
|
return self.parseTypedBinding(name, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multi-target assignment: ident, expr, ... = expr, expr, ...;
|
||||||
|
if (self.current.tag == .comma) {
|
||||||
|
const first_target = try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||||
|
return try self.parseMultiAssign(first_target, start);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for assignment operators
|
// Check for assignment operators
|
||||||
if (self.isAssignOp()) {
|
if (self.isAssignOp()) {
|
||||||
const op = self.assignOp();
|
const op = self.assignOp();
|
||||||
@@ -918,6 +924,11 @@ pub const Parser = struct {
|
|||||||
// Expression statement
|
// Expression statement
|
||||||
const expr = try self.parseExpr();
|
const expr = try self.parseExpr();
|
||||||
|
|
||||||
|
// Multi-target assignment: expr, expr, ... = expr, expr, ...;
|
||||||
|
if (self.current.tag == .comma) {
|
||||||
|
return try self.parseMultiAssign(expr, expr.span.start);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for field assignment: expr = value; (e.g. a.b = 1;)
|
// Check for field assignment: expr = value; (e.g. a.b = 1;)
|
||||||
if (self.isAssignOp()) {
|
if (self.isAssignOp()) {
|
||||||
const op = self.assignOp();
|
const op = self.assignOp();
|
||||||
@@ -1623,6 +1634,45 @@ pub const Parser = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parseMultiAssign(self: *Parser, first_target: *Node, start: u32) !*Node {
|
||||||
|
var targets = std.ArrayList(*Node).empty;
|
||||||
|
try targets.append(self.allocator, first_target);
|
||||||
|
|
||||||
|
// Consume remaining targets separated by commas
|
||||||
|
while (self.current.tag == .comma) {
|
||||||
|
self.advance();
|
||||||
|
const target = try self.parseExpr();
|
||||||
|
try targets.append(self.allocator, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only plain '=' is allowed
|
||||||
|
if (self.current.tag != .equal) {
|
||||||
|
return self.fail("multi-target assignment requires '='");
|
||||||
|
}
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
// Parse RHS values separated by commas
|
||||||
|
var values = std.ArrayList(*Node).empty;
|
||||||
|
const first_val = try self.parseExpr();
|
||||||
|
try values.append(self.allocator, first_val);
|
||||||
|
while (self.current.tag == .comma) {
|
||||||
|
self.advance();
|
||||||
|
const val = try self.parseExpr();
|
||||||
|
try values.append(self.allocator, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targets.items.len != values.items.len) {
|
||||||
|
return self.fail("multi-target assignment: target count does not match value count");
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.expect(.semicolon);
|
||||||
|
|
||||||
|
return try self.createNode(start, .{ .multi_assign = .{
|
||||||
|
.targets = try targets.toOwnedSlice(self.allocator),
|
||||||
|
.values = try values.toOwnedSlice(self.allocator),
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
fn binaryPrec(self: *const Parser) u8 {
|
fn binaryPrec(self: *const Parser) u8 {
|
||||||
return switch (self.current.tag) {
|
return switch (self.current.tag) {
|
||||||
.kw_or => 1,
|
.kw_or => 1,
|
||||||
|
|||||||
12
src/sema.zig
12
src/sema.zig
@@ -689,6 +689,10 @@ pub const Analyzer = struct {
|
|||||||
try self.analyzeNode(asgn.target);
|
try self.analyzeNode(asgn.target);
|
||||||
try self.analyzeNode(asgn.value);
|
try self.analyzeNode(asgn.value);
|
||||||
},
|
},
|
||||||
|
.multi_assign => |ma| {
|
||||||
|
for (ma.targets) |t| try self.analyzeNode(t);
|
||||||
|
for (ma.values) |v| try self.analyzeNode(v);
|
||||||
|
},
|
||||||
.return_stmt => |ret| {
|
.return_stmt => |ret| {
|
||||||
if (ret.value) |val| {
|
if (ret.value) |val| {
|
||||||
try self.analyzeNode(val);
|
try self.analyzeNode(val);
|
||||||
@@ -959,6 +963,14 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
|||||||
if (findNodeAtOffset(asgn.target, offset)) |found| return found;
|
if (findNodeAtOffset(asgn.target, offset)) |found| return found;
|
||||||
if (findNodeAtOffset(asgn.value, offset)) |found| return found;
|
if (findNodeAtOffset(asgn.value, offset)) |found| return found;
|
||||||
},
|
},
|
||||||
|
.multi_assign => |ma| {
|
||||||
|
for (ma.targets) |t| {
|
||||||
|
if (findNodeAtOffset(t, offset)) |found| return found;
|
||||||
|
}
|
||||||
|
for (ma.values) |v| {
|
||||||
|
if (findNodeAtOffset(v, offset)) |found| return found;
|
||||||
|
}
|
||||||
|
},
|
||||||
.return_stmt => |ret| {
|
.return_stmt => |ret| {
|
||||||
if (ret.value) |val| {
|
if (ret.value) |val| {
|
||||||
if (findNodeAtOffset(val, offset)) |found| return found;
|
if (findNodeAtOffset(val, offset)) |found| return found;
|
||||||
|
|||||||
@@ -236,4 +236,8 @@ flags-all: .read | .write | .execute
|
|||||||
flags-raw: 3
|
flags-raw: 3
|
||||||
flags-explicit: .vsync | .resizable
|
flags-explicit: .vsync | .resizable
|
||||||
flags-explicit-raw: 68
|
flags-explicit-raw: 68
|
||||||
|
--- swap ---
|
||||||
|
var swap: 20 10
|
||||||
|
arr swap: 3 1
|
||||||
|
3-way: 3 1 2
|
||||||
=== DONE ===
|
=== DONE ===
|
||||||
|
|||||||
Reference in New Issue
Block a user