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>
This commit is contained in:
2026-02-10 22:41:53 +00:00
parent c5915c06fb
commit 383fe83626
3 changed files with 100 additions and 10 deletions

3
ast.h
View File

@@ -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.

View File

@@ -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),
},
});

View File

@@ -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 {