multiple assign

This commit is contained in:
agra
2026-02-16 01:13:34 +02:00
parent fb60818424
commit 58e2a5bdb1
9 changed files with 171 additions and 13 deletions

View File

@@ -30,6 +30,7 @@ pub const Node = struct {
const_decl: ConstDecl,
var_decl: VarDecl,
assignment: Assignment,
multi_assign: MultiAssign,
enum_decl: EnumDecl,
struct_decl: StructDecl,
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 {
name: []const u8,
variant_names: []const []const u8,

View File

@@ -2447,6 +2447,9 @@ pub const CodeGen = struct {
.assignment => |asgn| {
return self.genAssignment(asgn);
},
.multi_assign => |ma| {
return self.genMultiAssign(ma);
},
.return_stmt => |rs| {
// Evaluate return value first, then emit all defers, then return
if (rs.value) |val_node| {
@@ -2945,6 +2948,65 @@ pub const CodeGen = struct {
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 {
const fa = asgn.target.data.field_access;
@@ -3276,6 +3338,9 @@ pub const CodeGen = struct {
.assignment => |asgn| {
return self.genAssignment(asgn);
},
.multi_assign => |ma| {
return self.genMultiAssign(ma);
},
.return_stmt => |rs| {
if (rs.value) |val_node| {
const raw_val = try self.genExpr(val_node);

View File

@@ -834,6 +834,12 @@ pub const Parser = struct {
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
if (self.isAssignOp()) {
const op = self.assignOp();
@@ -918,6 +924,11 @@ pub const Parser = struct {
// Expression statement
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;)
if (self.isAssignOp()) {
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 {
return switch (self.current.tag) {
.kw_or => 1,

View File

@@ -689,6 +689,10 @@ pub const Analyzer = struct {
try self.analyzeNode(asgn.target);
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| {
if (ret.value) |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.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| {
if (ret.value) |val| {
if (findNodeAtOffset(val, offset)) |found| return found;