diff options
author | Michael Voronin <survivor.mail@gmail.com> | 2018-05-03 17:12:01 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-03 17:12:01 +0300 |
commit | 5ea967d97a30f0084883d4efa81b05bea3e5d148 (patch) | |
tree | 05ea0e3624f6720c2f5af28b5a70c87c85feafc7 | |
parent | 3949c9f977378ea3ab2b3c750f4dc2bc8d853022 (diff) | |
parent | 5564289b577c620cbd775f477b7fc8b6507adbfa (diff) | |
download | Nim-5ea967d97a30f0084883d4efa81b05bea3e5d148.tar.gz |
Merge pull request #3 from nim-lang/devel
pull #3
107 files changed, 1137 insertions, 2841 deletions
diff --git a/changelog.md b/changelog.md index 8ac02a388..6cd0faf52 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -## v0.X.X - XX/XX/2018 +## v0.19.X - XX/XX/2018 ### Changes affecting backwards compatibility @@ -11,9 +11,12 @@ to deal with! - Indexing into a ``cstring`` for the JS target is now mapped to ``charCodeAt``. -- Assignments that would "slice" an object into its supertype are not prevented +- Assignments that would "slice" an object into its supertype are now prevented at runtime. Use ``ref object`` with inheritance rather than ``object`` with inheritance to prevent this issue. +- The ``not nil`` type annotation now has to be enabled explicitly + via ``{.experimental: "notnil"}`` as we are still not pleased with how this + feature works with Nim's containers. #### Breaking changes in the standard library @@ -81,6 +84,14 @@ Imported exceptions can be raised and caught just like Nim exceptions. More details in language manual. +- ``nil`` for strings/seqs is finally gone. Instead the default value for + these is ``"" / @[]``. +- Accessing the binary zero terminator in Nim's native strings + is now invalid. Internally a Nim string still has the trailing zero for + zero-copy interoperability with ``cstring``. Compile your code with the + next switch ``--laxStrings:on`` if you need a transition period. + + ### Tool changes - ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 5f14b6804..2ddc88509 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -125,15 +125,15 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = of tyString, tySequence: if skipTypes(n.typ, abstractInst).kind == tyVar and not compileToCpp(p.module): - result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)] + result = "(*$1)->data, (*$1 ? (*$1)->$2 : 0)" % [a.rdLoc, lenField(p)] else: - result = "$1->data, $1->$2" % [a.rdLoc, lenField(p)] + result = "$1->data, ($1 ? $1->$2 : 0)" % [a.rdLoc, lenField(p)] of tyArray: result = "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))] of tyPtr, tyRef: case lastSon(a.t).kind of tyString, tySequence: - result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)] + result = "(*$1)->data, (*$1 ? (*$1)->$2 : 0)" % [a.rdLoc, lenField(p)] of tyArray: result = "$1, $2" % [rdLoc(a), rope(lengthOrd(lastSon(a.t)))] else: @@ -143,7 +143,7 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = proc genArgStringToCString(p: BProc, n: PNode): Rope {.inline.} = var a: TLoc initLocExpr(p, n.sons[0], a) - result = "$1->data" % [a.rdLoc] + result = "($1 ? $1->data : (NCSTRING)\"\")" % [a.rdLoc] proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope = var a: TLoc diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ea373f5a6..96f9265f1 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -63,6 +63,10 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope = of tyNil: result = genNilStringLiteral(p.module, n.info) of tyString: + # with the new semantics for 'nil' strings, we can map "" to nil and + # save tons of allocations: + #if n.strVal.len == 0: result = genNilStringLiteral(p.module, n.info) + #else: result = genStringLiteral(p.module, n) else: if n.strVal.isNil: result = rope("NIM_NIL") @@ -882,7 +886,7 @@ proc genIndexCheck(p: BProc; arr, idx: TLoc) = rdCharLoc(idx), first, intLiteral(lastOrd(ty))) of tySequence, tyString: linefmt(p, cpsStmts, - "if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", + "if (!$2 || (NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", rdLoc(idx), rdLoc(arr), lenField(p)) else: discard @@ -905,14 +909,14 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) # emit range check: if optBoundsCheck in p.options: - if ty.kind == tyString: + if ty.kind == tyString and (not defined(nimNoZeroTerminator) or optLaxStrings in p.options): linefmt(p, cpsStmts, - "if ((NU)($1) > (NU)($2->$3)) #raiseIndexError();$n", - rdLoc(b), rdLoc(a), lenField(p)) + "if (!$2 || (NU)($1) > (NU)($2->$3)) #raiseIndexError();$n", + rdLoc(b), rdLoc(a), lenField(p)) else: linefmt(p, cpsStmts, - "if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", - rdLoc(b), rdLoc(a), lenField(p)) + "if (!$2 || (NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", + rdLoc(b), rdLoc(a), lenField(p)) if d.k == locNone: d.storage = OnHeap if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}: a.r = rfmt(nil, "(*$1)", a.r) @@ -980,10 +984,10 @@ proc genEcho(p: BProc, n: PNode) = var a: TLoc for it in n.sons: if it.skipConv.kind == nkNilLit: - add(args, ", \"nil\"") + add(args, ", \"\"") else: initLocExpr(p, it, a) - addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)]) + addf(args, ", $1? ($1)->data:\"\"", [rdLoc(a)]) p.module.includeHeader("<base/log.h>") linefmt(p, cpsStmts, """Genode::log(""$1);$n""", args) else: @@ -1034,7 +1038,7 @@ proc genStrConcat(p: BProc, e: PNode, d: var TLoc) = if e.sons[i + 1].kind in {nkStrLit..nkTripleStrLit}: inc(L, len(e.sons[i + 1].strVal)) else: - addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)]) + addf(lens, "($1 ? $1->$2 : 0) + ", [rdLoc(a), lenField(p)]) add(appends, rfmt(p.module, "#appendString($1, $2);$n", tmp.r, rdLoc(a))) linefmt(p, cpsStmts, "$1 = #rawNewString($2$3);$n", tmp.r, lens, rope(L)) add(p.s(cpsStmts), appends) @@ -1073,7 +1077,7 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = if e.sons[i + 2].kind in {nkStrLit..nkTripleStrLit}: inc(L, len(e.sons[i + 2].strVal)) else: - addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)]) + addf(lens, "($1 ? $1->$2 : 0) + ", [rdLoc(a), lenField(p)]) add(appends, rfmt(p.module, "#appendString($1, $2);$n", rdLoc(dest), rdLoc(a))) linefmt(p, cpsStmts, "$1 = #resizeString($1, $2$3);$n", @@ -1086,17 +1090,17 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = # seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x)); # seq->data[seq->len-1] = x; let seqAppendPattern = if not p.module.compileToCpp: - "$1 = ($2) #incrSeqV2(&($1)->Sup, sizeof($3));$n" + "$1 = ($2) #incrSeqV3(&($1)->Sup, $3);$n" else: - "$1 = ($2) #incrSeqV2($1, sizeof($3));$n" + "$1 = ($2) #incrSeqV3($1, $3);$n" var a, b, dest, tmpL: TLoc initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - let bt = skipTypes(e.sons[2].typ, {tyVar}) + let seqType = skipTypes(e.sons[1].typ, {tyVar}) lineCg(p, cpsStmts, seqAppendPattern, [ rdLoc(a), getTypeDesc(p.module, e.sons[1].typ), - getTypeDesc(p.module, bt)]) + genTypeInfo(p.module, seqType, e.info)]) #if bt != b.t: # echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t) initLoc(dest, locExpr, e.sons[2], OnHeap) @@ -1417,7 +1421,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage) of tyString, tySequence: putIntoDest(p, b, e, - "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.storage) + "$1->data, ($1 ? $1->$2 : 0)" % [rdLoc(a), lenField(p)], a.storage) of tyArray: putIntoDest(p, b, e, "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))], a.storage) @@ -1500,13 +1504,13 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[2], b) let t = skipTypes(e.sons[1].typ, {tyVar}) let setLenPattern = if not p.module.compileToCpp: - "$1 = ($3) #setLengthSeq(&($1)->Sup, sizeof($4), $2);$n" + "$1 = ($3) #setLengthSeqV2(&($1)->Sup, $4, $2);$n" else: - "$1 = ($3) #setLengthSeq($1, sizeof($4), $2);$n" + "$1 = ($3) #setLengthSeqV2($1, $4, $2);$n" lineCg(p, cpsStmts, setLenPattern, [ rdLoc(a), rdLoc(b), getTypeDesc(p.module, t), - getTypeDesc(p.module, t.skipTypes(abstractInst).sons[0])]) + genTypeInfo(p.module, t.skipTypes(abstractInst), e.info)]) gcUsage(e) proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) = @@ -1740,7 +1744,7 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) = proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, n, "$1->data" % [rdLoc(a)], + putIntoDest(p, d, n, "($1 ? $1->data : (NCSTRING)\"\")" % [rdLoc(a)], a.storage) proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = @@ -1755,16 +1759,14 @@ proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = var x: TLoc var a = e.sons[1] var b = e.sons[2] - if (a.kind == nkNilLit) or (b.kind == nkNilLit): - binaryExpr(p, e, d, "($1 == $2)") - elif (a.kind in {nkStrLit..nkTripleStrLit}) and (a.strVal == ""): + if a.kind in {nkStrLit..nkTripleStrLit} and a.strVal == "": initLocExpr(p, e.sons[2], x) putIntoDest(p, d, e, - rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) - elif (b.kind in {nkStrLit..nkTripleStrLit}) and (b.strVal == ""): + rfmt(nil, "(!($1) || ($1)->$2 == 0)", rdLoc(x), lenField(p))) + elif b.kind in {nkStrLit..nkTripleStrLit} and b.strVal == "": initLocExpr(p, e.sons[1], x) putIntoDest(p, d, e, - rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) + rfmt(nil, "(!($1) || ($1)->$2 == 0)", rdLoc(x), lenField(p))) else: binaryExpr(p, e, d, "#eqStrings($1, $2)") diff --git a/compiler/commands.nim b/compiler/commands.nim index 06b487cf0..d8d8ae4b7 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -54,6 +54,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; const HelpMessage = "Nim Compiler Version $1 [$2: $3]\n" & + "Compiled at $4 $5\n" & "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n" const @@ -68,7 +69,8 @@ const proc getCommandLineDesc(): string = result = (HelpMessage % [VersionAsString, platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]) & Usage + CPU[platform.hostCPU].name, CompileDate, CompileTime]) & + Usage proc helpOnError(pass: TCmdLinePass) = if pass == passCmd1: @@ -79,7 +81,8 @@ proc writeAdvancedUsage(pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(`%`(HelpMessage, [VersionAsString, platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]) & AdvancedUsage, + CPU[platform.hostCPU].name, CompileDate, CompileTime]) & + AdvancedUsage, {msgStdout}) msgQuit(0) @@ -87,7 +90,8 @@ proc writeFullhelp(pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(`%`(HelpMessage, [VersionAsString, platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]) & Usage & AdvancedUsage, + CPU[platform.hostCPU].name, CompileDate, CompileTime]) & + Usage & AdvancedUsage, {msgStdout}) msgQuit(0) @@ -95,7 +99,7 @@ proc writeVersionInfo(pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(`%`(HelpMessage, [VersionAsString, platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]), + CPU[platform.hostCPU].name, CompileDate, CompileTime]), {msgStdout}) const gitHash = gorge("git log -n 1 --format=%H").strip @@ -188,11 +192,11 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass, if i < len(arg) and (arg[i] in {':', '='}): inc(i) else: invalidCmdLineOption(pass, orig, info) if state == wHint: - var x = findStr(msgs.HintsToStr, id) + let x = findStr(msgs.HintsToStr, id) if x >= 0: n = TNoteKind(x + ord(hintMin)) else: localError(info, "unknown hint: " & id) else: - var x = findStr(msgs.WarningsToStr, id) + let x = findStr(msgs.WarningsToStr, id) if x >= 0: n = TNoteKind(x + ord(warnMin)) else: localError(info, "unknown warning: " & id) case substr(arg, i).normalize @@ -499,6 +503,7 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; undefSymbol("nimOldNewlines") else: localError(info, errOnOrOffExpectedButXFound, arg) + of "laxstrings": processOnOffSwitch({optLaxStrings}, arg, pass, info) of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info) of "floatchecks": processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 028aedb5b..f8a75e68e 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -82,7 +82,6 @@ proc countDefinedSymbols*(): int = proc initDefines*() = gSymbols = newStringTable(modeStyleInsensitive) - defineSymbol("nimrod") # 'nimrod' is always defined # for bootstrapping purposes and old code: defineSymbol("nimhygiene") defineSymbol("niminheritable") @@ -115,3 +114,6 @@ proc initDefines*() = defineSymbol("nimHasNilChecks") defineSymbol("nimSymKind") defineSymbol("nimVmEqIdent") + defineSymbol("nimNoNil") + defineSymbol("nimNoZeroTerminator") + defineSymbol("nimNotNil") diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim index a1ba9113c..51ccb8390 100644 --- a/compiler/filter_tmpl.nim +++ b/compiler/filter_tmpl.nim @@ -42,7 +42,8 @@ proc newLine(p: var TTmplParser) = proc scanPar(p: var TTmplParser, d: int) = var i = d - while true: + let hi = p.x.len - 1 + while i <= hi: case p.x[i] of '\0': break of '(': inc(p.par) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 0b1090bb1..0478ed574 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -165,13 +165,12 @@ proc isKeyword*(kind: TTokType): bool = template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion proc isNimIdentifier*(s: string): bool = - if s[0] in SymStartChars: + let sLen = s.len + if sLen > 0 and s[0] in SymStartChars: var i = 1 - var sLen = s.len while i < sLen: - if s[i] == '_': - inc(i) - if s[i] notin SymChars: return + if s[i] == '_': inc(i) + if i < sLen and s[i] notin SymChars: return inc(i) result = true @@ -311,12 +310,12 @@ template tokenEndPrevious(tok, pos) = # We need to parse the largest uint literal without overflow checks proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int = var i = start - if s[i] in {'0'..'9'}: + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: b = b * 10 + (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start {.pop.} # overflowChecks diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 5ae2c4970..5da375c1c 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -126,7 +126,7 @@ type warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, warnUnknownSubstitutionX, warnLanguageXNotSupported, warnFieldXNotSupported, warnCommentXIgnored, - warnNilStatement, warnTypelessParam, + warnTypelessParam, warnUseBase, warnWriteToForeignHeap, warnUnsafeCode, warnEachIdentIsTuple, warnShadowIdent, warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, @@ -400,7 +400,6 @@ const warnLanguageXNotSupported: "language \'$1\' not supported", warnFieldXNotSupported: "field \'$1\' not supported", warnCommentXIgnored: "comment \'$1\' ignored", - warnNilStatement: "'nil' statement is deprecated; use an empty 'discard' statement instead", warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'", warnUseBase: "use {.base.} for base methods; baseless methods are deprecated", warnWriteToForeignHeap: "write to foreign heap", @@ -451,7 +450,7 @@ const "SmallLshouldNotBeUsed", "UnknownMagic", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "FieldXNotSupported", - "CommentXIgnored", "NilStmt", + "CommentXIgnored", "TypelessParam", "UseBase", "WriteToForeignHeap", "UnsafeCode", "EachIdentIsTuple", "ShadowIdent", "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", @@ -474,6 +473,10 @@ const hintMin* = hintSuccess hintMax* = high(TMsgKind) +static: + doAssert HintsToStr.len == ord(hintMax) - ord(hintMin) + 1 + doAssert WarningsToStr.len == ord(warnMax) - ord(warnMin) + 1 + type TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints TNoteKinds* = set[TNoteKind] diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim index a97d88078..2ef375b00 100644 --- a/compiler/nimfix/nimfix.nim +++ b/compiler/nimfix/nimfix.nim @@ -47,7 +47,7 @@ proc mainCommand = compileProject(newModuleGraph(), newIdentCache()) pretty.overwriteFiles() -proc processCmdLine*(pass: TCmdLinePass, cmd: string) = +proc processCmdLine*(pass: TCmdLinePass, cmd: string, config: ConfigRef) = var p = parseopt.initOptParser(cmd) var argsCount = 0 gOnlyMainfile = true @@ -76,16 +76,16 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of "wholeproject": gOnlyMainfile = false of "besteffort": msgs.gErrorMax = high(int) # don't stop after first error else: - processSwitch(pass, p) + processSwitch(pass, p, config) of cmdArgument: options.gProjectName = unixToNativePath(p.key) # if processArgument(pass, p, argsCount): break -proc handleCmdLine() = +proc handleCmdLine(config: ConfigRef) = if paramCount() == 0: stdout.writeLine(Usage) else: - processCmdLine(passCmd1, "") + processCmdLine(passCmd1, "", config) if gProjectName != "": try: gProjectFull = canonicalizePath(gProjectName) @@ -96,11 +96,11 @@ proc handleCmdLine() = gProjectName = p.name else: gProjectPath = getCurrentDir() - loadConfigs(DefaultConfig) # load all config files + loadConfigs(DefaultConfig, config) # load all config files # now process command line arguments again, because some options in the # command line can overwite the config file's settings extccomp.initVars() - processCmdLine(passCmd2, "") + processCmdLine(passCmd2, "", config) mainCommand() when compileOption("gc", "v2") or compileOption("gc", "refc"): @@ -108,4 +108,4 @@ when compileOption("gc", "v2") or compileOption("gc", "refc"): condsyms.initDefines() defineSymbol "nimfix" -handleCmdline() +handleCmdline newConfigRef() diff --git a/compiler/options.nim b/compiler/options.nim index a5dfaa81c..5baaa1bfd 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -37,7 +37,8 @@ type # please make sure we have under 32 options # evaluation optPatterns, # en/disable pattern matching optMemTracker, - optHotCodeReloading + optHotCodeReloading, + optLaxStrings TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** @@ -108,7 +109,8 @@ type dotOperators, callOperator, parallel, - destructor + destructor, + notnil ConfigRef* = ref object ## eventually all global configuration should be moved here cppDefines*: HashSet[string] diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 2a65a10a8..95a622d4e 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -175,10 +175,11 @@ proc put(g: var TSrcGen, kind: TTokType, s: string) = proc putComment(g: var TSrcGen, s: string) = if s.isNil: return var i = 0 + let hi = len(s) - 1 var isCode = (len(s) >= 2) and (s[1] != ' ') var ind = g.lineLen var com = "## " - while true: + while i <= hi: case s[i] of '\0': break @@ -201,12 +202,12 @@ proc putComment(g: var TSrcGen, s: string) = # gets too long: # compute length of the following word: var j = i - while s[j] > ' ': inc(j) + while j <= hi and s[j] > ' ': inc(j) if not isCode and (g.lineLen + (j - i) > MaxLineLen): put(g, tkComment, com) optNL(g, ind) com = "## " - while s[i] > ' ': + while i <= hi and s[i] > ' ': add(com, s[i]) inc(i) put(g, tkComment, com) @@ -215,8 +216,9 @@ proc putComment(g: var TSrcGen, s: string) = proc maxLineLength(s: string): int = if s.isNil: return 0 var i = 0 + let hi = len(s) - 1 var lineLen = 0 - while true: + while i <= hi: case s[i] of '\0': break @@ -235,7 +237,7 @@ proc maxLineLength(s: string): int = proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) = var i = 0 - var hi = len(s) - 1 + let hi = len(s) - 1 var str = "" while i <= hi: case s[i] @@ -1064,6 +1066,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if n.len > 1: let opr = if n[0].kind == nkIdent: n[0].ident elif n[0].kind == nkSym: n[0].sym.name + elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil if n[1].kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) diff --git a/compiler/ropes.nim b/compiler/ropes.nim index 358ce8a53..05d5e840c 100644 --- a/compiler/ropes.nim +++ b/compiler/ropes.nim @@ -188,11 +188,11 @@ iterator leaves*(r: Rope): string = var stack = @[r] while stack.len > 0: var it = stack.pop - while isNil(it.data): + while it.left != nil: + assert it.right != nil stack.add(it.right) it = it.left assert(it != nil) - assert(it.data != nil) yield it.data iterator items*(r: Rope): char = @@ -251,7 +251,7 @@ proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope = while true: j = j * 10 + ord(frmt[i]) - ord('0') inc(i) - if frmt[i] notin {'0'..'9'}: break + if i >= frmt.len or frmt[i] notin {'0'..'9'}: break num = j if j > high(args) + 1: errorHandler(rInvalidFormatStr, $(j)) diff --git a/compiler/sem.nim b/compiler/sem.nim index 041f2e127..52282d0e4 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -153,8 +153,9 @@ proc commonType*(x, y: PType): PType = if a.kind in {tyRef, tyPtr}: k = a.kind if b.kind != a.kind: return x - a = a.lastSon - b = b.lastSon + # bug #7601, array construction of ptr generic + a = a.lastSon.skipTypes({tyGenericInst}) + b = b.lastSon.skipTypes({tyGenericInst}) if a.kind == tyObject and b.kind == tyObject: result = commonSuperclass(a, b) # this will trigger an error later: diff --git a/compiler/semcall.nim b/compiler/semcall.nim index f443339f5..aa53fda3b 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -59,7 +59,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, filter: TSymKinds, best, alt: var TCandidate, errors: var CandidateErrors, - diagnosticsFlag = false) = + diagnosticsFlag: bool, + errorsEnabled: bool) = var o: TOverloadIter var sym = initOverloadIter(o, c, headSymbol) var scope = o.lastOverloadScope @@ -68,6 +69,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' let counterInitial = c.currentScope.symbols.counter var syms: seq[tuple[s: PSym, scope: int]] + var noSyms = true var nextSymIndex = 0 while sym != nil: if sym.kind in filter: @@ -102,7 +104,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, var cmp = cmpCandidates(best, z) if cmp < 0: best = z # x is better than the best so far elif cmp == 0: alt = z # x is as good as the best so far - elif errors != nil or z.diagnostics != nil: + elif errorsEnabled or z.diagnosticsEnabled: errors.safeAdd(CandidateError( sym: sym, unmatchedVarParam: int z.mutabilityProblem, @@ -113,7 +115,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # before any further candidate init and compare. SLOW, but rare case. syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o, diagnosticsFlag) - if syms == nil: + noSyms = false + if noSyms: sym = nextOverloadIter(o, c, headSymbol) scope = o.lastOverloadScope elif nextSymIndex < syms.len: @@ -206,7 +209,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = if errorOutputs == {}: # fail fast: globalError(n.info, errTypeMismatch, "") - if errors.isNil or errors.len == 0: + if errors.len == 0: localError(n.info, errExprXCannotBeCalled, n[0].renderTree) return @@ -227,7 +230,8 @@ proc bracketNotFoundError(c: PContext; n: PNode) = if symx.kind in routineKinds: errors.add(CandidateError(sym: symx, unmatchedVarParam: 0, firstMismatch: 0, - diagnostics: nil)) + diagnostics: nil, + enabled: false)) symx = nextOverloadIter(o, c, headSymbol) if errors.len == 0: localError(n.info, "could not resolve: " & $n) @@ -236,7 +240,8 @@ proc bracketNotFoundError(c: PContext; n: PNode) = proc resolveOverloads(c: PContext, n, orig: PNode, filter: TSymKinds, flags: TExprFlags, - errors: var CandidateErrors): TCandidate = + errors: var CandidateErrors, + errorsEnabled: bool): TCandidate = var initialBinding: PNode var alt: TCandidate var f = n.sons[0] @@ -249,7 +254,8 @@ proc resolveOverloads(c: PContext, n, orig: PNode, template pickBest(headSymbol) = pickBestCandidate(c, headSymbol, n, orig, initialBinding, - filter, result, alt, errors, efExplain in flags) + filter, result, alt, errors, efExplain in flags, + errorsEnabled) pickBest(f) let overloadsState = result.state @@ -423,9 +429,8 @@ proc tryDeref(n: PNode): PNode = proc semOverloadedCall(c: PContext, n, nOrig: PNode, filter: TSymKinds, flags: TExprFlags): PNode = - var errors: CandidateErrors = if efExplain in flags: @[] - else: nil - var r = resolveOverloads(c, n, nOrig, filter, flags, errors) + var errors: CandidateErrors = if efExplain in flags: @[] else: nil + var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) if r.state == csMatch: # this may be triggered, when the explain pragma is used if errors.len > 0: @@ -443,7 +448,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # into sigmatch with hidden conversion produced there # n.sons[1] = n.sons[1].tryDeref - var r = resolveOverloads(c, n, nOrig, filter, flags, errors) + var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) if r.state == csMatch: result = semResolvedCall(c, n, r) else: # get rid of the deref again for a better error message: diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 8159abf8f..fc0488814 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -63,7 +63,7 @@ type # to the user. efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, + efNoEvaluateGeneric, efInCall, efFromHlo TExprFlags* = set[TExprFlag] diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 6ad5d931d..feca087fc 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -51,7 +51,6 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = renderTree(result, {renderNoComments})) result.typ = errorType(c) else: - if efNoProcvarCheck notin flags: semProcvarCheck(c, result) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = @@ -63,8 +62,6 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = localError(n.info, errExprXHasNoType, renderTree(result, {renderNoComments})) result.typ = errorType(c) - else: - semProcvarCheck(c, result) proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = result = symChoice(c, n, s, scClosed) @@ -308,7 +305,9 @@ proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) - if efExplain in flags: m.diagnostics = @[] + if efExplain in flags: + m.diagnostics = @[] + m.diagnosticsEnabled = true let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) @@ -540,7 +539,6 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = # we need to recurse explicitly here as converters can create nested # calls and then they wouldn't be analysed otherwise analyseIfAddressTakenInCall(c, n.sons[i]) - semProcvarCheck(c, n.sons[i]) if i < sonsLen(t) and skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar: if n.sons[i].kind != nkHiddenAddr: @@ -1245,7 +1243,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = checkMinSonsLen(n, 2) # make sure we don't evaluate generic macros/templates n.sons[0] = semExprWithType(c, n.sons[0], - {efNoProcvarCheck, efNoEvaluateGeneric}) + {efNoEvaluateGeneric}) let arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) case arr.kind diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 6fcc9a0a4..c4d79a4a3 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -13,7 +13,7 @@ import strutils, options, ast, astalgo, trees, treetab, nimsets, times, nversion, platform, math, msgs, os, condsyms, idents, renderer, types, - commands, magicsys, saturate + commands, magicsys proc getConstExpr*(m: PSym, n: PNode): PNode # evaluates the constant expression or returns nil if it is no constant @@ -24,6 +24,63 @@ proc newIntNodeT*(intVal: BiggestInt, n: PNode): PNode proc newFloatNodeT(floatVal: BiggestFloat, n: PNode): PNode proc newStrNodeT*(strVal: string, n: PNode): PNode +proc checkInRange(n: PNode, res: BiggestInt): bool = + if res in firstOrd(n.typ)..lastOrd(n.typ): + result = true + +proc foldAdd(a, b: BiggestInt, n: PNode): PNode = + let res = a +% b + if ((res xor a) >= 0'i64 or (res xor b) >= 0'i64) and + checkInRange(n, res): + result = newIntNodeT(res, n) + +proc foldSub*(a, b: BiggestInt, n: PNode): PNode = + let res = a -% b + if ((res xor a) >= 0'i64 or (res xor not b) >= 0'i64) and + checkInRange(n, res): + result = newIntNodeT(res, n) + +proc foldAbs*(a: BiggestInt, n: PNode): PNode = + if a != firstOrd(n.typ): + result = newIntNodeT(a, n) + +proc foldMod*(a, b: BiggestInt, n: PNode): PNode = + if b != 0'i64: + result = newIntNodeT(a mod b, n) + +proc foldModU*(a, b: BiggestInt, n: PNode): PNode = + if b != 0'i64: + result = newIntNodeT(a %% b, n) + +proc foldDiv*(a, b: BiggestInt, n: PNode): PNode = + if b != 0'i64 and (a != firstOrd(n.typ) or b != -1'i64): + result = newIntNodeT(a div b, n) + +proc foldDivU*(a, b: BiggestInt, n: PNode): PNode = + if b != 0'i64: + result = newIntNodeT(a /% b, n) + +proc foldMul*(a, b: BiggestInt, n: PNode): PNode = + let res = a *% b + let floatProd = toBiggestFloat(a) * toBiggestFloat(b) + let resAsFloat = toBiggestFloat(res) + + # Fast path for normal case: small multiplicands, and no info + # is lost in either method. + if resAsFloat == floatProd and checkInRange(n, res): + return newIntNodeT(res, n) + + # Somebody somewhere lost info. Close enough, or way off? Note + # that a != 0 and b != 0 (else resAsFloat == floatProd == 0). + # The difference either is or isn't significant compared to the + # true value (of which floatProd is a good approximation). + + # abs(diff)/abs(prod) <= 1/32 iff + # 32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough" + if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd) and + checkInRange(n, res): + return newIntNodeT(res, n) + # implementation proc newIntNodeT(intVal: BiggestInt, n: PNode): PNode = @@ -172,23 +229,22 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = of mUnaryPlusI, mUnaryPlusF64: result = a # throw `+` away of mToFloat, mToBiggestFloat: result = newFloatNodeT(toFloat(int(getInt(a))), n) + # XXX: Hides overflow/underflow of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n) of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n) - of mAbsI: - if getInt(a) >= 0: result = a - else: result = newIntNodeT(- getInt(a), n) + of mAbsI: result = foldAbs(getInt(a), n) of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64: # byte(-128) = 1...1..1000_0000'64 --> 0...0..1000_0000'64 result = newIntNodeT(getInt(a) and (`shl`(1, getSize(a.typ) * 8) - 1), n) of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n) of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n) of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n) - of mUnaryLt: result = newIntNodeT(getOrdValue(a) |-| 1, n) - of mSucc: result = newIntNodeT(getOrdValue(a) |+| getInt(b), n) - of mPred: result = newIntNodeT(getOrdValue(a) |-| getInt(b), n) - of mAddI: result = newIntNodeT(getInt(a) |+| getInt(b), n) - of mSubI: result = newIntNodeT(getInt(a) |-| getInt(b), n) - of mMulI: result = newIntNodeT(getInt(a) |*| getInt(b), n) + of mUnaryLt: result = foldSub(getOrdValue(a), 1, n) + of mSucc: result = foldAdd(getOrdValue(a), getInt(b), n) + of mPred: result = foldSub(getOrdValue(a), getInt(b), n) + of mAddI: result = foldAdd(getInt(a), getInt(b), n) + of mSubI: result = foldSub(getInt(a), getInt(b), n) + of mMulI: result = foldMul(getInt(a), getInt(b), n) of mMinI: if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n) else: result = newIntNodeT(getInt(a), n) @@ -211,14 +267,8 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = of tyInt64, tyInt, tyUInt..tyUInt64: result = newIntNodeT(`shr`(getInt(a), getInt(b)), n) else: internalError(n.info, "constant folding for shr") - of mDivI: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`|div|`(getInt(a), y), n) - of mModI: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`|mod|`(getInt(a), y), n) + of mDivI: result = foldDiv(getInt(a), getInt(b), n) + of mModI: result = foldMod(getInt(a), getInt(b), n) of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n) of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n) of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n) @@ -258,14 +308,8 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n) of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n) of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n) - of mModU: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`%%`(getInt(a), y), n) - of mDivU: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`/%`(getInt(a), y), n) + of mModU: result = foldModU(getInt(a), getInt(b), n) + of mDivU: result = foldDivU(getInt(a), getInt(b), n) of mLeSet: result = newIntNodeT(ord(containsSets(a, b)), n) of mEqSet: result = newIntNodeT(ord(equalSets(a, b)), n) of mLtSet: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 94090852f..8b466f1da 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -71,37 +71,12 @@ proc toCover(t: PType): BiggestInt = else: result = lengthOrd(skipTypes(t, abstractVar-{tyTypeDesc})) -when false: - proc performProcvarCheck(c: PContext, info: TLineInfo, s: PSym) = - ## Checks that the given symbol is a proper procedure variable, meaning - ## that it - var smoduleId = getModule(s).id - if sfProcvar notin s.flags and s.typ.callConv == ccDefault and - smoduleId != c.module.id: - block outer: - for module in c.friendModules: - if smoduleId == module.id: - break outer - localError(info, errXCannotBePassedToProcVar, s.name.s) - -template semProcvarCheck(c: PContext, n: PNode) = - when false: - var n = n.skipConv - if n.kind in nkSymChoices: - for x in n: - if x.sym.kind in {skProc, skMethod, skConverter, skIterator}: - performProcvarCheck(c, n.info, x.sym) - elif n.kind == nkSym and n.sym.kind in {skProc, skMethod, skConverter, - skIterator}: - performProcvarCheck(c, n.info, n.sym) - proc semProc(c: PContext, n: PNode): PNode proc semExprBranch(c: PContext, n: PNode): PNode = result = semExpr(c, n) if result.typ != nil: # XXX tyGenericInst here? - semProcvarCheck(c, result) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprBranchScope(c: PContext, n: PNode): PNode = @@ -132,30 +107,23 @@ proc fixNilType(n: PNode) = proc discardCheck(c: PContext, result: PNode) = if c.matchedConcept != nil: return if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: - if result.kind == nkNilLit: - result.typ = nil - message(result.info, warnNilStatement) - elif implicitlyDiscardable(result): + if implicitlyDiscardable(result): var n = result result.typ = nil while n.kind in skipForDiscardable: n = n.lastSon n.typ = nil elif result.typ.kind != tyError and gCmd != cmdInteractive: - if result.typ.kind == tyNil: - fixNilType(result) - message(result.info, warnNilStatement) - else: - var n = result - while n.kind in skipForDiscardable: n = n.lastSon - var s = "expression '" & $n & "' is of type '" & - result.typ.typeToString & "' and has to be discarded" - if result.info.line != n.info.line or - result.info.fileIndex != n.info.fileIndex: - s.add "; start of expression here: " & $result.info - if result.typ.kind == tyProc: - s.add "; for a function call use ()" - localError(n.info, s) + var n = result + while n.kind in skipForDiscardable: n = n.lastSon + var s = "expression '" & $n & "' is of type '" & + result.typ.typeToString & "' and has to be discarded" + if result.info.line != n.info.line or + result.info.fileIndex != n.info.fileIndex: + s.add "; start of expression here: " & $result.info + if result.typ.kind == tyProc: + s.add "; for a function call use ()" + localError(n.info, s) proc semIf(c: PContext, n: PNode): PNode = result = n @@ -384,7 +352,7 @@ proc checkNilable(v: PSym) = include semasgn -proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) = +proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) = let L = identDefs.len let value = identDefs[L-1] if result.kind == nkStmtList: @@ -525,12 +493,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = localError(a.info, errXExpected, "tuple") elif length-2 != sonsLen(tup): localError(a.info, errWrongNumberOfVariables) - else: - b = newNodeI(nkVarTuple, a.info) - newSons(b, length) - b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator - b.sons[length-1] = def - addToVarSection(c, result, n, b) + b = newNodeI(nkVarTuple, a.info) + newSons(b, length) + b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator + b.sons[length-1] = def + addToVarSection(c, result, n, b) elif tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and a.kind == nkIdentDefs and a.len > 3: message(a.info, warnEachIdentIsTuple) @@ -572,7 +539,9 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = addToVarSection(c, result, n, b) else: if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j] - setVarType(v, tup.sons[j]) + # bug #7663, for 'nim check' this can be a non-tuple: + if tup.kind == tyTuple: setVarType(v, tup.sons[j]) + else: v.typ = tup b.sons[j] = newSymNode(v) checkNilable(v) if sfCompileTime in v.flags: hasCompileTime = true @@ -1588,9 +1557,11 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) if s.name.s[0] in {'.', '('}: if s.name.s in [".", ".()", ".="] and {destructor, dotOperators} * c.features == {}: - message(n.info, warnDeprecated, "overloaded '.' and '()' operators are now .experimental; " & s.name.s) + localError(n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"dotOperators\".}") elif s.name.s == "()" and callOperator notin c.features: - message(n.info, warnDeprecated, "overloaded '()' operators are now .experimental; " & s.name.s) + localError(n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"callOperator\".}") if n.sons[bodyPos].kind != nkEmpty: # for DLL generation it is annoying to check for sfImportc! @@ -1682,11 +1653,6 @@ proc semIterator(c: PContext, n: PNode): PNode = incl(s.typ.flags, tfCapturesEnv) else: s.typ.callConv = ccInline - when false: - if s.typ.callConv != ccInline: - s.typ.callConv = ccClosure - # and they always at least use the 'env' for the state field: - incl(s.typ.flags, tfCapturesEnv) if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone: localError(n.info, errImplOfXexpected, s.name.s) @@ -1792,14 +1758,6 @@ proc semStaticStmt(c: PContext, n: PNode): PNode = evalStaticStmt(c.module, c.cache, a, c.p.owner) result = newNodeI(nkDiscardStmt, n.info, 1) result.sons[0] = emptyNode - when false: - result = evalStaticStmt(c.module, a, c.p.owner) - if result.isNil: - LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) - result = emptyNode - elif result.kind == nkEmpty: - result = newNodeI(nkDiscardStmt, n.info, 1) - result.sons[0] = emptyNode proc usesResult(n: PNode): bool = # nkStmtList(expr) properly propagates the void context, @@ -1841,80 +1799,47 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = # nkNilLit, nkEmpty}: # dec last for i in countup(0, length - 1): - let k = n.sons[i].kind - case k - of nkFinally, nkExceptBranch: - # stand-alone finally and except blocks are - # transformed into regular try blocks: - # - # var f = fopen("somefile") | var f = fopen("somefile") - # finally: fclose(f) | try: - # ... | ... - # | finally: - # | fclose(f) - var deferPart: PNode - if k == nkDefer: - deferPart = newNodeI(nkFinally, n.sons[i].info) - deferPart.add n.sons[i].sons[0] - elif k == nkFinally: - message(n.info, warnDeprecated, - "use 'defer'; standalone 'finally'") - deferPart = n.sons[i] - else: - message(n.info, warnDeprecated, - "use an explicit 'try'; standalone 'except'") - deferPart = n.sons[i] - var tryStmt = newNodeI(nkTryStmt, n.sons[i].info) - var body = newNodeI(nkStmtList, n.sons[i].info) - if i < n.sonsLen - 1: - body.sons = n.sons[(i+1)..n.len-1] - tryStmt.addSon(body) - tryStmt.addSon(deferPart) - n.sons[i] = semTry(c, tryStmt) - n.sons.setLen(i+1) + var expr = semExpr(c, n.sons[i], flags) + n.sons[i] = expr + if c.matchedConcept != nil and expr.typ != nil and + (nfFromTemplate notin n.flags or i != last): + case expr.typ.kind + of tyBool: + if expr.kind == nkInfix and + expr[0].kind == nkSym and + expr[0].sym.name.s == "==": + if expr[1].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[1], expr[2]) + continue + elif expr[2].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[2], expr[1]) + continue + + let verdict = semConstExpr(c, n[i]) + if verdict.intVal == 0: + localError(result.info, "concept predicate failed") + of tyUnknown: continue + else: discard + if n.sons[i].typ == enforceVoidContext: #or usesResult(n.sons[i]): + voidContext = true + n.typ = enforceVoidContext + if i == last and (length == 1 or efWantValue in flags): n.typ = n.sons[i].typ - return + if not isEmptyType(n.typ): n.kind = nkStmtListExpr + elif i != last or voidContext: + discardCheck(c, n.sons[i]) else: - var expr = semExpr(c, n.sons[i], flags) - n.sons[i] = expr - if c.matchedConcept != nil and expr.typ != nil and - (nfFromTemplate notin n.flags or i != last): - case expr.typ.kind - of tyBool: - if expr.kind == nkInfix and - expr[0].kind == nkSym and - expr[0].sym.name.s == "==": - if expr[1].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[1], expr[2]) - continue - elif expr[2].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[2], expr[1]) - continue - - let verdict = semConstExpr(c, n[i]) - if verdict.intVal == 0: - localError(result.info, "concept predicate failed") - of tyUnknown: continue - else: discard - if n.sons[i].typ == enforceVoidContext: #or usesResult(n.sons[i]): - voidContext = true - n.typ = enforceVoidContext - if i == last and (length == 1 or efWantValue in flags): - n.typ = n.sons[i].typ - if not isEmptyType(n.typ): n.kind = nkStmtListExpr - elif i != last or voidContext: - discardCheck(c, n.sons[i]) - else: - n.typ = n.sons[i].typ - if not isEmptyType(n.typ): n.kind = nkStmtListExpr - if n.sons[i].kind in LastBlockStmts or - n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and sfNoReturn in n.sons[i][0].sym.flags: - for j in countup(i + 1, length - 1): - case n.sons[j].kind - of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, - nkBlockStmt, nkState: discard - else: localError(n.sons[j].info, errStmtInvalidAfterReturn) - else: discard + n.typ = n.sons[i].typ + if not isEmptyType(n.typ): n.kind = nkStmtListExpr + if n.sons[i].kind in LastBlockStmts or + n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and + sfNoReturn in n.sons[i][0].sym.flags: + for j in countup(i + 1, length - 1): + case n.sons[j].kind + of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, + nkBlockStmt, nkState: discard + else: localError(n.sons[j].info, errStmtInvalidAfterReturn) + else: discard if result.len == 1 and # concept bodies should be preserved as a stmt list: @@ -1930,15 +1855,6 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = not (result.comment[0] == '#' and result.comment[1] == '#'): # it is an old-style comment statement: we replace it with 'discard ""': prettybase.replaceComment(result.info) - when false: - # a statement list (s; e) has the type 'e': - if result.kind == nkStmtList and result.len > 0: - var lastStmt = lastSon(result) - if lastStmt.kind != nkNilLit and not implicitlyDiscardable(lastStmt): - result.typ = lastStmt.typ - #localError(lastStmt.info, errGenerated, - # "Last expression must be explicitly returned if it " & - # "is discardable or discarded") proc semStmt(c: PContext, n: PNode): PNode = # now: simply an alias: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 1fc263617..537319d66 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1389,6 +1389,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = n.sons[2].kind == nkNilLit: result = freshType(result, prev) result.flags.incl(tfNotNil) + if notnil notin c.features: + localError(n.info, "enable the 'not nil' annotation with {.experimental: \"notnil\".}") else: localError(n.info, errGenerated, "invalid type") of 2: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 96d815df7..4263ef581 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -26,6 +26,7 @@ type sym*: PSym unmatchedVarParam*, firstMismatch*: int diagnostics*: seq[string] + enabled*: bool CandidateErrors* = seq[CandidateError] @@ -60,7 +61,8 @@ type # matching. they will be reset if the matching # is not successful. may replace the bindings # table in the future. - diagnostics*: seq[string] # when this is not nil, the matching process + diagnostics*: seq[string] # \ + # when diagnosticsEnabled, the matching process # will collect extra diagnostics that will be # displayed to the user. # triggered when overload resolution fails @@ -70,6 +72,7 @@ type inheritancePenalty: int # to prefer closest father object type firstMismatch*: int # position of the first type mismatch for # better error messages + diagnosticsEnabled*: bool TTypeRelFlag* = enum trDontBind @@ -124,7 +127,8 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} = idTablePut(c.bindings, key, val.skipIntLit) proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, - binding: PNode, calleeScope = -1, diagnostics = false) = + binding: PNode, calleeScope = -1, + diagnosticsEnabled = false) = initCandidateAux(ctx, c, callee.typ) c.calleeSym = callee if callee.kind in skProcKinds and calleeScope == -1: @@ -139,7 +143,8 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, c.calleeScope = 1 else: c.calleeScope = calleeScope - c.diagnostics = if diagnostics: @[] else: nil + c.diagnostics = if diagnosticsEnabled: @[] else: nil + c.diagnosticsEnabled = diagnosticsEnabled c.magic = c.calleeSym.magic initIdTable(c.bindings) if binding != nil and callee.kind in routineKinds: @@ -717,7 +722,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = diagnostics: seq[string] errorPrefix: string flags: TExprFlags = {} - collectDiagnostics = m.diagnostics != nil or + collectDiagnostics = m.diagnosticsEnabled or sfExplain in typeClass.sym.flags if collectDiagnostics: @@ -736,7 +741,9 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = if collectDiagnostics: writelnHook = oldWriteHook - for msg in diagnostics: m.diagnostics.safeAdd msg + for msg in diagnostics: + m.diagnostics.safeAdd msg + m.diagnosticsEnabled = true if checkedBody == nil: return nil @@ -1388,8 +1395,13 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, var aAsObject = roota.lastSon - if fKind in {tyRef, tyPtr} and aAsObject.kind == fKind: - aAsObject = aAsObject.base + if fKind in {tyRef, tyPtr}: + if aAsObject.kind == tyObject: + # bug #7600, tyObject cannot be passed + # as argument to tyRef/tyPtr + return isNone + elif aAsObject.kind == fKind: + aAsObject = aAsObject.base if aAsObject.kind == tyObject: let baseType = aAsObject.base diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 4014d4c58..5413565e6 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -51,15 +51,15 @@ proc parseTopLevelStmt*(p: var TParsers): PNode = result = ast.emptyNode proc utf8Bom(s: string): int = - if s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF': + if s.len >= 3 and s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF': result = 3 else: result = 0 proc containsShebang(s: string, i: int): bool = - if s[i] == '#' and s[i+1] == '!': + if i+1 < s.len and s[i] == '#' and s[i+1] == '!': var j = i + 2 - while s[j] in Whitespace: inc(j) + while j < s.len and s[j] in Whitespace: inc(j) result = s[j] == '/' proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNode = @@ -74,9 +74,9 @@ proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNo discard llStreamReadLine(s, line) i = 0 inc linenumber - if line[i] == '#' and line[i+1] == '?': + if i+1 < line.len and line[i] == '#' and line[i+1] == '?': inc(i, 2) - while line[i] in Whitespace: inc(i) + while i < line.len and line[i] in Whitespace: inc(i) var q: TParser parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache) result = parser.parseAll(q) diff --git a/compiler/types.nim b/compiler/types.nim index 5d60fa9b4..edf4e5b46 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -612,13 +612,13 @@ proc firstOrd*(t: PType): BiggestInt = else: assert(t.n.sons[0].kind == nkSym) result = t.n.sons[0].sym.position - of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic: result = firstOrd(lastSon(t)) of tyOrdinal: if t.len > 0: result = firstOrd(lastSon(t)) - else: internalError("invalid kind for first(" & $t.kind & ')') + else: internalError("invalid kind for firstOrd(" & $t.kind & ')') else: - internalError("invalid kind for first(" & $t.kind & ')') + internalError("invalid kind for firstOrd(" & $t.kind & ')') result = 0 proc lastOrd*(t: PType; fixedUnsigned = false): BiggestInt = @@ -651,14 +651,14 @@ proc lastOrd*(t: PType; fixedUnsigned = false): BiggestInt = of tyEnum: assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym) result = t.n.sons[sonsLen(t.n) - 1].sym.position - of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic: result = lastOrd(lastSon(t)) of tyProxy: result = 0 of tyOrdinal: if t.len > 0: result = lastOrd(lastSon(t)) - else: internalError("invalid kind for last(" & $t.kind & ')') + else: internalError("invalid kind for lastOrd(" & $t.kind & ')') else: - internalError("invalid kind for last(" & $t.kind & ')') + internalError("invalid kind for lastOrd(" & $t.kind & ')') result = 0 proc lengthOrd*(t: PType): BiggestInt = diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index 071cc7706..ba37237e8 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -208,7 +208,12 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add mapTypeToAst(t.sons[0], info) else: result = mapTypeToBracket("ref", mRef, t, info) - of tyVar: result = mapTypeToBracket("var", mVar, t, info) + of tyVar: + if inst: + result = newNodeX(nkVarTy) + result.add mapTypeToAst(t.sons[0], info) + else: + result = mapTypeToBracket("var", mVar, t, info) of tyLent: result = mapTypeToBracket("lent", mBuiltinType, t, info) of tySink: result = mapTypeToBracket("sink", mBuiltinType, t, info) of tySequence: result = mapTypeToBracket("seq", mSeq, t, info) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index c3eaf8946..0544dc311 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1553,6 +1553,7 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = result = newNodeIT(nkFloatLit, info, t) of tyCString, tyString: result = newNodeIT(nkStrLit, info, t) + result.strVal = "" of tyVar, tyLent, tyPointer, tyPtr, tySequence, tyExpr, tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil: result = newNodeIT(nkNilLit, info, t) diff --git a/doc/advopt.txt b/doc/advopt.txt index d4a4b4961..685c8127d 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -66,6 +66,8 @@ Advanced options: --excessiveStackTrace:on|off stack traces use full file paths --oldNewlines:on|off turn on|off the old behaviour of "\n" + --laxStrings:on|off when turned on, accessing the zero terminator in + strings is allowed; only for backwards compatibility --skipCfg do not read the general configuration file --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files diff --git a/doc/manual.rst b/doc/manual.rst index 636bf796b..fabcf058a 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -1471,7 +1471,7 @@ mysterious crashes. **Note**: The example only works because the memory is initialized to zero (``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to -``nil`` which the string assignment can handle. One needs to know low level +binary zero which the string assignment can handle. One needs to know low level details like this when mixing garbage collected data with unmanaged memory. .. XXX finalizers for traced objects @@ -2512,8 +2512,8 @@ char '\\0' bool false ref or pointer type nil procedural type nil -sequence nil (*not* ``@[]``) -string nil (*not* "") +sequence ``@[]`` +string ``""`` tuple[x: A, y: B, ...] (default(A), default(B), ...) (analogous for objects) array[0..., T] [default(T), ...] @@ -4270,7 +4270,7 @@ therefore very useful for type specialization within generic code: Table[Key, Value] = object keys: seq[Key] values: seq[Value] - when not (Key is string): # nil value for strings used for optimization + when not (Key is string): # empty value for strings used for optimization deletedKeys: seq[bool] @@ -7434,8 +7434,8 @@ code generation directly, but their presence can be detected by macros. Custom pragmas are defined using templates annotated with pragma ``pragma``: .. code-block:: nim - template dbTable(name: string, table_space: string = nil) {.pragma.} - template dbKey(name: string = nil, primary_key: bool = false) {.pragma.} + template dbTable(name: string, table_space: string = "") {.pragma.} + template dbKey(name: string = "", primary_key: bool = false) {.pragma.} template dbForeignKey(t: typedesc) {.pragma.} template dbIgnore {.pragma.} diff --git a/doc/tut1.rst b/doc/tut1.rst index cbfef183e..f2251c463 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -944,12 +944,8 @@ String variables are **mutable**, so appending to a string is possible, and quite efficient. Strings in Nim are both zero-terminated and have a length field. A string's length can be retrieved with the builtin ``len`` procedure; the length never counts the terminating zero. Accessing the -terminating zero is not an error and often leads to simpler code: - -.. code-block:: nim - if s[i] == 'a' and s[i+1] == 'b': - # no need to check whether ``i < len(s)``! - ... +terminating zero is an error, it only exists so that a Nim string can be converted +to a ``cstring`` without doing a copy. The assignment operator for strings copies the string. You can use the ``&`` operator to concatenate strings and ``add`` to append to a string. @@ -960,11 +956,7 @@ enforced. For example, when reading strings from binary files, they are merely a sequence of bytes. The index operation ``s[i]`` means the i-th *char* of ``s``, not the i-th *unichar*. -String variables are initialized with a special value, called ``nil``. However, -most string operations cannot deal with ``nil`` (leading to an exception being -raised) for performance reasons. It is best to use empty strings ``""`` -rather than ``nil`` as the *empty* value. But ``""`` often creates a string -object on the heap, so there is a trade-off to be made here. +A string variable is initialized with the empty string ``""``. Integers @@ -1309,11 +1301,7 @@ Example: x: seq[int] # a reference to a sequence of integers x = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heap -Sequence variables are initialized with ``nil``. However, most sequence -operations cannot deal with ``nil`` (leading to an exception being -raised) for performance reasons. Thus one should use empty sequences ``@[]`` -rather than ``nil`` as the *empty* value. But ``@[]`` creates a sequence -object on the heap, so there is a trade-off to be made here. +Sequence variables are initialized with ``@[]``. The ``for`` statement can be used with one or two variables when used with a sequence. When you use the one variable form, the variable will hold the value @@ -1355,11 +1343,9 @@ type does not matter. .. code-block:: nim :test: "nim c $1" var - fruits: seq[string] # reference to a sequence of strings that is initialized with 'nil' + fruits: seq[string] # reference to a sequence of strings that is initialized with '@[]' capitals: array[3, string] # array of strings with a fixed size - fruits = @[] # creates an empty sequence on the heap that will be referenced by 'fruits' - capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime fruits.add("Mango") @@ -1691,7 +1677,7 @@ rules apply: write(stdout, x(3)) # no error: A.x is called write(stdout, x("")) # no error: B.x is called - proc x*(a: int): string = nil + proc x*(a: int): string = discard write(stdout, x(3)) # ambiguous: which `x` is to call? diff --git a/examples/httpserver2.nim b/examples/httpserver2.nim index 050684d3e..1843ff967 100644 --- a/examples/httpserver2.nim +++ b/examples/httpserver2.nim @@ -107,7 +107,7 @@ proc executeCgi(server: var TServer, client: Socket, path, query: string, dataAvail = recvLine(client, buf) if buf.len == 0: break - var L = toLower(buf) + var L = toLowerAscii(buf) if L.startsWith("content-length:"): var i = len("content-length:") while L[i] in Whitespace: inc(i) @@ -205,7 +205,7 @@ proc acceptRequest(server: var TServer, client: Socket) = client.close() else: when defined(Windows): - var ext = splitFile(path).ext.toLower + var ext = splitFile(path).ext.toLowerAscii if ext == ".exe" or ext == ".cgi": # XXX: extract interpreter information here? cgi = true diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 3d4afc0ae..6058128dd 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -10,7 +10,7 @@ from pcre import nil import nre.private.util import tables -from strutils import toLower, `%` +from strutils import `%` from math import ceil import options from unicode import runeLenAt @@ -326,15 +326,15 @@ proc `$`*(pattern: RegexMatch): string = proc `==`*(a, b: Regex): bool = if not a.isNil and not b.isNil: - return a.pattern == b.pattern and - a.pcreObj == b.pcreObj and + return a.pattern == b.pattern and + a.pcreObj == b.pcreObj and a.pcreExtra == b.pcreExtra else: return system.`==`(a, b) proc `==`*(a, b: RegexMatch): bool = return a.pattern == b.pattern and - a.str == b.str + a.str == b.str # }}} # Creation & Destruction {{{ @@ -645,7 +645,6 @@ template replaceImpl(str: string, pattern: Regex, let bounds = match.matchBounds result.add(str.substr(lastIdx, bounds.a - 1)) let nextVal = replacement - assert(nextVal != nil) result.add(nextVal) lastIdx = bounds.b + 1 diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index ee1f0076c..1547c888d 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -774,7 +774,7 @@ proc renderImage(d: PDoc, n: PRstNode, result: var string) = Digits + Letters + WhiteSpace) let arg = getArgument(n) - isObject = arg.toLower().endsWith(".svg") + isObject = arg.toLowerAscii().endsWith(".svg") var options = "" content = "" diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 6df6527d5..8fedb16fe 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -177,7 +177,7 @@ proc fail*[T](future: Future[T], error: ref Exception) = if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error) future.callbacks.call() -proc clearCallbacks(future: FutureBase) = +proc clearCallbacks*(future: FutureBase) = future.callbacks.function = nil future.callbacks.next = nil diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index a75f9daac..7c354151b 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -201,7 +201,7 @@ when defineSsl: flags: set[SocketFlag]) {.async.} = let len = bioCtrlPending(socket.bioOut) if len > 0: - var data = newStringOfCap(len) + var data = newString(len) let read = bioRead(socket.bioOut, addr data[0], len) assert read != 0 if read < 0: diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index 4b0d08292..bfb8a1666 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -52,7 +52,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = if numLines > 0: inc(total, (numLines - 1) * newLine.len) result = newString(total) - var + var i = 0 r = 0 currLine = 0 @@ -76,7 +76,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = currLine = 0 if i < s.len-1: - let + let a = ord(s[i]) b = ord(s[i+1]) result[r] = cb64[a shr 2] @@ -130,11 +130,11 @@ proc decode*(s: string): string = # total is an upper bound, as we will skip arbitrary whitespace: result = newString(total) - var + var i = 0 r = 0 while true: - while s[i] in Whitespace: inc(i) + while i < s.len and s[i] in Whitespace: inc(i) if i < s.len-3: let a = s[i].decodeByte diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 5de6aa487..a5a404497 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -97,11 +97,10 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = var name = "" var value = "" # decode everything in one pass: - while data[i] != '\0': + while i < data.len: setLen(name, 0) # reuse memory - while true: + while i < data.len: case data[i] - of '\0': break of '%': var x = 0 handleHexChar(data[i+1], x) @@ -112,15 +111,16 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = of '=', '&': break else: add(name, data[i]) inc(i) - if data[i] != '=': cgiError("'=' expected") + if i >= data.len or data[i] != '=': cgiError("'=' expected") inc(i) # skip '=' setLen(value, 0) # reuse memory - while true: + while i < data.len: case data[i] of '%': var x = 0 - handleHexChar(data[i+1], x) - handleHexChar(data[i+2], x) + if i+2 < data.len: + handleHexChar(data[i+1], x) + handleHexChar(data[i+2], x) inc(i, 2) add(value, chr(x)) of '+': add(value, ' ') @@ -128,9 +128,9 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = else: add(value, data[i]) inc(i) yield (name.TaintedString, value.TaintedString) - if data[i] == '&': inc(i) - elif data[i] == '\0': break - else: cgiError("'&' expected") + if i < data.len: + if data[i] == '&': inc(i) + else: cgiError("'&' expected") iterator decodeData*(allowedMethods: set[RequestMethod] = {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] = diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 34f5c5470..95fffdf88 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -74,18 +74,19 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] = var newByte = 0 block blockX: while newbyte < key.len: - if it.key[newbyte] != key[newbyte]: - newotherbits = it.key[newbyte].ord xor key[newbyte].ord + let ch = if newbyte < it.key.len: it.key[newbyte] else: '\0' + if ch != key[newbyte]: + newotherbits = ch.ord xor key[newbyte].ord break blockX inc newbyte - if it.key[newbyte] != '\0': + if newbyte < it.key.len: newotherbits = it.key[newbyte].ord else: return it while (newOtherBits and (newOtherBits-1)) != 0: newOtherBits = newOtherBits and (newOtherBits-1) newOtherBits = newOtherBits xor 255 - let ch = it.key[newByte] + let ch = if newByte < it.key.len: it.key[newByte] else: '\0' let dir = (1 + (ord(ch) or newOtherBits)) shr 8 var inner: Node[T] diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index aca0ac2b4..eaff86ae6 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -25,16 +25,16 @@ proc parseCookies*(s: string): StringTableRef = result = newStringTable(modeCaseInsensitive) var i = 0 while true: - while s[i] == ' ' or s[i] == '\t': inc(i) + while i < s.len and (s[i] == ' ' or s[i] == '\t'): inc(i) var keystart = i - while s[i] != '=' and s[i] != '\0': inc(i) + while i < s.len and s[i] != '=': inc(i) var keyend = i-1 - if s[i] == '\0': break + if i >= s.len: break inc(i) # skip '=' var valstart = i - while s[i] != ';' and s[i] != '\0': inc(i) + while i < s.len and s[i] != ';': inc(i) result[substr(s, keystart, keyend)] = substr(s, valstart, i-1) - if s[i] == '\0': break + if i >= s.len: break inc(i) # skip ';' proc setCookie*(key, value: string, domain = "", path = "", diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim index 5840d443d..731fbbc29 100644 --- a/lib/pure/encodings.nim +++ b/lib/pure/encodings.nim @@ -36,7 +36,7 @@ when defined(windows): while i < a.len and j < b.len: if a[i] in {'-', '_'}: inc i if b[j] in {'-', '_'}: inc j - if a[i].toLower != b[j].toLower: return false + if i < a.len and j < b.len and a[i].toLowerAscii != b[j].toLowerAscii: return false inc i inc j result = i == a.len and j == b.len diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 70fb19434..b7617b0b5 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -73,12 +73,18 @@ ## progress of the HTTP request. ## ## .. code-block:: Nim -## var client = newAsyncHttpClient() +## import asyncdispatch, httpclient +## ## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} = ## echo("Downloaded ", progress, " of ", total) ## echo("Current rate: ", speed div 1000, "kb/s") -## client.onProgressChanged = onProgressChanged -## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") +## +## proc asyncProc() {.async.} = +## var client = newAsyncHttpClient() +## client.onProgressChanged = onProgressChanged +## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") +## +## waitFor asyncProc() ## ## If you would like to remove the callback simply set it to ``nil``. ## @@ -241,7 +247,7 @@ proc parseChunks(s: Socket, timeout: int): string = var i = 0 if chunkSizeStr == "": httpError("Server terminated connection prematurely") - while true: + while i < chunkSizeStr.len: case chunkSizeStr[i] of '0'..'9': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) @@ -249,8 +255,6 @@ proc parseChunks(s: Socket, timeout: int): string = chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) - of '\0': - break of ';': # http://tools.ietf.org/html/rfc2616#section-3.6.1 # We don't care about chunk-extensions. @@ -739,10 +743,11 @@ proc downloadFile*(url: string, outputFilename: string, proc generateHeaders(requestUrl: Uri, httpMethod: string, headers: HttpHeaders, body: string, proxy: Proxy): string = # GET - result = httpMethod.toUpperAscii() + let upperMethod = httpMethod.toUpperAscii() + result = upperMethod result.add ' ' - if proxy.isNil or (not proxy.isNil and requestUrl.scheme == "https"): + if proxy.isNil or requestUrl.scheme == "https": # /path?query if requestUrl.path[0] != '/': result.add '/' result.add(requestUrl.path) @@ -768,7 +773,9 @@ proc generateHeaders(requestUrl: Uri, httpMethod: string, add(result, "Connection: Keep-Alive\c\L") # Content length header. - if body.len > 0 and not headers.hasKey("Content-Length"): + const requiresBody = ["POST", "PUT", "PATCH"] + let needsContentLength = body.len > 0 or upperMethod in requiresBody + if needsContentLength and not headers.hasKey("Content-Length"): add(result, "Content-Length: " & $body.len & "\c\L") # Proxy auth header. @@ -929,7 +936,7 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] var i = 0 if chunkSizeStr == "": httpError("Server terminated connection prematurely") - while true: + while i < chunkSizeStr.len: case chunkSizeStr[i] of '0'..'9': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) @@ -937,8 +944,6 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) - of '\0': - break of ';': # http://tools.ietf.org/html/rfc2616#section-3.6.1 # We don't care about chunk-extensions. diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim index f150fa1c1..5df26fdd5 100644 --- a/lib/pure/httpcore.nim +++ b/lib/pure/httpcore.nim @@ -190,11 +190,11 @@ proc len*(headers: HttpHeaders): int = return headers.table.len proc parseList(line: string, list: var seq[string], start: int): int = var i = 0 var current = "" - while line[start + i] notin {'\c', '\l', '\0'}: + while start+i < line.len and line[start + i] notin {'\c', '\l'}: i += line.skipWhitespace(start + i) i += line.parseUntil(current, {'\c', '\l', ','}, start + i) list.add(current) - if line[start + i] == ',': + if start+i < line.len and line[start + i] == ',': i.inc # Skip , current.setLen(0) diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 632eb198a..9f54ed9e8 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -126,7 +126,7 @@ when false: var dataAvail = false while dataAvail: dataAvail = recvLine(client, buf) # TODO: This is incorrect. - var L = toLower(buf.string) + var L = toLowerAscii(buf.string) if L.startsWith("content-length:"): var i = len("content-length:") while L[i] in Whitespace: inc(i) @@ -199,7 +199,7 @@ when false: notFound(client) else: when defined(Windows): - var ext = splitFile(path).ext.toLower + var ext = splitFile(path).ext.toLowerAscii if ext == ".exe" or ext == ".cgi": # XXX: extract interpreter information here? cgi = true @@ -303,7 +303,7 @@ proc next*(s: var Server) = if s.reqMethod == "POST": # Check for Expect header if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLower == "100-continue": + if s.headers["Expect"].toLowerAscii == "100-continue": s.client.sendStatus("100 Continue") else: s.client.sendStatus("417 Expectation Failed") @@ -427,7 +427,7 @@ proc nextAsync(s: PAsyncHTTPServer) = if s.reqMethod == "POST": # Check for Expect header if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLower == "100-continue": + if s.headers["Expect"].toLowerAscii == "100-continue": s.client.sendStatus("100 Continue") else: s.client.sendStatus("417 Expectation Failed") diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 6740f483d..574b13fbf 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -696,7 +696,7 @@ proc getBiggestInt*(n: JsonNode, default: BiggestInt = 0): BiggestInt = else: return n.num proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt {.deprecated.} = - ## Deprecated - use getInt or getBiggestInt instead + ## **Deprecated since v0.18.2:** use ``getInt`` or ``getBiggestInt`` instead. getBiggestInt(n, default) proc getFloat*(n: JsonNode, default: float = 0.0): float = @@ -710,7 +710,7 @@ proc getFloat*(n: JsonNode, default: float = 0.0): float = else: return default proc getFNum*(n: JsonNode, default: float = 0.0): float {.deprecated.} = - ## Deprecated - use getFloat instead + ## **Deprecated since v0.18.2:** use ``getFloat`` instead. getFloat(n, default) proc getBool*(n: JsonNode, default: bool = false): bool = @@ -721,7 +721,7 @@ proc getBool*(n: JsonNode, default: bool = false): bool = else: return n.bval proc getBVal*(n: JsonNode, default: bool = false): bool {.deprecated.} = - ## Deprecated - use getBVal instead + ## **Deprecated since v0.18.2:** use ``getBool`` instead. getBool(n, default) proc getFields*(n: JsonNode, @@ -947,7 +947,7 @@ proc contains*(node: JsonNode, val: JsonNode): bool = find(node.elems, val) >= 0 proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key) - ## Deprecated for `hasKey` + ## **Deprecated:** use `hasKey` instead. proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = ## Sets a field from a `JObject`. diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 751fc0e8d..43b5ab3ab 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -126,7 +126,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str var v = "" let app = when defined(js): "" else: getAppFilename() while frmt[i] in IdentChars: - v.add(toLower(frmt[i])) + v.add(toLowerAscii(frmt[i])) inc(i) case v of "date": result.add(getDateStr()) diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim index 685c3b07a..97223ed01 100644 --- a/lib/pure/matchers.nim +++ b/lib/pure/matchers.nim @@ -29,21 +29,21 @@ proc validEmailAddress*(s: string): bool {.noSideEffect, chars = Letters + Digits + {'!','#','$','%','&', '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} var i = 0 - if s[i] notin chars or s[i] == '.': return false - while s[i] in chars: - if s[i] == '.' and s[i+1] == '.': return false + if i >= s.len or s[i] notin chars or s[i] == '.': return false + while i < s.len and s[i] in chars: + if i+1 < s.len and s[i] == '.' and s[i+1] == '.': return false inc(i) - if s[i] != '@': return false + if i >= s.len or s[i] != '@': return false var j = len(s)-1 - if s[j] notin Letters: return false + if j >= 0 and s[j] notin Letters: return false while j >= i and s[j] in Letters: dec(j) inc(i) # skip '@' - while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) - if s[i] != '\0': return false + while i < s.len and s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) + if i != s.len: return false var x = substr(s, j+1) if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true - case toLower(x) + case toLowerAscii(x) of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", "aero", "jobs", "museum": return true else: return false diff --git a/lib/pure/net.nim b/lib/pure/net.nim index ebb59ca6d..e522d86fe 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -1421,8 +1421,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, ## ## **Note:** This proc is not available for SSL sockets. assert(not socket.isClosed, "Cannot `sendTo` on a closed socket") - var aiList = getAddrInfo(address, port, af) - + var aiList = getAddrInfo(address, port, af, socket.sockType, socket.protocol) # try all possibilities: var success = false var it = aiList @@ -1443,7 +1442,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, ## this function will try each IP of that hostname. ## ## This is the high-level version of the above ``sendTo`` function. - result = socket.sendTo(address, port, cstring(data), data.len) + result = socket.sendTo(address, port, cstring(data), data.len, socket.domain ) proc isSsl*(socket: Socket): bool = diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 11be8f0c1..644afee32 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1060,18 +1060,17 @@ proc parseCmdLine*(c: string): seq[string] {. while true: setLen(a, 0) # eat all delimiting whitespace - while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i) + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break when defined(windows): # parse a single argument according to the above rules: - if c[i] == '\0': break var inQuote = false - while true: + while i < c.len: case c[i] - of '\0': break of '\\': var j = i - while c[j] == '\\': inc(j) - if c[j] == '"': + while j < c.len and c[j] == '\\': inc(j) + if j < c.len and c[j] == '"': for k in 1..(j-i) div 2: a.add('\\') if (j-i) mod 2 == 0: i = j @@ -1084,7 +1083,7 @@ proc parseCmdLine*(c: string): seq[string] {. of '"': inc(i) if not inQuote: inQuote = true - elif c[i] == '"': + elif i < c.len and c[i] == '"': a.add(c[i]) inc(i) else: @@ -1102,13 +1101,12 @@ proc parseCmdLine*(c: string): seq[string] {. of '\'', '\"': var delim = c[i] inc(i) # skip ' or " - while c[i] != '\0' and c[i] != delim: + while i < c.len and c[i] != delim: add a, c[i] inc(i) - if c[i] != '\0': inc(i) - of '\0': break + if i < c.len: inc(i) else: - while c[i] > ' ': + while i < c.len and c[i] > ' ': add(a, c[i]) inc(i) add(result, a) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 0d638abb9..ee2b715d3 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -196,7 +196,7 @@ proc joinPath*(head, tail: string): string {. else: result = head & tail else: - if tail[0] in {DirSep, AltSep}: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: result = head & tail else: result = head & DirSep & tail @@ -477,7 +477,7 @@ proc unixToNativePath*(path: string, drive=""): string {. var i = start while i < len(path): # ../../../ --> :::: - if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': + if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': # parent directory when defined(macos): if result[high(result)] == ':': diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index ffb69a72b..c1a6161f9 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -57,26 +57,26 @@ type {.deprecated: [TCmdLineKind: CmdLineKind, TOptParser: OptParser].} proc parseWord(s: string, i: int, w: var string, - delim: set[char] = {'\x09', ' ', '\0'}): int = + delim: set[char] = {'\x09', ' '}): int = result = i - if s[result] == '\"': + if result < s.len and s[result] == '\"': inc(result) - while not (s[result] in {'\0', '\"'}): + while result < s.len and s[result] != '\"': add(w, s[result]) inc(result) - if s[result] == '\"': inc(result) + if result < s.len and s[result] == '\"': inc(result) else: - while not (s[result] in delim): + while result < s.len and s[result] notin delim: add(w, s[result]) inc(result) when declared(os.paramCount): proc quote(s: string): string = - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': + if find(s, {' ', '\t'}) >= 0 and s.len > 0 and s[0] != '"': if s[0] == '-': result = newStringOfCap(s.len) - var i = parseWord(s, 0, result, {'\0', ' ', '\x09', ':', '='}) - if s[i] in {':','='}: + var i = parseWord(s, 0, result, {' ', '\x09', ':', '='}) + if i < s.len and s[i] in {':','='}: result.add s[i] inc i result.add '"' @@ -144,43 +144,45 @@ proc handleShortOption(p: var OptParser) = add(p.key.string, p.cmd[i]) inc(i) p.inShortState = true - while p.cmd[i] in {'\x09', ' '}: + while i < p.cmd.len and p.cmd[i] in {'\x09', ' '}: inc(i) p.inShortState = false - if p.cmd[i] in {':', '='} or card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal: - if p.cmd[i] in {':', '='}: + if i < p.cmd.len and p.cmd[i] in {':', '='} or + card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal: + if i < p.cmd.len and p.cmd[i] in {':', '='}: inc(i) p.inShortState = false - while p.cmd[i] in {'\x09', ' '}: inc(i) + while i < p.cmd.len and p.cmd[i] in {'\x09', ' '}: inc(i) i = parseWord(p.cmd, i, p.val.string) - if p.cmd[i] == '\0': p.inShortState = false + if i >= p.cmd.len: p.inShortState = false p.pos = i proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = ## parses the first or next option; ``p.kind`` describes what token has been ## parsed. ``p.key`` and ``p.val`` are set accordingly. var i = p.pos - while p.cmd[i] in {'\x09', ' '}: inc(i) + while i < p.cmd.len and p.cmd[i] in {'\x09', ' '}: inc(i) p.pos = i setLen(p.key.string, 0) setLen(p.val.string, 0) if p.inShortState: handleShortOption(p) return - case p.cmd[i] - of '\0': + if i >= p.cmd.len: p.kind = cmdEnd - of '-': + return + if p.cmd[i] == '-': inc(i) - if p.cmd[i] == '-': + if i < p.cmd.len and p.cmd[i] == '-': p.kind = cmdLongOption inc(i) - i = parseWord(p.cmd, i, p.key.string, {'\0', ' ', '\x09', ':', '='}) - while p.cmd[i] in {'\x09', ' '}: inc(i) - if p.cmd[i] in {':', '='} or len(p.longNoVal) > 0 and p.key.string notin p.longNoVal: - if p.cmd[i] in {':', '='}: + i = parseWord(p.cmd, i, p.key.string, {' ', '\x09', ':', '='}) + while i < p.cmd.len and p.cmd[i] in {'\x09', ' '}: inc(i) + if i < p.cmd.len and p.cmd[i] in {':', '='} or + len(p.longNoVal) > 0 and p.key.string notin p.longNoVal: + if i < p.cmd.len and p.cmd[i] in {':', '='}: inc(i) - while p.cmd[i] in {'\x09', ' '}: inc(i) + while i < p.cmd.len and p.cmd[i] in {'\x09', ' '}: inc(i) p.pos = parseWord(p.cmd, i, p.val.string) else: p.pos = i diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index cecc94e92..1d76234ea 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -51,9 +51,9 @@ proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. ## upper bound. Not more than ```maxLen`` characters are parsed. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) - elif s[i] == '#': inc(i) let last = if maxLen == 0: s.len else: i+maxLen + if i+1 < last and s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) + elif i < last and s[i] == '#': inc(i) while i < last: case s[i] of '_': discard @@ -76,8 +76,8 @@ proc parseOct*(s: string, number: var int, start = 0): int {. ## the number of the parsed characters or 0 in case of an error. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) - while true: + if i+1 < s.len and s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) + while i < s.len: case s[i] of '_': discard of '0'..'7': @@ -93,8 +93,8 @@ proc parseBin*(s: string, number: var int, start = 0): int {. ## the number of the parsed characters or 0 in case of an error. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2) - while true: + if i+1 < s.len and s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2) + while i < s.len: case s[i] of '_': discard of '0'..'1': @@ -108,9 +108,9 @@ proc parseIdent*(s: string, ident: var string, start = 0): int = ## parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. var i = start - if s[i] in IdentStartChars: + if i < s.len and s[i] in IdentStartChars: inc(i) - while s[i] in IdentChars: inc(i) + while i < s.len and s[i] in IdentChars: inc(i) ident = substr(s, start, i-1) result = i-start @@ -119,11 +119,9 @@ proc parseIdent*(s: string, start = 0): string = ## Returns the parsed identifier or an empty string in case of an error. result = "" var i = start - - if s[i] in IdentStartChars: + if i < s.len and s[i] in IdentStartChars: inc(i) - while s[i] in IdentChars: inc(i) - + while i < s.len and s[i] in IdentChars: inc(i) result = substr(s, start, i-1) proc parseToken*(s: string, token: var string, validChars: set[char], @@ -134,24 +132,26 @@ proc parseToken*(s: string, token: var string, validChars: set[char], ## ## **Deprecated since version 0.8.12**: Use ``parseWhile`` instead. var i = start - while s[i] in validChars: inc(i) + while i < s.len and s[i] in validChars: inc(i) result = i-start token = substr(s, start, i-1) proc skipWhitespace*(s: string, start = 0): int {.inline.} = ## skips the whitespace starting at ``s[start]``. Returns the number of ## skipped characters. - while s[start+result] in Whitespace: inc(result) + while start+result < s.len and s[start+result] in Whitespace: inc(result) proc skip*(s, token: string, start = 0): int {.inline.} = ## skips the `token` starting at ``s[start]``. Returns the length of `token` ## or 0 if there was no `token` at ``s[start]``. - while result < token.len and s[result+start] == token[result]: inc(result) + while start+result < s.len and result < token.len and + s[result+start] == token[result]: + inc(result) if result != token.len: result = 0 proc skipIgnoreCase*(s, token: string, start = 0): int = ## same as `skip` but case is ignored for token matching. - while result < token.len and + while start+result < s.len and result < token.len and toLower(s[result+start]) == toLower(token[result]): inc(result) if result != token.len: result = 0 @@ -159,18 +159,18 @@ proc skipUntil*(s: string, until: set[char], start = 0): int {.inline.} = ## Skips all characters until one char from the set `until` is found ## or the end is reached. ## Returns number of characters skipped. - while s[result+start] notin until and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] notin until: inc(result) proc skipUntil*(s: string, until: char, start = 0): int {.inline.} = ## Skips all characters until the char `until` is found ## or the end is reached. ## Returns number of characters skipped. - while s[result+start] != until and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] != until: inc(result) proc skipWhile*(s: string, toSkip: set[char], start = 0): int {.inline.} = ## Skips all characters while one char from the set `token` is found. ## Returns number of characters skipped. - while s[result+start] in toSkip and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] in toSkip: inc(result) proc parseUntil*(s: string, token: var string, until: set[char], start = 0): int {.inline.} = @@ -214,7 +214,7 @@ proc parseWhile*(s: string, token: var string, validChars: set[char], ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters in `validChars`. var i = start - while s[i] in validChars: inc(i) + while i < s.len and s[i] in validChars: inc(i) result = i-start token = substr(s, start, i-1) @@ -231,16 +231,17 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var sign: BiggestInt = -1 i = start - if s[i] == '+': inc(i) - elif s[i] == '-': - inc(i) - sign = 1 - if s[i] in {'0'..'9'}: + if i < s.len: + if s[i] == '+': inc(i) + elif s[i] == '-': + inc(i) + sign = 1 + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: b = b * 10 - (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = b * sign result = i - start {.pop.} # overflowChecks @@ -281,17 +282,17 @@ proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = ## discard parseSaturatedNatural("848", res) ## doAssert res == 848 var i = start - if s[i] == '+': inc(i) - if s[i] in {'0'..'9'}: + if i < s.len and s[i] == '+': inc(i) + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: let c = ord(s[i]) - ord('0') if b <= (high(int) - c) div 10: b = b * 10 + c else: b = high(int) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start # overflowChecks doesn't work with BiggestUInt @@ -300,16 +301,16 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = res = 0.BiggestUInt prev = 0.BiggestUInt i = start - if s[i] == '+': inc(i) # Allow - if s[i] in {'0'..'9'}: + if i < s.len and s[i] == '+': inc(i) # Allow + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: prev = res res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt if prev > res: return 0 # overflowChecks emulation inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = res result = i - start @@ -389,31 +390,31 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind, var kind: InterpolatedKind while true: var j = i - if s[j] == '$': - if s[j+1] == '{': + if j < s.len and s[j] == '$': + if j+1 < s.len and s[j+1] == '{': inc j, 2 var nesting = 0 - while true: - case s[j] - of '{': inc nesting - of '}': - if nesting == 0: - inc j - break - dec nesting - of '\0': - raise newException(ValueError, - "Expected closing '}': " & substr(s, i, s.high)) - else: discard - inc j + block curlies: + while j < s.len: + case s[j] + of '{': inc nesting + of '}': + if nesting == 0: + inc j + break curlies + dec nesting + else: discard + inc j + raise newException(ValueError, + "Expected closing '}': " & substr(s, i, s.high)) inc i, 2 # skip ${ kind = ikExpr - elif s[j+1] in IdentStartChars: + elif j+1 < s.len and s[j+1] in IdentStartChars: inc j, 2 - while s[j] in IdentChars: inc(j) + while j < s.len and s[j] in IdentChars: inc(j) inc i # skip $ kind = ikVar - elif s[j+1] == '$': + elif j+1 < s.len and s[j+1] == '$': inc j, 2 inc i # skip $ kind = ikDollar diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 5ae2d9182..6d415efd0 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -534,15 +534,15 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. case p.kind of pkEmpty: result = 0 # match of length 0 of pkAny: - if s[start] != '\0': result = 1 + if start < s.len: result = 1 else: result = -1 of pkAnyRune: - if s[start] != '\0': + if start < s.len: result = runeLenAt(s, start) else: result = -1 of pkLetter: - if s[start] != '\0': + if start < s.len: var a: Rune result = start fastRuneAt(s, result, a) @@ -551,7 +551,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. else: result = -1 of pkLower: - if s[start] != '\0': + if start < s.len: var a: Rune result = start fastRuneAt(s, result, a) @@ -560,7 +560,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. else: result = -1 of pkUpper: - if s[start] != '\0': + if start < s.len: var a: Rune result = start fastRuneAt(s, result, a) @@ -569,7 +569,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. else: result = -1 of pkTitle: - if s[start] != '\0': + if start < s.len: var a: Rune result = start fastRuneAt(s, result, a) @@ -578,7 +578,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. else: result = -1 of pkWhitespace: - if s[start] != '\0': + if start < s.len: var a: Rune result = start fastRuneAt(s, result, a) @@ -589,15 +589,15 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. of pkGreedyAny: result = len(s) - start of pkNewLine: - if s[start] == '\L': result = 1 - elif s[start] == '\C': - if s[start+1] == '\L': result = 2 + if start < s.len and s[start] == '\L': result = 1 + elif start < s.len and s[start] == '\C': + if start+1 < s.len and s[start+1] == '\L': result = 2 else: result = 1 else: result = -1 of pkTerminal: result = len(p.term) for i in 0..result-1: - if p.term[i] != s[start+i]: + if start+i >= s.len or p.term[i] != s[start+i]: result = -1 break of pkTerminalIgnoreCase: @@ -606,6 +606,9 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. a, b: Rune result = start while i < len(p.term): + if result >= s.len: + result = -1 + break fastRuneAt(p.term, i, a) fastRuneAt(s, result, b) if toLower(a) != toLower(b): @@ -621,18 +624,23 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. while true: fastRuneAt(p.term, i, a) if a != Rune('_'): break - while true: + while result < s.len: fastRuneAt(s, result, b) if b != Rune('_'): break - if toLower(a) != toLower(b): + if result >= s.len: + if i >= p.term.len: break + else: + result = -1 + break + elif toLower(a) != toLower(b): result = -1 break dec(result, start) of pkChar: - if p.ch == s[start]: result = 1 + if start < s.len and p.ch == s[start]: result = 1 else: result = -1 of pkCharChoice: - if contains(p.charChoice[], s[start]): result = 1 + if start < s.len and contains(p.charChoice[], s[start]): result = 1 else: result = -1 of pkNonTerminal: var oldMl = c.ml @@ -695,10 +703,10 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. of pkGreedyRepChar: result = 0 var ch = p.ch - while ch == s[start+result]: inc(result) + while start+result < s.len and ch == s[start+result]: inc(result) of pkGreedyRepSet: result = 0 - while contains(p.charChoice[], s[start+result]): inc(result) + while start+result < s.len and contains(p.charChoice[], s[start+result]): inc(result) of pkOption: result = max(0, rawMatch(s, p.sons[0], start, c)) of pkAndPredicate: diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index abdb655d7..a8b128460 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -278,7 +278,7 @@ template callFormatOption(res, arg, option) {.dirty.} = macro `&`*(pattern: string): untyped = ## For a specification of the ``&`` macro, see the module level documentation. if pattern.kind notin {nnkStrLit..nnkTripleStrLit}: - error "& only works with string literals", pattern + error "string formatting (fmt(), &) only works with string literals", pattern let f = pattern.strVal var i = 0 let res = genSym(nskVar, "fmtRes") diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index f8a0c43ee..b0af149b5 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -87,7 +87,7 @@ which we then use in our scanf pattern to help us in the matching process: proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int = # Note: The parameters and return value must match to what ``scanf`` requires result = 0 - while input[start+result] in seps: inc result + while start+result < input.len and input[start+result] in seps: inc result if scanf(input, "$w$[someSep]$w", key, value): ... @@ -231,7 +231,7 @@ is performed. var i = start var u = 0 while true: - if s[i] == '\0' or s[i] == unless: + if i >= s.len or s[i] == unless: return 0 elif s[i] == until[0]: u = 1 @@ -315,6 +315,8 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b conds.add resLen.notZero conds.add resLen + template at(s: string; i: int): char = (if i < s.len: s[i] else: '\0') + var i = 0 var p = 0 var idx = genSym(nskVar, "idx") @@ -397,7 +399,7 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b var nesting = 0 let start = p while true: - case pattern[p] + case pattern.at(p) of '{': inc nesting of '}': if nesting == 0: break @@ -419,7 +421,7 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b var nesting = 0 let start = p while true: - case pattern[p] + case pattern.at(p) of '[': inc nesting of ']': if nesting == 0: break @@ -451,10 +453,12 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b template atom*(input: string; idx: int; c: char): bool = ## Used in scanp for the matching of atoms (usually chars). - input[idx] == c + idx < input.len and input[idx] == c template atom*(input: string; idx: int; s: set[char]): bool = - input[idx] in s + idx < input.len and input[idx] in s + +template hasNxt*(input: string; idx: int): bool = idx < input.len #template prepare*(input: string): int = 0 template success*(x: int): bool = x != 0 @@ -462,7 +466,7 @@ template success*(x: int): bool = x != 0 template nxt*(input: string; idx, step: int = 1) = inc(idx, step) macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = - ## ``scanp`` is currently undocumented. + ## See top level documentation of his module of how ``scanf`` works. type StmtTriple = tuple[init, cond, action: NimNode] template interf(x): untyped = bindSym(x, brForceOpen) @@ -508,8 +512,8 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = !!newCall(interf"nxt", input, idx, resLen)) of nnkCallKinds: # *{'A'..'Z'} !! s.add(!_) - template buildWhile(init, cond, action): untyped = - while true: + template buildWhile(input, idx, init, cond, action): untyped = + while hasNxt(input, idx): init if not cond: break action @@ -528,7 +532,7 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = !!newCall(interf"nxt", input, idx, it[2])) elif it.kind == nnkPrefix and it[0].eqIdent"*": let (init, cond, action) = atm(it[1], input, idx, attached) - result = (getAst(buildWhile(init, cond, action)), + result = (getAst(buildWhile(input, idx, init, cond, action)), newEmptyNode(), newEmptyNode()) elif it.kind == nnkPrefix and it[0].eqIdent"+": # x+ is the same as xx* @@ -621,7 +625,7 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = when isMainModule: proc twoDigits(input: string; x: var int; start: int): int = - if input[start] == '0' and input[start+1] == '0': + if start+1 < input.len and input[start] == '0' and input[start+1] == '0': result = 2 x = 13 else: @@ -629,10 +633,10 @@ when isMainModule: proc someSep(input: string; start: int; seps: set[char] = {';',',','-','.'}): int = result = 0 - while input[start+result] in seps: inc result + while start+result < input.len and input[start+result] in seps: inc result proc demangle(s: string; res: var string; start: int): int = - while s[result+start] in {'_', '@'}: inc result + while result+start < s.len and s[result+start] in {'_', '@'}: inc result res = "" while result+start < s.len and s[result+start] > ' ' and s[result+start] != '_': res.add s[result+start] @@ -652,7 +656,7 @@ when isMainModule: var info = "" if scanp(resp, idx, *`whites`, '#', *`digits`, +`whites`, ?("0x", *`hexdigits`, " in "), demangle($input, prc, $index), *`whites`, '(', * ~ ')', ')', - *`whites`, "at ", +(~{'\C', '\L', '\0'} -> info.add($_)) ): + *`whites`, "at ", +(~{'\C', '\L'} -> info.add($_)) ): result.add prc & " " & info else: break @@ -713,7 +717,7 @@ when isMainModule: "NimMainInner c:/users/anwender/projects/nim/lib/system.nim:2605", "NimMain c:/users/anwender/projects/nim/lib/system.nim:2613", "main c:/users/anwender/projects/nim/lib/system.nim:2620"] - doAssert parseGDB(gdbOut) == result + #doAssert parseGDB(gdbOut) == result # bug #6487 var count = 0 diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 75c5e171d..75f9b42cd 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -29,7 +29,7 @@ type modeCaseSensitive, ## the table is case sensitive modeCaseInsensitive, ## the table is case insensitive modeStyleInsensitive ## the table is style insensitive - KeyValuePair = tuple[key, val: string] + KeyValuePair = tuple[key, val: string, hasValue: bool] KeyValuePairSeq = seq[KeyValuePair] StringTableObj* = object of RootObj counter: int @@ -48,19 +48,19 @@ proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} = iterator pairs*(t: StringTableRef): tuple[key, value: string] = ## iterates over every (key, value) pair in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield (t.data[h].key, t.data[h].val) iterator keys*(t: StringTableRef): string = ## iterates over every key in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield t.data[h].key iterator values*(t: StringTableRef): string = ## iterates over every value in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield t.data[h].val type @@ -102,7 +102,7 @@ proc nextTry(h, maxHash: Hash): Hash {.inline.} = proc rawGet(t: StringTableRef, key: string): int = var h: Hash = myhash(t, key) and high(t.data) # start with real hash value - while not isNil(t.data[h].key): + while t.data[h].hasValue: if myCmp(t, t.data[h].key, key): return h h = nextTry(h, high(t.data)) @@ -144,16 +144,17 @@ proc contains*(t: StringTableRef, key: string): bool = proc rawInsert(t: StringTableRef, data: var KeyValuePairSeq, key, val: string) = var h: Hash = myhash(t, key) and high(data) - while not isNil(data[h].key): + while data[h].hasValue: h = nextTry(h, high(data)) data[h].key = key data[h].val = val + data[h].hasValue = true proc enlarge(t: StringTableRef) = var n: KeyValuePairSeq newSeq(n, len(t.data) * growthFactor) for i in countup(0, high(t.data)): - if not isNil(t.data[i].key): rawInsert(t, n, t.data[i].key, t.data[i].val) + if t.data[i].hasValue: rawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) proc `[]=`*(t: StringTableRef, key, val: string) {.rtlFunc, extern: "nstPut".} = @@ -198,8 +199,7 @@ proc clear*(s: StringTableRef, mode: StringTableMode) = s.counter = 0 s.data.setLen(startSize) for i in 0..<s.data.len: - if not isNil(s.data[i].key): - s.data[i].key = nil + s.data[i].hasValue = false proc newStringTable*(keyValuePairs: varargs[string], mode: StringTableMode): StringTableRef {. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index cdc5ec4f9..3f01f0285 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -106,6 +106,12 @@ proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, ## This checks ASCII characters only. return c in {'A'..'Z'} +template isImpl(call) = + if s.len == 0: return false + result = true + for c in s: + if not call(c): return false + proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaAsciiStr".} = ## Checks whether or not `s` is alphabetical. @@ -114,12 +120,7 @@ proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## alphabetic and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isAlphaAscii(): return false + isImpl isAlphaAscii proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaNumericStr".} = @@ -129,13 +130,7 @@ proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## alpanumeric and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isAlphaNumeric(): - return false + isImpl isAlphaNumeric proc isDigit*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsDigitStr".} = @@ -145,13 +140,7 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## numeric and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isDigit(): - return false + isImpl isDigit proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsSpaceAsciiStr".} = @@ -159,13 +148,7 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, ## ## Returns true if all characters in `s` are whitespace ## characters and there is at least one character in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isSpaceAscii(): - return false + isImpl isSpaceAscii proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsLowerAsciiStr".} = @@ -174,13 +157,7 @@ proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, ## This checks ASCII characters only. ## Returns true if all characters in `s` are lower case ## and there is at least one character in `s`. - if s.len() == 0: - return false - - for c in s: - if not c.isLowerAscii(): - return false - true + isImpl isLowerAscii proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsUpperAsciiStr".} = @@ -189,13 +166,7 @@ proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, ## This checks ASCII characters only. ## Returns true if all characters in `s` are upper case ## and there is at least one character in `s`. - if s.len() == 0: - return false - - for c in s: - if not c.isUpperAscii(): - return false - true + isImpl isUpperAscii proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = @@ -209,6 +180,11 @@ proc toLowerAscii*(c: char): char {.noSideEffect, procvar, else: result = c +template toImpl(call) = + result = newString(len(s)) + for i in 0..len(s) - 1: + result[i] = call(s[i]) + proc toLowerAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiStr".} = ## Converts `s` into lower case. @@ -216,9 +192,7 @@ proc toLowerAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. - result = newString(len(s)) - for i in 0..len(s) - 1: - result[i] = toLowerAscii(s[i]) + toImpl toLowerAscii proc toUpperAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiChar".} = @@ -239,147 +213,15 @@ proc toUpperAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. - result = newString(len(s)) - for i in 0..len(s) - 1: - result[i] = toUpperAscii(s[i]) + toImpl toUpperAscii proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuCapitalizeAscii".} = ## Converts the first character of `s` into upper case. ## ## This works only for the letters ``A-Z``. - result = toUpperAscii(s[0]) & substr(s, 1) - -proc isSpace*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsSpaceChar".}= - ## Checks whether or not `c` is a whitespace character. - ## - ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. - isSpaceAscii(c) - -proc isLower*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsLowerChar".}= - ## Checks whether or not `c` is a lower case character. - ## - ## This checks ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. - isLowerAscii(c) - -proc isUpper*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsUpperChar".}= - ## Checks whether or not `c` is an upper case character. - ## - ## This checks ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. - isUpperAscii(c) - -proc isAlpha*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsAlphaChar".}= - ## Checks whether or not `c` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. - isAlphaAscii(c) - -proc isAlpha*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsAlphaStr".}= - ## Checks whether or not `s` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## Returns true if all characters in `s` are - ## alphabetic and there is at least one character - ## in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. - isAlphaAscii(s) - -proc isSpace*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsSpaceStr".}= - ## Checks whether or not `s` is completely whitespace. - ## - ## Returns true if all characters in `s` are whitespace - ## characters and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. - isSpaceAscii(s) - -proc isLower*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsLowerStr".}= - ## Checks whether or not `s` contains all lower case characters. - ## - ## This checks ASCII characters only. - ## Returns true if all characters in `s` are lower case - ## and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. - isLowerAscii(s) - -proc isUpper*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsUpperStr".}= - ## Checks whether or not `s` contains all upper case characters. - ## - ## This checks ASCII characters only. - ## Returns true if all characters in `s` are upper case - ## and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. - isUpperAscii(s) - -proc toLower*(c: char): char {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToLowerChar".} = - ## Converts `c` into lower case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toLower - ## <unicode.html#toLower>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. - toLowerAscii(c) - -proc toLower*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToLowerStr".} = - ## Converts `s` into lower case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toLower - ## <unicode.html#toLower>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. - toLowerAscii(s) - -proc toUpper*(c: char): char {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToUpperChar".} = - ## Converts `c` into upper case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toUpper - ## <unicode.html#toUpper>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. - toUpperAscii(c) - -proc toUpper*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToUpperStr".} = - ## Converts `s` into upper case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toUpper - ## <unicode.html#toUpper>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. - toUpperAscii(s) - -proc capitalize*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuCapitalize".} = - ## Converts the first character of `s` into upper case. - ## - ## This works only for the letters ``A-Z``. - ## - ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead. - capitalizeAscii(s) + if s.len == 0: result = "" + else: result = toUpperAscii(s[0]) & substr(s, 1) proc normalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuNormalize".} = @@ -419,9 +261,9 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreStyle", procvar.} = ## Semantically the same as ``cmp(normalize(a), normalize(b))``. It - ## is just optimized to not allocate temporary strings. This should + ## is just optimized to not allocate temporary strings. This should ## NOT be used to compare Nim identifier names. use `macros.eqIdent` - ## for that. Returns: + ## for that. Returns: ## ## | 0 iff a == b ## | < 0 iff a < b @@ -429,14 +271,22 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, var i = 0 var j = 0 while true: - while a[i] == '_': inc(i) - while b[j] == '_': inc(j) # BUGFIX: typo - var aa = toLowerAscii(a[i]) - var bb = toLowerAscii(b[j]) + while i < a.len and a[i] == '_': inc i + while j < b.len and b[j] == '_': inc j + var aa = if i < a.len: toLowerAscii(a[i]) else: '\0' + var bb = if j < b.len: toLowerAscii(b[j]) else: '\0' result = ord(aa) - ord(bb) - if result != 0 or aa == '\0': break - inc(i) - inc(j) + if result != 0: return result + # the characters are identical: + if i >= a.len: + # both cursors at the end: + if j >= b.len: return 0 + # not yet at the end of 'b': + return -1 + elif j >= b.len: + return 1 + inc i + inc j proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string @@ -451,7 +301,7 @@ proc strip*(s: string, leading = true, trailing = true, first = 0 last = len(s)-1 if leading: - while s[first] in chars: inc(first) + while first <= last and s[first] in chars: inc(first) if trailing: while last >= 0 and s[last] in chars: dec(last) result = substr(s, first, last) @@ -467,7 +317,9 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 -proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} = +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, + extern: "nsuIsNilOrEmpty", + deprecated: "use 'x.len == 0' instead".} = ## Checks if `s` is nil or empty. result = len(s) == 0 @@ -486,7 +338,6 @@ proc substrEq(s: string, pos: int, substr: string): bool = var length = substr.len while i < length and s[pos+i] == substr[i]: inc i - return i == length # --------- Private templates for different split separators ----------- @@ -520,7 +371,7 @@ template oldSplit(s, seps, maxsplit) = var splits = maxsplit assert(not ('\0' in seps)) while last < len(s): - while s[last] in seps: inc(last) + while last < len(s) and s[last] in seps: inc(last) var first = last while last < len(s) and s[last] notin seps: inc(last) if first <= last-1: @@ -571,10 +422,7 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## - when defined(nimOldSplit): - oldSplit(s, seps, maxsplit) - else: - splitCommon(s, seps, maxsplit, 1) + splitCommon(s, seps, maxsplit, 1) iterator splitWhitespace*(s: string, maxsplit: int = -1): string = ## Splits the string ``s`` at whitespace stripping leading and trailing @@ -660,7 +508,6 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## "is" ## "corrupted" ## - splitCommon(s, sep, maxsplit, sep.len) template rsplitCommon(s, sep, maxsplit, sepLen) = @@ -670,29 +517,21 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = first = last splits = maxsplit startPos = 0 - # go to -1 in order to get separators at the beginning while first >= -1: while first >= 0 and not stringHasSep(s, first, sep): dec(first) - if splits == 0: # No more splits means set first to the beginning first = -1 - if first == -1: startPos = 0 else: startPos = first + sepLen - yield substr(s, startPos, last) - - if splits == 0: - break - + if splits == 0: break dec(splits) dec(first) - last = first iterator rsplit*(s: string, seps: set[char] = Whitespace, @@ -712,7 +551,6 @@ iterator rsplit*(s: string, seps: set[char] = Whitespace, ## "foo" ## ## Substrings are separated from the right by the set of chars `seps` - rsplitCommon(s, seps, maxsplit, 1) iterator rsplit*(s: string, sep: char, @@ -779,14 +617,14 @@ iterator splitLines*(s: string): string = var first = 0 var last = 0 while true: - while s[last] notin {'\0', '\c', '\l'}: inc(last) + while last < s.len and s[last] notin {'\c', '\l'}: inc(last) yield substr(s, first, last-1) # skip newlines: + if last >= s.len: break if s[last] == '\l': inc(last) elif s[last] == '\c': inc(last) - if s[last] == '\l': inc(last) - else: break # was '\0' + if last < s.len and s[last] == '\l': inc(last) first = last proc splitLines*(s: string): seq[string] {.noSideEffect, @@ -811,7 +649,7 @@ proc countLines*(s: string): int {.noSideEffect, while i < s.len: case s[i] of '\c': - if s[i+1] == '\l': inc i + if i+1 < s.len and s[i+1] == '\l': inc i inc result of '\l': inc result else: discard @@ -1025,9 +863,9 @@ proc parseHexInt*(s: string): int {.noSideEffect, procvar, ## of the following optional prefixes: ``0x``, ``0X``, ``#``. Underscores ## within `s` are ignored. var i = 0 - if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) - elif s[i] == '#': inc(i) - while true: + if i+1 < s.len and s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) + elif i < s.len and s[i] == '#': inc(i) + while i < s.len: case s[i] of '_': inc(i) of '0'..'9': @@ -1039,7 +877,6 @@ proc parseHexInt*(s: string): int {.noSideEffect, procvar, of 'A'..'F': result = result shl 4 or (ord(s[i]) - ord('A') + 10) inc(i) - of '\0': break else: raise newException(ValueError, "invalid integer: " & s) proc generateHexCharToValueMap(): string = @@ -1148,14 +985,6 @@ template spaces*(n: Natural): string = repeat(' ', n) ## echo text1 & spaces(max(0, width - text1.len)) & "|" ## echo text2 & spaces(max(0, width - text2.len)) & "|" -proc repeatChar*(count: Natural, c: char = ' '): string {.deprecated.} = - ## deprecated: use repeat() or spaces() - repeat(c, count) - -proc repeatStr*(count: Natural, s: string): string {.deprecated.} = - ## deprecated: use repeat(string, count) or string.repeat(count) - repeat(s, count) - proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = ## Aligns a string `s` with `padding`, so that it is of length `count`. @@ -1226,7 +1055,7 @@ iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ var i = 0 while true: var j = i - var isSep = s[j] in seps + var isSep = j < s.len and s[j] in seps while j < s.len and (s[j] in seps) == isSep: inc(j) if j > i: yield (substr(s, i, j-1), isSep) @@ -1297,7 +1126,7 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string var indentCount = 0 for j in 0..<count.int: indentCount.inc - if line[j .. j + padding.len-1] != padding: + if j + padding.len-1 >= line.len or line[j .. j + padding.len-1] != padding: indentCount = j break result.add(line[indentCount*padding.len .. ^1]) @@ -1325,13 +1154,13 @@ proc startsWith*(s, prefix: string): bool {.noSideEffect, ## If ``prefix == ""`` true is returned. var i = 0 while true: - if prefix[i] == '\0': return true - if s[i] != prefix[i]: return false + if i >= prefix.len: return true + if i >= s.len or s[i] != prefix[i]: return false inc(i) proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` starts with ``prefix``. - result = s[0] == prefix + result = s.len > 0 and s[0] == prefix proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = @@ -1343,11 +1172,11 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, while i+j <% s.len: if s[i+j] != suffix[i]: return false inc(i) - if suffix[i] == '\0': return true + if i >= suffix.len: return true proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` ends with ``suffix``. - result = s[s.high] == suffix + result = s.len > 0 and s[s.high] == suffix proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = @@ -1356,8 +1185,8 @@ proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, ## If ``substr == ""`` true is returned. var i = 0 while true: - if substr[i] == '\0': return true - if s[i+start] != substr[i]: return false + if i >= substr.len: return true + if i+start >= s.len or s[i+start] != substr[i]: return false inc(i) proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) @@ -1502,12 +1331,8 @@ proc find*(s, sub: string, start: Natural = 0, last: Natural = 0): int {.noSideE ## If `last` is unspecified, it defaults to `s.high`. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - if sub.len > s.len: - return -1 - - if sub.len == 1: - return find(s, sub[0], start, last) - + if sub.len > s.len: return -1 + if sub.len == 1: return find(s, sub[0], start, last) var a {.noinit.}: SkipTable initSkipTable(a, sub) result = find(a, s, sub, start, last) @@ -1564,18 +1389,14 @@ proc center*(s: string, width: int, fillChar: char = ' '): string {. ## ## The original string is returned if `width` is less than or equal ## to `s.len`. - if width <= s.len: - return s - + if width <= s.len: return s result = newString(width) - # Left padding will be one fillChar # smaller if there are an odd number # of characters let charsLeft = (width - s.len) leftPadding = charsLeft div 2 - for i in 0 ..< width: if i >= leftPadding and i < leftPadding + s.len: # we are where the string should be located @@ -1593,27 +1414,22 @@ proc count*(s: string, sub: string, overlapping: bool = false): int {. var i = 0 while true: i = s.find(sub, i) - if i < 0: - break - if overlapping: - inc i - else: - i += sub.len + if i < 0: break + if overlapping: inc i + else: i += sub.len inc result proc count*(s: string, sub: char): int {.noSideEffect, rtl, extern: "nsuCountChar".} = ## Count the occurrences of the character `sub` in the string `s`. for c in s: - if c == sub: - inc result + if c == sub: inc result proc count*(s: string, subs: set[char]): int {.noSideEffect, rtl, extern: "nsuCountCharSet".} = ## Count the occurrences of the group of character `subs` in the string `s`. for c in s: - if c in subs: - inc result + if c in subs: inc result proc quoteIfContainsWhite*(s: string): string {.deprecated.} = ## Returns ``'"' & s & '"'`` if `s` contains a space and does not @@ -1621,10 +1437,8 @@ proc quoteIfContainsWhite*(s: string): string {.deprecated.} = ## ## **DEPRECATED** as it was confused for shell quoting function. For this ## application use `osproc.quoteShell <osproc.html#quoteShell>`_. - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': - result = '"' & s & '"' - else: - result = s + if find(s, {' ', '\t'}) >= 0 and s[0] != '"': result = '"' & s & '"' + else: result = s proc contains*(s: string, c: char): bool {.noSideEffect.} = ## Same as ``find(s, c) >= 0``. @@ -1704,9 +1518,8 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { ## Same as replace, but specialized for doing multiple replacements in a single ## pass through the input string. ## - ## Calling replace multiple times after each other is inefficient and result in too many allocations - ## follwed by immediate deallocations as portions of the string gets replaced. - ## multiReplace performs all replacements in a single pass. + ## multiReplace performs all replacements in a single pass, this means it can be used + ## to swap the occurences of "a" and "b", for instance. ## ## If the resulting string is not longer than the original input string, only a single ## memory allocation is required. @@ -1753,14 +1566,13 @@ proc parseOctInt*(s: string): int {.noSideEffect, ## of the following optional prefixes: ``0o``, ``0O``. Underscores within ## `s` are ignored. var i = 0 - if s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) - while true: + if i+1 < s.len and s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) + while i < s.len: case s[i] of '_': inc(i) of '0'..'7': result = result shl 3 or (ord(s[i]) - ord('0')) inc(i) - of '\0': break else: raise newException(ValueError, "invalid integer: " & s) proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, @@ -1849,16 +1661,18 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, ## If `s` does not begin with ``prefix`` and end with ``suffix`` a ## ValueError exception will be raised. ## - ## **Warning:** This procedure is deprecated because it's to easy to missuse. + ## **Warning:** This procedure is deprecated because it's to easy to missuse. result = newStringOfCap(s.len) var i = prefix.len if not s.startsWith(prefix): raise newException(ValueError, - "String does not start with a prefix of: " & prefix) + "String does not start with: " & prefix) while true: - if i == s.len-suffix.len: break - case s[i] - of '\\': + if i >= s.len-suffix.len: break + if s[i] == '\\': + if i+1 >= s.len: + result.add('\\') + break case s[i+1]: of 'x': inc i, 2 @@ -1872,15 +1686,15 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, result.add('\'') of '\"': result.add('\"') - else: result.add("\\" & s[i+1]) - inc(i) - of '\0': break + else: + result.add("\\" & s[i+1]) + inc(i, 2) else: result.add(s[i]) - inc(i) + inc(i) if not s.endsWith(suffix): raise newException(ValueError, - "String does not end with a suffix of: " & suffix) + "String does not end in: " & suffix) proc validIdentifier*(s: string): bool {.noSideEffect, rtl, extern: "nsuValidIdentifier".} = @@ -1890,7 +1704,7 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## and is followed by any number of characters of the set `IdentChars`. runnableExamples: doAssert "abc_def08".validIdentifier - if s[0] in IdentStartChars: + if s.len > 0 and s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false return true @@ -1909,7 +1723,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, # strip common prefix: var s = 0 - while a[s] == b[s] and a[s] != '\0': + while s < len1 and a[s] == b[s]: inc(s) dec(len1) dec(len2) @@ -1982,8 +1796,6 @@ proc editDistance*(a, b: string): int {.noSideEffect, if x > c3: x = c3 row[p] = x result = row[e] - #dealloc(row) - # floating point formating: when not defined(js): @@ -2092,7 +1904,7 @@ proc trimZeros*(x: var string) {.noSideEffect.} = var spl: seq[string] if x.contains('.') or x.contains(','): if x.contains('e'): - spl= x.split('e') + spl = x.split('e') x = spl[0] while x[x.high] == '0': x.setLen(x.len-1) @@ -2310,9 +2122,8 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. var i = 0 var num = 0 while i < len(formatstr): - if formatstr[i] == '$': - case formatstr[i+1] # again we use the fact that strings - # are zero-terminated here + if formatstr[i] == '$' and i+1 < len(formatstr): + case formatstr[i+1] of '#': if num > a.high: invalidFormatString() add s, a[num] @@ -2326,7 +2137,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. inc(i) # skip $ var negative = formatstr[i] == '-' if negative: inc i - while formatstr[i] in Digits: + while i < formatstr.len and formatstr[i] in Digits: j = j * 10 + ord(formatstr[i]) - ord('0') inc(i) let idx = if not negative: j-1 else: a.len-j @@ -2338,7 +2149,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. var negative = formatstr[j] == '-' if negative: inc j var isNumber = 0 - while formatstr[j] notin {'\0', '}'}: + while j < formatstr.len and formatstr[j] notin {'\0', '}'}: if formatstr[j] in Digits: k = k * 10 + ord(formatstr[j]) - ord('0') if isNumber == 0: isNumber = 1 @@ -2356,7 +2167,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. i = j+1 of 'a'..'z', 'A'..'Z', '\128'..'\255', '_': var j = i+1 - while formatstr[j] in PatternChars: inc(j) + while j < formatstr.len and formatstr[j] in PatternChars: inc(j) var x = findNormalized(substr(formatstr, i+1, j-1), a) if x >= 0 and x < high(a): add s, a[x+1] else: invalidFormatString() @@ -2628,13 +2439,7 @@ when isMainModule: doAssert isSpaceAscii(" ") doAssert(not isSpaceAscii("ABc \td")) - doAssert(isNilOrEmpty("")) - doAssert(isNilOrEmpty(nil)) - doAssert(not isNilOrEmpty("test")) - doAssert(not isNilOrEmpty(" ")) - doAssert(isNilOrWhitespace("")) - doAssert(isNilOrWhitespace(nil)) doAssert(isNilOrWhitespace(" ")) doAssert(isNilOrWhitespace("\t\l \v\r\f")) doAssert(not isNilOrWhitespace("ABc \td")) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 7d101beab..bc8a50fd7 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1318,24 +1318,23 @@ proc format*(dt: DateTime, f: string): string {.tags: [].}= result = "" var i = 0 var currentF = "" - while true: + while i < f.len: case f[i] - of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': + of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': formatToken(dt, currentF, result) currentF = "" - if f[i] == '\0': break if f[i] == '\'': inc(i) # Skip ' - while f[i] != '\'' and f.len-1 > i: + while i < f.len-1 and f[i] != '\'': result.add(f[i]) inc(i) else: result.add(f[i]) else: # Check if the letter being added matches previous accumulated buffer. - if currentF.len < 1 or currentF[high(currentF)] == f[i]: + if currentF.len == 0 or currentF[high(currentF)] == f[i]: currentF.add(f[i]) else: formatToken(dt, currentF, result) @@ -1343,6 +1342,7 @@ proc format*(dt: DateTime, f: string): string {.tags: [].}= currentF = "" inc(i) + formatToken(dt, currentF, result) proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = ## Converts a `DateTime` object to a string representation. @@ -1439,58 +1439,58 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) = dt.month = month.Month of "MMM": case value[j..j+2].toLowerAscii(): - of "jan": dt.month = mJan - of "feb": dt.month = mFeb - of "mar": dt.month = mMar - of "apr": dt.month = mApr - of "may": dt.month = mMay - of "jun": dt.month = mJun - of "jul": dt.month = mJul - of "aug": dt.month = mAug - of "sep": dt.month = mSep - of "oct": dt.month = mOct - of "nov": dt.month = mNov - of "dec": dt.month = mDec + of "jan": dt.month = mJan + of "feb": dt.month = mFeb + of "mar": dt.month = mMar + of "apr": dt.month = mApr + of "may": dt.month = mMay + of "jun": dt.month = mJun + of "jul": dt.month = mJul + of "aug": dt.month = mAug + of "sep": dt.month = mSep + of "oct": dt.month = mOct + of "nov": dt.month = mNov + of "dec": dt.month = mDec else: raise newException(ValueError, "Couldn't parse month (MMM), got: " & value) j += 3 of "MMMM": if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: - dt.month = mJan + dt.month = mJan j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - dt.month = mFeb + dt.month = mFeb j += 8 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - dt.month = mMar + dt.month = mMar j += 5 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - dt.month = mApr + dt.month = mApr j += 5 elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - dt.month = mMay + dt.month = mMay j += 3 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - dt.month = mJun + dt.month = mJun j += 4 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - dt.month = mJul + dt.month = mJul j += 4 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - dt.month = mAug + dt.month = mAug j += 6 elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - dt.month = mSep + dt.month = mSep j += 9 elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - dt.month = mOct + dt.month = mOct j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - dt.month = mNov + dt.month = mNov j += 8 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - dt.month = mDec + dt.month = mDec j += 8 else: raise newException(ValueError, @@ -1521,44 +1521,47 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) = j += 4 of "z": dt.isDst = false - if value[j] == '+': + let ch = if j < value.len: value[j] else: '\0' + if ch == '+': dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour - elif value[j] == '-': + elif ch == '-': dt.utcOffset = parseInt($value[j+1]) * secondsInHour - elif value[j] == 'Z': + elif ch == 'Z': dt.utcOffset = 0 j += 1 return else: raise newException(ValueError, - "Couldn't parse timezone offset (z), got: " & value[j]) + "Couldn't parse timezone offset (z), got: " & ch) j += 2 of "zz": dt.isDst = false - if value[j] == '+': + let ch = if j < value.len: value[j] else: '\0' + if ch == '+': dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour - elif value[j] == '-': + elif ch == '-': dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour - elif value[j] == 'Z': + elif ch == 'Z': dt.utcOffset = 0 j += 1 return else: raise newException(ValueError, - "Couldn't parse timezone offset (zz), got: " & value[j]) + "Couldn't parse timezone offset (zz), got: " & ch) j += 3 of "zzz": dt.isDst = false var factor = 0 - if value[j] == '+': factor = -1 - elif value[j] == '-': factor = 1 - elif value[j] == 'Z': + let ch = if j < value.len: value[j] else: '\0' + if ch == '+': factor = -1 + elif ch == '-': factor = 1 + elif ch == 'Z': dt.utcOffset = 0 j += 1 return else: raise newException(ValueError, - "Couldn't parse timezone offset (zzz), got: " & value[j]) + "Couldn't parse timezone offset (zzz), got: " & ch) dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour j += 4 dt.utcOffset += factor * value[j..j+1].parseInt() * 60 @@ -1620,20 +1623,18 @@ proc parse*(value, layout: string, zone: Timezone = local()): DateTime = dt.nanosecond = 0 dt.isDst = true # using this is flag for checking whether a timezone has \ # been read (because DST is always false when a tz is parsed) - while true: + while i < layout.len: case layout[i] - of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': + of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': if token.len > 0: parseToken(dt, token, value, j) # Reset token token = "" - # Break if at end of line - if layout[i] == '\0': break # Skip separator and everything between single quotes # These are literals in both the layout and the value string if layout[i] == '\'': inc(i) - while layout[i] != '\'' and layout.len-1 > i: + while i < layout.len-1 and layout[i] != '\'': inc(i) inc(j) inc(i) @@ -1642,13 +1643,15 @@ proc parse*(value, layout: string, zone: Timezone = local()): DateTime = inc(j) else: # Check if the letter being added matches previous accumulated buffer. - if token.len < 1 or token[high(token)] == layout[i]: + if token.len == 0 or token[high(token)] == layout[i]: token.add(layout[i]) inc(i) else: parseToken(dt, token, value, j) token = "" + if i >= layout.len and token.len > 0: + parseToken(dt, token, value, j) if dt.isDst: # No timezone parsed - assume timezone is `zone` result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index d2d11253a..9bf25b86b 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -60,7 +60,7 @@ proc encodeUrl*(s: string): string = else: add(result, '%') add(result, toHex(ord(s[i]), 2)) - + proc decodeUrl*(s: string): string = ## Decodes a value from its HTTP representation: This means that a ``'+'`` ## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal @@ -72,7 +72,7 @@ proc decodeUrl*(s: string): string = of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) else: assert(false) - + result = newString(s.len) var i = 0 var j = 0 @@ -94,7 +94,7 @@ proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false var inIPv6 = false - while true: + while i < authority.len: case authority[i] of '@': swap result.password, result.port @@ -111,7 +111,6 @@ proc parseAuthority(authority: string, result: var Uri) = inIPv6 = true of ']': inIPv6 = false - of '\0': break else: if inPort: result.port.add(authority[i]) @@ -128,11 +127,11 @@ proc parsePath(uri: string, i: var int, result: var Uri) = parseAuthority(result.path, result) result.path.setLen(0) - if uri[i] == '?': + if i < uri.len and uri[i] == '?': i.inc # Skip '?' i.inc parseUntil(uri, result.query, {'#'}, i) - if uri[i] == '#': + if i < uri.len and uri[i] == '#': i.inc # Skip '#' i.inc parseUntil(uri, result.anchor, {}, i) @@ -156,7 +155,7 @@ proc parseUri*(uri: string, result: var Uri) = # Check if this is a reference URI (relative URI) let doubleSlash = uri.len > 1 and uri[1] == '/' - if uri[i] == '/': + if i < uri.len and uri[i] == '/': # Make sure ``uri`` doesn't begin with '//'. if not doubleSlash: parsePath(uri, i, result) @@ -164,7 +163,7 @@ proc parseUri*(uri: string, result: var Uri) = # Scheme i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i) - if uri[i] != ':' and not doubleSlash: + if (i >= uri.len or uri[i] != ':') and not doubleSlash: # Assume this is a reference URI (relative URI) i = 0 result.scheme.setLen(0) @@ -174,7 +173,7 @@ proc parseUri*(uri: string, result: var Uri) = i.inc # Skip ':' # Authority - if uri[i] == '/' and uri[i+1] == '/': + if i+1 < uri.len and uri[i] == '/' and uri[i+1] == '/': i.inc(2) # Skip // var authority = "" i.inc parseUntil(uri, authority, {'/', '?', '#'}, i) @@ -197,13 +196,13 @@ proc removeDotSegments(path: string): string = let endsWithSlash = path[path.len-1] == '/' var i = 0 var currentSegment = "" - while true: + while i < path.len: case path[i] of '/': collection.add(currentSegment) currentSegment = "" of '.': - if path[i+1] == '.' and path[i+2] == '/': + if i+2 < path.len and path[i+1] == '.' and path[i+2] == '/': if collection.len > 0: discard collection.pop() i.inc 3 @@ -212,13 +211,11 @@ proc removeDotSegments(path: string): string = i.inc 2 continue currentSegment.add path[i] - of '\0': - if currentSegment != "": - collection.add currentSegment - break else: currentSegment.add path[i] i.inc + if currentSegment != "": + collection.add currentSegment result = collection.join("/") if endsWithSlash: result.add '/' @@ -320,18 +317,18 @@ proc `/`*(x: Uri, path: string): Uri = result = x if result.path.len == 0: - if path[0] != '/': + if path.len == 0 or path[0] != '/': result.path = "/" result.path.add(path) return - if result.path[result.path.len-1] == '/': - if path[0] == '/': + if result.path.len > 0 and result.path[result.path.len-1] == '/': + if path.len > 0 and path[0] == '/': result.path.add(path[1 .. path.len-1]) else: result.path.add(path) else: - if path[0] != '/': + if path.len == 0 or path[0] != '/': result.path.add '/' result.path.add(path) @@ -373,7 +370,7 @@ when isMainModule: const test1 = "abc\L+def xyz" doAssert encodeUrl(test1) == "abc%0A%2Bdef+xyz" doAssert decodeUrl(encodeUrl(test1)) == test1 - + block: let str = "http://localhost" let test = parseUri(str) @@ -464,7 +461,7 @@ when isMainModule: doAssert test.hostname == "github.com" doAssert test.port == "dom96" doAssert test.path == "/packages" - + block: let str = "file:///foo/bar/baz.txt" let test = parseUri(str) diff --git a/lib/pure/xmldom.nim b/lib/pure/xmldom.nim index 3c891c81b..c38d36026 100644 --- a/lib/pure/xmldom.nim +++ b/lib/pure/xmldom.nim @@ -232,10 +232,10 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str raise newException(EInvalidCharacterErr, "Invalid character") # Exceptions if qualifiedName.contains(':'): - let qfnamespaces = qualifiedName.toLower().split(':') + let qfnamespaces = qualifiedName.toLowerAscii().split(':') if isNil(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") - elif qfnamespaces[0] == "xml" and + elif qfnamespaces[0] == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and qfnamespaces[1] notin stdattrnames: raise newException(ENamespaceErr, @@ -311,10 +311,10 @@ proc createElement*(doc: PDocument, tagName: string): PElement = proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = ## Creates an element of the given qualified name and namespace URI. if qualifiedName.contains(':'): - let qfnamespaces = qualifiedName.toLower().split(':') + let qfnamespaces = qualifiedName.toLowerAscii().split(':') if isNil(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") - elif qfnamespaces[0] == "xml" and + elif qfnamespaces[0] == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and qfnamespaces[1] notin stdattrnames: raise newException(ENamespaceErr, @@ -533,13 +533,13 @@ proc `prefix=`*(n: PNode, value: string) = if isNil(n.fNamespaceURI): raise newException(ENamespaceErr, "namespaceURI cannot be nil") - elif value.toLower() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": + elif value.toLowerAscii() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") - elif value.toLower() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": + elif value.toLowerAscii() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") - elif value.toLower() == "xmlns" and n.fNodeType == AttributeNode: + elif value.toLowerAscii() == "xmlns" and n.fNodeType == AttributeNode: raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") n.fNodeName = value & ":" & n.fLocalName diff --git a/lib/system.nim b/lib/system.nim index 98c133428..0c8659fda 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1280,15 +1280,13 @@ proc setLen*[T](s: var seq[T], newlen: Natural) {. ## sets the length of `s` to `newlen`. ## ``T`` may be any sequence type. ## If the current length is greater than the new length, - ## ``s`` will be truncated. `s` cannot be nil! To initialize a sequence with - ## a size, use ``newSeq`` instead. + ## ``s`` will be truncated. proc setLen*(s: var string, newlen: Natural) {. magic: "SetLengthStr", noSideEffect.} ## sets the length of `s` to `newlen`. ## If the current length is greater than the new length, - ## ``s`` will be truncated. `s` cannot be nil! To initialize a string with - ## a size, use ``newString`` instead. + ## ``s`` will be truncated. ## ## .. code-block:: Nim ## var myS = "Nim is great!!" @@ -2414,8 +2412,9 @@ proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} = if seqToPtr(x) == seqToPtr(y): return true - if x.isNil or y.isNil: - return false + when not defined(nimNoNil): + if x.isNil or y.isNil: + return false if x.len != y.len: return false @@ -3224,7 +3223,7 @@ when not defined(JS): #and not defined(nimscript): when declared(initGC): initGC() when not defined(nimscript): - proc setControlCHook*(hook: proc () {.noconv.} not nil) + proc setControlCHook*(hook: proc () {.noconv.}) ## allows you to override the behaviour of your application when CTRL+C ## is pressed. Only one such hook is supported. @@ -4012,18 +4011,18 @@ proc addQuoted*[T](s: var string, x: T) = when hasAlloc: # XXX: make these the default (or implement the NilObject optimization) - proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect.} = + proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x`` unless ``x`` is not yet initialized; in that case, ## ``x`` becomes ``@[y]`` if x == nil: x = @[y] else: x.add(y) - proc safeAdd*(x: var string, y: char) = + proc safeAdd*(x: var string, y: char) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x``. If ``x`` is ``nil`` it is initialized to ``""`` if x == nil: x = "" x.add(y) - proc safeAdd*(x: var string, y: string) = + proc safeAdd*(x: var string, y: string) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x`` unless ``x`` is not yet initalized; in that ## case, ``x`` becomes ``y`` if x == nil: x = y diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index a14f43e7e..46e84e056 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -40,4 +40,4 @@ proc reraiseException() {.compilerRtl.} = proc writeStackTrace() = discard -proc setControlCHook(hook: proc () {.noconv.} not nil) = discard +proc setControlCHook(hook: proc () {.noconv.}) = discard diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index afeab2b6c..fb38948f7 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -56,7 +56,7 @@ var # list of exception handlers # a global variable for the root of all try blocks currException {.threadvar.}: ref Exception - raise_counter {.threadvar.}: uint + raise_counter {.threadvar.}: uint gcFramePtr {.threadvar.}: GcFrame @@ -126,10 +126,10 @@ proc popCurrentExceptionEx(id: uint) {.compilerRtl.} = while cur != nil and cur.raise_id != id: prev = cur cur = cur.up - if cur == nil: + if cur == nil: showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...") quitOrDebug() - prev.up = cur.up + prev.up = cur.up # some platforms have native support for stack traces: const @@ -503,7 +503,7 @@ when not defined(noSignalHandler) and not defined(useNimRtl): registerSignalHandler() # call it in initialization section -proc setControlCHook(hook: proc () {.noconv.} not nil) = +proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: type SignalHandler = proc (sign: cint) {.noconv, benign.} c_signal(SIGINT, cast[SignalHandler](hook)) diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index ea0273340..bba59e930 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -22,21 +22,34 @@ proc resize(old: int): int {.inline.} = proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = if a == b: return 0 - if a == nil: return -1 - if b == nil: return 1 - let minlen = min(a.len, b.len) + when defined(nimNoNil): + let alen = if a == nil: 0 else: a.len + let blen = if b == nil: 0 else: b.len + else: + if a == nil: return -1 + if b == nil: return 1 + let alen = a.len + let blen = b.len + let minlen = min(alen, blen) if minlen > 0: result = c_memcmp(addr a.data, addr b.data, minlen.csize) if result == 0: - result = a.len - b.len + result = alen - blen else: - result = a.len - b.len + result = alen - blen proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = if a == b: return true - if a == nil or b == nil: return false - return a.len == b.len and - equalMem(addr(a.data), addr(b.data), a.len) + when defined(nimNoNil): + let alen = if a == nil: 0 else: a.len + let blen = if b == nil: 0 else: b.len + else: + if a == nil or b == nil: return false + let alen = a.len + let blen = b.len + if alen == blen: + if alen == 0: return true + return equalMem(addr(a.data), addr(b.data), alen) when declared(allocAtomic): template allocStr(size: untyped): untyped = @@ -101,9 +114,6 @@ proc cstrToNimstr(str: cstring): NimString {.compilerRtl.} = if str == nil: NimString(nil) else: toNimStr(str, str.len) -template wasMoved(x: NimString): bool = false -# (x.reserved and seqShallowFlag) != 0 - proc copyString(src: NimString): NimString {.compilerRtl.} = if src != nil: if (src.reserved and seqShallowFlag) != 0: @@ -161,14 +171,16 @@ proc hashString(s: string): int {.compilerproc.} = proc addChar(s: NimString, c: char): NimString = # is compilerproc! - result = s - if result.len >= result.space: - let r = resize(result.space) - result = cast[NimString](growObj(result, - sizeof(TGenericSeq) + r + 1)) - result.reserved = r - elif wasMoved(s): - result = newOwnedString(s, s.len) + if s == nil: + result = rawNewStringNoInit(1) + result.len = 0 + else: + result = s + if result.len >= result.space: + let r = resize(result.space) + result = cast[NimString](growObj(result, + sizeof(TGenericSeq) + r + 1)) + result.reserved = r result.data[result.len] = c result.data[result.len+1] = '\0' inc(result.len) @@ -205,7 +217,9 @@ proc addChar(s: NimString, c: char): NimString = # s = rawNewString(0); proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} = - if dest.len + addlen <= dest.space and not wasMoved(dest): + if dest == nil: + result = rawNewStringNoInit(addlen) + elif dest.len + addlen <= dest.space: result = dest else: # slow path: var sp = max(resize(dest.space), dest.len + addlen) @@ -216,8 +230,9 @@ proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} = # DO NOT UPDATE LEN YET: dest.len = newLen proc appendString(dest, src: NimString) {.compilerproc, inline.} = - copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1) - inc(dest.len, src.len) + if src != nil: + copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1) + inc(dest.len, src.len) proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = dest.data[dest.len] = c @@ -226,8 +241,8 @@ proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} = var n = max(newLen, 0) - if wasMoved(s): - result = newOwnedString(s, n) + if s == nil: + result = mnewString(newLen) elif n <= s.space: result = s else: @@ -261,6 +276,18 @@ proc incrSeqV2(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = GenericSeqSize)) result.reserved = r +proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.} = + if s == nil: + result = cast[PGenericSeq](newSeq(typ, 1)) + result.len = 0 + else: + result = s + if result.len >= result.space: + let r = resize(result.space) + result = cast[PGenericSeq](growObj(result, typ.base.size * r + + GenericSeqSize)) + result.reserved = r + proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. compilerRtl, inl.} = result = seq @@ -301,6 +328,13 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. (newLen*%elemSize)), (result.len-%newLen) *% elemSize) result.len = newLen +proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. + compilerRtl.} = + if s == nil: + result = cast[PGenericSeq](newSeq(typ, newLen)) + else: + result = setLengthSeq(s, typ.base.size, newLen) + # --------------- other string routines ---------------------------------- proc add*(result: var string; x: int64) = let base = result.len diff --git a/tests/array/tarraycons_ptr_generic.nim b/tests/array/tarraycons_ptr_generic.nim new file mode 100644 index 000000000..eb89a196f --- /dev/null +++ b/tests/array/tarraycons_ptr_generic.nim @@ -0,0 +1,51 @@ +discard """ + output: '''apple +banana +Fruit +2 +4 +3 +none +skin +paper +''' +""" +type + Fruit = object of RootObj + name: string + Apple = object of Fruit + Banana = object of Fruit + +var + ir = Fruit(name: "Fruit") + ia = Apple(name: "apple") + ib = Banana(name: "banana") + +let x = [ia.addr, ib.addr, ir.addr] +for c in x: echo c.name + +type + Vehicle[T] = object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + +var v = Vehicle[int](tire: 3) +var c = Car[int](tire: 4) +var b = Bike[int](tire: 2) + +let y = [b.addr, c.addr, v.addr] +for c in y: echo c.tire + +type + Book[T] = ref object of RootObj + cover: T + Hard[T] = ref object of Book[T] + Soft[T] = ref object of Book[T] + +var bn = Book[string](cover: "none") +var hs = Hard[string](cover: "skin") +var bp = Soft[string](cover: "paper") + +let z = [bn, hs, bp] +for c in z: echo c.cover diff --git a/tests/array/tarraycons_ptr_generic2.nim b/tests/array/tarraycons_ptr_generic2.nim new file mode 100644 index 000000000..fce7af669 --- /dev/null +++ b/tests/array/tarraycons_ptr_generic2.nim @@ -0,0 +1,17 @@ +discard """ + file: "tarraycons_ptr_generic2.nim" + line: 17 + errormsg: "type mismatch: got <ptr Hard[system.string]> but expected 'Book[system.string]'" +""" + +type + Book[T] = ref object of RootObj + cover: T + Hard[T] = ref object of Book[T] + Soft[T] = ref object of Book[T] + +var bn = Book[string](cover: "none") +var hs = Hard[string](cover: "skin") +var bp = Soft[string](cover: "paper") + +let z = [bn, hs.addr, bp] diff --git a/tests/async/tlambda.nim b/tests/async/tlambda.nim index d187c0d50..8f570689b 100644 --- a/tests/async/tlambda.nim +++ b/tests/async/tlambda.nim @@ -1,7 +1,7 @@ # bug 2007 -import asyncdispatch, asyncnet, logging, json, uri, strutils, future +import asyncdispatch, asyncnet, logging, json, uri, strutils, sugar type Builder = ref object @@ -27,7 +27,7 @@ proc newBuild*(onProgress: ProgressCB): Build = result.onProgress = onProgress proc start(build: Build, repo, hash: string) {.async.} = - let path = repo.parseUri().path.toLower() + let path = repo.parseUri().path.toLowerAscii() proc onProgress(builder: Builder, message: string) {.async.} = debug($message) diff --git a/tests/compiles/trecursive_generic_in_compiles.nim b/tests/compiles/trecursive_generic_in_compiles.nim index 77bf0bb02..9c7fd10b3 100644 --- a/tests/compiles/trecursive_generic_in_compiles.nim +++ b/tests/compiles/trecursive_generic_in_compiles.nim @@ -1,6 +1,6 @@ # bug #3313 -import unittest, future - +import unittest, sugar +{.experimental: "notnil".} type ListNodeKind = enum lnkNil, lnkCons diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim index 2238dacb6..cb8db566d 100644 --- a/tests/concepts/tstackconcept.nim +++ b/tests/concepts/tstackconcept.nim @@ -31,7 +31,7 @@ type s.pop() is T type ValueType = T - const ValueTypeName = T.name.toUpper + const ValueTypeName = T.name.toUpperAscii proc genericAlgorithm[T](s: var Stack[T], y: T) = static: diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim index bb3b1bebb..b3e56eec6 100644 --- a/tests/constructors/tinvalid_construction.nim +++ b/tests/constructors/tinvalid_construction.nim @@ -3,12 +3,12 @@ template accept(x) = template reject(x) = static: assert(not compiles(x)) - +{.experimental: "notnil".} type TRefObj = ref object x: int - THasNotNils = object of TObject + THasNotNils = object of RootObj a: TRefObj not nil b: TRefObj not nil c: TRefObj diff --git a/tests/converter/tconvert.nim b/tests/converter/tconvert.nim index a37140234..48367a85b 100644 --- a/tests/converter/tconvert.nim +++ b/tests/converter/tconvert.nim @@ -15,6 +15,6 @@ type TFoo = object converter toPtr*(some: var TFoo): ptr TFoo = (addr some) -proc zoot(x: ptr TFoo) = nil +proc zoot(x: ptr TFoo) = discard var x: Tfoo zoot(x) diff --git a/tests/distinct/tnil.nim b/tests/distinct/tnil.nim index e60437a1f..759a14657 100644 --- a/tests/distinct/tnil.nim +++ b/tests/distinct/tnil.nim @@ -1,15 +1,11 @@ discard """ file: "tnil.nim" - output: '''0x1 - -nil - -nil - + output: '''1 +0 +0 ''' - disabled: "windows" """ - +{.experimental: "notnil".} type MyPointer = distinct pointer MyString = distinct string @@ -17,7 +13,8 @@ type MyInt = distinct int proc foo(a: MyPointer) = - echo a.repr + # workaround a Windows 'repr' difference: + echo cast[int](a) foo(cast[MyPointer](1)) foo(cast[MyPointer](nil)) diff --git a/tests/effects/teffects4.nim b/tests/effects/teffects4.nim index fd5dd49e2..d0960126f 100644 --- a/tests/effects/teffects4.nim +++ b/tests/effects/teffects4.nim @@ -12,7 +12,7 @@ type EIO2 = ref object of EIO proc q() {.tags: [FIO].} = - nil + discard proc raiser(): int = writeLine stdout, "arg" diff --git a/tests/errmsgs/tproper_stacktrace2.nim b/tests/errmsgs/tproper_stacktrace2.nim index 5f312b870..44b208c87 100644 --- a/tests/errmsgs/tproper_stacktrace2.nim +++ b/tests/errmsgs/tproper_stacktrace2.nim @@ -3,7 +3,7 @@ discard """ exitcode: 1 """ -proc returnsNil(): string = return nil +proc returnsNil(): ref int = return nil iterator fields*(a, b: int): int = if a == b: @@ -17,6 +17,6 @@ proc main(): string = result = "" for i in fields(0, 1): let x = returnsNil() - result &= "string literal " & $x + result &= "string literal " & $x[] echo main() diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim index 14352066d..1095a893e 100644 --- a/tests/manyloc/argument_parser/argument_parser.nim +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -209,7 +209,7 @@ proc `$`*(data: Tcommandline_results): string = # - Parse code -template raise_or_quit(exception, message: expr): stmt {.immediate.} = +template raise_or_quit(exception, message: untyped) = ## Avoids repeating if check based on the default quit_on_failure variable. ## ## As a special case, if message has a zero length the call to quit won't @@ -230,15 +230,15 @@ template run_custom_proc(parsed_parameter: Tparsed_parameter, ## ## Pass in the string of the parameter triggering the call. If the if not custom_validator.isNil: + try: + let message = custom_validator(parameter, parsed_parameter) + if not message.isNil and message.len > 0: + raise_or_quit(ValueError, ("Failed to validate value for " & + "parameter $1:\n$2" % [escape(parameter), message])) except: raise_or_quit(ValueError, ("Couldn't run custom proc for " & "parameter $1:\n$2" % [escape(parameter), getCurrentExceptionMsg()])) - let message = custom_validator(parameter, parsed_parameter) - if not message.isNil and message.len > 0: - raise_or_quit(ValueError, ("Failed to validate value for " & - "parameter $1:\n$2" % [escape(parameter), message])) - proc parse_parameter(quit_on_failure: bool, param, value: string, param_kind: Tparam_kind): Tparsed_parameter = diff --git a/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim b/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim index f06c4e0be..ac425c7a0 100644 --- a/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim +++ b/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim @@ -388,13 +388,13 @@ type cdecl.} ##cp property emulators -template defGetter(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defGetter(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = proc `get procName`*(obj: otype): memberType {.cdecl.} = return obj.memberName -template defSetter(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defSetter(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = proc `set procName`*(obj: otype, value: memberType) {.cdecl.} = obj.memberName = value -template defProp(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defProp(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = defGetter(otype, memberType, memberName, procName) defSetter(otype, memberType, memberName, procName) @@ -908,7 +908,7 @@ proc getShapes*(arb: PArbiter, a, b: var PShape) {.inline.} = #/ A macro shortcut for defining and retrieving the shapes from an arbiter. #define CP_ARBITER_GET_SHAPES(arb, a, b) cpShape *a, *b; cpArbiterGetShapes(arb, &a, &b); -template getShapes*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = +template getShapes*(arb: PArbiter, name1, name2: untyped) = var name1, name2: PShape getShapes(arb, name1, name2) @@ -923,7 +923,7 @@ template getShapes*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = #/ A macro shortcut for defining and retrieving the bodies from an arbiter. #define CP_ARBITER_GET_BODIES(arb, a, b) cpBody *a, *b; cpArbiterGetBodies(arb, &a, &b); -template getBodies*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = +template getBodies*(arb: PArbiter, name1, name2: untyped) = var name1, name2: PBOdy getBodies(arb, name1, name2) @@ -947,11 +947,11 @@ proc getDepth*(arb: PArbiter; i: cint): CpFloat {. cdecl, importc: "cpArbiterGetDepth", dynlib: Lib.} ##Shapes -template defShapeSetter(memberType: typedesc, memberName: expr, procName: expr, activates: bool): stmt {.immediate.} = +template defShapeSetter(memberType: typedesc, memberName: untyped, procName: untyped, activates: bool) = proc `set procName`*(obj: PShape, value: memberType) {.cdecl.} = if activates and obj.body != nil: obj.body.activate() obj.memberName = value -template defShapeProp(memberType: typedesc, memberName: expr, procName: expr, activates: bool): stmt {.immediate.} = +template defShapeProp(memberType: typedesc, memberName: untyped, procName: untyped, activates: bool) = defGetter(PShape, memberType, memberName, procName) defShapeSetter(memberType, memberName, procName, activates) @@ -1272,11 +1272,11 @@ proc activateBodies(constraint: PConstraint) {.inline.} = # cpConstraintActivateBodies(constraint); \ # constraint->member = value; \ # } -template defConstraintSetter(memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defConstraintSetter(memberType: typedesc, member, name: untyped) = proc `set name`*(constraint: PConstraint, value: memberType) {.cdecl.} = activateBodies(constraint) constraint.member = value -template defConstraintProp(memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defConstraintProp(memberType: typedesc, member, name: untyped) = defGetter(PConstraint, memberType, member, name) defConstraintSetter(memberType, member, name) # CP_DefineConstraintStructGetter(cpSpace*, CP_PRIVATE(space), Space) @@ -1306,18 +1306,18 @@ proc getImpulse*(constraint: PConstraint): CpFloat {.inline.} = # cpConstraintActivateBodies(constraint); \ # ((struct *)constraint)->member = value; \ # } -template constraintCheckCast(constraint: PConstraint, ctype: expr): stmt {.immediate.} = +template constraintCheckCast(constraint: PConstraint, ctype: untyped) = assert(constraint.klass == `ctype getClass`(), "Constraint is the wrong class") -template defCGetter(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCGetter(ctype: untyped, memberType: typedesc, member, name: untyped) = proc `get ctype name`*(constraint: PConstraint): memberType {.cdecl.} = constraintCheckCast(constraint, ctype) result = cast[`P ctype`](constraint).member -template defCSetter(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCSetter(ctype: untyped, memberType: typedesc, member, name: untyped) = proc `set ctype name`*(constraint: PConstraint, value: memberType) {.cdecl.} = constraintCheckCast(constraint, ctype) activateBodies(constraint) cast[`P ctype`](constraint).member = value -template defCProp(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCProp(ctype: untyped, memberType: typedesc, member, name: untyped) = defCGetter(ctype, memberType, member, name) defCSetter(ctype, memberType, member, name) diff --git a/tests/manyloc/keineschweine/dependencies/enet/enet.nim b/tests/manyloc/keineschweine/dependencies/enet/enet.nim index 3ea8172d5..07079f2ea 100644 --- a/tests/manyloc/keineschweine/dependencies/enet/enet.nim +++ b/tests/manyloc/keineschweine/dependencies/enet/enet.nim @@ -24,7 +24,7 @@ const ENET_VERSION_MAJOR* = 1 ENET_VERSION_MINOR* = 3 ENET_VERSION_PATCH* = 3 -template ENET_VERSION_CREATE(major, minor, patch: expr): expr = +template ENET_VERSION_CREATE(major, minor, patch: untyped): untyped = (((major) shl 16) or ((minor) shl 8) or (patch)) const @@ -277,22 +277,22 @@ when defined(Linux) or true: dataLength*: csize TENetSocketSet* = Tfd_set ## see if these are different on win32, if not then get rid of these - template ENET_HOST_TO_NET_16*(value: expr): expr = + template ENET_HOST_TO_NET_16*(value: untyped): untyped = (htons(value)) - template ENET_HOST_TO_NET_32*(value: expr): expr = + template ENET_HOST_TO_NET_32*(value: untyped): untyped = (htonl(value)) - template ENET_NET_TO_HOST_16*(value: expr): expr = + template ENET_NET_TO_HOST_16*(value: untyped): untyped = (ntohs(value)) - template ENET_NET_TO_HOST_32*(value: expr): expr = + template ENET_NET_TO_HOST_32*(value: untyped): untyped = (ntohl(value)) - template ENET_SOCKETSET_EMPTY*(sockset: expr): expr = + template ENET_SOCKETSET_EMPTY*(sockset: untyped): untyped = FD_ZERO(addr((sockset))) - template ENET_SOCKETSET_ADD*(sockset, socket: expr): expr = + template ENET_SOCKETSET_ADD*(sockset, socket: untyped): untyped = FD_SET(socket, addr((sockset))) - template ENET_SOCKETSET_REMOVE*(sockset, socket: expr): expr = + template ENET_SOCKETSET_REMOVE*(sockset, socket: untyped): untyped = FD_CLEAR(socket, addr((sockset))) - template ENET_SOCKETSET_CHECK*(sockset, socket: expr): expr = + template ENET_SOCKETSET_CHECK*(sockset, socket: untyped): untyped = FD_ISSET(socket, addr((sockset))) when defined(Windows): @@ -606,7 +606,7 @@ proc protocolCommandSize*(commandNumber: cuchar): csize{. {.pop.} -from hashes import `!$`, `!&`, THash, hash -proc hash*(x: TAddress): THash {.nimcall, noSideEffect.} = +from hashes import `!$`, `!&`, Hash, hash +proc hash*(x: TAddress): Hash {.nimcall, noSideEffect.} = result = !$(hash(x.host.int32) !& hash(x.port.int16)) diff --git a/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim b/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim index 142b190ab..3fb4dc7d9 100644 --- a/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim +++ b/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim @@ -1,15 +1,15 @@ import macros, macro_dsl, estreams from strutils import format -template newLenName(): stmt {.immediate.} = +template newLenName() = let lenName {.inject.} = ^("len"& $lenNames) inc(lenNames) -template defPacketImports*(): stmt {.immediate, dirty.} = +template defPacketImports*() {.dirty.} = import macros, macro_dsl, estreams from strutils import format -macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = +macro defPacket*(typeNameN: untyped, typeFields: untyped): untyped = result = newNimNode(nnkStmtList) let typeName = quoted2ident(typeNameN) @@ -60,7 +60,7 @@ macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = let name = typeFields[i][0] dotName = packetID.dot(name) - resName = newIdentNode(!"result").dot(name) + resName = newIdentNode("result").dot(name) case typeFields[i][1].kind of nnkBracketExpr: #ex: paddedstring[32, '\0'], array[range, type] case $typeFields[i][1][0].ident @@ -141,7 +141,7 @@ macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = const emptyFields = {nnkEmpty, nnkNilLit} var objFields = newNimNode(nnkRecList) - for i in 0.. < len(typeFields): + for i in 0 ..< len(typeFields): let fname = typeFields[i][0] constructorParams.add(newNimNode(nnkIdentDefs).und( fname, @@ -200,7 +200,7 @@ proc iddefs*(a: string; b: NimNode): NimNode {.compileTime.} = proc varTy*(a: NimNode): NimNode {.compileTime.} = result = newNimNode(nnkVarTy).und(a) -macro forwardPacket*(typeName: expr, underlyingType: expr): stmt {.immediate.} = +macro forwardPacket*(typeName: untyped, underlyingType: untyped): untyped = var packetID = ^"p" streamID = ^"s" @@ -234,7 +234,7 @@ macro forwardPacket*(typeName: expr, underlyingType: expr): stmt {.immediate.} = echo "unknown type:", repr(underlyingtype) echo(repr(result)) -template forwardPacketT*(typeName: expr; underlyingType: expr): stmt {.dirty, immediate.} = +template forwardPacketT*(typeName: untyped; underlyingType: untyped) {.dirty.} = proc `read typeName`*(buffer: PBuffer): typeName = #discard readData(s, addr result, sizeof(result)) var res: underlyingType diff --git a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim index 86c12fbb0..33d2a7177 100644 --- a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim +++ b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim @@ -10,7 +10,7 @@ proc und*(a: NimNode; b: varargs[NimNode]): NimNode {.compileTime.} = proc `^`*(a: string): NimNode {.compileTime.} = ## new ident node - result = newIdentNode(!a) + result = newIdentNode(a) proc `[]`*(a, b: NimNode): NimNode {.compileTime.} = ## new bracket expression: node[node] not to be confused with node[indx] result = newNimNode(nnkBracketExpr).und(a, b) @@ -34,7 +34,7 @@ proc emptyNode*(): NimNode {.compileTime.} = proc dot*(left, right: NimNode): NimNode {.compileTime.} = result = newNimNode(nnkDotExpr).und(left, right) proc prefix*(a: string, b: NimNode): NimNode {.compileTime.} = - result = newNimNode(nnkPrefix).und(newIdentNode(!a), b) + result = newNimNode(nnkPrefix).und(newIdentNode(a), b) proc quoted2ident*(a: NimNode): NimNode {.compileTime.} = if a.kind != nnkAccQuoted: @@ -45,13 +45,13 @@ proc quoted2ident*(a: NimNode): NimNode {.compileTime.} = result = ^pname -macro `?`(a: expr): expr = +macro `?`(a: untyped): untyped = ## Character literal ?A #=> 'A' result = ($a[1].ident)[0].lit ## echo(?F,?a,?t,?t,?y) when isMainModule: - macro foo(x: stmt): stmt = + macro foo(x: untyped) = result = newNimNode(nnkStmtList) result.add(newNimNode(nnkCall).und(!!"echo", "Hello thar".lit)) result.add(newCall("echo", lit("3 * 45 = "), (3.lit.infix("*", 45.lit)))) diff --git a/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim b/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim index bdf2139c9..5490211de 100644 --- a/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim +++ b/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim @@ -7,7 +7,7 @@ task "install", "compile and install nake binary": for index, dir in pairs(path): echo " ", index, ". ", dir echo "Where to install nake binary? (quit with ^C or quit or exit)" - let ans = stdin.readLine().toLower + let ans = stdin.readLine().toLowerAscii var index = 0 case ans of "q", "quit", "x", "exit": diff --git a/tests/manyloc/keineschweine/lib/estreams.nim b/tests/manyloc/keineschweine/lib/estreams.nim index bdf9b2bf0..00a55c626 100644 --- a/tests/manyloc/keineschweine/lib/estreams.nim +++ b/tests/manyloc/keineschweine/lib/estreams.nim @@ -78,7 +78,7 @@ proc write*(buffer: PBuffer; val: var string) = setLen buffer.data, buffer.pos + length.int copyMem(addr buffer.data[buffer.pos], addr val[0], length.int) inc buffer.pos, length.int -proc write*[T: TNumber|bool|char|byte](buffer: PBuffer; val: T) = +proc write*[T: SomeNumber|bool|char|byte](buffer: PBuffer; val: T) = var v: T shallowCopy v, val writeBE buffer, v diff --git a/tests/manyloc/keineschweine/lib/input_helpers.nim b/tests/manyloc/keineschweine/lib/input_helpers.nim index 1953cb58c..ff1286c8d 100644 --- a/tests/manyloc/keineschweine/lib/input_helpers.nim +++ b/tests/manyloc/keineschweine/lib/input_helpers.nim @@ -5,8 +5,8 @@ type TInputFinishedProc* = proc() TKeyCallback = proc() PKeyClient* = ref object - onKeyDown: TTable[int32, TKeyCallback] - onKeyUp: TTable[int32, TKeyCallback] + onKeyDown: Table[int32, TKeyCallback] + onKeyUp: Table[int32, TKeyCallback] name: string PTextInput* = ref object text*: string @@ -134,5 +134,5 @@ iterator pollEvents*(window: PRenderWindow): PEvent = of EvtMouseButtonReleased: addButtonEvent(event.mouseButton.button, up) of EvtTextEntered: recordText(activeInput, event.text) of EvtMouseMoved: setMousePos(event.mouseMove.x, event.mouseMove.y) - else: nil + else: discard yield(addr event) diff --git a/tests/manyloc/keineschweine/lib/sg_assets.nim b/tests/manyloc/keineschweine/lib/sg_assets.nim index fbc3c9ab8..1e8a99c83 100644 --- a/tests/manyloc/keineschweine/lib/sg_assets.nim +++ b/tests/manyloc/keineschweine/lib/sg_assets.nim @@ -59,7 +59,7 @@ type of Projectile: bullet*: PBulletRecord else: - nil + discard PBulletRecord* = ref TBulletRecord TBulletRecord* = object id*: int16 @@ -115,10 +115,10 @@ var cfg: PZoneSettings SpriteSheets* = initTable[string, PSpriteSheet](64) SoundCache * = initTable[string, PSoundRecord](64) - nameToVehID*: TTable[string, int] - nameToItemID*: TTable[string, int] - nameToObjID*: TTable[string, int] - nameToBulletID*: TTable[string, int] + nameToVehID*: Table[string, int] + nameToItemID*: Table[string, int] + nameToObjID*: Table[string, int] + nameToBulletID*: Table[string, int] activeState = Lobby proc newSprite*(filename: string; errors: var seq[string]): PSpriteSheet @@ -126,7 +126,7 @@ proc load*(ss: PSpriteSheet): bool {.discardable.} proc newSound*(filename: string; errors: var seq[string]): PSoundRecord proc load*(s: PSoundRecord): bool {.discardable.} -proc validateSettings*(settings: PJsonNode; errors: var seq[string]): bool +proc validateSettings*(settings: JsonNode; errors: var seq[string]): bool proc loadSettings*(rawJson: string, errors: var seq[string]): bool proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool @@ -135,17 +135,17 @@ proc fetchItm*(itm: string): PItemRecord proc fetchObj*(name: string): PObjectRecord proc fetchBullet(name: string): PBulletRecord -proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings -proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord -proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord -proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord -proc importPhys(data: PJsonNode): TPhysicsRecord -proc importAnim(data: PJsonNode; errors: var seq[string]): PAnimationRecord -proc importHandling(data: PJsonNode): THandlingRecord -proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord -proc importSoul(data: PJsonNode): TSoulRecord -proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord -proc importSound*(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord +proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings +proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord +proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord +proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord +proc importPhys(data: JsonNode): TPhysicsRecord +proc importAnim(data: JsonNode; errors: var seq[string]): PAnimationRecord +proc importHandling(data: JsonNode): THandlingRecord +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord +proc importSoul(data: JsonNode): TSoulRecord +proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord +proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord ## this is the only pipe between lobby and main.nim proc getActiveState*(): TGameState = @@ -203,7 +203,7 @@ iterator playableVehicles*(): PVehicleRecord = if v.playable: yield v -template allAssets*(body: stmt) {.dirty.}= +template allAssets*(body: untyped) {.dirty.}= block: var assetType = FGraphics for file, asset in pairs(SpriteSheets): @@ -212,7 +212,7 @@ template allAssets*(body: stmt) {.dirty.}= for file, asset in pairs(SoundCache): body -template cacheImpl(procName, cacheName, resultType: expr; body: stmt) {.dirty, immediate.} = +template cacheImpl(procName, cacheName, resultType, body: untyped) {.dirty.} = proc procName*(filename: string; errors: var seq[string]): resulttype = if hasKey(cacheName, filename): return cacheName[filename] @@ -220,7 +220,7 @@ template cacheImpl(procName, cacheName, resultType: expr; body: stmt) {.dirty, i body cacheName[filename] = result -template checkFile(path: expr): stmt {.dirty, immediate.} = +template checkFile(path: untyped) {.dirty.} = if not existsFile(path): errors.add("File missing: "& path) @@ -243,7 +243,7 @@ proc expandPath*(assetType: TAssetType; fileName: string): string = case assetType of FGraphics: result.add "gfx/" of FSound: result.add "sfx/" - else: nil + else: discard result.add fileName proc expandPath*(fc: ScFileChallenge): string {.inline.} = result = expandPath(fc.assetType, fc.file) @@ -280,10 +280,10 @@ else: if not s.soundBuf.isNil: result = true -template addError(e: expr): stmt {.immediate.} = +template addError(e: untyped) = errors.add(e) result = false -proc validateSettings*(settings: PJsonNode, errors: var seq[string]): bool = +proc validateSettings*(settings: JsonNode, errors: var seq[string]): bool = result = true if settings.kind != JObject: addError("Settings root must be an object") @@ -328,10 +328,10 @@ proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool = result = loadSettings(readFile(filename), errors) proc loadSettings*(rawJson: string, errors: var seq[string]): bool = - var settings: PJsonNode + var settings: JsonNode try: settings = parseJson(rawJson) - except EJsonParsingError: + except JsonParsingError: errors.add("JSON parsing error: "& getCurrentExceptionMsg()) return except: @@ -407,21 +407,21 @@ proc fetchObj*(name: string): PObjectRecord = proc fetchBullet(name: string): PBulletRecord = return cfg.bullets[nameToBulletID[name]] -proc getField(node: PJsonNode, field: string, target: var float) = +proc getField(node: JsonNode, field: string, target: var float) = if not node.hasKey(field): return if node[field].kind == JFloat: target = node[field].fnum elif node[field].kind == JInt: target = node[field].num.float -proc getField(node: PJsonNode, field: string, target: var int) = +proc getField(node: JsonNode, field: string, target: var int) = if not node.hasKey(field): return if node[field].kind == JInt: target = node[field].num.int elif node[field].kind == JFloat: target = node[field].fnum.int -proc getField(node: PJsonNode; field: string; target: var bool) = +proc getField(node: JsonNode; field: string; target: var bool) = if not node.hasKey(field): return case node[field].kind @@ -431,19 +431,19 @@ proc getField(node: PJsonNode; field: string; target: var bool) = target = (node[field].num != 0) of JFloat: target = (node[field].fnum != 0.0) - else: nil + else: discard -template checkKey(node: expr; key: string): stmt = +template checkKey(node: untyped; key: string) = if not hasKey(node, key): return -proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord = +proc importTrail(data: JsonNode; errors: var seq[string]): TTrailRecord = checkKey(data, "trail") result.anim = importAnim(data["trail"], errors) result.timer = 1000.0 getField(data["trail"], "timer", result.timer) result.timer /= 1000.0 -proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings = +proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings = new(result) result.size = vec2i(5000, 5000) result.starfield = @[] @@ -456,7 +456,7 @@ proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings = if level.hasKey("starfield"): for star in level["starfield"].items: result.starfield.add(newSprite(star.str, errors)) -proc importPhys(data: PJsonNode): TPhysicsRecord = +proc importPhys(data: JsonNode): TPhysicsRecord = result.radius = 20.0 result.mass = 10.0 @@ -466,7 +466,7 @@ proc importPhys(data: PJsonNode): TPhysicsRecord = phys.getField("mass", result.mass) when not defined(NoChipmunk): result.moment = momentForCircle(result.mass, 0.0, result.radius, VectorZero) * MomentMult -proc importHandling(data: PJsonNode): THandlingRecord = +proc importHandling(data: JsonNode): THandlingRecord = result.thrust = 45.0 result.topSpeed = 100.0 #unused result.reverse = 30.0 @@ -483,7 +483,7 @@ proc importHandling(data: PJsonNode): THandlingRecord = hand.getField("reverse", result.reverse) hand.getField("strafe", result.strafe) hand.getField("rotation", result.rotation) -proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord = +proc importAnim(data: JsonNode, errors: var seq[string]): PAnimationRecord = new(result) result.angle = 0.0 result.delay = 1000.0 @@ -502,26 +502,26 @@ proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord = result.angle = radians(result.angle) ## comes in as degrees result.delay /= 1000 ## delay comes in as milliseconds -proc importSoul(data: PJsonNode): TSoulRecord = +proc importSoul(data: JsonNode): TSoulRecord = result.energy = 10000 result.health = 1 checkKey(data, "soul") let soul = data["soul"] soul.getField("energy", result.energy) soul.getField("health", result.health) -proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord = +proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord = checkKey(data, "explode") let expl = data["explode"] result.anim = importAnim(expl, errors) result.sound = importSound(expl, errors, "sound") -proc importSound*(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord = +proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord = if data.kind == JObject: checkKey(data, fieldName) result = newSound(data[fieldName].str, errors) elif data.kind == JString: result = newSound(data.str, errors) -proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord = +proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord = new(result) result.playable = false if data.kind != JArray or data.len != 2 or @@ -538,7 +538,7 @@ proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord = vehdata.getField("playable", result.playable) if result.anim.spriteSheet.isNil and result.playable: result.playable = false -proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord = +proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord = new(result) if data.kind != JArray or data.len != 2: result.name = "(broken)" @@ -546,7 +546,7 @@ proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord = result.name = data[0].str result.anim = importAnim(data[1], errors) result.physics = importPhys(data[1]) -proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = +proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord = new(result) if data.kind != JArray or data.len != 3: result.name = "(broken)" @@ -562,7 +562,7 @@ proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = result.useSound = importSound(data[2], errors, "useSound") - case data[1].str.toLower + case data[1].str.toLowerAscii of "projectile": result.kind = Projectile if data[2]["bullet"].kind == JString: @@ -576,15 +576,15 @@ proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = of "ammo": result.kind = Ammo of "utility": - nil + discard else: errors.add "Invalid item type \""&data[1].str&"\" for item "&result.name -proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord = +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord = new(result) result.id = -1 - var bdata: PJsonNode + var bdata: JsonNode if data.kind == JArray: result.name = data[0].str bdata = data[1] diff --git a/tests/manyloc/keineschweine/lib/sg_packets.nim b/tests/manyloc/keineschweine/lib/sg_packets.nim index d84bf72fc..f3a0e8925 100644 --- a/tests/manyloc/keineschweine/lib/sg_packets.nim +++ b/tests/manyloc/keineschweine/lib/sg_packets.nim @@ -4,14 +4,14 @@ defPacketImports() type PacketID* = char -template idpacket(pktName, id, s2c, c2s: expr): stmt {.immediate, dirty.} = +template idpacket(pktName, id, s2c, c2s: untyped) {.dirty.} = let `H pktName`* {.inject.} = id defPacket(`Sc pktName`, s2c) defPacket(`Cs pktName`, c2s) forwardPacketT(uint8, int8) forwardPacketT(uint16, int16) -forwardPacketT(TPort, int16) +forwardPacketT(Port, int16) idPacket(Login, 'a', tuple[id: int32; alias: string; sessionKey: string], @@ -22,7 +22,7 @@ defPacket(CsZoneJoinReq, tuple[session: ScLogin]) defPacket(ScZoneRecord, tuple[ name: string = "", desc: string = "", - ip: string = "", port: TPort = 0.Tport]) + ip: string = "", port: Port = 0.Port]) idPacket(ZoneList, 'z', tuple[network: string = "", zones: seq[ScZoneRecord]], tuple[time: string]) diff --git a/tests/manyloc/nake/nake.nim b/tests/manyloc/nake/nake.nim index 1e88fa73b..728619e47 100644 --- a/tests/manyloc/nake/nake.nim +++ b/tests/manyloc/nake/nake.nim @@ -67,7 +67,7 @@ else: for kind, key, val in getOpt(): case kind of cmdLongOption, cmdShortOption: - case key.tolower + case key.tolowerAscii of "tasks", "t": printTaskList = true else: diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim index 97af79a84..3e8609169 100644 --- a/tests/manyloc/nake/nakefile.nim +++ b/tests/manyloc/nake/nakefile.nim @@ -88,7 +88,7 @@ task "download", "download game assets": if existsFile(path): echo "The file already exists\n", "[R]emove [M]ove [Q]uit [S]kip Source: ", GameAssets - case stdin.readLine.toLower + case stdin.readLine.toLowerAscii of "r": removeFile path of "m": @@ -120,7 +120,7 @@ task "download", "download game assets": echo "Download binary libs? Only libs for linux are available currently, enjoy the irony.\n", "[Y]es [N]o Source: ", BinLibs - case stdin.readline.toLower + case stdin.readline.toLowerAscii of "y", "yes": discard ## o_O else: diff --git a/tests/metatype/tbindtypedesc.nim b/tests/metatype/tbindtypedesc.nim index b287aad01..039acfbe9 100644 --- a/tests/metatype/tbindtypedesc.nim +++ b/tests/metatype/tbindtypedesc.nim @@ -46,7 +46,7 @@ type type1 = typedesc type2 = typedesc -proc typePairs(A, B: type1; C, D: type2) = nil +proc typePairs(A, B: type1; C, D: type2) = discard accept typePairs(int, int, TFoo, TFOO) accept typePairs(TBAR, TBar, TBAR, TBAR) @@ -55,7 +55,7 @@ accept typePairs(int, int, string, string) reject typePairs(TBAR, TBar, TBar, TFoo) reject typePairs(string, int, TBAR, TBAR) -proc typePairs2[T: typedesc, U: typedesc](A, B: T; C, D: U) = nil +proc typePairs2[T: typedesc, U: typedesc](A, B: T; C, D: U) = discard accept typePairs2(int, int, TFoo, TFOO) accept typePairs2(TBAR, TBar, TBAR, TBAR) @@ -71,12 +71,12 @@ proc dontBind(a: typedesc, b: typedesc) = accept dontBind(int, float) accept dontBind(TFoo, TFoo) -proc dontBind2(a, b: typedesc) = nil +proc dontBind2(a, b: typedesc) = discard accept dontBind2(int, float) accept dontBind2(TBar, int) -proc bindArg(T: typedesc, U: typedesc, a, b: T, c, d: U) = nil +proc bindArg(T: typedesc, U: typedesc, a, b: T, c, d: U) = discard accept bindArg(int, string, 10, 20, "test", "nest") accept bindArg(int, int, 10, 20, 30, 40) diff --git a/tests/method/tsimmeth.nim b/tests/method/tsimmeth.nim index 11ff2674f..a057c35b7 100644 --- a/tests/method/tsimmeth.nim +++ b/tests/method/tsimmeth.nim @@ -6,7 +6,7 @@ discard """ import strutils -var x = "hello world!".toLower.toUpper +var x = "hello world!".toLowerAscii.toUpperAscii x.echo() #OUT HELLO WORLD! diff --git a/tests/misc/tmemoization.nim b/tests/misc/tmemoization.nim index 180acd89b..840eb3b0d 100644 --- a/tests/misc/tmemoization.nim +++ b/tests/misc/tmemoization.nim @@ -8,7 +8,7 @@ import strutils proc foo(s: static[string]): string = static: echo s - const R = s.toUpper + const R = s.toUpperAscii return R echo foo("test 1") diff --git a/tests/misc/tsemfold.nim b/tests/misc/tsemfold.nim new file mode 100644 index 000000000..18c282d9e --- /dev/null +++ b/tests/misc/tsemfold.nim @@ -0,0 +1,23 @@ +discard """ + action: run +""" + +doAssertRaises(OverflowError): discard low(int8) - 1'i8 +doAssertRaises(OverflowError): discard high(int8) + 1'i8 +doAssertRaises(OverflowError): discard abs(low(int8)) +doAssertRaises(DivByZeroError): discard 1 mod 0 +doAssertRaises(DivByZeroError): discard 1 div 0 +doAssertRaises(OverflowError): discard low(int8) div -1'i8 + +doAssertRaises(OverflowError): discard low(int64) - 1'i64 +doAssertRaises(OverflowError): discard high(int64) + 1'i64 + +type E = enum eA, eB +doAssertRaises(OverflowError): discard eA.pred +doAssertRaises(OverflowError): discard eB.succ + +doAssertRaises(OverflowError): discard low(int8) * -1 +doAssertRaises(OverflowError): discard low(int64) * -1 +doAssertRaises(OverflowError): discard high(int8) * 2 +doAssertRaises(OverflowError): discard high(int64) * 2 + diff --git a/tests/notnil/tmust_compile.nim b/tests/notnil/tmust_compile.nim index 10da154f0..a32c6c7ec 100644 --- a/tests/notnil/tmust_compile.nim +++ b/tests/notnil/tmust_compile.nim @@ -3,6 +3,7 @@ discard """ """ # bug #6682 +{.experimental: "notnil".} type Fields = enum diff --git a/tests/notnil/tnotnil.nim b/tests/notnil/tnotnil.nim index f65634ed6..e392b155c 100644 --- a/tests/notnil/tnotnil.nim +++ b/tests/notnil/tnotnil.nim @@ -2,7 +2,7 @@ discard """ line: 22 errormsg: "type mismatch" """ - +{.experimental: "notnil".} type PObj = ref TObj not nil TObj = object @@ -15,8 +15,8 @@ type proc p(x: string not nil): int = result = 45 -proc q(x: MyString) = nil -proc q2(x: string) = nil +proc q(x: MyString) = discard +proc q2(x: string) = discard q2(nil) q(nil) diff --git a/tests/notnil/tnotnil1.nim b/tests/notnil/tnotnil1.nim index 73472752c..7f9d02295 100644 --- a/tests/notnil/tnotnil1.nim +++ b/tests/notnil/tnotnil1.nim @@ -4,7 +4,7 @@ discard """ """ import strutils - +{.experimental: "notnil".} type TObj = object @@ -18,13 +18,13 @@ proc q(s: superstring) = echo s proc p2() = - var a: string = "I am not nil" + var a: string = "I am not nil" q(a) # but this should and does not p2() proc q(x: pointer not nil) = - nil + discard proc p() = var x: pointer diff --git a/tests/notnil/tnotnil2.nim b/tests/notnil/tnotnil2.nim index bd6b8b675..6cd08de73 100644 --- a/tests/notnil/tnotnil2.nim +++ b/tests/notnil/tnotnil2.nim @@ -4,14 +4,14 @@ discard """ """ import strutils - +{.experimental: "notnil".} type TObj = object x, y: int proc q(x: pointer not nil) = - nil + discard proc p() = var x: pointer diff --git a/tests/notnil/tnotnil3.nim b/tests/notnil/tnotnil3.nim index b7c7a811d..31a4efef7 100644 --- a/tests/notnil/tnotnil3.nim +++ b/tests/notnil/tnotnil3.nim @@ -5,7 +5,7 @@ discard """ # bug #584 # Testprogram for 'not nil' check - +{.experimental: "notnil".} const testWithResult = true type diff --git a/tests/notnil/tnotnil4.nim b/tests/notnil/tnotnil4.nim index 2fa888357..4fd169827 100644 --- a/tests/notnil/tnotnil4.nim +++ b/tests/notnil/tnotnil4.nim @@ -2,6 +2,8 @@ discard "" type TObj = ref object +{.experimental: "notnil".} + proc check(a: TObj not nil) = echo repr(a) diff --git a/tests/notnil/tnotnil_in_generic.nim b/tests/notnil/tnotnil_in_generic.nim index 357ab2c7c..89d20f182 100644 --- a/tests/notnil/tnotnil_in_generic.nim +++ b/tests/notnil/tnotnil_in_generic.nim @@ -3,6 +3,7 @@ discard """ """ # bug #2216 +{.experimental: "notnil".} type A[T] = ref object diff --git a/tests/notnil/tnotnil_in_objconstr.nim b/tests/notnil/tnotnil_in_objconstr.nim index 7dce98c29..d33709906 100644 --- a/tests/notnil/tnotnil_in_objconstr.nim +++ b/tests/notnil/tnotnil_in_objconstr.nim @@ -2,7 +2,7 @@ discard """ errormsg: "fields not initialized: bar" line: "13" """ - +{.experimental: "notnil".} # bug #2355 type Foo = object diff --git a/tests/objvariant/tcheckedfield1.nim b/tests/objvariant/tcheckedfield1.nim index 56d784a2b..a7f232c5b 100644 --- a/tests/objvariant/tcheckedfield1.nim +++ b/tests/objvariant/tcheckedfield1.nim @@ -6,7 +6,7 @@ discard """ import strutils {.warning[ProveField]: on.} - +{.experimental: "notnil".} type TNodeKind = enum nkBinary, nkTernary, nkStr diff --git a/tests/overload/tissue966.nim b/tests/overload/tissue966.nim index c5b28e217..d0a723875 100644 --- a/tests/overload/tissue966.nim +++ b/tests/overload/tissue966.nim @@ -5,7 +5,7 @@ discard """ type PTest = ref object -proc test(x: PTest, y: int) = nil +proc test(x: PTest, y: int) = discard var buf: PTest buf.test() diff --git a/tests/proc/tprocredef.nim b/tests/proc/tprocredef.nim index 86ed92b62..4ec771510 100644 --- a/tests/proc/tprocredef.nim +++ b/tests/proc/tprocredef.nim @@ -4,6 +4,6 @@ discard """ errormsg: "redefinition of \'foo\'" """ -proc foo(a: int, b: string) = nil -proc foo(a: int, b: string) = nil +proc foo(a: int, b: string) = discard +proc foo(a: int, b: string) = discard diff --git a/tests/range/tsubrange.nim b/tests/range/tsubrange.nim index 6f88c5a22..914e7c6e7 100644 --- a/tests/range/tsubrange.nim +++ b/tests/range/tsubrange.nim @@ -7,7 +7,7 @@ type TRange = range[0..40] proc p(r: TRange) = - nil + discard var r: TRange diff --git a/tests/stdlib/tpegs.nim b/tests/stdlib/tpegs.nim deleted file mode 100644 index e2a5a1715..000000000 --- a/tests/stdlib/tpegs.nim +++ /dev/null @@ -1,1770 +0,0 @@ -discard """ - output: '''this -is -an -example -d -e -f -('keyvalue' 'key'*)''' -""" -# PEGs module turned out to be a good test to detect memory management bugs. - -include "system/inclrtl" - -const - useUnicode = true ## change this to deactivate proper UTF-8 support - -import - strutils - -when useUnicode: - import unicode - -const - InlineThreshold = 5 ## number of leaves; -1 to disable inlining - MaxSubpatterns* = 10 ## defines the maximum number of subpatterns that - ## can be captured. More subpatterns cannot be captured! - -type - TPegKind = enum - pkEmpty, - pkAny, ## any character (.) - pkAnyRune, ## any Unicode character (_) - pkNewLine, ## CR-LF, LF, CR - pkLetter, ## Unicode letter - pkLower, ## Unicode lower case letter - pkUpper, ## Unicode upper case letter - pkTitle, ## Unicode title character - pkWhitespace, ## Unicode whitespace character - pkTerminal, - pkTerminalIgnoreCase, - pkTerminalIgnoreStyle, - pkChar, ## single character to match - pkCharChoice, - pkNonTerminal, - pkSequence, ## a b c ... --> Internal DSL: peg(a, b, c) - pkOrderedChoice, ## a / b / ... --> Internal DSL: a / b or /[a, b, c] - pkGreedyRep, ## a* --> Internal DSL: *a - ## a+ --> (a a*) - pkGreedyRepChar, ## x* where x is a single character (superop) - pkGreedyRepSet, ## [set]* (superop) - pkGreedyAny, ## .* or _* (superop) - pkOption, ## a? --> Internal DSL: ?a - pkAndPredicate, ## &a --> Internal DSL: &a - pkNotPredicate, ## !a --> Internal DSL: !a - pkCapture, ## {a} --> Internal DSL: capture(a) - pkBackRef, ## $i --> Internal DSL: backref(i) - pkBackRefIgnoreCase, - pkBackRefIgnoreStyle, - pkSearch, ## @a --> Internal DSL: !*a - pkCapturedSearch, ## {@} a --> Internal DSL: !*\a - pkRule, ## a <- b - pkList, ## a, b - pkStartAnchor ## ^ --> Internal DSL: startAnchor() - TNonTerminalFlag = enum - ntDeclared, ntUsed - TNonTerminal {.final.} = object ## represents a non terminal symbol - name: string ## the name of the symbol - line: int ## line the symbol has been declared/used in - col: int ## column the symbol has been declared/used in - flags: set[TNonTerminalFlag] ## the nonterminal's flags - rule: TNode ## the rule that the symbol refers to - TNode {.final, shallow.} = object - case kind: TPegKind - of pkEmpty..pkWhitespace: nil - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string - of pkChar, pkGreedyRepChar: ch: char - of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] - of pkNonTerminal: nt: PNonTerminal - of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] - else: sons: seq[TNode] - PNonTerminal* = ref TNonTerminal - - TPeg* = TNode ## type that represents a PEG - -proc term*(t: string): TPeg {.rtl, extern: "npegs$1Str".} = - ## constructs a PEG from a terminal string - if t.len != 1: - result.kind = pkTerminal - result.term = t - else: - result.kind = pkChar - result.ch = t[0] - -proc termIgnoreCase*(t: string): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore case for matching - result.kind = pkTerminalIgnoreCase - result.term = t - -proc termIgnoreStyle*(t: string): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore style for matching - result.kind = pkTerminalIgnoreStyle - result.term = t - -proc term*(t: char): TPeg {.rtl, extern: "npegs$1Char".} = - ## constructs a PEG from a terminal char - assert t != '\0' - result.kind = pkChar - result.ch = t - -proc charSet*(s: set[char]): TPeg {.rtl, extern: "npegs$1".} = - ## constructs a PEG from a character set `s` - assert '\0' notin s - result.kind = pkCharChoice - new(result.charChoice) - result.charChoice[] = s - -proc len(a: TPeg): int {.inline.} = return a.sons.len -proc add(d: var TPeg, s: TPeg) {.inline.} = add(d.sons, s) - -proc copyPeg(a: TPeg): TPeg = - result.kind = a.kind - case a.kind - of pkEmpty..pkWhitespace: discard - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: - result.term = a.term - of pkChar, pkGreedyRepChar: - result.ch = a.ch - of pkCharChoice, pkGreedyRepSet: - new(result.charChoice) - result.charChoice[] = a.charChoice[] - of pkNonTerminal: result.nt = a.nt - of pkBackRef..pkBackRefIgnoreStyle: - result.index = a.index - else: - result.sons = a.sons - -proc addChoice(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkCharChoice: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkCharChoice: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + elem.charChoice[]) - of pkChar: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + {elem.ch}) - else: add(dest, elem) - else: add(dest, elem) - -template multipleOp(k: TPegKind, localOpt) = - result.kind = k - result.sons = @[] - for x in items(a): - if x.kind == k: - for y in items(x.sons): - localOpt(result, y) - else: - localOpt(result, x) - if result.len == 1: - result = result.sons[0] - -proc `/`*(a: varargs[TPeg]): TPeg {. - rtl, extern: "npegsOrderedChoice".} = - ## constructs an ordered choice with the PEGs in `a` - multipleOp(pkOrderedChoice, addChoice) - -proc addSequence(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkTerminal: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkTerminal: - dest.sons[L] = term(dest.sons[L].term & elem.term) - of pkChar: - dest.sons[L] = term(dest.sons[L].term & elem.ch) - else: add(dest, elem) - else: add(dest, elem) - -proc sequence*(a: varargs[TPeg]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a sequence with all the PEGs from `a` - multipleOp(pkSequence, addSequence) - -proc `?`*(a: TPeg): TPeg {.rtl, extern: "npegsOptional".} = - ## constructs an optional for the PEG `a` - if a.kind in {pkOption, pkGreedyRep, pkGreedyAny, pkGreedyRepChar, - pkGreedyRepSet}: - # a* ? --> a* - # a? ? --> a? - result = a - else: - result.kind = pkOption - result.sons = @[a] - -proc `*`*(a: TPeg): TPeg {.rtl, extern: "npegsGreedyRep".} = - ## constructs a "greedy repetition" for the PEG `a` - case a.kind - of pkGreedyRep, pkGreedyRepChar, pkGreedyRepSet, pkGreedyAny, pkOption: - assert false - # produces endless loop! - of pkChar: - result.kind = pkGreedyRepChar - result.ch = a.ch - of pkCharChoice: - result.kind = pkGreedyRepSet - result.charChoice = a.charChoice # copying a reference suffices! - of pkAny, pkAnyRune: - result.kind = pkGreedyAny - else: - result.kind = pkGreedyRep - result.sons = @[a] - -proc `!*`*(a: TPeg): TPeg {.rtl, extern: "npegsSearch".} = - ## constructs a "search" for the PEG `a` - result.kind = pkSearch - result.sons = @[a] - -proc `!*\`*(a: TPeg): TPeg {.rtl, - extern: "npgegsCapturedSearch".} = - ## constructs a "captured search" for the PEG `a` - result.kind = pkCapturedSearch - result.sons = @[a] - -when false: - proc contains(a: TPeg, k: TPegKind): bool = - if a.kind == k: return true - case a.kind - of pkEmpty, pkAny, pkAnyRune, pkGreedyAny, pkNewLine, pkTerminal, - pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, pkGreedyRepChar, - pkCharChoice, pkGreedyRepSet: discard - of pkNonTerminal: return true - else: - for i in 0..a.sons.len-1: - if contains(a.sons[i], k): return true - -proc `+`*(a: TPeg): TPeg {.rtl, extern: "npegsGreedyPosRep".} = - ## constructs a "greedy positive repetition" with the PEG `a` - return sequence(a, *a) - -proc `&`*(a: TPeg): TPeg {.rtl, extern: "npegsAndPredicate".} = - ## constructs an "and predicate" with the PEG `a` - result.kind = pkAndPredicate - result.sons = @[a] - -proc `!`*(a: TPeg): TPeg {.rtl, extern: "npegsNotPredicate".} = - ## constructs a "not predicate" with the PEG `a` - result.kind = pkNotPredicate - result.sons = @[a] - -proc any*: TPeg {.inline.} = - ## constructs the PEG `any character`:idx: (``.``) - result.kind = pkAny - -proc anyRune*: TPeg {.inline.} = - ## constructs the PEG `any rune`:idx: (``_``) - result.kind = pkAnyRune - -proc newLine*: TPeg {.inline.} = - ## constructs the PEG `newline`:idx: (``\n``) - result.kind = pkNewline - -proc UnicodeLetter*: TPeg {.inline.} = - ## constructs the PEG ``\letter`` which matches any Unicode letter. - result.kind = pkLetter - -proc UnicodeLower*: TPeg {.inline.} = - ## constructs the PEG ``\lower`` which matches any Unicode lowercase letter. - result.kind = pkLower - -proc UnicodeUpper*: TPeg {.inline.} = - ## constructs the PEG ``\upper`` which matches any Unicode lowercase letter. - result.kind = pkUpper - -proc UnicodeTitle*: TPeg {.inline.} = - ## constructs the PEG ``\title`` which matches any Unicode title letter. - result.kind = pkTitle - -proc UnicodeWhitespace*: TPeg {.inline.} = - ## constructs the PEG ``\white`` which matches any Unicode - ## whitespace character. - result.kind = pkWhitespace - -proc startAnchor*: TPeg {.inline.} = - ## constructs the PEG ``^`` which matches the start of the input. - result.kind = pkStartAnchor - -proc endAnchor*: TPeg {.inline.} = - ## constructs the PEG ``$`` which matches the end of the input. - result = !any() - -proc capture*(a: TPeg): TPeg {.rtl, extern: "npegsCapture".} = - ## constructs a capture with the PEG `a` - result.kind = pkCapture - result.sons = @[a] - -proc backref*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. - result.kind = pkBackRef - result.index = index-1 - -proc backrefIgnoreCase*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores case for matching. - result.kind = pkBackRefIgnoreCase - result.index = index-1 - -proc backrefIgnoreStyle*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".}= - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores style for matching. - result.kind = pkBackRefIgnoreStyle - result.index = index-1 - -proc spaceCost(n: TPeg): int = - case n.kind - of pkEmpty: discard - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, - pkGreedyRepChar, pkCharChoice, pkGreedyRepSet, - pkAny..pkWhitespace, pkGreedyAny: - result = 1 - of pkNonTerminal: - # we cannot inline a rule with a non-terminal - result = InlineThreshold+1 - else: - for i in 0..n.len-1: - inc(result, spaceCost(n.sons[i])) - if result >= InlineThreshold: break - -proc nonterminal*(n: PNonTerminal): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG that consists of the nonterminal symbol - assert n != nil - if ntDeclared in n.flags and spaceCost(n.rule) < InlineThreshold: - when false: echo "inlining symbol: ", n.name - result = n.rule # inlining of rule enables better optimizations - else: - result.kind = pkNonTerminal - result.nt = n - -proc newNonTerminal*(name: string, line, column: int): PNonTerminal {. - rtl, extern: "npegs$1".} = - ## constructs a nonterminal symbol - new(result) - result.name = name - result.line = line - result.col = column - -template letters*: TPeg = - ## expands to ``charset({'A'..'Z', 'a'..'z'})`` - charset({'A'..'Z', 'a'..'z'}) - -template digits*: TPeg = - ## expands to ``charset({'0'..'9'})`` - charset({'0'..'9'}) - -template whitespace*: TPeg = - ## expands to ``charset({' ', '\9'..'\13'})`` - charset({' ', '\9'..'\13'}) - -template identChars*: TPeg = - ## expands to ``charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})`` - charset({'a'..'z', 'A'..'Z', '0'..'9', '_'}) - -template identStartChars*: TPeg = - ## expands to ``charset({'A'..'Z', 'a'..'z', '_'})`` - charset({'a'..'z', 'A'..'Z', '_'}) - -template ident*: TPeg = - ## same as ``[a-zA-Z_][a-zA-z_0-9]*``; standard identifier - sequence(charset({'a'..'z', 'A'..'Z', '_'}), - *charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})) - -template natural*: TPeg = - ## same as ``\d+`` - +digits - -# ------------------------- debugging ----------------------------------------- - -proc esc(c: char, reserved = {'\0'..'\255'}): string = - case c - of '\b': result = "\\b" - of '\t': result = "\\t" - of '\c': result = "\\c" - of '\L': result = "\\l" - of '\v': result = "\\v" - of '\f': result = "\\f" - of '\e': result = "\\e" - of '\a': result = "\\a" - of '\\': result = "\\\\" - of 'a'..'z', 'A'..'Z', '0'..'9', '_': result = $c - elif c < ' ' or c >= '\128': result = '\\' & $ord(c) - elif c in reserved: result = '\\' & c - else: result = $c - -proc singleQuoteEsc(c: char): string = return "'" & esc(c, {'\''}) & "'" - -proc singleQuoteEsc(str: string): string = - result = "'" - for c in items(str): add result, esc(c, {'\''}) - add result, '\'' - -proc charSetEscAux(cc: set[char]): string = - const reserved = {'^', '-', ']'} - result = "" - var c1 = 0 - while c1 <= 0xff: - if chr(c1) in cc: - var c2 = c1 - while c2 < 0xff and chr(succ(c2)) in cc: inc(c2) - if c1 == c2: - add result, esc(chr(c1), reserved) - elif c2 == succ(c1): - add result, esc(chr(c1), reserved) & esc(chr(c2), reserved) - else: - add result, esc(chr(c1), reserved) & '-' & esc(chr(c2), reserved) - c1 = c2 - inc(c1) - -proc charSetEsc(cc: set[char]): string = - if card(cc) >= 128+64: - result = "[^" & charSetEscAux({'\1'..'\xFF'} - cc) & ']' - else: - result = '[' & charSetEscAux(cc) & ']' - -proc toStrAux(r: TPeg, res: var string) = - case r.kind - of pkEmpty: add(res, "()") - of pkAny: add(res, '.') - of pkAnyRune: add(res, '_') - of pkLetter: add(res, "\\letter") - of pkLower: add(res, "\\lower") - of pkUpper: add(res, "\\upper") - of pkTitle: add(res, "\\title") - of pkWhitespace: add(res, "\\white") - - of pkNewline: add(res, "\\n") - of pkTerminal: add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreCase: - add(res, 'i') - add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreStyle: - add(res, 'y') - add(res, singleQuoteEsc(r.term)) - of pkChar: add(res, singleQuoteEsc(r.ch)) - of pkCharChoice: add(res, charSetEsc(r.charChoice[])) - of pkNonTerminal: add(res, r.nt.name) - of pkSequence: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, ' ') - toStrAux(r.sons[i], res) - add(res, ')') - of pkOrderedChoice: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, " / ") - toStrAux(r.sons[i], res) - add(res, ')') - of pkGreedyRep: - toStrAux(r.sons[0], res) - add(res, '*') - of pkGreedyRepChar: - add(res, singleQuoteEsc(r.ch)) - add(res, '*') - of pkGreedyRepSet: - add(res, charSetEsc(r.charChoice[])) - add(res, '*') - of pkGreedyAny: - add(res, ".*") - of pkOption: - toStrAux(r.sons[0], res) - add(res, '?') - of pkAndPredicate: - add(res, '&') - toStrAux(r.sons[0], res) - of pkNotPredicate: - add(res, '!') - toStrAux(r.sons[0], res) - of pkSearch: - add(res, '@') - toStrAux(r.sons[0], res) - of pkCapturedSearch: - add(res, "{@}") - toStrAux(r.sons[0], res) - of pkCapture: - add(res, '{') - toStrAux(r.sons[0], res) - add(res, '}') - of pkBackRef: - add(res, '$') - add(res, $r.index) - of pkBackRefIgnoreCase: - add(res, "i$") - add(res, $r.index) - of pkBackRefIgnoreStyle: - add(res, "y$") - add(res, $r.index) - of pkRule: - toStrAux(r.sons[0], res) - add(res, " <- ") - toStrAux(r.sons[1], res) - of pkList: - for i in 0 .. high(r.sons): - toStrAux(r.sons[i], res) - add(res, "\n") - of pkStartAnchor: - add(res, '^') - -proc `$` *(r: TPeg): string {.rtl, extern: "npegsToString".} = - ## converts a PEG to its string representation - result = "" - toStrAux(r, result) - -# --------------------- core engine ------------------------------------------- - -type - TCaptures* {.final.} = object ## contains the captured substrings. - matches: array[0..MaxSubpatterns-1, tuple[first, last: int]] - ml: int - origStart: int - -proc bounds*(c: TCaptures, - i: range[0..MaxSubpatterns-1]): tuple[first, last: int] = - ## returns the bounds ``[first..last]`` of the `i`'th capture. - result = c.matches[i] - -when not useUnicode: - type - Rune = char - template fastRuneAt(s, i, ch) = - ch = s[i] - inc(i) - template runeLenAt(s, i): untyped = 1 - - proc isAlpha(a: char): bool {.inline.} = return a in {'a'..'z','A'..'Z'} - proc isUpper(a: char): bool {.inline.} = return a in {'A'..'Z'} - proc isLower(a: char): bool {.inline.} = return a in {'a'..'z'} - proc isTitle(a: char): bool {.inline.} = return false - proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'} - -proc rawMatch*(s: string, p: TPeg, start: int, c: var TCaptures): int {. - rtl, extern: "npegs$1".} = - ## low-level matching proc that implements the PEG interpreter. Use this - ## for maximum efficiency (every other PEG operation ends up calling this - ## proc). - ## Returns -1 if it does not match, else the length of the match - case p.kind - of pkEmpty: result = 0 # match of length 0 - of pkAny: - if s[start] != '\0': result = 1 - else: result = -1 - of pkAnyRune: - if s[start] != '\0': - result = runeLenAt(s, start) - else: - result = -1 - of pkLetter: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isAlpha(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkLower: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isLower(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkUpper: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isUpper(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkTitle: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isTitle(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkWhitespace: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isWhitespace(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkGreedyAny: - result = len(s) - start - of pkNewLine: - if s[start] == '\L': result = 1 - elif s[start] == '\C': - if s[start+1] == '\L': result = 2 - else: result = 1 - else: result = -1 - of pkTerminal: - result = len(p.term) - for i in 0..result-1: - if p.term[i] != s[start+i]: - result = -1 - break - of pkTerminalIgnoreCase: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - fastRuneAt(p.term, i, a) - fastRuneAt(s, result, b) - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkTerminalIgnoreStyle: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - while true: - fastRuneAt(p.term, i, a) - if a != Rune('_'): break - while true: - fastRuneAt(s, result, b) - if b != Rune('_'): break - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkChar: - if p.ch == s[start]: result = 1 - else: result = -1 - of pkCharChoice: - if contains(p.charChoice[], s[start]): result = 1 - else: result = -1 - of pkNonTerminal: - var oldMl = c.ml - when false: echo "enter: ", p.nt.name - result = rawMatch(s, p.nt.rule, start, c) - when false: echo "leave: ", p.nt.name - if result < 0: c.ml = oldMl - of pkSequence: - var oldMl = c.ml - result = 0 - assert(not isNil(p.sons)) - for i in 0..high(p.sons): - var x = rawMatch(s, p.sons[i], start+result, c) - if x < 0: - c.ml = oldMl - result = -1 - break - else: inc(result, x) - of pkOrderedChoice: - var oldMl = c.ml - for i in 0..high(p.sons): - result = rawMatch(s, p.sons[i], start, c) - if result >= 0: break - c.ml = oldMl - of pkSearch: - var oldMl = c.ml - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - inc(result, x) - return - inc(result) - result = -1 - c.ml = oldMl - of pkCapturedSearch: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - inc(result, x) - return - inc(result) - result = -1 - c.ml = idx - of pkGreedyRep: - result = 0 - while true: - var x = rawMatch(s, p.sons[0], start+result, c) - # if x == 0, we have an endless loop; so the correct behaviour would be - # not to break. But endless loops can be easily introduced: - # ``(comment / \w*)*`` is such an example. Breaking for x == 0 does the - # expected thing in this case. - if x <= 0: break - inc(result, x) - of pkGreedyRepChar: - result = 0 - var ch = p.ch - while ch == s[start+result]: inc(result) - of pkGreedyRepSet: - result = 0 - while contains(p.charChoice[], s[start+result]): inc(result) - of pkOption: - result = max(0, rawMatch(s, p.sons[0], start, c)) - of pkAndPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: result = 0 # do not consume anything - else: c.ml = oldMl - of pkNotPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result < 0: result = 0 - else: - c.ml = oldMl - result = -1 - of pkCapture: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - else: - c.ml = idx - of pkBackRef..pkBackRefIgnoreStyle: - if p.index >= c.ml: return -1 - var (a, b) = c.matches[p.index] - var n: TPeg - n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef)) - n.term = s.substr(a, b) - result = rawMatch(s, n, start, c) - of pkStartAnchor: - if c.origStart == start: result = 0 - else: result = -1 - of pkRule, pkList: assert false - -proc match*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): bool {.rtl, extern: "npegs$1Capture".} = - ## returns ``true`` if ``s[start..]`` matches the ``pattern`` and - ## the captured substrings in the array ``matches``. If it does not - ## match, nothing is written into ``matches`` and ``false`` is - ## returned. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s)-start - if result: - for i in 0..c.ml-1: - matches[i] = substr(s, c.matches[i][0], c.matches[i][1]) - -proc match*(s: string, pattern: TPeg, - start = 0): bool {.rtl, extern: "npegs$1".} = - ## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s)-start - -proc matchLen*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): int {.rtl, extern: "npegs$1Capture".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) - if result >= 0: - for i in 0..c.ml-1: - matches[i] = substr(s, c.matches[i][0], c.matches[i][1]) - -proc matchLen*(s: string, pattern: TPeg, - start = 0): int {.rtl, extern: "npegs$1".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) - -proc find*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): int {.rtl, extern: "npegs$1Capture".} = - ## returns the starting position of ``pattern`` in ``s`` and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and -1 is returned. - for i in start .. s.len-1: - if matchLen(s, pattern, matches, i) >= 0: return i - return -1 - # could also use the pattern here: (!P .)* P - -proc findBounds*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): tuple[first, last: int] {. - rtl, extern: "npegs$1Capture".} = - ## returns the starting position and end position of ``pattern`` in ``s`` - ## and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and (-1,0) is returned. - for i in start .. s.len-1: - var L = matchLen(s, pattern, matches, i) - if L >= 0: return (i, i+L-1) - return (-1, 0) - -proc find*(s: string, pattern: TPeg, - start = 0): int {.rtl, extern: "npegs$1".} = - ## returns the starting position of ``pattern`` in ``s``. If it does not - ## match, -1 is returned. - for i in start .. s.len-1: - if matchLen(s, pattern, i) >= 0: return i - return -1 - -iterator findAll*(s: string, pattern: TPeg, start = 0): string = - ## yields all matching captures of pattern in `s`. - var matches: array[0..MaxSubpatterns-1, string] - var i = start - while i < s.len: - var L = matchLen(s, pattern, matches, i) - if L < 0: break - for k in 0..MaxSubPatterns-1: - if isNil(matches[k]): break - yield matches[k] - inc(i, L) - -proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. - rtl, extern: "npegs$1".} = - ## returns all matching captures of pattern in `s`. - ## If it does not match, @[] is returned. - accumulateResult(findAll(s, pattern, start)) - -template `=~`*(s: string, pattern: TPeg): untyped = - ## This calls ``match`` with an implicit declared ``matches`` array that - ## can be used in the scope of the ``=~`` call: - ## - ## .. code-block:: nim - ## - ## if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}": - ## # matches a key=value pair: - ## echo("Key: ", matches[0]) - ## echo("Value: ", matches[1]) - ## elif line =~ peg"\s*{'#'.*}": - ## # matches a comment - ## # note that the implicit ``matches`` array is different from the - ## # ``matches`` array of the first branch - ## echo("comment: ", matches[0]) - ## else: - ## echo("syntax error") - ## - when not declaredInScope(matches): - var matches {.inject.}: array[0..MaxSubpatterns-1, string] - match(s, pattern, matches) - -# ------------------------- more string handling ------------------------------ - -proc contains*(s: string, pattern: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## same as ``find(s, pattern, start) >= 0`` - return find(s, pattern, start) >= 0 - -proc contains*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): bool {.rtl, extern: "npegs$1Capture".} = - ## same as ``find(s, pattern, matches, start) >= 0`` - return find(s, pattern, matches, start) >= 0 - -proc startsWith*(s: string, prefix: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## returns true if `s` starts with the pattern `prefix` - result = matchLen(s, prefix, start) >= 0 - -proc endsWith*(s: string, suffix: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## returns true if `s` ends with the pattern `prefix` - for i in start .. s.len-1: - if matchLen(s, suffix, i) == s.len - i: return true - -proc replacef*(s: string, sub: TPeg, by: string): string {. - rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by` - ## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples: - ## - ## .. code-block:: nim - ## "var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2") - ## - ## Results in: - ## - ## .. code-block:: nim - ## - ## "var1<-keykey; val2<-key2key2" - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - var x = matchLen(s, sub, caps, i) - if x <= 0: - add(result, s[i]) - inc(i) - else: - addf(result, by, caps) - inc(i, x) - add(result, substr(s, i)) - -proc replace*(s: string, sub: TPeg, by = ""): string {. - rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures cannot be accessed - ## in `by`. - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - var x = matchLen(s, sub, caps, i) - if x <= 0: - add(result, s[i]) - inc(i) - else: - addf(result, by, caps) - inc(i, x) - add(result, substr(s, i)) - -proc parallelReplace*(s: string, subs: varargs[ - tuple[pattern: TPeg, repl: string]]): string {. - rtl, extern: "npegs$1".} = - ## Returns a modified copy of `s` with the substitutions in `subs` - ## applied in parallel. - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - block searchSubs: - for j in 0..high(subs): - var x = matchLen(s, subs[j][0], caps, i) - if x > 0: - addf(result, subs[j][1], caps) - inc(i, x) - break searchSubs - add(result, s[i]) - inc(i) - # copy the rest: - add(result, substr(s, i)) - -proc transformFile*(infile, outfile: string, - subs: varargs[tuple[pattern: TPeg, repl: string]]) {. - rtl, extern: "npegs$1".} = - ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Calls ``quit`` if an - ## error occurs. This is supposed to be used for quick scripting. - var x = readFile(infile) - if not isNil(x): - var f: File - if open(f, outfile, fmWrite): - write(f, x.parallelReplace(subs)) - close(f) - else: - quit("cannot open for writing: " & outfile) - else: - quit("cannot open for reading: " & infile) - -iterator split*(s: string, sep: TPeg): string = - ## Splits the string `s` into substrings. - ## - ## Substrings are separated by the PEG `sep`. - ## Examples: - ## - ## .. code-block:: nim - ## for word in split("00232this02939is39an22example111", peg"\d+"): - ## writeLine(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: nim - ## "this" - ## "is" - ## "an" - ## "example" - ## - var - first = 0 - last = 0 - while last < len(s): - var x = matchLen(s, sep, last) - if x > 0: inc(last, x) - first = last - while last < len(s): - inc(last) - x = matchLen(s, sep, last) - if x > 0: break - if first < last: - yield substr(s, first, last-1) - -proc split*(s: string, sep: TPeg): seq[string] {. - rtl, extern: "npegs$1".} = - ## Splits the string `s` into substrings. - accumulateResult(split(s, sep)) - -# ------------------- scanner ------------------------------------------------- - -type - TModifier = enum - modNone, - modVerbatim, - modIgnoreCase, - modIgnoreStyle - TTokKind = enum ## enumeration of all tokens - tkInvalid, ## invalid token - tkEof, ## end of file reached - tkAny, ## . - tkAnyRune, ## _ - tkIdentifier, ## abc - tkStringLit, ## "abc" or 'abc' - tkCharSet, ## [^A-Z] - tkParLe, ## '(' - tkParRi, ## ')' - tkCurlyLe, ## '{' - tkCurlyRi, ## '}' - tkCurlyAt, ## '{@}' - tkArrow, ## '<-' - tkBar, ## '/' - tkStar, ## '*' - tkPlus, ## '+' - tkAmp, ## '&' - tkNot, ## '!' - tkOption, ## '?' - tkAt, ## '@' - tkBuiltin, ## \identifier - tkEscaped, ## \\ - tkBackref, ## '$' - tkDollar, ## '$' - tkHat ## '^' - - TToken {.final.} = object ## a token - kind: TTokKind ## the type of the token - modifier: TModifier - literal: string ## the parsed (string) literal - charset: set[char] ## if kind == tkCharSet - index: int ## if kind == tkBackref - - TPegLexer {.inheritable.} = object ## the lexer object. - bufpos: int ## the current position within the buffer - buf: cstring ## the buffer itself - lineNumber: int ## the current line number - lineStart: int ## index of last line start in buffer - colOffset: int ## column to add - filename: string - -const - tokKindToStr: array[TTokKind, string] = [ - "invalid", "[EOF]", ".", "_", "identifier", "string literal", - "character set", "(", ")", "{", "}", "{@}", - "<-", "/", "*", "+", "&", "!", "?", - "@", "built-in", "escaped", "$", "$", "^" - ] - -proc HandleCR(L: var TPegLexer, pos: int): int = - assert(L.buf[pos] == '\c') - inc(L.linenumber) - result = pos+1 - if L.buf[result] == '\L': inc(result) - L.lineStart = result - -proc HandleLF(L: var TPegLexer, pos: int): int = - assert(L.buf[pos] == '\L') - inc(L.linenumber) - result = pos+1 - L.lineStart = result - -proc init(L: var TPegLexer, input, filename: string, line = 1, col = 0) = - L.buf = input - L.bufpos = 0 - L.lineNumber = line - L.colOffset = col - L.lineStart = 0 - L.filename = filename - -proc getColumn(L: TPegLexer): int {.inline.} = - result = abs(L.bufpos - L.lineStart) + L.colOffset - -proc getLine(L: TPegLexer): int {.inline.} = - result = L.linenumber - -proc errorStr(L: TPegLexer, msg: string, line = -1, col = -1): string = - var line = if line < 0: getLine(L) else: line - var col = if col < 0: getColumn(L) else: col - result = "$1($2, $3) Error: $4" % [L.filename, $line, $col, msg] - -proc handleHexChar(c: var TPegLexer, xi: var int) = - case c.buf[c.bufpos] - of '0'..'9': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0')) - inc(c.bufpos) - of 'a'..'f': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10) - inc(c.bufpos) - of 'A'..'F': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10) - inc(c.bufpos) - else: discard - -proc getEscapedChar(c: var TPegLexer, tok: var TToken) = - inc(c.bufpos) - case c.buf[c.bufpos] - of 'r', 'R', 'c', 'C': - add(tok.literal, '\c') - inc(c.bufpos) - of 'l', 'L': - add(tok.literal, '\L') - inc(c.bufpos) - of 'f', 'F': - add(tok.literal, '\f') - inc(c.bufpos) - of 'e', 'E': - add(tok.literal, '\e') - inc(c.bufpos) - of 'a', 'A': - add(tok.literal, '\a') - inc(c.bufpos) - of 'b', 'B': - add(tok.literal, '\b') - inc(c.bufpos) - of 'v', 'V': - add(tok.literal, '\v') - inc(c.bufpos) - of 't', 'T': - add(tok.literal, '\t') - inc(c.bufpos) - of 'x', 'X': - inc(c.bufpos) - var xi = 0 - handleHexChar(c, xi) - handleHexChar(c, xi) - if xi == 0: tok.kind = tkInvalid - else: add(tok.literal, chr(xi)) - of '0'..'9': - var val = ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - var i = 1 - while (i <= 3) and (c.buf[c.bufpos] in {'0'..'9'}): - val = val * 10 + ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - inc(i) - if val > 0 and val <= 255: add(tok.literal, chr(val)) - else: tok.kind = tkInvalid - of '\0'..'\31': - tok.kind = tkInvalid - elif c.buf[c.bufpos] in strutils.Letters: - tok.kind = tkInvalid - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc skip(c: var TPegLexer) = - var pos = c.bufpos - var buf = c.buf - while true: - case buf[pos] - of ' ', '\t': - inc(pos) - of '#': - while not (buf[pos] in {'\c', '\L', '\0'}): inc(pos) - of '\c': - pos = HandleCR(c, pos) - buf = c.buf - of '\L': - pos = HandleLF(c, pos) - buf = c.buf - else: - break # EndOfFile also leaves the loop - c.bufpos = pos - -proc getString(c: var TPegLexer, tok: var TToken) = - tok.kind = tkStringLit - var pos = c.bufPos + 1 - var buf = c.buf - var quote = buf[pos-1] - while true: - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - of '\c', '\L', '\0': - tok.kind = tkInvalid - break - elif buf[pos] == quote: - inc(pos) - break - else: - add(tok.literal, buf[pos]) - inc(pos) - c.bufpos = pos - -proc getDollar(c: var TPegLexer, tok: var TToken) = - var pos = c.bufPos + 1 - var buf = c.buf - if buf[pos] in {'0'..'9'}: - tok.kind = tkBackref - tok.index = 0 - while buf[pos] in {'0'..'9'}: - tok.index = tok.index * 10 + ord(buf[pos]) - ord('0') - inc(pos) - else: - tok.kind = tkDollar - c.bufpos = pos - -proc getCharSet(c: var TPegLexer, tok: var TToken) = - tok.kind = tkCharSet - tok.charset = {} - var pos = c.bufPos + 1 - var buf = c.buf - var caret = false - if buf[pos] == '^': - inc(pos) - caret = true - while true: - var ch: char - case buf[pos] - of ']': - inc(pos) - break - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch = buf[pos] - inc(pos) - incl(tok.charset, ch) - if buf[pos] == '-': - if buf[pos+1] == ']': - incl(tok.charset, '-') - inc(pos) - else: - inc(pos) - var ch2: char - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch2 = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch2 = buf[pos] - inc(pos) - for i in ord(ch)+1 .. ord(ch2): - incl(tok.charset, chr(i)) - c.bufpos = pos - if caret: tok.charset = {'\1'..'\xFF'} - tok.charset - -proc getSymbol(c: var TPegLexer, tok: var TToken) = - var pos = c.bufpos - var buf = c.buf - while true: - add(tok.literal, buf[pos]) - inc(pos) - if buf[pos] notin strutils.IdentChars: break - c.bufpos = pos - tok.kind = tkIdentifier - -proc getBuiltin(c: var TPegLexer, tok: var TToken) = - if c.buf[c.bufpos+1] in strutils.Letters: - inc(c.bufpos) - getSymbol(c, tok) - tok.kind = tkBuiltin - else: - tok.kind = tkEscaped - getEscapedChar(c, tok) # may set tok.kind to tkInvalid - -proc getTok(c: var TPegLexer, tok: var TToken) = - tok.kind = tkInvalid - tok.modifier = modNone - setlen(tok.literal, 0) - skip(c) - case c.buf[c.bufpos] - of '{': - inc(c.bufpos) - if c.buf[c.bufpos] == '@' and c.buf[c.bufpos+1] == '}': - tok.kind = tkCurlyAt - inc(c.bufpos, 2) - add(tok.literal, "{@}") - else: - tok.kind = tkCurlyLe - add(tok.literal, '{') - of '}': - tok.kind = tkCurlyRi - inc(c.bufpos) - add(tok.literal, '}') - of '[': - getCharset(c, tok) - of '(': - tok.kind = tkParLe - inc(c.bufpos) - add(tok.literal, '(') - of ')': - tok.kind = tkParRi - inc(c.bufpos) - add(tok.literal, ')') - of '.': - tok.kind = tkAny - inc(c.bufpos) - add(tok.literal, '.') - of '_': - tok.kind = tkAnyRune - inc(c.bufpos) - add(tok.literal, '_') - of '\\': - getBuiltin(c, tok) - of '\'', '"': getString(c, tok) - of '$': getDollar(c, tok) - of '\0': - tok.kind = tkEof - tok.literal = "[EOF]" - of 'a'..'z', 'A'..'Z', '\128'..'\255': - getSymbol(c, tok) - if c.buf[c.bufpos] in {'\'', '"'} or - c.buf[c.bufpos] == '$' and c.buf[c.bufpos+1] in {'0'..'9'}: - case tok.literal - of "i": tok.modifier = modIgnoreCase - of "y": tok.modifier = modIgnoreStyle - of "v": tok.modifier = modVerbatim - else: discard - setLen(tok.literal, 0) - if c.buf[c.bufpos] == '$': - getDollar(c, tok) - else: - getString(c, tok) - if tok.modifier == modNone: tok.kind = tkInvalid - of '+': - tok.kind = tkPlus - inc(c.bufpos) - add(tok.literal, '+') - of '*': - tok.kind = tkStar - inc(c.bufpos) - add(tok.literal, '+') - of '<': - if c.buf[c.bufpos+1] == '-': - inc(c.bufpos, 2) - tok.kind = tkArrow - add(tok.literal, "<-") - else: - add(tok.literal, '<') - of '/': - tok.kind = tkBar - inc(c.bufpos) - add(tok.literal, '/') - of '?': - tok.kind = tkOption - inc(c.bufpos) - add(tok.literal, '?') - of '!': - tok.kind = tkNot - inc(c.bufpos) - add(tok.literal, '!') - of '&': - tok.kind = tkAmp - inc(c.bufpos) - add(tok.literal, '!') - of '@': - tok.kind = tkAt - inc(c.bufpos) - add(tok.literal, '@') - if c.buf[c.bufpos] == '@': - tok.kind = tkCurlyAt - inc(c.bufpos) - add(tok.literal, '@') - of '^': - tok.kind = tkHat - inc(c.bufpos) - add(tok.literal, '^') - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc arrowIsNextTok(c: TPegLexer): bool = - # the only look ahead we need - var pos = c.bufpos - while c.buf[pos] in {'\t', ' '}: inc(pos) - result = c.buf[pos] == '<' and c.buf[pos+1] == '-' - -# ----------------------------- parser ---------------------------------------- - -type - EInvalidPeg* = object of ValueError ## raised if an invalid - ## PEG has been detected - TPegParser = object of TPegLexer ## the PEG parser object - tok: TToken - nonterms: seq[PNonTerminal] - modifier: TModifier - captures: int - identIsVerbatim: bool - skip: TPeg - -proc pegError(p: TPegParser, msg: string, line = -1, col = -1) = - var e: ref EInvalidPeg - new(e) - e.msg = errorStr(p, msg, line, col) - raise e - -proc getTok(p: var TPegParser) = - getTok(p, p.tok) - if p.tok.kind == tkInvalid: pegError(p, "invalid token") - -proc eat(p: var TPegParser, kind: TTokKind) = - if p.tok.kind == kind: getTok(p) - else: pegError(p, tokKindToStr[kind] & " expected") - -proc parseExpr(p: var TPegParser): TPeg - -proc getNonTerminal(p: var TPegParser, name: string): PNonTerminal = - for i in 0..high(p.nonterms): - result = p.nonterms[i] - if cmpIgnoreStyle(result.name, name) == 0: return - # forward reference: - result = newNonTerminal(name, getLine(p), getColumn(p)) - add(p.nonterms, result) - -proc modifiedTerm(s: string, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = term(s) - of modIgnoreCase: result = termIgnoreCase(s) - of modIgnoreStyle: result = termIgnoreStyle(s) - -proc modifiedBackref(s: int, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = backRef(s) - of modIgnoreCase: result = backRefIgnoreCase(s) - of modIgnoreStyle: result = backRefIgnoreStyle(s) - -proc builtin(p: var TPegParser): TPeg = - # do not use "y", "skip" or "i" as these would be ambiguous - case p.tok.literal - of "n": result = newLine() - of "d": result = charset({'0'..'9'}) - of "D": result = charset({'\1'..'\xff'} - {'0'..'9'}) - of "s": result = charset({' ', '\9'..'\13'}) - of "S": result = charset({'\1'..'\xff'} - {' ', '\9'..'\13'}) - of "w": result = charset({'a'..'z', 'A'..'Z', '_', '0'..'9'}) - of "W": result = charset({'\1'..'\xff'} - {'a'..'z','A'..'Z','_','0'..'9'}) - of "a": result = charset({'a'..'z', 'A'..'Z'}) - of "A": result = charset({'\1'..'\xff'} - {'a'..'z', 'A'..'Z'}) - of "ident": result = tpegs.ident - of "letter": result = UnicodeLetter() - of "upper": result = UnicodeUpper() - of "lower": result = UnicodeLower() - of "title": result = UnicodeTitle() - of "white": result = UnicodeWhitespace() - else: pegError(p, "unknown built-in: " & p.tok.literal) - -proc token(terminal: TPeg, p: TPegParser): TPeg = - if p.skip.kind == pkEmpty: result = terminal - else: result = sequence(p.skip, terminal) - -proc primary(p: var TPegParser): TPeg = - case p.tok.kind - of tkAmp: - getTok(p) - return &primary(p) - of tkNot: - getTok(p) - return !primary(p) - of tkAt: - getTok(p) - return !*primary(p) - of tkCurlyAt: - getTok(p) - return !*\primary(p).token(p) - else: discard - case p.tok.kind - of tkIdentifier: - if p.identIsVerbatim: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - elif not arrowIsNextTok(p): - var nt = getNonTerminal(p, p.tok.literal) - incl(nt.flags, ntUsed) - result = nonTerminal(nt).token(p) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - of tkStringLit: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - of tkCharSet: - if '\0' in p.tok.charset: - pegError(p, "binary zero ('\\0') not allowed in character class") - result = charset(p.tok.charset).token(p) - getTok(p) - of tkParLe: - getTok(p) - result = parseExpr(p) - eat(p, tkParRi) - of tkCurlyLe: - getTok(p) - result = capture(parseExpr(p)).token(p) - eat(p, tkCurlyRi) - inc(p.captures) - of tkAny: - result = any().token(p) - getTok(p) - of tkAnyRune: - result = anyRune().token(p) - getTok(p) - of tkBuiltin: - result = builtin(p).token(p) - getTok(p) - of tkEscaped: - result = term(p.tok.literal[0]).token(p) - getTok(p) - of tkDollar: - result = endAnchor() - getTok(p) - of tkHat: - result = startAnchor() - getTok(p) - of tkBackref: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedBackRef(p.tok.index, m).token(p) - if p.tok.index < 0 or p.tok.index > p.captures: - pegError(p, "invalid back reference index: " & $p.tok.index) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - getTok(p) # we must consume a token here to prevent endless loops! - while true: - case p.tok.kind - of tkOption: - result = ?result - getTok(p) - of tkStar: - result = *result - getTok(p) - of tkPlus: - result = +result - getTok(p) - else: break - -proc seqExpr(p: var TPegParser): TPeg = - result = primary(p) - while true: - case p.tok.kind - of tkAmp, tkNot, tkAt, tkStringLit, tkCharset, tkParLe, tkCurlyLe, - tkAny, tkAnyRune, tkBuiltin, tkEscaped, tkDollar, tkBackref, - tkHat, tkCurlyAt: - result = sequence(result, primary(p)) - of tkIdentifier: - if not arrowIsNextTok(p): - result = sequence(result, primary(p)) - else: break - else: break - -proc parseExpr(p: var TPegParser): TPeg = - result = seqExpr(p) - while p.tok.kind == tkBar: - getTok(p) - result = result / seqExpr(p) - -proc parseRule(p: var TPegParser): PNonTerminal = - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = getNonTerminal(p, p.tok.literal) - if ntDeclared in result.flags: - pegError(p, "attempt to redefine: " & result.name) - result.line = getLine(p) - result.col = getColumn(p) - getTok(p) - eat(p, tkArrow) - result.rule = parseExpr(p) - incl(result.flags, ntDeclared) # NOW inlining may be attempted - else: - pegError(p, "rule expected, but found: " & p.tok.literal) - -proc rawParse(p: var TPegParser): TPeg = - ## parses a rule or a PEG expression - while p.tok.kind == tkBuiltin: - case p.tok.literal - of "i": - p.modifier = modIgnoreCase - getTok(p) - of "y": - p.modifier = modIgnoreStyle - getTok(p) - of "skip": - getTok(p) - p.skip = ?primary(p) - else: break - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = parseRule(p).rule - while p.tok.kind != tkEof: - discard parseRule(p) - else: - p.identIsVerbatim = true - result = parseExpr(p) - if p.tok.kind != tkEof: - pegError(p, "EOF expected, but found: " & p.tok.literal) - for i in 0..high(p.nonterms): - var nt = p.nonterms[i] - if ntDeclared notin nt.flags: - pegError(p, "undeclared identifier: " & nt.name, nt.line, nt.col) - elif ntUsed notin nt.flags and i > 0: - pegError(p, "unused rule: " & nt.name, nt.line, nt.col) - -proc parsePeg*(pattern: string, filename = "pattern", line = 1, col = 0): TPeg = - ## constructs a TPeg object from `pattern`. `filename`, `line`, `col` are - ## used for error messages, but they only provide start offsets. `parsePeg` - ## keeps track of line and column numbers within `pattern`. - var p: TPegParser - init(TPegLexer(p), pattern, filename, line, col) - p.tok.kind = tkInvalid - p.tok.modifier = modNone - p.tok.literal = "" - p.tok.charset = {} - p.nonterms = @[] - p.identIsVerbatim = false - getTok(p) - result = rawParse(p) - -proc peg*(pattern: string): TPeg = - ## constructs a TPeg object from the `pattern`. The short name has been - ## chosen to encourage its use as a raw string modifier:: - ## - ## peg"{\ident} \s* '=' \s* {.*}" - result = parsePeg(pattern, "pattern") - -proc escapePeg*(s: string): string = - ## escapes `s` so that it is matched verbatim when used as a peg. - result = "" - var inQuote = false - for c in items(s): - case c - of '\0'..'\31', '\'', '"', '\\': - if inQuote: - result.add('\'') - inQuote = false - result.add("\\x") - result.add(toHex(ord(c), 2)) - else: - if not inQuote: - result.add('\'') - inQuote = true - result.add(c) - if inQuote: result.add('\'') - -when isMainModule: - doAssert escapePeg("abc''def'") == r"'abc'\x27\x27'def'\x27" - #doAssert match("(a b c)", peg"'(' @ ')'") - doAssert match("W_HI_Le", peg"\y 'while'") - doAssert(not match("W_HI_L", peg"\y 'while'")) - doAssert(not match("W_HI_Le", peg"\y v'while'")) - doAssert match("W_HI_Le", peg"y'while'") - - doAssert($ +digits == $peg"\d+") - doAssert "0158787".match(peg"\d+") - doAssert "ABC 0232".match(peg"\w+\s+\d+") - doAssert "ABC".match(peg"\d+ / \w+") - - for word in split("00232this02939is39an22example111", peg"\d+"): - writeLine(stdout, word) - - doAssert matchLen("key", ident) == 3 - - var pattern = sequence(ident, *whitespace, term('='), *whitespace, ident) - doAssert matchLen("key1= cal9", pattern) == 11 - - var ws = newNonTerminal("ws", 1, 1) - ws.rule = *whitespace - - var expr = newNonTerminal("expr", 1, 1) - expr.rule = sequence(capture(ident), *sequence( - nonterminal(ws), term('+'), nonterminal(ws), nonterminal(expr))) - - var c: TCaptures - var s = "a+b + c +d+e+f" - doAssert rawMatch(s, expr.rule, 0, c) == len(s) - var a = "" - for i in 0..c.ml-1: - a.add(substr(s, c.matches[i][0], c.matches[i][1])) - doAssert a == "abcdef" - #echo expr.rule - - #const filename = "lib/devel/peg/grammar.txt" - #var grammar = parsePeg(newFileStream(filename, fmRead), filename) - #echo "a <- [abc]*?".match(grammar) - doAssert find("_____abc_______", term("abc"), 2) == 5 - doAssert match("_______ana", peg"A <- 'ana' / . A") - doAssert match("abcs%%%", peg"A <- ..A / .A / '%'") - - if "abc" =~ peg"{'a'}'bc' 'xyz' / {\ident}": - doAssert matches[0] == "abc" - else: - doAssert false - - var g2 = peg"""S <- A B / C D - A <- 'a'+ - B <- 'b'+ - C <- 'c'+ - D <- 'd'+ - """ - doAssert($g2 == "((A B) / (C D))") - doAssert match("cccccdddddd", g2) - doAssert("var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey; var2<-key2key2") - doAssert "var1=key; var2=key2".endsWith(peg"{\ident}'='{\ident}") - - if "aaaaaa" =~ peg"'aa' !. / ({'a'})+": - doAssert matches[0] == "a" - else: - doAssert false - - block: - var matches: array[0..2, string] - if match("abcdefg", peg"c {d} ef {g}", matches, 2): - doAssert matches[0] == "d" - doAssert matches[1] == "g" - else: - doAssert false - - for x in findAll("abcdef", peg"{.}", 3): - echo x - - if "f(a, b)" =~ peg"{[0-9]+} / ({\ident} '(' {@} ')')": - doAssert matches[0] == "f" - doAssert matches[1] == "a, b" - else: - doAssert false - - doAssert match("eine übersicht und außerdem", peg"(\letter \white*)+") - # ß is not a lower cased letter?! - doAssert match("eine übersicht und auerdem", peg"(\lower \white*)+") - doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+") - doAssert(not match("456678", peg"(\letter)+")) - - doAssert("var1 = key; var2 = key2".replacef( - peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey;var2<-key2key2") - - doAssert match("prefix/start", peg"^start$", 7) - - # tricky test to check for false aliasing: - block: - var a = term"key" - echo($sequence(sequence(a, term"value"), *a)) - diff --git a/tests/stdlib/twchartoutf8.nim b/tests/stdlib/twchartoutf8.nim index b2f68ee32..a6602e3e3 100644 --- a/tests/stdlib/twchartoutf8.nim +++ b/tests/stdlib/twchartoutf8.nim @@ -30,7 +30,6 @@ else: result = newString(size) let res = WideCharToMultiByte(CP_UTF8, 0'i32, cast[LPWCSTR](addr(wc[0])), wclen, cstring(result), size, cstring(nil), LPBOOL(nil)) - result[size] = chr(0) doAssert size == res proc testCP(wc: WideCString, lo, hi: int) = diff --git a/tests/system/tnilconcats.nim b/tests/system/tnilconcats.nim new file mode 100644 index 000000000..ce059b7b0 --- /dev/null +++ b/tests/system/tnilconcats.nim @@ -0,0 +1,25 @@ +discard """ + output: '''@[nil, nil, nil, nil, nil, nil, nil, "meh"]''' + exitcode: "0" +""" + +when true: + var ab: string + ab &= "more" + + doAssert ab == "more" + + var x: seq[string] + + setLen(x, 7) + + x.add "meh" + + var s: string + var z = "abc" + var zz: string + s &= "foo" & z & zz + + doAssert s == "fooabc" + + echo x diff --git a/tests/system/toString.nim b/tests/system/toString.nim index ea9d6b05b..37c678a74 100644 --- a/tests/system/toString.nim +++ b/tests/system/toString.nim @@ -51,3 +51,59 @@ import strutils let arr = ['H','e','l','l','o',' ','W','o','r','l','d','!','\0'] doAssert $arr == "['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\\x00']" doAssert $cstring(unsafeAddr arr) == "Hello World!" + +proc takes(c: cstring) = + doAssert c == "" + +proc testm() = + var x: string + # nil is mapped to "": + takes(x) + +testm() + +# nil tests +var xx: seq[string] +var yy: string +doAssert xx == @[] +doAssert yy == "" + +proc bar(arg: cstring): void = + doAssert arg[0] == '\0' + +proc baz(arg: openarray[char]): void = + doAssert arg.len == 0 + +proc stringCompare(): void = + var a,b,c,d,e,f,g: string + a.add 'a' + doAssert a == "a" + b.add "bee" + doAssert b == "bee" + b.add g + doAssert b == "bee" + c.add 123.456 + doAssert c == "123.456" + d.add 123456 + doAssert d == "123456" + + doAssert e == "" + doAssert "" == e + doAssert nil == e + doAssert e == nil + doAssert f == g + doAssert "" == "" + doAssert "" == nil + doAssert nil == "" + + g.setLen(10) + doAssert g == "\0\0\0\0\0\0\0\0\0\0" + doAssert "" != "\0\0\0\0\0\0\0\0\0\0" + + var nilstring: string + bar(nilstring) + baz(nilstring) + +stringCompare() +static: + stringCompare() \ No newline at end of file diff --git a/tests/typerel/t7600_1.nim b/tests/typerel/t7600_1.nim new file mode 100644 index 000000000..e3a5fefa2 --- /dev/null +++ b/tests/typerel/t7600_1.nim @@ -0,0 +1,18 @@ +discard """ +errormsg: "type mismatch: got <Thin[system.int]>" +nimout: '''t7600_1.nim(18, 6) Error: type mismatch: got <Thin[system.int]> +but expected one of: +proc test[T](x: Paper[T]) + +expression: test tn''' +""" + +type + Paper[T] = ref object of RootObj + thickness: T + Thin[T] = object of Paper[T] + +proc test[T](x: Paper[T]) = discard + +var tn = Thin[int]() +test tn diff --git a/tests/typerel/t7600_2.nim b/tests/typerel/t7600_2.nim new file mode 100644 index 000000000..7badb69cf --- /dev/null +++ b/tests/typerel/t7600_2.nim @@ -0,0 +1,17 @@ +discard """ +errormsg: "type mismatch: got <Thin>" +nimout: '''t7600_2.nim(17, 6) Error: type mismatch: got <Thin> +but expected one of: +proc test(x: Paper) + +expression: test tn''' +""" + +type + Paper = ref object of RootObj + Thin = object of Paper + +proc test(x: Paper) = discard + +var tn = Thin() +test tn diff --git a/tests/typerel/ttypelessemptyset.nim b/tests/typerel/ttypelessemptyset.nim index 3e171387b..5f49c33fd 100644 --- a/tests/typerel/ttypelessemptyset.nim +++ b/tests/typerel/ttypelessemptyset.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "internal error: invalid kind for last(tyEmpty)" + errormsg: "internal error: invalid kind for lastOrd(tyEmpty)" """ var q = false discard (if q: {} else: {}) diff --git a/tools/finish.nim b/tools/finish.nim index 2681f7ccf..b5ef78b65 100644 --- a/tools/finish.nim +++ b/tools/finish.nim @@ -52,14 +52,17 @@ when defined(windows): proc askBool(m: string): bool = stdout.write m while true: - let answer = stdin.readLine().normalize - case answer - of "y", "yes": - return true - of "n", "no": - return false - else: - echo "Please type 'y' or 'n'" + try: + let answer = stdin.readLine().normalize + case answer + of "y", "yes": + return true + of "n", "no": + return false + else: + echo "Please type 'y' or 'n'" + except EOFError: + quit(1) proc askNumber(m: string; a, b: int): int = stdout.write m @@ -99,10 +102,6 @@ when defined(windows): proc addToPathEnv*(e: string) = var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER) - if p.len == 0: - p = tryGetUnicodeValue( - r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", - "Path", HKEY_LOCAL_MACHINE) let x = if e.contains(Whitespace): "\"" & e & "\"" else: e if p.len > 0: p.add ";" @@ -189,8 +188,10 @@ when defined(windows): proc main() = when defined(windows): let desiredPath = expandFilename(getCurrentDir() / "bin") - let p = getUnicodeValue(r"Environment", "Path", - HKEY_CURRENT_USER) + let p = tryGetUnicodeValue(r"Environment", "Path", + HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue( + r"System\CurrentControlSet\Control\Session Manager\Environment", "Path", + HKEY_LOCAL_MACHINE) var alreadyInPath = false var mingWchoices: seq[string] = @[] var incompat: seq[string] = @[] @@ -199,7 +200,7 @@ proc main() = let y = try: expandFilename(if x[0] == '"' and x[^1] == '"': substr(x, 1, x.len-2) else: x) except: "" - if y == desiredPath: alreadyInPath = true + if y.cmpIgnoreCase(desiredPath) == 0: alreadyInPath = true if y.toLowerAscii.contains("mingw"): if dirExists(y): if checkGccArch(y): mingWchoices.add y diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index 4cfcbe9bc..9cfd7a86f 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -128,7 +128,7 @@ proc highlight(s, match, repl: string, t: tuple[first, last: int], stdout.write("\n") stdout.flushFile() -proc processFile(pattern; filename: string) = +proc processFile(pattern; filename: string; counter: var int) = var filenameShown = false template beforeHighlight = if not filenameShown and optVerbose notin options and not oneline: @@ -166,6 +166,7 @@ proc processFile(pattern; filename: string) = var wholeMatch = buffer.substr(t.first, t.last) beforeHighlight() + inc counter if optReplace notin options: highlight(buffer, wholeMatch, "", t, filename, line, showRepl=false) else: @@ -241,17 +242,17 @@ proc styleInsensitive(s: string): string = addx() else: addx() -proc walker(pattern; dir: string) = +proc walker(pattern; dir: string; counter: var int) = for kind, path in walkDir(dir): case kind of pcFile: if extensions.len == 0 or path.hasRightExt(extensions): - processFile(pattern, path) + processFile(pattern, path, counter) of pcDir: if optRecursive in options: - walker(pattern, path) + walker(pattern, path, counter) else: discard - if existsFile(dir): processFile(pattern, dir) + if existsFile(dir): processFile(pattern, dir, counter) proc writeHelp() = stdout.write(Usage) @@ -321,6 +322,7 @@ if optStdin in options: if pattern.len == 0: writeHelp() else: + var counter = 0 if filenames.len == 0: filenames.add(os.getCurrentDir()) if optRegex notin options: @@ -332,7 +334,7 @@ else: pattern = "\\i " & pattern let pegp = peg(pattern) for f in items(filenames): - walker(pegp, f) + walker(pegp, f, counter) else: var reflags = {reStudy, reExtended} if optIgnoreStyle in options: @@ -343,5 +345,6 @@ else: reflags.incl reIgnoreCase let rep = re(pattern, reflags) for f in items(filenames): - walker(rep, f) - + walker(rep, f, counter) + if not oneline: + stdout.write($counter & " matches\n") |