diff --git a/changelog.md b/changelog.md index 847a36d841..5858ec28f8 100644 --- a/changelog.md +++ b/changelog.md @@ -108,3 +108,32 @@ This now needs to be written as: - [``poly``](https://github.com/lcrees/polynumeric) - [``pdcurses``](https://github.com/lcrees/pdcurses) - [``romans``](https://github.com/lcrees/romans) + +- Added ``system.runnableExamples`` to make examples in Nim's documentation easier + to write and test. The examples are tested as the last step of + ``nim doc``. +- Nim's ``rst2html`` command now supports the testing of code snippets via an RST + extension that we called ``:test:``:: + + .. code-block:: nim + :test: + # shows how the 'if' statement works + if true: echo "yes" +- The ``[]`` proc for strings now raises an ``IndexError`` exception when + the specified slice is out of bounds. See issue + [#6223](https://github.com/nim-lang/Nim/issues/6223) for more details. +- ``strutils.split`` and ``strutils.rsplit`` with an empty string and a + separator now returns that empty string. + See issue [#4377](https://github.com/nim-lang/Nim/issues/4377). +- The experimental overloading of the dot ``.`` operators now take + an ``untyped``` parameter as the field name, it used to be + a ``static[string]``. You can use ``when defined(nimNewDot)`` to make + your code work with both old and new Nim versions. + See [special-operators](https://nim-lang.org/docs/manual.html#special-operators) + for more information. +- Added ``macros.unpackVarargs``. +- The memory manager now uses a variant of the TLSF algorithm that has much + better memory fragmentation behaviour. According + to [http://www.gii.upv.es/tlsf/](http://www.gii.upv.es/tlsf/) the maximum + fragmentation measured is lower than 25%. As a nice bonus ``alloc`` and + ``dealloc`` became O(1) operations. diff --git a/compiler/ast.nim b/compiler/ast.nim index 787cb49977..5bf4184c95 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -639,7 +639,7 @@ type mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNHint, mNWarning, mNError, mInstantiationInfo, mGetTypeInfo, mNGenSym, - mNimvm, mIntDefine, mStrDefine + mNimvm, mIntDefine, mStrDefine, mRunnableExamples # things that we can evaluate safely at compile time, even if not asked for it: const diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 571135fbb5..5f107f21f9 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1860,7 +1860,10 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[2], b) genDeepCopy(p, a, b) of mDotDot, mEqCString: genCall(p, e, d) - else: internalError(e.info, "genMagicExpr: " & $op) + else: + when defined(debugMagics): + echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind + internalError(e.info, "genMagicExpr: " & $op) proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # example: { a..b, c, d, e, f..g } diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 8dfb82963d..cfa2afdd95 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -968,8 +968,11 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; addf(m.s[cfsTypeInit3], "$1.flags = $2;$n", [name, rope(flags)]) discard cgsym(m, "TNimType") if isDefined("nimTypeNames"): + var typename = typeToString(origType, preferName) + if typename == "ref object" and origType.skipTypes(skipPtrs).sym != nil: + typename = "anon ref object from " & $origType.skipTypes(skipPtrs).sym.info addf(m.s[cfsTypeInit3], "$1.name = $2;$n", - [name, makeCstring typeToString(origType, preferName)]) + [name, makeCstring typename]) discard cgsym(m, "nimTypeRoot") addf(m.s[cfsTypeInit3], "$1.nextType = nimTypeRoot; nimTypeRoot=&$1;$n", [name]) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 19ab2fe50a..0f8fa760e2 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -54,7 +54,7 @@ type TCProcSections* = array[TCProcSection, Rope] # represents a generated C proc BModule* = ref TCGen BProc* = ref TCProc - TBlock*{.final.} = object + TBlock* = object id*: int # the ID of the label; positive means that it label*: Rope # generated text for the label # nil if label is not used @@ -64,7 +64,7 @@ type nestedExceptStmts*: int16 # how many except statements is it nested into frameLen*: int16 - TCProc{.final.} = object # represents C proc that is currently generated + TCProc = object # represents C proc that is currently generated prc*: PSym # the Nim proc that this C proc belongs to beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed threadVarAccessed*: bool # true if the proc already accessed some threadvar diff --git a/compiler/commands.nim b/compiler/commands.nim index 11a66cf55e..de474c6e68 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -654,6 +654,9 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; gListFullPaths = true of "dynliboverride": dynlibOverride(switch, arg, pass, info) + of "dynliboverrideall": + expectNoArg(switch, arg, pass, info) + gDynlibOverrideAll = true of "cs": # only supported for compatibility. Does nothing. expectArg(switch, arg, pass, info) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 2050a746b4..a52214e734 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -110,3 +110,5 @@ proc initDefines*() = when false: defineSymbol("nimHasOpt") defineSymbol("nimNoArrayToCstringConversion") defineSymbol("nimNewRoof") + defineSymbol("nimHasRunnableExamples") + defineSymbol("nimNewDot") diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index 36839bf0ba..caa18af92f 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -167,10 +167,13 @@ template interestingSym(s: PSym): bool = proc patchHead(n: PNode) = if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1: let s = n[0].sym - if sfFromGeneric in s.flags and s.name.s[0] == '=' and - s.name.s in ["=sink", "=", "=destroy"]: - excl(s.flags, sfFromGeneric) - patchHead(s.getBody) + if s.name.s[0] == '=' and s.name.s in ["=sink", "=", "=destroy"]: + if sfFromGeneric in s.flags: + excl(s.flags, sfFromGeneric) + patchHead(s.getBody) + if n[1].typ.isNil: + # XXX toptree crashes without this workaround. Figure out why. + return let t = n[1].typ.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred}) template patch(op, field) = if s.name.s == op and field != nil and field != s: @@ -181,24 +184,30 @@ proc patchHead(n: PNode) = for x in n: patchHead(x) +proc patchHead(s: PSym) = + if sfFromGeneric in s.flags: + patchHead(s.ast[bodyPos]) + +template genOp(opr, opname) = + let op = opr + if op == nil: + globalError(dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) + elif op.ast[genericParamsPos].kind != nkEmpty: + globalError(dest.info, "internal error: '" & opname & "' operator is generic") + patchHead op + result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + proc genSink(t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias}) - let op = if t.sink != nil: t.sink else: t.assignment - assert op != nil - patchHead op.ast[bodyPos] - result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + genOp(if t.sink != nil: t.sink else: t.assignment, "=sink") proc genCopy(t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias}) - assert t.assignment != nil - patchHead t.assignment.ast[bodyPos] - result = newTree(nkCall, newSymNode(t.assignment), newTree(nkHiddenAddr, dest)) + genOp(t.assignment, "=") proc genDestroy(t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias}) - assert t.destructor != nil - patchHead t.destructor.ast[bodyPos] - result = newTree(nkCall, newSymNode(t.destructor), newTree(nkHiddenAddr, dest)) + genOp(t.destructor, "=destroy") proc addTopVar(c: var Con; v: PNode) = c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, emptyNode) @@ -210,7 +219,7 @@ template recurse(n, dest) = dest.add p(n[i], c) proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = - if ri.kind in nkCallKinds: + if ri.kind in nkCallKinds+{nkObjConstr}: result = genSink(ri.typ, dest) # watch out and no not transform 'ri' twice if it's a call: let ri2 = copyNode(ri) @@ -287,6 +296,7 @@ proc p(n: PNode; c: var Con): PNode = recurse(n, result) proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = + echo "injecting into ", n var c: Con c.owner = owner c.tmp = newSym(skTemp, getIdent":d", owner, n.info) @@ -312,7 +322,7 @@ proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = result.add body when defined(nimDebugDestroys): - if owner.name.s == "createSeq": + if owner.name.s == "main" or true: echo "------------------------------------" echo owner.name.s, " transformed to: " echo result diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 8978052e2e..65dcb73c95 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -22,7 +22,6 @@ type TSections = array[TSymKind, Rope] TDocumentor = object of rstgen.RstGenerator modDesc: Rope # module description - id: int # for generating IDs toc, section: TSections indexValFilename: string analytics: string # Google Analytics javascript, "" if doesn't exist @@ -109,6 +108,8 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc = result.id = 100 result.jArray = newJArray() initStrTable result.types + result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = + localError(newLineInfo(d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute") proc dispA(dest: var Rope, xml, tex: string, args: openArray[Rope]) = if gCmd != cmdRst2tex: addf(dest, xml, args) @@ -204,10 +205,87 @@ proc getPlainDocstring(n: PNode): string = if n.comment != nil and startsWith(n.comment, "##"): result = n.comment if result.len < 1: - if n.kind notin {nkEmpty..nkNilLit}: - for i in countup(0, len(n)-1): - result = getPlainDocstring(n.sons[i]) - if result.len > 0: return + for i in countup(0, safeLen(n)-1): + result = getPlainDocstring(n.sons[i]) + if result.len > 0: return + +proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) = + var r: TSrcGen + var literal = "" + initTokRender(r, n, renderFlags) + var kind = tkEof + while true: + getNextTok(r, kind, literal) + case kind + of tkEof: + break + of tkComment: + dispA(result, "$1", "\\spanComment{$1}", + [rope(esc(d.target, literal))]) + of tokKeywordLow..tokKeywordHigh: + dispA(result, "$1", "\\spanKeyword{$1}", + [rope(literal)]) + of tkOpr: + dispA(result, "$1", "\\spanOperator{$1}", + [rope(esc(d.target, literal))]) + of tkStrLit..tkTripleStrLit: + dispA(result, "$1", + "\\spanStringLit{$1}", [rope(esc(d.target, literal))]) + of tkCharLit: + dispA(result, "$1", "\\spanCharLit{$1}", + [rope(esc(d.target, literal))]) + of tkIntLit..tkUInt64Lit: + dispA(result, "$1", + "\\spanDecNumber{$1}", [rope(esc(d.target, literal))]) + of tkFloatLit..tkFloat128Lit: + dispA(result, "$1", + "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) + of tkSymbol: + dispA(result, "$1", + "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) + of tkSpaces, tkInvalid: + add(result, literal) + of tkCurlyDotLe: + dispA(result, """$1
""", + "\\spanOther{$1}", + [rope(esc(d.target, literal))]) + of tkCurlyDotRi: + dispA(result, "
$1", + "\\spanOther{$1}", + [rope(esc(d.target, literal))]) + of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi, + tkBracketDotLe, tkBracketDotRi, tkParDotLe, + tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot, + tkAccent, tkColonColon, + tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr: + dispA(result, "$1", "\\spanOther{$1}", + [rope(esc(d.target, literal))]) + +proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = + case n.kind + of nkCallKinds: + if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and + n.len >= 2 and n.lastSon.kind == nkStmtList: + dispA(dest, "\n$1\n", + "\n\\textbf{$1}\n", [rope"Examples:"]) + inc d.listingCounter + let id = $d.listingCounter + dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"]) + # this is a rather hacky way to get rid of the initial indentation + # that the renderer currently produces: + var i = 0 + var body = n.lastSon + if body.len == 1 and body.kind == nkStmtList and + body.lastSon.kind == nkStmtList: + body = body.lastSon + for b in body: + if i > 0: dest.add "\n" + inc i + nodeToHighlightedHtml(d, b, dest, {}) + dest.add(d.config.getOrDefault"doc.listing_end" % id) + else: discard + for i in 0 ..< n.safeLen: + getAllRunnableExamples(d, n[i], dest) when false: proc findDocComment(n: PNode): PNode = @@ -379,11 +457,12 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = let name = getName(d, nameNode) nameRope = name.rope - plainDocstring = getPlainDocstring(n) # call here before genRecComment! + var plainDocstring = getPlainDocstring(n) # call here before genRecComment! var result: Rope = nil var literal, plainName = "" var kind = tkEof var comm = genRecComment(d, n) # call this here for the side-effect! + getAllRunnableExamples(d, n, comm) var r: TSrcGen # Obtain the plain rendered string for hyperlink titles. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments, @@ -395,53 +474,7 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = plainName.add(literal) # Render the HTML hyperlink. - initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments}) - while true: - getNextTok(r, kind, literal) - case kind - of tkEof: - break - of tkComment: - dispA(result, "$1", "\\spanComment{$1}", - [rope(esc(d.target, literal))]) - of tokKeywordLow..tokKeywordHigh: - dispA(result, "$1", "\\spanKeyword{$1}", - [rope(literal)]) - of tkOpr: - dispA(result, "$1", "\\spanOperator{$1}", - [rope(esc(d.target, literal))]) - of tkStrLit..tkTripleStrLit: - dispA(result, "$1", - "\\spanStringLit{$1}", [rope(esc(d.target, literal))]) - of tkCharLit: - dispA(result, "$1", "\\spanCharLit{$1}", - [rope(esc(d.target, literal))]) - of tkIntLit..tkUInt64Lit: - dispA(result, "$1", - "\\spanDecNumber{$1}", [rope(esc(d.target, literal))]) - of tkFloatLit..tkFloat128Lit: - dispA(result, "$1", - "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) - of tkSymbol: - dispA(result, "$1", - "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) - of tkSpaces, tkInvalid: - add(result, literal) - of tkCurlyDotLe: - dispA(result, """$1
""", - "\\spanOther{$1}", - [rope(esc(d.target, literal))]) - of tkCurlyDotRi: - dispA(result, "
$1", - "\\spanOther{$1}", - [rope(esc(d.target, literal))]) - of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi, - tkBracketDotLe, tkBracketDotRi, tkParDotLe, - tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot, - tkAccent, tkColonColon, - tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr: - dispA(result, "$1", "\\spanOther{$1}", - [rope(esc(d.target, literal))]) + nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, renderDocComments}) inc(d.id) let @@ -520,12 +553,24 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = proc checkForFalse(n: PNode): bool = result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0 -proc traceDeps(d: PDoc, n: PNode) = +proc traceDeps(d: PDoc, it: PNode) = const k = skModule - if d.section[k] != nil: add(d.section[k], ", ") - dispA(d.section[k], - "$1", - "$1", [rope(getModuleName(n))]) + + if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: + let sep = it[0] + let dir = it[1] + let a = newNodeI(nkInfix, it.info) + a.add sep + a.add dir + a.add sep # dummy entry, replaced in the loop + for x in it[2]: + a.sons[2] = x + traceDeps(d, a) + else: + if d.section[k] != nil: add(d.section[k], ", ") + dispA(d.section[k], + "$1", + "$1", [rope(getModuleName(it))]) proc generateDoc*(d: PDoc, n: PNode) = case n.kind @@ -609,10 +654,7 @@ proc generateJson*(d: PDoc, n: PNode) = else: discard proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string = - var - name = getName(d, nameNode) - - result = name & "\n" + result = getName(d, nameNode) & "\n" proc generateTags*(d: PDoc, n: PNode, r: var Rope) = case n.kind @@ -758,6 +800,26 @@ proc commandDoc*() = proc commandRstAux(filename, outExt: string) = var filen = addFileExt(filename, "txt") var d = newDocumentor(filen, options.gConfigVars) + d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; + status: int; content: string) = + var outp: string + if filename.len == 0: + inc(d.id) + let nameOnly = splitFile(d.filename).name + let subdir = getNimcacheDir() / nameOnly + createDir(subdir) + outp = subdir / (nameOnly & "_snippet_" & $d.id & ".nim") + elif isAbsolute(filename): + outp = filename + else: + # Nim's convention: every path is relative to the file it was written in: + outp = splitFile(d.filename).dir / filename + writeFile(outp, content) + let cmd = unescape(cmd) % quoteShell(outp) + rawMessage(hintExecuting, cmd) + if execShellCmd(cmd) != status: + rawMessage(errExecutionOfProgramFailed, cmd) + d.isPureRst = true var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc, {roSupportRawDirective}) diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index 2c8abdfced..7fa6df3da4 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -42,7 +42,7 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam: handleParam actual.sons[s.owner.typ.len + s.position - 1] else: - internalAssert sfGenSym in s.flags + internalAssert sfGenSym in s.flags or s.kind == skType var x = PSym(idTableGet(c.mapping, s)) if x == nil: x = copySym(s, false) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index cac3a6e9f9..42c341651b 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -728,13 +728,13 @@ proc execCmdsInParallel(cmds: seq[string]; prettyCb: proc (idx: int)) = else: tryExceptOSErrorMessage("invocation of external compiler program failed."): if optListCmd in gGlobalOptions or gVerbosity > 1: - res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, + res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath}, gNumberOfProcessors, afterRunEvent=runCb) elif gVerbosity == 1: - res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, + res = execProcesses(cmds, {poStdErrToStdOut, poUsePath}, gNumberOfProcessors, prettyCb, afterRunEvent=runCb) else: - res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, + res = execProcesses(cmds, {poStdErrToStdOut, poUsePath}, gNumberOfProcessors, afterRunEvent=runCb) if res != 0: if gNumberOfProcessors <= 1: @@ -764,8 +764,9 @@ proc callCCompiler*(projectfile: string) = add(objfiles, quoteShell( addFileExt(objFile, CC[cCompiler].objExt))) for x in toCompile: + let objFile = if noAbsolutePaths(): x.obj.extractFilename else: x.obj add(objfiles, ' ') - add(objfiles, quoteShell(x.obj)) + add(objfiles, quoteShell(objFile)) linkCmd = getLinkCmd(projectfile, objfiles) if optCompileOnly notin gGlobalOptions: diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 855a85be7f..bc0f90e179 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1563,14 +1563,22 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = internalError("createVar: " & $t.kind) result = nil +template returnType: untyped = + ~"" + proc genVarInit(p: PProc, v: PSym, n: PNode) = var a: TCompRes s: Rope + varCode: string + if v.constraint.isNil: + varCode = "var $2" + else: + varCode = v.constraint.strVal if n.kind == nkEmpty: let mname = mangleName(v, p.target) - lineF(p, "var $1 = $2;$n" | "$$$1 = $2;$n", - [mname, createVar(p, v.typ, isIndirect(v))]) + lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", + [returnType, mname, createVar(p, v.typ, isIndirect(v))]) if v.typ.kind in { tyVar, tyPtr, tyRef } and mapType(p, v.typ) == etyBaseIndex: lineF(p, "var $1_Idx = 0;$n", [ mname ]) else: @@ -1587,25 +1595,25 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {} if a.typ == etyBaseIndex: if targetBaseIndex: - lineF(p, "var $1 = $2, $1_Idx = $3;$n", - [v.loc.r, a.address, a.res]) + lineF(p, varCode & " = $3, $2_Idx = $4;$n", + [returnType, v.loc.r, a.address, a.res]) else: - lineF(p, "var $1 = [$2, $3];$n", - [v.loc.r, a.address, a.res]) + lineF(p, varCode & " = [$3, $4];$n", + [returnType, v.loc.r, a.address, a.res]) else: if targetBaseIndex: let tmp = p.getTemp lineF(p, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n", [tmp, a.res, v.loc.r]) else: - lineF(p, "var $1 = $2;$n", [v.loc.r, a.res]) + lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, a.res]) return else: s = a.res if isIndirect(v): - lineF(p, "var $1 = [$2];$n", [v.loc.r, s]) + lineF(p, varCode & " = [$3];$n", [returnType, v.loc.r, s]) else: - lineF(p, "var $1 = $2;$n" | "$$$1 = $2;$n", [v.loc.r, s]) + lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s]) proc genVarStmt(p: PProc, n: PNode) = for i in countup(0, sonsLen(n) - 1): @@ -2162,8 +2170,22 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = returnStmt = "return $#;$n" % [a.res] p.nested: genStmt(p, prc.getBody) - let def = "function $#($#) {$n$#$#$#$#$#" % - [name, header, + + var def: Rope + if not prc.constraint.isNil: + def = (prc.constraint.strVal & " {$n$#$#$#$#$#") % + [ returnType, + name, + header, + optionaLine(p.globals), + optionaLine(p.locals), + optionaLine(resultAsgn), + optionaLine(genProcBody(p, prc)), + optionaLine(p.indentLine(returnStmt))] + else: + def = "function $#($#) {$n$#$#$#$#$#" % + [ name, + header, optionaLine(p.globals), optionaLine(p.locals), optionaLine(resultAsgn), diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 4106494c4a..bca07e5000 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -860,6 +860,23 @@ proc getOperator(L: var TLexer, tok: var TToken) = if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: tok.strongSpaceB = -1 +proc newlineFollows*(L: var TLexer): bool = + var pos = L.bufpos + var buf = L.buf + while true: + case buf[pos] + of ' ', '\t': + inc(pos) + of CR, LF: + result = true + break + of '#': + inc(pos) + if buf[pos] == '#': inc(pos) + if buf[pos] != '[': return true + else: + break + proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; isDoc: bool) = var pos = start diff --git a/compiler/options.nim b/compiler/options.nim index eec9ce4487..8c4fe485eb 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -145,6 +145,7 @@ var gNoNimblePath* = false gExperimentalMode*: bool newDestructors*: bool + gDynlibOverrideAll*: bool proc importantComments*(): bool {.inline.} = gCmd in {cmdDoc, cmdIdeTools} proc usesNativeGC*(): bool {.inline.} = gSelectedGC >= gcRefc @@ -427,7 +428,7 @@ proc inclDynlibOverride*(lib: string) = gDllOverrides[lib.canonDynlibName] = "true" proc isDynlibOverride*(lib: string): bool = - result = gDllOverrides.hasKey(lib.canonDynlibName) + result = gDynlibOverrideAll or gDllOverrides.hasKey(lib.canonDynlibName) proc binaryStrSearch*(x: openArray[string], y: string): int = var a = 0 diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index f1d81f7983..b598cadb20 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -23,7 +23,7 @@ const wMagic, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, - wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl, + wAsmNoStackFrame, wError, wDiscardable, wNoInit, wCodegenDecl, wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, wOverride, wConstructor, wExportNims, wUsed, wLiftLocals} converterPragmas* = procPragmas @@ -759,10 +759,6 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, incl(sym.loc.flags, lfNoDecl) # implies nodecl, because otherwise header would not make sense if sym.loc.r == nil: sym.loc.r = rope(sym.name.s) - of wDestructor: - sym.flags.incl sfOverriden - if sym.name.s.normalize != "destroy": - localError(n.info, errGenerated, "destructor has to be named 'destroy'") of wOverride: sym.flags.incl sfOverriden of wNosideeffect: diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 03267c53ef..6f80afefa7 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -826,7 +826,10 @@ proc gident(g: var TSrcGen, n: PNode) = t = tkOpr put(g, t, s) if n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags): - put(g, tkIntLit, $n.sym.id) + when defined(debugMagics): + put(g, tkIntLit, $n.sym.id & $n.sym.magic) + else: + put(g, tkIntLit, $n.sym.id) proc doParamsAux(g: var TSrcGen, params: PNode) = if params.len > 1: diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 22377a1e2e..dac2672636 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -143,6 +143,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; proc runNimScript*(cache: IdentCache; scriptName: string; freshDefines=true; config: ConfigRef=nil) = + rawMessage(hintConf, scriptName) passes.gIncludeFile = includeModule passes.gImportModule = importModule let graph = newModuleGraph(config) diff --git a/compiler/sem.nim b/compiler/sem.nim index 3608bc11c0..495321de45 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -570,6 +570,18 @@ proc myProcess(context: PPassContext, n: PNode): PNode = result = ast.emptyNode #if gCmd == cmdIdeTools: findSuggest(c, n) +proc testExamples(c: PContext) = + let inp = toFullPath(c.module.info) + let outp = inp.changeFileExt"" & "_examples.nim" + renderModule(c.runnableExamples, inp, outp) + let backend = if isDefined("js"): "js" + elif isDefined("cpp"): "cpp" + elif isDefined("objc"): "objc" + else: "c" + if os.execShellCmd("nim " & backend & " -r " & outp) != 0: + quit "[Examples] failed" + removeFile(outp) + proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) if gCmd == cmdIdeTools and not c.suggestionsMade: @@ -584,5 +596,6 @@ proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = result.add(c.module.ast) popOwner(c) popProcCon(c) + if c.runnableExamples != nil: testExamples(c) const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose) diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index cad5087082..db08605cfb 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -7,8 +7,8 @@ # distribution, for details about the copyright. # -## This module implements lifting for assignments. Later versions of this code -## will be able to also lift ``=deepCopy`` and ``=destroy``. +## This module implements lifting for type-bound operations +## (``=sink``, ``=``, ``=destroy``, ``=deepCopy``). # included from sem.nim @@ -302,6 +302,7 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; n.sons[paramsPos] = result.typ.n n.sons[bodyPos] = body result.ast = n + incl result.flags, sfFromGeneric proc getAsgnOrLiftBody(c: PContext; typ: PType; info: TLineInfo): PSym = @@ -319,8 +320,10 @@ proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) = ## to ensure we lift assignment, destructors and moves properly. ## The later 'destroyer' pass depends on it. if not newDestructors or not hasDestructor(typ): return - # do not produce wrong liftings while we're still instantiating generics: - if c.typesWithOps.len > 0: return + when false: + # do not produce wrong liftings while we're still instantiating generics: + # now disabled; breaks topttree.nim! + if c.typesWithOps.len > 0: return let typ = typ.skipTypes({tyGenericInst, tyAlias}) # we generate the destructor first so that other operators can depend on it: if typ.destructor == nil: @@ -329,3 +332,6 @@ proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) = liftBody(c, typ, attachedAsgn, info) if typ.sink == nil: liftBody(c, typ, attachedSink, info) + +#proc patchResolvedTypeBoundOp*(c: PContext; n: PNode): PNode = +# if n.kind == nkCall and diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 5c0624a77f..a51b9afe30 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -179,7 +179,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = add(result, ')') if candidates != "": add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) - localError(n.info, errGenerated, result) + localError(n.info, errGenerated, result & "\nexpression: " & $n) proc bracketNotFoundError(c: PContext; n: PNode) = var errors: CandidateErrors = @[] @@ -235,12 +235,11 @@ proc resolveOverloads(c: PContext, n, orig: PNode, if nfDotField in n.flags: internalAssert f.kind == nkIdent and n.sonsLen >= 2 - let calleeName = newStrNode(nkStrLit, f.ident.s).withInfo(n.info) # leave the op head symbol empty, # we are going to try multiple variants - n.sons[0..1] = [nil, n[1], calleeName] - orig.sons[0..1] = [nil, orig[1], calleeName] + n.sons[0..1] = [nil, n[1], f] + orig.sons[0..1] = [nil, orig[1], f] template tryOp(x) = let op = newIdentNode(getIdent(x), n.info) @@ -255,8 +254,8 @@ proc resolveOverloads(c: PContext, n, orig: PNode, tryOp "." elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3: - let calleeName = newStrNode(nkStrLit, - f.ident.s[0..f.ident.s.len-2]).withInfo(n.info) + # we need to strip away the trailing '=' here: + let calleeName = newIdentNode(getIdent(f.ident.s[0..f.ident.s.len-2]), n.info) let callOp = newIdentNode(getIdent".=", n.info) n.sons[0..1] = [callOp, n[1], calleeName] orig.sons[0..1] = [callOp, orig[1], calleeName] diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 5057260a4b..8affee649f 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -136,6 +136,7 @@ type # the generic type has been constructed completely. See # tests/destructor/topttree.nim for an example that # would otherwise fail. + runnableExamples*: PNode proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = result.genericSym = s diff --git a/compiler/semdestruct.nim b/compiler/semdestruct.nim deleted file mode 100644 index 4b61c6316c..0000000000 --- a/compiler/semdestruct.nim +++ /dev/null @@ -1,186 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements destructors. - -# included from sem.nim - -# special marker values that indicates that we are -# 1) AnalyzingDestructor: currently analyzing the type for destructor -# generation (needed for recursive types) -# 2) DestructorIsTrivial: completed the analysis before and determined -# that the type has a trivial destructor -var analyzingDestructor, destructorIsTrivial: PSym -new(analyzingDestructor) -new(destructorIsTrivial) - -var - destructorName = getIdent"destroy_" - destructorParam = getIdent"this_" - destructorPragma = newIdentNode(getIdent"destructor", unknownLineInfo()) - -proc instantiateDestructor(c: PContext, typ: PType): PType - -proc doDestructorStuff(c: PContext, s: PSym, n: PNode) = - var t = s.typ.sons[1].skipTypes({tyVar}) - if t.kind == tyGenericInvocation: - for i in 1 ..< t.sonsLen: - if t.sons[i].kind != tyGenericParam: - localError(n.info, errDestructorNotGenericEnough) - return - t = t.base - elif t.kind == tyCompositeTypeClass: - t = t.base - if t.kind != tyGenericBody: - localError(n.info, errDestructorNotGenericEnough) - return - - t.destructor = s - # automatically insert calls to base classes' destructors - if n.sons[bodyPos].kind != nkEmpty: - for i in countup(0, t.sonsLen - 1): - # when inheriting directly from object - # there will be a single nil son - if t.sons[i] == nil: continue - let destructableT = instantiateDestructor(c, t.sons[i]) - if destructableT != nil: - n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[ - useSym(destructableT.destructor, c.graph.usageSym), - n.sons[paramsPos][1][0]])) - -proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode - -proc destroySym(c: PContext, field: PSym, holder: PNode): PNode = - let destructableT = instantiateDestructor(c, field.typ) - if destructableT != nil: - result = newNode(nkCall, field.info, @[ - useSym(destructableT.destructor, c.graph.usageSym), - newNode(nkDotExpr, field.info, @[holder, useSym(field, c.graph.usageSym)])]) - -proc destroyCase(c: PContext, n: PNode, holder: PNode): PNode = - var nonTrivialFields = 0 - result = newNode(nkCaseStmt, n.info, @[]) - # case x.kind - result.addSon(newNode(nkDotExpr, n.info, @[holder, n.sons[0]])) - for i in countup(1, n.len - 1): - # of A, B: - let ni = n[i] - var caseBranch = newNode(ni.kind, ni.info, ni.sons[0..ni.len-2]) - - let stmt = destroyFieldOrFields(c, ni.lastSon, holder) - if stmt == nil: - caseBranch.addSon(newNode(nkStmtList, ni.info, @[])) - else: - caseBranch.addSon(stmt) - nonTrivialFields += stmt.len - - result.addSon(caseBranch) - - # maybe no fields were destroyed? - if nonTrivialFields == 0: - result = nil - -proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode = - template maybeAddLine(e) = - let stmt = e - if stmt != nil: - if result == nil: result = newNode(nkStmtList) - result.addSon(stmt) - - case field.kind - of nkRecCase: - maybeAddLine destroyCase(c, field, holder) - of nkSym: - maybeAddLine destroySym(c, field.sym, holder) - of nkRecList: - for son in field: - maybeAddLine destroyFieldOrFields(c, son, holder) - else: - internalAssert false - -proc generateDestructor(c: PContext, t: PType): PNode = - ## generate a destructor for a user-defined object or tuple type - ## returns nil if the destructor turns out to be trivial - - # XXX: This may be true for some C-imported types such as - # Tposix_spawnattr - if t.n == nil or t.n.sons == nil: return - internalAssert t.n.kind == nkRecList - let destructedObj = newIdentNode(destructorParam, unknownLineInfo()) - # call the destructods of all fields - result = destroyFieldOrFields(c, t.n, destructedObj) - # base classes' destructors will be automatically called by - # semProcAux for both auto-generated and user-defined destructors - -proc instantiateDestructor(c: PContext, typ: PType): PType = - # returns nil if a variable of type `typ` doesn't require a - # destructor. Otherwise, returns the type, which holds the - # destructor that must be used for the varialbe. - # The destructor is either user-defined or automatically - # generated by the compiler in a member-wise fashion. - var t = typ.skipGenericAlias - let typeHoldingUserDefinition = if t.kind == tyGenericInst: t.base else: t - - if typeHoldingUserDefinition.destructor != nil: - # XXX: This is not entirely correct for recursive types, but we need - # it temporarily to hide the "destroy is already defined" problem - if typeHoldingUserDefinition.destructor notin - [analyzingDestructor, destructorIsTrivial]: - return typeHoldingUserDefinition - else: - return nil - - t = t.skipTypes({tyGenericInst, tyAlias}) - case t.kind - of tySequence, tyArray, tyOpenArray, tyVarargs: - t.destructor = analyzingDestructor - if instantiateDestructor(c, t.sons[0]) != nil: - t.destructor = getCompilerProc"nimDestroyRange" - return t - else: - return nil - of tyTuple, tyObject: - t.destructor = analyzingDestructor - let generated = generateDestructor(c, t) - if generated != nil: - internalAssert t.sym != nil - var i = t.sym.info - let fullDef = newNode(nkProcDef, i, @[ - newIdentNode(destructorName, i), - emptyNode, - emptyNode, - newNode(nkFormalParams, i, @[ - emptyNode, - newNode(nkIdentDefs, i, @[ - newIdentNode(destructorParam, i), - symNodeFromType(c, makeVarType(c, t), t.sym.info), - emptyNode]), - ]), - newNode(nkPragma, i, @[destructorPragma]), - emptyNode, - generated - ]) - let semantizedDef = semProc(c, fullDef) - t.destructor = semantizedDef[namePos].sym - return t - else: - t.destructor = destructorIsTrivial - return nil - else: - return nil - -proc createDestructorCall(c: PContext, s: PSym): PNode = - let varTyp = s.typ - if varTyp == nil or sfGlobal in s.flags: return - let destructableT = instantiateDestructor(c, varTyp) - if destructableT != nil: - let call = semStmt(c, newNode(nkCall, s.info, @[ - useSym(destructableT.destructor, c.graph.usageSym), - useSym(s, c.graph.usageSym)])) - result = newNode(nkDefer, s.info, @[call]) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d600b1c486..65b111d8f5 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -53,7 +53,6 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = else: if efNoProcvarCheck notin flags: semProcvarCheck(c, result) if result.typ.kind == tyVar: result = newDeref(result) - semDestructorCheck(c, result, flags) proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = semExpr(c, n, flags) @@ -66,7 +65,6 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result.typ = errorType(c) else: semProcvarCheck(c, result) - semDestructorCheck(c, result, flags) proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = result = symChoice(c, n, s, scClosed) @@ -671,6 +669,7 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) if result.typ != nil: liftTypeBoundOps(c, result.typ, n.info) + #result = patchResolvedTypeBoundOp(c, result) if c.matchedConcept == nil: result = evalAtCompileTime(c, result) @@ -1847,6 +1846,18 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = analyseIfAddressTakenInCall(c, result) if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) + of mRunnableExamples: + if gCmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: + if n.sons[0].kind == nkIdent: + if sfMainModule in c.module.flags: + let inp = toFullPath(c.module.info) + if c.runnableExamples == nil: + c.runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) + c.runnableExamples.add newTree(nkBlockStmt, emptyNode, copyTree n.lastSon) + result = setMs(n, s) + else: + result = emptyNode else: result = semDirectOp(c, n, flags) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index c1bf3662f6..e01f867faf 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -97,27 +97,12 @@ template semProcvarCheck(c: PContext, n: PNode) = proc semProc(c: PContext, n: PNode): PNode -include semdestruct - -proc semDestructorCheck(c: PContext, n: PNode, flags: TExprFlags) {.inline.} = - if not newDestructors: - if efAllowDestructor notin flags and - n.kind in nkCallKinds+{nkObjConstr,nkBracket}: - if instantiateDestructor(c, n.typ) != nil: - localError(n.info, warnDestructor) - # This still breaks too many things: - when false: - if efDetermineType notin flags and n.typ.kind == tyTypeDesc and - c.p.owner.kind notin {skTemplate, skMacro}: - localError(n.info, errGenerated, "value expected, but got a type") - 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 == tyVar: result = newDeref(result) - semDestructorCheck(c, result, {}) proc semExprBranchScope(c: PContext, n: PNode): PNode = openScope(c) @@ -421,15 +406,6 @@ proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) = else: result.add identDefs -proc addDefer(c: PContext; result: var PNode; s: PSym) = - let deferDestructorCall = createDestructorCall(c, s) - if deferDestructorCall != nil: - if result.kind != nkStmtList: - let oldResult = result - result = newNodeI(nkStmtList, result.info) - result.add oldResult - result.add deferDestructorCall - proc isDiscardUnderscore(v: PSym): bool = if v.name.s == "_": v.flags.incl(sfGenSym) @@ -609,7 +585,6 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if def.kind == nkPar: v.ast = def[j] setVarType(v, tup.sons[j]) b.sons[j] = newSymNode(v) - if not newDestructors: addDefer(c, result, v) checkNilable(v) if sfCompileTime in v.flags: hasCompileTime = true if hasCompileTime: vm.setupCompileTimeVar(c.module, c.cache, result) @@ -1041,6 +1016,8 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = checkConstructedType(s.info, s.typ) if s.typ.kind in {tyObject, tyTuple} and not s.typ.n.isNil: checkForMetaFields(s.typ.n) + instAllTypeBoundOp(c, n.info) + proc semAllTypeSections(c: PContext; n: PNode): PNode = proc gatherStmts(c: PContext; n: PNode; result: PNode) {.nimcall.} = @@ -1095,9 +1072,11 @@ proc semTypeSection(c: PContext, n: PNode): PNode = ## to allow the type definitions in the section to reference each other ## without regard for the order of their definitions. if sfNoForward notin c.module.flags or nfSem notin n.flags: + inc c.inTypeContext typeSectionLeftSidePass(c, n) typeSectionRightSidePass(c, n) typeSectionFinalPass(c, n) + dec c.inTypeContext result = n proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) = @@ -1318,7 +1297,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = var obj = t.sons[1].sons[0] while true: incl(obj.flags, tfHasAsgn) - if obj.kind == tyGenericBody: obj = obj.lastSon + if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon elif obj.kind == tyGenericInvocation: obj = obj.sons[0] else: break if obj.kind in {tyObject, tyDistinct}: @@ -1331,10 +1310,6 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = if not noError and sfSystemModule notin s.owner.flags: localError(n.info, errGenerated, "signature for '" & s.name.s & "' must be proc[T: object](x: var T)") - else: - doDestructorStuff(c, s, n) - if not experimentalMode(c): - localError n.info, "use the {.experimental.} pragma to enable destructors" incl(s.flags, sfUsed) of "deepcopy", "=deepcopy": if s.typ.len == 2 and @@ -1561,8 +1536,11 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, s.options = gOptions 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 not experimentalMode(c): + if s.name.s in [".", ".()", ".="] and not experimentalMode(c) and not newDestructors: message(n.info, warnDeprecated, "overloaded '.' and '()' operators are now .experimental; " & s.name.s) + elif s.name.s == "()" and not experimentalMode(c): + message(n.info, warnDeprecated, "overloaded '()' operators are now .experimental; " & s.name.s) + if n.sons[bodyPos].kind != nkEmpty: # for DLL generation it is annoying to check for sfImportc! if sfBorrow in s.flags: diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 504992cf5c..5d6b5978d8 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -87,6 +87,7 @@ type CoProc CoType CoOwnerSig + CoIgnoreRange proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) @@ -159,14 +160,15 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = return else: discard - c &= char(t.kind) case t.kind of tyBool, tyChar, tyInt..tyUInt64: # no canonicalization for integral types, so that e.g. ``pid_t`` is # produced instead of ``NI``: + c &= char(t.kind) if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}: c.hashSym(t.sym) of tyObject, tyEnum: + c &= char(t.kind) if t.typeInst != nil: assert t.typeInst.kind == tyGenericInst for i in countup(1, sonsLen(t.typeInst) - 2): @@ -199,26 +201,35 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = if t.len > 0 and t.sons[0] != nil: hashType c, t.sons[0], flags of tyRef, tyPtr, tyGenericBody, tyVar: + c &= char(t.kind) c.hashType t.lastSon, flags if tfVarIsPtr in t.flags: c &= ".varisptr" of tyFromExpr: + c &= char(t.kind) c.hashTree(t.n) of tyTuple: + c &= char(t.kind) if t.n != nil and CoType notin flags: assert(sonsLen(t.n) == sonsLen(t)) for i in countup(0, sonsLen(t.n) - 1): assert(t.n.sons[i].kind == nkSym) c &= t.n.sons[i].sym.name.s c &= ':' - c.hashType(t.sons[i], flags) + c.hashType(t.sons[i], flags+{CoIgnoreRange}) c &= ',' else: - for i in countup(0, sonsLen(t) - 1): c.hashType t.sons[i], flags - of tyRange, tyStatic: - #if CoType notin flags: + for i in countup(0, sonsLen(t) - 1): c.hashType t.sons[i], flags+{CoIgnoreRange} + of tyRange: + if CoIgnoreRange notin flags: + c &= char(t.kind) + c.hashTree(t.n) + c.hashType(t.sons[0], flags) + of tyStatic: + c &= char(t.kind) c.hashTree(t.n) c.hashType(t.sons[0], flags) of tyProc: + c &= char(t.kind) c &= (if tfIterator in t.flags: "iterator " else: "proc ") if CoProc in flags and t.n != nil: let params = t.n @@ -236,7 +247,11 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = if tfNoSideEffect in t.flags: c &= ".noSideEffect" if tfThread in t.flags: c &= ".thread" if tfVarargs in t.flags: c &= ".varargs" + of tyArray: + c &= char(t.kind) + for i in 0.. """ +@if boot: +# This is enabled with the "boot" directive to generate +# the compiler documentation. +# As a user, tweak the block below instead. +# You can add your own global-links entries doc.body_toc_group = """
-
+ +
+ Search: +
+
+ Group by: + +
+ $tableofcontents +
+
+
+

$moduledesc

+ $content +
+
+""" + +@else + +doc.body_toc_group = """ +
+
+ +
Search:
@@ -111,6 +159,7 @@ doc.body_toc_group = """
""" +@end doc.body_no_toc = """ $moduledesc @@ -135,7 +184,7 @@ doc.file = """ - + @@ -168,18 +217,19 @@ html { /* Where we want fancier font if available */ h1, h2, h3, h4, h5, h6, p.module-desc, table.docinfo + blockquote p, table.docinfo blockquote p, h1 + blockquote p { - font-family: "Raleway", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif !important; } + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif !important; } h1.title { font-weight: 900; } body { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: 400; - font-size: 14px; + font-size: 16px; line-height: 20px; - color: #666; - background-color: rgba(252, 248, 244, 0.75); } + color: #444; + letter-spacing: 0.15px; + background-color: rgba(252, 248, 244, 0.45); } /* Skeleton grid */ .container { @@ -295,8 +345,8 @@ cite { font-style: italic !important; } dt > pre { - border-color: rgba(0, 0, 0, 0.15); - background-color: transparent; + border-color: rgba(0, 0, 0, 0.1); + background-color: rgba(255, 255, 255, 0.3); margin: 15px 0px 5px; } dd > pre { @@ -313,6 +363,17 @@ dd > pre { width: 100%; table-layout: fixed; } +/* Nim search input */ +div#searchInput { + margin-bottom: 8px; +} +div#searchInput input#searchInput { + width: 10em; +} +div.search-groupby { + margin-bottom: 8px; +} + table.line-nums-table { border-radius: 4px; border: 1px solid #cccccc; @@ -456,7 +517,7 @@ img { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } p { - margin: 0 0 12px; } + margin: 0 0 8px; } small { font-size: 85%; } @@ -476,7 +537,7 @@ h3, h4, h5, h6 { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: 600; line-height: 20px; color: inherit; @@ -484,6 +545,7 @@ h6 { h1 { font-size: 2em; + font-weight: 400; padding-bottom: .15em; border-bottom: 1px solid #aaaaaa; margin-top: 1.0em; @@ -614,13 +676,13 @@ pre { box-sizing: border-box; min-width: calc(100% - 19.5px); padding: 9.5px; - margin: 0.25em 10px 0.25em 10px; - font-size: 14px; + margin: 0.25em 10px 10px 10px; + font-size: 15px; line-height: 20px; white-space: pre !important; overflow-y: hidden; overflow-x: visible; - background-color: whitesmoke; + background-color: rgba(0, 0, 0, 0.01); border: 1px solid #cccccc; -webkit-border-radius: 4px; -moz-border-radius: 4px; @@ -899,14 +961,14 @@ div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold; - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; } + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title, .code .error { color: #b30000; font-weight: bold; - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; } + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. @@ -953,7 +1015,7 @@ div.sidebar { clear: right; } div.sidebar p.rubric { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-size: medium; } div.system-messages { @@ -1060,12 +1122,12 @@ p.rubric { text-align: center; } p.sidebar-title { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: bold; font-size: larger; } p.sidebar-subtitle { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: bold; } p.topic-title { @@ -1107,15 +1169,15 @@ pre.code .inserted, code .inserted { background-color: #A3D289; } span.classifier { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-style: oblique; } span.classifier-delimiter { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: bold; } span.interpreted { - font-family: "Helvetica Neue", "HelveticaNeue", "Raleway", Helvetica, Arial, sans-serif; } + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; } span.option { white-space: nowrap; } @@ -1138,7 +1200,7 @@ table.docinfo { margin: 0em; margin-top: 2em; margin-bottom: 2em; - font-family: "Raleway", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif !important; + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif !important; color: #444444; } table.docutils { @@ -1268,15 +1330,15 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator background-repeat: no-repeat; background-image: url("data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAUAAAAF////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAIAAABbAAAAlQAAAKIAAACbAAAAmwAAAKIAAACVAAAAWwAAAAL///8A////AP///wD///8A////AAAAABQAAADAAAAAYwAAAA3///8A////AP///wD///8AAAAADQAAAGMAAADAAAAAFP///wD///8A////AP///wAAAACdAAAAOv///wD///8A////AP///wD///8A////AP///wD///8AAAAAOgAAAJ3///8A////AP///wAAAAAnAAAAcP///wAAAAAoAAAASv///wD///8A////AP///wAAAABKAAAAKP///wAAAABwAAAAJ////wD///8AAAAAgQAAABwAAACIAAAAkAAAAJMAAACtAAAAFQAAABUAAACtAAAAkwAAAJAAAACIAAAAHAAAAIH///8A////AAAAAKQAAACrAAAAaP///wD///8AAAAARQAAANIAAADSAAAARf///wD///8AAAAAaAAAAKsAAACk////AAAAADMAAACcAAAAnQAAABj///8A////AP///wAAAAAYAAAAGP///wD///8A////AAAAABgAAACdAAAAnAAAADMAAAB1AAAAwwAAAP8AAADpAAAAsQAAAE4AAAAb////AP///wAAAAAbAAAATgAAALEAAADpAAAA/wAAAMMAAAB1AAAAtwAAAOkAAAD/AAAA/wAAAP8AAADvAAAA3gAAAN4AAADeAAAA3gAAAO8AAAD/AAAA/wAAAP8AAADpAAAAtwAAAGUAAAA/AAAA3wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADfAAAAPwAAAGX///8A////AAAAAEgAAADtAAAAvwAAAL0AAADGAAAA7wAAAO8AAADGAAAAvQAAAL8AAADtAAAASP///wD///8A////AP///wD///8AAAAAO////wD///8A////AAAAAIcAAACH////AP///wD///8AAAAAO////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD4HwAA7/cAAN/7AAD//wAAoYUAAJ55AACf+QAAh+EAAAAAAADAAwAA4AcAAP5/AAD//wAA//8AAA=="); margin-bottom: -5px; } - div.pragma { - display: none; - } - span.pragmabegin { - cursor: pointer; - } - span.pragmaend { - cursor: pointer; - } +div.pragma { + display: none; +} +span.pragmabegin { + cursor: pointer; +} +span.pragmaend { + cursor: pointer; +} div.search_results { background-color: antiquewhite; @@ -1284,6 +1346,11 @@ div.search_results { padding: 1em; border: 1px solid #4d4d4d; } + +div#global-links ul { + margin-left: 0; + list-style-type: none; +} diff --git a/doc/advopt.txt b/doc/advopt.txt index 60fd081b8a..ab10d65ba7 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -79,6 +79,7 @@ Advanced options: symbol matching is fuzzy so that --dynlibOverride:lua matches dynlib: "liblua.so.3" + --dynlibOverrideAll makes the dynlib pragma have no effect --listCmd list the commands used to execute external programs --parallelBuild:0|1|... perform a parallel build value = number of processors (0 for auto-detect) diff --git a/doc/manual/special_ops.txt b/doc/manual/special_ops.txt index 1c7136bec7..93977f81b8 100644 --- a/doc/manual/special_ops.txt +++ b/doc/manual/special_ops.txt @@ -17,8 +17,8 @@ or dynamic file formats such as JSON or XML. When Nim encounters an expression that cannot be resolved by the standard overload resolution rules, the current scope will be searched for a dot operator that can be matched against a re-written form of -the expression, where the unknown field or proc name is converted to -an additional static string parameter: +the expression, where the unknown field or proc name is passed to +an ``untyped`` parameter: .. code-block:: nim a.b # becomes `.`(a, "b") @@ -28,7 +28,7 @@ The matched dot operators can be symbols of any callable kind (procs, templates and macros), depending on the desired effect: .. code-block:: nim - proc `.` (js: PJsonNode, field: string): JSON = js[field] + template `.` (js: PJsonNode, field: untyped): JSON = js[astToStr(field)] var js = parseJson("{ x: 1, y: 2}") echo js.x # outputs 1 diff --git a/doc/tut1.rst b/doc/tut1.rst index 6731efde97..9e6f1ab3c2 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -30,6 +30,7 @@ The first program We start the tour with a modified "hello world" program: .. code-block:: Nim + :test: "nim c $1" # This is a comment echo "What's your name? " var name: string = readLine(stdin) @@ -72,6 +73,7 @@ you can leave out the type in the declaration (this is called `local type inference`:idx:). So this will work too: .. code-block:: Nim + :test: "nim c $1" var name = readLine(stdin) Note that this is basically the only form of type inference that exists in @@ -116,6 +118,7 @@ Comments start anywhere outside a string or character literal with the hash character ``#``. Documentation comments start with ``##``: .. code-block:: nim + :test: "nim c $1" # A comment. var myVariable: int ## a documentation comment @@ -129,6 +132,7 @@ Multiline comments are started with ``#[`` and terminated with ``]#``. Multilin comments can also be nested. .. code-block:: nim + :test: "nim c $1" #[ You can have any Nim code text commented out inside this with no indentation restrictions. @@ -142,6 +146,7 @@ You can also use the `discard statement <#procedures-discard-statement>`_ togeth literals* to create block comments: .. code-block:: nim + :test: "nim c $1" discard """ You can have any Nim code text commented out inside this with no indentation restrictions. yes("May I ask a pointless question?") """ @@ -169,6 +174,7 @@ Indentation can be used after the ``var`` keyword to list a whole section of variables: .. code-block:: + :test: "nim c $1" var x, y: int # a comment can occur here too @@ -186,10 +192,11 @@ to a storage location: x = "xyz" # assigns a new value to `x` ``=`` is the *assignment operator*. The assignment operator can be -overloaded. You can declare multiple variables with a single assignment +overloaded. You can declare multiple variables with a single assignment statement and all the variables will have the same value: .. code-block:: + :test: "nim c $1" var x, y = 3 # assigns 3 to the variables `x` and `y` echo "x ", x # outputs "x 3" echo "y ", y # outputs "y 3" @@ -212,12 +219,14 @@ cannot change. The compiler must be able to evaluate the expression in a constant declaration at compile time: .. code-block:: nim + :test: "nim c $1" const x = "abc" # the constant x contains the string "abc" Indentation can be used after the ``const`` keyword to list a whole section of constants: .. code-block:: + :test: "nim c $1" const x = 1 # a comment can occur here too @@ -243,6 +252,7 @@ and put it into a data section": const input = readLine(stdin) # Error: constant expression expected .. code-block:: + :test: "nim c $1" let input = readLine(stdin) # works @@ -260,6 +270,7 @@ If statement The if statement is one way to branch the control flow: .. code-block:: nim + :test: "nim c $1" let name = readLine(stdin) if name == "": echo "Poor soul, you lost your name?" @@ -281,6 +292,7 @@ Another way to branch is provided by the case statement. A case statement is a multi-branch: .. code-block:: nim + :test: "nim c $1" let name = readLine(stdin) case name of "": @@ -338,6 +350,7 @@ While statement The while statement is a simple looping construct: .. code-block:: nim + :test: "nim c $1" echo "What's your name? " var name = readLine(stdin) @@ -358,6 +371,7 @@ provides. The example uses the built-in `countup `_ iterator: .. code-block:: nim + :test: "nim c $1" echo "Counting to ten: " for i in countup(1, 10): echo i @@ -409,6 +423,7 @@ Other useful iterators for collections (like arrays and sequences) are * ``pairs`` and ``mpairs`` which provides the element and an index number (immutable and mutable respectively) .. code-block:: nim + :test: "nim c $1" for index, item in ["a","b"].pairs: echo item, " at index ", index # => a at index 0 @@ -421,6 +436,8 @@ new scope. This means that in the following example, ``x`` is not accessible outside the loop: .. code-block:: nim + :test: "nim c $1" + :status: 1 while false: var x = "hi" echo x # does not work @@ -430,6 +447,8 @@ are only visible within the block they have been declared. The ``block`` statement can be used to open a new block explicitly: .. code-block:: nim + :test: "nim c $1" + :status: 1 block myblock: var x = "hi" echo x # does not work either @@ -444,6 +463,7 @@ can leave a ``while``, ``for``, or a ``block`` statement. It leaves the innermost construct, unless a label of a block is given: .. code-block:: nim + :test: "nim c $1" block myblock: echo "entering block" while true: @@ -465,6 +485,7 @@ Like in many other programming languages, a ``continue`` statement starts the next iteration immediately: .. code-block:: nim + :test: "nim c $1" while true: let x = readLine(stdin) if x == "": continue @@ -477,6 +498,7 @@ When statement Example: .. code-block:: nim + :test: "nim c $1" when system.hostOS == "windows": echo "running on Windows!" @@ -549,6 +571,7 @@ an expression is allowed: .. code-block:: nim # computes fac(4) at compile time: + :test: "nim c $1" const fac4 = (var x = 1; for i in 1..4: x *= i; x) @@ -561,6 +584,7 @@ is needed. (Some languages call them *methods* or *functions*.) In Nim new procedures are defined with the ``proc`` keyword: .. code-block:: nim + :test: "nim c $1" proc yes(question: string): bool = echo question, " (y/n)" while true: @@ -597,6 +621,7 @@ automatically at the end of a procedure if there is no ``return`` statement at the exit. .. code-block:: nim + :test: "nim c $1" proc sumTillNegative(x: varargs[int]): int = for i in x: if i < 0: @@ -624,6 +649,7 @@ to be declared with ``var`` in the procedure body. Shadowing the parameter name is possible, and actually an idiom: .. code-block:: nim + :test: "nim c $1" proc printSeq(s: seq, nprinted: int = -1) = var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) for i in 0 .. `_ returns the lowest valid index for the array `a` and `high(a) `_ the highest valid index. .. code-block:: nim + :test: "nim c $1" type Direction = enum north, east, south, west @@ -1228,6 +1270,7 @@ It is quite common to have arrays start at zero, so there's a shortcut syntax to specify a range from zero to the specified index minus one: .. code-block:: nim + :test: "nim c $1" type IntArray = array[0..5, int] # an array that is indexed with 0..5 QuickArray = array[6, int] # an array that is indexed with 0..5 @@ -1260,6 +1303,7 @@ A sequence may be passed to an openarray parameter. Example: .. code-block:: nim + :test: "nim c $1" var x: seq[int] # a reference to a sequence of integers @@ -1282,6 +1326,7 @@ value. Here the ``for`` statement is looping over the results from the `_ module. Examples: .. code-block:: nim + :test: "nim c $1" for value in @[3, 4, 5]: echo value # --> 3 @@ -1308,6 +1353,7 @@ with a compatible base type can be passed to an openarray parameter, the index 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' capitals: array[3, string] # array of strings with a fixed size @@ -1337,6 +1383,7 @@ arguments to a procedure. The compiler converts the list of arguments to an array automatically: .. code-block:: nim + :test: "nim c $1" proc myWriteln(f: File, a: varargs[string]) = for s in items(a): write(f, s) @@ -1351,6 +1398,7 @@ last parameter in the procedure header. It is also possible to perform type conversions in this context: .. code-block:: nim + :test: "nim c $1" proc myWriteln(f: File, a: varargs[string, `$`]) = for s in items(a): write(f, s) @@ -1374,6 +1422,7 @@ context. A slice is just an object of type Slice which contains two bounds, define operators which accept Slice objects to define ranges. .. code-block:: nim + :test: "nim c $1" var a = "Nim is a progamming language" @@ -1388,27 +1437,31 @@ slice's bounds can hold any value supported by their type, but it is the proc using the slice object which defines what values are accepted. - To understand some of the different ways of specifying the indices of strings, arrays, sequences, etc., - it must be remembered that Nim uses zero-based indices. +To understand some of the different ways of specifying the indices of +strings, arrays, sequences, etc., it must be remembered that Nim uses +zero-based indices. - So the string ``b`` is of length 19, and two different ways of specifying the indices are +So the string ``b`` is of length 19, and two different ways of specifying the +indices are - .. code-block:: nim +.. code-block:: nim "Slices are useless." | | | 0 11 17 using indices ^19 ^8 ^2 using ^ syntax - where ``b[0..^1]`` is equivalent to ``b[0..b.len-1]`` and ``b[0..`_. Distinct type ------------- -A Distinct type allows for the creation of new type that "does not imply a subtype relationship between it and its base type". +A Distinct type allows for the creation of new type that "does not imply a +subtype relationship between it and its base type". You must **explicitly** define all behaviour for the distinct type. -To help with this, both the distinct type and its base type can cast from one type to the other. +To help with this, both the distinct type and its base type can cast from one +type to the other. Examples are provided in the `manual `_. Modules @@ -1592,39 +1651,6 @@ Each module has a special magic constant ``isMainModule`` that is true if the module is compiled as the main file. This is very useful to embed tests within the module as shown by the above example. -Modules that depend on each other are possible, but strongly discouraged, -because then one module cannot be reused without the other. - -The algorithm for compiling modules is: - -- Compile the whole module as usual, following import statements recursively. -- If there is a cycle only import the already parsed symbols (that are - exported); if an unknown identifier occurs then abort. - -This is best illustrated by an example: - -.. code-block:: nim - # Module A - type - T1* = int # Module A exports the type ``T1`` - import B # the compiler starts parsing B - - proc main() = - var i = p(3) # works because B has been parsed completely here - - main() - -.. code-block:: nim - # Module B - import A # A is not parsed here! Only the already known symbols - # of A are imported. - - proc p*(x: A.T1): A.T1 = - # this works because the compiler has already - # added T1 to A's interface symbol table - result = x + 1 - - A symbol of a module *can* be *qualified* with the ``module.symbol`` syntax. And if a symbol is ambiguous, it *must* be qualified. A symbol is ambiguous if it is defined in two (or more) different modules and both modules are diff --git a/doc/tut2.rst b/doc/tut2.rst index 0636c4ed6a..91cb528341 100644 --- a/doc/tut2.rst +++ b/doc/tut2.rst @@ -55,6 +55,7 @@ Objects have access to their type at runtime. There is an ``of`` operator that can be used to check the object's type: .. code-block:: nim + :test: "nim c $1" type Person = ref object of RootObj name*: string # the * means that `name` is accessible from other modules @@ -103,6 +104,7 @@ would require arbitrary symbol lookahead which slows down compilation.) Example: .. code-block:: nim + :test: "nim c $1" type Node = ref object # a reference to an object with the following field: le, ri: Node # left and right subtrees @@ -144,6 +146,7 @@ variant types are needed. An example: .. code-block:: nim + :test: "nim c $1" # This is an example how an abstract syntax tree could be modelled in Nim type @@ -201,9 +204,11 @@ This method call syntax is not restricted to objects, it can be used for any type: .. code-block:: nim + :test: "nim c $1" + import strutils echo "abc".len # is the same as echo len("abc") - echo "abc".toUpper() + echo "abc".toUpperAscii() echo({'a', 'b', 'c'}.card) stdout.writeLine("Hallo") # the same as writeLine(stdout, "Hallo") @@ -213,6 +218,7 @@ postfix notation.) So "pure object oriented" code is easy to write: .. code-block:: nim + :test: "nim c $1" import strutils, sequtils stdout.writeLine("Give a list of numbers (separated by spaces): ") @@ -228,6 +234,7 @@ the same. But setting a value is different; for this a special setter syntax is needed: .. code-block:: nim + :test: "nim c $1" type Socket* = ref object of RootObj @@ -252,6 +259,7 @@ The ``[]`` array access operator can be overloaded to provide `array properties`:idx:\ : .. code-block:: nim + :test: "nim c $1" type Vector* = object x, y, z: float @@ -283,23 +291,24 @@ Procedures always use static dispatch. For dynamic dispatch replace the ``proc`` keyword by ``method``: .. code-block:: nim + :test: "nim c $1" type - PExpr = ref object of RootObj ## abstract base class for an expression - PLiteral = ref object of PExpr + Expression = ref object of RootObj ## abstract base class for an expression + Literal = ref object of Expression x: int - PPlusExpr = ref object of PExpr - a, b: PExpr + PlusExpr = ref object of Expression + a, b: Expression # watch out: 'eval' relies on dynamic binding - method eval(e: PExpr): int = + method eval(e: Expression): int = # override this base method quit "to override!" - method eval(e: PLiteral): int = e.x - method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b) + method eval(e: Literal): int = e.x + method eval(e: PlusExpr): int = eval(e.a) + eval(e.b) - proc newLit(x: int): PLiteral = PLiteral(x: x) - proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(a: a, b: b) + proc newLit(x: int): Literal = Literal(x: x) + proc newPlus(a, b: Expression): PlusExpr = PlusExpr(a: a, b: b) echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) @@ -311,6 +320,7 @@ In a multi-method all parameters that have an object type are used for the dispatching: .. code-block:: nim + :test: "nim c $1" type Thing = ref object of RootObj @@ -365,6 +375,7 @@ Raise statement Raising an exception is done with the ``raise`` statement: .. code-block:: nim + :test: "nim c $1" var e: ref OSError new(e) @@ -385,6 +396,9 @@ Try statement The ``try`` statement handles exceptions: .. code-block:: nim + :test: "nim c $1" + from strutils import parseInt + # read the first two lines of a text file that should contain numbers # and tries to add them var @@ -479,6 +493,7 @@ with `type parameters`:idx:. They are most useful for efficient type safe containers: .. code-block:: nim + :test: "nim c $1" type BinaryTree*[T] = ref object # BinaryTree is a generic type with # generic param ``T`` @@ -573,6 +588,7 @@ Templates are especially useful for lazy evaluation purposes. Consider a simple proc for logging: .. code-block:: nim + :test: "nim c $1" const debug = true @@ -590,6 +606,7 @@ evaluation for procedures is *eager*). Turning the ``log`` proc into a template solves this problem: .. code-block:: nim + :test: "nim c $1" const debug = true @@ -611,6 +628,7 @@ If the template has no explicit return type, To pass a block of statements to a template, use 'untyped' for the last parameter: .. code-block:: nim + :test: "nim c $1" template withFile(f: untyped, filename: string, mode: FileMode, body: untyped): typed = @@ -665,6 +683,7 @@ The following example implements a powerful ``debug`` command that accepts a variable number of arguments: .. code-block:: nim + :test: "nim c $1" # to work with Nim syntax trees, we need an API that is defined in the # ``macros`` module: import macros @@ -744,6 +763,7 @@ dynamic code into something that compiles statically. For the exercise we will use the following snippet of code as the starting point: .. code-block:: nim + :test: "nim c $1" import strutils, tables @@ -863,9 +883,9 @@ variables with ``cfg``. In essence, what the compiler is doing is replacing the line calling the macro with the following snippet of code: .. code-block:: nim - const cfgversion= "1.1" - const cfglicenseOwner= "Hyori Lee" - const cfglicenseKey= "M1Tl3PjBWO2CC48m" + const cfgversion = "1.1" + const cfglicenseOwner = "Hyori Lee" + const cfglicenseKey = "M1Tl3PjBWO2CC48m" You can verify this yourself adding the line ``echo source`` somewhere at the end of the macro and compiling the program. Another difference is that instead @@ -891,12 +911,13 @@ an expression macro. Since we know that we want to generate a bunch of see what the compiler *expects* from us: .. code-block:: nim + :test: "nim c $1" import macros dumpTree: const cfgversion: string = "1.1" - const cfglicenseOwner= "Hyori Lee" - const cfglicenseKey= "M1Tl3PjBWO2CC48m" + const cfglicenseOwner = "Hyori Lee" + const cfglicenseKey = "M1Tl3PjBWO2CC48m" During compilation of the source code we should see the following lines in the output (again, since this is a macro, compilation is enough, you don't have to @@ -996,6 +1017,7 @@ Lifting Procs +++++++++++++ .. code-block:: nim + :test: "nim c $1" import math template liftScalarProc(fname) = diff --git a/lib/core/macros.nim b/lib/core/macros.nim index ebc9f77142..ee6c1a09ff 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1226,3 +1226,8 @@ when not defined(booting): macro payload: untyped {.gensym.} = result = parseStmt(e) payload() + +macro unpackVarargs*(callee: untyped; args: varargs[untyped]): untyped = + result = newCall(callee) + for i in 0 ..< args.len: + result.add args[i] diff --git a/lib/impure/re.nim b/lib/impure/re.nim index 24fc83366d..c7f8f336bd 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -49,9 +49,6 @@ type RegexError* = object of ValueError ## is raised if the pattern is no valid regular expression. -{.deprecated: [TRegexFlag: RegexFlag, TRegexDesc: RegexDesc, TRegex: Regex, - EInvalidRegEx: RegexError].} - proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} = var e: ref RegexError new(e) @@ -470,8 +467,8 @@ proc replacef*(s: string, sub: Regex, by: string): string = prev = match.last + 1 add(result, substr(s, prev)) -proc parallelReplace*(s: string, subs: openArray[ - tuple[pattern: Regex, repl: string]]): string = +proc multiReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string = ## Returns a modified copy of ``s`` with the substitutions in ``subs`` ## applied in parallel. result = "" @@ -490,13 +487,20 @@ proc parallelReplace*(s: string, subs: openArray[ # copy the rest: add(result, substr(s, i)) +proc parallelReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string {.deprecated.} = + ## Returns a modified copy of ``s`` with the substitutions in ``subs`` + ## applied in parallel. + ## **Deprecated since version 0.18.0**: Use ``multiReplace`` instead. + result = multiReplace(s, subs) + proc transformFile*(infile, outfile: string, subs: openArray[tuple[pattern: Regex, repl: string]]) = ## reads in the file ``infile``, performs a parallel replacement (calls ## ``parallelReplace``) and writes back to ``outfile``. Raises ``IOError`` if an ## error occurs. This is supposed to be used for quick scripting. var x = readFile(infile).string - writeFile(outfile, x.parallelReplace(subs)) + writeFile(outfile, x.multiReplace(subs)) iterator split*(s: string, sep: Regex): string = ## Splits the string ``s`` into substrings. @@ -579,12 +583,12 @@ const ## common regular expressions ## describes an URL when isMainModule: - doAssert match("(a b c)", re"\( .* \)") + doAssert match("(a b c)", rex"\( .* \)") doAssert match("WHiLe", re("while", {reIgnoreCase})) doAssert "0158787".match(re"\d+") doAssert "ABC 0232".match(re"\w+\s+\d+") - doAssert "ABC".match(re"\d+ | \w+") + doAssert "ABC".match(rex"\d+ | \w+") {.push warnings:off.} doAssert matchLen("key", re(reIdentifier)) == 3 diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index 13eb1e759e..f34efe9a29 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -177,7 +177,7 @@ proc `==`*(x, y: JsRoot): bool {. importcpp: "(# === #)" .} ## and not strings or numbers, this is a *comparison of references*. {. experimental .} -macro `.`*(obj: JsObject, field: static[cstring]): JsObject = +macro `.`*(obj: JsObject, field: untyped): JsObject = ## Experimental dot accessor (get) for type JsObject. ## Returns the value of a property of name `field` from a JsObject `x`. ## @@ -196,14 +196,14 @@ macro `.`*(obj: JsObject, field: static[cstring]): JsObject = helper(`obj`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) let importString = "#." & mangledNames[$field] result = quote do: proc helper(o: JsObject): JsObject {. importcpp: `importString`, gensym .} helper(`obj`) -macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = +macro `.=`*(obj: JsObject, field, value: untyped): untyped = ## Experimental dot accessor (set) for type JsObject. ## Sets the value of a property of name `field` in a JsObject `x` to `value`. if validJsName($field): @@ -214,7 +214,7 @@ macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = helper(`obj`, `value`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) let importString = "#." & mangledNames[$field] & " = #" result = quote do: proc helper(o: JsObject, v: auto) @@ -222,7 +222,7 @@ macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = helper(`obj`, `value`) macro `.()`*(obj: JsObject, - field: static[cstring], + field: untyped, args: varargs[JsObject, jsFromAst]): JsObject = ## Experimental "method call" operator for type JsObject. ## Takes the name of a method of the JavaScript object (`field`) and calls @@ -245,7 +245,7 @@ macro `.()`*(obj: JsObject, importString = "#." & $field & "(@)" else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] & "(@)" result = quote: proc helper(o: JsObject): JsObject @@ -257,7 +257,7 @@ macro `.()`*(obj: JsObject, result[1].add args[idx].copyNimTree macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], - field: static[cstring]): V = + field: untyped): V = ## Experimental dot accessor (get) for type JsAssoc. ## Returns the value of a property of name `field` from a JsObject `x`. var importString: string @@ -265,7 +265,7 @@ macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], importString = "#." & $field else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] result = quote do: proc helper(o: type(`obj`)): `obj`.V @@ -273,7 +273,7 @@ macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], helper(`obj`) macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], - field: static[cstring], + field: untyped, value: V): untyped = ## Experimental dot accessor (set) for type JsAssoc. ## Sets the value of a property of name `field` in a JsObject `x` to `value`. @@ -282,7 +282,7 @@ macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], importString = "#." & $field & " = #" else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] & " = #" result = quote do: proc helper(o: type(`obj`), v: `obj`.V) @@ -290,7 +290,7 @@ macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], helper(`obj`, `value`) macro `.()`*[K: string | cstring, V: proc](obj: JsAssoc[K, V], - field: static[cstring], + field: untyped, args: varargs[untyped]): auto = ## Experimental "method call" operator for type JsAssoc. ## Takes the name of a method of the JavaScript object (`field`) and calls diff --git a/lib/nimbase.h b/lib/nimbase.h index 76192713b9..ac2cc097c1 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -70,7 +70,7 @@ __clang__ #if defined(_MSC_VER) # pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309) # pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706) -# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090) +# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090 4297) #endif /* ------------------------------------------------------------------------- */ diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 70369b001d..2a58854a68 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -31,14 +31,12 @@ type state: TokenClass SourceLanguage* = enum - langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava, + langNone, langNim, langCpp, langCsharp, langC, langJava, langYaml -{.deprecated: [TSourceLanguage: SourceLanguage, TTokenClass: TokenClass, - TGeneralTokenizer: GeneralTokenizer].} const sourceLanguageToStr*: array[SourceLanguage, string] = ["none", - "Nim", "Nimrod", "C++", "C#", "C", "Java", "Yaml"] + "Nim", "C++", "C#", "C", "Java", "Yaml"] tokenClassToStr*: array[TokenClass, string] = ["Eof", "None", "Whitespace", "DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber", "Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit", @@ -398,7 +396,6 @@ type TokenizerFlag = enum hasPreprocessor, hasNestedComments TokenizerFlags = set[TokenizerFlag] -{.deprecated: [TTokenizerFlag: TokenizerFlag, TTokenizerFlags: TokenizerFlags].} proc clikeNextToken(g: var GeneralTokenizer, keywords: openArray[string], flags: TokenizerFlags) = @@ -888,7 +885,7 @@ proc yamlNextToken(g: var GeneralTokenizer) = proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) = case lang of langNone: assert false - of langNim, langNimrod: nimNextToken(g) + of langNim: nimNextToken(g) of langCpp: cppNextToken(g) of langCsharp: csharpNextToken(g) of langC: cNextToken(g) diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 53699166fb..223fc836a3 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -45,8 +45,6 @@ type MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind, arg: string) {.nimcall.} ## what to do in case of an error FindFileHandler* = proc (filename: string): string {.nimcall.} -{.deprecated: [TRstParseOptions: RstParseOptions, TRstParseOption: RstParseOption, - TMsgKind: MsgKind].} const messages: array[MsgKind, string] = [ @@ -127,8 +125,6 @@ type bufpos*: int line*, col*, baseIndent*: int skipPounds*: bool -{.deprecated: [TTokType: TokType, TToken: Token, TTokenSeq: TokenSeq, - TLexer: Lexer].} proc getThing(L: var Lexer, tok: var Token, s: set[char]) = tok.kind = tkWord @@ -288,10 +284,6 @@ type hasToc*: bool EParseError* = object of ValueError -{.deprecated: [TLevelMap: LevelMap, TSubstitution: Substitution, - TSharedState: SharedState, TRstParser: RstParser, - TMsgHandler: MsgHandler, TFindFileHandler: FindFileHandler, - TMsgClass: MsgClass].} proc whichMsgClass*(k: MsgKind): MsgClass = ## returns which message class `k` belongs to. @@ -341,11 +333,6 @@ proc rstMessage(p: RstParser, msgKind: MsgKind) = p.col + p.tok[p.idx].col, msgKind, p.tok[p.idx].symbol) -when false: - proc corrupt(p: RstParser) = - assert p.indentStack[0] == 0 - for i in 1 .. high(p.indentStack): assert p.indentStack[i] < 1_000 - proc currInd(p: RstParser): int = result = p.indentStack[high(p.indentStack)] diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 1272affdc8..e6c95b59ef 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -46,7 +46,7 @@ type target*: OutputTarget config*: StringTableRef splitAfter*: int # split too long entries in the TOC - listingCounter: int + listingCounter*: int tocPart*: seq[TocEntry] hasToc*: bool theIndex: string # Contents of the index file to be dumped at the end. @@ -61,6 +61,9 @@ type seenIndexTerms: Table[string, int] ## \ ## Keeps count of same text index terms to generate different identifiers ## for hyperlinks. See renderIndexTerm proc for details. + id*: int ## A counter useful for generating IDs. + onTestSnippet*: proc (d: var RstGenerator; filename, cmd: string; status: int; + content: string) PDoc = var RstGenerator ## Alias to type less. @@ -69,8 +72,9 @@ type startLine: int ## The starting line of the code block, by default 1. langStr: string ## Input string used to specify the language. lang: SourceLanguage ## Type of highlighting, by default none. -{.deprecated: [TRstGenerator: RstGenerator, TTocEntry: TocEntry, - TOutputTarget: OutputTarget, TMetaEnum: MetaEnum].} + filename: string + testCmd: string + status: int proc init(p: var CodeBlockParams) = ## Default initialisation of CodeBlockParams to sane values. @@ -133,6 +137,7 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget, g.options = options g.findFile = findFile g.currentSection = "" + g.id = 0 let fileParts = filename.splitFile if fileParts.ext == ".nim": g.currentSection = "Module " & fileParts.name @@ -368,7 +373,6 @@ type ## ## The value indexed by this IndexEntry is a sequence with the real index ## entries found in the ``.idx`` file. -{.deprecated: [TIndexEntry: IndexEntry, TIndexedDocs: IndexedDocs].} proc cmp(a, b: IndexEntry): int = ## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`. @@ -823,13 +827,20 @@ proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) = var number: int if parseInt(n.getFieldValue, number) > 0: params.startLine = number - of "file": + of "file", "filename": # The ``file`` option is a Nim extension to the official spec, it acts # like it would for other directives like ``raw`` or ``cvs-table``. This # field is dealt with in ``rst.nim`` which replaces the existing block with # the referenced file, so we only need to ignore it here to avoid incorrect # warning messages. - discard + params.filename = n.getFieldValue.strip + of "test": + params.testCmd = n.getFieldValue.strip + if params.testCmd.len == 0: params.testCmd = "nim c -r $1" + of "status": + var status: int + if parseInt(n.getFieldValue, status) > 0: + params.status = status of "default-language": params.langStr = n.getFieldValue.strip params.lang = params.langStr.getSourceLanguage @@ -901,6 +912,9 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = var m = n.sons[2].sons[0] assert m.kind == rnLeaf + if params.testCmd.len > 0 and d.onTestSnippet != nil: + d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text) + let (blockStart, blockEnd) = buildLinesHTMLTable(d, params, m.text) dispA(d.target, result, blockStart, "\\begin{rstpre}\n", []) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 65004cbe0e..d48274bb99 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -59,9 +59,10 @@ export asyncfutures, asyncstreams ## ## .. code-block::nim ## var future = socket.recv(100) -## future.callback = +## future.addCallback( ## proc () = ## echo(future.read) +## ) ## ## All asynchronous functions returning a ``Future`` will not block. They ## will not however return immediately. An asynchronous function will have diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 8941dca6ed..82bc53aaff 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -334,7 +334,7 @@ proc all*[T](futs: varargs[Future[T]]): auto = let totalFutures = len(futs) for fut in futs: - fut.callback = proc(f: Future[T]) = + fut.addCallback proc (f: Future[T]) = inc(completedFutures) if not retFuture.finished: if f.failed: @@ -356,7 +356,7 @@ proc all*[T](futs: varargs[Future[T]]): auto = for i, fut in futs: proc setCallback(i: int) = - fut.callback = proc(f: Future[T]) = + fut.addCallback proc (f: Future[T]) = inc(completedFutures) if not retFuture.finished: if f.failed: diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 433931c9de..ba16156515 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -275,10 +275,7 @@ proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string lineFut.mget() = newStringOfCap(80) while not client.isClosed: - try: - await processRequest(server, request, client, address, lineFut, callback) - except: - asyncCheck request.mget().respondError(Http500) + await processRequest(server, request, client, address, lineFut, callback) proc serve*(server: AsyncHttpServer, port: Port, callback: proc (request: Request): Future[void] {.closure,gcsafe.}, diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim index d1207603d2..3f213c5ea3 100644 --- a/lib/pure/bitops.nim +++ b/lib/pure/bitops.nim @@ -181,7 +181,7 @@ elif useICC_builtins: proc countSetBits*(x: SomeInteger): int {.inline, nosideeffect.} = - ## Counts the set bits in integer. (also called Hamming weight.) + ## Counts the set bits in integer. (also called `Hamming weight`:idx:.) # TODO: figure out if ICC support _popcnt32/_popcnt64 on platform without POPCNT. # like GCC and MSVC when nimvm: diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index fcf2cf99f5..5de6aa4870 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -29,21 +29,8 @@ ## writeLine(stdout, "your password: " & myData["password"]) ## writeLine(stdout, "") -import strutils, os, strtabs, cookies - -proc encodeUrl*(s: string): string = - ## Encodes a value to be HTTP safe: This means that characters in the set - ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result, - ## a space is converted to ``'+'`` and every other character is encoded as - ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. - result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars - for i in 0..s.len-1: - case s[i] - of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i]) - of ' ': add(result, '+') - else: - add(result, '%') - add(result, toHex(ord(s[i]), 2)) +import strutils, os, strtabs, cookies, uri +export uri.encodeUrl, uri.decodeUrl proc handleHexChar(c: char, x: var int) {.inline.} = case c @@ -52,28 +39,6 @@ proc handleHexChar(c: char, x: var int) {.inline.} = of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) else: assert(false) -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 - ## value) is converted to the character with ordinal number ``xx``, and - ## and every other character is carried over. - result = newString(s.len) - var i = 0 - var j = 0 - while i < s.len: - case s[i] - of '%': - var x = 0 - handleHexChar(s[i+1], x) - handleHexChar(s[i+2], x) - inc(i, 2) - result[j] = chr(x) - of '+': result[j] = ' ' - else: result[j] = s[i] - inc(i) - inc(j) - setLen(result, j) - proc addXmlChar(dest: var string, c: char) {.inline.} = case c of '&': add(dest, "&") @@ -390,8 +355,3 @@ proc existsCookie*(name: string): bool = ## Checks if a cookie of `name` exists. if gcookies == nil: gcookies = parseCookies(getHttpCookie()) result = hasKey(gcookies, name) - -when isMainModule: - const test1 = "abc\L+def xyz" - assert encodeUrl(test1) == "abc%0A%2Bdef+xyz" - assert decodeUrl(encodeUrl(test1)) == test1 diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index d0f5c78e07..06e96ca36f 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -66,7 +66,7 @@ proc cycle*[T](s: openArray[T], n: Natural): seq[T] = ## ## Example: ## - ## .. code-block: + ## .. code-block:: ## ## let ## s = @[1, 2, 3] @@ -84,7 +84,7 @@ proc repeat*[T](x: T, n: Natural): seq[T] = ## ## Example: ## - ## .. code-block: + ## .. code-block:: ## ## let ## total = repeat(5, 3) diff --git a/lib/pure/htmlgen.nim b/lib/pure/htmlgen.nim index ad199a2156..c0934a45b7 100644 --- a/lib/pure/htmlgen.nim +++ b/lib/pure/htmlgen.nim @@ -59,8 +59,8 @@ proc xmlCheckedTag*(e: NimNode, tag: string, optAttr = "", reqAttr = "", # copy the attributes; when iterating over them these lists # will be modified, so that each attribute is only given one value - var req = split(reqAttr) - var opt = split(optAttr) + var req = splitWhitespace(reqAttr) + var opt = splitWhitespace(optAttr) result = newNimNode(nnkBracket, e) result.add(newStrLitNode("<")) result.add(newStrLitNode(tag)) diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim index 2165c5cb2e..7ed2503070 100644 --- a/lib/pure/ioselects/ioselectors_select.nim +++ b/lib/pure/ioselects/ioselectors_select.nim @@ -279,8 +279,9 @@ proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, inc(s.count) pkey.events = events -proc unregister*[T](s: Selector[T], fd: int | SocketHandle) = +proc unregister*[T](s: Selector[T], fd: SocketHandle|int) = s.withSelectLock(): + let fd = fd.SocketHandle var pkey = s.getKey(fd) if Event.Read in pkey.events: IOFD_CLR(fd, addr s.rSet) @@ -438,18 +439,19 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, body2: untyped) = mixin withSelectLock s.withSelectLock(): - var value: ptr T - let fdi = int(fd) - var i = 0 - while i < FD_SETSIZE: - if s.fds[i].ident == fdi: - value = addr(s.fds[i].data) - break - inc(i) - if i != FD_SETSIZE: - body1 - else: - body2 + block: + var value: ptr T + let fdi = int(fd) + var i = 0 + while i < FD_SETSIZE: + if s.fds[i].ident == fdi: + value = addr(s.fds[i].data) + break + inc(i) + if i != FD_SETSIZE: + body1 + else: + body2 proc getFd*[T](s: Selector[T]): int = diff --git a/lib/pure/json.nim b/lib/pure/json.nim index cea485c439..b5b84863ac 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -1346,6 +1346,16 @@ proc createJsonIndexer(jsonNode: NimNode, indexNode ) +proc transformJsonIndexer(jsonNode: NimNode): NimNode = + case jsonNode.kind + of nnkBracketExpr: + result = newNimNode(nnkCurlyExpr) + else: + result = jsonNode.copy() + + for child in jsonNode: + result.add(transformJsonIndexer(child)) + template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind], ast: string) = if node.kind notin kinds: @@ -1524,6 +1534,35 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = doAssert result.len > 0 +proc processFields(obj: NimNode, + jsonNode: NimNode): seq[NimNode] {.compileTime.} = + ## Process all the fields of an ``ObjectTy`` and any of its + ## parent type's fields (via inheritance). + result = @[] + case obj.kind + of nnkObjectTy: + expectKind(obj[2], nnkRecList) + for field in obj[2]: + let nodes = processObjField(field, jsonNode) + result.add(nodes) + + # process parent type fields + case obj[1].kind + of nnkBracketExpr: + assert $obj[1][0] == "ref" + result.add(processFields(getType(obj[1][1]), jsonNode)) + of nnkSym: + result.add(processFields(getType(obj[1]), jsonNode)) + else: + discard + of nnkTupleTy: + for identDefs in obj: + expectKind(identDefs, nnkIdentDefs) + let nodes = processObjField(identDefs[0], jsonNode) + result.add(nodes) + else: + doAssert false, "Unable to process field type: " & $obj.kind + proc processType(typeName: NimNode, obj: NimNode, jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} = ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``. @@ -1533,20 +1572,21 @@ proc processType(typeName: NimNode, obj: NimNode, ## .. code-block::plain ## ObjectTy ## Empty - ## Empty + ## InheritanceInformation ## RecList ## Sym "events" case obj.kind - of nnkObjectTy: + of nnkObjectTy, nnkTupleTy: # Create object constructor. - result = newNimNode(nnkObjConstr) - result.add(typeName) # Name of the type to construct. + result = + if obj.kind == nnkObjectTy: newNimNode(nnkObjConstr) + else: newNimNode(nnkPar) - # Process each object field and add it as an exprColonExpr - expectKind(obj[2], nnkRecList) - for field in obj[2]: - let nodes = processObjField(field, jsonNode) - result.add(nodes) + if obj.kind == nnkObjectTy: + result.add(typeName) # Name of the type to construct. + + # Process each object/tuple field and add it as an exprColonExpr + result.add(processFields(obj, jsonNode)) # Object might be null. So we need to check for that. if isRef: @@ -1569,25 +1609,14 @@ proc processType(typeName: NimNode, obj: NimNode, `getEnumCall` ) of nnkSym: - case ($typeName).normalize - of "float": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JFloat, JInt}, astToStr(`jsonNode`)); - if `jsonNode`.kind == JFloat: `jsonNode`.fnum else: `jsonNode`.num.float - ) + let name = ($typeName).normalize + case name of "string": result = quote do: ( verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`)); if `jsonNode`.kind == JNull: nil else: `jsonNode`.str ) - of "int": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); - `jsonNode`.num.int - ) of "biggestint": result = quote do: ( @@ -1601,12 +1630,36 @@ proc processType(typeName: NimNode, obj: NimNode, `jsonNode`.bval ) else: - doAssert false, "Unable to process nnkSym " & $typeName + if name.startsWith("int") or name.startsWith("uint"): + result = quote do: + ( + verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); + `jsonNode`.num.`obj` + ) + elif name.startsWith("float"): + result = quote do: + ( + verifyJsonKind(`jsonNode`, {JInt, JFloat}, astToStr(`jsonNode`)); + if `jsonNode`.kind == JFloat: `jsonNode`.fnum.`obj` else: `jsonNode`.num.`obj` + ) + else: + doAssert false, "Unable to process nnkSym " & $typeName else: doAssert false, "Unable to process type: " & $obj.kind doAssert(not result.isNil(), "processType not initialised.") +import options +proc workaroundMacroNone[T](): Option[T] = + none(T) + +proc depth(n: NimNode, current = 0): int = + result = 1 + for child in n: + let d = 1 + child.depth(current + 1) + if d > result: + result = d + proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ## Accepts a type description, i.e. "ref Type", "seq[Type]", "Type" etc. ## @@ -1616,10 +1669,50 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = # echo("--createConsuctor-- \n", treeRepr(typeSym)) # echo() + if depth(jsonNode) > 150: + error("The `to` macro does not support ref objects with cycles.", jsonNode) + case typeSym.kind of nnkBracketExpr: var bracketName = ($typeSym[0]).normalize case bracketName + of "option": + # TODO: Would be good to verify that this is Option[T] from + # options module I suppose. + let lenientJsonNode = transformJsonIndexer(jsonNode) + + let optionGeneric = typeSym[1] + let value = createConstructor(typeSym[1], jsonNode) + let workaround = bindSym("workaroundMacroNone") # TODO: Nim Bug: This shouldn't be necessary. + + result = quote do: + ( + if `lenientJsonNode`.isNil: `workaround`[`optionGeneric`]() else: some[`optionGeneric`](`value`) + ) + of "table", "orderedtable": + let tableKeyType = typeSym[1] + if ($tableKeyType).cmpIgnoreStyle("string") != 0: + error("JSON doesn't support keys of type " & $tableKeyType) + let tableValueType = typeSym[2] + + let forLoopKey = genSym(nskForVar, "key") + let indexerNode = createJsonIndexer(jsonNode, forLoopKey) + let constructorNode = createConstructor(tableValueType, indexerNode) + + let tableInit = + if bracketName == "table": + bindSym("initTable") + else: + bindSym("initOrderedTable") + + # Create a statement expression containing a for loop. + result = quote do: + ( + var map = `tableInit`[`tableKeyType`, `tableValueType`](); + verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`)); + for `forLoopKey` in keys(`jsonNode`.fields): map[`forLoopKey`] = `constructorNode`; + map + ) of "ref": # Ref type. var typeName = $typeSym[1] @@ -1663,12 +1756,23 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = let obj = getType(typeSym) result = processType(typeSym, obj, jsonNode, false) of nnkSym: + # Handle JsonNode. + if ($typeSym).cmpIgnoreStyle("jsonnode") == 0: + return jsonNode + + # Handle all other types. let obj = getType(typeSym) if obj.kind == nnkBracketExpr: # When `Sym "Foo"` turns out to be a `ref object`. result = createConstructor(obj, jsonNode) else: result = processType(typeSym, obj, jsonNode, false) + of nnkTupleTy: + result = processType(typeSym, typeSym, jsonNode, false) + of nnkPar: + # TODO: The fact that `jsonNode` here works to give a good line number + # is weird. Specifying typeSym should work but doesn't. + error("Use a named tuple instead of: " & $toStrLit(typeSym), jsonNode) else: doAssert false, "Unable to create constructor for: " & $typeSym.kind @@ -1796,10 +1900,18 @@ macro to*(node: JsonNode, T: typedesc): untyped = expectKind(typeNode, nnkBracketExpr) doAssert(($typeNode[0]).normalize == "typedesc") - result = createConstructor(typeNode[1], node) - # TODO: Rename postProcessValue and move it (?) - result = postProcessValue(result) + # Create `temp` variable to store the result in case the user calls this + # on `parseJson` (see bug #6604). + result = newNimNode(nnkStmtListExpr) + let temp = genSym(nskLet, "temp") + result.add quote do: + let `temp` = `node` + let constructor = createConstructor(typeNode[1], temp) + # TODO: Rename postProcessValue and move it (?) + result.add(postProcessValue(constructor)) + + # echo(treeRepr(result)) # echo(toStrLit(result)) when false: diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index e2a5bed969..830820fd13 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -202,13 +202,17 @@ when not defined(js): proc countLogLines(logger: RollingFileLogger): int = result = 0 - for line in logger.file.lines(): + let fp = open(logger.baseName, fmRead) + for line in fp.lines(): result.inc() + fp.close() proc countFiles(filename: string): int = # Example: file.log.1 result = 0 - let (dir, name, ext) = splitFile(filename) + var (dir, name, ext) = splitFile(filename) + if dir == "": + dir = "." for kind, path in walkDir(dir): if kind == pcFile: let llfn = name & ext & ExtSep diff --git a/lib/pure/mimetypes.nim b/lib/pure/mimetypes.nim index 1e315afb4a..b397ef47b5 100644 --- a/lib/pure/mimetypes.nim +++ b/lib/pure/mimetypes.nim @@ -491,6 +491,8 @@ const mimes* = { "vrml": "x-world/x-vrml", "wrl": "x-world/x-vrml"} +from strutils import startsWith + proc newMimetypes*(): MimeDB = ## Creates a new Mimetypes database. The database will contain the most ## common mimetypes. @@ -498,8 +500,11 @@ proc newMimetypes*(): MimeDB = proc getMimetype*(mimedb: MimeDB, ext: string, default = "text/plain"): string = ## Gets mimetype which corresponds to ``ext``. Returns ``default`` if ``ext`` - ## could not be found. - result = mimedb.mimes.getOrDefault(ext) + ## could not be found. ``ext`` can start with an optional dot which is ignored. + if ext.startsWith("."): + result = mimedb.mimes.getOrDefault(ext.substr(1)) + else: + result = mimedb.mimes.getOrDefault(ext) if result == "": return default diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 5c162317e5..621ef992cb 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -145,7 +145,7 @@ type SOBool* = enum ## Boolean socket options. OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive, - OptOOBInline, OptReuseAddr, OptReusePort + OptOOBInline, OptReuseAddr, OptReusePort, OptNoDelay ReadLineResult* = enum ## result for readLineAsync ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone @@ -865,6 +865,11 @@ proc close*(socket: Socket) = socket.fd.close() +when defined(posix): + from posix import TCP_NODELAY +else: + from winlean import TCP_NODELAY + proc toCInt*(opt: SOBool): cint = ## Converts a ``SOBool`` into its Socket Option cint representation. case opt @@ -876,6 +881,7 @@ proc toCInt*(opt: SOBool): cint = of OptOOBInline: SO_OOBINLINE of OptReuseAddr: SO_REUSEADDR of OptReusePort: SO_REUSEPORT + of OptNoDelay: TCP_NODELAY proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {. tags: [ReadIOEffect].} = @@ -898,6 +904,12 @@ proc getPeerAddr*(socket: Socket): (string, Port) = proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {. tags: [WriteIOEffect].} = ## Sets option ``opt`` to a boolean value specified by ``value``. + ## + ## .. code-block:: Nim + ## var socket = newSocket() + ## socket.setSockOpt(OptReusePort, true) + ## socket.setSockOpt(OptNoDelay, true, level=IPPROTO_TCP.toInt) + ## var valuei = cint(if value: 1 else: 0) setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 8b26552dee..a59134007a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -630,7 +630,7 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## the process has finished. To execute a program without having a ## shell involved, use the `execProcess` proc of the `osproc` ## module. - when defined(linux): + when defined(posix): result = c_system(command) shr 8 else: result = c_system(command) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index c3bd399db7..0d638abb9b 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -602,14 +602,13 @@ proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} else: return "'" & s.replace("'", "'\"'\"'") & "'" -proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to shell. - when defined(Windows): - return quoteShellWindows(s) - elif defined(posix): - return quoteShellPosix(s) - else: - {.error:"quoteShell is not supported on your system".} +when defined(windows) or defined(posix): + proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to shell. + when defined(windows): + return quoteShellWindows(s) + else: + return quoteShellPosix(s) when isMainModule: assert quoteShellWindows("aaa") == "aaa" diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index cc4c261613..2a1ce0c58b 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -41,6 +41,8 @@ type ## Windows: Named pipes are used so that you can peek ## at the process' output streams. poDemon ## Windows: The program creates no Window. + ## Unix: Start the program as a demon. This is still + ## work in progress! ProcessObj = object of RootObj when defined(windows): @@ -167,8 +169,7 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl, ## On posix, if the process has exited because of a signal, 128 + signal ## number will be returned. - -proc peekExitCode*(p: Process): int {.tags: [].} +proc peekExitCode*(p: Process): int {.rtl, extern: "nosp$1", tags: [].} ## return -1 if the process is still running. Otherwise the process' exit code ## ## On posix, if the process has exited because of a signal, 128 + signal @@ -231,55 +232,79 @@ proc execProcesses*(cmds: openArray[string], ## executes the commands `cmds` in parallel. Creates `n` processes ## that execute in parallel. The highest return value of all processes ## is returned. Runs `beforeRunEvent` before running each command. - when false: - # poParentStreams causes problems on Posix, so we simply disable it: - var options = options - {poParentStreams} assert n > 0 if n > 1: - var q: seq[Process] - newSeq(q, n) + var i = 0 + var q = newSeq[Process](n) var m = min(n, cmds.len) - for i in 0..m-1: + + when defined(windows): + var w: WOHandleArray + var wcount = m + + while i < m: if beforeRunEvent != nil: beforeRunEvent(i) - q[i] = startProcess(cmds[i], options=options + {poEvalCommand}) - when defined(noBusyWaiting): - var r = 0 - for i in m..high(cmds): - when defined(debugExecProcesses): - var err = "" - var outp = outputStream(q[r]) - while running(q[r]) or not atEnd(outp): - err.add(outp.readLine()) - err.add("\n") - echo(err) - result = max(waitForExit(q[r]), result) - if afterRunEvent != nil: afterRunEvent(r, q[r]) - if q[r] != nil: close(q[r]) - if beforeRunEvent != nil: - beforeRunEvent(i) - q[r] = startProcess(cmds[i], options=options + {poEvalCommand}) - r = (r + 1) mod n - else: - var i = m - while i <= high(cmds): - sleep(50) - for r in 0..n-1: + q[i] = startProcess(cmds[i], options = options + {poEvalCommand}) + when defined(windows): + w[i] = q[i].fProcessHandle + inc(i) + + var ecount = len(cmds) + while ecount > 0: + when defined(windows): + # waiting for all children, get result if any child exits + var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32, + INFINITE) + if ret == WAIT_TIMEOUT: + # must not be happen + discard + elif ret == WAIT_FAILED: + raiseOSError(osLastError()) + else: + var status : cint = 1 + # waiting for all children, get result if any child exits + let res = waitpid(-1, status, 0) + if res > 0: + for r in 0..m-1: + if not isNil(q[r]) and q[r].id == res: + # we updating `exitStatus` manually, so `running()` can work. + if WIFEXITED(status) or WIFSIGNALED(status): + q[r].exitStatus = status + break + else: + let err = osLastError() + if err == OSErrorCode(ECHILD): + # some child exits, we need to check our childs exit codes + discard + elif err == OSErrorCode(EINTR): + # signal interrupted our syscall, lets repeat it + continue + else: + # all other errors are exceptions + raiseOSError(err) + + for r in 0..m-1: + if not isNil(q[r]): if not running(q[r]): - #echo(outputStream(q[r]).readLine()) - result = max(waitForExit(q[r]), result) + result = max(result, q[r].peekExitCode()) if afterRunEvent != nil: afterRunEvent(r, q[r]) - if q[r] != nil: close(q[r]) - if beforeRunEvent != nil: - beforeRunEvent(i) - q[r] = startProcess(cmds[i], options=options + {poEvalCommand}) - inc(i) - if i > high(cmds): break - for j in 0..m-1: - result = max(waitForExit(q[j]), result) - if afterRunEvent != nil: afterRunEvent(j, q[j]) - if q[j] != nil: close(q[j]) + close(q[r]) + if i < len(cmds): + if beforeRunEvent != nil: beforeRunEvent(i) + q[r] = startProcess(cmds[i], + options = options + {poEvalCommand}) + when defined(windows): + w[r] = q[r].fProcessHandle + inc(i) + else: + q[r] = nil + when defined(windows): + for c in r..MAXIMUM_WAIT_OBJECTS - 2: + w[c] = w[c + 1] + dec(wcount) + dec(ecount) else: for i in 0..high(cmds): if beforeRunEvent != nil: @@ -321,6 +346,8 @@ when not defined(useNimRtl): elif not running(p): break close(p) +template streamAccess(p) = + assert poParentStreams notin p.options, "API usage error: stream access not allowed when you use poParentStreams" when defined(Windows) and not defined(useNimRtl): # We need to implement a handle stream for Windows: @@ -581,12 +608,15 @@ when defined(Windows) and not defined(useNimRtl): return res proc inputStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.inHandle) proc outputStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.outHandle) proc errorStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.errHandle) proc execCmd(command: string): int = @@ -682,9 +712,7 @@ elif not defined(useNimRtl): sysEnv: cstringArray workingDir: cstring pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] - optionPoUsePath: bool - optionPoParentStreams: bool - optionPoStdErrToStdOut: bool + options: set[ProcessOption] {.deprecated: [TStartProcessData: StartProcessData].} const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and @@ -749,10 +777,8 @@ elif not defined(useNimRtl): data.pStdin = pStdin data.pStdout = pStdout data.pStderr = pStderr - data.optionPoParentStreams = poParentStreams in options - data.optionPoUsePath = poUsePath in options - data.optionPoStdErrToStdOut = poStdErrToStdOut in options data.workingDir = workingDir + data.options = options when useProcessAuxSpawn: var currentDir = getCurrentDir() @@ -801,19 +827,22 @@ elif not defined(useNimRtl): var mask: Sigset chck sigemptyset(mask) chck posix_spawnattr_setsigmask(attr, mask) - chck posix_spawnattr_setpgroup(attr, 0'i32) + if poDemon in data.options: + chck posix_spawnattr_setpgroup(attr, 0'i32) - chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or - POSIX_SPAWN_SETSIGMASK or - POSIX_SPAWN_SETPGROUP) + var flags = POSIX_SPAWN_USEVFORK or + POSIX_SPAWN_SETSIGMASK + if poDemon in data.options: + flags = flags or POSIX_SPAWN_SETPGROUP + chck posix_spawnattr_setflags(attr, flags) - if not data.optionPoParentStreams: + if not (poParentStreams in data.options): chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) - if data.optionPoStdErrToStdOut: + if (poStdErrToStdOut in data.options): chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) else: chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) @@ -823,7 +852,7 @@ elif not defined(useNimRtl): setCurrentDir($data.workingDir) var pid: Pid - if data.optionPoUsePath: + if (poUsePath in data.options): res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) else: res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) @@ -885,7 +914,7 @@ elif not defined(useNimRtl): # Warning: no GC here! # Or anything that touches global structures - all called nim procs # must be marked with stackTrace:off. Inspect C code after making changes. - if not data.optionPoParentStreams: + if not (poParentStreams in data.options): discard close(data.pStdin[writeIdx]) if dup2(data.pStdin[readIdx], readIdx) < 0: startProcessFail(data) @@ -893,7 +922,7 @@ elif not defined(useNimRtl): if dup2(data.pStdout[writeIdx], writeIdx) < 0: startProcessFail(data) discard close(data.pStderr[readIdx]) - if data.optionPoStdErrToStdOut: + if (poStdErrToStdOut in data.options): if dup2(data.pStdout[writeIdx], 2) < 0: startProcessFail(data) else: @@ -907,7 +936,7 @@ elif not defined(useNimRtl): discard close(data.pErrorPipe[readIdx]) discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) - if data.optionPoUsePath: + if (poUsePath in data.options): when defined(uClibc) or defined(linux): # uClibc environment (OpenWrt included) doesn't have the full execvpe let exe = findExe(data.sysCommand) @@ -939,19 +968,22 @@ elif not defined(useNimRtl): if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError()) proc running(p: Process): bool = - var ret : int - var status : cint = 1 - ret = waitpid(p.id, status, WNOHANG) - if ret == int(p.id): - if isExitStatus(status): - p.exitStatus = status - return false - else: - return true - elif ret == 0: - return true # Can't establish status. Assume running. - else: + if p.exitStatus != -3: return false + else: + var ret : int + var status : cint = 1 + ret = waitpid(p.id, status, WNOHANG) + if ret == int(p.id): + if isExitStatus(status): + p.exitStatus = status + return false + else: + return true + elif ret == 0: + return true # Can't establish status. Assume running. + else: + raiseOSError(osLastError()) proc terminate(p: Process) = if kill(p.id, SIGTERM) != 0'i32: @@ -1152,16 +1184,19 @@ elif not defined(useNimRtl): stream = newFileStream(f) proc inputStream(p: Process): Stream = + streamAccess(p) if p.inStream == nil: createStream(p.inStream, p.inHandle, fmWrite) return p.inStream proc outputStream(p: Process): Stream = + streamAccess(p) if p.outStream == nil: createStream(p.outStream, p.outHandle, fmRead) return p.outStream proc errorStream(p: Process): Stream = + streamAccess(p) if p.errStream == nil: createStream(p.errStream, p.errHandle, fmRead) return p.errStream diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 00d007d015..6891e2ff74 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -956,6 +956,7 @@ proc parseInsert(p: var SqlParser): SqlNode = if p.tok.kind == tkParLe: var n = newNode(nkColumnList) parseParIdentList(p, n) + result.add n else: result.add(nil) if isKeyw(p, "default"): @@ -1160,7 +1161,7 @@ proc ra(n: SqlNode, s: var string, indent: int) = else: s.add("\"" & replace(n.strVal, "\"", "\"\"") & "\"") of nkStringLit: - s.add(escape(n.strVal, "e'", "'")) + s.add(escape(n.strVal, "'", "'")) of nkBitStringLit: s.add("b'" & n.strVal & "'") of nkHexStringLit: @@ -1240,7 +1241,7 @@ proc ra(n: SqlNode, s: var string, indent: int) = if n.sons[2].kind == nkDefault: s.add("default values") else: - s.add("\nvalues ") + s.add("\n") ra(n.sons[2], s, indent) s.add(';') of nkUpdate: diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 6eb3e5311e..ea90972fe6 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -310,6 +310,8 @@ else: include ioselects/ioselectors_select elif defined(solaris): include ioselects/ioselectors_poll # need to replace it with event ports + elif defined(genode): + include ioselects/ioselectors_select # TODO: use the native VFS layer else: include ioselects/ioselectors_poll diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index d773cc7d8f..62ceaa2e8b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -32,10 +32,6 @@ when defined(nimOldSplit): else: {.pragma: deprecatedSplit.} -type - CharSet* {.deprecated.} = set[char] # for compatibility with Nim -{.deprecated: [TCharSet: CharSet].} - const Whitespace* = {' ', '\t', '\v', '\r', '\l', '\f'} ## All the characters that count as whitespace. @@ -78,40 +74,40 @@ proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, return c in Letters proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericChar".}= + rtl, extern: "nsuIsAlphaNumericChar".} = ## Checks whether or not `c` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. - return c in Letters or c in Digits + return c in Letters+Digits proc isDigit*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitChar".}= + rtl, extern: "nsuIsDigitChar".} = ## Checks whether or not `c` is a number. ## ## This checks 0-9 ASCII characters only. return c in Digits proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiChar".}= + rtl, extern: "nsuIsSpaceAsciiChar".} = ## Checks whether or not `c` is a whitespace character. return c in Whitespace proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerAsciiChar".}= + rtl, extern: "nsuIsLowerAsciiChar".} = ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. return c in {'a'..'z'} proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperAsciiChar".}= + rtl, extern: "nsuIsUpperAsciiChar".} = ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. return c in {'A'..'Z'} proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaAsciiStr".}= + rtl, extern: "nsuIsAlphaAsciiStr".} = ## Checks whether or not `s` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. @@ -123,10 +119,10 @@ proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isAlphaAscii() and result + if not c.isAlphaAscii(): return false proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericStr".}= + rtl, extern: "nsuIsAlphaNumericStr".} = ## Checks whether or not `s` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. @@ -142,7 +138,7 @@ proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, return false proc isDigit*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitStr".}= + rtl, extern: "nsuIsDigitStr".} = ## Checks whether or not `s` is a numeric value. ## ## This checks 0-9 ASCII characters only. @@ -158,7 +154,7 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, return false proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiStr".}= + rtl, extern: "nsuIsSpaceAsciiStr".} = ## Checks whether or not `s` is completely whitespace. ## ## Returns true if all characters in `s` are whitespace @@ -172,7 +168,7 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, return false proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerAsciiStr".}= + rtl, extern: "nsuIsLowerAsciiStr".} = ## Checks whether or not `s` contains all lower case characters. ## ## This checks ASCII characters only. @@ -187,7 +183,7 @@ proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, true proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperAsciiStr".}= + rtl, extern: "nsuIsUpperAsciiStr".} = ## Checks whether or not `s` contains all upper case characters. ## ## This checks ASCII characters only. @@ -506,16 +502,15 @@ template splitCommon(s, sep, maxsplit, sepLen) = var last = 0 var splits = maxsplit - if len(s) > 0: - while last <= len(s): - var first = last - while last < len(s) and not stringHasSep(s, last, sep): - inc(last) - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) - inc(last, sepLen) + while last <= len(s): + var first = last + while last < len(s) and not stringHasSep(s, last, sep): + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) template oldSplit(s, seps, maxsplit) = var last = 0 @@ -673,30 +668,29 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = splits = maxsplit startPos = 0 - if len(s) > 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 - - dec(splits) + # 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) - last = 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 + + dec(splits) + dec(first) + + last = first iterator rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = @@ -824,12 +818,18 @@ proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[st noSideEffect, rtl, extern: "nsuSplitCharSet".} = ## The same as the `split iterator <#split.i,string,set[char],int>`_, but is a ## proc that returns a sequence of substrings. + runnableExamples: + doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] + doAssert "".split({' '}) == @[""] accumulateResult(split(s, seps, maxsplit)) proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = ## The same as the `split iterator <#split.i,string,char,int>`_, but is a proc ## that returns a sequence of substrings. + runnableExamples: + doAssert "a,b,c".split(',') == @["a", "b", "c"] + doAssert "".split(' ') == @[""] accumulateResult(split(s, sep, maxsplit)) proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, @@ -838,6 +838,13 @@ proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEff ## ## Substrings are separated by the string `sep`. This is a wrapper around the ## `split iterator <#split.i,string,string,int>`_. + runnableExamples: + doAssert "a,b,c".split(",") == @["a", "b", "c"] + doAssert "a man a plan a canal panama".split("a ") == @["", "man ", "plan ", "canal panama"] + doAssert "".split("Elon Musk") == @[""] + doAssert "a largely spaced sentence".split(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] + + doAssert "a largely spaced sentence".split(" ", maxsplit=1) == @["a", " largely spaced sentence"] doAssert(sep.len > 0) accumulateResult(split(s, sep, maxsplit)) @@ -906,6 +913,13 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## + runnableExamples: + doAssert "a largely spaced sentence".rsplit(" ", maxsplit=1) == @["a largely spaced", "sentence"] + + doAssert "a,b,c".rsplit(",") == @["a", "b", "c"] + doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", "plan ", "canal panama"] + doAssert "".rsplit("Elon Musk") == @[""] + doAssert "a largely spaced sentence".rsplit(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] accumulateResult(rsplit(s, sep, maxsplit)) result.reverse() @@ -1305,14 +1319,13 @@ proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) ## This is often useful for generating some code where the items need to ## be *separated* by `sep`. `sep` is only added if `dest` is longer than ## `startLen`. The following example creates a string describing - ## an array of integers: - ## - ## .. code-block:: nim - ## var arr = "[" - ## for x in items([2, 3, 5, 7, 11]): - ## addSep(arr, startLen=len("[")) - ## add(arr, $x) - ## add(arr, "]") + ## an array of integers. + runnableExamples: + var arr = "[" + for x in items([2, 3, 5, 7, 11]): + addSep(arr, startLen=len("[")) + add(arr, $x) + add(arr, "]") if dest.len > startLen: add(dest, sep) proc allCharsInSet*(s: string, theSet: set[char]): bool = @@ -1730,7 +1743,9 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, ## ## Even though the algorithm works with any string `s`, it is only useful ## if `s` contains a number. - ## Example: ``insertSep("1000000") == "1_000_000"`` + runnableExamples: + doAssert insertSep("1000000") == "1_000_000" + var L = (s.len-1) div digits + s.len result = newString(L) var j = 0 @@ -1818,6 +1833,8 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## ## A valid identifier starts with a character of the set `IdentStartChars` ## and is followed by any number of characters of the set `IdentChars`. + runnableExamples: + doAssert "abc_def08".validIdentifier if s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false @@ -1828,7 +1845,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, ## Returns the edit distance between `a` and `b`. ## ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. This implementation is highly optimized! + ## memory overhead. var len1 = a.len var len2 = b.len if len1 > len2: @@ -2007,16 +2024,11 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``float`` type. ## ## If ``precision == -1``, it tries to format it nicely. - ## - ## Examples: - ## - ## .. code-block:: nim - ## - ## let x = 123.456 - ## doAssert x.formatFloat() == "123.4560000000000" - ## doAssert x.formatFloat(ffDecimal, 4) == "123.4560" - ## doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" - ## + runnableExamples: + let x = 123.456 + doAssert x.formatFloat() == "123.4560000000000" + doAssert x.formatFloat(ffDecimal, 4) == "123.4560" + doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" result = formatBiggestFloat(f, format, precision, decimalSep) proc trimZeros*(x: var string) {.noSideEffect.} = @@ -2051,18 +2063,13 @@ proc formatSize*(bytes: int64, ## ## `includeSpace` can be set to true to include the (SI preferred) space ## between the number and the unit (e.g. 1 KiB). - ## - ## Examples: - ## - ## .. code-block:: nim - ## - ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" - ## formatSize((2.234*1024*1024).int) == "2.234MiB" - ## formatSize(4096, includeSpace=true) == "4 KiB" - ## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" - ## formatSize(4096) == "4KiB" - ## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" - ## + runnableExamples: + doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" + doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" + doAssert formatSize(4096, includeSpace=true) == "4 KiB" + doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + doAssert formatSize(4096) == "4KiB" + doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] var @@ -2156,7 +2163,7 @@ proc formatEng*(f: BiggestFloat, ## formatEng(4100, unit="V") == "4.1e3 V" ## formatEng(4100, unit="") == "4.1e3 " # Space with unit="" ## - ## `decimalSep` is used as the decimal separator + ## `decimalSep` is used as the decimal separator. var absolute: BiggestFloat significand: BiggestFloat @@ -2369,17 +2376,16 @@ proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. rtl, extern: "nsuRemoveSuffixCharSet".} = ## Removes all characters from `chars` from the end of the string `s` ## (in-place). - ## - ## .. code-block:: nim - ## var userInput = "Hello World!*~\r\n" - ## userInput.removeSuffix - ## doAssert userInput == "Hello World!*~" - ## userInput.removeSuffix({'~', '*'}) - ## doAssert userInput == "Hello World!" - ## - ## var otherInput = "Hello!?!" - ## otherInput.removeSuffix({'!', '?'}) - ## doAssert otherInput == "Hello" + runnableExamples: + var userInput = "Hello World!*~\r\n" + userInput.removeSuffix + doAssert userInput == "Hello World!*~" + userInput.removeSuffix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "Hello!?!" + otherInput.removeSuffix({'!', '?'}) + doAssert otherInput == "Hello" if s.len == 0: return var last = s.high while last > -1 and s[last] in chars: last -= 1 @@ -2390,24 +2396,23 @@ proc removeSuffix*(s: var string, c: char) {. ## Removes all occurrences of a single character (in-place) from the end ## of a string. ## - ## .. code-block:: nim - ## var table = "users" - ## table.removeSuffix('s') - ## doAssert table == "user" - ## - ## var dots = "Trailing dots......." - ## dots.removeSuffix('.') - ## doAssert dots == "Trailing dots" + runnableExamples: + var table = "users" + table.removeSuffix('s') + doAssert table == "user" + + var dots = "Trailing dots......." + dots.removeSuffix('.') + doAssert dots == "Trailing dots" removeSuffix(s, chars = {c}) proc removeSuffix*(s: var string, suffix: string) {. rtl, extern: "nsuRemoveSuffixString".} = ## Remove the first matching suffix (in-place) from a string. - ## - ## .. code-block:: nim - ## var answers = "yeses" - ## answers.removeSuffix("es") - ## doAssert answers == "yes" + runnableExamples: + var answers = "yeses" + answers.removeSuffix("es") + doAssert answers == "yes" var newLen = s.len if s.endsWith(suffix): newLen -= len(suffix) @@ -2418,16 +2423,16 @@ proc removePrefix*(s: var string, chars: set[char] = Newlines) {. ## Removes all characters from `chars` from the start of the string `s` ## (in-place). ## - ## .. code-block:: nim - ## var userInput = "\r\n*~Hello World!" - ## userInput.removePrefix - ## doAssert userInput == "*~Hello World!" - ## userInput.removePrefix({'~', '*'}) - ## doAssert userInput == "Hello World!" - ## - ## var otherInput = "?!?Hello!?!" - ## otherInput.removePrefix({'!', '?'}) - ## doAssert otherInput == "Hello!?!" + runnableExamples: + var userInput = "\r\n*~Hello World!" + userInput.removePrefix + doAssert userInput == "*~Hello World!" + userInput.removePrefix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "?!?Hello!?!" + otherInput.removePrefix({'!', '?'}) + doAssert otherInput == "Hello!?!" var start = 0 while start < s.len and s[start] in chars: start += 1 if start > 0: s.delete(0, start - 1) @@ -2437,20 +2442,20 @@ proc removePrefix*(s: var string, c: char) {. ## Removes all occurrences of a single character (in-place) from the start ## of a string. ## - ## .. code-block:: nim - ## var ident = "pControl" - ## ident.removePrefix('p') - ## doAssert ident == "Control" + runnableExamples: + var ident = "pControl" + ident.removePrefix('p') + doAssert ident == "Control" removePrefix(s, chars = {c}) proc removePrefix*(s: var string, prefix: string) {. rtl, extern: "nsuRemovePrefixString".} = ## Remove the first matching prefix (in-place) from a string. ## - ## .. code-block:: nim - ## var answers = "yesyes" - ## answers.removePrefix("yes") - ## doAssert answers == "yes" + runnableExamples: + var answers = "yesyes" + answers.removePrefix("yes") + doAssert answers == "yes" if s.startsWith(prefix): s.delete(0, prefix.len - 1) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 7d9c3108ba..257c620f79 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -293,33 +293,33 @@ proc runeSubStr*(s: string, pos:int, len:int = int.high): string = if pos < 0: let (o, rl) = runeReverseOffset(s, -pos) if len >= rl: - result = s[o.. s.len-1] + result = s.substr(o, s.len-1) elif len < 0: let e = rl + len if e < 0: result = "" else: - result = s[o.. runeOffset(s, e-(rl+pos) , o)-1] + result = s.substr(o, runeOffset(s, e-(rl+pos) , o)-1) else: - result = s[o.. runeOffset(s, len, o)-1] + result = s.substr(o, runeOffset(s, len, o)-1) else: let o = runeOffset(s, pos) if o < 0: result = "" elif len == int.high: - result = s[o.. s.len-1] + result = s.substr(o, s.len-1) elif len < 0: let (e, rl) = runeReverseOffset(s, -len) discard rl if e <= 0: result = "" else: - result = s[o.. e-1] + result = s.substr(o, e-1) else: var e = runeOffset(s, len, o) if e < 0: e = s.len - result = s[o.. e-1] + result = s.substr(o, e-1) const alphaRanges = [ diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 164a57ecfa..a651530c3d 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -47,6 +47,49 @@ proc add*(url: var Url, a: Url) {.deprecated.} = url = url / a {.pop.} +proc encodeUrl*(s: string): string = + ## Encodes a value to be HTTP safe: This means that characters in the set + ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result, + ## a space is converted to ``'+'`` and every other character is encoded as + ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. + result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars + for i in 0..s.len-1: + case s[i] + of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i]) + of ' ': add(result, '+') + 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 + ## value) is converted to the character with ordinal number ``xx``, and + ## and every other character is carried over. + proc handleHexChar(c: char, x: var int) {.inline.} = + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + 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 + while i < s.len: + case s[i] + of '%': + var x = 0 + handleHexChar(s[i+1], x) + handleHexChar(s[i+2], x) + inc(i, 2) + result[j] = chr(x) + of '+': result[j] = ' ' + else: result[j] = s[i] + inc(i) + inc(j) + setLen(result, j) + proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false @@ -327,6 +370,11 @@ proc `$`*(u: Uri): string = result.add(u.anchor) when isMainModule: + block: + 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) diff --git a/lib/system.nim b/lib/system.nim index 1b53bf9f57..b9f01c3065 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1439,7 +1439,11 @@ const ## is the value that should be passed to `quit <#quit>`_ to indicate ## failure. -var programResult* {.exportc: "nim_program_result".}: int +when defined(nodejs): + var programResult* {.importc: "process.exitCode".}: int + programResult = 0 +else: + var programResult* {.exportc: "nim_program_result".}: int ## modify this variable to specify the exit code of the program ## under normal circumstances. When the program is terminated ## prematurely using ``quit``, this value is ignored. @@ -3525,7 +3529,10 @@ when hasAlloc or defined(nimscript): ## .. code-block:: nim ## var s = "abcdef" ## assert s[1..3] == "bcd" - result = s.substr(s ^^ x.a, s ^^ x.b) + let a = s ^^ x.a + let L = (s ^^ x.b) - a + 1 + result = newString(L) + for i in 0 ..< L: result[i] = s[i + a] proc `[]=`*[T, U](s: var string, x: HSlice[T, U], b: string) = ## slice assignment for strings. If @@ -3752,6 +3759,7 @@ template assert*(cond: bool, msg = "") = ## that ``AssertionError`` is hidden from the effect system, so it doesn't ## produce ``{.raises: [AssertionError].}``. This exception is only supposed ## to be caught by unit testing frameworks. + ## ## The compiler may not generate any code at all for ``assert`` if it is ## advised to do so through the ``-d:release`` or ``--assertions:off`` ## `command line switches `_. @@ -3992,3 +4000,38 @@ when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage): proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32", importc: "SetConsoleOutputCP".} discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage + + +when defined(nimHasRunnableExamples): + proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} + ## A section you should use to mark `runnable example`:idx: code with. + ## + ## - In normal debug and release builds code within + ## a ``runnableExamples`` section is ignored. + ## - The documentation generator is aware of these examples and considers them + ## part of the ``##`` doc comment. As the last step of documentation + ## generation the examples are put into an ``$file_example.nim`` file, + ## compiled and tested. The collected examples are + ## put into their own module to ensure the examples do not refer to + ## non-exported symbols. +else: + template runnableExamples*(body: untyped) = + discard + +template doAssertRaises*(exception, code: untyped): typed = + ## Raises ``AssertionError`` if specified ``code`` does not raise the + ## specified exception. + runnableExamples: + doAssertRaises(ValueError): + raise newException(ValueError, "Hello World") + + try: + block: + code + raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) + except exception: + discard + except Exception as exc: + raiseAssert(astToStr(exception) & + " wasn't raised, another error was raised instead by:\n"& + astToStr(code)) \ No newline at end of file diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 19d27e7d2b..e274e8e0cd 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -8,8 +8,6 @@ # # Low level allocator for Nim. Has been designed to support the GC. -# TODO: -# - make searching for block O(1) {.push profiler:off.} include osalloc @@ -19,14 +17,17 @@ template track(op, address, size) = memTrackerOp(op, address, size) # We manage *chunks* of memory. Each chunk is a multiple of the page size. -# Each chunk starts at an address that is divisible by the page size. Chunks -# that are bigger than ``ChunkOsReturn`` are returned back to the operating -# system immediately. +# Each chunk starts at an address that is divisible by the page size. const - ChunkOsReturn = 256 * PageSize # 1 MB - InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn! + InitialMemoryRequest = 128 * PageSize # 0.5 MB SmallChunkSize = PageSize + MaxFli = 30 + MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32' + # everywhere! + MaxSli = 1 shl MaxLog2Sli + FliOffset = 6 + RealFli = MaxFli - FliOffset type PTrunk = ptr Trunk @@ -99,10 +100,12 @@ type MemRegion = object minLargeObj, maxLargeObj: int freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk] + flBitmap: uint32 + slBitmap: array[RealFli, uint32] + matrix: array[RealFli, array[MaxSli, PBigChunk]] llmem: PLLChunk currMem, maxMem, freeMem: int # memory sizes (allocated from OS) lastSize: int # needed for the case that OS gives us pages linearly - freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access chunkStarts: IntSet root, deleted, last, freeAvlNodes: PAvlNode locked, blockChunkSizeIncrease: bool # if locked, we cannot free pages. @@ -110,7 +113,109 @@ type bottomData: AvlNode heapLinks: HeapLinks -{.deprecated: [TMemRegion: MemRegion].} +const + fsLookupTable: array[byte, int8] = [ + -1'i8, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + ] + +proc msbit(x: uint32): int {.inline.} = + let a = if x <= 0xff_ff: + (if x <= 0xff: 0 else: 8) + else: + (if x <= 0xff_ff_ff: 16 else: 24) + result = int(fsLookupTable[byte(x shr a)]) + a + +proc lsbit(x: uint32): int {.inline.} = + msbit(x and ((not x) + 1)) + +proc setBit(nr: int; dest: var uint32) {.inline.} = + dest = dest or (1u32 shl (nr and 0x1f)) + +proc clearBit(nr: int; dest: var uint32) {.inline.} = + dest = dest and not (1u32 shl (nr and 0x1f)) + +proc mappingSearch(r, fl, sl: var int) {.inline.} = + #let t = (1 shl (msbit(uint32 r) - MaxLog2Sli)) - 1 + # This diverges from the standard TLSF algorithm because we need to ensure + # PageSize alignment: + let t = roundup((1 shl (msbit(uint32 r) - MaxLog2Sli)), PageSize) - 1 + r = r + t + fl = msbit(uint32 r) + sl = (r shr (fl - MaxLog2Sli)) - MaxSli + dec fl, FliOffset + r = r and not t + sysAssert((r and PageMask) == 0, "mappingSearch: still not aligned") + +# See http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf for details of +# this algorithm. + +proc mappingInsert(r: int): tuple[fl, sl: int] {.inline.} = + sysAssert((r and PageMask) == 0, "mappingInsert: still not aligned") + result.fl = msbit(uint32 r) + result.sl = (r shr (result.fl - MaxLog2Sli)) - MaxSli + dec result.fl, FliOffset + +template mat(): untyped = a.matrix[fl][sl] + +proc findSuitableBlock(a: MemRegion; fl, sl: var int): PBigChunk {.inline.} = + let tmp = a.slBitmap[fl] and (not 0u32 shl sl) + result = nil + if tmp != 0: + sl = lsbit(tmp) + result = mat() + else: + fl = lsbit(a.flBitmap and (not 0u32 shl (fl + 1))) + if fl > 0: + sl = lsbit(a.slBitmap[fl]) + result = mat() + +template clearBits(sl, fl) = + clearBit(sl, a.slBitmap[fl]) + if a.slBitmap[fl] == 0u32: + # do not forget to cascade: + clearBit(fl, a.flBitmap) + +proc removeChunkFromMatrix(a: var MemRegion; b: PBigChunk) = + let (fl, sl) = mappingInsert(b.size) + if b.next != nil: b.next.prev = b.prev + if b.prev != nil: b.prev.next = b.next + if mat() == b: + mat() = b.next + if mat() == nil: + clearBits(sl, fl) + b.prev = nil + b.next = nil + +proc removeChunkFromMatrix2(a: var MemRegion; b: PBigChunk; fl, sl: int) = + mat() = b.next + if mat() != nil: + mat().prev = nil + else: + clearBits(sl, fl) + b.prev = nil + b.next = nil + +proc addChunkToMatrix(a: var MemRegion; b: PBigChunk) = + let (fl, sl) = mappingInsert(b.size) + b.prev = nil + b.next = mat() + if mat() != nil: + mat().prev = b + mat() = b + setBit(sl, a.slBitmap[fl]) + setBit(fl, a.flBitmap) {.push stack_trace: off.} proc initAllocator() = discard "nothing to do anymore" @@ -203,6 +308,7 @@ proc llDeallocAll(a: var MemRegion) = var next = it.next osDeallocPages(it, PageSize) it = next + a.llmem = nil proc intSetGet(t: IntSet, key: int): PTrunk = var it = t.data[key and high(t.data)] @@ -369,6 +475,7 @@ proc requestOsChunks(a: var MemRegion, size: int): PBigChunk = result.prevSize = 0 or (result.prevSize and 1) # unknown # but do not overwrite 'used' field a.lastSize = size # for next request + sysAssert((cast[int](result) and PageMask) == 0, "requestOschunks: unaligned chunk") proc isAccessible(a: MemRegion, p: pointer): bool {.inline.} = result = contains(a.chunkStarts, pageIndex(p)) @@ -419,7 +526,7 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = if isAccessible(a, ri) and chunkUnused(ri): sysAssert(not isSmallChunk(ri), "freeBigChunk 3") if not isSmallChunk(ri): - listRemove(a.freeChunksList, cast[PBigChunk](ri)) + removeChunkFromMatrix(a, cast[PBigChunk](ri)) inc(c.size, ri.size) excl(a.chunkStarts, pageIndex(ri)) when coalescLeft: @@ -430,49 +537,44 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = if isAccessible(a, le) and chunkUnused(le): sysAssert(not isSmallChunk(le), "freeBigChunk 5") if not isSmallChunk(le): - listRemove(a.freeChunksList, cast[PBigChunk](le)) + removeChunkFromMatrix(a, cast[PBigChunk](le)) inc(le.size, c.size) excl(a.chunkStarts, pageIndex(c)) c = cast[PBigChunk](le) incl(a, a.chunkStarts, pageIndex(c)) updatePrevSize(a, c, c.size) - listAdd(a.freeChunksList, c) + addChunkToMatrix(a, c) # set 'used' to false: c.prevSize = c.prevSize and not 1 proc splitChunk(a: var MemRegion, c: PBigChunk, size: int) = var rest = cast[PBigChunk](cast[ByteAddress](c) +% size) - sysAssert(rest notin a.freeChunksList, "splitChunk") rest.size = c.size - size track("rest.origSize", addr rest.origSize, sizeof(int)) + # XXX check if these two nil assignments are dead code given + # addChunkToMatrix's implementation: rest.next = nil rest.prev = nil - # size and not used + # size and not used: rest.prevSize = size sysAssert((size and 1) == 0, "splitChunk 2") + sysAssert((size and PageMask) == 0, + "splitChunk: size is not a multiple of the PageSize") updatePrevSize(a, c, rest.size) c.size = size incl(a, a.chunkStarts, pageIndex(rest)) - listAdd(a.freeChunksList, rest) + addChunkToMatrix(a, rest) proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # use first fit for now: - sysAssert((size and PageMask) == 0, "getBigChunk 1") sysAssert(size > 0, "getBigChunk 2") - result = a.freeChunksList - block search: - while result != nil: - sysAssert chunkUnused(result), "getBigChunk 3" - if result.size == size: - listRemove(a.freeChunksList, result) - break search - elif result.size > size: - listRemove(a.freeChunksList, result) - splitChunk(a, result, size) - break search - result = result.next - sysAssert result != a.freeChunksList, "getBigChunk 4" + var size = size # roundup(size, PageSize) + var fl, sl: int + mappingSearch(size, fl, sl) + sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk") + result = findSuitableBlock(a, fl, sl) + if result == nil: if size < InitialMemoryRequest: result = requestOsChunks(a, InitialMemoryRequest) splitChunk(a, result, size) @@ -481,7 +583,10 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # if we over allocated split the chunk: if result.size > size: splitChunk(a, result, size) - + else: + removeChunkFromMatrix2(a, result, fl, sl) + if result.size >= size + PageSize: + splitChunk(a, result, size) # set 'used' to to true: result.prevSize = 1 track("setUsedToFalse", addr result.origSize, sizeof(int)) @@ -572,14 +677,14 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = size == 0, "rawAlloc 21") sysAssert(allocInv(a), "rawAlloc: end small size") else: - size = roundup(requestedSize+bigChunkOverhead(), PageSize) + size = requestedSize + bigChunkOverhead() # roundup(requestedSize+bigChunkOverhead(), PageSize) # allocate a large block var c = getBigChunk(a, size) sysAssert c.prev == nil, "rawAlloc 10" sysAssert c.next == nil, "rawAlloc 11" - sysAssert c.size == size, "rawAlloc 12" result = addr(c.data) - sysAssert((cast[ByteAddress](result) and (MemAlign-1)) == 0, "rawAlloc 13") + sysAssert((cast[ByteAddress](c) and (MemAlign-1)) == 0, "rawAlloc 13") + sysAssert((cast[ByteAddress](c) and PageMask) == 0, "rawAlloc: Not aligned on a page boundary") if a.root == nil: a.root = getBottom(a) add(a, a.root, cast[ByteAddress](result), cast[ByteAddress](result)+%size) sysAssert(isAccessible(a, result), "rawAlloc 14") diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 8249349669..9ac039e192 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -42,7 +42,8 @@ type # Page size of the system; in most cases 4096 bytes. For exotic OS or # CPU this needs to be changed: const - PageShift = when defined(cpu16): 8 else: 12 + PageShift = when defined(cpu16): 8 else: 12 # \ + # my tests showed no improvments for using larger page sizes. PageSize = 1 shl PageShift PageMask = PageSize-1 @@ -343,7 +344,6 @@ elif defined(gogc): const goFlagNoZero: uint32 = 1 shl 3 proc goRuntimeMallocGC(size: uint, typ: uint, flag: uint32): pointer {.importc: "runtime_mallocgc", dynlib: goLib.} - proc goFree(v: pointer) {.importc: "__go_free", dynlib: goLib.} proc goSetFinalizer(obj: pointer, f: pointer) {.importc: "set_finalizer", codegenDecl:"$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", dynlib: goLib.} @@ -376,7 +376,6 @@ elif defined(gogc): result = goRuntimeMallocGC(roundup(newsize, sizeof(pointer)).uint, 0.uint, goFlagNoZero) copyMem(result, old, oldsize) zeroMem(cast[pointer](cast[ByteAddress](result) +% oldsize), newsize - oldsize) - goFree(old) proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard @@ -573,3 +572,11 @@ when not declared(nimNewSeqOfCap): cast[PGenericSeq](result).reserved = cap {.pop.} + +when not declared(ForeignCell): + type ForeignCell* = object + data*: pointer + + proc protect*(x: pointer): ForeignCell = ForeignCell(data: x) + proc dispose*(x: ForeignCell) = discard + proc isNotForeign*(x: ForeignCell): bool = false diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 65a057772c..444f113069 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -166,7 +166,7 @@ elif defined(windows): # space heavily, so we now treat Windows as a strange unmap target. when reallyOsDealloc: if virtualFree(p, 0, MEM_RELEASE) == 0: - cprintf "yes, failing!" + cprintf "virtualFree failing!" quit 1 #VirtualFree(p, size, MEM_DECOMMIT) diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 3f8e0eff0c..56b8ade97a 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -259,7 +259,7 @@ proc incrSeqV2(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = result.reserved = r proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. - compilerRtl.} = + compilerRtl, inl.} = result = seq if result.space < newLen: let r = max(resize(result.space), newLen) @@ -282,10 +282,11 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. doDecRef(gch.tempStack.d[i], LocalHeap, MaybeCyclic) gch.tempStack.len = len0 else: - for i in newLen..result.len-1: - forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% - GenericSeqSize +% (i*%elemSize)), - extGetCellType(result).base, waZctDecRef) + if ntfNoRefs notin extGetCellType(result).base.flags: + for i in newLen..result.len-1: + forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% + GenericSeqSize +% (i*%elemSize)), + extGetCellType(result).base, waZctDecRef) # XXX: zeroing out the memory can still result in crashes if a wiped-out # cell is aliased by another pointer (ie proc parameter or a let variable). diff --git a/lib/system/threads.nim b/lib/system/threads.nim index 016bf5822b..f61cc4280f 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -255,9 +255,9 @@ when emulatedThreadVars: proc nimThreadVarsSize(): int {.noconv, importc: "NimThreadVarsSize".} # we preallocate a fixed size for thread local storage, so that no heap -# allocations are needed. Currently less than 7K are used on a 64bit machine. +# allocations are needed. Currently less than 16K are used on a 64bit machine. # We use ``float`` for proper alignment: -const nimTlsSize {.intdefine.} = 8000 +const nimTlsSize {.intdefine.} = 16000 type ThreadLocalStorage = array[0..(nimTlsSize div sizeof(float)), float] diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index c3229cc7bb..7eb268a9a4 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -541,6 +541,7 @@ var SO_DONTLINGER* {.importc, header: "winsock2.h".}: cint SO_EXCLUSIVEADDRUSE* {.importc, header: "winsock2.h".}: cint # disallow local address reuse SO_ERROR* {.importc, header: "winsock2.h".}: cint + TCP_NODELAY* {.importc, header: "winsock2.h".}: cint proc `==`*(x, y: SocketHandle): bool {.borrow.} diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 67645f0437..0328b817a0 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -526,6 +526,9 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of cmdEnd: break of cmdLongoption, cmdShortOption: case p.key.normalize + of "help": + stdout.writeline(Usage) + quit() of "port": gPort = parseInt(p.val).Port gMode = mtcp diff --git a/tests/async/tasyncall.nim b/tests/async/tasyncall.nim index a3926eabdd..775dd0c6f0 100644 --- a/tests/async/tasyncall.nim +++ b/tests/async/tasyncall.nim @@ -40,6 +40,16 @@ proc testVarargs(x, y, z: int): seq[int] = result = waitFor all(a, b, c) +proc testWithDupes() = + var + tasks = newSeq[Future[void]](taskCount) + fut = futureWithoutValue() + + for i in 0.. 0: - cmd = curl & " --out " & "dist" / mingw & " " & url + cmd = quoteShell(curl) & " --out " & "dist" / mingw & " " & url elif fileExists"bin/nimgrab.exe": cmd = "bin/nimgrab.exe " & url & " dist" / mingw if cmd.len > 0: diff --git a/tools/nimweb.nim b/tools/nimweb.nim index ffb1ac4e3a..c8b87c1f25 100644 --- a/tools/nimweb.nim +++ b/tools/nimweb.nim @@ -52,7 +52,7 @@ proc initConfigData(c: var TConfigData) = c.pdf = @[] c.infile = "" c.outdir = "" - c.nimArgs = "--hint[Conf]:off --hint[Path]:off --hint[Processing]:off " + c.nimArgs = "--hint[Conf]:off --hint[Path]:off --hint[Processing]:off -d:boot " c.authors = "" c.projectTitle = "" c.projectName = ""