a45a2ecd49
- Lexer (Nim + Bux): resolve escape sequences (\n, \t, \r, etc) instead of keeping raw source text in token. This was the root cause of \n being double-escaped to \\n in generated C code. - Manifest parser (src_bux/manifest.bux): use bux_strlen instead of broken String_SplitCount(s, "") hack for string length; fix quote stripping. - CLI (src_bux/cli.bux): --version/--help now recognized alongside version/help. - Remove ~30+ debug Print statements from cli, parser, lexer, sema, main. - Clean up unneeded Print/PrintInt extern declarations. - _selfhost/src/: synced from src_bux/.
1533 lines
53 KiB
Plaintext
1533 lines
53 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;
|
|
}
|
|
|
|
// Lookahead to determine if '<' starts a type argument list.
|
|
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);
|
|
if kind == tkEndOfFile || kind == tkLBrace || kind == tkSemicolon {
|
|
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 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;
|
|
e.callArgs = null as *ExprList;
|
|
e.callArgCount = 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 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) {
|
|
let argExpr: *Expr = parserParseExpr(p);
|
|
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
argNode.expr = argExpr;
|
|
argNode.next = null as *ExprList;
|
|
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
|
|
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;
|
|
}
|
|
|
|
// Path: expr::name
|
|
if kind == tkColonColon {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let name: LexToken = parserExpect(p, tkIdent, "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;
|
|
}
|
|
|
|
// ! (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 parserPrecedence(op: int) -> int {
|
|
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign { return 1; }
|
|
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 parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
|
|
var left: *Expr = parserParsePostfix(p);
|
|
while true {
|
|
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);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 >= 9 { 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;
|
|
} else if d.paramCount == 6 {
|
|
d.param6.name = nameTok.text;
|
|
d.param6.refParamType = ptype;
|
|
} else if d.paramCount == 7 {
|
|
d.param7.name = nameTok.text;
|
|
d.param7.refParamType = ptype;
|
|
} else if d.paramCount == 8 {
|
|
d.param8.name = nameTok.text;
|
|
d.param8.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;
|
|
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.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.childDecl1.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;
|
|
}
|
|
}
|