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:
3
ast.h
3
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.
|
||||
|
||||
50
parser.c
50
parser.c
@@ -2127,28 +2127,41 @@ 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) {
|
||||
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,
|
||||
@@ -2161,6 +2174,25 @@ static AstNodeIndex parseAsmExpr(Parser* p) {
|
||||
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,
|
||||
.main_token = asm_token,
|
||||
.data = {
|
||||
.lhs = template,
|
||||
.rhs = addExtra(p,
|
||||
(AstNodeIndex[]) { items_span.start, items_span.end,
|
||||
OPT((AstNodeIndex)0), rparen },
|
||||
4),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static AstNodeIndex parseSwitchExpr(Parser* p) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user