# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # This module implements the parser of the standard Nim syntax. # The parser strictly reflects the grammar ("doc/grammar.txt"); however # it uses several helper routines to keep the parser small. A special # efficient algorithm is used for the precedence levels. The parser here can # be seen as a refinement of the grammar, as it specifies how the AST is built # from the grammar and how comments belong to the AST. # In fact the grammar is generated from this file: when isMainModule or defined(nimTestGrammar): # Leave a note in grammar.txt that it is generated: #| # This file is generated by compiler/parser.nim. import std/pegs when defined(nimPreviewSlimSystem): import std/syncio proc writeGrammarFile(x: string) = var outp = open(x, fmWrite) for line in lines("compiler/parser.nim"): if line =~ peg" \s* '#| ' {.*}": outp.write matches[0], "\L" outp.close when defined(nimTestGrammar): import std/os from ../testament/lib/stdtest/specialpaths import buildDir const newGrammarText = buildDir / "grammar.txt" if not dirExists(buildDir): createDir(buildDir) writeGrammarFile(newGrammarText) proc checkSameGrammar*() = doAssert sameFileContent(newGrammarText, "doc/grammar.txt"), "execute 'nim r compiler/parser.nim' to keep grammar.txt up-to-date" else: writeGrammarFile("doc/grammar.txt") import ".." / tools / grammar_nanny checkGrammarFile() import llstream, lexer, idents, msgs, options, lineinfos, pathutils when not defined(nimCustomAst): import ast else: import plugins / customast import std/strutils when defined(nimpretty): import layouter when defined(nimPreviewSlimSystem): import std/assertions type Parser* = object # A Parser object represents a file that # is being parsed currInd: int # current indentation level firstTok: bool # Has the first token been read? hasProgress: bool # some while loop requires progress ensurance lex*: Lexer # The lexer that is used for parsing tok*: Token # The current token lineStartPrevious*: int lineNumberPrevious*: int bufposPrevious*: int inPragma*: int # Pragma level inSemiStmtList*: int when not defined(nimCustomAst): emptyNode: PNode when defined(nimpretty): em*: Emitter SymbolMode = enum smNormal, smAllowNil, smAfterDot PrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmTrySimple when defined(nimCustomAst): # For the `customast` version we cannot share nodes, not even empty nodes: template emptyNode(p: Parser): PNode = newNode(nkEmpty) # helpers for the other parsers proc isOperator*(tok: Token): bool proc getTok*(p: var Parser) proc parMessage*(p: Parser, msg: TMsgKind, arg: string = "") proc skipComment*(p: var Parser, node: PNode) proc newNodeP*(kind: TNodeKind, p: Parser): PNode proc newIntNodeP*(kind: TNodeKind, intVal: BiggestInt, p: Parser): PNode proc newFloatNodeP*(kind: TNodeKind, floatVal: BiggestFloat, p: Parser): PNode proc newStrNodeP*(kind: TNodeKind, strVal: sink string, p: Parser): PNode proc newIdentNodeP*(ident: PIdent, p: Parser): PNode proc expectIdentOrKeyw*(p: Parser) proc expectIdent*(p: Parser) proc parLineInfo*(p: Parser): TLineInfo proc eat*(p: var Parser, tokType: TokType) proc skipInd*(p: var Parser) proc optPar*(p: var Parser) proc optInd*(p: var Parser, n: PNode) proc indAndComment*(p: var Parser, n: PNode, maybeMissEquals = false) proc setBaseFlags*(n: PNode, base: NumericalBase) proc parseSymbol*(p: var Parser, mode = smNormal): PNode proc parseTry(p: var Parser; isExpr: bool): PNode proc parseCase(p: var Parser): PNode proc parseStmtPragma(p: var Parser): PNode proc parsePragma(p: var Parser): PNode proc postExprBlocks(p: var Parser, x: PNode): PNode proc parseExprStmt(p: var Parser): PNode proc parseBlock(p: var Parser): PNode proc primary(p: var Parser, mode: PrimaryMode): PNode proc simpleExprAux(p: var Parser, limit: int, mode: PrimaryMode): PNode # implementation template prettySection(body) = when defined(nimpretty): beginSection(p.em) body when defined(nimpretty): endSection(p.em) proc getTok(p: var Parser) = ## Get the next token from the parser's lexer, and store it in the parser's ## `tok` member. p.lineNumberPrevious = p.lex.lineNumber p.lineStartPrevious = p.lex.lineStart p.bufposPrevious = p.lex.bufpos rawGetTok(p.lex, p.tok) p.hasProgress = true when defined(nimpretty): emitTok(p.em, p.lex, p.tok) # skip the additional tokens that nimpretty needs but the parser has no # interest in: while p.tok.tokType == tkComment: rawGetTok(p.lex, p.tok) emitTok(p.em, p.lex, p.tok) proc openParser*(p: var Parser, fileIdx: FileIndex, inputStream: PLLStream, cache: IdentCache; config: ConfigRef) = ## Open a parser, using the given arguments to set up its internal state. ## reset(p.tok) openLexer(p.lex, fileIdx, inputStream, cache, config) when defined(nimpretty): openEmitter(p.em, cache, config, fileIdx) getTok(p) # read the first token p.firstTok = true when not defined(nimCustomAst): p.emptyNode = newNode(nkEmpty) proc openParser*(p: var Parser, filename: AbsoluteFile, inputStream: PLLStream, cache: IdentCache; config: ConfigRef) = openParser(p, fileInfoIdx(config, filename), inputStream, cache, config) proc closeParser*(p: var Parser) = ## Close a parser, freeing up its resources. closeLexer(p.lex) proc parMessage(p: Parser, msg: TMsgKind, arg = "") = ## Produce and emit the parser message `arg` to output. lexMessageTok(p.lex, msg, p.tok, arg) proc parMessage(p: Parser, msg: string, tok: Token) = ## Produce and emit a parser message to output about the token `tok` parMessage(p, errGenerated, msg % prettyTok(tok)) proc parMessage(p: Parser, arg: string) = ## Produce and emit the parser message `arg` to output. lexMessageTok(p.lex, errGenerated, p.tok, arg) template withInd(p, body: untyped) = let oldInd = p.currInd p.currInd = p.tok.indent body p.currInd = oldInd template newlineWasSplitting(p: var Parser) = when defined(nimpretty): layouter.newlineWasSplitting(p.em) template realInd(p): bool = p.tok.indent > p.currInd template sameInd(p): bool = p.tok.indent == p.currInd template sameOrNoInd(p): bool = p.tok.indent == p.currInd or p.tok.indent < 0 proc validInd(p: var Parser): bool {.inline.} = result = p.tok.indent < 0 or p.tok.indent > p.currInd proc rawSkipComment(p: var Parser, node: PNode) = if p.tok.tokType == tkComment: if node != nil: var rhs = node.comment when defined(nimpretty): if p.tok.commentOffsetB > p.tok.commentOffsetA: rhs.add fileSection(p.lex.config, p.lex.fileIdx, p.tok.commentOffsetA, p.tok.commentOffsetB) else: rhs.add p.tok.literal else: rhs.add p.tok.literal node.comment = move rhs else: parMessage(p, errInternal, "skipComment") getTok(p) proc skipComment(p: var Parser, node: PNode) = if p.tok.indent < 0: rawSkipComment(p, node) proc flexComment(p: var Parser, node: PNode) = if p.tok.indent < 0 or realInd(p): rawSkipComment(p, node) const errInvalidIndentation = "invalid indentation" errIdentifierExpected = "identifier expected, but got '$1'" errExprExpected = "expression expected, but found '$1'" proc skipInd(p: var Parser) = if p.tok.indent >= 0: if not realInd(p): parMessage(p, errInvalidIndentation) proc optPar(p: var Parser) = if p.tok.indent >= 0: if p.tok.indent < p.currInd: parMessage(p, errInvalidIndentation) proc optInd(p: var Parser, n: PNode) = skipComment(p, n) skipInd(p) proc getTokNoInd(p: var Parser) = getTok(p) if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) proc expectIdentOrKeyw(p: Parser) = if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType): lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok)) proc expectIdent(p: Parser) = if p.tok.tokType != tkSymbol: lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok)) proc eat(p: var Parser, tokType: TokType) = ## Move the parser to the next token if the current token is of type ## `tokType`, otherwise error. if p.tok.tokType == tokType: getTok(p) else: lexMessage(p.lex, errGenerated, "expected: '" & $tokType & "', but got: '" & prettyTok(p.tok) & "'") proc parLineInfo(p: Parser): TLineInfo = ## Retrieve the line information associated with the parser's current state. result = getLineInfo(p.lex, p.tok) proc indAndComment(p: var Parser, n: PNode, maybeMissEquals = false) = if p.tok.indent > p.currInd: if p.tok.tokType == tkComment: rawSkipComment(p, n) elif maybeMissEquals: let col = p.bufposPrevious - p.lineStartPrevious var info = newLineInfo(p.lex.fileIdx, p.lineNumberPrevious, col) parMessage(p, "invalid indentation, maybe you forgot a '=' at $1 ?" % [p.lex.config$info]) else: parMessage(p, errInvalidIndentation) else: skipComment(p, n) proc newNodeP(kind: TNodeKind, p: Parser): PNode = result = newNode(kind, parLineInfo(p)) proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: Parser): PNode = result = newAtom(kind, intVal, parLineInfo(p)) proc newFloatNodeP(kind: TNodeKind, floatVal: BiggestFloat, p: Parser): PNode = result = newAtom(kind, floatVal, parLineInfo(p)) proc newStrNodeP(kind: TNodeKind, strVal: sink string, p: Parser): PNode = result = newAtom(kind, strVal, parLineInfo(p)) proc newIdentNodeP(ident: PIdent, p: Parser): PNode = result = newAtom(ident, parLineInfo(p)) proc parseExpr(p: var Parser): PNode proc parseStmt(p: var Parser): PNode proc parseTypeDesc(p: var Parser, fullExpr = false): PNode proc parseTypeDefValue(p: var Parser): PNode proc parseParamList(p: var Parser, retColon = true): PNode proc isSigilLike(tok: Token): bool {.inline.} = result = tok.tokType == tkOpr and tok.ident.s[0] == '@' proc isRightAssociative(tok: Token): bool {.inline.} = ## Determines whether the token is right assocative. result = tok.tokType == tkOpr and tok.ident.s[0] == '^' # or (tok.ident.s.len > 1 and tok.ident.s[^1] == '>') proc isUnary(tok: Token): bool = ## Check if the given token is a unary operator tok.tokType in {tkOpr, tkDotDot} and tok.spacing == {tsLeading} proc checkBinary(p: Parser) {.inline.} = ## Check if the current parser token is a binary operator. # we don't check '..' here as that's too annoying if p.tok.tokType == tkOpr: if p.tok.spacing == {tsTrailing}: parMessage(p, warnInconsistentSpacing, prettyTok(p.tok)) #| module = complexOrSimpleStmt ^* (';' / IND{=}) #| #| comma = ',' COMMENT? #| semicolon = ';' COMMENT? #| colon = ':' COMMENT? #| colcom = ':' COMMENT? #| #| operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9 #| | 'or' | 'xor' | 'and' #| | 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from' #| | 'div' | 'mod' | 'shl' | 'shr' | 'not' | '..' #| #| prefixOperator = operator #| #| optInd = COMMENT? IND? #| optPar = (IND{>} | IND{=})? #| #| simpleExpr = arrowExpr (OP0 optInd arrowExpr)* pragma? #| arrowExpr = assignExpr (OP1 optInd assignExpr)* #| assignExpr = orExpr (OP2 optInd orExpr)* #| orExpr = andExpr (OP3 optInd andExpr)* #| andExpr = cmpExpr (OP4 optInd cmpExpr)* #| cmpExpr = sliceExpr (OP5 optInd sliceExpr)* #| sliceExpr = ampExpr (OP6 optInd ampExpr)* #| ampExpr = plusExpr (OP7 optInd plusExpr)* #| plusExpr = mulExpr (OP8 optInd mulExpr)* #| mulExpr = dollarExpr (OP9 optInd dollarExpr)* #| dollarExpr = primary (OP10 optInd primary)* proc isOperator(tok: Token): bool = #| operatorB = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9 | #| 'div' | 'mod' | 'shl' | 'shr' | 'in' | 'notin' | #| 'is' | 'isnot' | 'not' | 'of' | 'as' | 'from' | '..' | 'and' | 'or' | 'xor' tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs, tkFrom, tkDotDot, tkAnd, tkOr, tkXor} proc colcom(p: var Parser, n: PNode) = eat(p, tkColon) skipComment(p, n) const tkBuiltInMagics = {tkType, tkStatic, tkAddr} template setEndInfo() = when defined(nimsuggest): result.endInfo = TLineInfo(fileIndex: p.lex.fileIdx, line: p.lex.previousTokenEnd.line, col: p.lex.previousTokenEnd.col) proc parseSymbol(p: var Parser, mode = smNormal): PNode = #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`' #| | IDENT | 'addr' | 'type' | 'static' #| symbolOrKeyword = symbol | KEYW case p.tok.tokType of tkSymbol: result = newIdentNodeP(p.tok.ident, p) getTok(p) of tokKeywordLow..tokKeywordHigh: if p.tok.tokType in tkBuiltInMagics or mode == smAfterDot: # for backwards compatibility these 2 are always valid: result = newIdentNodeP(p.tok.ident, p) getTok(p) elif p.tok.tokType == tkNil and mode == smAllowNil: result = newNodeP(nkNilLit, p) getTok(p) else: parMessage(p, errIdentifierExpected, p.tok) result = p.emptyNode of tkAccent: result = newNodeP(nkAccQuoted, p) getTok(p) # progress guaranteed while true: case p.tok.tokType of tkAccent: if not result.hasSon: parMessage(p, errIdentifierExpected, p.tok) break of tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi: let lineinfo = parLineInfo(p) var accm = "" while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi}: accm.add($p.tok) getTok(p) let node = newAtom(p.lex.cache.getIdent(accm), lineinfo) result.add(node) of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCustomLit: result.add(newIdentNodeP(p.lex.cache.getIdent($p.tok), p)) getTok(p) else: parMessage(p, errIdentifierExpected, p.tok) break eat(p, tkAccent) else: parMessage(p, errIdentifierExpected, p.tok) # BUGFIX: We must consume a token here to prevent endless loops! # But: this really sucks for idetools and keywords, so we don't do it # if it is a keyword: #if not isKeyword(p.tok.tokType): getTok(p) result = p.emptyNode setEndInfo() proc equals(p: var Parser, a: PNode): PNode = if p.tok.tokType == tkEquals: result = newNodeP(nkExprEqExpr, p) getTok(p) #optInd(p, result) result.add(a) result.add(parseExpr(p)) else: result = a proc colonOrEquals(p: var Parser, a: PNode): PNode = if p.tok.tokType == tkColon: result = newNodeP(nkExprColonExpr, p) getTok(p) newlineWasSplitting(p) #optInd(p, result) result.add(a) result.add(parseExpr(p)) else: result = equals(p, a) proc exprColonEqExpr(p: var Parser): PNode = #| exprColonEqExpr = expr ((':'|'=') expr #| / doBlock extraPostExprBlock*)? var a = parseExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, a) else: result = colonOrEquals(p, a) proc exprEqExpr(p: var Parser): PNode = #| exprEqExpr = expr ('=' expr #| / doBlock extraPostExprBlock*)? var a = parseExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, a) else: result = equals(p, a) proc exprList(p: var Parser, endTok: TokType, result: PNode) = #| exprList = expr ^+ comma when defined(nimpretty): inc p.em.doIndentMore getTok(p) optInd(p, result) # progress guaranteed var a = parseExpr(p) result.add(a) while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof): if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) var a = parseExpr(p) result.add(a) when defined(nimpretty): dec p.em.doIndentMore proc optionalExprList(p: var Parser, endTok: TokType, result: PNode) = #| optionalExprList = expr ^* comma when defined(nimpretty): inc p.em.doIndentMore getTok(p) optInd(p, result) # progress guaranteed while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof): var a = parseExpr(p) result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) when defined(nimpretty): dec p.em.doIndentMore proc exprColonEqExprListAux(p: var Parser, endTok: TokType, result: PNode) = assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi}) getTok(p) flexComment(p, result) optPar(p) # progress guaranteed while p.tok.tokType != endTok and p.tok.tokType != tkEof: var a = exprColonEqExpr(p) result.add(a) if p.tok.tokType != tkComma: break elif result.kind == nkPar: result.transitionSonsKind(nkTupleConstr) getTok(p) skipComment(p, a) optPar(p) eat(p, endTok) proc exprColonEqExprList(p: var Parser, kind: TNodeKind, endTok: TokType): PNode = #| exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)? result = newNodeP(kind, p) exprColonEqExprListAux(p, endTok, result) proc dotExpr(p: var Parser, a: PNode): PNode = var info = p.parLineInfo getTok(p) result = newNode(nkDotExpr, info) optInd(p, result) result.add(a) result.add(parseSymbol(p, smAfterDot)) if p.tok.tokType == tkBracketLeColon and tsLeading notin p.tok.spacing: var x = newNode(nkBracketExpr, p.parLineInfo) # rewrite 'x.y[:z]()' to 'y[z](x)' x.add result.secondSon exprList(p, tkBracketRi, x) eat(p, tkBracketRi) var y = newNode(nkCall, p.parLineInfo) y.add x y.add result.firstSon if p.tok.tokType == tkParLe and tsLeading notin p.tok.spacing: exprColonEqExprListAux(p, tkParRi, y) result = y proc dotLikeExpr(p: var Parser, a: PNode): PNode = var info = p.parLineInfo result = newNode(nkInfix, info) optInd(p, result) var opNode = newIdentNodeP(p.tok.ident, p) getTok(p) result.add(opNode) result.add(a) result.add(parseSymbol(p, smAfterDot)) proc qualifiedIdent(p: var Parser): PNode = #| qualifiedIdent = symbol ('.' optInd symbolOrKeyword)? result = parseSymbol(p) if p.tok.tokType == tkDot: result = dotExpr(p, result) proc setOrTableConstr(p: var Parser): PNode = #| setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}' result = newNodeP(nkCurly, p) getTok(p) # skip '{' optInd(p, result) if p.tok.tokType == tkColon: getTok(p) # skip ':' result.transitionSonsKind(nkTableConstr) else: # progress guaranteed while p.tok.tokType notin {tkCurlyRi, tkEof}: var a = exprColonEqExpr(p) if a.kind == nkExprColonExpr: result.transitionSonsKind(nkTableConstr) result.add(a) if p.tok.tokType != tkComma: break getTok(p) skipComment(p, a) optPar(p) eat(p, tkCurlyRi) # skip '}' proc parseCast(p: var Parser): PNode = #| castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') / # ('(' optInd exprColonEqExpr optPar ')') result = newNodeP(nkCast, p) getTok(p) if p.tok.tokType == tkBracketLe: getTok(p) optInd(p, result) result.add(parseTypeDesc(p)) optPar(p) eat(p, tkBracketRi) eat(p, tkParLe) optInd(p, result) result.add(parseExpr(p)) else: result.add p.emptyNode eat(p, tkParLe) optInd(p, result) result.add(exprColonEqExpr(p)) optPar(p) eat(p, tkParRi) setEndInfo() template setNodeFlag(n: PNode; f: untyped) = when defined(nimCustomAst): discard else: incl n.flags, f proc setBaseFlags(n: PNode, base: NumericalBase) = case base of base10: discard of base2: setNodeFlag(n, nfBase2) of base8: setNodeFlag(n, nfBase8) of base16: setNodeFlag(n, nfBase16) proc parseGStrLit(p: var Parser, a: PNode): PNode = case p.tok.tokType of tkGStrLit: result = newNodeP(nkCallStrLit, p) result.add(a) result.add(newStrNodeP(nkRStrLit, p.tok.literal, p)) getTok(p) of tkGTripleStrLit: result = newNodeP(nkCallStrLit, p) result.add(a) result.add(newStrNodeP(nkTripleStrLit, p.tok.literal, p)) getTok(p) else: result = a setEndInfo() proc complexOrSimpleStmt(p: var Parser): PNode proc simpleExpr(p: var Parser, mode = pmNormal): PNode proc parseIfOrWhenExpr(p: var Parser, kind: TNodeKind): PNode proc semiStmtList(p: var Parser, result: PNode) = inc p.inSemiStmtList withInd(p): # Be lenient with the first stmt/expr let a = case p.tok.tokType of tkIf: parseIfOrWhenExpr(p, nkIfStmt) of tkWhen: parseIfOrWhenExpr(p, nkWhenStmt) else: complexOrSimpleStmt(p) result.add a while p.tok.tokType != tkEof: if p.tok.tokType == tkSemiColon: getTok(p) if p.tok.tokType == tkParRi: break elif not (sameInd(p) or realInd(p)): parMessage(p, errInvalidIndentation) let a = complexOrSimpleStmt(p) if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) getTok(p) else: result.add a dec p.inSemiStmtList result.transitionSonsKind(nkStmtListExpr) proc parsePar(p: var Parser): PNode = #| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' #| | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let' #| | 'when' | 'var' | 'mixin' #| par = '(' optInd #| ( &parKeyw (ifExpr / complexOrSimpleStmt) ^+ ';' #| | ';' (ifExpr / complexOrSimpleStmt) ^+ ';' #| | pragmaStmt #| | simpleExpr ( (doBlock extraPostExprBlock*) #| | ('=' expr (';' (ifExpr / complexOrSimpleStmt) ^+ ';' )? ) #| | (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) ) #| optPar ')' # # unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a # leading ';' could be used to enforce a 'stmt' context ... result = newNodeP(nkPar, p) getTok(p) optInd(p, result) flexComment(p, result) if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, tkTry, tkDefer, tkFinally, tkExcept, tkBlock, tkConst, tkLet, tkWhen, tkVar, tkFor, tkMixin}: # XXX 'bind' used to be an expression, so we exclude it here; # tests/reject/tbind2 fails otherwise. semiStmtList(p, result) elif p.tok.tokType == tkSemiColon: # '(;' enforces 'stmt' context: getTok(p) optInd(p, result) semiStmtList(p, result) elif p.tok.tokType == tkCurlyDotLe: result.add(parseStmtPragma(p)) elif p.tok.tokType == tkParRi: # Empty tuple '()' result.transitionSonsKind(nkTupleConstr) else: var a = simpleExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, a) elif p.tok.tokType == tkEquals: # special case: allow assignments let asgn = newNodeP(nkAsgn, p) getTok(p) optInd(p, result) let b = parseExpr(p) asgn.add a asgn.add b result.add(asgn) if p.tok.tokType == tkSemiColon: semiStmtList(p, result) elif p.tok.tokType == tkSemiColon: # stmt context: result.add(a) semiStmtList(p, result) else: a = colonOrEquals(p, a) if a.kind == nkExprColonExpr: result.transitionSonsKind(nkTupleConstr) result.add(a) if p.tok.tokType == tkComma: getTok(p) skipComment(p, a) # (1,) produces a tuple expression: result.transitionSonsKind(nkTupleConstr) # progress guaranteed while p.tok.tokType != tkParRi and p.tok.tokType != tkEof: var a = exprColonEqExpr(p) result.add(a) if p.tok.tokType != tkComma: break getTok(p) skipComment(p, a) optPar(p) eat(p, tkParRi) setEndInfo() proc identOrLiteral(p: var Parser, mode: PrimaryMode): PNode = #| literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT #| | UINT_LIT | UINT8_LIT | UINT16_LIT | UINT32_LIT | UINT64_LIT #| | FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT #| | STR_LIT | RSTR_LIT | TRIPLESTR_LIT #| | CHAR_LIT | CUSTOM_NUMERIC_LIT #| | NIL #| generalizedLit = GENERALIZED_STR_LIT | GENERALIZED_TRIPLESTR_LIT #| identOrLiteral = generalizedLit | symbol | literal #| | par | arrayConstr | setOrTableConstr | tupleConstr #| | castExpr #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' case p.tok.tokType of tkSymbol, tkBuiltInMagics, tkOut: result = newIdentNodeP(p.tok.ident, p) getTok(p) result = parseGStrLit(p, result) of tkAccent: result = parseSymbol(p) # literals of tkIntLit: result = newIntNodeP(nkIntLit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkInt8Lit: result = newIntNodeP(nkInt8Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkInt16Lit: result = newIntNodeP(nkInt16Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkInt32Lit: result = newIntNodeP(nkInt32Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkInt64Lit: result = newIntNodeP(nkInt64Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkUIntLit: result = newIntNodeP(nkUIntLit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkUInt8Lit: result = newIntNodeP(nkUInt8Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkUInt16Lit: result = newIntNodeP(nkUInt16Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkUInt32Lit: result = newIntNodeP(nkUInt32Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkUInt64Lit: result = newIntNodeP(nkUInt64Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkFloatLit: result = newFloatNodeP(nkFloatLit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkFloat32Lit: result = newFloatNodeP(nkFloat32Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkFloat64Lit: result = newFloatNodeP(nkFloat64Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkFloat128Lit: result = newFloatNodeP(nkFloat128Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) of tkStrLit: result = newStrNodeP(nkStrLit, p.tok.literal, p) getTok(p) of tkRStrLit: result = newStrNodeP(nkRStrLit, p.tok.literal, p) getTok(p) of tkTripleStrLit: result = newStrNodeP(nkTripleStrLit, p.tok.literal, p) getTok(p) of tkCharLit: result = newIntNodeP(nkCharLit, ord(p.tok.literal[0]), p) getTok(p) of tkCustomLit: let splitPos = p.tok.iNumber.int let str = newStrNodeP(nkRStrLit, p.tok.literal.substr(0, splitPos-1), p) let callee = newIdentNodeP(getIdent(p.lex.cache, p.tok.literal.substr(splitPos)), p) result = newNodeP(nkDotExpr, p) result.add str result.add callee getTok(p) of tkNil: result = newNodeP(nkNilLit, p) getTok(p) of tkParLe: # () constructor if mode in {pmTypeDesc, pmTypeDef}: result = exprColonEqExprList(p, nkPar, tkParRi) else: result = parsePar(p) of tkCurlyLe: # {} constructor result = setOrTableConstr(p) of tkBracketLe: # [] constructor result = exprColonEqExprList(p, nkBracket, tkBracketRi) of tkCast: result = parseCast(p) else: parMessage(p, errExprExpected, p.tok) getTok(p) # we must consume a token here to prevent endless loops! result = p.emptyNode proc namedParams(p: var Parser, callee: PNode, kind: TNodeKind, endTok: TokType): PNode = let a = callee result = newNodeP(kind, p) result.add(a) # progress guaranteed exprColonEqExprListAux(p, endTok, result) proc commandParam(p: var Parser, isFirstParam: var bool; mode: PrimaryMode): PNode = if mode == pmTypeDesc: result = simpleExpr(p, mode) elif not isFirstParam: result = exprEqExpr(p) else: result = parseExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, result) isFirstParam = false proc commandExpr(p: var Parser; r: PNode; mode: PrimaryMode): PNode = if mode == pmTrySimple: result = r else: result = newNodeP(nkCommand, p) result.add(r) var isFirstParam = true # progress NOT guaranteed p.hasProgress = false result.add commandParam(p, isFirstParam, mode) proc isDotLike(tok: Token): bool = result = tok.tokType == tkOpr and tok.ident.s.len > 1 and tok.ident.s[0] == '.' and tok.ident.s[1] != '.' proc primarySuffix(p: var Parser, r: PNode, baseIndent: int, mode: PrimaryMode): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' #| | '.' optInd symbolOrKeyword ('[:' exprList ']' ( '(' exprColonEqExpr ')' )?)? generalizedLit? #| | DOTLIKEOP optInd symbolOrKeyword generalizedLit? #| | '[' optInd exprColonEqExprList optPar ']' #| | '{' optInd exprColonEqExprList optPar '}' # XXX strong spaces need to be reflected above result = r # progress guaranteed while p.tok.indent < 0 or (p.tok.tokType == tkDot and p.tok.indent >= baseIndent): case p.tok.tokType of tkParLe: # progress guaranteed if tsLeading in p.tok.spacing: result = commandExpr(p, result, mode) break result = namedParams(p, result, nkCall, tkParRi) if result.has2Sons and result.secondSon.kind == nkExprColonExpr: result.transitionSonsKind(nkObjConstr) of tkDot: # progress guaranteed result = dotExpr(p, result) result = parseGStrLit(p, result) of tkBracketLe: # progress guaranteed if tsLeading in p.tok.spacing: result = commandExpr(p, result, mode) break result = namedParams(p, result, nkBracketExpr, tkBracketRi) of tkCurlyLe: # progress guaranteed if tsLeading in p.tok.spacing: result = commandExpr(p, result, mode) break result = namedParams(p, result, nkCurlyExpr, tkCurlyRi) of tkSymbol, tkAccent, tkIntLit..tkCustomLit, tkNil, tkCast, tkOpr, tkDotDot, tkVar, tkOut, tkStatic, tkType, tkEnum, tkTuple, tkObject, tkProc: # XXX: In type sections we allow the free application of the # command syntax, with the exception of expressions such as # `foo ref` or `foo ptr`. Unfortunately, these two are also # used as infix operators for the memory regions feature and # the current parsing rules don't play well here. let isDotLike2 = p.tok.isDotLike if isDotLike2 and p.lex.config.isDefined("nimPreviewDotLikeOps"): # synchronize with `tkDot` branch result = dotLikeExpr(p, result) result = parseGStrLit(p, result) else: if isDotLike2: parMessage(p, warnDotLikeOps, "dot-like operators will be parsed differently with `-d:nimPreviewDotLikeOps`") if p.inPragma == 0 and (isUnary(p.tok) or p.tok.tokType notin {tkOpr, tkDotDot}): # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet # solution, but pragmas.nim can't handle that result = commandExpr(p, result, mode) break else: break proc parseOperators(p: var Parser, headNode: PNode, limit: int, mode: PrimaryMode): PNode = result = headNode # expand while operators have priorities higher than 'limit' var opPrec = getPrecedence(p.tok) let modeB = if mode == pmTypeDef: pmTypeDesc else: mode # the operator itself must not start on a new line: # progress guaranteed while opPrec >= limit and p.tok.indent < 0 and not isUnary(p.tok): checkBinary(p) let leftAssoc = ord(not isRightAssociative(p.tok)) var a = newNodeP(nkInfix, p) var opNode = newIdentNodeP(p.tok.ident, p) # skip operator: getTok(p) flexComment(p, a) optPar(p) # read sub-expression with higher priority: var b = simpleExprAux(p, opPrec + leftAssoc, modeB) a.add(opNode) a.add(result) a.add(b) result = a opPrec = getPrecedence(p.tok) setEndInfo() proc simpleExprAux(p: var Parser, limit: int, mode: PrimaryMode): PNode = var mode = mode result = primary(p, mode) if mode == pmTrySimple: mode = pmNormal if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)) and mode == pmNormal: var pragmaExp = newNodeP(nkPragmaExpr, p) pragmaExp.add result pragmaExp.add p.parsePragma result = pragmaExp result = parseOperators(p, result, limit, mode) proc simpleExpr(p: var Parser, mode = pmNormal): PNode = when defined(nimpretty): inc p.em.doIndentMore result = simpleExprAux(p, -1, mode) when defined(nimpretty): dec p.em.doIndentMore proc parsePragma(p: var Parser): PNode = #| pragma = '{.' optInd (exprColonEqExpr comma?)* optPar ('.}' | '}') result = newNodeP(nkPragma, p) inc p.inPragma when defined(nimpretty): inc p.em.doIndentMore inc p.em.keepIndents getTok(p) optInd(p, result) while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}: p.hasProgress = false var a = exprColonEqExpr(p) if not p.hasProgress: break result.add(a) if p.tok.tokType == tkComma: getTok(p) skipComment(p, a) optPar(p) if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: when defined(nimpretty): if p.tok.tokType == tkCurlyRi: curlyRiWasPragma(p.em) getTok(p) else: parMessage(p, "expected '.}'") dec p.inPragma when defined(nimpretty): dec p.em.doIndentMore dec p.em.keepIndents setEndInfo() proc identVis(p: var Parser; allowDot=false): PNode = #| identVis = symbol OPR? # postfix position #| identVisDot = symbol '.' optInd symbolOrKeyword OPR? var a = parseSymbol(p) if p.tok.tokType == tkOpr: when defined(nimpretty): starWasExportMarker(p.em) result = newNodeP(nkPostfix, p) result.add(newIdentNodeP(p.tok.ident, p)) result.add(a) getTok(p) elif p.tok.tokType == tkDot and allowDot: result = dotExpr(p, a) else: result = a proc identWithPragma(p: var Parser; allowDot=false): PNode = #| identWithPragma = identVis pragma? #| identWithPragmaDot = identVisDot pragma? var a = identVis(p, allowDot) if p.tok.tokType == tkCurlyDotLe: result = newNodeP(nkPragmaExpr, p) result.add(a) result.add(parsePragma(p)) else: result = a type DeclaredIdentFlag = enum withPragma, # identifier may have pragma withBothOptional # both ':' and '=' parts are optional withDot # allow 'var ident.ident = value' DeclaredIdentFlags = set[DeclaredIdentFlag] proc parseIdentColonEquals(p: var Parser, flags: DeclaredIdentFlags): PNode = #| declColonEquals = identWithPragma (comma identWithPragma)* comma? #| (':' optInd typeDescExpr)? ('=' optInd expr)? #| identColonEquals = IDENT (comma IDENT)* comma? #| (':' optInd typeDescExpr)? ('=' optInd expr)?) var a: PNode result = newNodeP(nkIdentDefs, p) # progress guaranteed while true: case p.tok.tokType of tkSymbol, tkAccent: if withPragma in flags: a = identWithPragma(p, allowDot=withDot in flags) else: a = parseSymbol(p) if a.kind == nkEmpty: return else: break result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) if p.tok.tokType == tkColon: getTok(p) optInd(p, result) result.add(parseTypeDesc(p, fullExpr = true)) else: result.add(newNodeP(nkEmpty, p)) if p.tok.tokType != tkEquals and withBothOptional notin flags: parMessage(p, "':' or '=' expected, but got '$1'", p.tok) if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) result.add(parseExpr(p)) else: result.add(newNodeP(nkEmpty, p)) setEndInfo() proc parseTuple(p: var Parser, indentAllowed = false): PNode = #| tupleTypeBracket = '[' optInd (identColonEquals (comma/semicolon)?)* optPar ']' #| tupleType = 'tuple' tupleTypeBracket #| tupleDecl = 'tuple' (tupleTypeBracket / #| COMMENT? (IND{>} identColonEquals (IND{=} identColonEquals)*)?) result = newNodeP(nkTupleTy, p) getTok(p) if p.tok.tokType == tkBracketLe: getTok(p) optInd(p, result) # progress guaranteed while p.tok.tokType in {tkSymbol, tkAccent}: var a = parseIdentColonEquals(p, {}) result.add(a) if p.tok.tokType notin {tkComma, tkSemiColon}: break when defined(nimpretty): commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) eat(p, tkBracketRi) elif indentAllowed: skipComment(p, result) if realInd(p): withInd(p): rawSkipComment(p, result) # progress guaranteed while true: case p.tok.tokType of tkSymbol, tkAccent: var a = parseIdentColonEquals(p, {}) if p.tok.indent < 0 or p.tok.indent >= p.currInd: rawSkipComment(p, a) result.add(a) of tkEof: break else: parMessage(p, errIdentifierExpected, p.tok) break if not sameInd(p): break elif p.tok.tokType == tkParLe: parMessage(p, errGenerated, "the syntax for tuple types is 'tuple[...]', not 'tuple(...)'") else: result = newNodeP(nkTupleClassTy, p) setEndInfo() proc parseParamList(p: var Parser, retColon = true): PNode = #| paramList = '(' declColonEquals ^* (comma/semicolon) ')' #| paramListArrow = paramList? ('->' optInd typeDesc)? #| paramListColon = paramList? (':' optInd typeDesc)? var a: PNode result = newNodeP(nkFormalParams, p) result.add(p.emptyNode) # return type when defined(nimpretty): inc p.em.doIndentMore inc p.em.keepIndents let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0 if hasParLe: getTok(p) optInd(p, result) # progress guaranteed while true: case p.tok.tokType of tkSymbol, tkAccent: a = parseIdentColonEquals(p, {withBothOptional, withPragma}) of tkParRi: break of tkVar: parMessage(p, errGenerated, "the syntax is 'parameter: var T', not 'var parameter: T'") break else: parMessage(p, "expected closing ')'") break result.add(a) if p.tok.tokType notin {tkComma, tkSemiColon}: break when defined(nimpretty): commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) eat(p, tkParRi) let hasRet = if retColon: p.tok.tokType == tkColon else: p.tok.tokType == tkOpr and p.tok.ident.s == "->" if hasRet and p.tok.indent < 0: getTok(p) optInd(p, result) result.replaceFirstSon parseTypeDesc(p) elif not retColon and not hasParLe: # Mark as "not there" in order to mark for deprecation in the semantic pass: result = p.emptyNode when defined(nimpretty): dec p.em.doIndentMore dec p.em.keepIndents setEndInfo() proc optPragmas(p: var Parser): PNode = if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)): result = parsePragma(p) else: result = p.emptyNode proc parseDoBlock(p: var Parser; info: TLineInfo): PNode = #| doBlock = 'do' paramListArrow pragma? colcom stmt result = nil var params = parseParamList(p, retColon=false) let pragmas = optPragmas(p) colcom(p, result) result = parseStmt(p) if params.kind != nkEmpty or pragmas.kind != nkEmpty: if params.kind == nkEmpty: params = newNodeP(nkFormalParams, p) params.add(p.emptyNode) # return type result = newProcNode(nkDo, info, body = result, params = params, name = p.emptyNode, pattern = p.emptyNode, genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode) setEndInfo() proc parseProcExpr(p: var Parser; isExpr: bool; kind: TNodeKind): PNode = #| routineExpr = ('proc' | 'func' | 'iterator') paramListColon pragma? ('=' COMMENT? stmt)? #| routineType = ('proc' | 'iterator') paramListColon pragma? # either a proc type or a anonymous proc let info = parLineInfo(p) let hasSignature = p.tok.tokType in {tkParLe, tkColon} and p.tok.indent < 0 let params = parseParamList(p) let pragmas = optPragmas(p) if p.tok.tokType == tkEquals and isExpr: getTok(p) result = newProcNode(kind, info, body = p.emptyNode, params = params, name = p.emptyNode, pattern = p.emptyNode, genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode) skipComment(p, result) result.replaceSon bodyPos, parseStmt(p) else: result = newNode(if kind == nkIteratorDef: nkIteratorTy else: nkProcTy, info) if hasSignature or pragmas.kind != nkEmpty: if hasSignature: result.add(params) else: # pragmas but no param list, implies typeclass with pragmas result.add(p.emptyNode) if kind == nkFuncDef: parMessage(p, "func keyword is not allowed in type descriptions, use proc with {.noSideEffect.} pragma instead") result.add(pragmas) setEndInfo() proc isExprStart(p: Parser): bool = case p.tok.tokType of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor, tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCustomLit, tkVar, tkRef, tkPtr, tkEnum, tkTuple, tkObject, tkWhen, tkCase, tkOut, tkTry, tkBlock: result = true else: result = false proc parseSymbolList(p: var Parser, result: PNode) = # progress guaranteed while true: var s = parseSymbol(p, smAllowNil) if s.kind == nkEmpty: break result.add(s) if p.tok.tokType != tkComma: break getTok(p) optInd(p, s) setEndInfo() proc parseTypeDescKAux(p: var Parser, kind: TNodeKind, mode: PrimaryMode): PNode = result = newNodeP(kind, p) getTok(p) if p.tok.indent != -1 and p.tok.indent <= p.currInd: return optInd(p, result) let isTypedef = mode == pmTypeDef and p.tok.tokType in {tkObject, tkTuple} if not isOperator(p.tok) and isExprStart(p): if isTypedef: result.add(parseTypeDefValue(p)) else: result.add(primary(p, mode)) if kind == nkDistinctTy and p.tok.tokType == tkSymbol: # XXX document this feature! var nodeKind: TNodeKind if p.tok.ident.s == "with": nodeKind = nkWith elif p.tok.ident.s == "without": nodeKind = nkWithout else: return result getTok(p) let list = newNodeP(nodeKind, p) result.add list parseSymbolList(p, list) if mode == pmTypeDef and not isTypedef: result = parseOperators(p, result, -1, mode) setEndInfo() proc parseVarTuple(p: var Parser): PNode proc parseFor(p: var Parser): PNode = #| forStmt = 'for' ((varTuple / identWithPragma) ^+ comma) 'in' expr colcom stmt #| forExpr = forStmt getTokNoInd(p) result = newNodeP(nkForStmt, p) if p.tok.tokType == tkParLe: result.add(parseVarTuple(p)) else: var a = identWithPragma(p) result.add(a) while p.tok.tokType == tkComma: getTok(p) optInd(p, a) if p.tok.tokType == tkParLe: result.add(parseVarTuple(p)) break a = identWithPragma(p) result.add(a) eat(p, tkIn) result.add(parseExpr(p)) colcom(p, result) result.add(parseStmt(p)) setEndInfo() template nimprettyDontTouch(body) = when defined(nimpretty): inc p.em.keepIndents body when defined(nimpretty): dec p.em.keepIndents proc parseExpr(p: var Parser): PNode = #| expr = (blockExpr #| | ifExpr #| | whenExpr #| | caseStmt #| | forExpr #| | tryExpr) #| / simpleExpr case p.tok.tokType of tkBlock: nimprettyDontTouch: result = parseBlock(p) of tkIf: nimprettyDontTouch: result = parseIfOrWhenExpr(p, nkIfExpr) of tkFor: nimprettyDontTouch: result = parseFor(p) of tkWhen: nimprettyDontTouch: result = parseIfOrWhenExpr(p, nkWhenStmt) of tkCase: # Currently we think nimpretty is good enough with case expressions, # so it is allowed to touch them: #nimprettyDontTouch: result = parseCase(p) of tkTry: nimprettyDontTouch: result = parseTry(p, isExpr=true) else: result = simpleExpr(p) setEndInfo() proc parseEnum(p: var Parser): PNode proc parseObject(p: var Parser): PNode proc parseTypeClass(p: var Parser): PNode proc primary(p: var Parser, mode: PrimaryMode): PNode = #| simplePrimary = SIGILLIKEOP? identOrLiteral primarySuffix* #| commandStart = &('`'|IDENT|literal|'cast'|'addr'|'type'|'var'|'out'| #| 'static'|'enum'|'tuple'|'object'|'proc') #| primary = simplePrimary (commandStart expr (doBlock extraPostExprBlock*)?)? #| / operatorB primary #| / routineExpr #| / rawTypeDesc #| / prefixOperator primary # XXX strong spaces need to be reflected in commandStart # command part is handled in the primarySuffix proc # prefix operators: if isOperator(p.tok): # Note 'sigil like' operators are currently not reflected in the grammar # and should be removed for Nim 2.0, I don't think anybody uses them. let isSigil = isSigilLike(p.tok) result = newNodeP(nkPrefix, p) var a = newIdentNodeP(p.tok.ident, p) result.add(a) getTok(p) optInd(p, a) const identOrLiteralKinds = tkBuiltInMagics + {tkSymbol, tkAccent, tkNil, tkIntLit..tkCustomLit, tkCast, tkOut, tkParLe, tkBracketLe, tkCurlyLe} if isSigil and p.tok.tokType in identOrLiteralKinds: let baseInd = p.lex.currLineIndent result.add(identOrLiteral(p, mode)) result = primarySuffix(p, result, baseInd, mode) else: result.add(primary(p, pmNormal)) return case p.tok.tokType of tkProc: getTok(p) result = parseProcExpr(p, mode != pmTypeDesc, nkLambda) of tkFunc: getTok(p) result = parseProcExpr(p, mode != pmTypeDesc, nkFuncDef) of tkIterator: getTok(p) result = parseProcExpr(p, mode != pmTypeDesc, nkIteratorDef) of tkBind: # legacy syntax, no-op in current nim result = newNodeP(nkBind, p) getTok(p) optInd(p, result) result.add(primary(p, pmNormal)) of tkTuple, tkEnum, tkObject, tkConcept, tkVar, tkOut, tkRef, tkPtr, tkDistinct: result = parseTypeDesc(p) else: let baseInd = p.lex.currLineIndent result = identOrLiteral(p, mode) result = primarySuffix(p, result, baseInd, mode) proc binaryNot(p: var Parser; a: PNode): PNode = if p.tok.tokType == tkNot and p.tok.indent < 0: let notOpr = newIdentNodeP(p.tok.ident, p) getTok(p) optInd(p, notOpr) let b = primary(p, pmTypeDesc) result = newNodeP(nkInfix, p) result.add notOpr result.add a result.add b else: result = a proc parseTypeDesc(p: var Parser, fullExpr = false): PNode = #| rawTypeDesc = (tupleType | routineType | 'enum' | 'object' | #| ('var' | 'out' | 'ref' | 'ptr' | 'distinct') typeDesc?) #| ('not' primary)? #| typeDescExpr = (routineType / simpleExpr) ('not' primary)? #| typeDesc = rawTypeDesc / typeDescExpr newlineWasSplitting(p) if fullExpr: result = simpleExpr(p, pmTypeDesc) else: case p.tok.tokType of tkTuple: result = parseTuple(p, false) of tkProc: getTok(p) result = parseProcExpr(p, false, nkLambda) of tkIterator: getTok(p) result = parseProcExpr(p, false, nkIteratorDef) of tkEnum: result = newNodeP(nkEnumTy, p) getTok(p) of tkObject: result = newNodeP(nkObjectTy, p) getTok(p) of tkConcept: result = p.emptyNode parMessage(p, "the 'concept' keyword is only valid in 'type' sections") of tkVar: result = parseTypeDescKAux(p, nkVarTy, pmTypeDesc) of tkOut: result = parseTypeDescKAux(p, nkOutTy, pmTypeDesc) of tkRef: result = parseTypeDescKAux(p, nkRefTy, pmTypeDesc) of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, pmTypeDesc) of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, pmTypeDesc) else: result = simpleExpr(p, pmTypeDesc) result = binaryNot(p, result) setEndInfo() proc parseTypeDefValue(p: var Parser): PNode = #| typeDefValue = ((tupleDecl | enumDecl | objectDecl | conceptDecl | #| ('ref' | 'ptr' | 'distinct') (tupleDecl | objectDecl)) #| / (simpleExpr (exprEqExpr ^+ comma postExprBlocks?)?)) #| ('not' primary)? case p.tok.tokType of tkTuple: result = parseTuple(p, true) of tkRef: result = parseTypeDescKAux(p, nkRefTy, pmTypeDef) of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, pmTypeDef) of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, pmTypeDef) of tkEnum: prettySection: result = parseEnum(p) of tkObject: prettySection: result = parseObject(p) of tkConcept: result = parseTypeClass(p) else: result = simpleExpr(p, pmTypeDef) if p.tok.tokType != tkNot: if result.kind == nkCommand: var isFirstParam = false while p.tok.tokType == tkComma: getTok(p) optInd(p, result) result.add(commandParam(p, isFirstParam, pmTypeDef)) result = postExprBlocks(p, result) result = binaryNot(p, result) setEndInfo() proc makeCall(n: PNode): PNode = ## Creates a call if the given node isn't already a call. if n.kind in nkCallKinds: result = n else: result = newNode(nkCall, n.info) result.add n proc postExprBlocks(p: var Parser, x: PNode): PNode = #| extraPostExprBlock = ( IND{=} doBlock #| | IND{=} 'of' exprList ':' stmt #| | IND{=} 'elif' expr ':' stmt #| | IND{=} 'except' optionalExprList ':' stmt #| | IND{=} 'finally' ':' stmt #| | IND{=} 'else' ':' stmt ) #| postExprBlocks = (doBlock / ':' (extraPostExprBlock / stmt)) extraPostExprBlock* result = x if p.tok.indent >= 0: return var openingParams = p.emptyNode openingPragmas = p.emptyNode if p.tok.tokType == tkDo: getTok(p) openingParams = parseParamList(p, retColon=false) openingPragmas = optPragmas(p) if p.tok.tokType == tkColon: result = makeCall(result) getTok(p) skipComment(p, result) if not (p.tok.tokType in {tkOf, tkElif, tkElse, tkExcept, tkFinally} and sameInd(p)): var stmtList = newNodeP(nkStmtList, p) stmtList.add parseStmt(p) # to keep backwards compatibility (see tests/vm/tstringnil) if stmtList.firstSon.kind == nkStmtList: stmtList = stmtList.firstSon setNodeFlag stmtList, nfBlockArg if openingParams.kind != nkEmpty or openingPragmas.kind != nkEmpty: if openingParams.kind == nkEmpty: openingParams = newNodeP(nkFormalParams, p) openingParams.add(p.emptyNode) # return type result.add newProcNode(nkDo, stmtList.info, body = stmtList, params = openingParams, name = p.emptyNode, pattern = p.emptyNode, genericParams = p.emptyNode, pragmas = openingPragmas, exceptions = p.emptyNode) else: result.add stmtList while sameInd(p): var nextBlock: PNode let nextToken = p.tok.tokType if nextToken == tkDo: let info = parLineInfo(p) getTok(p) nextBlock = parseDoBlock(p, info) else: case nextToken of tkOf: nextBlock = newNodeP(nkOfBranch, p) exprList(p, tkColon, nextBlock) of tkElif: nextBlock = newNodeP(nkElifBranch, p) getTok(p) optInd(p, nextBlock) nextBlock.add parseExpr(p) of tkExcept: nextBlock = newNodeP(nkExceptBranch, p) optionalExprList(p, tkColon, nextBlock) of tkFinally: nextBlock = newNodeP(nkFinally, p) getTok(p) of tkElse: nextBlock = newNodeP(nkElse, p) getTok(p) else: break eat(p, tkColon) nextBlock.add parseStmt(p) setNodeFlag nextBlock, nfBlockArg result.add nextBlock if nextBlock.kind in {nkElse, nkFinally}: break else: if openingParams.kind != nkEmpty: parMessage(p, "expected ':'") proc parseExprStmt(p: var Parser): PNode = #| exprStmt = simpleExpr postExprBlocks? #| / simplePrimary (exprEqExpr ^+ comma) postExprBlocks? #| / simpleExpr '=' optInd (expr postExprBlocks?) var a = simpleExpr(p, pmTrySimple) if p.tok.tokType == tkEquals: result = newNodeP(nkAsgn, p) getTok(p) optInd(p, result) var b = parseExpr(p) b = postExprBlocks(p, b) result.add(a) result.add(b) else: var isFirstParam = false # if an expression is starting here, a simplePrimary was parsed and # this is the start of a command if p.tok.indent < 0 and isExprStart(p): result = newTree(nkCommand, a.info, a) let baseIndent = p.currInd while true: result.add(commandParam(p, isFirstParam, pmNormal)) if p.tok.tokType != tkComma or (p.tok.indent >= 0 and p.tok.indent < baseIndent): break getTok(p) optInd(p, result) else: result = a result = postExprBlocks(p, result) setEndInfo() proc parseModuleName(p: var Parser, kind: TNodeKind): PNode = result = parseExpr(p) when false: # parseExpr already handles 'as' syntax ... if p.tok.tokType == tkAs and kind == nkImportStmt: let a = result result = newNodeP(nkImportAs, p) getTok(p) result.add(a) result.add(parseExpr(p)) setEndInfo() proc parseImport(p: var Parser, kind: TNodeKind): PNode = #| importStmt = 'import' optInd expr #| ((comma expr)* #| / 'except' optInd (expr ^+ comma)) #| exportStmt = 'export' optInd expr #| ((comma expr)* #| / 'except' optInd (expr ^+ comma)) result = newNodeP(kind, p) getTok(p) # skip `import` or `export` optInd(p, result) var a = parseModuleName(p, kind) result.add(a) if p.tok.tokType in {tkComma, tkExcept}: if p.tok.tokType == tkExcept: result.transitionSonsKind(succ(kind)) getTok(p) optInd(p, result) while true: # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}: p.hasProgress = false a = parseModuleName(p, kind) if a.kind == nkEmpty or not p.hasProgress: break result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) #expectNl(p) setEndInfo() proc parseIncludeStmt(p: var Parser): PNode = #| includeStmt = 'include' optInd expr ^+ comma result = newNodeP(nkIncludeStmt, p) getTok(p) # skip `import` or `include` optInd(p, result) while true: # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}: p.hasProgress = false var a = parseExpr(p) if a.kind == nkEmpty or not p.hasProgress: break result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) #expectNl(p) setEndInfo() proc parseFromStmt(p: var Parser): PNode = #| fromStmt = 'from' expr 'import' optInd expr (comma expr)* result = newNodeP(nkFromStmt, p) getTok(p) # skip `from` optInd(p, result) var a = parseModuleName(p, nkImportStmt) result.add(a) #optInd(p, a); eat(p, tkImport) optInd(p, result) while true: # p.tok.tokType notin {tkEof, tkSad, tkDed}: p.hasProgress = false a = parseExpr(p) if a.kind == nkEmpty or not p.hasProgress: break result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) #expectNl(p) setEndInfo() proc parseReturnOrRaise(p: var Parser, kind: TNodeKind): PNode = #| returnStmt = 'return' optInd expr? #| raiseStmt = 'raise' optInd expr? #| yieldStmt = 'yield' optInd expr? #| discardStmt = 'discard' optInd expr? #| breakStmt = 'break' optInd expr? #| continueStmt = 'continue' optInd expr? result = newNodeP(kind, p) getTok(p) if p.tok.tokType == tkComment: skipComment(p, result) result.add(p.emptyNode) elif p.tok.indent >= 0 and p.tok.indent <= p.currInd or not isExprStart(p): # NL terminates: result.add(p.emptyNode) # nimpretty here! else: var e = parseExpr(p) e = postExprBlocks(p, e) result.add(e) setEndInfo() proc parseIfOrWhen(p: var Parser, kind: TNodeKind): PNode = #| condStmt = expr colcom stmt COMMENT? #| (IND{=} 'elif' expr colcom stmt)* #| (IND{=} 'else' colcom stmt)? #| ifStmt = 'if' condStmt #| whenStmt = 'when' condStmt result = newNodeP(kind, p) while true: getTok(p) # skip `if`, `when`, `elif` var branch = newNodeP(nkElifBranch, p) optInd(p, branch) branch.add(parseExpr(p)) colcom(p, branch) branch.add(parseStmt(p)) skipComment(p, branch) result.add(branch) if p.tok.tokType != tkElif or not sameOrNoInd(p): break if p.tok.tokType == tkElse and sameOrNoInd(p): var branch = newNodeP(nkElse, p) eat(p, tkElse) colcom(p, branch) branch.add(parseStmt(p)) result.add(branch) setEndInfo() proc parseIfOrWhenExpr(p: var Parser, kind: TNodeKind): PNode = #| condExpr = expr colcom stmt optInd #| ('elif' expr colcom stmt optInd)* #| 'else' colcom stmt #| ifExpr = 'if' condExpr #| whenExpr = 'when' condExpr result = newNodeP(kind, p) while true: getTok(p) # skip `if`, `when`, `elif` var branch = newNodeP(nkElifExpr, p) optInd(p, branch) branch.add(parseExpr(p)) colcom(p, branch) branch.add(parseStmt(p)) skipComment(p, branch) result.add(branch) if p.tok.tokType != tkElif: break if p.tok.tokType == tkElse: var branch = newNodeP(nkElseExpr, p) eat(p, tkElse) colcom(p, branch) branch.add(parseStmt(p)) result.add(branch) setEndInfo() proc parseWhile(p: var Parser): PNode = #| whileStmt = 'while' expr colcom stmt result = newNodeP(nkWhileStmt, p) getTok(p) optInd(p, result) result.add(parseExpr(p)) colcom(p, result) result.add(parseStmt(p)) setEndInfo() proc parseCase(p: var Parser): PNode = #| ofBranch = 'of' exprList colcom stmt #| ofBranches = ofBranch (IND{=} ofBranch)* #| (IND{=} 'elif' expr colcom stmt)* #| (IND{=} 'else' colcom stmt)? #| caseStmt = 'case' expr ':'? COMMENT? #| (IND{>} ofBranches DED #| | IND{=} ofBranches) var b: PNode inElif = false wasIndented = false result = newNodeP(nkCaseStmt, p) getTok(p) result.add(parseExpr(p)) if p.tok.tokType == tkColon: getTok(p) skipComment(p, result) let oldInd = p.currInd if realInd(p): p.currInd = p.tok.indent wasIndented = true while sameInd(p): case p.tok.tokType of tkOf: if inElif: break b = newNodeP(nkOfBranch, p) exprList(p, tkColon, b) of tkElif: inElif = true b = newNodeP(nkElifBranch, p) getTok(p) optInd(p, b) b.add(parseExpr(p)) of tkElse: b = newNodeP(nkElse, p) getTok(p) else: break colcom(p, b) b.add(parseStmt(p)) result.add(b) if b.kind == nkElse: break if wasIndented: p.currInd = oldInd setEndInfo() proc parseTry(p: var Parser; isExpr: bool): PNode = #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally') #| (IND{=}? 'except' optionalExprList colcom stmt)* #| (IND{=}? 'finally' colcom stmt)? #| tryExpr = 'try' colcom stmt &(optInd 'except'|'finally') #| (optInd 'except' optionalExprList colcom stmt)* #| (optInd 'finally' colcom stmt)? result = newNodeP(nkTryStmt, p) let parentIndent = p.currInd # isExpr getTok(p) colcom(p, result) result.add(parseStmt(p)) var b: PNode = nil while sameOrNoInd(p) or (isExpr and parentIndent <= p.tok.indent): case p.tok.tokType of tkExcept: b = newNodeP(nkExceptBranch, p) optionalExprList(p, tkColon, b) of tkFinally: b = newNodeP(nkFinally, p) getTok(p) else: break colcom(p, b) b.add(parseStmt(p)) result.add(b) if b == nil: parMessage(p, "expected 'except'") setEndInfo() proc parseExceptBlock(p: var Parser, kind: TNodeKind): PNode = result = newNodeP(kind, p) getTok(p) colcom(p, result) result.add(parseStmt(p)) setEndInfo() proc parseBlock(p: var Parser): PNode = #| blockStmt = 'block' symbol? colcom stmt #| blockExpr = 'block' symbol? colcom stmt result = newNodeP(nkBlockStmt, p) getTokNoInd(p) if p.tok.tokType == tkColon: result.add(p.emptyNode) else: result.add(parseSymbol(p)) colcom(p, result) result.add(parseStmt(p)) setEndInfo() proc parseStaticOrDefer(p: var Parser; k: TNodeKind): PNode = #| staticStmt = 'static' colcom stmt #| deferStmt = 'defer' colcom stmt result = newNodeP(k, p) getTok(p) colcom(p, result) result.add(parseStmt(p)) setEndInfo() proc parseAsm(p: var Parser): PNode = #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLESTR_LIT) result = newNodeP(nkAsmStmt, p) getTokNoInd(p) if p.tok.tokType == tkCurlyDotLe: result.add(parsePragma(p)) else: result.add(p.emptyNode) case p.tok.tokType of tkStrLit: result.add(newStrNodeP(nkStrLit, p.tok.literal, p)) of tkRStrLit: result.add(newStrNodeP(nkRStrLit, p.tok.literal, p)) of tkTripleStrLit: result.add(newStrNodeP(nkTripleStrLit, p.tok.literal, p)) else: parMessage(p, "the 'asm' statement takes a string literal") result.add(p.emptyNode) return getTok(p) setEndInfo() proc parseGenericParam(p: var Parser): PNode = #| genericParam = symbol (comma symbol)* (colon expr)? ('=' optInd expr)? var a: PNode result = newNodeP(nkIdentDefs, p) # progress guaranteed while true: case p.tok.tokType of tkIn, tkOut: let x = p.lex.cache.getIdent(if p.tok.tokType == tkIn: "in" else: "out") a = newNodeP(nkPrefix, p) a.add newIdentNodeP(x, p) getTok(p) expectIdent(p) a.add(parseSymbol(p)) of tkSymbol, tkAccent: a = parseSymbol(p) if a.kind == nkEmpty: return else: break result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) if p.tok.tokType == tkColon: getTok(p) optInd(p, result) result.add(parseExpr(p)) else: result.add(p.emptyNode) if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) result.add(parseExpr(p)) else: result.add(p.emptyNode) setEndInfo() proc parseGenericParamList(p: var Parser): PNode = #| genericParamList = '[' optInd #| genericParam ^* (comma/semicolon) optPar ']' result = newNodeP(nkGenericParams, p) getTok(p) optInd(p, result) # progress guaranteed while p.tok.tokType in {tkSymbol, tkAccent, tkIn, tkOut}: var a = parseGenericParam(p) result.add(a) if p.tok.tokType notin {tkComma, tkSemiColon}: break when defined(nimpretty): commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) eat(p, tkBracketRi) setEndInfo() proc parsePattern(p: var Parser): PNode = #| pattern = '{' stmt '}' eat(p, tkCurlyLe) result = parseStmt(p) eat(p, tkCurlyRi) setEndInfo() proc parseRoutine(p: var Parser, kind: TNodeKind): PNode = #| indAndComment = (IND{>} COMMENT)? | COMMENT? #| routine = optInd identVis pattern? genericParamList? #| paramListColon pragma? ('=' COMMENT? stmt)? indAndComment result = newNodeP(kind, p) getTok(p) optInd(p, result) if kind in {nkProcDef, nkLambda, nkIteratorDef, nkFuncDef} and p.tok.tokType notin {tkSymbol, tokKeywordLow..tokKeywordHigh, tkAccent}: # no name; lambda or proc type # in every context that we can parse a routine, we can also parse these result = parseProcExpr(p, true, if kind == nkProcDef: nkLambda else: kind) return result.add(identVis(p)) if p.tok.tokType == tkCurlyLe and p.validInd: result.add(p.parsePattern) else: result.add(p.emptyNode) if p.tok.tokType == tkBracketLe and p.validInd: result.add(p.parseGenericParamList) else: result.add(p.emptyNode) result.add(p.parseParamList) if p.tok.tokType == tkCurlyDotLe and p.validInd: result.add(p.parsePragma) else: result.add(p.emptyNode) # empty exception tracking: result.add(p.emptyNode) let maybeMissEquals = p.tok.tokType != tkEquals if (not maybeMissEquals) and p.validInd: getTok(p) skipComment(p, result) result.add(parseStmt(p)) else: result.add(p.emptyNode) indAndComment(p, result, maybeMissEquals) let body = result.lastSon if body.kind == nkStmtList and body.hasSon and body.firstSon.comment.len > 0 and body.firstSon.kind != nkCommentStmt: if result.comment.len == 0: # proc fn*(a: int): int = a ## foo # => moves comment `foo` to `fn` result.comment = body.firstSon.comment body.firstSon.comment = "" #else: # assert false, p.lex.config$body.info # avoids hard to track bugs, fail early. # Yeah, that worked so well. There IS a bug in this logic, now what? setEndInfo() proc newCommentStmt(p: var Parser): PNode = #| commentStmt = COMMENT result = newNodeP(nkCommentStmt, p) result.comment = p.tok.literal getTok(p) proc parseSection(p: var Parser, kind: TNodeKind, defparser: proc (p: var Parser): PNode {.nimcall.}): PNode = #| section(RULE) = COMMENT? RULE / (IND{>} (RULE / COMMENT)^+IND{=} DED) result = newNodeP(kind, p) if kind != nkTypeSection: getTok(p) skipComment(p, result) if realInd(p): withInd(p): skipComment(p, result) # progress guaranteed while sameInd(p): case p.tok.tokType of tkSymbol, tkAccent, tkParLe: var a = defparser(p) skipComment(p, a) result.add(a) of tkComment: var a = newCommentStmt(p) result.add(a) else: parMessage(p, errIdentifierExpected, p.tok) break if not result.hasSon: parMessage(p, errIdentifierExpected, p.tok) elif p.tok.tokType in {tkSymbol, tkAccent, tkParLe} and p.tok.indent < 0: # tkParLe is allowed for ``var (x, y) = ...`` tuple parsing result.add(defparser(p)) else: parMessage(p, errIdentifierExpected, p.tok) setEndInfo() proc parseEnum(p: var Parser): PNode = #| enumDecl = 'enum' optInd (symbol pragma? optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) getTok(p) result.add(p.emptyNode) optInd(p, result) flexComment(p, result) # progress guaranteed while true: var a = parseSymbol(p) if a.kind == nkEmpty: return var symPragma = a var pragma: PNode if (p.tok.indent < 0 or p.tok.indent >= p.currInd) and p.tok.tokType == tkCurlyDotLe: pragma = optPragmas(p) symPragma = newNodeP(nkPragmaExpr, p) symPragma.add(a) symPragma.add(pragma) # nimpretty support here if p.tok.indent >= 0 and p.tok.indent <= p.currInd: result.add(symPragma) break if p.tok.tokType == tkEquals and p.tok.indent < 0: getTok(p) optInd(p, symPragma) var b = symPragma symPragma = newNodeP(nkEnumFieldDef, p) symPragma.add(b) symPragma.add(parseExpr(p)) if p.tok.indent < 0 or p.tok.indent >= p.currInd: rawSkipComment(p, symPragma) if p.tok.tokType == tkComma and p.tok.indent < 0: getTok(p) rawSkipComment(p, symPragma) else: if p.tok.indent < 0 or p.tok.indent >= p.currInd: rawSkipComment(p, symPragma) result.add(symPragma) if p.tok.indent >= 0 and p.tok.indent <= p.currInd or p.tok.tokType == tkEof: break if not result.has2Sons: parMessage(p, errIdentifierExpected, p.tok) setEndInfo() proc parseObjectPart(p: var Parser): PNode proc parseObjectWhen(p: var Parser): PNode = #| objectWhen = 'when' expr colcom objectPart COMMENT? #| ('elif' expr colcom objectPart COMMENT?)* #| ('else' colcom objectPart COMMENT?)? result = newNodeP(nkRecWhen, p) # progress guaranteed while sameInd(p): getTok(p) # skip `when`, `elif` var branch = newNodeP(nkElifBranch, p) optInd(p, branch) branch.add(parseExpr(p)) colcom(p, branch) branch.add(parseObjectPart(p)) flexComment(p, branch) result.add(branch) if p.tok.tokType != tkElif: break if p.tok.tokType == tkElse and sameInd(p): var branch = newNodeP(nkElse, p) eat(p, tkElse) colcom(p, branch) branch.add(parseObjectPart(p)) flexComment(p, branch) result.add(branch) setEndInfo() proc parseObjectCase(p: var Parser): PNode = #| objectBranch = 'of' exprList colcom objectPart #| objectBranches = objectBranch (IND{=} objectBranch)* #| (IND{=} 'elif' expr colcom objectPart)* #| (IND{=} 'else' colcom objectPart)? #| objectCase = 'case' declColonEquals ':'? COMMENT? #| (IND{>} objectBranches DED #| | IND{=} objectBranches) result = newNodeP(nkRecCase, p) getTokNoInd(p) var a = parseIdentColonEquals(p, {withPragma}) result.add(a) if p.tok.tokType == tkColon: getTok(p) flexComment(p, result) var wasIndented = false let oldInd = p.currInd if realInd(p): p.currInd = p.tok.indent wasIndented = true # progress guaranteed while sameInd(p): var b: PNode case p.tok.tokType of tkOf: b = newNodeP(nkOfBranch, p) exprList(p, tkColon, b) of tkElse: b = newNodeP(nkElse, p) getTok(p) else: break colcom(p, b) var fields = parseObjectPart(p) if fields.kind == nkEmpty: parMessage(p, errIdentifierExpected, p.tok) fields = newNodeP(nkNilLit, p) # don't break further semantic checking b.add(fields) result.add(b) if b.kind == nkElse: break if wasIndented: p.currInd = oldInd setEndInfo() proc parseObjectPart(p: var Parser): PNode = #| objectPart = IND{>} objectPart^+IND{=} DED #| / objectWhen / objectCase / 'nil' / 'discard' / declColonEquals if realInd(p): result = newNodeP(nkRecList, p) withInd(p): rawSkipComment(p, result) while sameInd(p): case p.tok.tokType of tkCase, tkWhen, tkSymbol, tkAccent, tkNil, tkDiscard: result.add(parseObjectPart(p)) else: parMessage(p, errIdentifierExpected, p.tok) break elif sameOrNoInd(p): case p.tok.tokType of tkWhen: result = parseObjectWhen(p) of tkCase: result = parseObjectCase(p) of tkSymbol, tkAccent: result = parseIdentColonEquals(p, {withPragma}) if p.tok.indent < 0 or p.tok.indent >= p.currInd: rawSkipComment(p, result) of tkNil, tkDiscard: result = newNodeP(nkNilLit, p) getTok(p) else: result = p.emptyNode else: result = p.emptyNode setEndInfo() proc parseObject(p: var Parser): PNode = #| objectDecl = 'object' ('of' typeDesc)? COMMENT? objectPart result = newNodeP(nkObjectTy, p) getTok(p) result.add(p.emptyNode) # compatibility with old pragma node if p.tok.tokType == tkOf and p.tok.indent < 0: var a = newNodeP(nkOfInherit, p) getTok(p) a.add(parseTypeDesc(p)) result.add(a) else: result.add(p.emptyNode) if p.tok.tokType == tkComment: skipComment(p, result) # an initial IND{>} HAS to follow: if not realInd(p): result.add(p.emptyNode) else: result.add(parseObjectPart(p)) setEndInfo() proc parseTypeClassParam(p: var Parser): PNode = let modifier = case p.tok.tokType of tkVar: nkVarTy of tkOut: nkOutTy of tkPtr: nkPtrTy of tkRef: nkRefTy of tkStatic: nkStaticTy of tkType: nkTypeOfExpr else: nkEmpty if modifier != nkEmpty: result = newNodeP(modifier, p) getTok(p) result.add(p.parseSymbol) else: result = p.parseSymbol setEndInfo() proc parseTypeClass(p: var Parser): PNode = #| conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol #| conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')? #| &IND{>} stmt result = newNodeP(nkTypeClassTy, p) getTok(p) if p.tok.tokType == tkComment: skipComment(p, result) if p.tok.indent < 0: var args = newNodeP(nkArgList, p) result.add(args) args.add(p.parseTypeClassParam) while p.tok.tokType == tkComma: getTok(p) args.add(p.parseTypeClassParam) else: result.add(p.emptyNode) # see ast.isNewStyleConcept if p.tok.tokType == tkCurlyDotLe and p.validInd: result.add(parsePragma(p)) else: result.add(p.emptyNode) if p.tok.tokType == tkOf and p.tok.indent < 0: var a = newNodeP(nkOfInherit, p) getTok(p) # progress guaranteed while true: a.add(parseTypeDesc(p)) if p.tok.tokType != tkComma: break getTok(p) result.add(a) else: result.add(p.emptyNode) if p.tok.tokType == tkComment: skipComment(p, result) # an initial IND{>} HAS to follow: if not realInd(p): if result.isNewStyleConcept: parMessage(p, "routine expected, but found '$1' (empty new-styled concepts are not allowed)", p.tok) result.add(p.emptyNode) else: result.add(parseStmt(p)) setEndInfo() proc parseTypeDef(p: var Parser): PNode = #| #| typeDef = identVisDot genericParamList? pragma '=' optInd typeDefValue #| indAndComment? result = newNodeP(nkTypeDef, p) var identifier = identVis(p, allowDot=true) var identPragma = identifier var pragma: PNode var genericParam: PNode if p.tok.tokType == tkBracketLe and p.validInd: genericParam = parseGenericParamList(p) else: genericParam = p.emptyNode pragma = optPragmas(p) if pragma.kind != nkEmpty: identPragma = newNodeP(nkPragmaExpr, p) identPragma.add(identifier) identPragma.add(pragma) result.add(identPragma) result.add(genericParam) if p.tok.tokType == tkEquals: result.info = parLineInfo(p) getTok(p) optInd(p, result) result.add(parseTypeDefValue(p)) else: result.add(p.emptyNode) indAndComment(p, result) # special extension! setEndInfo() proc parseVarTuple(p: var Parser): PNode = #| varTupleLhs = '(' optInd (identWithPragma / varTupleLhs) ^+ comma optPar ')' (':' optInd typeDescExpr)? #| varTuple = varTupleLhs '=' optInd expr result = newNodeP(nkVarTuple, p) getTok(p) # skip '(' optInd(p, result) # progress guaranteed while p.tok.tokType in {tkSymbol, tkAccent, tkParLe}: var a: PNode if p.tok.tokType == tkParLe: a = parseVarTuple(p) a.add(p.emptyNode) else: a = identWithPragma(p, allowDot=true) result.add(a) if p.tok.tokType != tkComma: break getTok(p) skipComment(p, a) optPar(p) eat(p, tkParRi) if p.tok.tokType == tkColon: getTok(p) optInd(p, result) result.add(parseTypeDesc(p, fullExpr = true)) else: result.add(p.emptyNode) # no type desc setEndInfo() proc parseVariable(p: var Parser): PNode = #| colonBody = colcom stmt postExprBlocks? #| variable = (varTuple / identColonEquals) colonBody? indAndComment if p.tok.tokType == tkParLe: result = parseVarTuple(p) eat(p, tkEquals) optInd(p, result) result.add(parseExpr(p)) else: result = parseIdentColonEquals(p, {withPragma, withDot}) result.setLastSon postExprBlocks(p, result.lastSon) indAndComment(p, result) setEndInfo() proc parseConstant(p: var Parser): PNode = #| constant = (varTuple / identWithPragma) (colon typeDesc)? '=' optInd expr indAndComment if p.tok.tokType == tkParLe: result = parseVarTuple(p) else: result = newNodeP(nkConstDef, p) result.add(identWithPragma(p)) if p.tok.tokType == tkColon: getTok(p) optInd(p, result) result.add(parseTypeDesc(p)) else: result.add(p.emptyNode) eat(p, tkEquals) optInd(p, result) #add(result, parseStmtListExpr(p)) let a = parseExpr(p) result.add postExprBlocks(p, a) indAndComment(p, result) setEndInfo() proc parseBind(p: var Parser, k: TNodeKind): PNode = #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma #| mixinStmt = 'mixin' optInd qualifiedIdent ^+ comma result = newNodeP(k, p) getTok(p) optInd(p, result) # progress guaranteed while true: var a = qualifiedIdent(p) result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) #expectNl(p) setEndInfo() proc parseStmtPragma(p: var Parser): PNode = #| pragmaStmt = pragma (':' COMMENT? stmt)? result = parsePragma(p) if p.tok.tokType == tkColon and p.tok.indent < 0: let a = result result = newNode(nkPragmaBlock, a.info) getTok(p) skipComment(p, result) result.add a result.add parseStmt(p) setEndInfo() proc simpleStmt(p: var Parser): PNode = #| simpleStmt = ((returnStmt | raiseStmt | yieldStmt | discardStmt | breakStmt #| | continueStmt | pragmaStmt | importStmt | exportStmt | fromStmt #| | includeStmt | commentStmt) / exprStmt) COMMENT? #| case p.tok.tokType of tkReturn: result = parseReturnOrRaise(p, nkReturnStmt) of tkRaise: result = parseReturnOrRaise(p, nkRaiseStmt) of tkYield: result = parseReturnOrRaise(p, nkYieldStmt) of tkDiscard: result = parseReturnOrRaise(p, nkDiscardStmt) of tkBreak: result = parseReturnOrRaise(p, nkBreakStmt) of tkContinue: result = parseReturnOrRaise(p, nkContinueStmt) of tkCurlyDotLe: result = parseStmtPragma(p) of tkImport: result = parseImport(p, nkImportStmt) of tkExport: result = parseImport(p, nkExportStmt) of tkFrom: result = parseFromStmt(p) of tkInclude: result = parseIncludeStmt(p) of tkComment: result = newCommentStmt(p) else: if isExprStart(p): result = parseExprStmt(p) else: result = p.emptyNode if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result) proc complexOrSimpleStmt(p: var Parser): PNode = #| complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt #| | tryStmt | forStmt #| | blockStmt | staticStmt | deferStmt | asmStmt #| | 'proc' routine #| | 'method' routine #| | 'func' routine #| | 'iterator' routine #| | 'macro' routine #| | 'template' routine #| | 'converter' routine #| | 'type' section(typeDef) #| | 'const' section(constant) #| | ('let' | 'var' | 'using') section(variable) #| | bindStmt | mixinStmt) #| / simpleStmt case p.tok.tokType of tkIf: result = parseIfOrWhen(p, nkIfStmt) of tkWhile: result = parseWhile(p) of tkCase: result = parseCase(p) of tkTry: result = parseTry(p, isExpr=false) of tkFinally: result = parseExceptBlock(p, nkFinally) of tkExcept: result = parseExceptBlock(p, nkExceptBranch) of tkFor: result = parseFor(p) of tkBlock: result = parseBlock(p) of tkStatic: result = parseStaticOrDefer(p, nkStaticStmt) of tkDefer: result = parseStaticOrDefer(p, nkDefer) of tkAsm: result = parseAsm(p) of tkProc: result = parseRoutine(p, nkProcDef) of tkFunc: result = parseRoutine(p, nkFuncDef) of tkMethod: result = parseRoutine(p, nkMethodDef) of tkIterator: result = parseRoutine(p, nkIteratorDef) of tkMacro: result = parseRoutine(p, nkMacroDef) of tkTemplate: result = parseRoutine(p, nkTemplateDef) of tkConverter: result = parseRoutine(p, nkConverterDef) of tkType: getTok(p) if p.tok.tokType == tkParLe: getTok(p) result = newNodeP(nkTypeOfExpr, p) result.add(primary(p, pmTypeDesc)) eat(p, tkParRi) result = parseOperators(p, result, -1, pmNormal) else: result = parseSection(p, nkTypeSection, parseTypeDef) of tkConst: prettySection: result = parseSection(p, nkConstSection, parseConstant) of tkLet: prettySection: result = parseSection(p, nkLetSection, parseVariable) of tkVar: prettySection: result = parseSection(p, nkVarSection, parseVariable) of tkWhen: result = parseIfOrWhen(p, nkWhenStmt) of tkBind: result = parseBind(p, nkBindStmt) of tkMixin: result = parseBind(p, nkMixinStmt) of tkUsing: result = parseSection(p, nkUsingStmt, parseVariable) else: result = simpleStmt(p) proc parseStmt(p: var Parser): PNode = #| stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED) #| / simpleStmt ^+ ';' if p.tok.indent > p.currInd: # nimpretty support here result = newNodeP(nkStmtList, p) withInd(p): while true: if p.tok.indent == p.currInd: discard elif p.tok.tokType == tkSemiColon: getTok(p) if p.tok.indent < 0 or p.tok.indent == p.currInd: discard else: break else: if p.tok.indent > p.currInd and p.tok.tokType != tkDot: parMessage(p, errInvalidIndentation) break if p.tok.tokType in {tkCurlyRi, tkParRi, tkCurlyDotRi, tkBracketRi}: # XXX this ensures tnamedparamanonproc still compiles; # deprecate this syntax later break p.hasProgress = false if p.tok.tokType in {tkElse, tkElif}: break # Allow this too, see tests/parser/tifexprs let a = complexOrSimpleStmt(p) if a.kind == nkEmpty and not p.hasProgress: parMessage(p, errExprExpected, p.tok) break else: result.add a if not p.hasProgress and p.tok.tokType == tkEof: break else: # the case statement is only needed for better error messages: case p.tok.tokType of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkFunc, tkIterator, tkMacro, tkType, tkConst, tkWhen, tkVar: parMessage(p, "nestable statement requires indentation") result = p.emptyNode else: if p.inSemiStmtList > 0: result = simpleStmt(p) if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) else: result = newNodeP(nkStmtList, p) while true: if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) p.hasProgress = false let a = simpleStmt(p) let err = not p.hasProgress if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) result.add(a) if p.tok.tokType != tkSemiColon: break getTok(p) if err and p.tok.tokType == tkEof: break setEndInfo() proc checkFirstLineIndentation*(p: var Parser) = if p.tok.indent != 0 and tsLeading in p.tok.spacing: parMessage(p, errInvalidIndentation) proc parseTopLevelStmt*(p: var Parser): PNode = ## Implements an iterator which, when called repeatedly, returns the next ## top-level statement or emptyNode if end of stream. result = p.emptyNode # progress guaranteed while true: # nimpretty support here if p.tok.indent != 0: if p.firstTok and p.tok.indent < 0: discard elif p.tok.tokType != tkSemiColon: # special casing for better error messages: if p.tok.tokType == tkOpr and p.tok.ident.s == "*": parMessage(p, errGenerated, "invalid indentation; an export marker '*' follows the declared identifier") else: parMessage(p, errInvalidIndentation) p.firstTok = false case p.tok.tokType of tkSemiColon: getTok(p) if p.tok.indent <= 0: discard else: parMessage(p, errInvalidIndentation) p.firstTok = true of tkEof: break else: result = complexOrSimpleStmt(p) if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) break setEndInfo() proc parseAll*(p: var Parser): PNode = ## Parses the rest of the input stream held by the parser into a PNode. result = newNodeP(nkStmtList, p) while true: let nextStmt = p.parseTopLevelStmt() if nextStmt.kind == nkEmpty: break result &= nextStmt setEndInfo() proc parseString*(s: string; cache: IdentCache; config: ConfigRef; filename: string = ""; line: int = 0; errorHandler: ErrorHandler = nil): PNode = ## Parses a string into an AST, returning the top node. ## `filename` and `line`, although optional, provide info so that the ## compiler can generate correct error messages referring to the original ## source. var stream = llStreamOpen(s) stream.lineOffset = line var p = Parser() p.lex.errorHandler = errorHandler openParser(p, AbsoluteFile filename, stream, cache, config) result = p.parseAll closeParser(p) setEndInfo()