feat: assignment operations

This commit is contained in:
Brett Broadhurst 2026-04-03 11:24:47 -06:00
parent ecd6017673
commit f82b361005
Failed to generate hash of commit
9 changed files with 79 additions and 48 deletions

View file

@ -51,6 +51,8 @@ pub const Node = struct {
divert_expr,
selector_expr,
assign_stmt,
assign_add_stmt,
assign_sub_stmt,
block_stmt,
content_stmt,
divert_stmt,

View file

@ -83,6 +83,8 @@ fn nodeTagToString(tag: Ast.Node.Tag) []const u8 {
.call_expr => "CallExpr",
.choice_expr => "ChoiceContentExpr",
.assign_stmt => "AssignStmt",
.assign_add_stmt => "AssignAddStmt",
.assign_sub_stmt => "AssignSubStmt",
.block_stmt => "BlockStmt",
.content_stmt => "ContentStmt",
.divert_stmt => "DivertStmt",
@ -396,6 +398,8 @@ fn renderAstWalk(
.logical_lesser_or_equal_expr,
.logical_lesser_expr,
.assign_stmt,
.assign_add_stmt,
.assign_sub_stmt,
.divert_stmt,
.return_stmt,
.expr_stmt,

View file

@ -783,44 +783,8 @@ fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!I
.logical_lesser_expr => return binaryOp(gi, scope, node, .cmp_lt),
.logical_lesser_or_equal_expr => return binaryOp(gi, scope, node, .cmp_lte),
.call_expr => return callExpr(gi, scope, node, .call),
.choice_expr => unreachable,
.divert_expr => unreachable,
.selector_expr => return fieldAccess(gi, scope, node),
.assign_stmt => unreachable,
.block_stmt => unreachable,
.content_stmt => unreachable,
.divert_stmt => unreachable,
.return_stmt => unreachable,
.expr_stmt => unreachable,
.choice_stmt => unreachable,
.choice_star_stmt => unreachable,
.choice_plus_stmt => unreachable,
.gather_point_stmt => unreachable,
.gathered_stmt => unreachable,
.function_prototype => unreachable,
.stitch_prototype => unreachable,
.knot_prototype => unreachable,
.function_decl => unreachable,
.stitch_decl => unreachable,
.knot_decl => unreachable,
.const_decl => unreachable,
.var_decl => unreachable,
.list_decl => unreachable,
.temp_decl => unreachable,
.parameter_decl => unreachable,
.ref_parameter_decl => unreachable,
.argument_list => unreachable,
.parameter_list => unreachable,
.switch_stmt => unreachable, // Handled in switchStmt
.switch_case => unreachable, // Handled in switchStmt
.if_stmt => unreachable, // Handled in ifStmt
.multi_if_stmt => unreachable, // Handled in multiIfStmt
.if_branch => unreachable, // Handled in ifStmt and multiIfStmt
.else_branch => unreachable, // Handled in switchStmt, multiIfStmt, and ifStmt
.content => unreachable,
.inline_logic_expr => unreachable,
.inline_if_stmt => unreachable,
.invalid => unreachable,
inline else => |_| unreachable,
}
}
@ -1164,7 +1128,7 @@ fn contentStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
}
}
fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
fn assign(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
const astgen = gi.astgen;
const identifier_node = node.data.bin.lhs.?;
const expr_node = node.data.bin.rhs.?;
@ -1178,6 +1142,32 @@ fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void
return fail(astgen, identifier_node, "unknown identifier", .{});
}
fn assignOp(
gi: *GenIr,
scope: *Scope,
node: *const Ast.Node,
op_inst_tag: Ir.Inst.Tag,
) InnerError!void {
const astgen = gi.astgen;
const identifier_node = node.data.bin.lhs.?;
const expr_node = node.data.bin.rhs.?;
const name_str = try astgen.strFromNode(identifier_node);
if (scope.lookup(name_str.index)) |decl| {
const lhs = try gi.addUnaryNode(.load, decl.inst_index.toRef());
const rhs = try expr(gi, scope, expr_node);
const result = switch (op_inst_tag) {
.add => try gi.addBinaryNode(.add, lhs, rhs),
.sub => try gi.addBinaryNode(.sub, lhs, rhs),
else => unreachable,
};
_ = try gi.addBinaryNode(.store, decl.inst_index.toRef(), result);
return;
}
return fail(astgen, identifier_node, "unknown identifier", .{});
}
fn choiceStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
const astgen = gi.astgen;
const gpa = astgen.gpa;
@ -1540,7 +1530,9 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
.var_decl => try varDecl(gi, &child_scope, node),
.const_decl => try varDecl(gi, &child_scope, node),
.temp_decl => try tempDecl(gi, &child_scope, node),
.assign_stmt => try assignStmt(gi, &child_scope, node),
.assign_stmt => try assign(gi, &child_scope, node),
.assign_add_stmt => try assignOp(gi, &child_scope, node, .add),
.assign_sub_stmt => try assignOp(gi, &child_scope, node, .sub),
.content_stmt => try contentStmt(gi, &child_scope, node),
.choice_stmt => try choiceStmt(gi, &child_scope, node),
.expr_stmt => try exprStmt(gi, &child_scope, node),

