parser: implement asm parsing, port formatting tests

Implement in parser.c:
- parseAsmExpr: asm_simple and asm nodes with outputs, inputs,
  clobbers (including legacy string clobber format)
- parseAsmOutputItem, parseAsmInputItem helper functions

Port tests:
- "preserve spacing"
- "return types"
- "imports"
- "global declarations"
- "extern declaration"
- "function attributes"
- "nested pointers with ** tokens"
- "test declaration"
- "top-level for/while loop"
- Various error set, switch prong, comment tests

Note: asm test cases that require asm_legacy AST node (not yet in
ast.h) are deferred.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-10 21:43:33 +00:00
parent a1fef56b95
commit 97c9fb6378

129
parser.c
View File

@@ -39,6 +39,7 @@ static void parsePtrPayload(Parser*);
static void parsePayload(Parser*);
static AstNodeIndex parseSwitchExpr(Parser*);
static AstNodeIndex parseForExpr(Parser*);
static AstNodeIndex parseAsmExpr(Parser*);
typedef struct {
enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
@@ -1910,6 +1911,130 @@ static AstNodeIndex expectExpr(Parser* p) {
return node;
}
static AstNodeIndex parseAsmOutputItem(Parser* p) {
if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) {
p->tok_i++; // [
const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER);
expectToken(p, TOKEN_R_BRACKET);
expectToken(p, TOKEN_STRING_LITERAL);
expectToken(p, TOKEN_L_PAREN);
AstNodeIndex operand = 0;
if (p->token_tags[p->tok_i] == TOKEN_ARROW) {
p->tok_i++;
operand = parseTypeExpr(p);
}
const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASM_OUTPUT,
.main_token = ident,
.data = { .lhs = operand, .rhs = rparen },
});
}
return null_node;
}
static AstNodeIndex parseAsmInputItem(Parser* p) {
if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) {
p->tok_i++; // [
const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER);
expectToken(p, TOKEN_R_BRACKET);
expectToken(p, TOKEN_STRING_LITERAL);
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex operand = expectExpr(p);
const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASM_INPUT,
.main_token = ident,
.data = { .lhs = operand, .rhs = rparen },
});
}
return null_node;
}
static AstNodeIndex parseAsmExpr(Parser* p) {
const AstTokenIndex asm_token = nextToken(p);
assert(p->token_tags[asm_token] == TOKEN_KEYWORD_ASM);
eatToken(p, TOKEN_KEYWORD_VOLATILE);
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex template = expectExpr(p);
// Simple asm: asm("...")
if (eatToken(p, TOKEN_R_PAREN) != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASM_SIMPLE,
.main_token = asm_token,
.data = { .lhs = template, .rhs = p->tok_i - 1 },
});
}
// Complex asm with outputs, inputs, clobbers
expectToken(p, TOKEN_COLON);
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
// Parse outputs
while (true) {
const AstNodeIndex output = parseAsmOutputItem(p);
if (output == 0)
break;
SLICE_APPEND(AstNodeIndex, &p->scratch, output);
if (eatToken(p, TOKEN_COMMA) == null_token)
break;
}
// Parse inputs (after second colon)
if (eatToken(p, TOKEN_COLON) != null_token) {
while (true) {
const AstNodeIndex input = parseAsmInputItem(p);
if (input == 0)
break;
SLICE_APPEND(AstNodeIndex, &p->scratch, input);
if (eatToken(p, TOKEN_COMMA) == null_token)
break;
}
}
// 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
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,
.main_token = asm_token,
.data = {
.lhs = template,
.rhs = addExtra(p,
(AstNodeIndex[]) { items_span.start,
items_span.end, OPT(clobbers), rparen },
4),
},
});
}
static AstNodeIndex parseSwitchExpr(Parser* p) {
const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
if (switch_token == null_token)
@@ -2072,9 +2197,7 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
const char* tok = tokenizerGetTagString(p->token_tags[p->tok_i]);
switch (p->token_tags[p->tok_i]) {
case TOKEN_KEYWORD_ASM:
fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
exit(1);
break;
return parseAsmExpr(p);
case TOKEN_KEYWORD_IF:
return parseIfExpr(p);
case TOKEN_KEYWORD_BREAK: