par2serial-cc / src /parser.c
clarenceleo's picture
Add parser.c - complete recursive-descent parser for C + parallel extensions
d779f5b verified
Raw
History Blame Contribute Delete
31.1 kB
/*
* par2serial-cc: parser.c - Recursive-descent parser
* Parses C subset + parallel extensions into AST
*/
#include "parser.h"
/* ── Parser helpers ──────────────────────────────────────── */
#define CUR(p) ((p)->tokens[(p)->pos])
#define CURTYPE(p) (CUR(p).type)
#define IS(p, t) (CURTYPE(p) == (t))
#define LOC(p) (CUR(p).loc)
static Token eat(Parser *p) {
Token t = CUR(p);
if (p->pos < p->count - 1) p->pos++;
return t;
}
static bool check(Parser *p, TokenType t) {
return CURTYPE(p) == t;
}
static bool match_tok(Parser *p, TokenType t) {
if (check(p, t)) { eat(p); return true; }
return false;
}
static Token expect(Parser *p, TokenType t) {
if (check(p, t)) return eat(p);
p2s_error(LOC(p), "expected '%s', got '%s' (\"%.*s\")",
token_type_str(t), token_type_str(CURTYPE(p)),
(int)CUR(p).text_len, CUR(p).text);
p->error_count++;
return CUR(p);
}
static bool is_type_token(Parser *p) {
TokenType t = CURTYPE(p);
return t == TOK_VOID || t == TOK_CHAR || t == TOK_SHORT ||
t == TOK_INT || t == TOK_LONG || t == TOK_FLOAT ||
t == TOK_DOUBLE || t == TOK_UNSIGNED || t == TOK_SIGNED ||
t == TOK_STRUCT || t == TOK_UNION || t == TOK_ENUM ||
t == TOK_CONST || t == TOK_VOLATILE || t == TOK_STATIC ||
t == TOK_EXTERN || t == TOK_INLINE || t == TOK_REGISTER ||
t == TOK_RESTRICT || t == TOK_TYPEDEF;
}
/* ── Type parsing ────────────────────────────────────────── */
static ASTType *parse_type(Parser *p);
static ASTNode *parse_expr(Parser *p);
static ASTNode *parse_assign_expr(Parser *p);
static ASTNode *parse_stmt(Parser *p);
static ASTNode *parse_block(Parser *p);
static ASTNode *parse_decl_or_stmt(Parser *p);
static ASTType *parse_base_type(Parser *p) {
ASTType *ty = ast_new_type(p->arena, TYPE_INT); /* default */
bool has_type = false;
/* qualifiers and storage class */
while (true) {
if (match_tok(p, TOK_CONST)) { ty->is_const = true; continue; }
if (match_tok(p, TOK_VOLATILE)) { ty->is_volatile = true; continue; }
if (match_tok(p, TOK_STATIC)) { ty->is_static = true; continue; }
if (match_tok(p, TOK_EXTERN)) { continue; }
if (match_tok(p, TOK_INLINE)) { continue; }
if (match_tok(p, TOK_REGISTER)) { continue; }
if (match_tok(p, TOK_RESTRICT)) { ty->is_restrict = true; continue; }
if (match_tok(p, TOK_TYPEDEF)) { continue; }
break;
}
/* base type */
if (match_tok(p, TOK_VOID)) { ty->kind = TYPE_VOID; has_type = true; }
else if (match_tok(p, TOK_CHAR)) { ty->kind = TYPE_CHAR; has_type = true; }
else if (match_tok(p, TOK_SHORT)) { ty->kind = TYPE_SHORT; has_type = true; }
else if (match_tok(p, TOK_INT)) { ty->kind = TYPE_INT; has_type = true; }
else if (match_tok(p, TOK_LONG)) { ty->kind = TYPE_LONG; has_type = true; }
else if (match_tok(p, TOK_FLOAT)) { ty->kind = TYPE_FLOAT; has_type = true; }
else if (match_tok(p, TOK_DOUBLE)) { ty->kind = TYPE_DOUBLE; has_type = true; }
else if (match_tok(p, TOK_UNSIGNED)){
if (match_tok(p, TOK_CHAR)) ty->kind = TYPE_UNSIGNED_CHAR;
else if (match_tok(p, TOK_SHORT)) ty->kind = TYPE_UNSIGNED_SHORT;
else if (match_tok(p, TOK_INT)) ty->kind = TYPE_UNSIGNED_INT;
else if (match_tok(p, TOK_LONG)) ty->kind = TYPE_UNSIGNED_LONG;
else ty->kind = TYPE_UNSIGNED_INT;
has_type = true;
}
else if (match_tok(p, TOK_SIGNED)) {
if (match_tok(p, TOK_CHAR)) ty->kind = TYPE_CHAR;
else if (match_tok(p, TOK_SHORT)) ty->kind = TYPE_SHORT;
else if (match_tok(p, TOK_INT)) ty->kind = TYPE_INT;
else if (match_tok(p, TOK_LONG)) ty->kind = TYPE_LONG;
else ty->kind = TYPE_INT;
has_type = true;
}
else if (check(p, TOK_STRUCT) || check(p, TOK_UNION)) {
bool is_union = check(p, TOK_UNION);
eat(p);
ty->kind = is_union ? TYPE_UNION : TYPE_STRUCT;
if (check(p, TOK_IDENT)) {
ty->name = arena_strdup(p->arena, CUR(p).text);
eat(p);
}
has_type = true;
/* skip struct body if present */
if (check(p, TOK_LBRACE)) {
eat(p);
int depth = 1;
while (depth > 0 && !IS(p, TOK_EOF)) {
if (check(p, TOK_LBRACE)) depth++;
if (check(p, TOK_RBRACE)) depth--;
eat(p);
}
}
}
else if (check(p, TOK_ENUM)) {
eat(p);
ty->kind = TYPE_ENUM;
if (check(p, TOK_IDENT)) { ty->name = arena_strdup(p->arena, CUR(p).text); eat(p); }
if (check(p, TOK_LBRACE)) {
eat(p);
while (!check(p, TOK_RBRACE) && !IS(p, TOK_EOF)) eat(p);
expect(p, TOK_RBRACE);
}
has_type = true;
}
else if (check(p, TOK_IDENT)) {
/* typedef name - treat as int for now */
ty->kind = TYPE_INT;
ty->name = arena_strdup(p->arena, CUR(p).text);
eat(p);
has_type = true;
}
if (!has_type) ty->kind = TYPE_INT;
/* trailing long */
while (match_tok(p, TOK_LONG)) { ty->kind = TYPE_LONG; }
return ty;
}
static ASTType *parse_type(Parser *p) {
ASTType *base = parse_base_type(p);
/* pointer */
while (check(p, TOK_STAR)) {
eat(p);
ASTType *ptr = ast_ptr_type(p->arena, base);
while (match_tok(p, TOK_CONST)) ptr->is_const = true;
while (match_tok(p, TOK_RESTRICT)) ptr->is_restrict = true;
base = ptr;
}
return base;
}
/* ── Expression parsing (precedence climbing) ────────────── */
static ASTNode *parse_primary(Parser *p) {
SourceLoc loc = LOC(p);
if (check(p, TOK_INT_LIT)) {
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_INT_LIT, loc);
n->int_lit.value = t.int_val;
return n;
}
if (check(p, TOK_FLOAT_LIT)) {
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_FLOAT_LIT, loc);
n->float_lit.value = t.float_val;
return n;
}
if (check(p, TOK_STRING_LIT)) {
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_STRING_LIT, loc);
n->string_lit.value = t.text;
return n;
}
if (check(p, TOK_CHAR_LIT)) {
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_CHAR_LIT, loc);
n->char_lit.value = t.int_val;
return n;
}
if (check(p, TOK_IDENT)) {
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_IDENT, loc);
n->ident.name = t.text;
return n;
}
if (match_tok(p, TOK_LPAREN)) {
/* check for cast: (type)expr */
if (is_type_token(p)) {
ASTType *ty = parse_type(p);
expect(p, TOK_RPAREN);
ASTNode *expr = parse_primary(p); /* unary precedence */
ASTNode *n = ast_new(p->arena, NODE_CAST, loc);
n->cast.target_type = ty;
n->cast.expr = expr;
return n;
}
ASTNode *expr = parse_expr(p);
expect(p, TOK_RPAREN);
return expr;
}
if (check(p, TOK_SIZEOF)) {
eat(p);
ASTNode *n = ast_new(p->arena, NODE_SIZEOF, loc);
if (match_tok(p, TOK_LPAREN)) {
if (is_type_token(p)) {
n->size_of.of_type = parse_type(p);
} else {
n->size_of.of_expr = parse_expr(p);
}
expect(p, TOK_RPAREN);
} else {
n->size_of.of_expr = parse_primary(p);
}
return n;
}
p2s_error(loc, "expected expression, got '%s'", token_type_str(CURTYPE(p)));
p->error_count++;
eat(p);
return ast_new(p->arena, NODE_INT_LIT, loc); /* error recovery */
}
static ASTNode *parse_postfix(Parser *p) {
ASTNode *expr = parse_primary(p);
while (true) {
SourceLoc loc = LOC(p);
if (match_tok(p, TOK_LBRACKET)) {
ASTNode *idx = parse_expr(p);
expect(p, TOK_RBRACKET);
ASTNode *n = ast_new(p->arena, NODE_INDEX, loc);
n->index.array = expr;
n->index.index = idx;
expr = n;
}
else if (match_tok(p, TOK_LPAREN)) {
/* function call */
ASTNode *n = ast_new(p->arena, NODE_CALL, loc);
n->call.func = expr;
vec_init(&n->call.args);
if (!check(p, TOK_RPAREN)) {
vec_push(&n->call.args, parse_assign_expr(p));
while (match_tok(p, TOK_COMMA))
vec_push(&n->call.args, parse_assign_expr(p));
}
expect(p, TOK_RPAREN);
expr = n;
}
else if (match_tok(p, TOK_DOT)) {
Token field = expect(p, TOK_IDENT);
ASTNode *n = ast_new(p->arena, NODE_MEMBER, loc);
n->member.object = expr;
n->member.field = field.text;
expr = n;
}
else if (match_tok(p, TOK_ARROW)) {
Token field = expect(p, TOK_IDENT);
ASTNode *n = ast_new(p->arena, NODE_ARROW, loc);
n->member.object = expr;
n->member.field = field.text;
expr = n;
}
else if (match_tok(p, TOK_INC)) {
ASTNode *n = ast_new(p->arena, NODE_POSTFIX, loc);
n->postfix.op = OP_POST_INC;
n->postfix.operand = expr;
expr = n;
}
else if (match_tok(p, TOK_DEC)) {
ASTNode *n = ast_new(p->arena, NODE_POSTFIX, loc);
n->postfix.op = OP_POST_DEC;
n->postfix.operand = expr;
expr = n;
}
else break;
}
return expr;
}
static ASTNode *parse_unary(Parser *p) {
SourceLoc loc = LOC(p);
if (match_tok(p, TOK_INC)) {
ASTNode *n = ast_new(p->arena, NODE_UNARY, loc);
n->unary.op = OP_INC;
n->unary.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_DEC)) {
ASTNode *n = ast_new(p->arena, NODE_UNARY, loc);
n->unary.op = OP_DEC;
n->unary.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_MINUS)) {
ASTNode *n = ast_new(p->arena, NODE_UNARY, loc);
n->unary.op = OP_NEG;
n->unary.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_PLUS)) {
return parse_unary(p);
}
if (match_tok(p, TOK_BANG)) {
ASTNode *n = ast_new(p->arena, NODE_UNARY, loc);
n->unary.op = OP_LNOT;
n->unary.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_TILDE)) {
ASTNode *n = ast_new(p->arena, NODE_UNARY, loc);
n->unary.op = OP_NOT;
n->unary.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_AMP)) {
ASTNode *n = ast_new(p->arena, NODE_ADDR_OF, loc);
n->unary_expr.operand = parse_unary(p);
return n;
}
if (match_tok(p, TOK_STAR)) {
ASTNode *n = ast_new(p->arena, NODE_DEREF, loc);
n->unary_expr.operand = parse_unary(p);
return n;
}
return parse_postfix(p);
}
/* Binary expression parsing with precedence */
static int get_precedence(TokenType t) {
switch (t) {
case TOK_STAR: case TOK_SLASH: case TOK_PERCENT: return 13;
case TOK_PLUS: case TOK_MINUS: return 12;
case TOK_LSHIFT: case TOK_RSHIFT: return 11;
case TOK_LT: case TOK_GT: case TOK_LE: case TOK_GE: return 10;
case TOK_EQ: case TOK_NEQ: return 9;
case TOK_AMP: return 8;
case TOK_CARET: return 7;
case TOK_PIPE: return 6;
case TOK_AND: return 5;
case TOK_OR: return 4;
default: return -1;
}
}
static OpType token_to_binop(TokenType t) {
switch (t) {
case TOK_PLUS: return OP_ADD;
case TOK_MINUS: return OP_SUB;
case TOK_STAR: return OP_MUL;
case TOK_SLASH: return OP_DIV;
case TOK_PERCENT: return OP_MOD;
case TOK_AMP: return OP_AND;
case TOK_PIPE: return OP_OR;
case TOK_CARET: return OP_XOR;
case TOK_LSHIFT: return OP_LSHIFT;
case TOK_RSHIFT: return OP_RSHIFT;
case TOK_EQ: return OP_EQ;
case TOK_NEQ: return OP_NEQ;
case TOK_LT: return OP_LT;
case TOK_GT: return OP_GT;
case TOK_LE: return OP_LE;
case TOK_GE: return OP_GE;
case TOK_AND: return OP_LAND;
case TOK_OR: return OP_LOR;
default: return OP_ADD; /* unreachable */
}
}
static ASTNode *parse_binary(Parser *p, int min_prec) {
ASTNode *left = parse_unary(p);
while (true) {
int prec = get_precedence(CURTYPE(p));
if (prec < min_prec) break;
SourceLoc loc = LOC(p);
OpType op = token_to_binop(CURTYPE(p));
eat(p);
ASTNode *right = parse_binary(p, prec + 1);
ASTNode *n = ast_new(p->arena, NODE_BINARY, loc);
n->binary.op = op;
n->binary.left = left;
n->binary.right = right;
left = n;
}
return left;
}
static ASTNode *parse_ternary(Parser *p) {
ASTNode *cond = parse_binary(p, 0);
if (match_tok(p, TOK_QUESTION)) {
SourceLoc loc = LOC(p);
ASTNode *then_expr = parse_expr(p);
expect(p, TOK_COLON);
ASTNode *else_expr = parse_ternary(p);
ASTNode *n = ast_new(p->arena, NODE_TERNARY, loc);
n->ternary.cond = cond;
n->ternary.then_expr = then_expr;
n->ternary.else_expr = else_expr;
return n;
}
return cond;
}
static OpType assign_op(TokenType t) {
switch (t) {
case TOK_ASSIGN: return OP_ASSIGN;
case TOK_PLUS_EQ: return OP_ADD_ASSIGN;
case TOK_MINUS_EQ: return OP_SUB_ASSIGN;
case TOK_STAR_EQ: return OP_MUL_ASSIGN;
case TOK_SLASH_EQ: return OP_DIV_ASSIGN;
case TOK_PERCENT_EQ: return OP_MOD_ASSIGN;
case TOK_AMP_EQ: return OP_AND_ASSIGN;
case TOK_PIPE_EQ: return OP_OR_ASSIGN;
case TOK_CARET_EQ: return OP_XOR_ASSIGN;
case TOK_LSHIFT_EQ: return OP_LSHIFT_ASSIGN;
case TOK_RSHIFT_EQ: return OP_RSHIFT_ASSIGN;
default: return (OpType)-1;
}
}
static bool is_assign_token(TokenType t) {
return t == TOK_ASSIGN || t == TOK_PLUS_EQ || t == TOK_MINUS_EQ ||
t == TOK_STAR_EQ || t == TOK_SLASH_EQ || t == TOK_PERCENT_EQ ||
t == TOK_AMP_EQ || t == TOK_PIPE_EQ || t == TOK_CARET_EQ ||
t == TOK_LSHIFT_EQ || t == TOK_RSHIFT_EQ;
}
static ASTNode *parse_assign_expr(Parser *p) {
ASTNode *left = parse_ternary(p);
if (is_assign_token(CURTYPE(p))) {
SourceLoc loc = LOC(p);
OpType op = assign_op(CURTYPE(p));
eat(p);
ASTNode *right = parse_assign_expr(p);
ASTNode *n = ast_new(p->arena, NODE_ASSIGN, loc);
n->assign.op = op;
n->assign.target = left;
n->assign.value = right;
return n;
}
return left;
}
static ASTNode *parse_expr(Parser *p) {
ASTNode *left = parse_assign_expr(p);
while (match_tok(p, TOK_COMMA)) {
SourceLoc loc = LOC(p);
ASTNode *right = parse_assign_expr(p);
ASTNode *n = ast_new(p->arena, NODE_COMMA_EXPR, loc);
n->binary.op = OP_COMMA;
n->binary.left = left;
n->binary.right = right;
left = n;
}
return left;
}
/* ── Parallel construct parsing ──────────────────────────── */
static ReduceOp parse_reduce_op(Parser *p) {
if (check(p, TOK_PLUS)) { eat(p); return REDUCE_ADD; }
if (check(p, TOK_STAR)) { eat(p); return REDUCE_MUL; }
if (check(p, TOK_AMP)) { eat(p); return REDUCE_AND; }
if (check(p, TOK_PIPE)) { eat(p); return REDUCE_OR; }
if (check(p, TOK_CARET)) { eat(p); return REDUCE_XOR; }
if (check(p, TOK_IDENT)) {
if (strcmp(CUR(p).text, "min") == 0) { eat(p); return REDUCE_MIN; }
if (strcmp(CUR(p).text, "max") == 0) { eat(p); return REDUCE_MAX; }
}
p2s_error(LOC(p), "expected reduction operator (+, *, &, |, ^, min, max)");
p->error_count++;
return REDUCE_ADD;
}
static ASTNode *parse_parallel_for(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* parallel_for */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_PARALLEL_FOR, loc);
Token var = expect(p, TOK_IDENT);
n->par_for.iter_var = var.text;
expect(p, TOK_COMMA);
n->par_for.lo = parse_expr(p);
expect(p, TOK_COMMA);
n->par_for.hi = parse_expr(p);
/* optional step */
if (match_tok(p, TOK_COMMA)) {
n->par_for.step = parse_expr(p);
}
expect(p, TOK_RPAREN);
n->par_for.body = parse_block(p);
return n;
}
static ASTNode *parse_parallel_reduce(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* parallel_reduce */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_PARALLEL_REDUCE, loc);
Token acc = expect(p, TOK_IDENT);
n->par_reduce.accum_var = acc.text;
expect(p, TOK_COMMA);
n->par_reduce.op = parse_reduce_op(p);
expect(p, TOK_COMMA);
Token ivar = expect(p, TOK_IDENT);
n->par_reduce.iter_var = ivar.text;
expect(p, TOK_COMMA);
n->par_reduce.lo = parse_expr(p);
expect(p, TOK_COMMA);
n->par_reduce.hi = parse_expr(p);
expect(p, TOK_RPAREN);
n->par_reduce.body = parse_block(p);
return n;
}
static ASTNode *parse_parallel_scan(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* parallel_scan */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_PARALLEL_SCAN, loc);
Token out = expect(p, TOK_IDENT);
n->par_scan.output_var = out.text;
expect(p, TOK_COMMA);
n->par_scan.op = parse_reduce_op(p);
expect(p, TOK_COMMA);
Token ivar = expect(p, TOK_IDENT);
n->par_scan.iter_var = ivar.text;
expect(p, TOK_COMMA);
n->par_scan.lo = parse_expr(p);
expect(p, TOK_COMMA);
n->par_scan.hi = parse_expr(p);
expect(p, TOK_RPAREN);
n->par_scan.body = parse_block(p);
return n;
}
static ASTNode *parse_parallel_map(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* parallel_map */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_PARALLEL_MAP, loc);
Token dst = expect(p, TOK_IDENT);
n->par_map.dst = dst.text;
expect(p, TOK_COMMA);
Token src = expect(p, TOK_IDENT);
n->par_map.src = src.text;
expect(p, TOK_COMMA);
n->par_map.count = parse_expr(p);
if (match_tok(p, TOK_COMMA)) {
Token fn = expect(p, TOK_IDENT);
n->par_map.func_name = fn.text;
}
expect(p, TOK_RPAREN);
n->par_map.body = parse_block(p);
return n;
}
static ASTNode *parse_tile_hint(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* tile_hint */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_TILE_HINT, loc);
Token tgt = expect(p, TOK_IDENT);
n->tile_hint.target = tgt.text;
expect(p, TOK_COMMA);
Token sz = expect(p, TOK_INT_LIT);
n->tile_hint.tile_size = (int)sz.int_val;
expect(p, TOK_RPAREN);
expect(p, TOK_SEMICOLON);
return n;
}
static ASTNode *parse_simd_hint(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* simd_hint */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_SIMD_HINT, loc);
Token tgt = expect(p, TOK_IDENT);
n->simd_hint.target = tgt.text;
expect(p, TOK_RPAREN);
expect(p, TOK_SEMICOLON);
return n;
}
static ASTNode *parse_memory_layout(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* memory_layout */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_MEMORY_LAYOUT, loc);
Token tgt = expect(p, TOK_IDENT);
n->mem_layout.target = tgt.text;
expect(p, TOK_COMMA);
Token layout = expect(p, TOK_IDENT);
n->mem_layout.soa = (strcmp(layout.text, "SOA") == 0 || strcmp(layout.text, "soa") == 0);
expect(p, TOK_RPAREN);
expect(p, TOK_SEMICOLON);
return n;
}
static ASTNode *parse_barrier(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* barrier */
expect(p, TOK_LPAREN);
expect(p, TOK_RPAREN);
expect(p, TOK_SEMICOLON);
return ast_new(p->arena, NODE_BARRIER, loc);
}
/* ── Statement parsing ───────────────────────────────────── */
static ASTNode *parse_block(Parser *p) {
SourceLoc loc = LOC(p);
expect(p, TOK_LBRACE);
ASTNode *n = ast_new(p->arena, NODE_BLOCK, loc);
vec_init(&n->block.stmts);
while (!check(p, TOK_RBRACE) && !IS(p, TOK_EOF)) {
vec_push(&n->block.stmts, parse_decl_or_stmt(p));
}
expect(p, TOK_RBRACE);
return n;
}
static ASTNode *parse_if(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* if */
expect(p, TOK_LPAREN);
ASTNode *cond = parse_expr(p);
expect(p, TOK_RPAREN);
ASTNode *n = ast_new(p->arena, NODE_IF, loc);
n->if_stmt.cond = cond;
n->if_stmt.then_body = parse_stmt(p);
if (match_tok(p, TOK_ELSE))
n->if_stmt.else_body = parse_stmt(p);
return n;
}
static ASTNode *parse_while(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* while */
expect(p, TOK_LPAREN);
ASTNode *cond = parse_expr(p);
expect(p, TOK_RPAREN);
ASTNode *n = ast_new(p->arena, NODE_WHILE, loc);
n->while_stmt.cond = cond;
n->while_stmt.body = parse_stmt(p);
return n;
}
static ASTNode *parse_do_while(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* do */
ASTNode *n = ast_new(p->arena, NODE_DO_WHILE, loc);
n->while_stmt.body = parse_stmt(p);
expect(p, TOK_WHILE);
expect(p, TOK_LPAREN);
n->while_stmt.cond = parse_expr(p);
expect(p, TOK_RPAREN);
expect(p, TOK_SEMICOLON);
return n;
}
static ASTNode *parse_for(Parser *p) {
SourceLoc loc = LOC(p);
eat(p); /* for */
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_FOR, loc);
/* init */
if (!check(p, TOK_SEMICOLON)) {
if (is_type_token(p)) {
/* variable declaration in init */
ASTType *ty = parse_type(p);
Token name = expect(p, TOK_IDENT);
ASTNode *decl = ast_new(p->arena, NODE_VAR_DECL, loc);
decl->var.name = name.text;
decl->var.var_type = ty;
if (match_tok(p, TOK_ASSIGN))
decl->var.init = parse_assign_expr(p);
n->for_stmt.init = decl;
} else {
n->for_stmt.init = parse_expr(p);
}
}
expect(p, TOK_SEMICOLON);
if (!check(p, TOK_SEMICOLON))
n->for_stmt.cond = parse_expr(p);
expect(p, TOK_SEMICOLON);
if (!check(p, TOK_RPAREN))
n->for_stmt.step = parse_expr(p);
expect(p, TOK_RPAREN);
n->for_stmt.body = parse_stmt(p);
return n;
}
static ASTNode *parse_return(Parser *p) {
SourceLoc loc = LOC(p);
eat(p);
ASTNode *n = ast_new(p->arena, NODE_RETURN, loc);
if (!check(p, TOK_SEMICOLON))
n->ret.value = parse_expr(p);
expect(p, TOK_SEMICOLON);
return n;
}
static ASTNode *parse_stmt(Parser *p) {
switch (CURTYPE(p)) {
case TOK_LBRACE: return parse_block(p);
case TOK_IF: return parse_if(p);
case TOK_WHILE: return parse_while(p);
case TOK_DO: return parse_do_while(p);
case TOK_FOR: return parse_for(p);
case TOK_RETURN: return parse_return(p);
case TOK_PARALLEL_FOR: return parse_parallel_for(p);
case TOK_PARALLEL_REDUCE: return parse_parallel_reduce(p);
case TOK_PARALLEL_SCAN: return parse_parallel_scan(p);
case TOK_PARALLEL_MAP: return parse_parallel_map(p);
case TOK_BARRIER: return parse_barrier(p);
case TOK_TILE_HINT: return parse_tile_hint(p);
case TOK_SIMD_HINT: return parse_simd_hint(p);
case TOK_MEMORY_LAYOUT: return parse_memory_layout(p);
case TOK_BREAK:
eat(p); expect(p, TOK_SEMICOLON);
return ast_new(p->arena, NODE_BREAK, LOC(p));
case TOK_CONTINUE:
eat(p); expect(p, TOK_SEMICOLON);
return ast_new(p->arena, NODE_CONTINUE, LOC(p));
default: {
SourceLoc loc = LOC(p);
ASTNode *expr = parse_expr(p);
expect(p, TOK_SEMICOLON);
ASTNode *n = ast_new(p->arena, NODE_EXPR_STMT, loc);
n->expr_stmt.expr = expr;
return n;
}
}
}
/* ── Declaration parsing ─────────────────────────────────── */
static ASTNode *parse_var_decl(Parser *p, ASTType *type) {
SourceLoc loc = LOC(p);
Token name = expect(p, TOK_IDENT);
ASTNode *n = ast_new(p->arena, NODE_VAR_DECL, loc);
n->var.name = name.text;
n->var.var_type = type;
/* array dimension */
while (match_tok(p, TOK_LBRACKET)) {
if (check(p, TOK_RBRACKET)) {
type = ast_array_type(p->arena, type, -1);
} else {
Token sz = expect(p, TOK_INT_LIT);
type = ast_array_type(p->arena, type, (int)sz.int_val);
}
expect(p, TOK_RBRACKET);
n->var.var_type = type;
}
/* initializer */
if (match_tok(p, TOK_ASSIGN))
n->var.init = parse_assign_expr(p);
return n;
}
static ASTNode *parse_func_decl(Parser *p, ASTType *ret_type, const char *name) {
SourceLoc loc = LOC(p);
expect(p, TOK_LPAREN);
ASTNode *n = ast_new(p->arena, NODE_FUNC_DECL, loc);
n->func.name = name;
n->func.return_type = ret_type;
vec_init(&n->func.params);
if (!check(p, TOK_RPAREN)) {
if (check(p, TOK_VOID) && p->tokens[p->pos + 1].type == TOK_RPAREN) {
eat(p); /* void params */
} else {
do {
if (check(p, TOK_ELLIPSIS)) { eat(p); break; }
ASTType *ptype = parse_type(p);
ASTNode *param = ast_new(p->arena, NODE_PARAM, LOC(p));
if (check(p, TOK_IDENT)) {
param->param.name = CUR(p).text;
eat(p);
/* array parameter */
while (match_tok(p, TOK_LBRACKET)) {
ptype = ast_ptr_type(p->arena, ptype);
if (!check(p, TOK_RBRACKET))
parse_expr(p); /* skip size */
expect(p, TOK_RBRACKET);
}
}
param->param.param_type = ptype;
vec_push(&n->func.params, param);
} while (match_tok(p, TOK_COMMA));
}
}
expect(p, TOK_RPAREN);
/* function body or just declaration */
if (check(p, TOK_LBRACE)) {
n->func.body = parse_block(p);
} else {
expect(p, TOK_SEMICOLON);
}
return n;
}
static ASTNode *parse_decl_or_stmt(Parser *p) {
/* preprocessor directives */
if (check(p, TOK_HASH)) {
SourceLoc loc = LOC(p);
Token t = eat(p);
ASTNode *n = ast_new(p->arena, NODE_PREPROC, loc);
n->preproc.text = t.text;
return n;
}
/* Parallel constructs */
switch (CURTYPE(p)) {
case TOK_PARALLEL_FOR: return parse_parallel_for(p);
case TOK_PARALLEL_REDUCE: return parse_parallel_reduce(p);
case TOK_PARALLEL_SCAN: return parse_parallel_scan(p);
case TOK_PARALLEL_MAP: return parse_parallel_map(p);
case TOK_BARRIER: return parse_barrier(p);
case TOK_TILE_HINT: return parse_tile_hint(p);
case TOK_SIMD_HINT: return parse_simd_hint(p);
case TOK_MEMORY_LAYOUT: return parse_memory_layout(p);
default: break;
}
/* Check if this looks like a declaration (type + name) */
if (is_type_token(p)) {
ASTType *type = parse_type(p);
/* struct/union declaration without variable */
if (check(p, TOK_SEMICOLON)) {
eat(p);
ASTNode *n = ast_new(p->arena, NODE_VAR_DECL, LOC(p));
n->var.var_type = type;
n->var.name = "";
return n;
}
if (!check(p, TOK_IDENT)) {
/* might be a statement like `(type)expr;` - fallback */
return parse_stmt(p);
}
Token name = eat(p);
/* Function declaration? */
if (check(p, TOK_LPAREN)) {
/* Look ahead: is this func(params) or func_call() ? */
/* If preceded by a type, it's a declaration */
return parse_func_decl(p, type, name.text);
}
/* Variable declaration */
/* Put back to re-parse as var decl is tricky, we handle inline */
ASTNode *first = ast_new(p->arena, NODE_VAR_DECL, LOC(p));
first->var.name = name.text;
first->var.var_type = type;
/* array */
while (match_tok(p, TOK_LBRACKET)) {
if (check(p, TOK_RBRACKET)) {
type = ast_array_type(p->arena, type, -1);
} else {
ASTNode *sz = parse_expr(p);
int arr_sz = -1;
if (sz->type == NODE_INT_LIT) arr_sz = (int)sz->int_lit.value;
type = ast_array_type(p->arena, type, arr_sz);
}
expect(p, TOK_RBRACKET);
first->var.var_type = type;
}
if (match_tok(p, TOK_ASSIGN))
first->var.init = parse_assign_expr(p);
/* multiple declarations: int a, b, c; */
/* For simplicity, just handle single for now */
expect(p, TOK_SEMICOLON);
return first;
}
return parse_stmt(p);
}
/* ── Top-level parser ────────────────────────────────────── */
Parser *parser_create(Arena *arena, Token *tokens, size_t count, const char *filename) {
Parser *p = (Parser *)arena_alloc(arena, sizeof(Parser));
p->tokens = tokens;
p->count = count;
p->pos = 0;
p->arena = arena;
p->error_count = 0;
p->filename = filename;
return p;
}
ASTNode *parser_parse(Parser *p) {
ASTNode *prog = ast_new(p->arena, NODE_PROGRAM, LOC(p));
vec_init(&prog->program.decls);
while (!IS(p, TOK_EOF)) {
ASTNode *decl = parse_decl_or_stmt(p);
if (decl) vec_push(&prog->program.decls, decl);
if (p->error_count > 20) {
p2s_fatal("too many errors, aborting");
break;
}
}
return prog;
}