diff options
34 files changed, 1998 insertions, 127 deletions
diff --git a/.travis.yml b/.travis.yml index b7880cd36..5a091d0c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,8 @@ script: - nimble install niminst - nim c --taintMode:on -d:nimCoroutines tests/testament/tester - tests/testament/tester --pedantic all -d:nimCoroutines + - nim c -o:bin/nimpretty nimpretty/nimpretty.nim + - nim c -r nimpretty/tester.nim - ./koch web - ./koch csource - ./koch nimsuggest diff --git a/appveyor.yml b/appveyor.yml index a79d32e41..daa1d4e48 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,6 +47,8 @@ build_script: - koch boot -d:release - koch nimble - nim e tests/test_nimscript.nims + - nim c -o:bin/nimpretty.exe nimpretty/nimpretty.nim + - nim c -r nimpretty/tester.nim - nimble install zip -y - nimble install opengl - nimble install sdl1 diff --git a/changelog.md b/changelog.md index 4067cb693..4a490dfdd 100644 --- a/changelog.md +++ b/changelog.md @@ -107,6 +107,9 @@ use the Nim VM in a native Nim application. - Added the parameter ``val`` for the ``CritBitTree[T].incl`` proc. - The proc ``tgamma`` was renamed to ``gamma``. ``tgamma`` is deprecated. +- The ``pegs`` module now exports getters for the fields of its ``Peg`` and ``NonTerminal`` + object types. ``Peg``s with child nodes now have the standard ``items`` and ``pairs`` + iterators. ### Language additions diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 71d212282..f9654bb1f 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -978,16 +978,17 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props: for x in splitLines(res): var j = 0 - while x[j] in {' ', '\t'}: inc(j) - if x[j] in {'"', ':'}: - # don't modify the line if already in quotes or - # some clobber register list: - add(result, x); add(result, "\L") - elif x[j] != '\0': - # ignore empty lines - add(result, "\"") - add(result, x) - add(result, "\\n\"\n") + while j < x.len and x[j] in {' ', '\t'}: inc(j) + if j < x.len: + if x[j] in {'"', ':'}: + # don't modify the line if already in quotes or + # some clobber register list: + add(result, x); add(result, "\L") + else: + # ignore empty lines + add(result, "\"") + add(result, x) + add(result, "\\n\"\n") else: res.add("\L") result = res.rope diff --git a/compiler/layouter.nim b/compiler/layouter.nim new file mode 100644 index 000000000..62844db4b --- /dev/null +++ b/compiler/layouter.nim @@ -0,0 +1,268 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + Emitter* = object + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote: bool + col, lastLineNumber, lineSpan, indentLevel, indWidth: int + nested: int + doIndentMore*: int + content: string + indentStack: seq[int] + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*(em: var Emitter, cache: IdentCache; + config: ConfigRef, fileIdx: FileIndex) = + let fullPath = config.toFullPath(fileIdx) + em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), + cache, config) + if em.indWidth == 0: em.indWidth = 2 + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + em.indentStack = newSeqOfCap[int](30) + em.indentStack.add 0 + +proc closeEmitter*(em: var Emitter) = + var f = llStreamOpen(em.config.outFile, fmWrite) + if f == nil: + rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile) + f.llStreamWrite em.content + llStreamClose(f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const + openPars = {tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + splitters = openPars + {tkComma, tkSemicolon} + oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, + tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +template moreIndent(em): int = + (if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth) + +proc softLinebreak(em: var Emitter, lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+moreIndent(em): wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + var spaces = 0 + while a+spaces < em.content.len and em.content[a+spaces] == ' ': + inc spaces + if spaces > 0: delete(em.content, a, a+spaces-1) + let ws = "\L" & repeat(' ',em.indentLevel+moreIndent(em)) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len == 0 or em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = false + if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + if em.lastTok in (splitters + oprSet): + em.indentLevel = tok.indent + else: + if tok.indent > em.indentStack[^1]: + em.indentStack.add tok.indent + else: + # dedent? + while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent: + discard em.indentStack.pop() + em.indentLevel = em.indentStack.high * em.indWidth + #[ we only correct the indentation if it is not in an expression context, + so that code like + + const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + + is not touched. + ]# + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..em.indentLevel: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): + wr(" ") + elif not em.inquote and not endsInWhite(em) and + em.lastTok notin openPars: + #and tok.tokType in oprSet + wr(" ") + + if not em.inquote: + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn, tkNotin: + rememberSplit(splitIn) + wr(" ") + else: discard + else: + # keywords in backticks are not normalized: + wr(tok.ident.s) + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, tkComma: + wr(TokTypeToStr[tok.tokType]) + rememberSplit(splitComma) + wr(" ") + of tkParDotLe, tkParLe, tkBracketDotLe, tkBracketLe, + tkCurlyLe, tkCurlyDotLe, tkBracketLeColon: + if tok.strongSpaceA > 0 and not em.endsInWhite: + wr(" ") + wr(TokTypeToStr[tok.tokType]) + rememberSplit(splitParLe) + of tkParRi, + tkBracketRi, tkCurlyRi, + tkBracketDotRi, + tkCurlyDotRi, + tkParDotRi, + tkColonColon, tkDot: + wr(TokTypeToStr[tok.tokType]) + of tkEquals: + if not em.inquote and not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + if not em.inquote: wr(" ") + of tkOpr, tkDotDot: + if tok.strongSpaceA == 0 and tok.strongSpaceB == 0: + # if not surrounded by whitespace, don't produce any whitespace either: + wr(tok.ident.s) + else: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok): + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + if not em.inquote and endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr(" ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +proc commaWasSemicolon*(em: var Emitter) = + if em.content.endsWith(", "): + setLen(em.content, em.content.len-2) + em.content.add("; ") + +proc curlyRiWasPragma*(em: var Emitter) = + if em.content.endsWith("}"): + setLen(em.content, em.content.len-1) + em.content.add(".}") diff --git a/compiler/lexer.nim b/compiler/lexer.nim index f2af3f51c..c5afa6e97 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -867,7 +867,7 @@ proc getOperator(L: var TLexer, tok: var TToken) = if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: tok.strongSpaceB = -1 -proc newlineFollows*(L: var TLexer): bool = +proc newlineFollows*(L: TLexer): bool = var pos = L.bufpos var buf = L.buf while true: @@ -949,6 +949,8 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; if isDoc or defined(nimpretty): tok.literal.add buf[pos] inc(pos) L.bufpos = pos + when defined(nimpretty): + tok.commentOffsetB = L.offsetBase + pos - 1 proc scanComment(L: var TLexer, tok: var TToken) = var pos = L.bufpos @@ -957,6 +959,9 @@ proc scanComment(L: var TLexer, tok: var TToken) = # iNumber contains the number of '\n' in the token tok.iNumber = 0 assert buf[pos+1] == '#' + when defined(nimpretty): + tok.commentOffsetA = L.offsetBase + pos - 1 + if buf[pos+2] == '[': skipMultiLineComment(L, tok, pos+3, true) return @@ -996,6 +1001,8 @@ proc scanComment(L: var TLexer, tok: var TToken) = tokenEndIgnore(tok, pos) break L.bufpos = pos + when defined(nimpretty): + tok.commentOffsetB = L.offsetBase + pos - 1 proc skip(L: var TLexer, tok: var TToken) = var pos = L.bufpos @@ -1016,6 +1023,9 @@ proc skip(L: var TLexer, tok: var TToken) = inc(pos) of CR, LF: tokenEndPrevious(tok, pos) + when defined(nimpretty): + # we are not yet in a comment, so update the comment token's line information: + if not hasComment: inc tok.line pos = handleCRLF(L, pos) buf = L.buf var indent = 0 @@ -1055,7 +1065,7 @@ proc skip(L: var TLexer, tok: var TToken) = L.bufpos = pos when defined(nimpretty): if hasComment: - tok.commentOffsetB = L.offsetBase + pos + tok.commentOffsetB = L.offsetBase + pos - 1 tok.tokType = tkComment if gIndentationWidth <= 0: gIndentationWidth = tok.indent @@ -1210,3 +1220,15 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')') inc(L.bufpos) atTokenEnd() + +proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream; + cache: IdentCache; config: ConfigRef): int = + var lex: TLexer + var tok: TToken + initToken(tok) + openLexer(lex, fileIdx, inputstream, cache, config) + while true: + rawGetTok(lex, tok) + result = tok.indent + if result > 0 or tok.tokType == tkEof: break + closeLexer(lex) diff --git a/compiler/parser.nim b/compiler/parser.nim index aedee8538..f575f3d7e 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -29,6 +29,9 @@ when isMainModule: import llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos +when defined(nimpretty2): + import layouter + type TParser* = object # A TParser object represents a file that # is being parsed @@ -41,6 +44,8 @@ type inPragma*: int # Pragma level inSemiStmtList*: int emptyNode: PNode + when defined(nimpretty2): + em: Emitter SymbolMode = enum smNormal, smAllowNil, smAfterDot @@ -83,6 +88,11 @@ proc getTok(p: var TParser) = ## `tok` member. rawGetTok(p.lex, p.tok) p.hasProgress = true + when defined(nimpretty2): + emitTok(p.em, p.lex, p.tok) + while p.tok.tokType == tkComment: + rawGetTok(p.lex, p.tok) + emitTok(p.em, p.lex, p.tok) proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, cache: IdentCache; config: ConfigRef; @@ -91,6 +101,8 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, ## initToken(p.tok) openLexer(p.lex, fileIdx, inputStream, cache, config) + when defined(nimpretty2): + openEmitter(p.em, cache, config, fileIdx) getTok(p) # read the first token p.firstTok = true p.strongSpaces = strongSpaces @@ -104,6 +116,8 @@ proc openParser*(p: var TParser, filename: string, inputStream: PLLStream, proc closeParser(p: var TParser) = ## Close a parser, freeing up its resources. closeLexer(p.lex) + when defined(nimpretty2): + closeEmitter(p.em) proc parMessage(p: TParser, msg: TMsgKind, arg = "") = ## Produce and emit the parser message `arg` to output. @@ -394,6 +408,8 @@ proc exprColonEqExpr(p: var TParser): PNode = proc exprList(p: var TParser, endTok: TTokType, result: PNode) = #| exprList = expr ^+ comma + when defined(nimpretty2): + inc p.em.doIndentMore getTok(p) optInd(p, result) # progress guaranteed @@ -403,6 +419,8 @@ proc exprList(p: var TParser, endTok: TTokType, result: PNode) = if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) + when defined(nimpretty2): + dec p.em.doIndentMore proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) = assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi}) @@ -823,7 +841,11 @@ proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = result = parseOperators(p, result, limit, mode) proc simpleExpr(p: var TParser, mode = pmNormal): PNode = + when defined(nimpretty2): + inc p.em.doIndentMore result = simpleExprAux(p, -1, mode) + when defined(nimpretty2): + dec p.em.doIndentMore proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode = #| condExpr = expr colcom expr optInd @@ -898,8 +920,12 @@ proc parsePragma(p: var TParser): PNode = getTok(p) skipComment(p, a) optPar(p) - if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p) - else: parMessage(p, "expected '.}'") + if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: + when defined(nimpretty2): + if p.tok.tokType == tkCurlyRi: curlyRiWasPragma(p.em) + getTok(p) + else: + parMessage(p, "expected '.}'") dec p.inPragma proc identVis(p: var TParser; allowDot=false): PNode = @@ -907,6 +933,8 @@ proc identVis(p: var TParser; allowDot=false): PNode = #| identVisDot = symbol '.' optInd symbol opr? var a = parseSymbol(p) if p.tok.tokType == tkOpr: + when defined(nimpretty2): + starWasExportMarker(p.em) result = newNodeP(nkPostfix, p) addSon(result, newIdentNodeP(p.tok.ident, p)) addSon(result, a) @@ -984,6 +1012,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode = var a = parseIdentColonEquals(p, {}) addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) @@ -1018,6 +1048,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode = var a: PNode result = newNodeP(nkFormalParams, p) addSon(result, p.emptyNode) # return type + when defined(nimpretty2): + inc p.em.doIndentMore let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0 if hasParLe: getTok(p) @@ -1037,6 +1069,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode = break addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) @@ -1050,6 +1084,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode = 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(nimpretty2): + dec p.em.doIndentMore proc optPragmas(p: var TParser): PNode = if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)): @@ -1652,6 +1688,8 @@ proc parseGenericParamList(p: var TParser): PNode = var a = parseGenericParam(p) addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index bfb8e78eb..c78a3519c 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -223,9 +223,9 @@ proc isTurnedOn(c: PContext, n: PNode): bool = if x.kind == nkIntLit: return x.intVal != 0 localError(c.config, n.info, "'on' or 'off' expected") -proc onOff(c: PContext, n: PNode, op: TOptions) = - if isTurnedOn(c, n): c.config.options = c.config.options + op - else: c.config.options = c.config.options - op +proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) = + if isTurnedOn(c, n): resOptions = resOptions + op + else: resOptions = resOptions - op proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = if isTurnedOn(c, n): incl(c.module.flags, flag) @@ -313,54 +313,68 @@ proc processNote(c: PContext, n: PNode) = else: invalidPragma(c, n) -proc processOption(c: PContext, n: PNode): bool = - if n.kind notin nkPragmaCallKinds or n.len != 2: result = true +proc pragmaToOptions(w: TSpecialWord): TOptions {.inline.} = + case w + of wChecks: ChecksOptions + of wObjChecks: {optObjCheck} + of wFieldChecks: {optFieldCheck} + of wRangechecks: {optRangeCheck} + of wBoundchecks: {optBoundsCheck} + of wOverflowchecks: {optOverflowCheck} + of wNilchecks: {optNilCheck} + of wFloatchecks: {optNaNCheck, optInfCheck} + of wNanChecks: {optNaNCheck} + of wInfChecks: {optInfCheck} + of wMovechecks: {optMoveCheck} + of wAssertions: {optAssert} + of wWarnings: {optWarns} + of wHints: {optHints} + of wLinedir: {optLineDir} + of wStacktrace: {optStackTrace} + of wLinetrace: {optLineTrace} + of wDebugger: {optEndb} + of wProfiler: {optProfiler, optMemTracker} + of wMemTracker: {optMemTracker} + of wByRef: {optByRef} + of wImplicitStatic: {optImplicitStatic} + of wPatterns: {optPatterns} + else: {} + +proc tryProcessOption(c: PContext, n: PNode, resOptions: var TOptions): bool = + result = true + if n.kind notin nkPragmaCallKinds or n.len != 2: result = false elif n.sons[0].kind == nkBracketExpr: processNote(c, n) - elif n.sons[0].kind != nkIdent: result = true + elif n.sons[0].kind != nkIdent: result = false else: let sw = whichKeyword(n.sons[0].ident) - case sw - of wChecks: onOff(c, n, ChecksOptions) - of wObjChecks: onOff(c, n, {optObjCheck}) - of wFieldChecks: onOff(c, n, {optFieldCheck}) - of wRangechecks: onOff(c, n, {optRangeCheck}) - of wBoundchecks: onOff(c, n, {optBoundsCheck}) - of wOverflowchecks: onOff(c, n, {optOverflowCheck}) - of wNilchecks: onOff(c, n, {optNilCheck}) - of wFloatchecks: onOff(c, n, {optNaNCheck, optInfCheck}) - of wNanChecks: onOff(c, n, {optNaNCheck}) - of wInfChecks: onOff(c, n, {optInfCheck}) - of wMovechecks: onOff(c, n, {optMoveCheck}) - of wAssertions: onOff(c, n, {optAssert}) - of wWarnings: onOff(c, n, {optWarns}) - of wHints: onOff(c, n, {optHints}) - of wCallconv: processCallConv(c, n) - of wLinedir: onOff(c, n, {optLineDir}) - of wStacktrace: onOff(c, n, {optStackTrace}) - of wLinetrace: onOff(c, n, {optLineTrace}) - of wDebugger: onOff(c, n, {optEndb}) - of wProfiler: onOff(c, n, {optProfiler, optMemTracker}) - of wMemTracker: onOff(c, n, {optMemTracker}) - of wByRef: onOff(c, n, {optByRef}) - of wDynlib: processDynLib(c, n, nil) - of wOptimization: - if n.sons[1].kind != nkIdent: - invalidPragma(c, n) - else: - case n.sons[1].ident.s.normalize - of "speed": - incl(c.config.options, optOptimizeSpeed) - excl(c.config.options, optOptimizeSize) - of "size": - excl(c.config.options, optOptimizeSpeed) - incl(c.config.options, optOptimizeSize) - of "none": - excl(c.config.options, optOptimizeSpeed) - excl(c.config.options, optOptimizeSize) - else: localError(c.config, n.info, "'none', 'speed' or 'size' expected") - of wImplicitStatic: onOff(c, n, {optImplicitStatic}) - of wPatterns: onOff(c, n, {optPatterns}) - else: result = true + let opts = pragmaToOptions(sw) + if opts != {}: + onOff(c, n, opts, resOptions) + else: + case sw + of wCallconv: processCallConv(c, n) + of wDynlib: processDynLib(c, n, nil) + of wOptimization: + if n.sons[1].kind != nkIdent: + invalidPragma(c, n) + else: + case n.sons[1].ident.s.normalize + of "speed": + incl(resOptions, optOptimizeSpeed) + excl(resOptions, optOptimizeSize) + of "size": + excl(resOptions, optOptimizeSpeed) + incl(resOptions, optOptimizeSize) + of "none": + excl(resOptions, optOptimizeSpeed) + excl(resOptions, optOptimizeSize) + else: localError(c.config, n.info, "'none', 'speed' or 'size' expected") + else: result = false + +proc processOption(c: PContext, n: PNode, resOptions: var TOptions) = + if not tryProcessOption(c, n, resOptions): + # calling conventions (boring...): + localError(c.config, n.info, "option expected") proc processPush(c: PContext, n: PNode, start: int) = if n.sons[start-1].kind in nkPragmaCallKinds: @@ -373,7 +387,7 @@ proc processPush(c: PContext, n: PNode, start: int) = x.notes = c.config.notes c.optionStack.add(x) for i in countup(start, sonsLen(n) - 1): - if processOption(c, n.sons[i]): + if not tryProcessOption(c, n.sons[i], c.config.options): # simply store it somewhere: if x.otherPragmas.isNil: x.otherPragmas = newNodeI(nkPragma, n.info) @@ -964,13 +978,14 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wCodegenDecl: processCodegenDecl(c, it, sym) of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, - wLinedir, wStacktrace, wLinetrace, wOptimization, wMovechecks, - wCallconv, - wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks, - wPatterns: - if processOption(c, it): - # calling conventions (boring...): - localError(c.config, it.info, "option expected") + wLinedir, wOptimization, wMovechecks, wCallconv, wDebugger, wProfiler, + wFloatchecks, wNanChecks, wInfChecks, wPatterns: + processOption(c, it, c.config.options) + of wStacktrace, wLinetrace: + if sym.kind in {skProc, skMethod, skConverter}: + processOption(c, it, sym.options) + else: + processOption(c, it, c.config.options) of FirstCallConv..LastCallConv: assert(sym != nil) if sym.typ == nil: invalidPragma(c, it) @@ -1000,7 +1015,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wByRef: noVal(c, it) if sym == nil or sym.typ == nil: - if processOption(c, it): localError(c.config, it.info, "option expected") + processOption(c, it, c.config.options) else: incl(sym.typ.flags, tfByRef) of wByCopy: diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 1f5ab7a13..ba87838db 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -1453,11 +1453,12 @@ proc `$`*(n: PNode): string = n.renderTree proc renderModule*(n: PNode, infile, outfile: string, renderFlags: TRenderFlags = {}; - fid = FileIndex(-1)) = + fid = FileIndex(-1); + conf: ConfigRef = nil) = var f: File g: TSrcGen - initSrcGen(g, renderFlags, newPartialConfigRef()) + initSrcGen(g, renderFlags, conf) g.fid = fid for i in countup(0, sonsLen(n) - 1): gsub(g, n.sons[i]) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 439ef8fca..292238dc9 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1486,10 +1486,11 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, s.ast = n #s.scope = c.currentScope + s.options = c.config.options + # before compiling the proc body, set as current the scope # where the proc was declared let oldScope = c.currentScope - let oldOptions = c.config.options #c.currentScope = s.scope pushOwner(c, s) openScope(c) @@ -1558,6 +1559,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, addParams(c, proto.typ.n, proto.kind) proto.info = s.info # more accurate line information s.typ = proto.typ + proto.options = s.options s = proto n.sons[genericParamsPos] = proto.ast.sons[genericParamsPos] n.sons[paramsPos] = proto.ast.sons[paramsPos] @@ -1569,8 +1571,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, proto.ast = n # needed for code generation popOwner(c) pushOwner(c, s) - s.options = c.config.options - c.config.options = oldOptions if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) if s.name.s[0] in {'.', '('}: diff --git a/doc/tut1.rst b/doc/tut1.rst index f2251c463..aa6114cf7 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -208,7 +208,8 @@ Note that declaring multiple variables with a single assignment which calls a procedure can have unexpected results: the compiler will *unroll* the assignments and end up calling the procedure several times. If the result of the procedure depends on side effects, your variables may end up having -different values! For safety use only constant values. +different values! For safety use side-effect free procedures if making multiple +assignments. Constants @@ -642,7 +643,7 @@ initialisation. Parameters ---------- -Parameters are constant in the procedure body. By default, their value cannot be +Parameters are immutable in the procedure body. By default, their value cannot be changed because this allows the compiler to implement parameter passing in the most efficient way. If a mutable variable is needed inside the procedure, it has to be declared with ``var`` in the procedure body. Shadowing the parameter name diff --git a/koch.nim b/koch.nim index 4f85c6583..97e1da776 100644 --- a/koch.nim +++ b/koch.nim @@ -254,15 +254,13 @@ proc buildTool(toolname, args: string) = copyFile(dest="bin" / splitFile(toolname).name.exe, source=toolname.exe) proc buildTools(latest: bool) = - let nimsugExe = "bin/nimsuggest".exe - nimexec "c --noNimblePath -p:compiler -d:release -o:" & nimsugExe & + nimexec "c --noNimblePath -p:compiler -d:release -o:" & ("bin/nimsuggest".exe) & " nimsuggest/nimsuggest.nim" - let nimgrepExe = "bin/nimgrep".exe - nimexec "c -d:release -o:" & nimgrepExe & " tools/nimgrep.nim" + nimexec "c -d:release -o:" & ("bin/nimgrep".exe) & " tools/nimgrep.nim" when defined(windows): buildVccTool() - #nimexec "c -o:" & ("bin/nimresolve".exe) & " tools/nimresolve.nim" + nimexec "c -o:" & ("bin/nimpretty".exe) & " nimpretty/nimpretty.nim" buildNimble(latest) diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 39c5790ed..d16527a56 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -32,7 +32,7 @@ const ## can be captured. More subpatterns cannot be captured! type - PegKind = enum + PegKind* = enum pkEmpty, pkAny, ## any character (.) pkAnyRune, ## any Unicode character (_) @@ -67,7 +67,7 @@ type pkRule, ## a <- b pkList, ## a, b pkStartAnchor ## ^ --> Internal DSL: startAnchor() - NonTerminalFlag = enum + NonTerminalFlag* = enum ntDeclared, ntUsed NonTerminalObj = object ## represents a non terminal symbol name: string ## the name of the symbol @@ -86,6 +86,25 @@ type else: sons: seq[Peg] NonTerminal* = ref NonTerminalObj +proc name*(nt: NonTerminal): string = nt.name +proc line*(nt: NonTerminal): int = nt.line +proc col*(nt: NonTerminal): int = nt.col +proc flags*(nt: NonTerminal): set[NonTerminalFlag] = nt.flags +proc rule*(nt: NonTerminal): Peg = nt.rule + +proc kind*(p: Peg): PegKind = p.kind +proc term*(p: Peg): string = p.term +proc ch*(p: Peg): char = p.ch +proc charChoice*(p: Peg): ref set[char] = p.charChoice +proc nt*(p: Peg): NonTerminal = p.nt +proc index*(p: Peg): range[0..MaxSubpatterns] = p.index +iterator items*(p: Peg): Peg {.inline.} = + for s in p.sons: + yield s +iterator pairs*(p: Peg): (int, Peg) {.inline.} = + for i in 0 ..< p.sons.len: + yield (i, p.sons[i]) + proc term*(t: string): Peg {.nosideEffect, rtl, extern: "npegs$1Str".} = ## constructs a PEG from a terminal string if t.len != 1: diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 5de013c26..ab34a0b2d 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -17,6 +17,10 @@ import parseutils from math import pow, round, floor, log10 from algorithm import reverse +when defined(nimVmExportFixed): + from unicode import toLower, toUpper + export toLower, toUpper + {.deadCodeElim: on.} # dce option deprecated {.push debugger:off .} # the user does not want to trace a part diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index fcca4d5d7..7ad243150 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -467,16 +467,18 @@ proc resetAttributes*(f: File) = f.write(ansiResetCode) type - Style* = enum ## different styles for text output + Style* = enum ## different styles for text output styleBright = 1, ## bright text styleDim, ## dim text - styleUnknown, ## unknown + styleItalic, ## italic (or reverse on terminals not supporting) styleUnderscore = 4, ## underscored text styleBlink, ## blinking/bold text - styleReverse = 7, ## unknown + styleReverse = 7, ## reverse styleHidden ## hidden text + styleStrikethrough, ## strikethrough {.deprecated: [TStyle: Style].} +{.deprecated: [styleUnknown: styleItalic].} when not defined(windows): var @@ -690,8 +692,8 @@ template styledEchoProcessArg(f: File, cmd: TerminalCmd) = when cmd == bgColor: fgSetColor = false -macro styledWriteLine*(f: File, m: varargs[typed]): untyped = - ## Similar to ``writeLine``, but treating terminal style arguments specially. +macro styledWrite*(f: File, m: varargs[typed]): untyped = + ## Similar to ``write``, but treating terminal style arguments specially. ## When some argument is ``Style``, ``set[Style]``, ``ForegroundColor``, ## ``BackgroundColor`` or ``TerminalCmd`` then it is not sent directly to ## ``f``, but instead corresponding terminal style proc is called. @@ -700,8 +702,8 @@ macro styledWriteLine*(f: File, m: varargs[typed]): untyped = ## ## .. code-block:: nim ## - ## proc error(msg: string) = - ## styledWriteLine(stderr, fgRed, "Error: ", resetStyle, msg) + ## stdout.styledWrite(fgRed, "red text ") + ## stdout.styledWrite(fgGreen, "green text") ## let m = callsite() var reset = false @@ -712,8 +714,8 @@ macro styledWriteLine*(f: File, m: varargs[typed]): untyped = case item.kind of nnkStrLit..nnkTripleStrLit: if i == m.len - 1: - # optimize if string literal is last, just call writeLine - result.add(newCall(bindSym"writeLine", f, item)) + # optimize if string literal is last, just call write + result.add(newCall(bindSym"write", f, item)) if reset: result.add(newCall(bindSym"resetAttributes", f)) return else: @@ -722,16 +724,24 @@ macro styledWriteLine*(f: File, m: varargs[typed]): untyped = else: result.add(newCall(bindSym"styledEchoProcessArg", f, item)) reset = true - - result.add(newCall(bindSym"write", f, newStrLitNode("\n"))) if reset: result.add(newCall(bindSym"resetAttributes", f)) -macro styledEcho*(args: varargs[untyped]): untyped = +template styledWriteLine*(f: File, args: varargs[untyped]) = + ## Calls ``styledWrite`` and appends a newline at the end. + ## + ## Example: + ## + ## .. code-block:: nim + ## + ## proc error(msg: string) = + ## styledWriteLine(stderr, fgRed, "Error: ", resetStyle, msg) + ## + styledWrite(f, args) + write(f, "\n") + +template styledEcho*(args: varargs[untyped]) = ## Echoes styles arguments to stdout using ``styledWriteLine``. - result = newCall(bindSym"styledWriteLine") - result.add(bindSym"stdout") - for arg in children(args): - result.add(arg) + stdout.styledWriteLine(args) proc getch*(): char = ## Read a single character from the terminal, blocking until it is entered. @@ -779,7 +789,7 @@ when defined(windows): inc i, x password.string.setLen(max(password.len - x, 0)) of chr(0x0): - # modifier key - ignore - for details see + # modifier key - ignore - for details see # https://github.com/nim-lang/Nim/issues/7764 continue else: @@ -838,16 +848,6 @@ proc resetAttributes*() {.noconv.} = ## ``system.addQuitProc(resetAttributes)``. resetAttributes(stdout) -when not defined(testing) and isMainModule: - #system.addQuitProc(resetAttributes) - write(stdout, "never mind") - stdout.eraseLine() - stdout.styledWriteLine("styled text ", {styleBright, styleBlink, styleUnderscore}) - stdout.setBackGroundColor(bgCyan, true) - stdout.setForeGroundColor(fgBlue) - stdout.writeLine("ordinary text") - stdout.resetAttributes() - proc isTrueColorSupported*(): bool = ## Returns true if a terminal supports true color. return trueColorIsSupported @@ -898,3 +898,40 @@ proc disableTrueColors*() = trueColorIsEnabled = false else: trueColorIsEnabled = false + +when not defined(testing) and isMainModule: + #system.addQuitProc(resetAttributes) + write(stdout, "never mind") + stdout.eraseLine() + stdout.styledWriteLine({styleBright, styleBlink, styleUnderscore}, "styled text ") + stdout.styledWriteLine("italic text ", {styleItalic}) + stdout.setBackGroundColor(bgCyan, true) + stdout.setForeGroundColor(fgBlue) + stdout.write("blue text in cyan background") + stdout.resetAttributes() + echo "" + stdout.writeLine("ordinary text") + echo "more ordinary text" + styledEcho styleBright, fgGreen, "[PASS]", resetStyle, fgGreen, " Yay!" + echo "ordinary text again" + styledEcho styleBright, fgRed, "[FAIL]", resetStyle, fgRed, " Nay :(" + echo "ordinary text again" + setForeGroundColor(fgGreen) + echo "green text" + echo "more green text" + setForeGroundColor(fgBlue) + echo "blue text" + resetAttributes() + echo "ordinary text" + + stdout.styledWriteLine(fgRed, "red text ") + stdout.styledWriteLine(fgWhite, bgRed, "white text in red background") + stdout.styledWriteLine(" ordinary text ") + stdout.styledWriteLine(fgGreen, "green text") + + stdout.styledWrite(fgRed, "red text ") + stdout.styledWrite(fgWhite, bgRed, "white text in red background") + stdout.styledWrite(" ordinary text ") + stdout.styledWrite(fgGreen, "green text") + echo "" + echo "ordinary text" diff --git a/tools/nimpretty.nim b/nimpretty/nimpretty.nim index 386eddfde..aa9756c45 100644 --- a/tools/nimpretty.nim +++ b/nimpretty/nimpretty.nim @@ -25,6 +25,7 @@ Usage: nimpretty [options] file.nim Options: --backup:on|off create a backup file before overwritting (default: ON) + --output:file set the output file (default: overwrite the .nim file) --version show the version --help show this help """ @@ -39,15 +40,18 @@ proc writeVersion() = stdout.flushFile() quit(0) -proc prettyPrint(infile: string) = - let conf = newConfigRef() +proc prettyPrint(infile, outfile: string) = + var conf = newConfigRef() let fileIdx = fileInfoIdx(conf, infile) - let tree = parseFile(fileIdx, newIdentCache(), conf) - let outfile = changeFileExt(infile, ".pretty.nim") - renderModule(tree, infile, outfile, {}, fileIdx) + conf.outFile = outfile + when defined(nimpretty2): + discard parseFile(fileIdx, newIdentCache(), conf) + else: + let tree = parseFile(fileIdx, newIdentCache(), conf) + renderModule(tree, infile, outfile, {}, fileIdx, conf) proc main = - var infile: string + var infile, outfile: string var backup = true for kind, key, val in getopt(): case kind @@ -58,12 +62,14 @@ proc main = of "help", "h": writeHelp() of "version", "v": writeVersion() of "backup": backup = parseBool(val) + of "output", "o": outfile = val else: writeHelp() of cmdEnd: assert(false) # cannot happen if infile.len == 0: quit "[Error] no input file." if backup: os.copyFile(source=infile, dest=changeFileExt(infile, ".nim.backup")) - prettyPrint(infile) + if outfile.len == 0: outfile = infile + prettyPrint(infile, outfile) main() diff --git a/nimpretty/nimpretty.nim.cfg b/nimpretty/nimpretty.nim.cfg new file mode 100644 index 000000000..5fafa6d2a --- /dev/null +++ b/nimpretty/nimpretty.nim.cfg @@ -0,0 +1,2 @@ +--define: nimpretty +--define: nimpretty2 diff --git a/nimpretty/tester.nim b/nimpretty/tester.nim new file mode 100644 index 000000000..8798ce06a --- /dev/null +++ b/nimpretty/tester.nim @@ -0,0 +1,29 @@ +# Small program that runs the test cases + +import strutils, os + +const + dir = "nimpretty/tests/" + +var + failures = 0 + +proc test(infile, outfile: string) = + if execShellCmd("nimpretty -o:$2 --backup:off $1" % [infile, outfile]) != 0: + quit("FAILURE") + let nimFile = splitFile(infile).name + let expected = dir / "expected" / nimFile & ".nim" + let produced = dir / nimFile & ".pretty" + if strip(readFile(expected)) != strip(readFile(produced)): + echo "FAILURE: files differ: ", nimFile + discard execShellCmd("diff -uNdr " & expected & " " & produced) + failures += 1 + else: + echo "SUCCESS: files identical: ", nimFile + +for t in walkFiles(dir / "*.nim"): + let res = t.changeFileExt("pretty") + test(t, res) + removeFile(res) + +if failures > 0: quit($failures & " failures occurred.") diff --git a/nimpretty/tests/exhaustive.nim b/nimpretty/tests/exhaustive.nim new file mode 100644 index 000000000..9f2141fbb --- /dev/null +++ b/nimpretty/tests/exhaustive.nim @@ -0,0 +1,316 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +import verylongnamehere,verylongnamehere,verylongnamehereverylongnamehereverylong,namehere,verylongnamehere + +proc `[]=`() = discard "index setter" +proc `putter=`() = discard cast[pointer](cast[int](buffer) + size) + +(not false) + +let expr = if true: "true" else: "false" + +var body = newNimNode(nnkIfExpr).add( + newNimNode(nnkElifBranch).add( + infix(newDotExpr(ident("a"), ident("kind")), "==", newDotExpr(ident("b"), ident("kind"))), + condition + ), + newNimNode(nnkElse).add(newStmtList(newNimNode(nnkReturnStmt).add(ident("false")))) +) + +# comment + +var x = 1 + +type + GeneralTokenizer* = object of RootObj ## comment here + kind*: TokenClass ## and here + start*, length*: int ## you know how it goes... + buf: cstring + pos: int # other comment here. + state: TokenClass + +var x*: string +var y: seq[string] #[ yay inline comments. So nice I have to care bout these. ]# + +echo "#", x, "##", y, "#" & "string" & $test + +echo (tup, here) +echo(argA, argB) + +import macros + +## A documentation comment here. +## That spans multiple lines. +## And is not to be touched. + +const numbers = [4u8, 5'u16, 89898_00] + +macro m(n): untyped = + result = foo"string literal" + +{.push m.} +proc p() = echo "p", 1+4 * 5, if true: 5 else: 6 +proc q(param: var ref ptr string) = + p() + if true: + echo a and b or not c and not -d +{.pop.} + +q() + +when false: + # bug #4766 + type + Plain = ref object + discard + + Wrapped[T] = object + value: T + + converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + + let result = Plain() + discard $result + +when false: + # bug #3670 + template someTempl(someConst: bool) = + when someConst: + var a: int + if true: + when not someConst: + var a: int + a = 5 + + someTempl(true) + + +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. Still primitive but useful. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + Emitter* = object + f: PLLStream + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote {.pragmaHereWrongCurlyEnd}: bool + col, lastLineNumber, lineSpan, indentLevel: int + content: string + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*[T, S](em: var Emitter, config: ConfigRef, fileIdx: FileIndex) {.pragmaHereWrongCurlyEnd} = + let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim") + em.f = llStreamOpen(outfile, fmWrite) + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + if em.f == nil: + rawMessage(config, errGenerated, "cannot open file: " & outfile) + +proc closeEmitter*(em: var Emitter) {.inline.} = + em.f.llStreamWrite em.content + llStreamClose(em.f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i+1] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +proc softLinebreak(em: var Emitter, lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+2: wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + let ws = "\L" & repeat(' ',em.indentLevel+2) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len > 0 and em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: 90 + else: + "case returns value" + + + if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + em.indentLevel = tok.indent + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..tok.indent: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: discard + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, + tkComma: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + rememberSplit(splitComma) + of tkParLe, tkParRi, tkBracketLe, + tkBracketRi, tkCurlyLe, tkCurlyRi, + tkBracketDotLe, tkBracketDotRi, + tkCurlyDotLe, tkCurlyDotRi, + tkParDotLe, tkParDotRi, + tkColonColon, tkDot, tkBracketLeColon: + wr(TokTypeToStr[tok.tokType]) + if tok.tokType in splitters: + rememberSplit(splitParLe) + of tkEquals: + if not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkOpr, tkDotDot: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}: + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr(" ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +type + Thing = ref object + grade: string + # this name is great + name: string + +proc f() = + var c: char + var str: string + if c == '\\': + # escape char + str &= c diff --git a/nimpretty/tests/expected/exhaustive.nim b/nimpretty/tests/expected/exhaustive.nim new file mode 100644 index 000000000..95071fce3 --- /dev/null +++ b/nimpretty/tests/expected/exhaustive.nim @@ -0,0 +1,325 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +import verylongnamehere, verylongnamehere, + verylongnamehereverylongnamehereverylong, namehere, verylongnamehere + +proc `[]=`() = discard "index setter" +proc `putter=`() = discard cast[pointer](cast[int](buffer) + size) + +(not false) + +let expr = if true: "true" else: "false" + +var body = newNimNode(nnkIfExpr).add( + newNimNode(nnkElifBranch).add( + infix(newDotExpr(ident("a"), ident("kind")), "==", newDotExpr(ident("b"), + ident("kind"))), + condition + ), + newNimNode(nnkElse).add(newStmtList(newNimNode(nnkReturnStmt).add(ident( + "false")))) +) + +# comment + +var x = 1 + +type + GeneralTokenizer* = object of RootObj ## comment here + kind*: TokenClass ## and here + start*, length*: int ## you know how it goes... + buf: cstring + pos: int # other comment here. + state: TokenClass + +var x*: string +var y: seq[string] #[ yay inline comments. So nice I have to care bout these. ]# + +echo "#", x, "##", y, "#" & "string" & $test + +echo (tup, here) +echo(argA, argB) + +import macros + +## A documentation comment here. +## That spans multiple lines. +## And is not to be touched. + +const numbers = [4u8, 5'u16, 89898_00] + +macro m(n): untyped = + result = foo"string literal" + +{.push m.} +proc p() = echo "p", 1+4 * 5, if true: 5 else: 6 +proc q(param: var ref ptr string) = + p() + if true: + echo a and b or not c and not -d +{.pop.} + +q() + +when false: + # bug #4766 + type + Plain = ref object + discard + + Wrapped[T] = object + value: T + + converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + + let result = Plain() + discard $result + +when false: + # bug #3670 + template someTempl(someConst: bool) = + when someConst: + var a: int + if true: + when not someConst: + var a: int + a = 5 + + someTempl(true) + + +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. Still primitive but useful. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + Emitter* = object + f: PLLStream + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote {.pragmaHereWrongCurlyEnd.}: bool + col, lastLineNumber, lineSpan, indentLevel: int + content: string + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*[T, S](em: var Emitter; config: ConfigRef; + fileIdx: FileIndex) {.pragmaHereWrongCurlyEnd.} = + let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim") + em.f = llStreamOpen(outfile, fmWrite) + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + if em.f == nil: + rawMessage(config, errGenerated, "cannot open file: " & outfile) + +proc closeEmitter*(em: var Emitter) {.inline.} = + em.f.llStreamWrite em.content + llStreamClose(em.f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i+1] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +proc softLinebreak(em: var Emitter; lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+2: wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + let ws = "\L" & repeat(' ', em.indentLevel+2) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len > 0 and em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, + tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, + LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: 90 + else: + "case returns value" + + + if tok.tokType == tkComment and tok.line == em.lastLineNumber and + tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + em.indentLevel = tok.indent + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..tok.indent: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: discard + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, + tkComma: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + rememberSplit(splitComma) + of tkParLe, tkParRi, tkBracketLe, + tkBracketRi, tkCurlyLe, tkCurlyRi, + tkBracketDotLe, tkBracketDotRi, + tkCurlyDotLe, tkCurlyDotRi, + tkParDotLe, tkParDotRi, + tkColonColon, tkDot, tkBracketLeColon: + wr(TokTypeToStr[tok.tokType]) + if tok.tokType in splitters: + rememberSplit(splitParLe) + of tkEquals: + if not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkOpr, tkDotDot: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}: + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, + tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr( + " ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +type + Thing = ref object + grade: string + # this name is great + name: string + +proc f() = + var c: char + var str: string + if c == '\\': + # escape char + str &= c diff --git a/tests/niminaction/Chapter1/various1.nim b/tests/niminaction/Chapter1/various1.nim new file mode 100644 index 000000000..688180fd2 --- /dev/null +++ b/tests/niminaction/Chapter1/various1.nim @@ -0,0 +1,45 @@ +discard """ + exitCode: 0 + outputsub: "Woof!" +""" + +import strutils +echo("hello".to_upper()) +echo("world".toUpper()) + +type + Dog = object #<1> + age: int #<2> + +let dog = Dog(age: 3) #<3> + +proc showNumber(num: int | float) = + echo(num) + +showNumber(3.14) +showNumber(42) + +for i in 0 .. <10: + echo(i) + +block: # Block added due to clash. + type + Dog = object + + proc bark(self: Dog) = #<1> + echo("Woof!") + + let dog = Dog() + dog.bark() #<2> + +import sequtils, future, strutils +let list = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +list.map( + (x: string) -> (string, string) => (x.split[0], x.split[1]) +).echo + +import strutils +let list1 = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +for name in list1: + echo((name.split[0], name.split[1])) + diff --git a/tests/niminaction/Chapter2/explicit_discard.nim b/tests/niminaction/Chapter2/explicit_discard.nim new file mode 100644 index 000000000..3e94c335b --- /dev/null +++ b/tests/niminaction/Chapter2/explicit_discard.nim @@ -0,0 +1,7 @@ +discard """ + line: 7 + errormsg: "has to be discarded" +""" + +proc myProc(name: string): string = "Hello " & name +myProc("Dominik") \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_def_eq.nim b/tests/niminaction/Chapter2/no_def_eq.nim new file mode 100644 index 000000000..77f0a7dd8 --- /dev/null +++ b/tests/niminaction/Chapter2/no_def_eq.nim @@ -0,0 +1,16 @@ +discard """ + line: 16 + errormsg: "type mismatch" +""" + +type + Dog = object + name: string + + Cat = object + name: string + +let dog: Dog = Dog(name: "Fluffy") +let cat: Cat = Cat(name: "Fluffy") + +echo(dog == cat) \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_iterator.nim b/tests/niminaction/Chapter2/no_iterator.nim new file mode 100644 index 000000000..331d69480 --- /dev/null +++ b/tests/niminaction/Chapter2/no_iterator.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "type mismatch" +""" + +for i in 5: + echo i \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_seq_type.nim b/tests/niminaction/Chapter2/no_seq_type.nim new file mode 100644 index 000000000..493be270a --- /dev/null +++ b/tests/niminaction/Chapter2/no_seq_type.nim @@ -0,0 +1,6 @@ +discard """ + line: 6 + errormsg: "cannot infer the type of the sequence" +""" + +var list = @[] \ No newline at end of file diff --git a/tests/niminaction/Chapter2/resultaccept.nim b/tests/niminaction/Chapter2/resultaccept.nim new file mode 100644 index 000000000..7dd976b40 --- /dev/null +++ b/tests/niminaction/Chapter2/resultaccept.nim @@ -0,0 +1,28 @@ +discard """ + output: "" +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +doAssert implicit() == "I will be returned" +doAssert discarded() == nil +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" \ No newline at end of file diff --git a/tests/niminaction/Chapter2/resultreject.nim b/tests/niminaction/Chapter2/resultreject.nim new file mode 100644 index 000000000..de59af7d9 --- /dev/null +++ b/tests/niminaction/Chapter2/resultreject.nim @@ -0,0 +1,33 @@ +discard """ + line: 27 + errormsg: "has to be discarded" +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +proc resultVar3: string = + result = "I am the result" + "I will cause an error" + +doAssert implicit() == "I will be returned" +doAssert discarded() == nil +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" \ No newline at end of file diff --git a/tests/niminaction/Chapter2/various2.nim b/tests/niminaction/Chapter2/various2.nim new file mode 100644 index 000000000..3f6a3f453 --- /dev/null +++ b/tests/niminaction/Chapter2/various2.nim @@ -0,0 +1,369 @@ +discard """ + exitCode: 0 + outputsub: '''42 is greater than 0''' +""" + +if 42 >= 0: + echo "42 is greater than 0" + + +echo("Output: ", + 5) +echo(5 + + 5) +# --- Removed code that is supposed to fail here. Not going to test those. --- + +# Single-line comment +#[ +Multiline comment +]# +when false: + echo("Commented-out code") + +let decimal = 42 +let hex = 0x42 +let octal = 0o42 +let binary = 0b101010 + +let a: int16 = 42 +let b = 42'i8 + +let c = 1'f32 # --- Changed names here to avoid clashes --- +let d = 1.0e19 + +let e = false +let f = true + +let g = 'A' +let h = '\109' +let i = '\x79' + +let text = "The book title is \"Nim in Action\"" + +let filepath = "C:\\Program Files\\Nim" + +# --- Changed name here to avoid clashes --- +let filepath1 = r"C:\Program Files\Nim" + +let multiLine = """foo + bar + baz +""" +echo multiLine + +import strutils +# --- Changed name here to avoid clashes --- +let multiLine1 = """foo + bar + baz +""" +echo multiLine1.unindent +doAssert multiLine1.unindent == "foo\nbar\nbaz\n" + +proc fillString(): string = + result = "" + echo("Generating string") + for i in 0 .. 4: + result.add($i) #<1> + +const count = fillString() + +var + text1 = "hello" + number: int = 10 + isTrue = false + +var 火 = "Fire" +let ogień = true + +var `var` = "Hello" +echo(`var`) + +proc myProc(name: string): string = "Hello " & name +discard myProc("Dominik") + +proc bar(): int #<1> + +proc foo(): float = bar().float +proc bar(): int = foo().int + +proc noReturn() = echo("Hello") +proc noReturn2(): void = echo("Hello") + +proc noReturn3 = echo("Hello") + +proc message(recipient: string): auto = + "Hello " & recipient + +doAssert message("Dom") == "Hello Dom" + +proc max(a: int, b: int): int = + if a > b: a else: b + +doAssert max(5, 10) == 10 + +proc max2(a, b: int): int = + if a > b: a else: b + +proc genHello(name: string, surname = "Doe"): string = + "Hello " & name & " " & surname + +# -- Leaving these as asserts as that is in the original code, just in case +# -- somehow in the future `assert` is removed :) +assert genHello("Peter") == "Hello Peter Doe" +assert genHello("Peter", "Smith") == "Hello Peter Smith" + +proc genHello2(names: varargs[string]): string = + result = "" + for name in names: + result.add("Hello " & name & "\n") + +doAssert genHello2("John", "Bob") == "Hello John\nHello Bob\n" + +proc getUserCity(firstName, lastName: string): string = + case firstName + of "Damien": return "Tokyo" + of "Alex": return "New York" + else: return "Unknown" + +proc getUserCity(userID: int): string = + case userID + of 1: return "Tokyo" + of 2: return "New York" + else: return "Unknown" + +doAssert getUserCity("Damien", "Lundi") == "Tokyo" +doAssert getUserCity(2) == "New York" # -- Errata here: missing closing " + +import sequtils +let numbers = @[1, 2, 3, 4, 5, 6] +let odd = filter(numbers, proc (x: int): bool = x mod 2 != 0) +doAssert odd == @[1, 3, 5] + +import sequtils, future +let numbers1 = @[1, 2, 3, 4, 5, 6] +let odd1 = filter(numbers1, (x: int) -> bool => x mod 2 != 0) +assert odd1 == @[1, 3, 5] + +proc isValid(x: int, validator: proc (x: int): bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +import future +proc isValid2(x: int, validator: (x: int) -> bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +var list: array[3, int] +list[0] = 1 +list[1] = 42 +assert list[0] == 1 +assert list[1] == 42 +assert list[2] == 0 #<1> + +echo list.repr #<2> + +# echo list[500] + +var list2: array[-10 .. -9, int] +list2[-10] = 1 +list2[-9] = 2 + +var list3 = ["Hi", "There"] + +var list4 = ["My", "name", "is", "Dominik"] +for item in list4: + echo(item) + +for i in list4.low .. list4.high: + echo(list4[i]) + +var list5: seq[int] = @[] +doAssertRaises(IndexError): + list5[0] = 1 + +list5.add(1) + +assert list5[0] == 1 +doAssertRaises(IndexError): + echo list5[42] + +# -- Errata: var list: seq[int]; echo(list[0]). This now creates an exception, +# -- not a SIGSEGV. + +block: + var list = newSeq[string](3) + assert list[0] == nil + list[0] = "Foo" + list[1] = "Bar" + list[2] = "Baz" + + list.add("Lorem") + +block: + let list = @[4, 8, 15, 16, 23, 42] + for i in 0 .. <list.len: + stdout.write($list[i] & " ") + +var collection: set[int16] +doAssert collection == {} + +block: + let collection = {'a', 'x', 'r'} + doAssert 'a' in collection + +block: + let collection = {'a', 'T', 'z'} + let isAllLowerCase = {'A' .. 'Z'} * collection == {} + doAssert(not isAllLowerCase) + +let age = 10 +if age > 0 and age <= 10: + echo("You're still a child") +elif age > 10 and age < 18: + echo("You're a teenager") +else: + echo("You're an adult") + +let variable = "Arthur" +case variable +of "Arthur", "Zaphod", "Ford": + echo("Male") +of "Marvin": + echo("Robot") +of "Trillian": + echo("Female") +else: + echo("Unknown") + +let ageDesc = if age < 18: "Non-Adult" else: "Adult" + +block: + var i = 0 + while i < 3: + echo(i) + i.inc + +block label: + var i = 0 + while true: + while i < 5: + if i > 3: break label + i.inc + +iterator values(): int = + var i = 0 + while i < 5: + yield i + i.inc + +for value in values(): + echo(value) + +import os +for filename in walkFiles("*.nim"): + echo(filename) + +for item in @[1, 2, 3]: + echo(item) + +for i, value in @[1, 2, 3]: echo("Value at ", i, ": ", value) + +doAssertRaises(IOError): + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + second() + + first() + +block: + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + try: + second() + except: + echo("Cannot perform second action because: " & + getCurrentExceptionMsg()) + + first() + +block: + type + Person = object + name: string + age: int + + var person: Person + var person1 = Person(name: "Neo", age: 28) + +block: + type + PersonObj = object + name: string + age: int + PersonRef = ref PersonObj + + # proc setName(person: PersonObj) = + # person.name = "George" + + proc setName(person: PersonRef) = + person.name = "George" + +block: + type + Dog = object + name: string + + Cat = object + name: string + + let dog: Dog = Dog(name: "Fluffy") + let cat: Cat = Cat(name: "Fluffy") + +block: + type + Dog = tuple + name: string + + Cat = tuple + name: string + + let dog: Dog = (name: "Fluffy") + let cat: Cat = (name: "Fluffy") + + echo(dog == cat) + +block: + type + Point = tuple[x, y: int] + Point2 = (int, int) + + let pos: Point = (x: 100, y: 50) + doAssert pos == (100, 50) + + let pos1: Point = (x: 100, y: 50) + let (x, y) = pos1 #<1> + let (left, _) = pos1 + doAssert x == pos1[0] + doAssert y == pos1[1] + doAssert left == x + +block: + type + Color = enum + colRed, + colGreen, + colBlue + + let color: Color = colRed + +block: + type + Color {.pure.} = enum + red, green, blue + + let color = Color.red \ No newline at end of file diff --git a/tests/niminaction/Chapter3/various3.nim b/tests/niminaction/Chapter3/various3.nim new file mode 100644 index 000000000..478229b00 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim @@ -0,0 +1,93 @@ +import threadpool +proc foo: string = "Dog" +var x: FlowVar[string] = spawn foo() +assert(^x == "Dog") + +block: + type + Box = object + case empty: bool + of false: + contents: string + else: + discard + + var obj = Box(empty: false, contents: "Hello") + assert obj.contents == "Hello" + + var obj2 = Box(empty: true) + doAssertRaises(FieldError): + echo(obj2.contents) + +import json +assert parseJson("null").kind == JNull +assert parseJson("true").kind == JBool +assert parseJson("42").kind == JInt +assert parseJson("3.14").kind == JFloat +assert parseJson("\"Hi\"").kind == JString +assert parseJson("""{ "key": "value" }""").kind == JObject +assert parseJson("[1, 2, 3, 4]").kind == JArray + +import json +let data = """ + {"username": "Dominik"} +""" + +let obj = parseJson(data) +assert obj.kind == JObject +assert obj["username"].kind == JString +assert obj["username"].str == "Dominik" + +block: + proc count10(): int = + for i in 0 .. <10: + result.inc + assert count10() == 10 + +type + Point = tuple[x, y: int] + +var point = (5, 10) +var point2 = (x: 5, y: 10) + +type + Human = object + name: string + age: int + +var jeff = Human(name: "Jeff", age: 23) +var amy = Human(name: "Amy", age: 20) + +import asyncdispatch + +var future = newFuture[int]() +doAssert(not future.finished) + +future.callback = + proc (future: Future[int]) = + echo("Future is no longer empty, ", future.read) + +future.complete(42) + +import asyncdispatch, asyncfile + +when false: + var file = openAsync("") + let dataFut = file.readAll() + dataFut.callback = + proc (future: Future[string]) = + echo(future.read()) + + asyncdispatch.runForever() + +import asyncdispatch, asyncfile, os + +proc readFiles() {.async.} = + # --- Changed to getTempDir here. + var file = openAsync(getTempDir() / "test.txt", fmReadWrite) + let data = await file.readAll() + echo(data) + await file.write("Hello!\n") + +waitFor readFiles() + diff --git a/tests/niminaction/Chapter3/various3.nim.cfg b/tests/niminaction/Chapter3/various3.nim.cfg new file mode 100644 index 000000000..6c1ded992 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim.cfg @@ -0,0 +1 @@ +threads:on \ No newline at end of file diff --git a/tests/overflw/toverflw.nim b/tests/overflw/toverflw.nim index 771a43303..20bc56a53 100644 --- a/tests/overflw/toverflw.nim +++ b/tests/overflw/toverflw.nim @@ -1,21 +1,84 @@ discard """ file: "toverflw.nim" - output: "the computation overflowed" + output: "ok" + cmd: "nim $target -d:release $options $file" + """ # Tests nim's ability to detect overflows {.push overflowChecks: on.} var - a, b: int -a = high(int) -b = -2 + a = high(int) + b = -2 + overflowDetected = false + try: writeLine(stdout, b - a) except OverflowError: - writeLine(stdout, "the computation overflowed") + overflowDetected = true {.pop.} # overflow check -#OUT the computation overflowed + +doAssert(overflowDetected) + +block: # Overflow checks in a proc + var + a = high(int) + b = -2 + overflowDetected = false + + {.push overflowChecks: on.} + proc foo() = + let c = b - a + {.pop.} + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(overflowDetected) + +block: # Overflow checks in a forward declared proc + var + a = high(int) + b = -2 + overflowDetected = false + + proc foo() + + {.push overflowChecks: on.} + proc foo() = + let c = b - a + {.pop.} + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(overflowDetected) + +block: # Overflow checks doesn't affect fwd declaration + var + a = high(int) + b = -2 + overflowDetected = false + + {.push overflowChecks: on.} + proc foo() + {.pop.} + + proc foo() = + let c = b - a + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(not overflowDetected) +echo "ok" diff --git a/tests/stdlib/tpegs.nim b/tests/stdlib/tpegs.nim new file mode 100644 index 000000000..e5b709a66 --- /dev/null +++ b/tests/stdlib/tpegs.nim @@ -0,0 +1,78 @@ +discard """ + output: ''' +pkNonTerminal: Sum @(2, 3) + pkSequence: (Product (('+' / '-') Product)*) + pkNonTerminal: Product @(3, 7) + pkSequence: (Value (('*' / '/') Value)*) + pkNonTerminal: Value @(4, 5) + pkOrderedChoice: (([0-9] [0-9]*) / ('(' Expr ')')) + pkSequence: ([0-9] [0-9]*) + pkCharChoice: [0-9] + pkGreedyRepSet: [0-9]* + pkSequence: ('(' Expr ')') + pkChar: '(' + pkNonTerminal: Expr @(1, 4) + pkNonTerminal: Sum @(2, 3) + pkChar: ')' + pkGreedyRep: (('*' / '/') Value)* + pkSequence: (('*' / '/') Value) + pkOrderedChoice: ('*' / '/') + pkChar: '*' + pkChar: '/' + pkNonTerminal: Value @(4, 5) + pkGreedyRep: (('+' / '-') Product)* + pkSequence: (('+' / '-') Product) + pkOrderedChoice: ('+' / '-') + pkChar: '+' + pkChar: '-' + pkNonTerminal: Product @(3, 7) +''' +""" + +import strutils, streams +import pegs + +const + indent = " " + +let + pegSrc = """ +Expr <- Sum +Sum <- Product (('+' / '-') Product)* +Product <- Value (('*' / '/') Value)* +Value <- [0-9]+ / '(' Expr ')' + """ + pegAst: Peg = pegSrc.peg + +var + outp = newStringStream() + processed: seq[string] = @[] + +proc prt(outp: Stream, kind: PegKind, s: string; level: int = 0) = + outp.writeLine indent.repeat(level) & "$1: $2" % [$kind, s] + +proc recLoop(p: Peg, level: int = 0) = + case p.kind + of pkEmpty..pkWhitespace: + discard + of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: + outp.prt(p.kind, $p, level) + of pkChar, pkGreedyRepChar: + outp.prt(p.kind, $p, level) + of pkCharChoice, pkGreedyRepSet: + outp.prt(p.kind, $p, level) + of pkNonTerminal: + outp.prt(p.kind, + "$1 @($3, $4)" % [p.nt.name, $p.nt.rule.kind, $p.nt.line, $p.nt.col], level) + if not(p.nt.name in processed): + processed.add p.nt.name + p.nt.rule.recLoop level+1 + of pkBackRef..pkBackRefIgnoreStyle: + outp.prt(p.kind, $p, level) + else: + outp.prt(p.kind, $p, level) + for s in items(p): + s.recLoop level+1 + +pegAst.recLoop +echo outp.data \ No newline at end of file diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 84e536636..9affbc159 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -266,8 +266,17 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = testSpec r, makeTest(filename, options, cat, actionCompile), targetCPP let tests = [ + "niminaction/Chapter1/various1", + "niminaction/Chapter2/various2", + "niminaction/Chapter2/resultaccept", + "niminaction/Chapter2/resultreject", + "niminaction/Chapter2/explicit_discard", + "niminaction/Chapter2/no_def_eq", + "niminaction/Chapter2/no_iterator", + "niminaction/Chapter2/no_seq_type", "niminaction/Chapter3/ChatApp/src/server", "niminaction/Chapter3/ChatApp/src/client", + "niminaction/Chapter3/various3", "niminaction/Chapter6/WikipediaStats/concurrency_regex", "niminaction/Chapter6/WikipediaStats/concurrency", "niminaction/Chapter6/WikipediaStats/naive", @@ -278,8 +287,34 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = "niminaction/Chapter7/Tweeter/src/tweeter", "niminaction/Chapter7/Tweeter/src/createDatabase", "niminaction/Chapter7/Tweeter/tests/database_test", - "niminaction/Chapter8/sdl/sdl_test", + "niminaction/Chapter8/sdl/sdl_test" ] + + # Verify that the files have not been modified. Death shall fall upon + # whoever edits these hashes without dom96's permission, j/k. But please only + # edit when making a conscious breaking change, also please try to make your + # commit message clear and notify me so I can easily compile an errata later. + var testHashes: seq[string] = @[] + + for test in tests: + testHashes.add(getMD5(readFile("tests" / test.addFileExt("nim")).string)) + + const refHashes = @[ + "51afdfa84b3ca3d810809d6c4e5037ba", "30f07e4cd5eaec981f67868d4e91cfcf", + "d14e7c032de36d219c9548066a97e846", "2e40bfd5daadb268268727da91bb4e81", + "c5d3853ed0aba04bf6d35ba28a98dca0", "058603145ff92d46c009006b06e5b228", + "7b94a029b94ddb7efafddd546c965ff6", "586d74514394e49f2370dfc01dd9e830", + "e1901837b757c9357dc8d259fd0ef0f6", "097670c7ae12e825debaf8ec3995227b", + "a8cb7b78cc78d28535ab467361db5d6e", "bfaec2816a1848991b530c1ad17a0184", + "47cb71bb4c1198d6d29cdbee05aa10b9", "87e4436809f9d73324cfc4f57f116770", + "7b7db5cddc8cf8fa9b6776eef1d0a31d", "e6e40219f0f2b877869b738737b7685e", + "6532ee87d819f2605a443d5e94f9422a", "9a8fe78c588d08018843b64b57409a02", + "03a801275b8b76b4170c870cd0da079d", "20bb7d3e2d38d43b0cb5fcff4909a4a8", + "af6844598f534fab6942abfa4dfe9ab2", "2a7a17f84f6503d9bc89a5ab8feea127" + ] + doAssert testHashes == refHashes, "Nim in Action tests were changed." + + # Run the tests. for testfile in tests: test "tests/" & testfile & ".nim", actionCompile @@ -291,6 +326,7 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = + # ------------------------- manyloc ------------------------------------------- #proc runSpecialTests(r: var TResults, options: string) = # for t in ["lib/packages/docutils/highlite"]: diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 0185156ec..0764b6363 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -129,7 +129,7 @@ proc callCCompiler(cmdTemplate, filename, options: string, let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir()]) - var p = startProcess(command="gcc", args=c[5.. ^1], + var p = startProcess(command="gcc", args=c[5 .. ^1], options={poStdErrToStdOut, poUsePath}) let outp = p.outputStream var x = newStringOfCap(120) |