commit 97c9fb637842d9dfde367ea455d99f52a95ea503 (tree)
parent a1fef56b951c0dd5645e6dd20830009a29031ae2
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 21:43:33 +0000
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>
Diffstat:
| M | parser.c | | | 129 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
1 file changed, 126 insertions(+), 3 deletions(-)
diff --git a/parser.c b/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: