e3ca724bfa
Support destructuring in match arms:
- Tuple: (a, b) binds subject._0 / _1
- Struct: Point { x: px, y: py } and shorthand Point { x, y }
Bootstrap: matchPatternBindings for pkTuple/pkStruct; register local
tuple typedefs from function bodies. Selfhost: parse, Sema_BindPattern,
Lcx_PatternBindings with scope defines. Fix operator-overload path that
crashed when typeName was null after pattern binds.
Example: examples/struct_tuple_pat.bux. Selfhost-loop IDENTICAL.
2363 lines
83 KiB
Plaintext
2363 lines
83 KiB
Plaintext
// parser.bux — Recursive descent parser (ported from parser.nim)
|
|
// Parses Bux source tokens into an AST.
|
|
module Parser {
|
|
|
|
extern func bux_strlen(s: String) -> uint;
|
|
extern func bux_str_to_int(s: String) -> int64;
|
|
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
|
|
|
// Forward declarations for mutual recursion
|
|
func parserParseExpr(p: *Parser) -> *Expr;
|
|
func parserParseStmt(p: *Parser) -> *Stmt;
|
|
func parserParseBlock(p: *Parser) -> *Block;
|
|
func parserParsePrimary(p: *Parser) -> *Expr;
|
|
func parserParsePostfixExpr(p: *Parser) -> *Expr;
|
|
func parserParseUnary(p: *Parser) -> *Expr;
|
|
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
|
|
func parserParsePattern(p: *Parser) -> *Pattern;
|
|
func parserParseMatchExpr(p: *Parser) -> *Expr;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parser state
|
|
// ---------------------------------------------------------------------------
|
|
struct Parser {
|
|
tokens: *LexToken,
|
|
tokenCount: int,
|
|
pos: int,
|
|
diagCount: int,
|
|
diags: *ParserDiag,
|
|
structInitAllowed: bool,
|
|
}
|
|
|
|
struct ParserDiag {
|
|
line: uint32,
|
|
column: uint32,
|
|
message: String,
|
|
severity: int, /* 0=error (fatal), 1=warning (recoverable) */
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Token helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserCurToken(p: *Parser) -> LexToken {
|
|
if p.pos < p.tokenCount {
|
|
return p.tokens[p.pos];
|
|
}
|
|
var eof: LexToken;
|
|
eof.kind = tkEndOfFile;
|
|
eof.text = "";
|
|
return eof;
|
|
}
|
|
|
|
func parserPeek(p: *Parser, ahead: int) -> int {
|
|
let i: int = p.pos + ahead;
|
|
if i >= 0 && i < p.tokenCount {
|
|
return p.tokens[i].kind;
|
|
}
|
|
return tkEndOfFile;
|
|
}
|
|
|
|
// Lookahead to determine if '<' starts a type argument list.
|
|
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 parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
|
|
if parserCheck(p, kind) {
|
|
return parserAdvance(p);
|
|
}
|
|
let tok: LexToken = parserCurToken(p);
|
|
if p.diagCount < 256 {
|
|
p.diags[p.diagCount] = ParserDiag {
|
|
line: tok.line, column: tok.column, message: msg, severity: 1
|
|
};
|
|
p.diagCount = p.diagCount + 1;
|
|
}
|
|
return tok;
|
|
}
|
|
|
|
func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
|
|
if p.diagCount < 256 {
|
|
p.diags[p.diagCount] = ParserDiag {
|
|
line: line, column: col, message: msg, severity: 1
|
|
};
|
|
p.diagCount = p.diagCount + 1;
|
|
}
|
|
}
|
|
|
|
func parserIsKeyword(kind: int) -> bool {
|
|
if kind >= tkIf && kind <= tkSuper { return true; }
|
|
if kind == tkSizeOf { return true; }
|
|
return false;
|
|
}
|
|
|
|
func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
|
|
let tok: LexToken = parserCurToken(p);
|
|
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
|
|
return parserAdvance(p);
|
|
}
|
|
if p.diagCount < 256 {
|
|
p.diags[p.diagCount] = ParserDiag {
|
|
line: tok.line, column: tok.column, message: msg, severity: 1
|
|
};
|
|
p.diagCount = p.diagCount + 1;
|
|
}
|
|
return tok;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseType(p: *Parser) -> *TypeExpr {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let kindTok: int = parserPeek(p, 0);
|
|
|
|
// &T (shared reference) and &mut T (mutable reference)
|
|
if kindTok == tkAmp {
|
|
discard parserAdvance(p); // &
|
|
var isMut: bool = false;
|
|
// Check for "mut" keyword
|
|
if parserCheck(p, tkIdent) {
|
|
let tok: LexToken = parserCurToken(p);
|
|
if String_Eq(tok.text, "mut") {
|
|
isMut = true;
|
|
discard parserAdvance(p); // mut
|
|
}
|
|
}
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
if isMut {
|
|
te.kind = tekMutRef;
|
|
} else {
|
|
te.kind = tekRef;
|
|
}
|
|
te.line = line;
|
|
te.column = col;
|
|
te.pointerPointee = parserParseType(p);
|
|
if te.pointerPointee != null as *TypeExpr {
|
|
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
|
|
}
|
|
return te;
|
|
}
|
|
|
|
// *T (pointer)
|
|
if kindTok == tkStar {
|
|
discard parserAdvance(p);
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
te.kind = tekPointer;
|
|
te.line = line;
|
|
te.column = col;
|
|
te.pointerPointee = parserParseType(p);
|
|
// Set typeName to "Pointee*"
|
|
if te.pointerPointee != null as *TypeExpr {
|
|
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
|
|
}
|
|
return te;
|
|
}
|
|
|
|
// func(Params) -> Ret
|
|
if kindTok == tkFunc {
|
|
discard parserAdvance(p);
|
|
discard parserExpect(p, tkLParen, "expected '(' after 'func'");
|
|
var params: *TypeExprList = null as *TypeExprList;
|
|
var paramsTail: *TypeExprList = null as *TypeExprList;
|
|
var count: int = 0;
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
let paramTe: *TypeExpr = parserParseType(p);
|
|
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
|
|
node.te = paramTe;
|
|
node.next = null as *TypeExprList;
|
|
if params == null as *TypeExprList {
|
|
params = node;
|
|
} else {
|
|
paramsTail.next = node;
|
|
}
|
|
paramsTail = node;
|
|
count = count + 1;
|
|
if parserCheck(p, tkComma) {
|
|
discard parserAdvance(p);
|
|
}
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' after func params");
|
|
var ret: *TypeExpr = null as *TypeExpr;
|
|
if parserCheck(p, tkArrow) {
|
|
discard parserAdvance(p);
|
|
ret = parserParseType(p);
|
|
}
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
te.kind = tekFunc;
|
|
te.line = line;
|
|
te.column = col;
|
|
te.funcParams = params;
|
|
te.funcRet = ret;
|
|
te.funcParamCount = count;
|
|
return te;
|
|
}
|
|
|
|
// (T, U, ...) tuple type
|
|
if kindTok == tkLParen {
|
|
discard parserAdvance(p);
|
|
var elems: *TypeExprList = null as *TypeExprList;
|
|
var elemsTail: *TypeExprList = null as *TypeExprList;
|
|
var count: int = 0;
|
|
var typeName: String = "Tuple";
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
let elemTe: *TypeExpr = parserParseType(p);
|
|
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
|
|
node.te = elemTe;
|
|
node.next = null as *TypeExprList;
|
|
if elems == null as *TypeExprList {
|
|
elems = node;
|
|
} else {
|
|
elemsTail.next = node;
|
|
}
|
|
elemsTail = node;
|
|
count = count + 1;
|
|
// Build mangled name: Tuple_int_int
|
|
var part: String = "int";
|
|
if elemTe != null as *TypeExpr {
|
|
if !String_Eq(elemTe.typeName, "") {
|
|
part = elemTe.typeName;
|
|
} else if elemTe.kind == tekPointer && elemTe.pointerPointee != null as *TypeExpr {
|
|
part = String_Concat(elemTe.pointerPointee.typeName, "Ptr");
|
|
}
|
|
}
|
|
if String_Eq(part, "String") || String_Eq(part, "str") { part = "cstr"; }
|
|
typeName = String_Concat(typeName, "_");
|
|
typeName = String_Concat(typeName, part);
|
|
if parserCheck(p, tkComma) {
|
|
discard parserAdvance(p);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' to close tuple type");
|
|
if count == 0 {
|
|
typeName = "Tuple_Empty";
|
|
}
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
te.kind = tekTuple;
|
|
te.line = line;
|
|
te.column = col;
|
|
te.tupleElems = elems;
|
|
te.tupleCount = count;
|
|
te.typeName = typeName;
|
|
return te;
|
|
}
|
|
|
|
// name
|
|
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
|
|
// self / Self -> tekSelf
|
|
if String_Eq(nameTok.text, "self") || String_Eq(nameTok.text, "Self") {
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
te.kind = tekSelf;
|
|
te.line = nameTok.line;
|
|
te.column = nameTok.column;
|
|
te.typeName = "Self";
|
|
return te;
|
|
}
|
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
te.kind = tekNamed;
|
|
te.line = nameTok.line;
|
|
te.column = nameTok.column;
|
|
te.typeName = nameTok.text;
|
|
|
|
// Optional <T, U> type args
|
|
if parserCheck(p, tkLt) {
|
|
discard parserAdvance(p); // <
|
|
let arg0: LexToken = parserExpect(p, tkIdent, "expected type argument");
|
|
te.typeArgName0 = arg0.text;
|
|
te.typeArgCount = 1;
|
|
if parserMatch(p, tkComma) {
|
|
let arg1: LexToken = parserExpect(p, tkIdent, "expected type argument");
|
|
te.typeArgName1 = arg1.text;
|
|
te.typeArgCount = 2;
|
|
}
|
|
discard parserExpect(p, tkGt, "expected '>' to close type arguments");
|
|
}
|
|
return te;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Forward declarations and helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseExpr(p: *Parser) -> *Expr;
|
|
func parserParseStmt(p: *Parser) -> *Stmt;
|
|
func parserParseBlock(p: *Parser) -> *Block;
|
|
|
|
func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
|
|
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
|
|
e.kind = kind;
|
|
e.line = line;
|
|
e.column = col;
|
|
e.strValue = "";
|
|
e.intValue = 0;
|
|
e.boolValue = false;
|
|
e.tokKind = 0;
|
|
e.tokText = "";
|
|
e.child1 = null as *Expr;
|
|
e.child2 = null as *Expr;
|
|
e.child3 = null as *Expr;
|
|
e.refType = null as *TypeExpr;
|
|
e.refBlock = null as *Block;
|
|
e.genericCallee = "";
|
|
e.genericTypeArg0 = "";
|
|
e.genericTypeArg1 = "";
|
|
e.genericTypeArgCount = 0;
|
|
e.structName = "";
|
|
e.structFieldCount = 0;
|
|
e.callArgs = null as *ExprList;
|
|
e.callArgCount = 0;
|
|
e.matchArms = null as *MatchArm;
|
|
e.matchArmCount = 0;
|
|
return e;
|
|
}
|
|
|
|
|
|
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
|
|
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
|
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
|
e.tokKind = tkStringLiteral;
|
|
e.tokText = quoted;
|
|
return e;
|
|
}
|
|
|
|
func parserParseInterpFragment(exprStr: String) -> *Expr {
|
|
let lex: *Lexer = Lexer_Tokenize(exprStr);
|
|
var sub: Parser;
|
|
sub.tokens = lex.tokens;
|
|
sub.tokenCount = lex.tokenCount;
|
|
sub.pos = 0;
|
|
sub.diagCount = 0;
|
|
sub.diags = null as *ParserDiag;
|
|
sub.structInitAllowed = true;
|
|
return parserParseExpr(&sub);
|
|
}
|
|
|
|
func parserAppendPart(head: *ExprList, tail: *ExprList, e: *Expr) -> *ExprList {
|
|
// returns new tail; head updated via pointer trick not possible — return pair as side effect on first arg using double pointer?
|
|
// simpler: just inline in main
|
|
return tail;
|
|
}
|
|
|
|
func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
|
|
let text: String = tok.text;
|
|
let tlen: uint = bux_strlen(text);
|
|
if tlen < 3 as uint {
|
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
|
e.tokKind = tkStringLiteral;
|
|
e.tokText = text;
|
|
return e;
|
|
}
|
|
if text[0] as int != 102 {
|
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
|
e.tokKind = tkStringLiteral;
|
|
e.tokText = text;
|
|
return e;
|
|
}
|
|
if text[1] as int != 34 {
|
|
let e: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
|
e.tokKind = tkStringLiteral;
|
|
e.tokText = text;
|
|
return e;
|
|
}
|
|
let inner: String = bux_str_slice(text, 2, tlen - 3);
|
|
let innerLen: uint = bux_strlen(inner);
|
|
var head: *ExprList = null as *ExprList;
|
|
var tail: *ExprList = null as *ExprList;
|
|
var currentText: String = "";
|
|
var i: uint = 0;
|
|
var partCount: int = 0;
|
|
while i < innerLen {
|
|
let ch: int = inner[i] as int;
|
|
var handled: bool = false;
|
|
if ch == 92 {
|
|
if i + 1 < innerLen {
|
|
let nch: int = inner[i + 1] as int;
|
|
if nch == 123 {
|
|
currentText = String_Concat(currentText, "{");
|
|
i = i + 2;
|
|
handled = true;
|
|
} else {
|
|
if nch == 125 {
|
|
currentText = String_Concat(currentText, "}");
|
|
i = i + 2;
|
|
handled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if handled {
|
|
continue;
|
|
}
|
|
if ch == 123 {
|
|
let textPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
|
|
let textNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
textNode.expr = textPart;
|
|
textNode.next = null as *ExprList;
|
|
textNode.argName = "";
|
|
if head == null as *ExprList {
|
|
head = textNode;
|
|
tail = textNode;
|
|
} else {
|
|
tail.next = textNode;
|
|
tail = textNode;
|
|
}
|
|
partCount = partCount + 1;
|
|
currentText = "";
|
|
var j: uint = i + 1;
|
|
var depth: int = 1;
|
|
while j < innerLen {
|
|
if depth <= 0 {
|
|
break;
|
|
}
|
|
let cj: int = inner[j] as int;
|
|
if cj == 123 {
|
|
depth = depth + 1;
|
|
}
|
|
if cj == 125 {
|
|
depth = depth - 1;
|
|
}
|
|
j = j + 1;
|
|
}
|
|
if depth != 0 {
|
|
parserEmitDiag(p, tok.line, tok.column, "unmatched brace in string interpolation");
|
|
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
|
bad.tokKind = tkStringLiteral;
|
|
bad.tokText = "\"\"";
|
|
return bad;
|
|
}
|
|
let exprLen: uint = j - i - 2;
|
|
let exprStr: String = bux_str_slice(inner, i + 1, exprLen);
|
|
if bux_strlen(exprStr) == 0 {
|
|
parserEmitDiag(p, tok.line, tok.column, "empty interpolation");
|
|
let bad: *Expr = parserMakeExpr(ekLiteral, tok.line, tok.column);
|
|
bad.tokKind = tkStringLiteral;
|
|
bad.tokText = "\"\"";
|
|
return bad;
|
|
}
|
|
let frag: *Expr = parserParseInterpFragment(exprStr);
|
|
let fragNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
fragNode.expr = frag;
|
|
fragNode.next = null as *ExprList;
|
|
fragNode.argName = "";
|
|
if head == null as *ExprList {
|
|
head = fragNode;
|
|
tail = fragNode;
|
|
} else {
|
|
tail.next = fragNode;
|
|
tail = fragNode;
|
|
}
|
|
partCount = partCount + 1;
|
|
i = j;
|
|
continue;
|
|
}
|
|
let one: String = bux_str_slice(inner, i, 1);
|
|
currentText = String_Concat(currentText, one);
|
|
i = i + 1;
|
|
}
|
|
let lastPart: *Expr = parserMakeStringLitExpr(currentText, tok.line, tok.column);
|
|
let lastNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
lastNode.expr = lastPart;
|
|
lastNode.next = null as *ExprList;
|
|
lastNode.argName = "";
|
|
if head == null as *ExprList {
|
|
head = lastNode;
|
|
tail = lastNode;
|
|
} else {
|
|
tail.next = lastNode;
|
|
tail = lastNode;
|
|
}
|
|
partCount = partCount + 1;
|
|
if partCount == 1 {
|
|
return head.expr;
|
|
}
|
|
let e: *Expr = parserMakeExpr(ekStringInterp, tok.line, tok.column);
|
|
e.callArgs = head;
|
|
e.callArgCount = partCount;
|
|
return e;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Primary expressions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParsePrimary(p: *Parser) -> *Expr {
|
|
while parserCheck(p, tkNewLine) {
|
|
discard parserAdvance(p);
|
|
}
|
|
let tok: LexToken = parserCurToken(p);
|
|
let line: uint32 = tok.line;
|
|
let col: uint32 = tok.column;
|
|
let kind: int = tok.kind;
|
|
|
|
// Literals
|
|
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
|
|| kind == tkCharLiteral || kind == tkBoolLiteral {
|
|
discard parserAdvance(p);
|
|
if kind == tkStringLiteral && bux_strlen(tok.text) >= 2 as uint
|
|
&& tok.text[0] as int == 102 && tok.text[1] as int == 34 {
|
|
return parserParseStringInterp(p, tok);
|
|
}
|
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
|
e.tokKind = kind;
|
|
e.tokText = tok.text;
|
|
if kind == tkIntLiteral {
|
|
e.intValue = bux_str_to_int(tok.text) as int;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
// Identifier
|
|
if kind == tkIdent {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekIdent, line, col);
|
|
e.strValue = tok.text;
|
|
return e;
|
|
}
|
|
|
|
// self
|
|
if kind == tkSelf {
|
|
discard parserAdvance(p);
|
|
return parserMakeExpr(ekSelf, line, col);
|
|
}
|
|
|
|
// null
|
|
if kind == tkNull {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
|
e.tokKind = tkNull;
|
|
return e;
|
|
}
|
|
|
|
// sizeof(Type)
|
|
if kind == tkSizeOf {
|
|
discard parserAdvance(p);
|
|
discard parserExpect(p, tkLParen, "expected '(' after sizeof");
|
|
let e: *Expr = parserMakeExpr(ekSizeOf, line, col);
|
|
e.refType = parserParseType(p);
|
|
discard parserExpect(p, tkRParen, "expected ')' after sizeof type");
|
|
return e;
|
|
}
|
|
|
|
// spawn Callee(args)
|
|
if kind == tkSpawn {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekSpawn, line, col);
|
|
e.child1 = parserParsePrimary(p);
|
|
// Optional call arguments
|
|
if parserCheck(p, tkLParen) {
|
|
discard parserAdvance(p);
|
|
if !parserCheck(p, tkRParen) {
|
|
e.child2 = parserParseExpr(p);
|
|
if parserMatch(p, tkComma) {
|
|
e.child3 = parserParseExpr(p);
|
|
while parserMatch(p, tkComma) {
|
|
discard parserParseExpr(p);
|
|
}
|
|
}
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' after spawn arguments");
|
|
}
|
|
return e;
|
|
}
|
|
|
|
// #intrinsics
|
|
if kind >= tkHashLine && kind <= tkHashModule {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
|
|
e.intValue = kind;
|
|
return e;
|
|
}
|
|
|
|
// ( expr ) or (a, b, ...) tuple
|
|
if kind == tkLParen {
|
|
discard parserAdvance(p);
|
|
// Empty tuple ()
|
|
if parserCheck(p, tkRParen) {
|
|
discard parserAdvance(p);
|
|
let te: *Expr = parserMakeExpr(ekTuple, line, col);
|
|
te.callArgCount = 0;
|
|
return te;
|
|
}
|
|
let first: *Expr = parserParseExpr(p);
|
|
if parserCheck(p, tkComma) {
|
|
// Tuple expression
|
|
let te: *Expr = parserMakeExpr(ekTuple, line, col);
|
|
var firstArg: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
firstArg.expr = first;
|
|
firstArg.next = null as *ExprList;
|
|
var lastArg: *ExprList = firstArg;
|
|
var count: int = 1;
|
|
while parserCheck(p, tkComma) {
|
|
discard parserAdvance(p);
|
|
if parserCheck(p, tkRParen) { break; }
|
|
let elem: *Expr = parserParseExpr(p);
|
|
let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
node.expr = elem;
|
|
node.next = null as *ExprList;
|
|
lastArg.next = node;
|
|
lastArg = node;
|
|
count = count + 1;
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' to close tuple");
|
|
te.callArgs = firstArg;
|
|
te.callArgCount = count;
|
|
return te;
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')'");
|
|
return first;
|
|
}
|
|
|
|
// Empty-param closure: `||` is lexed as tkPipePipe (logical-or token).
|
|
// As a primary it can only mean a zero-param closure: || -> T { ... }
|
|
if kind == tkPipePipe {
|
|
return parserParseEmptyClosure(p);
|
|
}
|
|
|
|
// Closure: |params| -> Ret { body }
|
|
if kind == tkPipe {
|
|
return parserParseClosure(p);
|
|
}
|
|
|
|
// unsafe { ... } — unsafe block expression
|
|
if kind == tkUnsafe {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekBlock, line, col);
|
|
e.boolValue = true; // marks this block as unsafe
|
|
e.refBlock = parserParseBlock(p);
|
|
return e;
|
|
}
|
|
|
|
// match expr { arms }
|
|
if kind == tkMatch {
|
|
return parserParseMatchExpr(p);
|
|
}
|
|
|
|
parserEmitDiag(p, line, col, "expected expression");
|
|
return parserMakeExpr(ekLiteral, line, col);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Patterns (for match arms)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
|
|
let pat: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
|
pat.kind = kind;
|
|
pat.line = line;
|
|
pat.column = col;
|
|
pat.patIdent = "";
|
|
pat.patLitKind = 0;
|
|
pat.patLitText = "";
|
|
pat.patRangeInclusive = false;
|
|
pat.patEnumPath = "";
|
|
pat.patStructName = "";
|
|
pat.patFieldName = "";
|
|
pat.patChild1 = null as *Pattern;
|
|
pat.patChild2 = null as *Pattern;
|
|
pat.patArgs = null as *Pattern;
|
|
pat.patNext = null as *Pattern;
|
|
return pat;
|
|
}
|
|
|
|
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
let tok: LexToken = parserCurToken(p);
|
|
let line: uint32 = tok.line;
|
|
let col: uint32 = tok.column;
|
|
let kind: int = tok.kind;
|
|
|
|
// _
|
|
if kind == tkUnderscore {
|
|
discard parserAdvance(p);
|
|
return parserMakePattern(pkWildcard, line, col);
|
|
}
|
|
|
|
// Literals
|
|
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
|
|| kind == tkCharLiteral || kind == tkBoolLiteral {
|
|
discard parserAdvance(p);
|
|
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
|
|
pat.patLitKind = kind;
|
|
pat.patLitText = tok.text;
|
|
return pat;
|
|
}
|
|
|
|
// Ident / enum path / true|false as names
|
|
if kind == tkIdent {
|
|
discard parserAdvance(p);
|
|
let name: String = tok.text;
|
|
if String_Eq(name, "true") || String_Eq(name, "false") {
|
|
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
|
|
pat.patLitKind = tkBoolLiteral;
|
|
pat.patLitText = name;
|
|
return pat;
|
|
}
|
|
// Enum path: Enum::Variant or Enum::Variant(...)
|
|
if parserCheck(p, tkColonColon) {
|
|
var path: String = name;
|
|
while parserCheck(p, tkColonColon) {
|
|
discard parserAdvance(p);
|
|
let seg: LexToken = parserExpectIdentOrKeyword(p, "expected identifier in pattern path");
|
|
path = String_Concat(path, "::");
|
|
path = String_Concat(path, seg.text);
|
|
}
|
|
// Optional (args) for algebraic variants — store as patArgs linked list
|
|
var enumArgs: *Pattern = null as *Pattern;
|
|
var lastArg: *Pattern = null as *Pattern;
|
|
if parserCheck(p, tkLParen) {
|
|
discard parserAdvance(p);
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
let argPat: *Pattern = parserParsePattern(p);
|
|
if enumArgs == null as *Pattern {
|
|
enumArgs = argPat;
|
|
lastArg = argPat;
|
|
} else {
|
|
lastArg.patNext = argPat;
|
|
lastArg = argPat;
|
|
}
|
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
|
else { break; }
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' to close enum pattern");
|
|
}
|
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
|
pat.patEnumPath = path;
|
|
pat.patArgs = enumArgs;
|
|
return pat;
|
|
}
|
|
// Struct pattern: Point { x: a, y: b } or shorthand Point { x, y }
|
|
if parserCheck(p, tkLBrace) {
|
|
discard parserAdvance(p);
|
|
var fieldHead: *Pattern = null as *Pattern;
|
|
var fieldTail: *Pattern = null as *Pattern;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
if parserCheck(p, tkRBrace) { break; }
|
|
let ftok: LexToken = parserExpectIdentOrKeyword(p, "expected field name in struct pattern");
|
|
let fieldName: String = ftok.text;
|
|
var fieldPat: *Pattern = null as *Pattern;
|
|
if parserCheck(p, tkColon) {
|
|
discard parserAdvance(p);
|
|
fieldPat = parserParsePattern(p);
|
|
} else {
|
|
// Shorthand { x } → { x: x }
|
|
fieldPat = parserMakePattern(pkIdent, line, col);
|
|
fieldPat.patIdent = fieldName;
|
|
}
|
|
fieldPat.patFieldName = fieldName;
|
|
if fieldHead == null as *Pattern {
|
|
fieldHead = fieldPat;
|
|
fieldTail = fieldPat;
|
|
} else {
|
|
fieldTail.patNext = fieldPat;
|
|
fieldTail = fieldPat;
|
|
}
|
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
|
else { break; }
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}' to close struct pattern");
|
|
let spat: *Pattern = parserMakePattern(pkStruct, line, col);
|
|
spat.patStructName = name;
|
|
spat.patArgs = fieldHead;
|
|
return spat;
|
|
}
|
|
// Bare name with (args): Variant(...) treated as single-segment enum
|
|
if parserCheck(p, tkLParen) {
|
|
discard parserAdvance(p);
|
|
var bareArgs: *Pattern = null as *Pattern;
|
|
var bareLast: *Pattern = null as *Pattern;
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
let argPat: *Pattern = parserParsePattern(p);
|
|
if bareArgs == null as *Pattern {
|
|
bareArgs = argPat;
|
|
bareLast = argPat;
|
|
} else {
|
|
bareLast.patNext = argPat;
|
|
bareLast = argPat;
|
|
}
|
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
|
else { break; }
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
|
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
|
pat.patEnumPath = name;
|
|
pat.patArgs = bareArgs;
|
|
return pat;
|
|
}
|
|
// Ident binding / catch-all name
|
|
let pat: *Pattern = parserMakePattern(pkIdent, line, col);
|
|
pat.patIdent = name;
|
|
return pat;
|
|
}
|
|
|
|
// Tuple pattern: (a, b)
|
|
if kind == tkLParen {
|
|
discard parserAdvance(p);
|
|
var head: *Pattern = null as *Pattern;
|
|
var tail: *Pattern = null as *Pattern;
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
let elem: *Pattern = parserParsePattern(p);
|
|
if head == null as *Pattern {
|
|
head = elem;
|
|
tail = elem;
|
|
} else {
|
|
tail.patNext = elem;
|
|
tail = elem;
|
|
}
|
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
|
else { break; }
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')' to close tuple pattern");
|
|
let tpat: *Pattern = parserMakePattern(pkTuple, line, col);
|
|
tpat.patArgs = head;
|
|
return tpat;
|
|
}
|
|
|
|
parserEmitDiag(p, line, col, "expected pattern");
|
|
return parserMakePattern(pkWildcard, line, col);
|
|
}
|
|
|
|
func parserParsePattern(p: *Parser) -> *Pattern {
|
|
let locTok: LexToken = parserCurToken(p);
|
|
let line: uint32 = locTok.line;
|
|
let col: uint32 = locTok.column;
|
|
let left: *Pattern = parserParsePrimaryPattern(p);
|
|
// Range pattern: lo..hi or lo..=hi
|
|
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
|
|
let inclusive: bool = parserCheck(p, tkDotDotEqual);
|
|
discard parserAdvance(p);
|
|
let right: *Pattern = parserParsePrimaryPattern(p);
|
|
let pat: *Pattern = parserMakePattern(pkRange, line, col);
|
|
pat.patRangeInclusive = inclusive;
|
|
pat.patChild1 = left;
|
|
pat.patChild2 = right;
|
|
return pat;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
// match subject { pat => body, ... }
|
|
func parserParseMatchExpr(p: *Parser) -> *Expr {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserAdvance(p); // match
|
|
p.structInitAllowed = false;
|
|
let subject: *Expr = parserParseExpr(p);
|
|
p.structInitAllowed = true;
|
|
// Allow newline before '{' (needed for `let x = match n \n { ... }`)
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
|
|
|
|
var firstArm: *MatchArm = null as *MatchArm;
|
|
var lastArm: *MatchArm = null as *MatchArm;
|
|
var armCount: int = 0;
|
|
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
|
|
let armLine: uint32 = parserCurToken(p).line;
|
|
let armCol: uint32 = parserCurToken(p).column;
|
|
let mp: int = p.pos;
|
|
let pat: *Pattern = parserParsePattern(p);
|
|
discard parserExpect(p, tkFatArrow, "expected '=>' in match arm");
|
|
let body: *Expr = parserParseExpr(p);
|
|
let arm: *MatchArm = bux_alloc(sizeof(MatchArm)) as *MatchArm;
|
|
arm.line = armLine;
|
|
arm.column = armCol;
|
|
arm.pattern = pat;
|
|
arm.body = body;
|
|
arm.next = null as *MatchArm;
|
|
if firstArm == null as *MatchArm {
|
|
firstArm = arm;
|
|
lastArm = arm;
|
|
} else {
|
|
lastArm.next = arm;
|
|
lastArm = arm;
|
|
}
|
|
armCount = armCount + 1;
|
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
|
if p.pos == mp { discard parserAdvance(p); }
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}' to close match");
|
|
|
|
let e: *Expr = parserMakeExpr(ekMatch, line, col);
|
|
e.child1 = subject;
|
|
e.matchArms = firstArm;
|
|
e.matchArmCount = armCount;
|
|
return e;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Closure: |params| -> Ret { body }
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Zero-param closure when written as `||` (single tkPipePipe token from lexer)
|
|
func parserParseEmptyClosure(p: *Parser) -> *Expr {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserAdvance(p); // ||
|
|
let e: *Expr = parserMakeExpr(ekClosure, line, col);
|
|
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
params.kind = dkFunc;
|
|
params.paramCount = 0;
|
|
e.closureParams = params;
|
|
if parserCheck(p, tkArrow) {
|
|
discard parserAdvance(p);
|
|
e.refType = parserParseType(p);
|
|
}
|
|
e.refBlock = parserParseBlock(p);
|
|
return e;
|
|
}
|
|
|
|
func parserParseClosure(p: *Parser) -> *Expr {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkPipe, "expected '|' to start closure params");
|
|
|
|
let e: *Expr = parserMakeExpr(ekClosure, line, col);
|
|
let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
params.kind = dkFunc;
|
|
params.paramCount = 0;
|
|
|
|
// Parse params: name: Type
|
|
while !parserCheck(p, tkPipe) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
}
|
|
if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
|
|
break;
|
|
}
|
|
if params.paramCount >= 9 { break; }
|
|
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure");
|
|
discard parserExpect(p, tkColon, "expected ':' in closure parameter");
|
|
let ptype: *TypeExpr = parserParseType(p);
|
|
|
|
let idx: int = params.paramCount;
|
|
if idx == 0 {
|
|
params.param0.name = nameTok.text;
|
|
params.param0.refParamType = ptype;
|
|
} else if idx == 1 {
|
|
params.param1.name = nameTok.text;
|
|
params.param1.refParamType = ptype;
|
|
} else if idx == 2 {
|
|
params.param2.name = nameTok.text;
|
|
params.param2.refParamType = ptype;
|
|
} else if idx == 3 {
|
|
params.param3.name = nameTok.text;
|
|
params.param3.refParamType = ptype;
|
|
} else if idx == 4 {
|
|
params.param4.name = nameTok.text;
|
|
params.param4.refParamType = ptype;
|
|
} else if idx == 5 {
|
|
params.param5.name = nameTok.text;
|
|
params.param5.refParamType = ptype;
|
|
} else if idx == 6 {
|
|
params.param6.name = nameTok.text;
|
|
params.param6.refParamType = ptype;
|
|
} else if idx == 7 {
|
|
params.param7.name = nameTok.text;
|
|
params.param7.refParamType = ptype;
|
|
} else if idx == 8 {
|
|
params.param8.name = nameTok.text;
|
|
params.param8.refParamType = ptype;
|
|
}
|
|
params.paramCount = params.paramCount + 1;
|
|
if parserMatch(p, tkComma) { continue; }
|
|
break;
|
|
}
|
|
|
|
discard parserExpect(p, tkPipe, "expected '|' to close closure params");
|
|
e.closureParams = params;
|
|
|
|
// Optional return type: -> Type
|
|
if parserMatch(p, tkArrow) {
|
|
e.refType = parserParseType(p);
|
|
}
|
|
|
|
// Body: { ... }
|
|
e.refBlock = parserParseBlock(p);
|
|
return e;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Postfix: call, index, field access, as, is, ?
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParsePostfixExpr(p: *Parser) -> *Expr {
|
|
var left: *Expr = parserParsePrimary(p);
|
|
while true {
|
|
let kind: int = parserPeek(p, 0);
|
|
|
|
// Call: expr(args)
|
|
if kind == tkLParen {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let e: *Expr = parserMakeExpr(ekCall, line, col);
|
|
e.child1 = left;
|
|
// Parse all arguments into linked list
|
|
var argCount: int = 0;
|
|
var firstArg: *ExprList = null as *ExprList;
|
|
var lastArg: *ExprList = null as *ExprList;
|
|
while !parserCheck(p, tkRParen) {
|
|
var argExpr: *Expr = null as *Expr;
|
|
var argName: String = "";
|
|
// Named argument: name: value
|
|
if parserPeek(p, 0) == tkIdent && parserPeek(p, 1) == tkColon {
|
|
let nameTok: LexToken = parserCurToken(p);
|
|
argName = nameTok.text;
|
|
discard parserAdvance(p); // ident
|
|
discard parserAdvance(p); // :
|
|
argExpr = parserParseExpr(p);
|
|
} else {
|
|
argExpr = parserParseExpr(p);
|
|
}
|
|
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
|
argNode.expr = argExpr;
|
|
argNode.next = null as *ExprList;
|
|
argNode.argName = argName;
|
|
if firstArg == null as *ExprList {
|
|
firstArg = argNode;
|
|
lastArg = argNode;
|
|
} else {
|
|
lastArg.next = argNode;
|
|
lastArg = argNode;
|
|
}
|
|
argCount = argCount + 1;
|
|
if !parserMatch(p, tkComma) { break; }
|
|
}
|
|
e.callArgs = firstArg;
|
|
e.callArgCount = argCount;
|
|
discard parserExpect(p, tkRParen, "expected ')'");
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// Generic call: Func<Type>(args) or Type<T> { ... }
|
|
if kind == tkLt {
|
|
if left.kind == ekIdent && parserIsTypeArgListAhead(p) {
|
|
discard parserAdvance(p); // <
|
|
let ta0: LexToken = parserExpect(p, tkIdent, "expected type argument");
|
|
left.genericCallee = left.strValue;
|
|
left.genericTypeArg0 = ta0.text;
|
|
left.genericTypeArgCount = 1;
|
|
if parserMatch(p, tkComma) {
|
|
let ta1: LexToken = parserExpect(p, tkIdent, "expected type argument");
|
|
left.genericTypeArg1 = ta1.text;
|
|
left.genericTypeArgCount = 2;
|
|
}
|
|
discard parserExpect(p, tkGt, "expected '>' to close type arguments");
|
|
// After generic args, continue loop to handle call or struct init
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Index: expr[expr]
|
|
if kind == tkLBracket {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let e: *Expr = parserMakeExpr(ekIndex, line, col);
|
|
e.child1 = left;
|
|
e.child2 = parserParseExpr(p);
|
|
discard parserExpect(p, tkRBracket, "expected ']'");
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// .await
|
|
if kind == tkDot {
|
|
if parserPeek(p, 1) == tkAwait {
|
|
discard parserAdvance(p); // .
|
|
discard parserAdvance(p); // await
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let e: *Expr = parserMakeExpr(ekAwait, line, col);
|
|
e.child1 = left;
|
|
left = e;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Field: expr.name or tuple index expr.0 / expr.1
|
|
if kind == tkDot {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
if parserCheck(p, tkIntLiteral) {
|
|
let idxTok: LexToken = parserCurToken(p);
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekField, line, col);
|
|
e.child1 = left;
|
|
e.strValue = String_Concat("_", idxTok.text);
|
|
left = e;
|
|
continue;
|
|
}
|
|
let name: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
|
|
let e: *Expr = parserMakeExpr(ekField, line, col);
|
|
e.child1 = left;
|
|
e.strValue = name.text;
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// Path: expr::name
|
|
if kind == tkColonColon {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let name: LexToken = parserExpectIdentOrKeyword(p, "expected path segment");
|
|
let e: *Expr = parserMakeExpr(ekField, line, col);
|
|
e.child1 = left;
|
|
e.strValue = name.text;
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// as, is
|
|
if kind == tkAs || kind == tkIs {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let ek: int = 0;
|
|
if kind == tkAs { ek = ekCast; } else { ek = ekIs; }
|
|
let e: *Expr = parserMakeExpr(ek, line, col);
|
|
e.child1 = left;
|
|
e.refType = parserParseType(p);
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// ? (try operator)
|
|
if kind == tkQuestion {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let e: *Expr = parserMakeExpr(ekTry, line, col);
|
|
e.child1 = left;
|
|
left = e;
|
|
continue;
|
|
}
|
|
|
|
// ! (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;
|
|
// Propagate generic type args from the identifier
|
|
e.genericTypeArg0 = left.genericTypeArg0;
|
|
e.genericTypeArg1 = left.genericTypeArg1;
|
|
e.genericTypeArgCount = left.genericTypeArgCount;
|
|
left = e;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Binary expression (precedence climbing)
|
|
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserPrecedence(op: int) -> int {
|
|
// Assignment operators are parsed by parserParseAssign, not here
|
|
if op == tkPipePipe { return 2; }
|
|
if op == tkAmpAmp { return 3; }
|
|
if op == tkPipe { return 4; }
|
|
if op == tkCaret { return 5; }
|
|
if op == tkAmp { return 6; }
|
|
if op == tkEq || op == tkNe { return 7; }
|
|
if op == tkLt || op == tkLe || op == tkGt || op == tkGe { return 8; }
|
|
if op == tkShl || op == tkShr { return 9; }
|
|
if op == tkPlus || op == tkMinus { return 10; }
|
|
if op == tkStar || op == tkSlash || op == tkPercent { return 11; }
|
|
if op == tkStarStar { return 12; }
|
|
return 0;
|
|
}
|
|
|
|
func parserParseUnary(p: *Parser) -> *Expr {
|
|
while parserCheck(p, tkNewLine) {
|
|
discard parserAdvance(p);
|
|
}
|
|
let tok: LexToken = parserCurToken(p);
|
|
let line: uint32 = tok.line;
|
|
let col: uint32 = tok.column;
|
|
let kind: int = tok.kind;
|
|
|
|
// -expr, !expr, ~expr, *expr, &expr
|
|
if kind == tkMinus || kind == tkBang || kind == tkTilde || kind == tkStar || kind == tkAmp {
|
|
discard parserAdvance(p);
|
|
let e: *Expr = parserMakeExpr(ekUnary, line, col);
|
|
e.intValue = kind;
|
|
e.child1 = parserParseUnary(p);
|
|
return e;
|
|
}
|
|
|
|
return parserParsePostfixExpr(p);
|
|
}
|
|
|
|
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
|
|
var left: *Expr = parserParseUnary(p);
|
|
while true {
|
|
while parserCheck(p, tkNewLine) {
|
|
discard parserAdvance(p);
|
|
}
|
|
let op: int = parserPeek(p, 0);
|
|
let prec: int = parserPrecedence(op);
|
|
if prec < minPrec { break; }
|
|
let opTok: LexToken = parserAdvance(p);
|
|
let line: uint32 = opTok.line;
|
|
let col: uint32 = opTok.column;
|
|
let nextMinPrec: int = prec + 1;
|
|
let right: *Expr = parserParseBinaryPrec(p, nextMinPrec);
|
|
let e: *Expr = parserMakeExpr(ekBinary, line, col);
|
|
e.intValue = opTok.kind;
|
|
e.child1 = left;
|
|
e.child2 = right;
|
|
left = e;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
func parserParseBinary(p: *Parser) -> *Expr {
|
|
return parserParseBinaryPrec(p, 1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Range: lo .. hi or lo ..= hi
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseRange(p: *Parser) -> *Expr {
|
|
var left: *Expr = parserParseBinary(p);
|
|
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
|
|
let inclusive: bool = parserCheck(p, tkDotDotEqual);
|
|
let opTok: LexToken = parserAdvance(p);
|
|
let right: *Expr = parserParseBinary(p);
|
|
let e: *Expr = parserMakeExpr(ekRange, opTok.line, opTok.column);
|
|
e.child1 = left;
|
|
e.child2 = right;
|
|
e.boolValue = inclusive;
|
|
return e;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Ternary: cond ? then : else
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseTernary(p: *Parser) -> *Expr {
|
|
var left: *Expr = parserParseRange(p);
|
|
if parserMatch(p, tkQuestion) {
|
|
let thenExpr: *Expr = parserParseExpr(p);
|
|
discard parserExpect(p, tkColon, "expected ':' in ternary");
|
|
let elseExpr: *Expr = parserParseExpr(p);
|
|
let e: *Expr = parserMakeExpr(ekTernary, left.line, left.column);
|
|
e.child1 = left;
|
|
e.child2 = thenExpr;
|
|
e.child3 = elseExpr;
|
|
return e;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Assignment: target = value (right-associative)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseAssign(p: *Parser) -> *Expr {
|
|
let left: *Expr = parserParseTernary(p);
|
|
let op: int = parserPeek(p, 0);
|
|
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign {
|
|
let opTok: LexToken = parserAdvance(p);
|
|
let right: *Expr = parserParseAssign(p);
|
|
let e: *Expr = parserMakeExpr(ekAssign, opTok.line, opTok.column);
|
|
e.intValue = opTok.kind;
|
|
e.child1 = left;
|
|
e.child2 = right;
|
|
return e;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Top-level expression
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseExpr(p: *Parser) -> *Expr {
|
|
return parserParseAssign(p);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Statements
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseStmt(p: *Parser) -> *Stmt {
|
|
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);
|
|
}
|
|
var init: *Expr = null as *Expr;
|
|
if parserMatch(p, tkAssign) {
|
|
init = parserParseExpr(p);
|
|
} else if !isVar {
|
|
discard parserExpect(p, tkAssign, "expected '=' in let statement");
|
|
init = parserParseExpr(p);
|
|
}
|
|
parserMatch(p, tkSemicolon); // optional ;
|
|
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skLet;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.strValue = nameTok.text;
|
|
s.boolValue = isVar;
|
|
s.child1 = init;
|
|
s.refStmtType = typeExpr;
|
|
return s;
|
|
}
|
|
|
|
// discard expr
|
|
if kind == tkDiscard {
|
|
discard parserAdvance(p);
|
|
var val: *Expr = null as *Expr;
|
|
if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkRBrace) && !parserCheck(p, tkNewLine) {
|
|
val = parserParseExpr(p);
|
|
}
|
|
parserMatch(p, tkSemicolon);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skExpr;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = val;
|
|
return s;
|
|
}
|
|
|
|
// defer expr
|
|
if kind == tkDefer {
|
|
discard parserAdvance(p);
|
|
let val: *Expr = parserParseExpr(p);
|
|
parserMatch(p, tkSemicolon);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skDefer;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = val;
|
|
return s;
|
|
}
|
|
|
|
// if
|
|
if kind == tkIf {
|
|
discard parserAdvance(p);
|
|
p.structInitAllowed = false;
|
|
let cond: *Expr = parserParseExpr(p);
|
|
p.structInitAllowed = true;
|
|
let thenBlock: *Block = parserParseBlock(p);
|
|
var elseBlock: *Block = null as *Block;
|
|
while parserCheck(p, tkNewLine) {
|
|
discard parserAdvance(p);
|
|
}
|
|
if parserMatch(p, tkElse) {
|
|
if parserCheck(p, tkIf) {
|
|
// else if → parse the if statement, wrap in a synthetic block
|
|
let innerIf: *Stmt = parserParseStmt(p);
|
|
elseBlock = bux_alloc(sizeof(Block)) as *Block;
|
|
elseBlock.line = innerIf.line;
|
|
elseBlock.column = innerIf.column;
|
|
elseBlock.stmtCount = 1;
|
|
elseBlock.firstStmt = innerIf;
|
|
elseBlock.lastStmt = innerIf;
|
|
innerIf.nextStmt = null as *Stmt;
|
|
} else {
|
|
elseBlock = parserParseBlock(p);
|
|
}
|
|
}
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skIf;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = cond;
|
|
s.refStmtBlock = thenBlock;
|
|
s.refStmtElse = elseBlock;
|
|
return s;
|
|
}
|
|
|
|
// while
|
|
if kind == tkWhile {
|
|
discard parserAdvance(p);
|
|
p.structInitAllowed = false;
|
|
let cond: *Expr = parserParseExpr(p);
|
|
p.structInitAllowed = true;
|
|
let body: *Block = parserParseBlock(p);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skWhile;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = cond;
|
|
s.refStmtBlock = body;
|
|
return s;
|
|
}
|
|
|
|
// loop
|
|
if kind == tkLoop {
|
|
discard parserAdvance(p);
|
|
let body: *Block = parserParseBlock(p);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skLoop;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.refStmtBlock = body;
|
|
return s;
|
|
}
|
|
|
|
// for
|
|
if kind == tkFor {
|
|
discard parserAdvance(p);
|
|
let varName: LexToken = parserExpect(p, tkIdent, "expected loop variable");
|
|
discard parserExpect(p, tkIn, "expected 'in'");
|
|
p.structInitAllowed = false;
|
|
let iter: *Expr = parserParseExpr(p);
|
|
p.structInitAllowed = true;
|
|
let body: *Block = parserParseBlock(p);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skFor;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.strValue = varName.text;
|
|
s.child1 = iter;
|
|
s.refStmtBlock = body;
|
|
return s;
|
|
}
|
|
|
|
// return
|
|
if kind == tkReturn {
|
|
discard parserAdvance(p);
|
|
var value: *Expr = null as *Expr;
|
|
if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkNewLine) && !parserCheck(p, tkRBrace) {
|
|
value = parserParseExpr(p);
|
|
}
|
|
parserMatch(p, tkSemicolon);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skReturn;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = value;
|
|
return s;
|
|
}
|
|
|
|
// break / continue
|
|
if kind == tkBreak || kind == tkContinue {
|
|
let sk: int = 0;
|
|
if kind == tkBreak { sk = skBreak; } else { sk = skContinue; }
|
|
discard parserAdvance(p);
|
|
parserMatch(p, tkSemicolon);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = sk;
|
|
s.line = line;
|
|
s.column = col;
|
|
return s;
|
|
}
|
|
|
|
// match — expression statement (arms fully parsed)
|
|
if kind == tkMatch {
|
|
let matchExpr: *Expr = parserParseMatchExpr(p);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skExpr;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = matchExpr;
|
|
return s;
|
|
}
|
|
|
|
// switch
|
|
if kind == tkSwitch {
|
|
discard parserAdvance(p);
|
|
p.structInitAllowed = false;
|
|
let subject: *Expr = parserParseExpr(p);
|
|
p.structInitAllowed = true;
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
discard parserExpect(p, tkLBrace, "expected '{' to start switch body");
|
|
var caseBlock: *Block = bux_alloc(sizeof(Block)) as *Block;
|
|
caseBlock.line = line;
|
|
caseBlock.column = col;
|
|
caseBlock.stmtCount = 0;
|
|
caseBlock.firstStmt = null as *Stmt;
|
|
caseBlock.lastStmt = null as *Stmt;
|
|
var defaultBody: *Block = null as *Block;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
|
|
if parserCheck(p, tkDefault) {
|
|
discard parserAdvance(p);
|
|
discard parserExpect(p, tkColon, "expected ':' after default");
|
|
let defaultStmt: *Stmt = parserParseStmt(p);
|
|
defaultBody = bux_alloc(sizeof(Block)) as *Block;
|
|
defaultBody.line = defaultStmt.line;
|
|
defaultBody.column = defaultStmt.column;
|
|
defaultBody.stmtCount = 1;
|
|
defaultBody.firstStmt = defaultStmt;
|
|
defaultBody.lastStmt = defaultStmt;
|
|
defaultStmt.nextStmt = null as *Stmt;
|
|
continue;
|
|
}
|
|
if parserCheck(p, tkCase) {
|
|
discard parserAdvance(p);
|
|
let caseVal: *Expr = parserParseExpr(p);
|
|
discard parserExpect(p, tkColon, "expected ':' after case value");
|
|
let caseStmt: *Stmt = parserParseStmt(p);
|
|
let caseBody: *Block = bux_alloc(sizeof(Block)) as *Block;
|
|
caseBody.line = caseStmt.line;
|
|
caseBody.column = caseStmt.column;
|
|
caseBody.stmtCount = 1;
|
|
caseBody.firstStmt = caseStmt;
|
|
caseBody.lastStmt = caseStmt;
|
|
caseStmt.nextStmt = null as *Stmt;
|
|
let c: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
c.kind = skExpr;
|
|
c.line = caseVal.line;
|
|
c.column = caseVal.column;
|
|
c.child1 = caseVal;
|
|
c.refStmtBlock = caseBody;
|
|
c.nextStmt = null as *Stmt;
|
|
if caseBlock.firstStmt == null as *Stmt {
|
|
caseBlock.firstStmt = c;
|
|
caseBlock.lastStmt = c;
|
|
} else {
|
|
caseBlock.lastStmt.nextStmt = c;
|
|
caseBlock.lastStmt = c;
|
|
}
|
|
caseBlock.stmtCount = caseBlock.stmtCount + 1;
|
|
continue;
|
|
}
|
|
discard parserAdvance(p);
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}' to close switch");
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skSwitch;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = subject;
|
|
s.refStmtBlock = caseBlock;
|
|
s.refStmtElse = defaultBody;
|
|
return s;
|
|
}
|
|
|
|
// Expression statement
|
|
if kind == tkNewLine || kind == tkSemicolon {
|
|
discard parserAdvance(p);
|
|
return null as *Stmt;
|
|
}
|
|
|
|
let expr: *Expr = parserParseExpr(p);
|
|
parserMatch(p, tkSemicolon);
|
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
|
s.kind = skExpr;
|
|
s.line = line;
|
|
s.column = col;
|
|
s.child1 = expr;
|
|
return s;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Block: { stmt* }
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseBlock(p: *Parser) -> *Block {
|
|
discard parserExpect(p, tkLBrace, "expected '{'");
|
|
let b: *Block = bux_alloc(sizeof(Block)) as *Block;
|
|
b.line = parserCurToken(p).line;
|
|
b.column = parserCurToken(p).column;
|
|
b.stmtCount = 0;
|
|
b.firstStmt = null as *Stmt;
|
|
b.lastStmt = null as *Stmt;
|
|
|
|
// Build AST with parserParseStmt (may not consume all tokens)
|
|
var blockPos: int = p.pos;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
}
|
|
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile {
|
|
break;
|
|
}
|
|
let beforePos: int = p.pos;
|
|
let s: *Stmt = parserParseStmt(p);
|
|
if p.pos == beforePos {
|
|
discard parserAdvance(p);
|
|
continue;
|
|
}
|
|
if s != null as *Stmt {
|
|
s.nextStmt = null as *Stmt;
|
|
if b.firstStmt == null as *Stmt {
|
|
b.firstStmt = s;
|
|
b.lastStmt = s;
|
|
} else {
|
|
b.lastStmt.nextStmt = s;
|
|
b.lastStmt = s;
|
|
}
|
|
b.stmtCount = b.stmtCount + 1;
|
|
}
|
|
}
|
|
// Reliable token consumption: reset to after { and use depth counter
|
|
p.pos = blockPos;
|
|
var depth: int = 1;
|
|
while depth > 0 && parserPeek(p, 0) != tkEndOfFile {
|
|
if parserCheck(p, tkLBrace) { depth = depth + 1; }
|
|
else if parserCheck(p, tkRBrace) { depth = depth - 1; }
|
|
discard parserAdvance(p);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Function parameters
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseParamList(p: *Parser) -> *Decl {
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkFunc;
|
|
d.paramCount = 0;
|
|
|
|
discard parserExpect(p, tkLParen, "expected '('");
|
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
}
|
|
if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile {
|
|
break;
|
|
}
|
|
if d.paramCount >= 9 { break; }
|
|
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
|
|
discard parserExpect(p, tkColon, "expected ':' in parameter");
|
|
let ptype: *TypeExpr = parserParseType(p);
|
|
var defExpr: *Expr = null as *Expr;
|
|
if parserMatch(p, tkAssign) {
|
|
defExpr = parserParseExpr(p);
|
|
}
|
|
|
|
if d.paramCount == 0 {
|
|
d.param0.name = nameTok.text;
|
|
d.param0.refParamType = ptype;
|
|
d.param0.defaultExpr = defExpr;
|
|
} else if d.paramCount == 1 {
|
|
d.param1.name = nameTok.text;
|
|
d.param1.refParamType = ptype;
|
|
d.param1.defaultExpr = defExpr;
|
|
} else if d.paramCount == 2 {
|
|
d.param2.name = nameTok.text;
|
|
d.param2.refParamType = ptype;
|
|
d.param2.defaultExpr = defExpr;
|
|
} else if d.paramCount == 3 {
|
|
d.param3.name = nameTok.text;
|
|
d.param3.refParamType = ptype;
|
|
d.param3.defaultExpr = defExpr;
|
|
} else if d.paramCount == 4 {
|
|
d.param4.name = nameTok.text;
|
|
d.param4.refParamType = ptype;
|
|
d.param4.defaultExpr = defExpr;
|
|
} else if d.paramCount == 5 {
|
|
d.param5.name = nameTok.text;
|
|
d.param5.refParamType = ptype;
|
|
d.param5.defaultExpr = defExpr;
|
|
} else if d.paramCount == 6 {
|
|
d.param6.name = nameTok.text;
|
|
d.param6.refParamType = ptype;
|
|
d.param6.defaultExpr = defExpr;
|
|
} else if d.paramCount == 7 {
|
|
d.param7.name = nameTok.text;
|
|
d.param7.refParamType = ptype;
|
|
d.param7.defaultExpr = defExpr;
|
|
} else if d.paramCount == 8 {
|
|
d.param8.name = nameTok.text;
|
|
d.param8.refParamType = ptype;
|
|
d.param8.defaultExpr = defExpr;
|
|
}
|
|
d.paramCount = d.paramCount + 1;
|
|
|
|
if parserMatch(p, tkComma) { continue; }
|
|
break;
|
|
}
|
|
discard parserExpect(p, tkRParen, "expected ')'");
|
|
return d;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type parameters: <T: Bound, U: Bound2>
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseTypeParams(p: *Parser, d: *Decl) {
|
|
if !parserCheck(p, tkLt) { return; }
|
|
discard parserAdvance(p);
|
|
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
|
|
d.typeParam0 = tp0.text;
|
|
d.typeParamCount = 1;
|
|
if parserMatch(p, tkColon) {
|
|
let bound0: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
|
|
d.typeParam0Bound = bound0.text;
|
|
}
|
|
if parserMatch(p, tkComma) {
|
|
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
|
|
d.typeParam1 = tp1.text;
|
|
d.typeParamCount = 2;
|
|
if parserMatch(p, tkColon) {
|
|
let bound1: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
|
|
d.typeParam1Bound = bound1.text;
|
|
}
|
|
}
|
|
discard parserExpect(p, tkGt, "expected '>'");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Declarations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkFunc, "expected 'func'");
|
|
|
|
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected function name");
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkFunc;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
d.isAsync = isAsync;
|
|
d.strValue = nameTok.text;
|
|
|
|
// Type params <T: Bound, U: Bound2>
|
|
parserParseTypeParams(p, d);
|
|
|
|
// Params
|
|
let params: *Decl = parserParseParamList(p);
|
|
d.paramCount = params.paramCount;
|
|
d.param0 = params.param0;
|
|
d.param1 = params.param1;
|
|
d.param2 = params.param2;
|
|
d.param3 = params.param3;
|
|
d.param4 = params.param4;
|
|
d.param5 = params.param5;
|
|
d.param6 = params.param6;
|
|
d.param7 = params.param7;
|
|
d.param8 = params.param8;
|
|
|
|
// Return type
|
|
if parserMatch(p, tkArrow) {
|
|
d.retType = parserParseType(p);
|
|
}
|
|
|
|
// Body
|
|
if !isExtern && parserCheck(p, tkLBrace) {
|
|
d.refBody = parserParseBlock(p);
|
|
} else {
|
|
d.refBody = null as *Block;
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkStruct, "expected 'struct'");
|
|
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected struct name");
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.fields = bux_alloc(256 as uint * sizeof(StructField)) as *StructField;
|
|
if d.fields == null as *StructField {
|
|
PrintLine("ERROR: bux_alloc returned null for fields");
|
|
}
|
|
d.kind = dkStruct;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
d.strValue = nameTok.text;
|
|
var fieldCount: int = 0;
|
|
|
|
// Type params <T: Bound, U: Bound2>
|
|
parserParseTypeParams(p, d);
|
|
|
|
discard parserExpect(p, tkLBrace, "expected '{'");
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
if fieldCount >= 256 { break; }
|
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
|
if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
|
|
let beforePos: int = p.pos;
|
|
let fName: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
|
|
if fieldCount >= 250 && fieldCount <= 256 {
|
|
PrintLine(String_Concat("RAW fieldCount=", bux_int_to_str(fieldCount as int64)));
|
|
PrintLine(String_Concat("RAW fName=", fName.text));
|
|
PrintLine(String_Concat("RAW pos=", bux_int_to_str(p.pos as int64)));
|
|
}
|
|
discard parserExpect(p, tkColon, "expected ':' in struct field");
|
|
let fType: *TypeExpr = parserParseType(p);
|
|
parserMatch(p, tkSemicolon);
|
|
// Infinite-loop safeguard
|
|
if p.pos == beforePos {
|
|
discard parserAdvance(p);
|
|
continue;
|
|
}
|
|
|
|
d.fields[fieldCount].name = fName.text;
|
|
d.fields[fieldCount].refFieldType = fType;
|
|
fieldCount = fieldCount + 1;
|
|
}
|
|
d.fieldCount = fieldCount;
|
|
discard parserExpect(p, tkRBrace, "expected '}'");
|
|
return d;
|
|
}
|
|
|
|
func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkEnum, "expected 'enum'");
|
|
let nameTok: LexToken = parserExpect(p, tkIdent, "expected enum name");
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkEnum;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
d.strValue = nameTok.text;
|
|
|
|
discard parserExpect(p, tkLBrace, "expected '{'");
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
if d.variantCount >= 9 { break; }
|
|
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
|
|
let vName: LexToken = parserExpect(p, tkIdent, "expected variant name");
|
|
|
|
var v: EnumVariant;
|
|
v.name = vName.text;
|
|
v.fieldCount = 0;
|
|
v.fieldTypeName0 = "";
|
|
v.fieldTypeName1 = "";
|
|
|
|
// Optional (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; }
|
|
else if d.variantCount == 4 { d.variant4 = v; }
|
|
else if d.variantCount == 5 { d.variant5 = v; }
|
|
else if d.variantCount == 6 { d.variant6 = v; }
|
|
else if d.variantCount == 7 { d.variant7 = v; }
|
|
else if d.variantCount == 8 { d.variant8 = v; }
|
|
d.variantCount = d.variantCount + 1;
|
|
|
|
parserMatch(p, tkComma);
|
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}'");
|
|
return d;
|
|
}
|
|
|
|
func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkImport, "expected 'import'");
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkUse;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
|
|
// Parse path: Std::Io::PrintLine
|
|
var pathStr: String = "";
|
|
var segCount: int = 0;
|
|
while parserCheck(p, tkIdent) || (segCount > 0 && parserCheck(p, tkColonColon) && parserPeek(p, 1) != tkLBrace) {
|
|
if segCount > 0 {
|
|
discard parserAdvance(p); // ::
|
|
if String_Len(pathStr) > 0 {
|
|
let tmp: *char8 = bux_alloc(256) as *char8;
|
|
// Append to path string (simplified)
|
|
pathStr = String_Concat(pathStr, "::");
|
|
}
|
|
}
|
|
let seg: LexToken = parserExpect(p, tkIdent, "expected module path segment");
|
|
pathStr = String_Concat(pathStr, seg.text);
|
|
segCount = segCount + 1;
|
|
}
|
|
d.usePath = pathStr;
|
|
|
|
// Optional ::{name1, name2} or ::*
|
|
if parserMatch(p, tkColonColon) {
|
|
if parserCheck(p, tkLBrace) {
|
|
discard parserAdvance(p); // {
|
|
var names: String = "";
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
while parserCheck(p, tkNewLine) {
|
|
discard parserAdvance(p);
|
|
}
|
|
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile {
|
|
break;
|
|
}
|
|
let n: LexToken = parserExpect(p, tkIdent, "expected import name");
|
|
names = String_Concat(names, n.text);
|
|
names = String_Concat(names, ",");
|
|
if !parserMatch(p, tkComma) { break; }
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}'");
|
|
d.useNames = names;
|
|
d.useKind = 2; // ukMulti
|
|
} else if parserCheck(p, tkStar) {
|
|
discard parserAdvance(p); // *
|
|
d.useKind = 1; // ukGlob
|
|
} else {
|
|
d.useKind = 0; // ukSingle
|
|
}
|
|
} else {
|
|
d.useKind = 0; // ukSingle
|
|
}
|
|
|
|
parserMatch(p, tkSemicolon);
|
|
return d;
|
|
}
|
|
|
|
func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkExtern, "expected 'extern'");
|
|
|
|
if parserCheck(p, tkFunc) {
|
|
let d: *Decl = parserParseFuncDecl(p, isPublic, true, false);
|
|
d.kind = dkExternFunc;
|
|
return d;
|
|
}
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkExternFunc;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
return d;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Interface declaration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
discard parserExpect(p, tkInterface, "expected 'interface'");
|
|
let nameTok: LexToken = parserExpect(p, tkIdent, "expected interface name");
|
|
discard parserExpect(p, tkLBrace, "expected '{' to start interface body");
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkInterface;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
d.strValue = nameTok.text;
|
|
|
|
var methods: *Decl = null as *Decl;
|
|
var lastMethod: *Decl = null as *Decl;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
|
if parserCheck(p, tkFunc) {
|
|
let m: *Decl = parserParseFuncDecl(p, false, false, false);
|
|
if methods == null as *Decl {
|
|
methods = m;
|
|
lastMethod = m;
|
|
} else {
|
|
lastMethod.childDecl2 = m;
|
|
lastMethod = m;
|
|
}
|
|
d.methodCount = d.methodCount + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
d.childDecl1 = methods;
|
|
discard parserExpect(p, tkRBrace, "expected '}' to close interface");
|
|
return d;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Top-level declaration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func parserParseDecl(p: *Parser) -> *Decl {
|
|
// Skip newlines before declaration (matching bootstrap behavior)
|
|
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
}
|
|
let isPublic: bool = parserMatch(p, tkPub);
|
|
|
|
// Parse @[Checked] / @[Drop] / @[Release] attribute
|
|
var isChecked: int = 0;
|
|
var isDrop: int = 0;
|
|
var isRelease: int = 0;
|
|
if parserCheck(p, tkAt) {
|
|
discard parserAdvance(p); // @
|
|
if parserCheck(p, tkLBracket) {
|
|
discard parserAdvance(p); // [
|
|
if parserCheck(p, tkIdent) {
|
|
let attrName: LexToken = parserCurToken(p);
|
|
if String_Eq(attrName.text, "Checked") {
|
|
isChecked = 1;
|
|
}
|
|
if String_Eq(attrName.text, "Drop") {
|
|
isDrop = 1;
|
|
}
|
|
if String_Eq(attrName.text, "Release") {
|
|
isRelease = 1;
|
|
}
|
|
discard parserAdvance(p); // attribute name
|
|
}
|
|
if parserCheck(p, tkRBracket) {
|
|
discard parserAdvance(p); // ]
|
|
}
|
|
}
|
|
// Skip newlines after attribute before the declaration
|
|
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
}
|
|
}
|
|
|
|
let kind: int = parserPeek(p, 0);
|
|
|
|
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
|
|
discard parserAdvance(p); // async
|
|
let d: *Decl = parserParseFuncDecl(p, isPublic, false, true);
|
|
d.isChecked = isChecked;
|
|
d.isRelease = isRelease;
|
|
return d;
|
|
}
|
|
if kind == tkConst && parserPeek(p, 1) == tkFunc {
|
|
discard parserAdvance(p); // const
|
|
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
|
|
d.isConst = 1;
|
|
d.isChecked = isChecked;
|
|
d.isRelease = isRelease;
|
|
return d;
|
|
}
|
|
if kind == tkFunc {
|
|
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
|
|
d.isChecked = isChecked;
|
|
d.isRelease = isRelease;
|
|
return d;
|
|
}
|
|
if kind == tkStruct {
|
|
let d: *Decl = parserParseStructDecl(p, isPublic);
|
|
d.isDrop = isDrop;
|
|
return d;
|
|
}
|
|
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
|
|
if kind == tkImport { return parserParseImportDecl(p, isPublic); }
|
|
if kind == tkExtern { return parserParseExternDecl(p, isPublic); }
|
|
if kind == tkInterface { return parserParseInterfaceDecl(p, isPublic); }
|
|
|
|
if kind == tkExtend {
|
|
discard parserAdvance(p);
|
|
let line: uint32 = parserCurToken(p).line;
|
|
let col: uint32 = parserCurToken(p).column;
|
|
let typeName: LexToken = parserExpect(p, tkIdent, "expected type name");
|
|
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkImpl;
|
|
d.line = line;
|
|
d.column = col;
|
|
d.isPublic = isPublic;
|
|
d.strValue = typeName.text;
|
|
|
|
// Optional <T: Bound>
|
|
parserParseTypeParams(p, d);
|
|
|
|
// Optional 'for InterfaceName'
|
|
if parserMatch(p, tkFor) {
|
|
let ifaceName: LexToken = parserExpect(p, tkIdent, "expected interface name");
|
|
d.strValue2 = ifaceName.text;
|
|
}
|
|
|
|
discard parserExpect(p, tkLBrace, "expected '{'");
|
|
var methods: *Decl = null as *Decl;
|
|
var lastMethod: *Decl = null as *Decl;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
|
if parserCheck(p, tkFunc) {
|
|
let m: *Decl = parserParseFuncDecl(p, false, false, false);
|
|
if methods == null as *Decl {
|
|
methods = m;
|
|
lastMethod = m;
|
|
} else {
|
|
lastMethod.childDecl2 = m;
|
|
lastMethod = m;
|
|
}
|
|
d.methodCount = d.methodCount + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
d.childDecl1 = methods;
|
|
discard parserExpect(p, tkRBrace, "expected '}'");
|
|
return d;
|
|
}
|
|
|
|
if kind == tkModule {
|
|
discard parserAdvance(p);
|
|
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
|
|
// Parse optional path segments (::Name)
|
|
while parserCheck(p, tkColonColon) {
|
|
discard parserAdvance(p);
|
|
discard parserExpect(p, tkIdent, "expected module path segment");
|
|
}
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkModule;
|
|
d.strValue = name.text;
|
|
// Parse module body with braces
|
|
if parserCheck(p, tkLBrace) {
|
|
discard parserAdvance(p); // consume {
|
|
var items: *Decl = null as *Decl;
|
|
var lastItem: *Decl = null as *Decl;
|
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
continue;
|
|
}
|
|
let beforePos: int = p.pos;
|
|
let item: *Decl = parserParseDecl(p);
|
|
if item != null as *Decl {
|
|
item.childDecl2 = null as *Decl;
|
|
if items == null as *Decl {
|
|
items = item;
|
|
lastItem = item;
|
|
} else {
|
|
lastItem.childDecl2 = item;
|
|
lastItem = item;
|
|
}
|
|
}
|
|
// Infinite-loop safeguard: if no progress, skip token
|
|
if p.pos == beforePos {
|
|
discard parserAdvance(p);
|
|
}
|
|
}
|
|
discard parserExpect(p, tkRBrace, "expected '}' to close module");
|
|
d.childDecl1 = items; // first item of module
|
|
} else {
|
|
parserMatch(p, tkSemicolon);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
if kind == tkConst {
|
|
discard parserAdvance(p);
|
|
let name: LexToken = parserExpect(p, tkIdent, "expected const name");
|
|
discard parserExpect(p, tkColon, "expected ':'");
|
|
let ct: *TypeExpr = parserParseType(p);
|
|
discard parserExpect(p, tkAssign, "expected '='");
|
|
let val: *Expr = parserParseExpr(p);
|
|
parserMatch(p, tkSemicolon);
|
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
|
d.kind = dkConst;
|
|
d.strValue = name.text;
|
|
d.constType = ct;
|
|
d.constValue = val;
|
|
return d;
|
|
}
|
|
|
|
// Unknown declaration — skip one token and return null
|
|
// (was: skip to newline/}, which was destructive)
|
|
parserEmitDiag(p, parserCurToken(p).line, parserCurToken(p).column, "skipping unknown declaration");
|
|
discard parserAdvance(p);
|
|
return null as *Decl;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Module parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
|
|
let p: *Parser = bux_alloc(sizeof(Parser)) as *Parser;
|
|
p.tokens = tokens;
|
|
p.tokenCount = tokenCount;
|
|
p.pos = 0;
|
|
p.structInitAllowed = true;
|
|
let diagBuf: *ParserDiag = bux_alloc(256 as uint * sizeof(ParserDiag)) as *ParserDiag;
|
|
p.diags = diagBuf;
|
|
p.diagCount = 0;
|
|
|
|
let mod: *Module = bux_alloc(sizeof(Module)) as *Module;
|
|
mod.name = "";
|
|
mod.itemCount = 0;
|
|
mod.firstItem = null as *Decl;
|
|
|
|
// Parse declarations until EOF
|
|
while parserPeek(p, 0) != tkEndOfFile {
|
|
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
|
discard parserAdvance(p);
|
|
continue;
|
|
}
|
|
let beforePos: int = p.pos;
|
|
let decl: *Decl = parserParseDecl(p);
|
|
if decl != null as *Decl {
|
|
decl.childDecl2 = mod.firstItem; // push front
|
|
mod.firstItem = decl;
|
|
mod.itemCount = mod.itemCount + 1;
|
|
}
|
|
// Infinite-loop safeguard: if no progress, skip token
|
|
if p.pos == beforePos {
|
|
discard parserAdvance(p);
|
|
}
|
|
}
|
|
|
|
/* Print fatal parser diagnostics (severity == 0) or if nothing valid was parsed */
|
|
if p.diagCount > 0 && mod.itemCount == 0 {
|
|
var di: int = 0;
|
|
while di < p.diagCount {
|
|
let d: ParserDiag = p.diags[di];
|
|
Print("error: ");
|
|
PrintLine(d.message);
|
|
Print(" --> <input>:");
|
|
PrintInt(d.line as int64);
|
|
Print(":");
|
|
PrintInt(d.column as int64);
|
|
PrintLine("");
|
|
Print(" |");
|
|
PrintLine("");
|
|
Print(" ");
|
|
PrintInt(d.line as int64);
|
|
Print(" | <source unavailable>");
|
|
PrintLine("");
|
|
Print(" | ");
|
|
var sp: uint32 = 0;
|
|
while sp < d.column - 1 && sp < 120 {
|
|
Print(" ");
|
|
sp = sp + 1;
|
|
}
|
|
PrintLine("^");
|
|
di = di + 1;
|
|
}
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
}
|