3b7fc455b0
Parser would hang (infinite loop) when encountering newline tokens inside certain constructs because expect() returns without advancing position when the expected token doesn't match. Fixed 5 places in parseBaseType, parsePrimaryPattern, parseTypeParams, parseParamList, and parseUseDecl by adding newline-skipping at the start of each iteration. Verified: compiler no longer hangs/crashes on all 3 real-world apps from apps/ (boko-framework, jwt-pitbul, nexus).
1432 lines
51 KiB
Nim
1432 lines
51 KiB
Nim
import std/strutils
|
|
import token, source_location, ast
|
|
|
|
type
|
|
ParserDiagnosticSeverity* = enum
|
|
pdsWarning
|
|
pdsError
|
|
|
|
ParserDiagnostic* = object
|
|
severity*: ParserDiagnosticSeverity
|
|
loc*: SourceLocation
|
|
message*: string
|
|
|
|
ParseResult* = object
|
|
module*: Module
|
|
diagnostics*: seq[ParserDiagnostic]
|
|
|
|
Parser* = object
|
|
tokens: seq[Token]
|
|
sourceName: string
|
|
pos: int
|
|
diagnostics: seq[ParserDiagnostic]
|
|
structInitAllowed: bool ## disabled inside if/while/for/match conditions
|
|
|
|
proc initParser*(tokens: seq[Token], sourceName: string = "<input>"): Parser =
|
|
result.tokens = tokens
|
|
result.sourceName = sourceName
|
|
result.pos = 0
|
|
result.structInitAllowed = true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Token helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc peek(p: Parser, ahead: int = 0): TokenKind =
|
|
let i = p.pos + ahead
|
|
if i < p.tokens.len:
|
|
return p.tokens[i].kind
|
|
return tkEndOfFile
|
|
|
|
proc at(p: Parser): Token =
|
|
if p.pos < p.tokens.len:
|
|
return p.tokens[p.pos]
|
|
return Token(kind: tkEndOfFile)
|
|
|
|
proc advance(p: var Parser): Token =
|
|
result = p.at
|
|
if p.pos < p.tokens.len:
|
|
inc p.pos
|
|
|
|
proc check(p: Parser, kind: TokenKind): bool =
|
|
p.peek() == kind
|
|
|
|
proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
|
|
for k in kinds:
|
|
if p.peek() == k: return true
|
|
return false
|
|
|
|
proc match(p: var Parser, kind: TokenKind): bool =
|
|
if p.check(kind):
|
|
discard p.advance()
|
|
return true
|
|
return false
|
|
|
|
proc isTypeArgListAhead(p: Parser): bool =
|
|
## Lookahead to determine if '<' starts a type argument list.
|
|
## Returns true if we can find a matching '>' before EOF, '{', or ';'.
|
|
if not p.check(tkLt): return false
|
|
var depth = 0
|
|
var ahead = 0
|
|
while true:
|
|
let kind = p.peek(ahead)
|
|
if kind == tkEndOfFile or kind == tkLBrace or kind == tkSemicolon:
|
|
return false
|
|
if kind == tkLt:
|
|
inc depth
|
|
elif kind == tkGt:
|
|
dec depth
|
|
if depth == 0:
|
|
return true
|
|
if depth < 0:
|
|
return false
|
|
inc ahead
|
|
|
|
proc expect(p: var Parser, kind: TokenKind, message: string): Token =
|
|
if p.check(kind):
|
|
return p.advance()
|
|
let tok = p.at
|
|
p.diagnostics.add(ParserDiagnostic(
|
|
severity: pdsError,
|
|
loc: tok.loc,
|
|
message: message & " (got " & tokenKindName(tok.kind) & ")"
|
|
))
|
|
result = tok
|
|
|
|
proc isKeywordToken(kind: TokenKind): bool =
|
|
return kind in {tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn, tkBreak,
|
|
tkContinue, tkReturn, tkMatch, tkFunc, tkLet, tkVar, tkConst, tkType,
|
|
tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
|
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkSizeOf, tkOwn,
|
|
tkDiscard}
|
|
|
|
proc expectIdentOrKeyword(p: var Parser, message: string): Token =
|
|
## Accept identifier OR keyword token as a name (for field names, param names, etc.)
|
|
if p.check(tkIdent) or isKeywordToken(p.at.kind):
|
|
return p.advance()
|
|
let tok = p.at
|
|
p.diagnostics.add(ParserDiagnostic(
|
|
severity: pdsError,
|
|
loc: tok.loc,
|
|
message: message & " (got " & tokenKindName(tok.kind) & ")"
|
|
))
|
|
result = tok
|
|
|
|
proc previous(p: Parser): Token =
|
|
if p.pos > 0 and p.pos <= p.tokens.len:
|
|
return p.tokens[p.pos - 1]
|
|
return Token(kind: tkEndOfFile)
|
|
|
|
proc currentLoc(p: Parser): SourceLocation =
|
|
p.at.loc
|
|
|
|
proc emitError(p: var Parser, loc: SourceLocation, message: string) =
|
|
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: loc, message: message))
|
|
|
|
proc skipNewlines(p: var Parser) =
|
|
while p.check(tkNewLine): discard p.advance()
|
|
|
|
proc emitError(p: var Parser, message: string) =
|
|
p.emitError(p.currentLoc, message)
|
|
|
|
proc isAtEnd(p: Parser): bool =
|
|
p.peek() == tkEndOfFile
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Recovery
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc synchronize(p: var Parser) =
|
|
## Skip tokens until a declaration boundary.
|
|
discard p.advance()
|
|
while not p.isAtEnd:
|
|
if p.previous.kind == tkSemicolon: return
|
|
case p.peek()
|
|
of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend,
|
|
tkModule, tkImport, tkConst, tkType, tkExtern, tkPub:
|
|
return
|
|
else:
|
|
discard p.advance()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Forward declarations for mutual recursion
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseDecl(p: var Parser): Decl
|
|
proc parseType(p: var Parser): TypeExpr
|
|
proc parseExpr(p: var Parser): Expr
|
|
proc parseStmt(p: var Parser): Stmt
|
|
proc parseBlock(p: var Parser): Block
|
|
proc parsePattern(p: var Parser): Pattern
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Attributes
|
|
# ---------------------------------------------------------------------------
|
|
|
|
type
|
|
ParsedAttrs* = object
|
|
importLib*: string
|
|
callConv*: CallingConvention
|
|
targetOs*: string
|
|
checked*: bool ## @[Checked] — enable borrow checking
|
|
shared*: bool ## @[Shared] — mark function as thread-safe
|
|
|
|
proc parseAttrs(p: var Parser): ParsedAttrs =
|
|
while p.check(tkAt):
|
|
discard p.advance() # @
|
|
discard p.expect(tkLBracket, "expected '[' after '@'")
|
|
let name = p.expect(tkIdent, "expected attribute name").text
|
|
if name == "Checked":
|
|
result.checked = true
|
|
elif name == "Shared":
|
|
result.shared = true
|
|
elif name == "Import":
|
|
discard p.expect(tkLParen, "expected '('")
|
|
let key = p.expect(tkIdent, "expected attribute key").text
|
|
if key == "lib":
|
|
discard p.expect(tkColon, "expected ':'")
|
|
result.importLib = p.expect(tkStringLiteral, "expected string literal").text
|
|
discard p.expect(tkRParen, "expected ')'")
|
|
discard p.expect(tkRBracket, "expected ']'")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Type expressions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseBaseType(p: var Parser): TypeExpr =
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkIdent:
|
|
let name = p.advance().text
|
|
if name == "self":
|
|
return TypeExpr(kind: tekSelf, loc: loc)
|
|
if p.check(tkLt):
|
|
var typeArgs: seq[TypeExpr] = @[]
|
|
discard p.advance()
|
|
while not p.check(tkGt) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkGt) or p.isAtEnd:
|
|
break
|
|
typeArgs.add(p.parseType())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkGt, "expected '>' to close type arguments")
|
|
return TypeExpr(kind: tekNamed, loc: loc, typeName: name, typeArgs: typeArgs)
|
|
return TypeExpr(kind: tekNamed, loc: loc, typeName: name)
|
|
of tkOwn:
|
|
discard p.advance()
|
|
return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
|
|
of tkStar:
|
|
discard p.advance()
|
|
return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
|
|
of tkAmp:
|
|
discard p.advance()
|
|
var lt = ""
|
|
if p.check(tkLifetime):
|
|
lt = p.advance().text
|
|
if p.check(tkMut):
|
|
discard p.advance()
|
|
return TypeExpr(kind: tekMutRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
|
|
if p.check(tkDyn):
|
|
discard p.advance()
|
|
let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text
|
|
return TypeExpr(kind: tekDynRef, loc: loc, dynInterface: ifaceName)
|
|
return TypeExpr(kind: tekRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
|
|
of tkLParen:
|
|
discard p.advance()
|
|
var elems: seq[TypeExpr] = @[]
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
elems.add(p.parseType())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close tuple type")
|
|
return TypeExpr(kind: tekTuple, loc: loc, tupleElements: elems)
|
|
else:
|
|
p.emitError(loc, "expected type expression")
|
|
return TypeExpr(kind: tekNamed, loc: loc, typeName: "")
|
|
|
|
proc parseType(p: var Parser): TypeExpr =
|
|
var left = p.parseBaseType()
|
|
while p.check(tkLBracket):
|
|
let loc = p.currentLoc
|
|
discard p.advance()
|
|
if p.check(tkRBracket):
|
|
discard p.advance()
|
|
left = TypeExpr(kind: tekSlice, loc: loc, sliceElement: left, sliceSize: nil)
|
|
else:
|
|
let sizeExpr = p.parseExpr()
|
|
discard p.expect(tkRBracket, "expected ']' to close array size")
|
|
left = TypeExpr(kind: tekSlice, loc: loc, sliceElement: left, sliceSize: sizeExpr)
|
|
# Path types: Std::Io::Reader
|
|
while p.check(tkColonColon):
|
|
discard p.advance()
|
|
let nextSeg = p.expect(tkIdent, "expected identifier after '::'")
|
|
if left.kind == tekNamed:
|
|
var segs = @[left.typeName]
|
|
segs.add(nextSeg.text)
|
|
left = TypeExpr(kind: tekPath, loc: left.loc, pathSegments: segs)
|
|
elif left.kind == tekPath:
|
|
left.pathSegments.add(nextSeg.text)
|
|
return left
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Patterns
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parsePrimaryPattern(p: var Parser): Pattern =
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkUnderscore:
|
|
discard p.advance()
|
|
return Pattern(kind: pkWildcard, loc: loc)
|
|
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
|
return Pattern(kind: pkLiteral, loc: loc, patLit: p.advance())
|
|
of tkIdent:
|
|
let name = p.advance().text
|
|
if name == "true" or name == "false":
|
|
# These were lexed as BoolLiteral already, but just in case
|
|
return Pattern(kind: pkLiteral, loc: loc, patLit: Token(kind: tkBoolLiteral, text: name, loc: loc))
|
|
# Could be enum pattern or ident pattern
|
|
if p.check(tkColonColon):
|
|
var path = @[name]
|
|
while p.check(tkColonColon):
|
|
discard p.advance()
|
|
path.add(p.expect(tkIdent, "expected identifier in pattern path").text)
|
|
if p.check(tkLParen):
|
|
discard p.advance()
|
|
var args: seq[Pattern] = @[]
|
|
var named: seq[tuple[name: string, pattern: Pattern]] = @[]
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
if p.check(tkIdent) and p.peek(1) == tkColon:
|
|
let fieldName = p.advance().text
|
|
discard p.advance() # :
|
|
named.add((fieldName, p.parsePattern()))
|
|
else:
|
|
args.add(p.parsePattern())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close enum pattern")
|
|
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: args, patEnumNamed: named)
|
|
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: @[], patEnumNamed: @[])
|
|
elif p.check(tkLBrace):
|
|
# Struct pattern: Point { x: 0, y: 0 }
|
|
discard p.advance()
|
|
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
|
|
discard p.expect(tkColon, "expected ':' after field name in pattern")
|
|
fields.add((fieldName, p.parsePattern()))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close struct pattern")
|
|
return Pattern(kind: pkStruct, loc: loc, patStructName: name, patStructFields: fields)
|
|
elif p.check(tkLParen):
|
|
# Enum-like pattern with path of length 1
|
|
discard p.advance()
|
|
var args: seq[Pattern] = @[]
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
args.add(p.parsePattern())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close pattern")
|
|
return Pattern(kind: pkEnum, loc: loc, patEnumPath: @[name], patEnumArgs: args)
|
|
return Pattern(kind: pkIdent, loc: loc, patIdent: name)
|
|
of tkLParen:
|
|
discard p.advance()
|
|
var elems: seq[Pattern] = @[]
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
elems.add(p.parsePattern())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close tuple pattern")
|
|
return Pattern(kind: pkTuple, loc: loc, patTupleElements: elems)
|
|
else:
|
|
p.emitError(loc, "expected pattern")
|
|
return Pattern(kind: pkWildcard, loc: loc)
|
|
|
|
proc parsePattern(p: var Parser): Pattern =
|
|
let loc = p.currentLoc
|
|
var left = p.parsePrimaryPattern()
|
|
# Range pattern
|
|
if p.check(tkDotDot) or p.check(tkDotDotEqual):
|
|
let inclusive = p.check(tkDotDotEqual)
|
|
discard p.advance()
|
|
let right = p.parsePrimaryPattern()
|
|
return Pattern(kind: pkRange, loc: loc, patRangeLo: left, patRangeHi: right, patRangeInclusive: inclusive)
|
|
# Guarded pattern
|
|
if p.check(tkIf):
|
|
discard p.advance()
|
|
let guard = p.parseExpr()
|
|
return Pattern(kind: pkGuarded, loc: loc, patGuardedInner: left, patGuardedExpr: guard)
|
|
return left
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Expressions (Pratt / precedence climbing)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parsePrimary(p: var Parser): Expr
|
|
proc parsePostfix(p: var Parser): Expr
|
|
proc parseUnary(p: var Parser): Expr
|
|
proc parseExp(p: var Parser): Expr
|
|
proc parseMul(p: var Parser): Expr
|
|
proc parseAdd(p: var Parser): Expr
|
|
proc parseShift(p: var Parser): Expr
|
|
proc parseCast(p: var Parser): Expr
|
|
proc parseComparison(p: var Parser): Expr
|
|
proc parseRange(p: var Parser): Expr
|
|
proc parseEquality(p: var Parser): Expr
|
|
proc parseBitAnd(p: var Parser): Expr
|
|
proc parseBitXor(p: var Parser): Expr
|
|
proc parseBitOr(p: var Parser): Expr
|
|
proc parseAnd(p: var Parser): Expr
|
|
proc parseOr(p: var Parser): Expr
|
|
proc parseTernary(p: var Parser): Expr
|
|
proc parseAssign(p: var Parser): Expr
|
|
|
|
proc parseExpr(p: var Parser): Expr =
|
|
p.parseAssign()
|
|
|
|
proc parsePrimary(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
|
return newLiteralExpr(p.advance())
|
|
of tkSelf:
|
|
discard p.advance()
|
|
return Expr(kind: ekSelf, loc: loc)
|
|
of tkIdent:
|
|
let name = p.advance().text
|
|
# Path expression: a::b::c
|
|
if p.check(tkColonColon):
|
|
var segs = @[name]
|
|
while p.check(tkColonColon):
|
|
discard p.advance()
|
|
segs.add(p.expect(tkIdent, "expected identifier in path").text)
|
|
return Expr(kind: ekPath, loc: loc, exprPath: segs)
|
|
return newIdentExpr(name, loc)
|
|
of tkLParen:
|
|
discard p.advance()
|
|
if p.check(tkRParen):
|
|
discard p.advance()
|
|
return Expr(kind: ekTuple, loc: loc, exprTupleElements: @[])
|
|
let expr = p.parseExpr()
|
|
if p.check(tkComma):
|
|
# Tuple expression
|
|
var elems = @[expr]
|
|
while p.check(tkComma):
|
|
discard p.advance()
|
|
elems.add(p.parseExpr())
|
|
discard p.expect(tkRParen, "expected ')' to close tuple")
|
|
return Expr(kind: ekTuple, loc: loc, exprTupleElements: elems)
|
|
discard p.expect(tkRParen, "expected ')'")
|
|
return expr
|
|
of tkLBrace:
|
|
# Block expression
|
|
let blk = p.parseBlock()
|
|
return Expr(kind: ekBlock, loc: loc, exprBlock: blk)
|
|
of tkLBracket:
|
|
# Slice expression [a, b, c]
|
|
discard p.advance()
|
|
var elems: seq[Expr] = @[]
|
|
while not p.check(tkRBracket) and not p.isAtEnd:
|
|
elems.add(p.parseExpr())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBracket, "expected ']' to close slice")
|
|
return Expr(kind: ekSlice, loc: loc, exprSliceElements: elems)
|
|
of tkMatch:
|
|
discard p.advance()
|
|
p.structInitAllowed = false
|
|
let subject = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
discard p.expect(tkLBrace, "expected '{' to start match")
|
|
var arms: seq[MatchArm] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
let armLoc = p.currentLoc
|
|
let pat = p.parsePattern()
|
|
discard p.expect(tkFatArrow, "expected '=>' in match arm")
|
|
let body = p.parseExpr()
|
|
arms.add(MatchArm(loc: armLoc, pattern: pat, body: body))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close match")
|
|
return Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms)
|
|
of tkMinus, tkBang, tkTilde, tkStar, tkAmp, tkPlusPlus, tkMinusMinus:
|
|
# These are handled in parseUnary, but ++/-- as prefix are rare
|
|
return p.parseUnary()
|
|
of tkSizeOf:
|
|
discard p.advance() # sizeof
|
|
discard p.expect(tkLParen, "expected '('")
|
|
let ty = p.parseType()
|
|
discard p.expect(tkRParen, "expected ')'")
|
|
return Expr(kind: ekSizeOf, loc: loc, exprSizeOfType: ty)
|
|
of tkSpawn:
|
|
discard p.advance() # spawn
|
|
let callee = p.parsePrimary()
|
|
var args: seq[Expr] = @[]
|
|
if p.check(tkLParen):
|
|
discard p.advance() # (
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
args.add(p.parseExpr())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' after spawn arguments")
|
|
return Expr(kind: ekSpawn, loc: loc, exprSpawnCallee: callee, exprSpawnArgs: args, exprSpawnAsync: false)
|
|
of tkHashLine:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikLine)
|
|
of tkHashColumn:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikColumn)
|
|
of tkHashFile:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikFile)
|
|
of tkHashFunction:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikFunction)
|
|
of tkHashDate:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikDate)
|
|
of tkHashTime:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikTime)
|
|
of tkHashModule:
|
|
discard p.advance()
|
|
return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikModule)
|
|
of tkNull:
|
|
discard p.advance()
|
|
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
|
|
else:
|
|
p.emitError(loc, "expected expression")
|
|
discard p.advance()
|
|
return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: loc))
|
|
|
|
proc parsePostfix(p: var Parser): Expr =
|
|
var left = p.parsePrimary()
|
|
while true:
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkLParen:
|
|
# Call expression
|
|
discard p.advance()
|
|
var args: seq[Expr] = @[]
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
if p.check(tkDotDotDot):
|
|
discard p.advance()
|
|
let operand = p.parseExpr()
|
|
args.add(Expr(kind: ekSpread, loc: operand.loc, exprSpreadOperand: operand))
|
|
else:
|
|
args.add(p.parseExpr())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close call")
|
|
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
|
of tkLt:
|
|
# Generic type arguments: Max<int>(10, 20)
|
|
# Only treat '<' as generic args if lookahead confirms a matching '>'
|
|
if left.kind == ekIdent and p.isTypeArgListAhead():
|
|
discard p.advance()
|
|
var typeArgs: seq[TypeExpr] = @[]
|
|
while not p.check(tkGt) and not p.isAtEnd:
|
|
typeArgs.add(p.parseType())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkGt, "expected '>' to close type arguments")
|
|
# Store type args in the identifier for later use
|
|
left = Expr(kind: ekGenericCall, loc: loc, exprGenericCallee: left.exprIdent, exprGenericTypeArgs: typeArgs)
|
|
else:
|
|
break
|
|
of tkLBracket:
|
|
# Index expression
|
|
discard p.advance()
|
|
let idx = p.parseExpr()
|
|
discard p.expect(tkRBracket, "expected ']' to close index")
|
|
left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
|
|
of tkDot:
|
|
# Field expression or .await
|
|
discard p.advance()
|
|
if p.check(tkAwait):
|
|
discard p.advance()
|
|
left = Expr(kind: ekAwait, loc: loc, exprAwaitOperand: left)
|
|
else:
|
|
let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
|
|
left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName)
|
|
of tkPlusPlus, tkMinusMinus:
|
|
let op = p.advance().kind
|
|
left = Expr(kind: ekPostfix, loc: loc, exprPostfixOp: op, exprPostfixOperand: left)
|
|
of tkAs:
|
|
discard p.advance()
|
|
let ty = p.parseType()
|
|
left = Expr(kind: ekCast, loc: loc, exprCastOperand: left, exprCastType: ty)
|
|
of tkIs:
|
|
discard p.advance()
|
|
let ty = p.parseType()
|
|
left = Expr(kind: ekIs, loc: loc, exprIsOperand: left, exprIsType: ty)
|
|
of tkQuestion:
|
|
discard p.advance()
|
|
left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil)
|
|
of tkBang:
|
|
discard p.advance()
|
|
left = Expr(kind: ekUnwrap, loc: loc, exprUnwrapOperand: left)
|
|
of tkLBrace:
|
|
if p.structInitAllowed and left.kind in {ekIdent, ekPath, ekGenericCall}:
|
|
discard p.advance()
|
|
var fields: seq[tuple[name: string, value: Expr]] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
while p.check(tkNewLine): discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd: break
|
|
let fieldName = p.expect(tkIdent, "expected field name").text
|
|
discard p.expect(tkColon, "expected ':'")
|
|
let fieldValue = p.parseExpr()
|
|
fields.add((fieldName, fieldValue))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}'")
|
|
var typeName = ""
|
|
var typeArgs: seq[TypeExpr] = @[]
|
|
if left.kind == ekIdent:
|
|
typeName = left.exprIdent
|
|
elif left.kind == ekPath:
|
|
typeName = left.exprPath.join("::")
|
|
elif left.kind == ekGenericCall:
|
|
typeName = left.exprGenericCallee
|
|
typeArgs = left.exprGenericTypeArgs
|
|
left = Expr(kind: ekStructInit, loc: loc, exprStructInitName: typeName,
|
|
exprStructInitTypeArgs: typeArgs, exprStructInitFields: fields)
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
return left
|
|
|
|
proc parseUnary(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkBorrow:
|
|
discard p.advance() # borrow
|
|
discard p.expect(tkAmp, "expected '&' after 'borrow'")
|
|
var isMut = p.check(tkMut)
|
|
if isMut:
|
|
discard p.advance() # mut
|
|
let operand = p.parseUnary() # parse the moved value
|
|
return Expr(kind: ekBorrow, loc: loc, exprBorrowOperand: operand, exprBorrowMutable: isMut)
|
|
of tkBang, tkMinus, tkTilde, tkStar, tkAmp:
|
|
let op = p.advance().kind
|
|
if op == tkAmp and p.check(tkMut):
|
|
discard p.advance() # mut
|
|
let savedStructInit = p.structInitAllowed
|
|
p.structInitAllowed = false
|
|
let operand = p.parseUnary()
|
|
p.structInitAllowed = savedStructInit
|
|
return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
|
|
of tkPlusPlus, tkMinusMinus:
|
|
let op = p.advance().kind
|
|
let operand = p.parseUnary()
|
|
return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
|
|
else:
|
|
return p.parsePostfix()
|
|
|
|
proc parseExp(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseUnary()
|
|
while p.check(tkStarStar):
|
|
let op = p.advance().kind
|
|
let right = p.parseUnary() # right-associative: parseUnary not parseExp
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseMul(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseExp()
|
|
while p.checkAny([tkStar, tkSlash, tkPercent]):
|
|
let op = p.advance().kind
|
|
let right = p.parseExp()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseAdd(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseMul()
|
|
while p.checkAny([tkPlus, tkMinus]):
|
|
let op = p.advance().kind
|
|
let right = p.parseMul()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseShift(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseAdd()
|
|
while p.checkAny([tkShl, tkShr]):
|
|
let op = p.advance().kind
|
|
let right = p.parseAdd()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseCast(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseShift()
|
|
# 'as' and 'is' are handled in postfix for chaining
|
|
return left
|
|
|
|
proc parseComparison(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseCast()
|
|
while p.checkAny([tkLt, tkLe, tkGt, tkGe]):
|
|
let op = p.advance().kind
|
|
let right = p.parseCast()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseRange(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseComparison()
|
|
if p.check(tkDotDot) or p.check(tkDotDotEqual):
|
|
let inclusive = p.check(tkDotDotEqual)
|
|
discard p.advance()
|
|
let right = p.parseComparison()
|
|
return Expr(kind: ekRange, loc: loc, exprRangeLo: left, exprRangeHi: right, exprRangeInclusive: inclusive)
|
|
return left
|
|
|
|
proc parseEquality(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseRange()
|
|
while p.checkAny([tkEq, tkNe]):
|
|
let op = p.advance().kind
|
|
let right = p.parseRange()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseBitAnd(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseEquality()
|
|
while p.check(tkAmp):
|
|
let op = p.advance().kind
|
|
let right = p.parseEquality()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseBitXor(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseBitAnd()
|
|
while p.check(tkCaret):
|
|
let op = p.advance().kind
|
|
let right = p.parseBitAnd()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseBitOr(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseBitXor()
|
|
while p.check(tkPipe):
|
|
let op = p.advance().kind
|
|
let right = p.parseBitXor()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
return left
|
|
|
|
proc parseAnd(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseBitOr()
|
|
p.skipNewlines()
|
|
while p.check(tkAmpAmp):
|
|
let op = p.advance().kind
|
|
p.skipNewlines()
|
|
let right = p.parseBitOr()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
p.skipNewlines()
|
|
return left
|
|
|
|
proc parseOr(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseAnd()
|
|
p.skipNewlines()
|
|
while p.check(tkPipePipe):
|
|
let op = p.advance().kind
|
|
p.skipNewlines()
|
|
let right = p.parseAnd()
|
|
left = newBinaryExpr(op, left, right, loc)
|
|
p.skipNewlines()
|
|
return left
|
|
|
|
proc parseTernary(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var cond = p.parseOr()
|
|
if p.check(tkQuestion):
|
|
discard p.advance()
|
|
let thenExpr = p.parseExpr()
|
|
discard p.expect(tkColon, "expected ':' in ternary expression")
|
|
let elseExpr = p.parseTernary()
|
|
return Expr(kind: ekTernary, loc: loc, exprTernaryCond: cond, exprTernaryThen: thenExpr, exprTernaryElse: elseExpr)
|
|
return cond
|
|
|
|
proc parseAssign(p: var Parser): Expr =
|
|
let loc = p.currentLoc
|
|
var left = p.parseTernary()
|
|
if p.checkAny([tkAssign, tkPlusAssign, tkMinusAssign, tkStarAssign, tkSlashAssign,
|
|
tkPercentAssign, tkAmpAssign, tkPipeAssign, tkCaretAssign,
|
|
tkShlAssign, tkShrAssign]):
|
|
let op = p.advance().kind
|
|
let right = p.parseAssign() # right-associative
|
|
return Expr(kind: ekAssign, loc: loc, exprAssignOp: op, exprAssignTarget: left, exprAssignValue: right)
|
|
return left
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Block
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseBlock(p: var Parser): Block =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkLBrace, "expected '{' to start block")
|
|
result = newBlock(loc)
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
result.stmts.add(p.parseStmt())
|
|
discard p.expect(tkRBrace, "expected '}' to close block")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Statements
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseStmt(p: var Parser): Stmt =
|
|
let loc = p.currentLoc
|
|
case p.peek()
|
|
of tkLet, tkVar:
|
|
let isMut = p.peek() == tkVar
|
|
discard p.advance()
|
|
let name = p.expect(tkIdent, "expected variable name").text
|
|
var pat: Pattern = nil
|
|
var ty: TypeExpr = nil
|
|
if p.check(tkColon):
|
|
discard p.advance()
|
|
ty = p.parseType()
|
|
var initExpr: Expr = nil
|
|
if p.check(tkAssign):
|
|
discard p.advance()
|
|
initExpr = p.parseExpr()
|
|
elif not isMut:
|
|
discard p.expect(tkAssign, "expected '=' in let statement")
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skLet, loc: loc, stmtLetMut: isMut, stmtLetName: name,
|
|
stmtLetPattern: pat, stmtLetType: ty, stmtLetInit: initExpr)
|
|
of tkIf:
|
|
discard p.advance()
|
|
p.structInitAllowed = false
|
|
let cond = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
let thenBlk = p.parseBlock()
|
|
var elseIfs: seq[ElseIf] = @[]
|
|
var elseBlk: Block = nil
|
|
while p.check(tkNewLine): discard p.advance()
|
|
while p.check(tkElse):
|
|
let elseLoc = p.currentLoc
|
|
discard p.advance()
|
|
while p.check(tkNewLine): discard p.advance()
|
|
if p.check(tkIf):
|
|
discard p.advance()
|
|
p.structInitAllowed = false
|
|
let elifCond = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
let elifBlk = p.parseBlock()
|
|
elseIfs.add(ElseIf(loc: elseLoc, cond: elifCond, blk: elifBlk))
|
|
while p.check(tkNewLine): discard p.advance()
|
|
else:
|
|
elseBlk = p.parseBlock()
|
|
break
|
|
return Stmt(kind: skIf, loc: loc, stmtIfCond: cond, stmtIfThen: thenBlk,
|
|
stmtIfElseIfs: elseIfs, stmtIfElse: elseBlk)
|
|
of tkWhile:
|
|
discard p.advance()
|
|
p.structInitAllowed = false
|
|
let cond = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
let body = p.parseBlock()
|
|
return Stmt(kind: skWhile, loc: loc, stmtWhileCond: cond, stmtWhileBody: body)
|
|
of tkDo:
|
|
discard p.advance()
|
|
let body = p.parseBlock()
|
|
discard p.expect(tkWhile, "expected 'while' after 'do' block")
|
|
let cond = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skDoWhile, loc: loc, stmtDoWhileBody: body, stmtDoWhileCond: cond)
|
|
of tkLoop:
|
|
discard p.advance()
|
|
let body = p.parseBlock()
|
|
return Stmt(kind: skLoop, loc: loc, stmtLoopBody: body)
|
|
of tkFor:
|
|
discard p.advance()
|
|
let varName = p.expect(tkIdent, "expected loop variable name").text
|
|
discard p.expect(tkIn, "expected 'in' in for loop")
|
|
p.structInitAllowed = false
|
|
let iter = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
let body = p.parseBlock()
|
|
return Stmt(kind: skFor, loc: loc, stmtForVar: varName, stmtForIter: iter, stmtForBody: body)
|
|
of tkMatch:
|
|
discard p.advance()
|
|
p.structInitAllowed = false
|
|
let subject = p.parseExpr()
|
|
p.structInitAllowed = true
|
|
# Skip newlines before opening brace
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
discard p.expect(tkLBrace, "expected '{' to start match")
|
|
var arms: seq[MatchArm] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
# Skip newlines
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
let armLoc = p.currentLoc
|
|
let pat = p.parsePattern()
|
|
discard p.expect(tkFatArrow, "expected '=>' in match arm")
|
|
let body = p.parseExpr()
|
|
arms.add(MatchArm(loc: armLoc, pattern: pat, body: body))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close match")
|
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
|
|
of tkReturn:
|
|
discard p.advance()
|
|
var val: Expr = nil
|
|
if not p.check(tkSemicolon) and not p.check(tkRBrace):
|
|
val = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skReturn, loc: loc, stmtReturnValue: val)
|
|
of tkBreak:
|
|
discard p.advance()
|
|
var label = ""
|
|
if p.check(tkIdent):
|
|
label = p.advance().text
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skBreak, loc: loc, stmtBreakLabel: label)
|
|
of tkContinue:
|
|
discard p.advance()
|
|
var label = ""
|
|
if p.check(tkIdent):
|
|
label = p.advance().text
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skContinue, loc: loc, stmtContinueLabel: label)
|
|
of tkStaticAssert:
|
|
discard p.advance()
|
|
let cond = p.parseExpr()
|
|
var msg: Expr = nil
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
msg = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skStaticAssert, loc: loc, stmtStaticAssertCond: cond, stmtStaticAssertMsg: msg)
|
|
of tkComptime:
|
|
discard p.advance()
|
|
let blk = p.parseBlock()
|
|
return Stmt(kind: skComptime, loc: loc, stmtComptimeBlock: blk)
|
|
of tkHashEmit:
|
|
discard p.advance()
|
|
let expr = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skEmit, loc: loc, stmtEmitExpr: expr, stmtEmitEvaluated: "")
|
|
of tkDiscard:
|
|
discard p.advance()
|
|
var val: Expr = nil
|
|
if not p.check(tkSemicolon) and not p.check(tkRBrace) and not p.check(tkNewLine):
|
|
val = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
# discard expr → expression statement; discard; → no-op (nil expr)
|
|
if val != nil:
|
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: val)
|
|
else:
|
|
# No-op: emit literal 0 as expression statement
|
|
let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc)
|
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok))
|
|
of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule,
|
|
tkImport, tkConst, tkType, tkExtern, tkPub:
|
|
# Local declaration
|
|
let decl = p.parseDecl()
|
|
return Stmt(kind: skDecl, loc: loc, stmtDecl: decl)
|
|
else:
|
|
# Expression statement
|
|
let expr = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Stmt(kind: skExpr, loc: loc, stmtExpr: expr)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Declarations
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseTypeParams(p: var Parser): seq[TypeParam] =
|
|
if p.check(tkLt):
|
|
discard p.advance()
|
|
while not p.check(tkGt) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkGt) or p.isAtEnd:
|
|
break
|
|
var name = ""
|
|
var isLifetime = false
|
|
if p.check(tkIdent):
|
|
name = p.advance().text
|
|
elif p.check(tkLifetime):
|
|
name = p.advance().text
|
|
isLifetime = true
|
|
else:
|
|
discard p.expect(tkIdent, "expected type parameter name")
|
|
var bounds: seq[string] = @[]
|
|
if p.check(tkColon):
|
|
discard p.advance()
|
|
# Parse bound: single identifier or path like Std::Comparable
|
|
var boundName = ""
|
|
while true:
|
|
let part = p.expect(tkIdent, "expected trait/interface name").text
|
|
if boundName.len > 0:
|
|
boundName.add("_")
|
|
boundName.add(part)
|
|
if p.check(tkColonColon):
|
|
discard p.advance()
|
|
else:
|
|
break
|
|
bounds.add(boundName)
|
|
result.add(TypeParam(name: name, bounds: bounds, isLifetime: isLifetime))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkGt, "expected '>' to close type parameters")
|
|
|
|
proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
|
|
discard p.expect(tkLParen, "expected '('")
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRParen) or p.isAtEnd:
|
|
break
|
|
let loc = p.currentLoc
|
|
var isVar = false
|
|
if allowVariadic and p.check(tkDotDotDot):
|
|
discard p.advance()
|
|
let nameTok = p.at
|
|
var name = ""
|
|
if nameTok.kind == tkIdent:
|
|
name = p.advance().text
|
|
elif nameTok.kind == tkSelf:
|
|
name = p.advance().text
|
|
else:
|
|
name = p.expect(tkIdent, "expected parameter name after '...'").text
|
|
let ty = p.parseType()
|
|
result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true))
|
|
else:
|
|
let nameTok = p.at
|
|
var name = ""
|
|
if nameTok.kind == tkIdent:
|
|
name = p.advance().text
|
|
elif nameTok.kind == tkSelf:
|
|
name = p.advance().text
|
|
else:
|
|
name = p.expect(tkIdent, "expected parameter name").text
|
|
discard p.expect(tkColon, "expected ':' after parameter name")
|
|
let ty = p.parseType()
|
|
var defaultVal: Expr = nil
|
|
if p.check(tkAssign):
|
|
discard p.advance()
|
|
defaultVal = p.parseExpr()
|
|
result.add(Param(loc: loc, name: name, ptype: ty, defaultValue: defaultVal))
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close parameter list")
|
|
|
|
proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttrs, isConst: bool = false, isAsync: bool = false): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkFunc, "expected 'func'")
|
|
let name = p.expect(tkIdent, "expected function name").text
|
|
let typeParams = p.parseTypeParams()
|
|
let params = p.parseParamList(true)
|
|
var retType: TypeExpr = nil
|
|
if p.check(tkArrow):
|
|
discard p.advance()
|
|
retType = p.parseType()
|
|
var body: Block = nil
|
|
if p.check(tkLBrace):
|
|
body = p.parseBlock()
|
|
elif p.check(tkSemicolon):
|
|
discard p.advance()
|
|
var declAttrs: seq[string] = @[]
|
|
if attrs.checked: declAttrs.add("Checked")
|
|
return Decl(kind: dkFunc, loc: loc, isPublic: isPublic,
|
|
declAttrs: declAttrs,
|
|
declFuncAsm: isAsm, declFuncCallConv: attrs.callConv,
|
|
declFuncConst: isConst, declFuncIsAsync: isAsync,
|
|
declFuncName: name, declFuncTypeParams: typeParams,
|
|
declFuncParams: params, declFuncReturnType: retType,
|
|
declFuncBody: body)
|
|
|
|
proc parseStructDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkStruct, "expected 'struct'")
|
|
let name = p.expect(tkIdent, "expected struct name").text
|
|
let typeParams = p.parseTypeParams()
|
|
discard p.expect(tkLBrace, "expected '{' to start struct body")
|
|
var fields: seq[StructField] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
# Skip newlines
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
let startPos = p.pos # Track position for infinite-loop safeguard
|
|
let fLoc = p.currentLoc
|
|
var fPub = false
|
|
if p.check(tkPub):
|
|
fPub = true
|
|
discard p.advance()
|
|
let fName = p.expectIdentOrKeyword("expected field name").text
|
|
discard p.expect(tkColon, "expected ':' after field name")
|
|
let fType = p.parseType()
|
|
if p.check(tkSemicolon) or p.check(tkComma):
|
|
discard p.advance()
|
|
fields.add(StructField(loc: fLoc, isPublic: fPub, name: fName, ftype: fType))
|
|
# Infinite-loop safeguard: if no progress, advance
|
|
if p.pos == startPos:
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close struct")
|
|
return Decl(kind: dkStruct, loc: loc, isPublic: isPublic,
|
|
declStructName: name, declStructTypeParams: typeParams,
|
|
declStructFields: fields)
|
|
|
|
proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkEnum, "expected 'enum'")
|
|
let name = p.expect(tkIdent, "expected enum name").text
|
|
var baseType: TypeExpr = nil
|
|
if p.check(tkColon):
|
|
discard p.advance()
|
|
baseType = p.parseType()
|
|
discard p.expect(tkLBrace, "expected '{' to start enum body")
|
|
var variants: seq[EnumVariant] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
# Skip newlines
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
let vLoc = p.currentLoc
|
|
let vName = p.expect(tkIdent, "expected variant name").text
|
|
var fields: seq[TypeExpr] = @[]
|
|
var namedFields: seq[tuple[name: string, ftype: TypeExpr]] = @[]
|
|
var discr: string = ""
|
|
if p.check(tkLParen):
|
|
discard p.advance()
|
|
while not p.check(tkRParen) and not p.isAtEnd:
|
|
if p.check(tkIdent) and p.peek(1) == tkColon:
|
|
let fn = p.advance().text
|
|
discard p.advance() # :
|
|
let ft = p.parseType()
|
|
namedFields.add((fn, ft))
|
|
else:
|
|
fields.add(p.parseType())
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRParen, "expected ')' to close variant")
|
|
if p.check(tkAssign):
|
|
discard p.advance()
|
|
discr = p.expect(tkIdent, "expected discriminant").text
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
variants.add(EnumVariant(loc: vLoc, name: vName, fields: fields,
|
|
namedFields: namedFields, discriminant: discr))
|
|
discard p.expect(tkRBrace, "expected '}' to close enum")
|
|
return Decl(kind: dkEnum, loc: loc, isPublic: isPublic,
|
|
declEnumName: name, declEnumBaseType: baseType,
|
|
declEnumVariants: variants)
|
|
|
|
proc parseUnionDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkUnion, "expected 'union'")
|
|
let name = p.expect(tkIdent, "expected union name").text
|
|
discard p.expect(tkLBrace, "expected '{' to start union body")
|
|
var fields: seq[UnionField] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
let fLoc = p.currentLoc
|
|
let fName = p.expect(tkIdent, "expected field name").text
|
|
discard p.expect(tkColon, "expected ':' after field name")
|
|
let fType = p.parseType()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
fields.add(UnionField(loc: fLoc, name: fName, ftype: fType))
|
|
discard p.expect(tkRBrace, "expected '}' to close union")
|
|
return Decl(kind: dkUnion, loc: loc, isPublic: isPublic,
|
|
declUnionName: name, declUnionFields: fields)
|
|
|
|
proc parseInterfaceDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkInterface, "expected 'interface'")
|
|
let name = p.expect(tkIdent, "expected interface name").text
|
|
discard p.expect(tkLBrace, "expected '{' to start interface body")
|
|
var methods: seq[Decl] = @[]
|
|
var assocTypes: seq[string] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
# Skip newlines
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
if p.check(tkType):
|
|
discard p.advance()
|
|
let assocName = p.expect(tkIdent, "expected associated type name").text
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
assocTypes.add(assocName)
|
|
else:
|
|
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
|
discard p.expect(tkRBrace, "expected '}' to close interface")
|
|
return Decl(kind: dkInterface, loc: loc, isPublic: isPublic,
|
|
declInterfaceName: name, declInterfaceAssocTypes: assocTypes,
|
|
declInterfaceMethods: methods)
|
|
|
|
proc parseImplDecl(p: var Parser): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkExtend, "expected 'extend'")
|
|
let typeName = p.expect(tkIdent, "expected type name").text
|
|
let typeParams = p.parseTypeParams()
|
|
var interfaceName = ""
|
|
if p.check(tkFor):
|
|
discard p.advance()
|
|
interfaceName = p.expect(tkIdent, "expected interface name").text
|
|
discard p.expect(tkLBrace, "expected '{' to start impl block")
|
|
var methods: seq[Decl] = @[]
|
|
var assocTypes: seq[tuple[name: string, typ: TypeExpr]] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
# Skip newlines
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
if p.check(tkType):
|
|
discard p.advance()
|
|
let assocName = p.expect(tkIdent, "expected associated type name").text
|
|
discard p.expect(tkAssign, "expected '=' in associated type implementation")
|
|
let assocType = p.parseType()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
assocTypes.add((assocName, assocType))
|
|
else:
|
|
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
|
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
|
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
|
declImplTypeParams: typeParams,
|
|
declImplInterface: interfaceName,
|
|
declImplAssocTypes: assocTypes,
|
|
declImplMethods: methods)
|
|
|
|
proc parseModuleDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkModule, "expected 'module'")
|
|
var path: seq[string] = @[]
|
|
path.add(p.expect(tkIdent, "expected module name").text)
|
|
while p.check(tkColonColon):
|
|
discard p.advance()
|
|
path.add(p.expect(tkIdent, "expected module path segment").text)
|
|
let name = path[^1]
|
|
discard p.expect(tkLBrace, "expected '{' to start module body")
|
|
var items: seq[Decl] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
items.add(p.parseDecl())
|
|
discard p.expect(tkRBrace, "expected '}' to close module")
|
|
return Decl(kind: dkModule, loc: loc, isPublic: isPublic,
|
|
declModuleName: name, declModulePath: path,
|
|
declModuleItems: items)
|
|
|
|
proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkImport, "expected 'import'")
|
|
var path: seq[string] = @[]
|
|
path.add(p.expect(tkIdent, "expected import path segment").text)
|
|
while p.check(tkColonColon):
|
|
# Lookahead: if :: is followed by { or *, don't consume it here
|
|
if p.peek(1) == tkLBrace or p.peek(1) == tkStar:
|
|
break
|
|
discard p.advance()
|
|
path.add(p.expect(tkIdent, "expected import path segment").text)
|
|
var kind = ukSingle
|
|
var names: seq[string] = @[]
|
|
if p.check(tkDotDotDot):
|
|
discard p.advance()
|
|
kind = ukGlob
|
|
elif p.check(tkDot):
|
|
discard p.advance()
|
|
discard p.expect(tkStar, "expected '*'")
|
|
kind = ukGlob
|
|
elif p.check(tkColonColon):
|
|
discard p.advance()
|
|
if p.check(tkLBrace):
|
|
discard p.advance()
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
while p.check(tkNewLine):
|
|
discard p.advance()
|
|
if p.check(tkRBrace) or p.isAtEnd:
|
|
break
|
|
names.add(p.expect(tkIdent, "expected name in multi-import").text)
|
|
if p.check(tkComma):
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close multi-import")
|
|
kind = ukMulti
|
|
elif p.check(tkStar):
|
|
discard p.advance()
|
|
kind = ukGlob
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Decl(kind: dkUse, loc: loc, declUsePath: path, declUseKind: kind,
|
|
declUseNames: names, declUseTargetOs: attrs.targetOs)
|
|
|
|
proc parseConstDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkConst, "expected 'const'")
|
|
let name = p.expect(tkIdent, "expected constant name").text
|
|
var ty: TypeExpr = nil
|
|
if p.check(tkColon):
|
|
discard p.advance()
|
|
ty = p.parseType()
|
|
discard p.expect(tkAssign, "expected '=' in const declaration")
|
|
let value = p.parseExpr()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Decl(kind: dkConst, loc: loc, isPublic: isPublic,
|
|
declConstName: name, declConstType: ty, declConstValue: value)
|
|
|
|
proc parseTypeAliasDecl(p: var Parser, isPublic: bool): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkType, "expected 'type'")
|
|
let name = p.expect(tkIdent, "expected type alias name").text
|
|
discard p.expect(tkAssign, "expected '=' in type alias")
|
|
let ty = p.parseType()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Decl(kind: dkTypeAlias, loc: loc, isPublic: isPublic,
|
|
declAliasName: name, declAliasType: ty)
|
|
|
|
proc parseExternDecl(p: var Parser, isPublic: bool, attrs: ParsedAttrs): Decl =
|
|
let loc = p.currentLoc
|
|
discard p.expect(tkExtern, "expected 'extern'")
|
|
if p.check(tkFunc):
|
|
let funcDecl = p.parseFuncDecl(isPublic, false, attrs)
|
|
# Convert dkFunc to dkExternFunc
|
|
return Decl(kind: dkExternFunc, loc: funcDecl.loc, isPublic: isPublic,
|
|
declExtFuncName: funcDecl.declFuncName,
|
|
declExtFuncDll: attrs.importLib,
|
|
declExtFuncCallConv: attrs.callConv,
|
|
declExtFuncParams: funcDecl.declFuncParams,
|
|
declExtFuncVariadic: false,
|
|
declExtFuncReturnType: funcDecl.declFuncReturnType)
|
|
elif p.check(tkLBrace):
|
|
discard p.advance()
|
|
var items: seq[Decl] = @[]
|
|
while not p.check(tkRBrace) and not p.isAtEnd:
|
|
if p.check(tkFunc):
|
|
items.add(p.parseFuncDecl(isPublic, false, attrs))
|
|
elif p.check(tkIdent):
|
|
let vName = p.advance().text
|
|
discard p.expect(tkColon, "expected ':'")
|
|
let vType = p.parseType()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
items.add(Decl(kind: dkExternVar, loc: loc, isPublic: isPublic,
|
|
declExtVarName: vName, declExtVarType: vType))
|
|
else:
|
|
p.emitError("expected function or variable in extern block")
|
|
discard p.advance()
|
|
discard p.expect(tkRBrace, "expected '}' to close extern block")
|
|
return Decl(kind: dkExternBlock, loc: loc, declExtBlockDll: attrs.importLib,
|
|
declExtBlockCallConv: attrs.callConv, declExtBlockItems: items)
|
|
else:
|
|
# Single extern variable
|
|
let vName = p.expect(tkIdent, "expected variable name").text
|
|
discard p.expect(tkColon, "expected ':'")
|
|
let vType = p.parseType()
|
|
if p.check(tkSemicolon):
|
|
discard p.advance()
|
|
return Decl(kind: dkExternVar, loc: loc, isPublic: isPublic,
|
|
declExtVarName: vName, declExtVarType: vType)
|
|
|
|
proc parseDecl(p: var Parser): Decl =
|
|
let loc = p.currentLoc
|
|
var isPublic = false
|
|
if p.check(tkPub):
|
|
isPublic = true
|
|
discard p.advance()
|
|
|
|
var attrs = ParsedAttrs()
|
|
if p.check(tkAt):
|
|
attrs = p.parseAttrs()
|
|
p.skipNewlines()
|
|
|
|
var isConst = false
|
|
if p.check(tkConst) and p.peek(1) == tkFunc:
|
|
isConst = true
|
|
discard p.advance()
|
|
|
|
var isAsync = false
|
|
if p.check(tkAsync) and p.peek(1) == tkFunc:
|
|
isAsync = true
|
|
discard p.advance()
|
|
|
|
case p.peek()
|
|
of tkFunc:
|
|
return p.parseFuncDecl(isPublic, false, attrs, isConst, isAsync)
|
|
of tkStruct:
|
|
return p.parseStructDecl(isPublic)
|
|
of tkEnum:
|
|
return p.parseEnumDecl(isPublic)
|
|
of tkUnion:
|
|
return p.parseUnionDecl(isPublic)
|
|
of tkInterface:
|
|
return p.parseInterfaceDecl(isPublic)
|
|
of tkExtend:
|
|
return p.parseImplDecl()
|
|
of tkModule:
|
|
return p.parseModuleDecl(isPublic)
|
|
of tkImport:
|
|
return p.parseUseDecl(attrs)
|
|
of tkConst:
|
|
return p.parseConstDecl(isPublic)
|
|
of tkType:
|
|
return p.parseTypeAliasDecl(isPublic)
|
|
of tkExtern:
|
|
return p.parseExternDecl(isPublic, attrs)
|
|
else:
|
|
p.emitError(loc, "expected declaration")
|
|
p.synchronize()
|
|
return Decl(kind: dkFunc, loc: loc, declFuncName: "")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Module (top-level)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
proc parseModule*(p: var Parser, name: string): ParseResult =
|
|
var modu = newModule(name)
|
|
while not p.isAtEnd:
|
|
if p.check(tkNewLine):
|
|
discard p.advance()
|
|
continue
|
|
modu.items.add(p.parseDecl())
|
|
result = ParseResult(module: modu, diagnostics: p.diagnostics)
|
|
|
|
proc parse*(tokens: seq[Token], sourceName: string = "<input>"): ParseResult =
|
|
var p = initParser(tokens, sourceName)
|
|
result = p.parseModule(sourceName)
|