diff options
Diffstat (limited to 'compiler/parser.nim')
-rw-r--r-- | compiler/parser.nim | 2763 |
1 files changed, 1763 insertions, 1000 deletions
diff --git a/compiler/parser.nim b/compiler/parser.nim index 46294925d..747505097 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1,591 +1,799 @@ # # -# The Nimrod Compiler -# (c) Copyright 2013 Andreas Rumpf +# 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 Nimrod syntax. +# 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. +# from the grammar and how comments belong to the AST. # In fact the grammar is generated from this file: -when isMainModule: - import pegs - var outp = open("compiler/grammar.txt", fmWrite) - for line in lines("compiler/parser.nim"): - if line =~ peg" \s* '#| ' {.*}": - outp.writeln matches[0] - outp.close +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, strutils, ast, astalgo, msgs + 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 - TParser*{.final.} = object # a TParser object represents a module that + Parser* = object # A Parser object represents a file that # is being parsed - currInd: int # current indentation - firstTok: bool - lex*: TLexer # the lexer that is used for parsing - tok*: TToken # the current token - -proc ParseAll*(p: var TParser): PNode -proc openParser*(p: var TParser, filename: string, inputstream: PLLStream) -proc closeParser*(p: var TParser) -proc parseTopLevelStmt*(p: var TParser): PNode - # implements an iterator. Returns the next top-level statement or - # emtyNode if end of stream. - -proc parseString*(s: string, filename: string = "", line: int = 0): PNode - # filename and line could be set optionally, when the string originates - # from a certain source file. This way, the compiler could generate - # correct error messages referring to the original source. - + 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 getPrecedence*(tok: TToken): int -proc isOperator*(tok: TToken): bool -proc getTok*(p: var TParser) -proc parMessage*(p: TParser, msg: TMsgKind, arg: string = "") -proc skipComment*(p: var TParser, node: PNode) -proc newNodeP*(kind: TNodeKind, p: TParser): PNode -proc newIntNodeP*(kind: TNodeKind, intVal: BiggestInt, p: TParser): PNode -proc newFloatNodeP*(kind: TNodeKind, floatVal: BiggestFloat, p: TParser): PNode -proc newStrNodeP*(kind: TNodeKind, strVal: string, p: TParser): PNode -proc newIdentNodeP*(ident: PIdent, p: TParser): PNode -proc expectIdentOrKeyw*(p: TParser) -proc ExpectIdent*(p: TParser) -proc parLineInfo*(p: TParser): TLineInfo -proc Eat*(p: var TParser, TokType: TTokType) -proc skipInd*(p: var TParser) -proc optPar*(p: var TParser) -proc optInd*(p: var TParser, n: PNode) -proc indAndComment*(p: var TParser, n: PNode) -proc setBaseFlags*(n: PNode, base: TNumericalBase) -proc parseSymbol*(p: var TParser): PNode -proc parseTry(p: var TParser): PNode -proc parseCase(p: var TParser): PNode +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 -proc getTok(p: var TParser) = +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) - -proc OpenParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream) = - initToken(p.tok) - OpenLexer(p.lex, fileIdx, inputstream) + 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 OpenParser*(p: var TParser, filename: string, inputStream: PLLStream) = - openParser(p, filename.fileInfoIdx, inputStream) +proc closeParser*(p: var Parser) = + ## Close a parser, freeing up its resources. + closeLexer(p.lex) -proc CloseParser(p: var TParser) = - 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: TParser, msg: TMsgKind, arg: string = "") = - lexMessage(p.lex, msg, 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: TParser, msg: TMsgKind, tok: TToken) = - lexMessage(p.lex, 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: expr, body: stmt) {.immediate.} = +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 rawSkipComment(p: var TParser, node: PNode) = +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: - if node.comment == nil: node.comment = "" - add(node.comment, p.tok.literal) + 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 TParser, node: PNode) = +proc skipComment(p: var Parser, node: PNode) = if p.tok.indent < 0: rawSkipComment(p, node) -proc skipInd(p: var TParser) = +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 TParser) = +proc optPar(p: var Parser) = if p.tok.indent >= 0: if p.tok.indent < p.currInd: parMessage(p, errInvalidIndentation) -proc optInd(p: var TParser, n: PNode) = +proc optInd(p: var Parser, n: PNode) = skipComment(p, n) skipInd(p) -proc getTokNoInd(p: var TParser) = +proc getTokNoInd(p: var Parser) = getTok(p) if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) -proc expectIdentOrKeyw(p: TParser) = +proc expectIdentOrKeyw(p: Parser) = if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType): - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) - -proc ExpectIdent(p: TParser) = + lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok)) + +proc expectIdent(p: Parser) = if p.tok.tokType != tkSymbol: - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) - -proc Eat(p: var TParser, TokType: TTokType) = - if p.tok.TokType == TokType: getTok(p) - else: lexMessage(p.lex, errTokenExpected, TokTypeToStr[tokType]) - -proc parLineInfo(p: TParser): TLineInfo = - result = getLineInfo(p.lex) - -proc indAndComment(p: var TParser, n: PNode) = + 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: TParser): PNode = - result = newNodeI(kind, getLineInfo(p.lex)) -proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: TParser): PNode = - result = newNodeP(kind, p) - result.intVal = intVal +proc newNodeP(kind: TNodeKind, p: Parser): PNode = + result = newNode(kind, parLineInfo(p)) -proc newFloatNodeP(kind: TNodeKind, floatVal: BiggestFloat, - p: TParser): PNode = - result = newNodeP(kind, p) - result.floatVal = floatVal +proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: Parser): PNode = + result = newAtom(kind, intVal, parLineInfo(p)) -proc newStrNodeP(kind: TNodeKind, strVal: string, p: TParser): PNode = - result = newNodeP(kind, p) - result.strVal = strVal - -proc newIdentNodeP(ident: PIdent, p: TParser): PNode = - result = newNodeP(nkIdent, p) - result.ident = ident - -proc parseExpr(p: var TParser): PNode -proc parseStmt(p: var TParser): PNode -proc parseTypeDesc(p: var TParser): PNode -proc parseDoBlocks(p: var TParser, call: PNode) -proc parseParamList(p: var TParser, retColon = true): PNode - -proc relevantOprChar(ident: PIdent): char {.inline.} = - result = ident.s[0] - var L = ident.s.len - if result == '\\' and L > 1: - result = ident.s[1] - -proc IsSigilLike(tok: TToken): bool {.inline.} = - result = tok.tokType == tkOpr and relevantOprChar(tok.ident) == '@' - -proc IsLeftAssociative(tok: TToken): bool {.inline.} = - result = tok.tokType != tkOpr or relevantOprChar(tok.ident) != '^' - -proc getPrecedence(tok: TToken): int = - case tok.tokType - of tkOpr: - let L = tok.ident.s.len - let relevantChar = relevantOprChar(tok.ident) - - template considerAsgn(value: expr) = - result = if tok.ident.s[L-1] == '=': 1 else: value - - case relevantChar - of '$', '^': considerAsgn(10) - of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 - of '+', '-', '|': considerAsgn(8) - of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 - of '.': considerAsgn(6) - of '?': result = 2 - else: considerAsgn(2) - of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotIn, tkIs, tkIsNot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 - of tkAnd: result = 4 - of tkOr, tkXor: result = 3 - else: result = - 10 - -proc isOperator(tok: TToken): bool = - result = getPrecedence(tok) >= 0 - -#| module = stmt ^* (';' / IND{=}) +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' -#| | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'addr' | 'static' | '..' -#| +#| | 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from' +#| | 'div' | 'mod' | 'shl' | 'shr' | 'not' | '..' +#| #| prefixOperator = operator -#| -#| optInd = COMMENT? +#| +#| optInd = COMMENT? IND? #| optPar = (IND{>} | IND{=})? -#| -#| simpleExpr = assignExpr (OP0 optInd assignExpr)* -#| assignExpr = orExpr (OP1 optInd orExpr)* -#| orExpr = andExpr (OP2 optInd andExpr)* -#| andExpr = cmpExpr (OP3 optInd cmpExpr)* -#| cmpExpr = sliceExpr (OP4 optInd sliceExpr)* -#| sliceExpr = ampExpr (OP5 optInd ampExpr)* -#| ampExpr = plusExpr (OP6 optInd plusExpr)* -#| plusExpr = mulExpr (OP7 optInd mulExpr)* -#| mulExpr = dollarExpr (OP8 optInd dollarExpr)* -#| dollarExpr = primary (OP9 optInd primary)* - -proc colcom(p: var TParser, n: PNode) = +#| +#| 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) -proc parseSymbol(p: var TParser): PNode = - #| symbol = '`' (KEYW|IDENT|operator|'(' ')'|'[' ']'|'{' '}'|'='|literal)+ '`' - #| | IDENT +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: + of tkSymbol: result = newIdentNodeP(p.tok.ident, p) getTok(p) - of tkAccent: + 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 tkBracketLe: - add(result, newIdentNodeP(getIdent"[]", p)) - getTok(p) - eat(p, tkBracketRi) - of tkEquals: - add(result, newIdentNodeP(getIdent"=", p)) - getTok(p) - of tkParLe: - add(result, newIdentNodeP(getIdent"()", p)) - getTok(p) - eat(p, tkParRi) - of tkCurlyLe: - add(result, newIdentNodeP(getIdent"{}", p)) - getTok(p) - eat(p, tkCurlyRi) - of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDotDot: - add(result, newIdentNodeP(p.tok.ident, p)) - getTok(p) - of tkIntLit..tkCharLit: - add(result, newIdentNodeP(getIdent(tokToStr(p.tok)), p)) + 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: - if result.len == 0: - parMessage(p, errIdentifierExpected, p.tok) + parMessage(p, errIdentifierExpected, p.tok) break eat(p, tkAccent) else: parMessage(p, errIdentifierExpected, p.tok) - getTok(p) # BUGFIX: We must consume a token here to prevent endless loops! - result = ast.emptyNode - -proc indexExpr(p: var TParser): PNode = - #| indexExpr = expr - result = parseExpr(p) - -proc indexExprList(p: var TParser, first: PNode, k: TNodeKind, - endToken: TTokType): PNode = - #| indexExprList = indexExpr ^+ comma - result = newNodeP(k, p) - addSon(result, first) - getTok(p) - optInd(p, result) - while p.tok.tokType notin {endToken, tkEof}: - var a = indexExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break + # 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) - skipComment(p, a) - optPar(p) - eat(p, endToken) + #optInd(p, result) + result.add(a) + result.add(parseExpr(p)) + else: + result = a -proc colonOrEquals(p: var TParser, a: PNode): PNode = +proc colonOrEquals(p: var Parser, a: PNode): PNode = if p.tok.tokType == tkColon: result = newNodeP(nkExprColonExpr, p) getTok(p) + newlineWasSplitting(p) #optInd(p, result) - addSon(result, a) - addSon(result, parseExpr(p)) - elif p.tok.tokType == tkEquals: - result = newNodeP(nkExprEqExpr, p) - getTok(p) - #optInd(p, result) - addSon(result, a) - addSon(result, parseExpr(p)) + result.add(a) + result.add(parseExpr(p)) else: - result = a + result = equals(p, a) -proc exprColonEqExpr(p: var TParser): PNode = - #| exprColonEqExpr = expr (':'|'=' expr)? +proc exprColonEqExpr(p: var Parser): PNode = + #| exprColonEqExpr = expr ((':'|'=') expr + #| / doBlock extraPostExprBlock*)? var a = parseExpr(p) - result = colonOrEquals(p, a) + if p.tok.tokType == tkDo: + result = postExprBlocks(p, a) + else: + result = colonOrEquals(p, a) -proc exprList(p: var TParser, endTok: TTokType, result: PNode) = +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) - while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof): - var a = parseExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break + # 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) - eat(p, endTok) - -proc dotExpr(p: var TParser, a: PNode): PNode = - #| dotExpr = expr '.' optInd ('type' | 'addr' | symbol) - var info = p.lex.getlineInfo + 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, a) - case p.tok.tokType - of tkType: - result = newNodeP(nkTypeOfExpr, p) - getTok(p) - addSon(result, a) - of tkAddr: - result = newNodeP(nkAddr, 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) - addSon(result, a) - else: - result = newNodeI(nkDotExpr, info) - addSon(result, a) - addSon(result, parseSymbol(p)) - -proc qualifiedIdent(p: var TParser): PNode = - #| qualifiedIdent = symbol ('.' optInd ('type' | 'addr' | symbol))? - result = parseSymbol(p) - if p.tok.tokType == tkDot: result = dotExpr(p, result) + optInd(p, a) + when defined(nimpretty): + dec p.em.doIndentMore -proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) = +proc exprColonEqExprListAux(p: var Parser, endTok: TokType, result: PNode) = assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi}) getTok(p) - optInd(p, result) + flexComment(p, result) + optPar(p) + # progress guaranteed while p.tok.tokType != endTok and p.tok.tokType != tkEof: var a = exprColonEqExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break + 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 TParser, kind: TNodeKind, - endTok: TTokType): PNode = +proc exprColonEqExprList(p: var Parser, kind: TNodeKind, + endTok: TokType): PNode = #| exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)? result = newNodeP(kind, p) exprColonEqExprListAux(p, endTok, result) -proc setOrTableConstr(p: var TParser): PNode = +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.kind = nkTableConstr + result.transitionSonsKind(nkTableConstr) else: + # progress guaranteed while p.tok.tokType notin {tkCurlyRi, tkEof}: var a = exprColonEqExpr(p) - if a.kind == nkExprColonExpr: result.kind = nkTableConstr - addSon(result, a) - if p.tok.tokType != tkComma: break + 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 TParser): PNode = - #| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' +proc parseCast(p: var Parser): PNode = + #| castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') / + # ('(' optInd exprColonEqExpr optPar ')') result = newNodeP(nkCast, p) getTok(p) - eat(p, tkBracketLe) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - optPar(p) - eat(p, tkBracketRi) - eat(p, tkParLe) - optInd(p, result) - addSon(result, parseExpr(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: TNumericalBase) = +proc setBaseFlags(n: PNode, base: NumericalBase) = case base - of base10: nil - of base2: incl(n.flags, nfBase2) - of base8: incl(n.flags, nfBase8) - of base16: incl(n.flags, nfBase16) - -proc parseGStrLit(p: var TParser, a: PNode): PNode = + 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: + of tkGStrLit: result = newNodeP(nkCallStrLit, p) - addSon(result, a) - addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p)) + result.add(a) + result.add(newStrNodeP(nkRStrLit, p.tok.literal, p)) getTok(p) - of tkGTripleStrLit: + of tkGTripleStrLit: result = newNodeP(nkCallStrLit, p) - addSon(result, a) - addSon(result, newStrNodeP(nkTripleStrLit, p.tok.literal, 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 -type - TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix - -proc complexOrSimpleStmt(p: var TParser): PNode -proc simpleExpr(p: var TParser, mode = pmNormal): PNode - -proc semiStmtList(p: var TParser, result: PNode) = - result.add(complexOrSimpleStmt(p)) - while p.tok.tokType == tkSemicolon: - getTok(p) - optInd(p, result) - result.add(complexOrSimpleStmt(p)) - result.kind = nkStmtListExpr + 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 TParser): PNode = +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 complexOrSimpleStmt ^+ ';' - #| | simpleExpr ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )? - #| | (':' expr)? (',' (exprColonEqExpr comma?)*)? )? - #| optPar ')' + #| 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 + # 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) - if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, - tkTry, tkFinally, tkExcept, tkFor, tkBlock, - tkConst, tkLet, tkWhen, tkVar, + 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: + elif p.tok.tokType == tkSemiColon: # '(;' enforces 'stmt' context: getTok(p) optInd(p, result) semiStmtList(p, result) - elif p.tok.tokType != tkParRi: + 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 == tkEquals: + 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) - let asgn = newNodeI(nkAsgn, a.info, 2) - asgn.sons[0] = a - asgn.sons[1] = b + asgn.add a + asgn.add b result.add(asgn) - elif p.tok.tokType == tkSemicolon: + 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) - addSon(result, a) - if p.tok.tokType != tkComma: break + result.add(a) + if p.tok.tokType != tkComma: break getTok(p) skipComment(p, a) optPar(p) eat(p, tkParRi) - -proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = + 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 - #| | 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 - #| | NIL - #| | par | arrayConstr | setOrTableConstr + #| 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: + of tkSymbol, tkBuiltInMagics, tkOut: result = newIdentNodeP(p.tok.ident, p) getTok(p) result = parseGStrLit(p, result) - of tkAccent: + of tkAccent: result = parseSymbol(p) # literals of tkIntLit: result = newIntNodeP(nkIntLit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkInt8Lit: + of tkInt8Lit: result = newIntNodeP(nkInt8Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkInt16Lit: + of tkInt16Lit: result = newIntNodeP(nkInt16Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkInt32Lit: + of tkInt32Lit: result = newIntNodeP(nkInt32Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkInt64Lit: + of tkInt64Lit: result = newIntNodeP(nkInt64Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkUIntLit: + of tkUIntLit: result = newIntNodeP(nkUIntLit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkUInt8Lit: + of tkUInt8Lit: result = newIntNodeP(nkUInt8Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkUInt16Lit: + of tkUInt16Lit: result = newIntNodeP(nkUInt16Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkUInt32Lit: + of tkUInt32Lit: result = newIntNodeP(nkUInt32Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkUInt64Lit: + of tkUInt64Lit: result = newIntNodeP(nkUInt64Lit, p.tok.iNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkFloatLit: + of tkFloatLit: result = newFloatNodeP(nkFloatLit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkFloat32Lit: + of tkFloat32Lit: result = newFloatNodeP(nkFloat32Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkFloat64Lit: + of tkFloat64Lit: result = newFloatNodeP(nkFloat64Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) @@ -593,19 +801,27 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = result = newFloatNodeP(nkFloat128Lit, p.tok.fNumber, p) setBaseFlags(result, p.tok.base) getTok(p) - of tkStrLit: + of tkStrLit: result = newStrNodeP(nkStrLit, p.tok.literal, p) getTok(p) - of tkRStrLit: + of tkRStrLit: result = newStrNodeP(nkRStrLit, p.tok.literal, p) getTok(p) - of tkTripleStrLit: + of tkTripleStrLit: result = newStrNodeP(nkTripleStrLit, p.tok.literal, p) getTok(p) - of tkCharLit: + of tkCharLit: result = newIntNodeP(nkCharLit, ord(p.tok.literal[0]), p) getTok(p) - of tkNil: + 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: @@ -620,181 +836,270 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = of tkBracketLe: # [] constructor result = exprColonEqExprList(p, nkBracket, tkBracketRi) - of tkCast: + of tkCast: result = parseCast(p) else: parMessage(p, errExprExpected, p.tok) - getTok(p) # we must consume a token here to prevend endless loops! - result = ast.emptyNode - -proc primarySuffix(p: var TParser, r: PNode): PNode = - #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? - #| | doBlocks - #| | '.' optInd ('type' | 'addr' | symbol) generalizedLit? - #| | '[' optInd indexExprList optPar ']' - #| | '{' optInd indexExprList optPar '}' + 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 - while p.tok.indent < 0: + + # progress guaranteed + while p.tok.indent < 0 or + (p.tok.tokType == tkDot and p.tok.indent >= baseIndent): case p.tok.tokType - of tkParLe: - var a = result - result = newNodeP(nkCall, p) - addSon(result, a) - exprColonEqExprListAux(p, tkParRi, result) - if result.len > 1 and result.sons[1].kind == nkExprColonExpr: - result.kind = nkObjConstr - else: - parseDoBlocks(p, result) - of tkDo: - var a = result - result = newNodeP(nkCall, p) - addSon(result, a) - parseDoBlocks(p, result) + 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: - result = indexExprList(p, result, nkBracketExpr, tkBracketRi) + of tkBracketLe: + # progress guaranteed + if tsLeading in p.tok.spacing: + result = commandExpr(p, result, mode) + break + result = namedParams(p, result, nkBracketExpr, tkBracketRi) of tkCurlyLe: - result = indexExprList(p, result, nkCurlyExpr, tkCurlyRi) - else: break - -proc primary(p: var TParser, mode: TPrimaryMode): PNode + # 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 simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = - result = primary(p, mode) +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: - while opPrec >= limit and p.tok.indent < 0: - var leftAssoc = ord(IsLeftAssociative(p.tok)) + # 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) - optInd(p, opNode) + flexComment(p, a) + optPar(p) # read sub-expression with higher priority: var b = simpleExprAux(p, opPrec + leftAssoc, modeB) - addSon(a, opNode) - addSon(a, result) - addSon(a, b) + a.add(opNode) + a.add(result) + a.add(b) result = a opPrec = getPrecedence(p.tok) - -proc simpleExpr(p: var TParser, mode = pmNormal): PNode = + 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 parseIfExpr(p: var TParser, kind: TNodeKind): PNode = - #| condExpr = expr colcom expr optInd - #| ('elif' expr colcom expr optInd)* - #| 'else' colcom expr - #| ifExpr = 'if' condExpr - #| whenExpr = 'when' condExpr - result = newNodeP(kind, p) - while true: - getTok(p) # skip `if`, `elif` - var branch = newNodeP(nkElifExpr, p) - addSon(branch, parseExpr(p)) - colcom(p, branch) - addSon(branch, parseExpr(p)) - optInd(p, branch) - addSon(result, branch) - if p.tok.tokType != tkElif: break - var branch = newNodeP(nkElseExpr, p) - eat(p, tkElse) - colcom(p, branch) - addSon(branch, parseExpr(p)) - addSon(result, branch) - -proc parsePragma(p: var TParser): PNode = - #| pragma = '{.' optInd (exprColonExpr comma?)* optPar ('.}' | '}') +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) - addSon(result, a) + 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}: getTok(p) - else: parMessage(p, errTokenExpected, ".}") - -proc identVis(p: var TParser): PNode = - #| identVis = symbol opr? # postfix position + 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: + if p.tok.tokType == tkOpr: + when defined(nimpretty): + starWasExportMarker(p.em) result = newNodeP(nkPostfix, p) - addSon(result, newIdentNodeP(p.tok.ident, p)) - addSon(result, a) + result.add(newIdentNodeP(p.tok.ident, p)) + result.add(a) getTok(p) - else: + elif p.tok.tokType == tkDot and allowDot: + result = dotExpr(p, a) + else: result = a - -proc identWithPragma(p: var TParser): PNode = + +proc identWithPragma(p: var Parser; allowDot=false): PNode = #| identWithPragma = identVis pragma? - var a = identVis(p) - if p.tok.tokType == tkCurlyDotLe: + #| identWithPragmaDot = identVisDot pragma? + var a = identVis(p, allowDot) + if p.tok.tokType == tkCurlyDotLe: result = newNodeP(nkPragmaExpr, p) - addSon(result, a) - addSon(result, parsePragma(p)) - else: + result.add(a) + result.add(parsePragma(p)) + else: result = a type - TDeclaredIdentFlag = enum + DeclaredIdentFlag = enum withPragma, # identifier may have pragma withBothOptional # both ':' and '=' parts are optional - TDeclaredIdentFlags = set[TDeclaredIdentFlag] + withDot # allow 'var ident.ident = value' + DeclaredIdentFlags = set[DeclaredIdentFlag] -proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode = +proc parseIdentColonEquals(p: var Parser, flags: DeclaredIdentFlags): PNode = #| declColonEquals = identWithPragma (comma identWithPragma)* comma? - #| (':' optInd typeDesc)? ('=' optInd expr)? - #| identColonEquals = ident (comma ident)* comma? - #| (':' optInd typeDesc)? ('=' optInd expr)?) + #| (':' optInd typeDescExpr)? ('=' optInd expr)? + #| identColonEquals = IDENT (comma IDENT)* comma? + #| (':' optInd typeDescExpr)? ('=' optInd expr)?) var a: PNode result = newNodeP(nkIdentDefs, p) - while true: + # progress guaranteed + while true: case p.tok.tokType - of tkSymbol, tkAccent: - if withPragma in flags: a = identWithPragma(p) + 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 - addSon(result, a) - if p.tok.tokType != tkComma: break + 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: + if p.tok.tokType == tkColon: getTok(p) optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, ast.emptyNode) - if (p.tok.tokType != tkEquals) and not (withBothOptional in flags): - parMessage(p, errColonOrEqualsExpected, p.tok) - if p.tok.tokType == tkEquals: + 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) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) - -proc parseTuple(p: var TParser, indentAllowed = false): PNode = - #| inlTupleDecl = 'tuple' - #| [' optInd (identColonEquals (comma/semicolon)?)* optPar ']' - #| extTupleDecl = 'tuple' - #| COMMENT? (IND{>} identColonEquals (IND{=} identColonEquals)*)? + 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, {}) - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemicolon}: 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) @@ -803,364 +1108,604 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode = skipComment(p, result) if realInd(p): withInd(p): - skipComment(p, result) + rawSkipComment(p, result) + # progress guaranteed while true: case p.tok.tokType of tkSymbol, tkAccent: var a = parseIdentColonEquals(p, {}) - skipComment(p, a) - addSon(result, a) + 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 TParser, retColon = true): PNode = +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) - addSon(result, ast.emptyNode) # return type - if p.tok.tokType == tkParLe and p.tok.indent < 0: + 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: + of tkSymbol, tkAccent: a = parseIdentColonEquals(p, {withBothOptional, withPragma}) - of tkParRi: - break - else: - parMessage(p, errTokenExpected, ")") - break - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemicolon}: break + 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 IdentEq(p.tok.ident, "->") + else: p.tok.tokType == tkOpr and p.tok.ident.s == "->" if hasRet and p.tok.indent < 0: getTok(p) optInd(p, result) - result.sons[0] = parseTypeDesc(p) - -proc optPragmas(p: var TParser): PNode = + 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 = ast.emptyNode + result = p.emptyNode -proc parseDoBlock(p: var TParser): PNode = - #| doBlock = 'do' paramListArrow pragmas? colcom stmt - let info = parLineInfo(p) - getTok(p) - let params = parseParamList(p, retColon=false) +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) - eat(p, tkColon) - skipComment(p, result) - result = newProcNode(nkDo, info, parseStmt(p), - params = params, - pragmas = pragmas) - -proc parseDoBlocks(p: var TParser, call: PNode) = - #| doBlocks = doBlock ^* IND{=} - if p.tok.tokType == tkDo: - addSon(call, parseDoBlock(p)) - while sameInd(p) and p.tok.tokType == tkDo: - addSon(call, parseDoBlock(p)) - -proc parseProcExpr(p: var TParser, isExpr: bool): PNode = - #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)? + 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) - getTok(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: + 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 = newProcNode(nkLambda, info, parseStmt(p), - params = params, - pragmas = pragmas) + result.replaceSon bodyPos, parseStmt(p) else: - result = newNodeI(nkProcTy, info) - if hasSignature: - addSon(result, params) - addSon(result, pragmas) - -proc isExprStart(p: TParser): bool = + 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, - tkProc, tkIterator, tkBind, tkAddr, - tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkObject, tkType, tkWhen, tkCase, tkShared: + 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 parseTypeDescKAux(p: var TParser, kind: TNodeKind, - mode: TPrimaryMode): PNode = + +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): - addSon(result, primary(p, mode)) + 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() -proc parseExpr(p: var TParser): PNode = - #| expr = (ifExpr +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 - #| | caseExpr - #| | tryStmt) + #| | caseStmt + #| | forExpr + #| | tryExpr) #| / simpleExpr - case p.tok.tokType: - of tkIf: result = parseIfExpr(p, nkIfExpr) - of tkWhen: result = parseIfExpr(p, nkWhenExpr) - of tkCase: result = parseCase(p) - of tkTry: result = parseTry(p) + 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) - -proc parseObject(p: var TParser): PNode -proc parseDistinct(p: var TParser): PNode -proc parseEnum(p: var TParser): PNode - -proc primary(p: var TParser, mode: TPrimaryMode): PNode = - #| typeKeyw = 'var' | 'ref' | 'ptr' | 'shared' | 'type' | 'tuple' - #| | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' - #| primary = typeKeyw typeDescK - #| / prefixOperator* identOrLiteral primarySuffix* - #| / 'addr' primary - #| / 'static' primary - #| / 'bind' primary + 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): - let isSigil = IsSigilLike(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) - addSon(result, a) + result.add(a) getTok(p) optInd(p, a) - if isSigil: - #XXX prefix operators - addSon(result, primary(p, pmSkipSuffix)) - result = primarySuffix(p, result) + 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: - addSon(result, primary(p, pmNormal)) + result.add(primary(p, pmNormal)) return - - case p.tok.tokType: - of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode) - of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode) - of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode) - of tkShared: result = parseTypeDescKAux(p, nkSharedTy, mode) - of tkType: result = parseTypeDescKAux(p, nkTypeOfExpr, mode) - of tkTuple: result = parseTuple(p, mode == pmTypeDef) - of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}) + + 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: - if mode in {pmTypeDesc, pmTypeDef}: - result = parseProcExpr(p, false) - result.kind = nkIteratorTy - else: - # no anon iterators for now: - parMessage(p, errExprExpected, p.tok) - getTok(p) # we must consume a token here to prevend endless loops! - result = ast.emptyNode - of tkEnum: - if mode == pmTypeDef: - result = parseEnum(p) - else: - result = newNodeP(nkEnumTy, p) - getTok(p) - of tkObject: - if mode == pmTypeDef: - result = parseObject(p) - else: - result = newNodeP(nkObjectTy, p) - getTok(p) - of tkDistinct: - if mode == pmTypeDef: - result = parseDistinct(p) - else: - result = newNodeP(nkDistinctTy, p) - getTok(p) - of tkAddr: - result = newNodeP(nkAddr, p) - getTokNoInd(p) - addSon(result, primary(p, pmNormal)) - of tkStatic: - result = newNodeP(nkStaticExpr, p) - getTokNoInd(p) - addSon(result, primary(p, pmNormal)) + 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) - addSon(result, primary(p, pmNormal)) + 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) - if mode != pmSkipSuffix: - result = primarySuffix(p, result) + result = primarySuffix(p, result, baseInd, mode) -proc parseTypeDesc(p: var TParser): PNode = - #| typeDesc = simpleExpr - result = simpleExpr(p, pmTypeDesc) +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 parseTypeDefAux(p: var TParser): PNode = - #| typeDefAux = simpleExpr - result = simpleExpr(p, pmTypeDef) +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 = newNodeI(nkCall, n.info) + result = newNode(nkCall, n.info) result.add n -proc parseExprStmt(p: var TParser): PNode = - #| exprStmt = simpleExpr - #| (( '=' optInd expr ) - #| / ( expr ^+ comma - #| doBlocks - #| / ':' stmt? ( IND{=} 'of' exprList ':' stmt - #| | IND{=} 'elif' expr ':' stmt - #| | IND{=} 'except' exprList ':' stmt - #| | IND{=} 'else' ':' stmt )* - #| ))? - var a = simpleExpr(p) - if p.tok.tokType == tkEquals: +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) - result = newNodeI(nkAsgn, a.info) - addSon(result, a) - addSon(result, b) + 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 = newNode(nkCommand, a.info, @[a]) + result = newTree(nkCommand, a.info, a) + let baseIndent = p.currInd while true: - var e = parseExpr(p) - addSon(result, e) - if p.tok.tokType != tkComma: break + 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 - if p.tok.tokType == tkDo and p.tok.indent < 0: - result = makeCall(result) - parseDoBlocks(p, result) - return result - if p.tok.tokType == tkColon and p.tok.indent < 0: - result = makeCall(result) + 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) - skipComment(p, result) - if p.tok.TokType notin {tkOf, tkElif, tkElse, tkExcept}: - let body = parseStmt(p) - addSon(result, newProcNode(nkDo, body.info, body)) - while sameInd(p): - var b: PNode - case p.tok.tokType - of tkOf: - b = newNodeP(nkOfBranch, p) - exprList(p, tkColon, b) - of tkElif: - b = newNodeP(nkElifBranch, p) - getTok(p) - optInd(p, b) - addSon(b, parseExpr(p)) - eat(p, tkColon) - of tkExcept: - b = newNodeP(nkExceptBranch, p) - exprList(p, tkColon, b) - skipComment(p, b) - of tkElse: - b = newNodeP(nkElse, p) - getTok(p) - eat(p, tkColon) - else: break - addSon(b, parseStmt(p)) - addSon(result, b) - if b.kind == nkElse: break + result.add(a) + result.add(parseExpr(p)) + setEndInfo() -proc parseImport(p: var TParser, kind: TNodeKind): PNode = +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 = parseExpr(p) - addSon(result, a) + var a = parseModuleName(p, kind) + result.add(a) if p.tok.tokType in {tkComma, tkExcept}: if p.tok.tokType == tkExcept: - result.kind = succ(kind) + result.transitionSonsKind(succ(kind)) getTok(p) optInd(p, result) while true: # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}: - a = parseExpr(p) - if a.kind == nkEmpty: break - addSon(result, a) - if p.tok.tokType != tkComma: break + 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 TParser): PNode = +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: break - addSon(result, a) - if p.tok.tokType != tkComma: break + 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 TParser): PNode = +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 = parseExpr(p) - addSon(result, a) #optInd(p, a); + 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: break - addSon(result, a) - if p.tok.tokType != tkComma: break + 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 TParser, kind: TNodeKind): PNode = +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 = 'break' optInd expr? + #| continueStmt = 'continue' optInd expr? result = newNodeP(kind, p) getTok(p) if p.tok.tokType == tkComment: skipComment(p, result) - addSon(result, ast.emptyNode) - elif p.tok.indent >= 0 and p.tok.indent <= p.currInd or - p.tok.tokType == tkEof: + result.add(p.emptyNode) + elif p.tok.indent >= 0 and p.tok.indent <= p.currInd or not isExprStart(p): # NL terminates: - addSon(result, ast.emptyNode) + result.add(p.emptyNode) + # nimpretty here! else: - addSon(result, parseExpr(p)) + var e = parseExpr(p) + e = postExprBlocks(p, e) + result.add(e) + setEndInfo() -proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode = +proc parseIfOrWhen(p: var Parser, kind: TNodeKind): PNode = #| condStmt = expr colcom stmt COMMENT? #| (IND{=} 'elif' expr colcom stmt)* #| (IND{=} 'else' colcom stmt)? @@ -1171,31 +1716,56 @@ proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode = getTok(p) # skip `if`, `when`, `elif` var branch = newNodeP(nkElifBranch, p) optInd(p, branch) - addSon(branch, parseExpr(p)) - eat(p, tkColon) - skipComment(p, branch) - addSon(branch, parseStmt(p)) + branch.add(parseExpr(p)) + colcom(p, branch) + branch.add(parseStmt(p)) skipComment(p, branch) - addSon(result, 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) - eat(p, tkColon) + 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) - addSon(branch, parseStmt(p)) - addSon(result, 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 TParser): PNode = +proc parseWhile(p: var Parser): PNode = #| whileStmt = 'while' expr colcom stmt result = newNodeP(nkWhileStmt, p) getTok(p) optInd(p, result) - addSon(result, parseExpr(p)) + result.add(parseExpr(p)) colcom(p, result) - addSon(result, parseStmt(p)) + result.add(parseStmt(p)) + setEndInfo() -proc parseCase(p: var TParser): PNode = +proc parseCase(p: var Parser): PNode = #| ofBranch = 'of' exprList colcom stmt #| ofBranches = ofBranch (IND{=} ofBranch)* #| (IND{=} 'elif' expr colcom stmt)* @@ -1205,19 +1775,19 @@ proc parseCase(p: var TParser): PNode = #| | IND{=} ofBranches) var b: PNode - inElif= false + inElif = false wasIndented = false result = newNodeP(nkCaseStmt, p) getTok(p) - addSon(result, parseExpr(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: @@ -1229,349 +1799,366 @@ proc parseCase(p: var TParser): PNode = b = newNodeP(nkElifBranch, p) getTok(p) optInd(p, b) - addSon(b, parseExpr(p)) - eat(p, tkColon) + b.add(parseExpr(p)) of tkElse: b = newNodeP(nkElse, p) getTok(p) - eat(p, tkColon) else: break - skipComment(p, b) - addSon(b, parseStmt(p)) - addSon(result, b) + colcom(p, b) + b.add(parseStmt(p)) + result.add(b) if b.kind == nkElse: break - + if wasIndented: p.currInd = oldInd - -proc parseTry(p: var TParser): PNode = + setEndInfo() + +proc parseTry(p: var Parser; isExpr: bool): PNode = #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally') - #| (IND{=}? 'except' exprList colcom stmt)* + #| (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) - eat(p, tkColon) - skipComment(p, result) - addSon(result, parseStmt(p)) + colcom(p, result) + result.add(parseStmt(p)) var b: PNode = nil - while sameOrNoInd(p): + + while sameOrNoInd(p) or (isExpr and parentIndent <= p.tok.indent): case p.tok.tokType - of tkExcept: + of tkExcept: b = newNodeP(nkExceptBranch, p) - exprList(p, tkColon, b) - of tkFinally: + optionalExprList(p, tkColon, b) + of tkFinally: b = newNodeP(nkFinally, p) - getTokNoInd(p) - eat(p, tkColon) + getTok(p) else: break - skipComment(p, b) - addSon(b, parseStmt(p)) - addSon(result, b) - if b.kind == nkFinally: break - if b == nil: parMessage(p, errTokenExpected, "except") - -proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode = - #| exceptBlock = 'except' colcom stmt - result = newNodeP(kind, p) - getTokNoInd(p) - colcom(p, result) - addSon(result, parseStmt(p)) + colcom(p, b) + b.add(parseStmt(p)) + result.add(b) + if b == nil: parMessage(p, "expected 'except'") + setEndInfo() -proc parseFor(p: var TParser): PNode = - #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt - result = newNodeP(nkForStmt, p) - getTokNoInd(p) - var a = identWithPragma(p) - addSon(result, a) - while p.tok.tokType == tkComma: - getTok(p) - optInd(p, a) - a = identWithPragma(p) - addSon(result, a) - eat(p, tkIn) - addSon(result, parseExpr(p)) +proc parseExceptBlock(p: var Parser, kind: TNodeKind): PNode = + result = newNodeP(kind, p) + getTok(p) colcom(p, result) - addSon(result, parseStmt(p)) + result.add(parseStmt(p)) + setEndInfo() -proc parseBlock(p: var TParser): PNode = +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: addSon(result, ast.emptyNode) - else: addSon(result, parseSymbol(p)) + if p.tok.tokType == tkColon: result.add(p.emptyNode) + else: result.add(parseSymbol(p)) colcom(p, result) - addSon(result, parseStmt(p)) + result.add(parseStmt(p)) + setEndInfo() -proc parseStatic(p: var TParser): PNode = +proc parseStaticOrDefer(p: var Parser; k: TNodeKind): PNode = #| staticStmt = 'static' colcom stmt - result = newNodeP(nkStaticStmt, p) - getTokNoInd(p) + #| deferStmt = 'defer' colcom stmt + result = newNodeP(k, p) + getTok(p) colcom(p, result) - addSon(result, parseStmt(p)) - -proc parseAsm(p: var TParser): PNode = - #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT) + 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: addSon(result, parsePragma(p)) - else: addSon(result, ast.emptyNode) + if p.tok.tokType == tkCurlyDotLe: result.add(parsePragma(p)) + else: result.add(p.emptyNode) case p.tok.tokType - of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p)) - of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p)) - of tkTripleStrLit: addSon(result, - newStrNodeP(nkTripleStrLit, p.tok.literal, p)) - else: - parMessage(p, errStringLiteralExpected) - addSon(result, ast.emptyNode) - return + 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 TParser): PNode = +proc parseGenericParam(p: var Parser): PNode = #| genericParam = symbol (comma symbol)* (colon expr)? ('=' optInd expr)? var a: PNode result = newNodeP(nkIdentDefs, p) - while true: + # progress guaranteed + while true: case p.tok.tokType - of tkSymbol, tkAccent: + 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 - addSon(result, a) - if p.tok.tokType != tkComma: break + 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: + if p.tok.tokType == tkColon: getTok(p) optInd(p, result) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkEquals: + result.add(parseExpr(p)) + else: + result.add(p.emptyNode) + if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) + result.add(parseExpr(p)) + else: + result.add(p.emptyNode) + setEndInfo() -proc parseGenericParamList(p: var TParser): PNode = +proc parseGenericParamList(p: var Parser): PNode = #| genericParamList = '[' optInd #| genericParam ^* (comma/semicolon) optPar ']' result = newNodeP(nkGenericParams, p) getTok(p) optInd(p, result) - while p.tok.tokType in {tkSymbol, tkAccent}: + # progress guaranteed + while p.tok.tokType in {tkSymbol, tkAccent, tkIn, tkOut}: var a = parseGenericParam(p) - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemicolon}: 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, tkBracketRi) + setEndInfo() -proc parsePattern(p: var TParser): PNode = +proc parsePattern(p: var Parser): PNode = #| pattern = '{' stmt '}' eat(p, tkCurlyLe) result = parseStmt(p) eat(p, tkCurlyRi) + setEndInfo() -proc validInd(p: var TParser): bool = - result = p.tok.indent < 0 or p.tok.indent > p.currInd - -proc parseRoutine(p: var TParser, kind: TNodeKind): PNode = +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) - addSon(result, identVis(p)) - if p.tok.tokType == tkCurlyLe and p.validInd: addSon(result, p.parsePattern) - else: addSon(result, ast.emptyNode) + 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: - addSon(result, ast.emptyNode) - addSon(result, p.parseParamList) - if p.tok.tokType == tkCurlyDotLe and p.validInd: addSon(result, p.parsePragma) - else: addSon(result, ast.emptyNode) + 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: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkEquals and p.validInd: + result.add(p.emptyNode) + let maybeMissEquals = p.tok.tokType != tkEquals + if (not maybeMissEquals) and p.validInd: getTok(p) skipComment(p, result) - addSon(result, parseStmt(p)) + result.add(parseStmt(p)) else: - addSon(result, ast.emptyNode) - indAndComment(p, result) - -proc newCommentStmt(p: var TParser): PNode = + 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.info.line = result.info.line - int16(1) - int16(p.tok.iNumber) result.comment = p.tok.literal getTok(p) -type - TDefParser = proc (p: var TParser): PNode {.nimcall.} - -proc parseSection(p: var TParser, kind: TNodeKind, - defparser: TDefParser): PNode = - #| section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED) +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) - getTok(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: + of tkSymbol, tkAccent, tkParLe: var a = defparser(p) skipComment(p, a) - addSon(result, a) - of tkComment: + result.add(a) + of tkComment: var a = newCommentStmt(p) - addSon(result, a) - else: + result.add(a) + else: parMessage(p, errIdentifierExpected, p.tok) break - if result.len == 0: parMessage(p, errIdentifierExpected, p.tok) + 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 - addSon(result, defparser(p)) - else: + result.add(defparser(p)) + else: parMessage(p, errIdentifierExpected, p.tok) - -proc parseConstant(p: var TParser): PNode = - #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment - result = newNodeP(nkConstDef, p) - addSon(result, identWithPragma(p)) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, ast.emptyNode) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) - indAndComment(p, result) - -proc parseEnum(p: var TParser): PNode = - #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ + setEndInfo() + +proc parseEnum(p: var Parser): PNode = + #| enumDecl = 'enum' optInd (symbol pragma? optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) getTok(p) - addSon(result, ast.emptyNode) + 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: - add(result, a) + result.add(symPragma) break - if p.tok.tokType == tkEquals and p.tok.indent < 0: + + if p.tok.tokType == tkEquals and p.tok.indent < 0: getTok(p) - optInd(p, a) - var b = a - a = newNodeP(nkEnumFieldDef, p) - addSon(a, b) - addSon(a, parseExpr(p)) - skipComment(p, a) + 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, a) + rawSkipComment(p, symPragma) else: - skipComment(p, a) - addSon(result, a) + 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 result.len <= 1: - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) + if not result.has2Sons: + parMessage(p, errIdentifierExpected, p.tok) + setEndInfo() -proc parseObjectPart(p: var TParser): PNode -proc parseObjectWhen(p: var TParser): PNode = +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) - while sameInd(p): + # progress guaranteed + while sameInd(p): getTok(p) # skip `when`, `elif` var branch = newNodeP(nkElifBranch, p) optInd(p, branch) - addSon(branch, parseExpr(p)) + branch.add(parseExpr(p)) colcom(p, branch) - addSon(branch, parseObjectPart(p)) - skipComment(p, branch) - addSon(result, 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) - addSon(branch, parseObjectPart(p)) - skipComment(p, branch) - addSon(result, branch) + branch.add(parseObjectPart(p)) + flexComment(p, branch) + result.add(branch) + setEndInfo() -proc parseObjectCase(p: var TParser): PNode = +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' identWithPragma ':' typeDesc ':'? COMMENT? + #| objectCase = 'case' declColonEquals ':'? COMMENT? #| (IND{>} objectBranches DED #| | IND{=} objectBranches) result = newNodeP(nkRecCase, p) getTokNoInd(p) - var a = newNodeP(nkIdentDefs, p) - addSon(a, identWithPragma(p)) - eat(p, tkColon) - addSon(a, parseTypeDesc(p)) - addSon(a, ast.emptyNode) - addSon(result, a) + var a = parseIdentColonEquals(p, {withPragma}) + result.add(a) if p.tok.tokType == tkColon: getTok(p) - skipComment(p, result) + 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: + of tkOf: b = newNodeP(nkOfBranch, p) exprList(p, tkColon, b) - of tkElse: + of tkElse: b = newNodeP(nkElse, p) getTok(p) - eat(p, tkColon) - else: break - skipComment(p, b) + 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 - addSon(b, fields) - addSon(result, b) + b.add(fields) + result.add(b) if b.kind == nkElse: break if wasIndented: p.currInd = oldInd - -proc parseObjectPart(p: var TParser): PNode = + setEndInfo() + +proc parseObjectPart(p: var Parser): PNode = #| objectPart = IND{>} objectPart^+IND{=} DED - #| / objectWhen / objectCase / 'nil' / declColonEquals + #| / 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: - addSon(result, parseObjectPart(p)) + of tkCase, tkWhen, tkSymbol, tkAccent, tkNil, tkDiscard: + result.add(parseObjectPart(p)) else: parMessage(p, errIdentifierExpected, p.tok) break - else: + elif sameOrNoInd(p): case p.tok.tokType of tkWhen: result = parseObjectWhen(p) @@ -1579,110 +2166,225 @@ proc parseObjectPart(p: var TParser): PNode = result = parseObjectCase(p) of tkSymbol, tkAccent: result = parseIdentColonEquals(p, {withPragma}) - skipComment(p, result) - of tkNil: + 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 = ast.emptyNode - -proc parseObject(p: var TParser): PNode = - #| object = 'object' pragma? ('of' typeDesc)? COMMENT? objectPart + 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) - if p.tok.tokType == tkCurlyDotLe and p.validInd: - addSon(result, parsePragma(p)) - else: - addSon(result, ast.emptyNode) + 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) - addSon(a, parseTypeDesc(p)) - addSon(result, a) - else: - addSon(result, ast.emptyNode) + 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): - addSon(result, emptyNode) - return - addSon(result, parseObjectPart(p)) + result.add(p.emptyNode) + else: + result.add(parseObjectPart(p)) + setEndInfo() -proc parseDistinct(p: var TParser): PNode = - #| distinct = 'distinct' optInd typeDesc - result = newNodeP(nkDistinctTy, p) +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) - optInd(p, result) - addSon(result, parseTypeDesc(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 TParser): PNode = - #| typeDef = identWithPragma genericParamList? '=' optInd typeDefAux +proc parseTypeDef(p: var Parser): PNode = + #| + #| typeDef = identVisDot genericParamList? pragma '=' optInd typeDefValue #| indAndComment? result = newNodeP(nkTypeDef, p) - addSon(result, identWithPragma(p)) + var identifier = identVis(p, allowDot=true) + var identPragma = identifier + var pragma: PNode + var genericParam: PNode + if p.tok.tokType == tkBracketLe and p.validInd: - addSon(result, parseGenericParamList(p)) + genericParam = parseGenericParamList(p) else: - addSon(result, ast.emptyNode) + 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) - addSon(result, parseTypeDefAux(p)) + result.add(parseTypeDefValue(p)) else: - addSon(result, ast.emptyNode) + result.add(p.emptyNode) indAndComment(p, result) # special extension! - -proc parseVarTuple(p: var TParser): PNode = - #| varTuple = '(' optInd identWithPragma ^+ comma optPar ')' '=' optInd expr + 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) - while p.tok.tokType in {tkSymbol, tkAccent}: - var a = identWithPragma(p) - addSon(result, a) - if p.tok.tokType != tkComma: break + # 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) - addSon(result, ast.emptyNode) # no type desc optPar(p) eat(p, tkParRi) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) + 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 parseVariable(p: var TParser): PNode = - #| variable = (varTuple / identColonEquals) indAndComment +proc parseConstant(p: var Parser): PNode = + #| constant = (varTuple / identWithPragma) (colon typeDesc)? '=' optInd expr indAndComment if p.tok.tokType == tkParLe: result = parseVarTuple(p) - else: result = parseIdentColonEquals(p, {withPragma}) + 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) - -proc parseBind(p: var TParser, k: TNodeKind): PNode = + 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) - addSon(result, a) + result.add(a) if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) #expectNl(p) - -proc parseStmtPragma(p: var TParser): PNode = + 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 = newNodeI(nkPragmaBlock, a.info) + result = newNode(nkPragmaBlock, a.info) getTok(p) skipComment(p, result) result.add a result.add parseStmt(p) + setEndInfo() -proc simpleStmt(p: var TParser): PNode = +proc simpleStmt(p: var Parser): PNode = #| simpleStmt = ((returnStmt | raiseStmt | yieldStmt | discardStmt | breakStmt #| | continueStmt | pragmaStmt | importStmt | exportStmt | fromStmt #| | includeStmt | commentStmt) / exprStmt) COMMENT? @@ -1702,126 +2404,187 @@ proc simpleStmt(p: var TParser): PNode = of tkComment: result = newCommentStmt(p) else: if isExprStart(p): result = parseExprStmt(p) - else: result = ast.emptyNode + else: result = p.emptyNode if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result) - -proc complexOrSimpleStmt(p: var TParser): PNode = + +proc complexOrSimpleStmt(p: var Parser): PNode = #| complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt - #| | tryStmt | finallyStmt | exceptStmt | forStmt - #| | blockStmt | staticStmt | asmStmt + #| | 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') section(variable) + #| | ('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) + 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 = parseStatic(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: result = parseSection(p, nkTypeSection, parseTypeDef) - of tkConst: result = parseSection(p, nkConstSection, parseConstant) - of tkLet: result = parseSection(p, nkLetSection, parseVariable) + 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 tkVar: result = parseSection(p, nkVarSection, parseVariable) 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 TParser): PNode = + +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: - nil - elif p.tok.tokType == tkSemicolon: - while p.tok.tokType == tkSemicolon: getTok(p) + 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: + if p.tok.indent > p.currInd and p.tok.tokType != tkDot: parMessage(p, errInvalidIndentation) break - if p.tok.toktype in {tkCurlyRi, tkParRi, tkCurlyDotRi, tkBracketRi}: + if p.tok.tokType in {tkCurlyRi, tkParRi, tkCurlyDotRi, tkBracketRi}: # XXX this ensures tnamedparamanonproc still compiles; # deprecate this syntax later break - var a = complexOrSimpleStmt(p) - if a.kind != nkEmpty: - addSon(result, a) - else: + 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) - getTok(p) + 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, tkIterator, - tkMacro, tkType, tkConst, tkWhen, tkVar: - parMessage(p, errComplexStmtRequiresInd) - result = ast.emptyNode + 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: - result = newNodeP(nkStmtList, p) - while true: - if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) - let a = simpleStmt(p) - if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) - result.add(a) - if p.tok.tokType != tkSemicolon: break - getTok(p) - -proc parseAll(p: var TParser): PNode = - result = newNodeP(nkStmtList, p) - while p.tok.tokType != tkEof: - var a = complexOrSimpleStmt(p) - if a.kind != nkEmpty: - addSon(result, a) - else: - parMessage(p, errExprExpected, p.tok) - # bugfix: consume a token here to prevent an endless loop: - getTok(p) - if p.tok.indent != 0: - parMessage(p, errInvalidIndentation) - -proc parseTopLevelStmt(p: var TParser): PNode = - result = ast.emptyNode + 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: - if p.tok.indent != 0: - if p.firstTok and p.tok.indent < 0: nil - else: parMessage(p, errInvalidIndentation) + # 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) + 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 parseString(s: string, filename: string = "", line: int = 0): PNode = - var stream = LLStreamOpen(s) +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 parser: TParser - OpenParser(parser, filename, stream) + var p = Parser() + p.lex.errorHandler = errorHandler + openParser(p, AbsoluteFile filename, stream, cache, config) - result = parser.parseAll - CloseParser(parser) + result = p.parseAll + closeParser(p) + setEndInfo() |