d1160ca5d1
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
1430 lines
49 KiB
Plaintext
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;
|
|
}
|
|
}
|