View file

@ -111,6 +111,15 @@ fn getTokenInfixType(tag: Token.Tag) Ast.Node.Tag {
};
}
fn getTokenAssignType(tag: Token.Tag) ?Ast.Node.Tag {
return switch (tag) {
.equal => .assign_stmt,
.plus_equal => .assign_add_stmt,
.minus_equal => .assign_sub_stmt,
else => null,
};
}
fn getBindingPower(tag: Token.Tag) Precedence {
return switch (tag) {
.ampersand_ampersand, .keyword_and => .logical_and,
@ -703,16 +712,18 @@ fn parseAssignStmt(p: *Parse) Error!*Ast.Node {
const main_token = p.token;
const lhs = try parseIdentifierExpr(p);
if (!p.checkToken(.equal)) return parseExprStmt(p, lhs);
_ = p.nextToken();
if (getTokenAssignType(p.token.tag)) |op| {
_ = p.nextToken();
const rhs = try p.expectExpr();
_ = try p.expectNewline();
const rhs = try p.expectExpr();
_ = try p.expectNewline();
return .createBinary(p.arena, .assign_stmt, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, lhs, rhs);
return .createBinary(p.arena, op, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, lhs, rhs);
}
return parseExprStmt(p, lhs);
}
fn parseTempDecl(p: *Parse) Error!*Ast.Node {

View file

@ -6,6 +6,10 @@ test "fixture - variable arithmetic" {
try testRuntimeFixture("variable-arithmetic");
}
test "fixture - assignment op" {
try testRuntimeFixture("assignment-op");
}
test "fixture - constant folding" {
try testRuntimeFixture("constant-folding");
}

View file

View file

@ -0,0 +1,5 @@
~ temp foo = 0
~ foo = 1
~ foo += 4
~ foo -= 2
{foo}

View file

@ -0,0 +1 @@
3

View file

@ -119,6 +119,7 @@ pub const Tokenizer = struct {
slash,
equal,
bang,
plus,
pipe,
less_than,
greater_than,
@ -197,7 +198,7 @@ pub const Tokenizer = struct {
},
'+' => {
self.index += 1;
result.tag = .plus;
continue :state .plus;
},
'-' => {
self.index += 1;
@ -284,6 +285,13 @@ pub const Tokenizer = struct {
}
},
},
.plus => switch (self.buffer[self.index]) {
'=' => {
self.index += 1;
result.tag = .plus_equal;
},
else => result.tag = .plus,
},
.pipe => switch (self.buffer[self.index]) {
'|' => {
self.index += 1;
@ -296,6 +304,10 @@ pub const Tokenizer = struct {
self.index += 1;
result.tag = .right_arrow;
},
'=' => {
self.index += 1;
result.tag = .minus_equal;
},
else => result.tag = .minus,
},
.slash => switch (self.buffer[self.index]) {