Files
bux-lang/src/parser.bux
T
dimgigov fe3b1e8b6a
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
Sessions 56–69: declarative macro! with rep/zip/literal/block and
unhygienic var $name binders; partial field-move skip Drop; @[Release]
polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
2026-07-20 17:19:46 +03:00

2747 lines
110 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;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
// Forward declarations for mutual recursion
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseUnary(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
func parserParsePattern(p: *Parser) -> *Pattern;
func parserParseMatchExpr(p: *Parser) -> *Expr;
// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
struct Parser {
tokens: *LexToken,
tokenCount: int,
pos: int,
diagCount: int,
diags: *ParserDiag,
structInitAllowed: bool,
macroTemplateMode: bool, // allows $(…)* in macro! bodies
}
struct ParserDiag {
line: uint32,
column: uint32,
message: String,
severity: int, /* 0=error (fatal), 1=warning (recoverable) */
}
// ---------------------------------------------------------------------------
// 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;
}
// Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
// Must not treat value comparisons `x < 0` as generics when a later `x > 0`
// exists (e.g. multiple match arm guards).
func parserIsTypeArgListAhead(p: *Parser) -> bool {
if !parserCheck(p, tkLt) { return false; }
var depth: int = 0;
var ahead: int = 0;
while true {
let kind: int = parserPeek(p, ahead);
// Hard stops: cannot appear inside <...> type args
if kind == tkEndOfFile || kind == tkLBrace || kind == tkRBrace || kind == tkSemicolon
|| kind == tkFatArrow || kind == tkIf || kind == tkElse || kind == tkWhile
|| kind == tkFor || kind == tkMatch || kind == tkReturn || kind == tkLet || kind == tkVar
|| kind == tkEq || kind == tkNe || kind == tkLe || kind == tkGe
|| kind == tkAmpAmp || kind == tkPipePipe || kind == tkAssign {
return false;
}
// Literals / arithmetic ⇒ value expression, not type args
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|| kind == tkCharLiteral || kind == tkBoolLiteral
|| kind == tkPlus || kind == tkMinus || kind == tkSlash || kind == tkPercent {
return false;
}
if kind == tkLt {
depth = depth + 1;
} else if kind == tkGt {
depth = depth - 1;
if depth == 0 {
return true;
}
}
ahead = ahead + 1;
}
return false;
}
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 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, severity: 1
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: line, column: col, message: msg, severity: 1
};
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, severity: 1
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
// ---------------------------------------------------------------------------
// Type parsing
// ---------------------------------------------------------------------------
// C-friendly type name from a TypeExpr (named, tuple, pointer, …).
func parserTypeExprCName(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return "int"; }
if te.kind == tekTuple {
if !String_Eq(te.typeName, "") { return te.typeName; }
return "Tuple_Empty";
}
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef {
if te.pointerPointee != null as *TypeExpr {
return String_Concat(parserTypeExprCName(te.pointerPointee), "*");
}
return "void*";
}
if !String_Eq(te.typeName, "") {
if String_Eq(te.typeName, "String") || String_Eq(te.typeName, "str") {
return "const char*";
}
return te.typeName;
}
return "int";
}
func parserParseType(p: *Parser) -> *TypeExpr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let kindTok: int = parserPeek(p, 0);
// &T / &'a T (shared) and &mut T / &'a mut T (mutable)
if kindTok == tkAmp {
discard parserAdvance(p); // &
var lt: String = "";
// Optional lifetime: &'a or &'a mut
if parserCheck(p, tkLifetime) {
let ltTok: LexToken = parserCurToken(p);
lt = ltTok.text;
discard parserAdvance(p);
}
var isMut: bool = false;
// Check for "mut" keyword
if parserCheck(p, tkIdent) {
let tok: LexToken = parserCurToken(p);
if String_Eq(tok.text, "mut") {
isMut = true;
discard parserAdvance(p); // mut
}
}
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
if isMut {
te.kind = tekMutRef;
} else {
te.kind = tekRef;
}
te.line = line;
te.column = col;
te.refLifetime = lt;
te.pointerPointee = parserParseType(p);
if te.pointerPointee != null as *TypeExpr {
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
}
return te;
}
// *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;
}
// func(Params) -> Ret
if kindTok == tkFunc {
discard parserAdvance(p);
discard parserExpect(p, tkLParen, "expected '(' after 'func'");
var params: *TypeExprList = null as *TypeExprList;
var paramsTail: *TypeExprList = null as *TypeExprList;
var count: int = 0;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let paramTe: *TypeExpr = parserParseType(p);
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
node.te = paramTe;
node.next = null as *TypeExprList;
if params == null as *TypeExprList {
params = node;
} else {
paramsTail.next = node;
}
paramsTail = node;
count = count + 1;
if parserCheck(p, tkComma) {
discard parserAdvance(p);
}
}
discard parserExpect(p, tkRParen, "expected ')' after func params");
var ret: *TypeExpr = null as *TypeExpr;
if parserCheck(p, tkArrow) {
discard parserAdvance(p);
ret = parserParseType(p);
}
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekFunc;
te.line = line;
te.column = col;
te.funcParams = params;
te.funcRet = ret;
te.funcParamCount = count;
return te;
}
// (T, U, ...) tuple type
if kindTok == tkLParen {
discard parserAdvance(p);
var elems: *TypeExprList = null as *TypeExprList;
var elemsTail: *TypeExprList = null as *TypeExprList;
var count: int = 0;
var typeName: String = "Tuple";
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let elemTe: *TypeExpr = parserParseType(p);
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
node.te = elemTe;
node.next = null as *TypeExprList;
if elems == null as *TypeExprList {
elems = node;
} else {
elemsTail.next = node;
}
elemsTail = node;
count = count + 1;
// Build mangled name: Tuple_int_int
var part: String = "int";
if elemTe != null as *TypeExpr {
if !String_Eq(elemTe.typeName, "") {
part = elemTe.typeName;
} else if elemTe.kind == tekPointer && elemTe.pointerPointee != null as *TypeExpr {
part = String_Concat(elemTe.pointerPointee.typeName, "Ptr");
}
}
if String_Eq(part, "String") || String_Eq(part, "str") { part = "cstr"; }
typeName = String_Concat(typeName, "_");
typeName = String_Concat(typeName, part);
if parserCheck(p, tkComma) {
discard parserAdvance(p);
} else {
break;
}
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple type");
if count == 0 {
typeName = "Tuple_Empty";
}
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekTuple;
te.line = line;
te.column = col;
te.tupleElems = elems;
te.tupleCount = count;
te.typeName = typeName;
return te;
}
// name
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
// self / Self -> tekSelf
if String_Eq(nameTok.text, "self") || String_Eq(nameTok.text, "Self") {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekSelf;
te.line = nameTok.line;
te.column = nameTok.column;
te.typeName = "Self";
return te;
}
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;
e.callArgs = null as *ExprList;
e.callArgCount = 0;
e.matchArms = null as *MatchArm;
e.matchArmCount = 0;
return e;
}
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.tokKind = tkStringLiteral;
e.tokText = quoted;
return e;
}
func parserParseInterpFragment(exprStr: String) -> *Expr {
let lex: *Lexer = Lexer_Tokenize(exprStr);
var sub: Parser;
sub.tokens = lex.tokens;
sub.tokenCount = lex.tokenCount;
sub.pos = 0;
sub.diagCount = 0;
sub.diags = null as *ParserDiag;
sub.structInitAllowed = true;
return parserParseExpr(&sub);
}
func parserAppendPart(head: *ExprList, tail: *ExprList, e: *Expr) -> *ExprList {
// returns new tail; head updated via pointer trick not possible — return pair as side effect on first arg using double pointer?
// simpler: just inline in main
return tail;
}
func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
let text: String = tok.text;
let tlen: uint = bux_strlen(text);
if tlen < 3 as uint {
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
e.tokKind = tkStringLiteral;
e.tokText = text;
return e;
}
if text[0] as int != 102 {
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
e.tokKind = tkStringLiteral;
e.tokText = text;
return e;
}
if text[1] as int != 34 {
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
e.tokKind = tkStringLiteral;
e.tokText = text;
return e;
}
let inner: String = bux_str_slice(text, 2, tlen - 3);
let innerLen: uint = bux_strlen(inner);
var head: *ExprList = null as *ExprList;
var tail: *ExprList = null as *ExprList;
var currentText: String = "";
var i: uint = 0;
var partCount: int = 0;
while i < innerLen {
let ch: int = inner[i] as int;
var handled: bool = false;
if ch == 92 {
if i + 1 < innerLen {
let nch: int = inner[i + 1] as int;
if nch == 123 {
currentText = String_Concat(currentText, "{");
i = i + 2;
handled = true;
} else {
if nch == 125 {
currentText = String_Concat(currentText, "}");
i = i + 2;
handled = true;
}
}
}
}
if handled {
continue;
}
if ch == 123 {
let textPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
let textNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
textNode.expr = textPart;
textNode.next = null as *ExprList;
textNode.argName = "";
if head == null as *ExprList {
head = textNode;
tail = textNode;
} else {
tail.next = textNode;
tail = textNode;
}
partCount = partCount + 1;
currentText = "";
var j: uint = i + 1;
var depth: int = 1;
while j < innerLen {
if depth <= 0 {
break;
}
let cj: int = inner[j] as int;
if cj == 123 {
depth = depth + 1;
}
if cj == 125 {
depth = depth - 1;
}
j = j + 1;
}
if depth != 0 {
parserEmitDiag(p, tok.line, tok.column, "unmatched brace in string interpolation");
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
bad.tokKind = tkStringLiteral;
bad.tokText = "\"\"";
return bad;
}
let exprLen: uint = j - i - 2;
let exprStr: String = bux_str_slice(inner, i + 1, exprLen);
if bux_strlen(exprStr) == 0 {
parserEmitDiag(p, tok.line, tok.column, "empty interpolation");
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
bad.tokKind = tkStringLiteral;
bad.tokText = "\"\"";
return bad;
}
let frag: *Expr = parserParseInterpFragment(exprStr);
let fragNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
fragNode.expr = frag;
fragNode.next = null as *ExprList;
fragNode.argName = "";
if head == null as *ExprList {
head = fragNode;
tail = fragNode;
} else {
tail.next = fragNode;
tail = fragNode;
}
partCount = partCount + 1;
i = j;
continue;
}
let one: String = bux_str_slice(inner, i, 1);
currentText = String_Concat(currentText, one);
i = i + 1;
}
let lastPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
let lastNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
lastNode.expr = lastPart;
lastNode.next = null as *ExprList;
lastNode.argName = "";
if head == null as *ExprList {
head = lastNode;
tail = lastNode;
} else {
tail.next = lastNode;
tail = lastNode;
}
partCount = partCount + 1;
if partCount == 1 {
return head.expr;
}
let e: *Expr = parserMakeExpr(ekStringInterp, tok.line, tok.column);
e.callArgs = head;
e.callArgCount = partCount;
return e;
}
// ---------------------------------------------------------------------------
// Primary expressions
// ---------------------------------------------------------------------------
func parserParsePrimary(p: *Parser) -> *Expr {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
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);
if kind == tkStringLiteral && bux_strlen(tok.text) >= 2 as uint
&& tok.text[0] as int == 102 && tok.text[1] as int == 34 {
return parserParseStringInterp(p, tok);
}
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.tokKind = kind;
e.tokText = tok.text;
if kind == tkIntLiteral {
e.intValue = bux_str_to_int(tok.text) as int;
}
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 ) or (a, b, ...) tuple
if kind == tkLParen {
discard parserAdvance(p);
// Empty tuple ()
if parserCheck(p, tkRParen) {
discard parserAdvance(p);
let te: *Expr = parserMakeExpr(ekTuple, line, col);
te.callArgCount = 0;
return te;
}
let first: *Expr = parserParseExpr(p);
if parserCheck(p, tkComma) {
// Tuple expression
let te: *Expr = parserMakeExpr(ekTuple, line, col);
var firstArg: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
firstArg.expr = first;
firstArg.next = null as *ExprList;
var lastArg: *ExprList = firstArg;
var count: int = 1;
while parserCheck(p, tkComma) {
discard parserAdvance(p);
if parserCheck(p, tkRParen) { break; }
let elem: *Expr = parserParseExpr(p);
let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
node.expr = elem;
node.next = null as *ExprList;
lastArg.next = node;
lastArg = node;
count = count + 1;
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple");
te.callArgs = firstArg;
te.callArgCount = count;
return te;
}
discard parserExpect(p, tkRParen, "expected ')'");
return first;
}
// Empty-param closure: `||` is lexed as tkPipePipe (logical-or token).
// As a primary it can only mean a zero-param closure: || -> T { ... }
if kind == tkPipePipe {
return parserParseEmptyClosure(p);
}
// Closure: |params| -> Ret { body }
if kind == tkPipe {
return parserParseClosure(p);
}
// Block expression { stmts; value }
if kind == tkLBrace {
let e: *Expr = parserMakeExpr(ekBlock, line, col);
e.boolValue = false;
e.refBlock = parserParseBlock(p);
return e;
}
// unsafe { ... } — unsafe block expression
if kind == tkUnsafe {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekBlock, line, col);
e.boolValue = true; // marks this block as unsafe
e.refBlock = parserParseBlock(p);
return e;
}
// match expr { arms }
if kind == tkMatch {
return parserParseMatchExpr(p);
}
parserEmitDiag(p, line, col, "expected expression");
return parserMakeExpr(ekLiteral, line, col);
}
// ---------------------------------------------------------------------------
// Patterns (for match arms)
// ---------------------------------------------------------------------------
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
let pat: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
pat.kind = kind;
pat.line = line;
pat.column = col;
pat.patIdent = "";
pat.patLitKind = 0;
pat.patLitText = "";
pat.patRangeInclusive = false;
pat.patEnumPath = "";
pat.patStructName = "";
pat.patFieldName = "";
pat.patChild1 = null as *Pattern;
pat.patChild2 = null as *Pattern;
pat.patArgs = null as *Pattern;
pat.patNext = null as *Pattern;
pat.patGuardExpr = null as *Expr;
return pat;
}
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// _
if kind == tkUnderscore {
discard parserAdvance(p);
return parserMakePattern(pkWildcard, line, col);
}
// Literals
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|| kind == tkCharLiteral || kind == tkBoolLiteral {
discard parserAdvance(p);
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
pat.patLitKind = kind;
pat.patLitText = tok.text;
return pat;
}
// Ident / enum path / true|false as names
if kind == tkIdent {
discard parserAdvance(p);
let name: String = tok.text;
if String_Eq(name, "true") || String_Eq(name, "false") {
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
pat.patLitKind = tkBoolLiteral;
pat.patLitText = name;
return pat;
}
// Enum path: Enum::Variant or Enum::Variant(...)
if parserCheck(p, tkColonColon) {
var path: String = name;
while parserCheck(p, tkColonColon) {
discard parserAdvance(p);
let seg: LexToken = parserExpectIdentOrKeyword(p, "expected identifier in pattern path");
path = String_Concat(path, "::");
path = String_Concat(path, seg.text);
}
// Optional (args) for algebraic variants — store as patArgs linked list
var enumArgs: *Pattern = null as *Pattern;
var lastArg: *Pattern = null as *Pattern;
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let argPat: *Pattern = parserParsePattern(p);
if enumArgs == null as *Pattern {
enumArgs = argPat;
lastArg = argPat;
} else {
lastArg.patNext = argPat;
lastArg = argPat;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close enum pattern");
}
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
pat.patEnumPath = path;
pat.patArgs = enumArgs;
return pat;
}
// Struct pattern: Point { x: a, y: b } or shorthand Point { x, y }
if parserCheck(p, tkLBrace) {
discard parserAdvance(p);
var fieldHead: *Pattern = null as *Pattern;
var fieldTail: *Pattern = null as *Pattern;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) { break; }
let ftok: LexToken = parserExpectIdentOrKeyword(p, "expected field name in struct pattern");
let fieldName: String = ftok.text;
var fieldPat: *Pattern = null as *Pattern;
if parserCheck(p, tkColon) {
discard parserAdvance(p);
fieldPat = parserParsePattern(p);
} else {
// Shorthand { x } → { x: x }
fieldPat = parserMakePattern(pkIdent, line, col);
fieldPat.patIdent = fieldName;
}
fieldPat.patFieldName = fieldName;
if fieldHead == null as *Pattern {
fieldHead = fieldPat;
fieldTail = fieldPat;
} else {
fieldTail.patNext = fieldPat;
fieldTail = fieldPat;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRBrace, "expected '}' to close struct pattern");
let spat: *Pattern = parserMakePattern(pkStruct, line, col);
spat.patStructName = name;
spat.patArgs = fieldHead;
return spat;
}
// Bare name with (args): Variant(...) treated as single-segment enum
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
var bareArgs: *Pattern = null as *Pattern;
var bareLast: *Pattern = null as *Pattern;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let argPat: *Pattern = parserParsePattern(p);
if bareArgs == null as *Pattern {
bareArgs = argPat;
bareLast = argPat;
} else {
bareLast.patNext = argPat;
bareLast = argPat;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
pat.patEnumPath = name;
pat.patArgs = bareArgs;
return pat;
}
// Ident binding / catch-all name
let pat: *Pattern = parserMakePattern(pkIdent, line, col);
pat.patIdent = name;
return pat;
}
// Tuple pattern: (a, b)
if kind == tkLParen {
discard parserAdvance(p);
var head: *Pattern = null as *Pattern;
var tail: *Pattern = null as *Pattern;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let elem: *Pattern = parserParsePattern(p);
if head == null as *Pattern {
head = elem;
tail = elem;
} else {
tail.patNext = elem;
tail = elem;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple pattern");
let tpat: *Pattern = parserMakePattern(pkTuple, line, col);
tpat.patArgs = head;
return tpat;
}
parserEmitDiag(p, line, col, "expected pattern");
return parserMakePattern(pkWildcard, line, col);
}
func parserParsePattern(p: *Parser) -> *Pattern {
let locTok: LexToken = parserCurToken(p);
let line: uint32 = locTok.line;
let col: uint32 = locTok.column;
let left: *Pattern = parserParsePrimaryPattern(p);
// Range pattern: lo..hi or lo..=hi
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
let inclusive: bool = parserCheck(p, tkDotDotEqual);
discard parserAdvance(p);
let right: *Pattern = parserParsePrimaryPattern(p);
let pat: *Pattern = parserMakePattern(pkRange, line, col);
pat.patRangeInclusive = inclusive;
pat.patChild1 = left;
pat.patChild2 = right;
// Range can still take a guard: `1..10 if x % 2 == 0`
if parserCheck(p, tkIf) {
discard parserAdvance(p);
let guard: *Expr = parserParseExpr(p);
let gpat: *Pattern = parserMakePattern(pkGuarded, line, col);
gpat.patChild1 = pat;
gpat.patGuardExpr = guard;
return gpat;
}
return pat;
}
// Guarded pattern: `p if cond` (bindings from p visible in cond)
if parserCheck(p, tkIf) {
discard parserAdvance(p);
let guard: *Expr = parserParseExpr(p);
let pat: *Pattern = parserMakePattern(pkGuarded, line, col);
pat.patChild1 = left;
pat.patGuardExpr = guard;
return pat;
}
return left;
}
// match subject { pat => body, ... }
func parserParseMatchExpr(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserAdvance(p); // match
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
// Allow newline before '{' (needed for `let x = match n \n { ... }`)
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
var firstArm: *MatchArm = null as *MatchArm;
var lastArm: *MatchArm = null as *MatchArm;
var armCount: int = 0;
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 armLine: uint32 = parserCurToken(p).line;
let armCol: uint32 = parserCurToken(p).column;
let mp: int = p.pos;
let pat: *Pattern = parserParsePattern(p);
discard parserExpect(p, tkFatArrow, "expected '=>' in match arm");
let body: *Expr = parserParseExpr(p);
let arm: *MatchArm = bux_alloc(sizeof(MatchArm)) as *MatchArm;
arm.line = armLine;
arm.column = armCol;
arm.pattern = pat;
arm.body = body;
arm.next = null as *MatchArm;
if firstArm == null as *MatchArm {
firstArm = arm;
lastArm = arm;
} else {
lastArm.next = arm;
lastArm = arm;
}
armCount = armCount + 1;
if parserCheck(p, tkComma) { discard parserAdvance(p); }
if p.pos == mp { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}' to close match");
let e: *Expr = parserMakeExpr(ekMatch, line, col);
e.child1 = subject;
e.matchArms = firstArm;
e.matchArmCount = armCount;
return e;
}
// ---------------------------------------------------------------------------
// Closure: |params| -> Ret { body }
// ---------------------------------------------------------------------------
// Zero-param closure when written as `||` (single tkPipePipe token from lexer)
func parserParseEmptyClosure(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserAdvance(p); // ||
let e: *Expr = parserMakeExpr(ekClosure, line, col);
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
params.kind = dkFunc;
params.paramCount = 0;
e.closureParams = params;
if parserCheck(p, tkArrow) {
discard parserAdvance(p);
e.refType = parserParseType(p);
}
e.refBlock = parserParseBlock(p);
return e;
}
func parserParseClosure(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkPipe, "expected '|' to start closure params");
let e: *Expr = parserMakeExpr(ekClosure, line, col);
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
params.kind = dkFunc;
params.paramCount = 0;
// Parse params: name: Type
while !parserCheck(p, tkPipe) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
break;
}
if params.paramCount >= 9 { break; }
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure");
discard parserExpect(p, tkColon, "expected ':' in closure parameter");
let ptype: *TypeExpr = parserParseType(p);
let idx: int = params.paramCount;
if idx == 0 {
params.param0.name = nameTok.text;
params.param0.refParamType = ptype;
} else if idx == 1 {
params.param1.name = nameTok.text;
params.param1.refParamType = ptype;
} else if idx == 2 {
params.param2.name = nameTok.text;
params.param2.refParamType = ptype;
} else if idx == 3 {
params.param3.name = nameTok.text;
params.param3.refParamType = ptype;
} else if idx == 4 {
params.param4.name = nameTok.text;
params.param4.refParamType = ptype;
} else if idx == 5 {
params.param5.name = nameTok.text;
params.param5.refParamType = ptype;
} else if idx == 6 {
params.param6.name = nameTok.text;
params.param6.refParamType = ptype;
} else if idx == 7 {
params.param7.name = nameTok.text;
params.param7.refParamType = ptype;
} else if idx == 8 {
params.param8.name = nameTok.text;
params.param8.refParamType = ptype;
}
params.paramCount = params.paramCount + 1;
if parserMatch(p, tkComma) { continue; }
break;
}
discard parserExpect(p, tkPipe, "expected '|' to close closure params");
e.closureParams = params;
// Optional return type: -> Type
if parserMatch(p, tkArrow) {
e.refType = parserParseType(p);
}
// Body: { ... }
e.refBlock = parserParseBlock(p);
return e;
}
// ---------------------------------------------------------------------------
// Postfix: call, index, field access, as, is, ?
// ---------------------------------------------------------------------------
func parserParsePostfixExpr(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 all arguments into linked list
var argCount: int = 0;
var firstArg: *ExprList = null as *ExprList;
var lastArg: *ExprList = null as *ExprList;
while !parserCheck(p, tkRParen) {
var argExpr: *Expr = null as *Expr;
var argName: String = "";
// Named argument: name: value
if parserPeek(p, 0) == tkIdent && parserPeek(p, 1) == tkColon {
let nameTok: LexToken = parserCurToken(p);
argName = nameTok.text;
discard parserAdvance(p); // ident
discard parserAdvance(p); // :
argExpr = parserParseExpr(p);
} else {
argExpr = parserParseExpr(p);
}
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
argNode.expr = argExpr;
argNode.next = null as *ExprList;
argNode.argName = argName;
if firstArg == null as *ExprList {
firstArg = argNode;
lastArg = argNode;
} else {
lastArg.next = argNode;
lastArg = argNode;
}
argCount = argCount + 1;
if !parserMatch(p, tkComma) { break; }
}
e.callArgs = firstArg;
e.callArgCount = argCount;
discard parserExpect(p, tkRParen, "expected ')'");
left = e;
continue;
}
// Generic call: Func<Type>(args) or Type<T> { ... }
if kind == tkLt {
if left.kind == ekIdent && parserIsTypeArgListAhead(p) {
discard parserAdvance(p); // <
let ta0: LexToken = parserExpect(p, tkIdent, "expected type argument");
left.genericCallee = left.strValue;
left.genericTypeArg0 = ta0.text;
left.genericTypeArgCount = 1;
if parserMatch(p, tkComma) {
let ta1: LexToken = parserExpect(p, tkIdent, "expected type argument");
left.genericTypeArg1 = ta1.text;
left.genericTypeArgCount = 2;
}
discard parserExpect(p, tkGt, "expected '>' to close type arguments");
// After generic args, continue loop to handle call or struct init
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 or tuple index expr.0 / expr.1
if kind == tkDot {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
if parserCheck(p, tkIntLiteral) {
let idxTok: LexToken = parserCurToken(p);
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekField, line, col);
e.child1 = left;
e.strValue = String_Concat("_", idxTok.text);
left = e;
continue;
}
let name: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
let e: *Expr = parserMakeExpr(ekField, line, col);
e.child1 = left;
e.strValue = name.text;
left = e;
continue;
}
// Path: expr::name
if kind == tkColonColon {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let name: LexToken = parserExpectIdentOrKeyword(p, "expected path segment");
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;
}
// ! — macro call name!(args) or unwrap
if kind == tkBang {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
if left.kind == ekIdent && parserCheck(p, tkLParen) {
discard parserAdvance(p); // (
let e: *Expr = parserMakeExpr(ekMacroCall, line, col);
e.strValue = left.strValue;
var argCount: int = 0;
var firstArg: *ExprList = null as *ExprList;
var lastArg: *ExprList = null as *ExprList;
// Multi-rep groups: m!(a,b; c,d) — lengths encoded in genericCallee
var groupLens: String = "";
var curGroup: int = 0;
var nGroups: int = 0;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRParen) { break; }
// `;` starts a new arg group
if parserMatch(p, tkSemicolon) {
if String_Eq(groupLens, "") {
groupLens = String_FromInt(curGroup as int64);
} else {
groupLens = String_Concat(groupLens, String_Concat(";", String_FromInt(curGroup as int64)));
}
nGroups = nGroups + 1;
curGroup = 0;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
continue;
}
let argExpr: *Expr = parserParseExpr(p);
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
argNode.expr = argExpr;
argNode.next = null as *ExprList;
argNode.argName = "";
if firstArg == null as *ExprList {
firstArg = argNode;
lastArg = argNode;
} else {
lastArg.next = argNode;
lastArg = argNode;
}
argCount = argCount + 1;
curGroup = curGroup + 1;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserMatch(p, tkComma) {
continue;
}
// allow `;` at loop top; otherwise end of args
if !parserCheck(p, tkSemicolon) {
break;
}
}
// finalize last group
if curGroup > 0 || nGroups == 0 {
if String_Eq(groupLens, "") {
groupLens = String_FromInt(curGroup as int64);
} else {
groupLens = String_Concat(groupLens, String_Concat(";", String_FromInt(curGroup as int64)));
}
nGroups = nGroups + 1;
}
// single group → empty genericCallee (flat match)
if nGroups <= 1 {
e.genericCallee = "";
} else {
e.genericCallee = groupLens;
}
e.callArgs = firstArg;
e.callArgCount = argCount;
discard parserExpect(p, tkRParen, "expected ')' to close macro arguments");
left = e;
continue;
}
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;
// Propagate generic type args from the identifier
e.genericTypeArg0 = left.genericTypeArg0;
e.genericTypeArg1 = left.genericTypeArg1;
e.genericTypeArgCount = left.genericTypeArgCount;
left = e;
continue;
} else {
break;
}
}
break;
}
return left;
}
// ---------------------------------------------------------------------------
// Binary expression (precedence climbing)
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
// ---------------------------------------------------------------------------
func parserPrecedence(op: int) -> int {
// Assignment operators are parsed by parserParseAssign, not here
if op == tkPipePipe { return 2; }
if op == tkAmpAmp { return 3; }
if op == tkPipe { return 4; }
if op == tkCaret { return 5; }
if op == tkAmp { return 6; }
if op == tkEq || op == tkNe { return 7; }
if op == tkLt || op == tkLe || op == tkGt || op == tkGe { return 8; }
if op == tkShl || op == tkShr { return 9; }
if op == tkPlus || op == tkMinus { return 10; }
if op == tkStar || op == tkSlash || op == tkPercent { return 11; }
if op == tkStarStar { return 12; }
return 0;
}
func parserParseUnary(p: *Parser) -> *Expr {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// -expr, !expr, ~expr, *expr, &expr
if kind == tkMinus || kind == tkBang || kind == tkTilde || kind == tkStar || kind == tkAmp {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekUnary, line, col);
e.intValue = kind;
e.child1 = parserParseUnary(p);
return e;
}
return parserParsePostfixExpr(p);
}
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
var left: *Expr = parserParseUnary(p);
while true {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
let op: int = parserPeek(p, 0);
let prec: int = parserPrecedence(op);
if prec < minPrec { break; }
let opTok: LexToken = parserAdvance(p);
let line: uint32 = opTok.line;
let col: uint32 = opTok.column;
let nextMinPrec: int = prec + 1;
let right: *Expr = parserParseBinaryPrec(p, nextMinPrec);
let e: *Expr = parserMakeExpr(ekBinary, line, col);
e.intValue = opTok.kind;
e.child1 = left;
e.child2 = right;
left = e;
}
return left;
}
func parserParseBinary(p: *Parser) -> *Expr {
return parserParseBinaryPrec(p, 1);
}
// ---------------------------------------------------------------------------
// Range: lo .. hi or lo ..= hi
// ---------------------------------------------------------------------------
func parserParseRange(p: *Parser) -> *Expr {
var left: *Expr = parserParseBinary(p);
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
let inclusive: bool = parserCheck(p, tkDotDotEqual);
let opTok: LexToken = parserAdvance(p);
let right: *Expr = parserParseBinary(p);
let e: *Expr = parserMakeExpr(ekRange, opTok.line, opTok.column);
e.child1 = left;
e.child2 = right;
e.boolValue = inclusive;
return e;
}
return left;
}
// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------
func parserParseTernary(p: *Parser) -> *Expr {
var left: *Expr = parserParseRange(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;
}
// ---------------------------------------------------------------------------
// Assignment: target = value (right-associative)
// ---------------------------------------------------------------------------
func parserParseAssign(p: *Parser) -> *Expr {
let left: *Expr = parserParseTernary(p);
let op: int = parserPeek(p, 0);
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign {
let opTok: LexToken = parserAdvance(p);
let right: *Expr = parserParseAssign(p);
let e: *Expr = parserMakeExpr(ekAssign, opTok.line, opTok.column);
e.intValue = opTok.kind;
e.child1 = left;
e.child2 = right;
return e;
}
return left;
}
// ---------------------------------------------------------------------------
// Top-level expression
// ---------------------------------------------------------------------------
func parserParseExpr(p: *Parser) -> *Expr {
return parserParseAssign(p);
}
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
func parserParseStmt(p: *Parser) -> *Stmt {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// Macro template: $( stmts… )*
if p.macroTemplateMode && kind == tkDollar && parserPeek(p, 1) == tkLParen {
discard parserAdvance(p); // $
discard parserAdvance(p); // (
let body: *Block = bux_alloc(sizeof(Block)) as *Block;
body.line = line;
body.column = col;
body.sourceFile = "";
body.stmtCount = 0;
body.firstStmt = null as *Stmt;
body.lastStmt = null as *Stmt;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRParen) { break; }
let inner: *Stmt = parserParseStmt(p);
if body.firstStmt == null as *Stmt {
body.firstStmt = inner;
body.lastStmt = inner;
} else {
body.lastStmt.nextStmt = inner;
body.lastStmt = inner;
}
body.stmtCount = body.stmtCount + 1;
}
discard parserExpect(p, tkRParen, "expected ')' to close macro repetition");
discard parserExpect(p, tkStar, "expected '*' after macro repetition");
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skMacroRep;
s.line = line;
s.column = col;
s.refStmtBlock = body;
s.nextStmt = null as *Stmt;
return s;
}
// 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);
}
var init: *Expr = null as *Expr;
if parserMatch(p, tkAssign) {
init = parserParseExpr(p);
} else if !isVar {
discard parserExpect(p, tkAssign, "expected '=' in let statement");
init = 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;
}
// discard expr
if kind == tkDiscard {
discard parserAdvance(p);
var val: *Expr = null as *Expr;
if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkRBrace) && !parserCheck(p, tkNewLine) {
val = 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 = val;
return s;
}
// defer expr
if kind == tkDefer {
discard parserAdvance(p);
let val: *Expr = parserParseExpr(p);
parserMatch(p, tkSemicolon);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skDefer;
s.line = line;
s.column = col;
s.child1 = val;
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;
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
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 — expression statement (arms fully parsed)
if kind == tkMatch {
let matchExpr: *Expr = parserParseMatchExpr(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skExpr;
s.line = line;
s.column = col;
s.child1 = matchExpr;
return s;
}
// switch
if kind == tkSwitch {
discard parserAdvance(p);
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
discard parserExpect(p, tkLBrace, "expected '{' to start switch body");
var caseBlock: *Block = bux_alloc(sizeof(Block)) as *Block;
caseBlock.line = line;
caseBlock.column = col;
caseBlock.stmtCount = 0;
caseBlock.firstStmt = null as *Stmt;
caseBlock.lastStmt = null as *Stmt;
var defaultBody: *Block = null as *Block;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
if parserCheck(p, tkDefault) {
discard parserAdvance(p);
discard parserExpect(p, tkColon, "expected ':' after default");
let defaultStmt: *Stmt = parserParseStmt(p);
defaultBody = bux_alloc(sizeof(Block)) as *Block;
defaultBody.line = defaultStmt.line;
defaultBody.column = defaultStmt.column;
defaultBody.stmtCount = 1;
defaultBody.firstStmt = defaultStmt;
defaultBody.lastStmt = defaultStmt;
defaultStmt.nextStmt = null as *Stmt;
continue;
}
if parserCheck(p, tkCase) {
discard parserAdvance(p);
let caseVal: *Expr = parserParseExpr(p);
discard parserExpect(p, tkColon, "expected ':' after case value");
let caseStmt: *Stmt = parserParseStmt(p);
let caseBody: *Block = bux_alloc(sizeof(Block)) as *Block;
caseBody.line = caseStmt.line;
caseBody.column = caseStmt.column;
caseBody.stmtCount = 1;
caseBody.firstStmt = caseStmt;
caseBody.lastStmt = caseStmt;
caseStmt.nextStmt = null as *Stmt;
let c: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
c.kind = skExpr;
c.line = caseVal.line;
c.column = caseVal.column;
c.child1 = caseVal;
c.refStmtBlock = caseBody;
c.nextStmt = null as *Stmt;
if caseBlock.firstStmt == null as *Stmt {
caseBlock.firstStmt = c;
caseBlock.lastStmt = c;
} else {
caseBlock.lastStmt.nextStmt = c;
caseBlock.lastStmt = c;
}
caseBlock.stmtCount = caseBlock.stmtCount + 1;
continue;
}
discard parserAdvance(p);
}
discard parserExpect(p, tkRBrace, "expected '}' to close switch");
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skSwitch;
s.line = line;
s.column = col;
s.child1 = subject;
s.refStmtBlock = caseBlock;
s.refStmtElse = defaultBody;
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;
// Build AST with parserParseStmt (may not consume all tokens)
var blockPos: int = p.pos;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile {
break;
}
let beforePos: int = p.pos;
let s: *Stmt = parserParseStmt(p);
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;
}
}
// Reliable token consumption: reset to after { and use depth counter
p.pos = blockPos;
var depth: int = 1;
while depth > 0 && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkLBrace) { depth = depth + 1; }
else if parserCheck(p, tkRBrace) { depth = depth - 1; }
discard parserAdvance(p);
}
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 {
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile {
break;
}
if d.paramCount >= 9 { break; }
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
discard parserExpect(p, tkColon, "expected ':' in parameter");
let ptype: *TypeExpr = parserParseType(p);
var defExpr: *Expr = null as *Expr;
if parserMatch(p, tkAssign) {
defExpr = parserParseExpr(p);
}
if d.paramCount == 0 {
d.param0.name = nameTok.text;
d.param0.refParamType = ptype;
d.param0.defaultExpr = defExpr;
} else if d.paramCount == 1 {
d.param1.name = nameTok.text;
d.param1.refParamType = ptype;
d.param1.defaultExpr = defExpr;
} else if d.paramCount == 2 {
d.param2.name = nameTok.text;
d.param2.refParamType = ptype;
d.param2.defaultExpr = defExpr;
} else if d.paramCount == 3 {
d.param3.name = nameTok.text;
d.param3.refParamType = ptype;
d.param3.defaultExpr = defExpr;
} else if d.paramCount == 4 {
d.param4.name = nameTok.text;
d.param4.refParamType = ptype;
d.param4.defaultExpr = defExpr;
} else if d.paramCount == 5 {
d.param5.name = nameTok.text;
d.param5.refParamType = ptype;
d.param5.defaultExpr = defExpr;
} else if d.paramCount == 6 {
d.param6.name = nameTok.text;
d.param6.refParamType = ptype;
d.param6.defaultExpr = defExpr;
} else if d.paramCount == 7 {
d.param7.name = nameTok.text;
d.param7.refParamType = ptype;
d.param7.defaultExpr = defExpr;
} else if d.paramCount == 8 {
d.param8.name = nameTok.text;
d.param8.refParamType = ptype;
d.param8.defaultExpr = defExpr;
}
d.paramCount = d.paramCount + 1;
if parserMatch(p, tkComma) { continue; }
break;
}
discard parserExpect(p, tkRParen, "expected ')'");
return d;
}
// ---------------------------------------------------------------------------
// Type parameters: <T: Bound, U: Bound2>
// ---------------------------------------------------------------------------
func parserParseTypeParams(p: *Parser, d: *Decl) {
// Lifetime params ('a) are accepted and skipped for monomorphization —
// they only annotate &/'a T on parameters/returns (stored in TypeExpr.refLifetime).
if !parserCheck(p, tkLt) { return; }
discard parserAdvance(p);
var typeCount: int = 0;
var first: bool = true;
while !parserCheck(p, tkGt) && parserPeek(p, 0) != tkEndOfFile {
if !first {
if !parserMatch(p, tkComma) { break; }
}
first = false;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkGt) { break; }
// Lifetime type param: 'a — parse and ignore for mono slots
if parserCheck(p, tkLifetime) {
discard parserAdvance(p);
// optional trait bound is nonsense for lifetimes; skip : Bound if present
if parserMatch(p, tkColon) {
discard parserExpect(p, tkIdent, "expected trait bound name");
}
continue;
}
let tpTok: LexToken = parserExpect(p, tkIdent, "expected type param");
if typeCount == 0 {
d.typeParam0 = tpTok.text;
typeCount = 1;
if parserMatch(p, tkColon) {
let bound0: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
d.typeParam0Bound = bound0.text;
}
} else if typeCount == 1 {
d.typeParam1 = tpTok.text;
typeCount = 2;
if parserMatch(p, tkColon) {
let bound1: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
d.typeParam1Bound = bound1.text;
}
} else {
// Extra type params beyond 2 — consume and ignore
if parserMatch(p, tkColon) {
discard parserExpect(p, tkIdent, "expected trait bound name");
}
}
}
d.typeParamCount = typeCount;
discard parserExpect(p, tkGt, "expected '>'");
}
// ---------------------------------------------------------------------------
// 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: Bound, U: Bound2>
parserParseTypeParams(p, d);
// 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;
d.param6 = params.param6;
d.param7 = params.param7;
d.param8 = params.param8;
// 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.fields = bux_alloc(256 as uint * sizeof(StructField)) as *StructField;
if d.fields == null as *StructField {
PrintLine("ERROR: bux_alloc returned null for fields");
}
d.kind = dkStruct;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = nameTok.text;
var fieldCount: int = 0;
// Type params <T: Bound, U: Bound2>
parserParseTypeParams(p, d);
discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if fieldCount >= 256 { 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");
if fieldCount >= 250 && fieldCount <= 256 {
PrintLine(String_Concat("RAW fieldCount=", bux_int_to_str(fieldCount as int64)));
PrintLine(String_Concat("RAW fName=", fName.text));
PrintLine(String_Concat("RAW pos=", bux_int_to_str(p.pos as int64)));
}
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;
}
d.fields[fieldCount].name = fName.text;
d.fields[fieldCount].refFieldType = fType;
fieldCount = fieldCount + 1;
}
d.fieldCount = fieldCount;
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 >= 9 { 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;
v.fieldCount = 0;
v.fieldTypeName0 = "";
v.fieldTypeName1 = "";
// Optional payload: (Type, Type) or (Tuple, ...) — full type expressions
if parserMatch(p, tkLParen) {
let te0: *TypeExpr = parserParseType(p);
v.fieldTypeName0 = parserTypeExprCName(te0);
v.fieldCount = 1;
if parserMatch(p, tkComma) {
let te1: *TypeExpr = parserParseType(p);
v.fieldTypeName1 = parserTypeExprCName(te1);
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; }
else if d.variantCount == 4 { d.variant4 = v; }
else if d.variantCount == 5 { d.variant5 = v; }
else if d.variantCount == 6 { d.variant6 = v; }
else if d.variantCount == 7 { d.variant7 = v; }
else if d.variantCount == 8 { d.variant8 = 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} or ::*
if parserMatch(p, tkColonColon) {
if parserCheck(p, tkLBrace) {
discard parserAdvance(p); // {
var names: String = "";
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 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 if parserCheck(p, tkStar) {
discard parserAdvance(p); // *
d.useKind = 1; // ukGlob
} else {
d.useKind = 0; // ukSingle
}
} 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;
}
// ---------------------------------------------------------------------------
// Interface declaration
// ---------------------------------------------------------------------------
func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkInterface, "expected 'interface'");
let nameTok: LexToken = parserExpect(p, tkIdent, "expected interface name");
discard parserExpect(p, tkLBrace, "expected '{' to start interface body");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkInterface;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = nameTok.text;
var methods: *Decl = null as *Decl;
var lastMethod: *Decl = null as *Decl;
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);
if methods == null as *Decl {
methods = m;
lastMethod = m;
} else {
lastMethod.childDecl2 = m;
lastMethod = m;
}
d.methodCount = d.methodCount + 1;
} else {
break;
}
}
d.childDecl1 = methods;
discard parserExpect(p, tkRBrace, "expected '}' to close interface");
return d;
}
// ---------------------------------------------------------------------------
// macro! name { ($x:expr, …) => { template } … }
// ---------------------------------------------------------------------------
func parserSetMacroFragName(d: *Decl, idx: int, name: String) {
if idx == 0 { d.param0.name = name; }
else if idx == 1 { d.param1.name = name; }
else if idx == 2 { d.param2.name = name; }
else if idx == 3 { d.param3.name = name; }
else if idx == 4 { d.param4.name = name; }
else if idx == 5 { d.param5.name = name; }
else if idx == 6 { d.param6.name = name; }
else if idx == 7 { d.param7.name = name; }
else if idx == 8 { d.param8.name = name; }
}
func parserParseMacroDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkMacro, "expected 'macro'");
discard parserExpect(p, tkBang, "expected '!' after macro");
let nameTok: LexToken = parserExpect(p, tkIdent, "expected macro name");
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
discard parserExpect(p, tkLBrace, "expected '{' to start macro body");
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkMacro;
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.strValue = nameTok.text;
d.childDecl1 = null as *Decl;
d.childDecl2 = null as *Decl;
var firstRule: *Decl = null as *Decl;
var lastRule: *Decl = null as *Decl;
var ruleCount: int = 0;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
if parserCheck(p, tkRBrace) { break; }
let rline: uint32 = parserCurToken(p).line;
let rcol: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkLParen, "expected '(' to start macro pattern");
let rule: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
rule.kind = dkMacro;
rule.line = rline;
rule.column = rcol;
rule.strValue = "";
rule.paramCount = 0;
rule.childDecl2 = null as *Decl;
// useNames encodes: "expr" | "ident" | "tt" | "rep:expr," | "rep:expr+expr," (compound)
// Multiple pattern elements joined by `;` → multi-rep groups at call site
var kindsEnc: String = "";
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRParen) { break; }
if parserMatch(p, tkSemicolon) { continue; }
// $( $a:kind , $b:kind ),*
if parserCheck(p, tkDollar) && parserPeek(p, 1) == tkLParen {
discard parserAdvance(p); // $
discard parserAdvance(p); // (
var repNames: String = "";
var repKinds: String = "";
var nIn: int = 0;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRParen) { break; }
let fragTok: LexToken = parserExpect(p, tkIdent, "expected $name fragment");
if !String_StartsWith(fragTok.text, "$") {
parserEmitDiag(p, fragTok.line, fragTok.column, "macro fragment must start with '$'");
}
discard parserExpect(p, tkColon, "expected ':' after fragment name");
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
var kname: String = kindTok.text;
if String_Eq(kname, "lit") { kname = "literal"; }
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
kname = "expr";
}
if nIn == 0 {
repNames = fragTok.text;
repKinds = kname;
} else {
repNames = String_Concat(repNames, String_Concat("+", fragTok.text));
repKinds = String_Concat(repKinds, String_Concat("+", kname));
}
if rule.paramCount < 9 {
parserSetMacroFragName(rule, rule.paramCount, fragTok.text);
rule.paramCount = rule.paramCount + 1;
}
nIn = nIn + 1;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if !parserMatch(p, tkComma) { break; }
}
discard parserExpect(p, tkRParen, "expected ')' after repeated fragment");
var sep: String = "";
if parserMatch(p, tkComma) { sep = ","; }
discard parserExpect(p, tkStar, "expected '*' after macro repetition");
let enc: String = String_Concat("rep:", String_Concat(repKinds, sep));
// mark compound count via leading digit in typeParam0 of rule (hack: use isDrop)
if rule.paramCount > 0 {
// store chunk size on last param via isVariadic false; use methodCount as chunk
// Encode: kindsEnc entry includes chunk after @
let enc2: String = String_Concat(enc, String_Concat("@", String_FromInt(nIn)));
if String_Eq(kindsEnc, "") { kindsEnc = enc2; }
else { kindsEnc = String_Concat(kindsEnc, String_Concat(";", enc2)); }
}
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserMatch(p, tkSemicolon) { continue; }
if parserMatch(p, tkComma) { continue; }
break;
} else {
let fragTok: LexToken = parserExpect(p, tkIdent, "expected $name fragment");
if !String_StartsWith(fragTok.text, "$") {
parserEmitDiag(p, fragTok.line, fragTok.column, "macro fragment must start with '$'");
}
discard parserExpect(p, tkColon, "expected ':' after fragment name");
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
var kname: String = kindTok.text;
if String_Eq(kname, "lit") { kname = "literal"; }
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
kname = "expr";
}
if rule.paramCount < 9 {
parserSetMacroFragName(rule, rule.paramCount, fragTok.text);
rule.paramCount = rule.paramCount + 1;
}
if String_Eq(kindsEnc, "") { kindsEnc = kname; }
else { kindsEnc = String_Concat(kindsEnc, String_Concat(";", kname)); }
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserMatch(p, tkComma) { continue; }
if parserMatch(p, tkSemicolon) { continue; }
break;
}
}
rule.useNames = kindsEnc;
discard parserExpect(p, tkRParen, "expected ')' to close macro pattern");
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
discard parserExpect(p, tkFatArrow, "expected '=>' after macro pattern");
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
let savedTpl: bool = p.macroTemplateMode;
p.macroTemplateMode = true;
rule.refBody = parserParseBlock(p);
p.macroTemplateMode = savedTpl;
if firstRule == null as *Decl {
firstRule = rule;
lastRule = rule;
} else {
lastRule.childDecl2 = rule;
lastRule = rule;
}
ruleCount = ruleCount + 1;
while parserCheck(p, tkNewLine) || parserCheck(p, tkComma) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
}
discard parserExpect(p, tkRBrace, "expected '}' to close macro");
d.childDecl1 = firstRule;
d.methodCount = ruleCount;
if ruleCount == 0 {
parserEmitDiag(p, line, col, "macro has no rules");
}
return d;
}
// Top-level declaration
// ---------------------------------------------------------------------------
func parserParseDecl(p: *Parser) -> *Decl {
// Skip newlines before declaration (matching bootstrap behavior)
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
let isPublic: bool = parserMatch(p, tkPub);
// Parse stacked @[Checked] / @[Drop] / @[Release] attributes
var isChecked: int = 0;
var isDrop: int = 0;
var isRelease: int = 0;
while true {
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
if !parserCheck(p, tkAt) { break; }
discard parserAdvance(p); // @
if parserCheck(p, tkLBracket) {
discard parserAdvance(p); // [
if parserCheck(p, tkIdent) {
let attrName: LexToken = parserCurToken(p);
if String_Eq(attrName.text, "Checked") {
isChecked = 1;
}
if String_Eq(attrName.text, "Drop") {
isDrop = 1;
}
if String_Eq(attrName.text, "Release") {
isRelease = 1;
}
discard parserAdvance(p); // attribute name
}
if parserCheck(p, tkRBracket) {
discard parserAdvance(p); // ]
}
}
}
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
}
let kind: int = parserPeek(p, 0);
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // async
let d: *Decl = parserParseFuncDecl(p, isPublic, false, true);
d.isChecked = isChecked;
d.isRelease = isRelease;
return d;
}
if kind == tkConst && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // const
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isConst = 1;
d.isChecked = isChecked;
d.isRelease = isRelease;
return d;
}
if kind == tkFunc {
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isChecked = isChecked;
d.isRelease = isRelease;
return d;
}
if kind == tkStruct {
let d: *Decl = parserParseStructDecl(p, isPublic);
d.isDrop = isDrop;
return d;
}
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
if kind == tkImport { return parserParseImportDecl(p, isPublic); }
if kind == tkExtern { return parserParseExternDecl(p, isPublic); }
if kind == tkInterface { return parserParseInterfaceDecl(p, isPublic); }
if kind == tkMacro { return parserParseMacroDecl(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: Bound>
parserParseTypeParams(p, d);
// Optional 'for InterfaceName'
if parserMatch(p, tkFor) {
let ifaceName: LexToken = parserExpect(p, tkIdent, "expected interface name");
d.strValue2 = ifaceName.text;
}
discard parserExpect(p, tkLBrace, "expected '{'");
var methods: *Decl = null as *Decl;
var lastMethod: *Decl = null as *Decl;
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);
if methods == null as *Decl {
methods = m;
lastMethod = m;
} else {
lastMethod.childDecl2 = m;
lastMethod = m;
}
d.methodCount = d.methodCount + 1;
} else {
break;
}
}
d.childDecl1 = methods;
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);
}
}
discard parserExpect(p, tkRBrace, "expected '}' to close module");
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;
}
// Unknown declaration — skip one token and return null
// (was: skip to newline/}, which was destructive)
parserEmitDiag(p, parserCurToken(p).line, parserCurToken(p).column, "skipping unknown declaration");
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;
p.macroTemplateMode = false;
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);
}
}
/* Print fatal parser diagnostics (severity == 0) or if nothing valid was parsed */
if p.diagCount > 0 && mod.itemCount == 0 {
var di: int = 0;
while di < p.diagCount {
let d: ParserDiag = p.diags[di];
Print("error: ");
PrintLine(d.message);
Print(" --> <input>:");
PrintInt(d.line as int64);
Print(":");
PrintInt(d.column as int64);
PrintLine("");
Print(" |");
PrintLine("");
Print(" ");
PrintInt(d.line as int64);
Print(" | <source unavailable>");
PrintLine("");
Print(" | ");
var sp: uint32 = 0;
while sp < d.column - 1 && sp < 120 {
Print(" ");
sp = sp + 1;
}
PrintLine("^");
di = di + 1;
}
}
return mod;
}
}