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:
129
parser.c
129
parser.c
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user