Files
bux-lang/src_bux/parser.bux
T
dimgigov d1160ca5d1 Fix critical segfault: add bounds checking to parser diag buffer
The self-hosted compiler crashed with SIGSEGV when processing files
that generated >=256 parser diagnostics (e.g. truncated source files).
The parser allocated a fixed-size diag buffer of 256 entries, but
parserExpect, parserExpectIdentOrKeyword, and parserEmitDiag wrote
into it without checking bounds. Once diagCount exceeded 256, writes
overflowed into adjacent heap objects (Decl structs), corrupting
their kind/childDecl2 fields and causing crashes during AST traversal.

This fix adds bounds checks to all three sites so excess diagnostics
are silently dropped rather than corrupting memory. The file size
threshold (7037 vs 7062 bytes) was a red herring — it determined
how many parser errors were generated before EOF.

Closes: selfhost segfault on 250+ line files
2026-06-02 01:31:18 +03:00

1430 lines
49 KiB
Plaintext

// parser.bux — Recursive descent parser (ported from parser.nim)
// Parses Bux source tokens into an AST.
module Parser {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
struct Parser {
tokens: *LexToken,
tokenCount: int,
pos: int,
diagCount: int,
diags: *ParserDiag,
structInitAllowed: bool,
}
struct ParserDiag {
line: uint32,
column: uint32,
message: String,
}
// ---------------------------------------------------------------------------
// Token helpers
// ---------------------------------------------------------------------------
func parserCurToken(p: *Parser) -> LexToken {
if p.pos < p.tokenCount {
return p.tokens[p.pos];
}
var eof: LexToken;
eof.kind = tkEndOfFile;
eof.text = "";
return eof;
}
func parserPeek(p: *Parser, ahead: int) -> int {
let i: int = p.pos + ahead;
if i >= 0 && i < p.tokenCount {
return p.tokens[i].kind;
}
return tkEndOfFile;
}
func parserAdvance(p: *Parser) -> LexToken {
let tok: LexToken = parserCurToken(p);
if p.pos < p.tokenCount {
p.pos = p.pos + 1;
}
return tok;
}
func parserCheck(p: *Parser, kind: int) -> bool {
return parserPeek(p, 0) == kind;
}
func parserMatch(p: *Parser, kind: int) -> bool {
if parserCheck(p, kind) {
discard parserAdvance(p);
return true;
}
return false;
}
func parserPrevious(p: *Parser) -> LexToken {
if p.pos > 0 && p.pos <= p.tokenCount {
return p.tokens[p.pos - 1];
}
var eof: LexToken;
eof.kind = tkEndOfFile;
return eof;
}
func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
if parserCheck(p, kind) {
return parserAdvance(p);
}
let tok: LexToken = parserCurToken(p);
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
p.diags[p.diagCount] = ParserDiag {
line: line, column: col, message: msg
};
p.diagCount = p.diagCount + 1;
}
func parserIsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
return false;
}
func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
let tok: LexToken = parserCurToken(p);
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
return parserAdvance(p);
}
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
// ---------------------------------------------------------------------------
// Type parsing
// ---------------------------------------------------------------------------
func parserParseType(p: *Parser) -> *TypeExpr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let kindTok: int = parserPeek(p, 0);
// *T (pointer)
if kindTok == tkStar {
discard parserAdvance(p);
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekPointer;
te.line = line;
te.column = col;
te.pointerPointee = parserParseType(p);
// Set typeName to "Pointee*"
if te.pointerPointee != null as *TypeExpr {
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
}
return te;
}
// name
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.line = nameTok.line;
te.column = nameTok.column;
te.typeName = nameTok.text;
// Optional <T, U> type args
if parserCheck(p, tkLt) {
discard parserAdvance(p); // <
let arg0: LexToken = parserExpect(p, tkIdent, "expected type argument");
te.typeArgName0 = arg0.text;
te.typeArgCount = 1;
if parserMatch(p, tkComma) {
let arg1: LexToken = parserExpect(p, tkIdent, "expected type argument");
te.typeArgName1 = arg1.text;
te.typeArgCount = 2;
}
discard parserExpect(p, tkGt, "expected '>' to close type arguments");
}
return te;
}
// ---------------------------------------------------------------------------
// Forward declarations and helpers
// ---------------------------------------------------------------------------
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
e.kind = kind;
e.line = line;
e.column = col;
e.strValue = "";
e.intValue = 0;
e.boolValue = false;
e.tokKind = 0;
e.tokText = "";
e.child1 = null as *Expr;
e.child2 = null as *Expr;
e.child3 = null as *Expr;
e.refType = null as *TypeExpr;
e.refBlock = null as *Block;
e.genericCallee = "";
e.genericTypeArg0 = "";
e.genericTypeArg1 = "";
e.genericTypeArgCount = 0;
e.structName = "";
e.structFieldCount = 0;
return e;
}
// ---------------------------------------------------------------------------
// Primary expressions
// ---------------------------------------------------------------------------
func parserParsePrimary(p: *Parser) -> *Expr {
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// Literals
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|| kind == tkCharLiteral || kind == tkBoolLiteral {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.tokKind = kind;
e.tokText = tok.text;
return e;
}
// Identifier
if kind == tkIdent {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekIdent, line, col);
e.strValue = tok.text;
return e;
}
// self
if kind == tkSelf {
discard parserAdvance(p);
return parserMakeExpr(ekSelf, line, col);
}
// null
if kind == tkNull {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.tokKind = tkNull;
return e;
}
// sizeof(Type)
if kind == tkSizeOf {
discard parserAdvance(p);
discard parserExpect(p, tkLParen, "expected '(' after sizeof");
let e: *Expr = parserMakeExpr(ekSizeOf, line, col);
e.refType = parserParseType(p);
discard parserExpect(p, tkRParen, "expected ')' after sizeof type");
return e;
}
// spawn Callee(args)
if kind == tkSpawn {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekSpawn, line, col);
e.child1 = parserParsePrimary(p);
// Optional call arguments
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
if !parserCheck(p, tkRParen) {
e.child2 = parserParseExpr(p);
if parserMatch(p, tkComma) {
e.child3 = parserParseExpr(p);
while parserMatch(p, tkComma) {
discard parserParseExpr(p);
}
}
}
discard parserExpect(p, tkRParen, "expected ')' after spawn arguments");
}
return e;
}
// #intrinsics
if kind >= tkHashLine && kind <= tkHashModule {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.intValue = kind;
return e;
}
// ( expr )
if kind == tkLParen {
discard parserAdvance(p);
let e: *Expr = parserParseExpr(p);
discard parserExpect(p, tkRParen, "expected ')'");
return e;
}
// -expr, !expr, *expr, &expr
if kind == tkMinus || kind == tkBang || kind == tkStar || kind == tkAmp {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekUnary, line, col);
e.intValue = kind;
e.child1 = parserParsePrimary(p);
return e;
}
parserEmitDiag(p, line, col, "expected expression");
return parserMakeExpr(ekLiteral, line, col);
}
// ---------------------------------------------------------------------------
// Postfix: call, index, field access, as, is, ?
// ---------------------------------------------------------------------------
func parserParsePostfix(p: *Parser) -> *Expr {
var left: *Expr = parserParsePrimary(p);
while true {
let kind: int = parserPeek(p, 0);
// Call: expr(args)
if kind == tkLParen {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekCall, line, col);
e.child1 = left;
// Parse arguments (up to 8)
// child2 = first arg, child3 = second arg, extra = rest
if !parserCheck(p, tkRParen) {
e.child2 = parserParseExpr(p);
if parserMatch(p, tkComma) {
e.child3 = parserParseExpr(p);
// Consume any remaining arguments (we only store 2)
while parserMatch(p, tkComma) {
discard parserParseExpr(p);
}
}
}
discard parserExpect(p, tkRParen, "expected ')'");
left = e;
continue;
}
// Index: expr[expr]
if kind == tkLBracket {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekIndex, line, col);
e.child1 = left;
e.child2 = parserParseExpr(p);
discard parserExpect(p, tkRBracket, "expected ']'");
left = e;
continue;
}
// .await
if kind == tkDot {
if parserPeek(p, 1) == tkAwait {
discard parserAdvance(p); // .
discard parserAdvance(p); // await
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekAwait, line, col);
e.child1 = left;
left = e;
continue;
}
}
// Field: expr.name
if kind == tkDot {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let name: LexToken = parserExpect(p, tkIdent, "expected field name");
let e: *Expr = parserMakeExpr(ekField, line, col);
e.child1 = left;
e.strValue = name.text;
left = e;
continue;
}
// as, is
if kind == tkAs || kind == tkIs {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let ek: int = 0;
if kind == tkAs { ek = ekCast; } else { ek = ekIs; }
let e: *Expr = parserMakeExpr(ek, line, col);
e.child1 = left;
e.refType = parserParseType(p);
left = e;
continue;
}
// ? (try operator)
if kind == tkQuestion {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekTry, line, col);
e.child1 = left;
left = e;
continue;
}
// ! (unwrap operator)
if kind == tkBang {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekUnwrap, line, col);
e.child1 = left;
left = e;
continue;
}
// ++, --
if kind == tkPlusPlus || kind == tkMinusMinus {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekPostfix, line, col);
e.child1 = left;
e.intValue = kind;
left = e;
continue;
}
// Struct init: TypeName { field: value, ... }
if kind == tkLBrace {
if p.structInitAllowed && left.kind == ekIdent {
discard parserAdvance(p); // consume {
let siLine: uint32 = parserCurToken(p).line;
let siCol: uint32 = parserCurToken(p).column;
let typeName: String = left.strValue;
var fieldCount: int = 0;
var firstField: *Expr = null as *Expr;
var lastField: *Expr = null as *Expr;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
let sip: int = p.pos;
let fName: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
discard parserExpect(p, tkColon, "expected ':'");
let fValue: *Expr = parserParseExpr(p);
let fExpr: *Expr = parserMakeExpr(ekField, fName.line, fName.column);
fExpr.strValue = fName.text;
fExpr.child1 = fValue;
if firstField == null as *Expr {
firstField = fExpr;
lastField = fExpr;
} else {
lastField.child3 = fExpr;
lastField = fExpr;
}
fieldCount = fieldCount + 1;
parserMatch(p, tkComma);
if p.pos == sip { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}'");
let e: *Expr = parserMakeExpr(ekStructInit, siLine, siCol);
e.structName = typeName;
e.structFieldCount = fieldCount;
e.child1 = firstField;
left = e;
continue;
} else {
break;
}
}
break;
}
return left;
}
// ---------------------------------------------------------------------------
// Binary expression (precedence climbing)
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
// ---------------------------------------------------------------------------
func parserIsBinaryOp(kind: int) -> bool {
if kind >= tkPlus && kind <= tkStarStar { return true; } // arithmetic
if kind == tkAmp || kind == tkPipe || kind == tkCaret { return true; } // bitwise (no &|)
if kind >= tkShl && kind <= tkShrAssign { return true; } // shift + assign
if kind >= tkEq && kind <= tkGe { return true; } // comparison
if kind == tkAmpAmp || kind == tkPipePipe { return true; } // logical
if kind >= tkAssign && kind <= tkShrAssign { return true; } // all assigns
if kind == tkDotDot || kind == tkDotDotEqual { return true; } // range
return false;
}
func parserParseBinary(p: *Parser) -> *Expr {
var left: *Expr = parserParsePostfix(p);
while parserIsBinaryOp(parserPeek(p, 0)) {
let opTok: LexToken = parserAdvance(p);
let line: uint32 = opTok.line;
let col: uint32 = opTok.column;
let right: *Expr = parserParsePostfix(p);
let e: *Expr = parserMakeExpr(ekBinary, line, col);
e.intValue = opTok.kind;
e.child1 = left;
e.child2 = right;
left = e;
}
return left;
}
// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------
func parserParseTernary(p: *Parser) -> *Expr {
var left: *Expr = parserParseBinary(p);
if parserMatch(p, tkQuestion) {
let thenExpr: *Expr = parserParseExpr(p);
discard parserExpect(p, tkColon, "expected ':' in ternary");
let elseExpr: *Expr = parserParseExpr(p);
let e: *Expr = parserMakeExpr(ekTernary, left.line, left.column);
e.child1 = left;
e.child2 = thenExpr;
e.child3 = elseExpr;
return e;
}
return left;
}
// ---------------------------------------------------------------------------
// Top-level expression
// ---------------------------------------------------------------------------
func parserParseExpr(p: *Parser) -> *Expr {
return parserParseTernary(p);
}
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
func parserParseStmt(p: *Parser) -> *Stmt {
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// let / var
if kind == tkLet || kind == tkVar {
let isVar: bool = (kind == tkVar);
discard parserAdvance(p);
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected variable name");
var typeExpr: *TypeExpr = null as *TypeExpr;
if parserMatch(p, tkColon) {
typeExpr = parserParseType(p);
}
discard parserExpect(p, tkAssign, "expected '=' in let/var statement");
let init: *Expr = parserParseExpr(p);
parserMatch(p, tkSemicolon); // optional ;
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skLet;
s.line = line;
s.column = col;
s.strValue = nameTok.text;
s.boolValue = isVar;
s.child1 = init;
s.refStmtType = typeExpr;
return s;
}
// if
if kind == tkIf {
discard parserAdvance(p);
p.structInitAllowed = false;
let cond: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let thenBlock: *Block = parserParseBlock(p);
var elseBlock: *Block = null as *Block;
if parserMatch(p, tkElse) {
if parserCheck(p, tkIf) {
// else if → parse the if statement, wrap in a synthetic block
let innerIf: *Stmt = parserParseStmt(p);
elseBlock = bux_alloc(sizeof(Block)) as *Block;
elseBlock.line = innerIf.line;
elseBlock.column = innerIf.column;
elseBlock.stmtCount = 1;
elseBlock.firstStmt = innerIf;
elseBlock.lastStmt = innerIf;
innerIf.nextStmt = null as *Stmt;
} else {
elseBlock = parserParseBlock(p);
}
}
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skIf;
s.line = line;
s.column = col;
s.child1 = cond;
s.refStmtBlock = thenBlock;
s.refStmtElse = elseBlock;
return s;
}
// while
if kind == tkWhile {
discard parserAdvance(p);
p.structInitAllowed = false;
let cond: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let body: *Block = parserParseBlock(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skWhile;
s.line = line;
s.column = col;
s.child1 = cond;
s.refStmtBlock = body;
return s;
}
// loop
if kind == tkLoop {
discard parserAdvance(p);
let body: *Block = parserParseBlock(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skLoop;
s.line = line;
s.column = col;
s.refStmtBlock = body;
return s;
}
// for
if kind == tkFor {
discard parserAdvance(p);
let varName: LexToken = parserExpect(p, tkIdent, "expected loop variable");
discard parserExpect(p, tkIn, "expected 'in'");
p.structInitAllowed = false;
let iter: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
let body: *Block = parserParseBlock(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skFor;
s.line = line;
s.column = col;
s.strValue = varName.text;
s.child1 = iter;
s.refStmtBlock = body;
return s;
}
// return
if kind == tkReturn {
discard parserAdvance(p);
var value: *Expr = null as *Expr;
if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkNewLine) && !parserCheck(p, tkRBrace) {
value = parserParseExpr(p);
}
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skReturn;
s.line = line;
s.column = col;
s.child1 = value;
return s;
}
// break / continue
if kind == tkBreak || kind == tkContinue {
let sk: int = 0;
if kind == tkBreak { sk = skBreak; } else { sk = skContinue; }
discard parserAdvance(p);
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = sk;
s.line = line;
s.column = col;
return s;
}
// match
if kind == tkMatch {
discard parserAdvance(p);
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
// Skip match body (simplified — just parse arms as empty)
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
let mp: int = p.pos;
discard parserParseExpr(p); // pattern
if parserMatch(p, tkFatArrow) {
discard parserParseExpr(p); // body
}
if p.pos == mp { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}' to close match");
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skMatch;
s.line = line;
s.column = col;
s.child1 = subject;
return s;
}
// Expression statement
if kind == tkNewLine || kind == tkSemicolon {
discard parserAdvance(p);
return null as *Stmt;
}
let expr: *Expr = parserParseExpr(p);
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skExpr;
s.line = line;
s.column = col;
s.child1 = expr;
return s;
}
// ---------------------------------------------------------------------------
// Block: { stmt* }
// ---------------------------------------------------------------------------
func parserParseBlock(p: *Parser) -> *Block {
discard parserExpect(p, tkLBrace, "expected '{'");
let b: *Block = bux_alloc(sizeof(Block)) as *Block;
b.line = parserCurToken(p).line;
b.column = parserCurToken(p).column;
b.stmtCount = 0;
b.firstStmt = null as *Stmt;
b.lastStmt = null as *Stmt;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
continue;
}
let beforePos: int = p.pos;
let s: *Stmt = parserParseStmt(p);
// Infinite-loop safeguard: if no progress, advance past current token
if p.pos == beforePos {
discard parserAdvance(p);
continue;
}
if s != null as *Stmt {
s.nextStmt = null as *Stmt;
if b.firstStmt == null as *Stmt {
b.firstStmt = s;
b.lastStmt = s;
} else {
b.lastStmt.nextStmt = s;
b.lastStmt = s;
}
b.stmtCount = b.stmtCount + 1;
}
}
discard parserExpect(p, tkRBrace, "expected '}'");
return b;
}
// ---------------------------------------------------------------------------
// Function parameters
// ---------------------------------------------------------------------------
func parserParseParamList(p: *Parser) -> *Decl {
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkFunc;
d.paramCount = 0;
discard parserExpect(p, tkLParen, "expected '('");
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
if d.paramCount >= 6 { break; }
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
discard parserExpect(p, tkColon, "expected ':' in parameter");
let ptype: *TypeExpr = parserParseType(p);
if d.paramCount == 0 {
d.param0.name = nameTok.text;
d.param0.refParamType = ptype;
} else if d.paramCount == 1 {
d.param1.name = nameTok.text;
d.param1.refParamType = ptype;
} else if d.paramCount == 2 {
d.param2.name = nameTok.text;
d.param2.refParamType = ptype;
} else if d.paramCount == 3 {
d.param3.name = nameTok.text;
d.param3.refParamType = ptype;
} else if d.paramCount == 4 {
d.param4.name = nameTok.text;
d.param4.refParamType = ptype;
} else if d.paramCount == 5 {
d.param5.name = nameTok.text;
d.param5.refParamType = ptype;
}
d.paramCount = d.paramCount + 1;
if parserMatch(p, tkComma) { continue; }
break;
}
discard parserExpect(p, tkRParen, "expected ')'");
return d;
}
// ---------------------------------------------------------------------------
// Declarations
// ---------------------------------------------------------------------------
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkFunc, "expected 'func'");
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected function name");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkFunc;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.isAsync = isAsync;
d.strValue = nameTok.text;
// Type params <T, U>
if parserCheck(p, tkLt) {
discard parserAdvance(p);
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam0 = tp0.text;
d.typeParamCount = 1;
if parserMatch(p, tkComma) {
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam1 = tp1.text;
d.typeParamCount = 2;
}
discard parserExpect(p, tkGt, "expected '>'");
}
// Params
let params: *Decl = parserParseParamList(p);
d.paramCount = params.paramCount;
d.param0 = params.param0;
d.param1 = params.param1;
d.param2 = params.param2;
d.param3 = params.param3;
d.param4 = params.param4;
d.param5 = params.param5;
// Return type
if parserMatch(p, tkArrow) {
d.retType = parserParseType(p);
}
// Body
if !isExtern && parserCheck(p, tkLBrace) {
d.refBody = parserParseBlock(p);
} else {
d.refBody = null as *Block;
}
return d;
}
func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkStruct, "expected 'struct'");
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected struct name");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkStruct;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = nameTok.text;
// Type params
if parserCheck(p, tkLt) {
discard parserAdvance(p);
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam0 = tp0.text;
d.typeParamCount = 1;
if parserMatch(p, tkComma) {
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam1 = tp1.text;
d.typeParamCount = 2;
}
discard parserExpect(p, tkGt, "expected '>'");
}
discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if d.fieldCount >= 64 { break; }
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
let beforePos: int = p.pos;
let fName: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
discard parserExpect(p, tkColon, "expected ':' in struct field");
let fType: *TypeExpr = parserParseType(p);
parserMatch(p, tkSemicolon);
// Infinite-loop safeguard
if p.pos == beforePos {
discard parserAdvance(p);
continue;
}
if d.fieldCount == 0 {
d.field0.name = fName.text;
d.field0.refFieldType = fType;
} else if d.fieldCount == 1 {
d.field1.name = fName.text;
d.field1.refFieldType = fType;
} else if d.fieldCount == 2 {
d.field2.name = fName.text;
d.field2.refFieldType = fType;
} else if d.fieldCount == 3 {
d.field3.name = fName.text;
d.field3.refFieldType = fType;
} else if d.fieldCount == 4 {
d.field4.name = fName.text;
d.field4.refFieldType = fType;
} else if d.fieldCount == 5 {
d.field5.name = fName.text;
d.field5.refFieldType = fType;
} else if d.fieldCount == 6 {
d.field6.name = fName.text;
d.field6.refFieldType = fType;
} else if d.fieldCount == 7 {
d.field7.name = fName.text;
d.field7.refFieldType = fType;
} else if d.fieldCount == 8 {
d.field8.name = fName.text;
d.field8.refFieldType = fType;
} else if d.fieldCount == 9 {
d.field9.name = fName.text;
d.field9.refFieldType = fType;
} else if d.fieldCount == 10 {
d.field10.name = fName.text;
d.field10.refFieldType = fType;
} else if d.fieldCount == 11 {
d.field11.name = fName.text;
d.field11.refFieldType = fType;
} else if d.fieldCount == 12 {
d.field12.name = fName.text;
d.field12.refFieldType = fType;
} else if d.fieldCount == 13 {
d.field13.name = fName.text;
d.field13.refFieldType = fType;
} else if d.fieldCount == 14 {
d.field14.name = fName.text;
d.field14.refFieldType = fType;
} else if d.fieldCount == 15 {
d.field15.name = fName.text;
d.field15.refFieldType = fType;
} else if d.fieldCount == 16 {
d.field16.name = fName.text;
d.field16.refFieldType = fType;
} else if d.fieldCount == 17 {
d.field17.name = fName.text;
d.field17.refFieldType = fType;
} else if d.fieldCount == 18 {
d.field18.name = fName.text;
d.field18.refFieldType = fType;
} else if d.fieldCount == 19 {
d.field19.name = fName.text;
d.field19.refFieldType = fType;
} else if d.fieldCount == 20 {
d.field20.name = fName.text;
d.field20.refFieldType = fType;
} else if d.fieldCount == 21 {
d.field21.name = fName.text;
d.field21.refFieldType = fType;
} else if d.fieldCount == 22 {
d.field22.name = fName.text;
d.field22.refFieldType = fType;
} else if d.fieldCount == 23 {
d.field23.name = fName.text;
d.field23.refFieldType = fType;
} else if d.fieldCount == 24 {
d.field24.name = fName.text;
d.field24.refFieldType = fType;
} else if d.fieldCount == 25 {
d.field25.name = fName.text;
d.field25.refFieldType = fType;
} else if d.fieldCount == 26 {
d.field26.name = fName.text;
d.field26.refFieldType = fType;
} else if d.fieldCount == 27 {
d.field27.name = fName.text;
d.field27.refFieldType = fType;
} else if d.fieldCount == 28 {
d.field28.name = fName.text;
d.field28.refFieldType = fType;
} else if d.fieldCount == 29 {
d.field29.name = fName.text;
d.field29.refFieldType = fType;
} else if d.fieldCount == 30 {
d.field30.name = fName.text;
d.field30.refFieldType = fType;
} else if d.fieldCount == 31 {
d.field31.name = fName.text;
d.field31.refFieldType = fType;
} else if d.fieldCount == 32 {
d.field32.name = fName.text;
d.field32.refFieldType = fType;
} else if d.fieldCount == 33 {
d.field33.name = fName.text;
d.field33.refFieldType = fType;
} else if d.fieldCount == 34 {
d.field34.name = fName.text;
d.field34.refFieldType = fType;
} else if d.fieldCount == 35 {
d.field35.name = fName.text;
d.field35.refFieldType = fType;
} else if d.fieldCount == 36 {
d.field36.name = fName.text;
d.field36.refFieldType = fType;
} else if d.fieldCount == 37 {
d.field37.name = fName.text;
d.field37.refFieldType = fType;
} else if d.fieldCount == 38 {
d.field38.name = fName.text;
d.field38.refFieldType = fType;
} else if d.fieldCount == 39 {
d.field39.name = fName.text;
d.field39.refFieldType = fType;
} else if d.fieldCount == 40 {
d.field40.name = fName.text;
d.field40.refFieldType = fType;
} else if d.fieldCount == 41 {
d.field41.name = fName.text;
d.field41.refFieldType = fType;
} else if d.fieldCount == 42 {
d.field42.name = fName.text;
d.field42.refFieldType = fType;
} else if d.fieldCount == 43 {
d.field43.name = fName.text;
d.field43.refFieldType = fType;
} else if d.fieldCount == 44 {
d.field44.name = fName.text;
d.field44.refFieldType = fType;
} else if d.fieldCount == 45 {
d.field45.name = fName.text;
d.field45.refFieldType = fType;
} else if d.fieldCount == 46 {
d.field46.name = fName.text;
d.field46.refFieldType = fType;
} else if d.fieldCount == 47 {
d.field47.name = fName.text;
d.field47.refFieldType = fType;
} else if d.fieldCount == 48 {
d.field48.name = fName.text;
d.field48.refFieldType = fType;
} else if d.fieldCount == 49 {
d.field49.name = fName.text;
d.field49.refFieldType = fType;
} else if d.fieldCount == 50 {
d.field50.name = fName.text;
d.field50.refFieldType = fType;
} else if d.fieldCount == 51 {
d.field51.name = fName.text;
d.field51.refFieldType = fType;
} else if d.fieldCount == 52 {
d.field52.name = fName.text;
d.field52.refFieldType = fType;
} else if d.fieldCount == 53 {
d.field53.name = fName.text;
d.field53.refFieldType = fType;
} else if d.fieldCount == 54 {
d.field54.name = fName.text;
d.field54.refFieldType = fType;
} else if d.fieldCount == 55 {
d.field55.name = fName.text;
d.field55.refFieldType = fType;
} else if d.fieldCount == 56 {
d.field56.name = fName.text;
d.field56.refFieldType = fType;
} else if d.fieldCount == 57 {
d.field57.name = fName.text;
d.field57.refFieldType = fType;
} else if d.fieldCount == 58 {
d.field58.name = fName.text;
d.field58.refFieldType = fType;
} else if d.fieldCount == 59 {
d.field59.name = fName.text;
d.field59.refFieldType = fType;
} else if d.fieldCount == 60 {
d.field60.name = fName.text;
d.field60.refFieldType = fType;
} else if d.fieldCount == 61 {
d.field61.name = fName.text;
d.field61.refFieldType = fType;
} else if d.fieldCount == 62 {
d.field62.name = fName.text;
d.field62.refFieldType = fType;
} else if d.fieldCount == 63 {
d.field63.name = fName.text;
d.field63.refFieldType = fType;
}
d.fieldCount = d.fieldCount + 1;
}
discard parserExpect(p, tkRBrace, "expected '}'");
return d;
}
func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkEnum, "expected 'enum'");
let nameTok: LexToken = parserExpect(p, tkIdent, "expected enum name");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkEnum;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = nameTok.text;
discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if d.variantCount >= 8 { break; }
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
let vName: LexToken = parserExpect(p, tkIdent, "expected variant name");
var v: EnumVariant;
v.name = vName.text;
// Optional (Type, Type) data
if parserMatch(p, tkLParen) {
let t0: LexToken = parserExpect(p, tkIdent, "expected data type");
v.fieldTypeName0 = t0.text;
v.fieldCount = 1;
if parserMatch(p, tkComma) {
let t1: LexToken = parserExpect(p, tkIdent, "expected data type");
v.fieldTypeName1 = t1.text;
v.fieldCount = 2;
}
discard parserExpect(p, tkRParen, "expected ')'");
}
if d.variantCount == 0 { d.variant0 = v; }
else if d.variantCount == 1 { d.variant1 = v; }
else if d.variantCount == 2 { d.variant2 = v; }
else if d.variantCount == 3 { d.variant3 = v; }
d.variantCount = d.variantCount + 1;
parserMatch(p, tkComma);
if parserCheck(p, tkNewLine) { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}'");
return d;
}
func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkImport, "expected 'import'");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkUse;
d.line = line;
d.column = col;
d.isPublic = isPublic;
// Parse path: Std::Io::PrintLine
var pathStr: String = "";
var segCount: int = 0;
while parserCheck(p, tkIdent) || (segCount > 0 && parserCheck(p, tkColonColon) && parserPeek(p, 1) != tkLBrace) {
if segCount > 0 {
discard parserAdvance(p); // ::
if String_Len(pathStr) > 0 {
let tmp: *char8 = bux_alloc(256) as *char8;
// Append to path string (simplified)
pathStr = String_Concat(pathStr, "::");
}
}
let seg: LexToken = parserExpect(p, tkIdent, "expected module path segment");
pathStr = String_Concat(pathStr, seg.text);
segCount = segCount + 1;
}
d.usePath = pathStr;
// Optional ::{name1, name2}
if parserMatch(p, tkColonColon) && parserCheck(p, tkLBrace) {
discard parserAdvance(p); // {
var names: String = "";
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
let n: LexToken = parserExpect(p, tkIdent, "expected import name");
names = String_Concat(names, n.text);
names = String_Concat(names, ",");
if !parserMatch(p, tkComma) { break; }
}
discard parserExpect(p, tkRBrace, "expected '}'");
d.useNames = names;
d.useKind = 2; // ukMulti
} else {
d.useKind = 0; // ukSingle
}
parserMatch(p, tkSemicolon);
return d;
}
func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkExtern, "expected 'extern'");
if parserCheck(p, tkFunc) {
let d: *Decl = parserParseFuncDecl(p, isPublic, true, false);
d.kind = dkExternFunc;
return d;
}
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkExternFunc;
d.line = line;
d.column = col;
d.isPublic = isPublic;
return d;
}
// ---------------------------------------------------------------------------
// Top-level declaration
// ---------------------------------------------------------------------------
func parserParseDecl(p: *Parser) -> *Decl {
let isPublic: bool = parserMatch(p, tkPub);
let kind: int = parserPeek(p, 0);
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // async
return parserParseFuncDecl(p, isPublic, false, true);
}
if kind == tkFunc { return parserParseFuncDecl(p, isPublic, false, false); }
if kind == tkStruct { return parserParseStructDecl(p, isPublic); }
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
if kind == tkImport { return parserParseImportDecl(p, isPublic); }
if kind == tkExtern { return parserParseExternDecl(p, isPublic); }
if kind == tkExtend {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let typeName: LexToken = parserExpect(p, tkIdent, "expected type name");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkImpl;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = typeName.text;
// Optional <T>
if parserCheck(p, tkLt) {
discard parserAdvance(p);
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam0 = tp0.text;
d.typeParamCount = 1;
discard parserExpect(p, tkGt, "expected '>'");
}
discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
if parserCheck(p, tkFunc) {
let m: *Decl = parserParseFuncDecl(p, false, false, false);
// Store method in linked list
if d.methodCount == 0 {
d.childDecl1 = m;
} else if d.methodCount == 1 {
d.childDecl2 = m;
}
d.methodCount = d.methodCount + 1;
} else {
break;
}
}
discard parserExpect(p, tkRBrace, "expected '}'");
return d;
}
if kind == tkModule {
discard parserAdvance(p);
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
// Parse optional path segments (::Name)
while parserCheck(p, tkColonColon) {
discard parserAdvance(p);
discard parserExpect(p, tkIdent, "expected module path segment");
}
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkModule;
d.strValue = name.text;
// Parse module body with braces
if parserCheck(p, tkLBrace) {
discard parserAdvance(p); // consume {
var items: *Decl = null as *Decl;
var lastItem: *Decl = null as *Decl;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
continue;
}
let beforePos: int = p.pos;
let item: *Decl = parserParseDecl(p);
if item != null as *Decl {
item.childDecl2 = null as *Decl;
if items == null as *Decl {
items = item;
lastItem = item;
} else {
lastItem.childDecl2 = item;
lastItem = item;
}
}
// Infinite-loop safeguard: if no progress, skip token
if p.pos == beforePos {
discard parserAdvance(p);
}
}
parserMatch(p, tkRBrace); // consume }
d.childDecl1 = items; // first item of module
} else {
parserMatch(p, tkSemicolon);
}
return d;
}
if kind == tkConst {
discard parserAdvance(p);
let name: LexToken = parserExpect(p, tkIdent, "expected const name");
discard parserExpect(p, tkColon, "expected ':'");
let ct: *TypeExpr = parserParseType(p);
discard parserExpect(p, tkAssign, "expected '='");
let val: *Expr = parserParseExpr(p);
parserMatch(p, tkSemicolon);
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkConst;
d.strValue = name.text;
d.constType = ct;
d.constValue = val;
return d;
}
// Skip unknown declarations
while parserPeek(p, 0) != tkEndOfFile && parserPeek(p, 0) != tkNewLine && parserPeek(p, 0) != tkRBrace {
discard parserAdvance(p);
}
return null as *Decl;
}
// ---------------------------------------------------------------------------
// Module parsing
// ---------------------------------------------------------------------------
func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
let p: *Parser = bux_alloc(sizeof(Parser)) as *Parser;
p.tokens = tokens;
p.tokenCount = tokenCount;
p.pos = 0;
p.structInitAllowed = true;
let diagBuf: *ParserDiag = bux_alloc(256 as uint * sizeof(ParserDiag)) as *ParserDiag;
p.diags = diagBuf;
p.diagCount = 0;
let mod: *Module = bux_alloc(sizeof(Module)) as *Module;
mod.name = "";
mod.itemCount = 0;
mod.firstItem = null as *Decl;
// Parse declarations until EOF
while parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
continue;
}
let beforePos: int = p.pos;
let decl: *Decl = parserParseDecl(p);
if decl != null as *Decl {
decl.childDecl2 = mod.firstItem; // push front
mod.firstItem = decl;
mod.itemCount = mod.itemCount + 1;
}
// Infinite-loop safeguard: if no progress, skip token
if p.pos == beforePos {
discard parserAdvance(p);
}
}
return mod;
}
func Parser_DiagCount(p: *Parser) -> int {
return p.diagCount;
}
func Parser_Free(p: *Parser) {
bux_free(p.diags as *void);
bux_free(p as *void);
}
// ---------------------------------------------------------------------------
// Module merging — merge multiple parsed modules into one
// ---------------------------------------------------------------------------
// Add a single declaration to the merged module (push front to linked list)
func parserMergeAddDecl(mod: *Module, decl: *Decl) {
if decl == null as *Decl { return; }
// Flatten module declarations: extract inner items
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let next: *Decl = inner.childDecl2;
inner.childDecl2 = mod.firstItem;
mod.firstItem = inner;
mod.itemCount = mod.itemCount + 1;
inner = next;
}
} else {
decl.childDecl2 = mod.firstItem;
mod.firstItem = decl;
mod.itemCount = mod.itemCount + 1;
}
}
func Parser_MergeModules(modules: *void, count: int) -> *Module {
// This function is kept for future use but not called by Cli_BuildProject
// which does inline merging for simplicity
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
merged.name = "main";
merged.path = "";
merged.itemCount = 0;
merged.firstItem = null as *Decl;
return merged;
}
}