commit 383fe836264ec99e499e8a538d181c5462be0d5d (tree)
parent c5915c06fb0bd42c94525052abc0b0092e3ecb98
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 22:41:53 +0000
parser: implement asm_legacy, port inline asm tests
Add AST_NODE_ASM_LEGACY for legacy string clobber format.
When asm clobbers use string literals ("clobber1", "clobber2"),
produce asm_legacy node instead of asm node.
Port tests:
- "preserves clobbers in inline asm with stray comma"
- "remove trailing comma at the end of assembly clobber"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | ast.h | | | 3 | +++ |
| M | parser.c | | | 52 | ++++++++++++++++++++++++++++++++++++++++++---------- |
| M | parser_test.zig | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 100 insertions(+), 10 deletions(-)
diff --git a/ast.h b/ast.h
@@ -484,6 +484,9 @@ typedef enum {
AST_NODE_BLOCK_SEMICOLON,
/// `asm(lhs)`. rhs is the token index of the rparen.
AST_NODE_ASM_SIMPLE,
+ /// Legacy asm with string clobbers. `asm(lhs, a)`.
+ /// `AsmLegacy[rhs]`.
+ AST_NODE_ASM_LEGACY,
/// `asm(lhs, a)`. `Asm[rhs]`.
AST_NODE_ASM,
/// `[a] "b" (c)`. lhs is 0, rhs is token index of the rparen.
diff --git a/parser.c b/parser.c
@@ -2127,28 +2127,60 @@ static AstNodeIndex parseAsmExpr(Parser* p) {
}
// Parse clobbers (after third colon)
- // Legacy format: "str1", "str2", ...
- // New format: .{ .clobber = true }
- AstNodeIndex clobbers = 0;
if (eatToken(p, TOKEN_COLON) != null_token) {
if (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) {
- // Legacy clobber format — skip all string literals and commas
+ // Legacy clobber format: "str1", "str2", ...
+ // Produces asm_legacy node
while (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) {
p->tok_i++;
if (eatToken(p, TOKEN_COMMA) == null_token)
break;
}
- } else if (p->token_tags[p->tok_i] != TOKEN_R_PAREN) {
- clobbers = expectExpr(p);
+ const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
+ const uint32_t items_len = p->scratch.len - scratch_top.old_len;
+ const AstSubRange items_span = listToSpan(
+ p, &p->scratch.arr[scratch_top.old_len], items_len);
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_ASM_LEGACY,
+ .main_token = asm_token,
+ .data = {
+ .lhs = template,
+ .rhs = addExtra(p,
+ (AstNodeIndex[]) { items_span.start,
+ items_span.end, rparen },
+ 3),
+ },
+ });
}
+ // New clobber format: expression (e.g. .{ .clobber = true })
+ AstNodeIndex clobbers = 0;
+ if (p->token_tags[p->tok_i] != TOKEN_R_PAREN)
+ clobbers = expectExpr(p);
+
+ const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
+ const uint32_t items_len = p->scratch.len - scratch_top.old_len;
+ const AstSubRange items_span
+ = listToSpan(p, &p->scratch.arr[scratch_top.old_len], items_len);
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_ASM,
+ .main_token = asm_token,
+ .data = {
+ .lhs = template,
+ .rhs = addExtra(p,
+ (AstNodeIndex[]) { items_span.start,
+ items_span.end, OPT(clobbers), rparen },
+ 4),
+ },
+ });
}
+ // No clobbers
const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
-
const uint32_t items_len = p->scratch.len - scratch_top.old_len;
const AstSubRange items_span
= listToSpan(p, &p->scratch.arr[scratch_top.old_len], items_len);
-
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASM,
@@ -2156,8 +2188,8 @@ static AstNodeIndex parseAsmExpr(Parser* p) {
.data = {
.lhs = template,
.rhs = addExtra(p,
- (AstNodeIndex[]) { items_span.start,
- items_span.end, OPT(clobbers), rparen },
+ (AstNodeIndex[]) { items_span.start, items_span.end,
+ OPT((AstNodeIndex)0), rparen },
4),
},
});
diff --git a/parser_test.zig b/parser_test.zig
@@ -171,6 +171,7 @@ fn zigNode(token: c_uint) Ast.Node.Tag {
c.AST_NODE_BLOCK => .block,
c.AST_NODE_BLOCK_SEMICOLON => .block_semicolon,
c.AST_NODE_ASM_SIMPLE => .asm_simple,
+ c.AST_NODE_ASM_LEGACY => .asm_legacy,
c.AST_NODE_ASM => .@"asm",
c.AST_NODE_ASM_OUTPUT => .asm_output,
c.AST_NODE_ASM_INPUT => .asm_input,
@@ -569,6 +570,60 @@ test "zig fmt: tuple struct" {
);
}
+test "zig fmt: preserves clobbers in inline asm with stray comma" {
+ try testTransform(
+ \\fn foo() void {
+ \\ asm volatile (""
+ \\ : [_] "" (-> type),
+ \\ :
+ \\ : "clobber"
+ \\ );
+ \\ asm volatile (""
+ \\ :
+ \\ : [_] "" (type),
+ \\ : "clobber"
+ \\ );
+ \\}
+ \\
+ ,
+ \\fn foo() void {
+ \\ asm volatile (""
+ \\ : [_] "" (-> type),
+ \\ :
+ \\ : .{ .clobber = true }
+ \\ );
+ \\ asm volatile (""
+ \\ :
+ \\ : [_] "" (type),
+ \\ : .{ .clobber = true }
+ \\ );
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: remove trailing comma at the end of assembly clobber" {
+ try testTransform(
+ \\fn foo() void {
+ \\ asm volatile (""
+ \\ : [_] "" (-> type),
+ \\ :
+ \\ : "clobber1", "clobber2",
+ \\ );
+ \\}
+ \\
+ ,
+ \\fn foo() void {
+ \\ asm volatile (""
+ \\ : [_] "" (-> type),
+ \\ :
+ \\ : .{ .clobber1 = true, .clobber2 = true }
+ \\ );
+ \\}
+ \\
+ );
+}
+
test "zig fmt: respect line breaks in struct field value declaration" {
try testCanonical(
\\const Foo = struct {