diff options
Diffstat (limited to 'lib')
55 files changed, 1154 insertions, 456 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 5be3a2cce..e4a56d6f9 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -140,10 +140,6 @@ const {.push warnings: off.} -proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated: - "Deprecated since version 0.18.0: Use 'ident' or 'newIdentNode' instead.".} - ## Constructs an identifier from the string `s`. - proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated: "Deprecated since version 0.18.0: Use 'ident' or 'newIdentNode' instead.".} ## Constructs an identifier from the string `s`. @@ -281,9 +277,12 @@ else: # bootstrapping substitute {.pop.} -when defined(nimSymImplTransform): +when (NimMajor, NimMinor, NimPatch) >= (1, 3, 5) or defined(nimSymImplTransform): proc getImplTransformed*(symbol: NimNode): NimNode {.magic: "GetImplTransf", noSideEffect.} - ## For a typed proc returns the AST after transformation pass. + ## For a typed proc returns the AST after transformation pass; this is useful + ## for debugging how the compiler transforms code (eg: `defer`, `for`) but + ## note that code transformations are implementation dependent and subject to change. + ## See an example in `tests/macros/tmacros_various.nim`. when defined(nimHasSymOwnerInMacro): proc owner*(sym: NimNode): NimNode {.magic: "SymOwner", noSideEffect.} @@ -393,13 +392,6 @@ proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect, de {.pop.} -#proc `typ=`*(n: NimNode, typ: typedesc) {.magic: "NSetType".} -# this is not sound! Unfortunately forbidding 'typ=' is not enough, as you -# can easily do: -# let bracket = semCheck([1, 2]) -# let fake = semCheck(2.0) -# bracket[0] = fake # constructs a mixed array with ints and floats! - proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.} ## Sets the string value of a string literal or comment. ## Setting `strVal` is disallowed for `nnkIdent` and `nnkSym` nodes; a new node @@ -830,14 +822,6 @@ proc nestList*(op: NimNode; pack: NimNode; init: NimNode): NimNode {.compileTime for i in countdown(pack.len - 1, 0): result = newCall(op, pack[i], result) -{.push warnings: off.} - -proc nestList*(theProc: NimIdent, x: NimNode): NimNode {.compileTime, deprecated: - "Deprecated since v0.18.1; use one of 'nestList(NimNode, ...)' instead.".} = - nestList(newIdentNode(theProc), x) - -{.pop.} - proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.benign.} = if level > 0: if indented: @@ -1628,21 +1612,6 @@ macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = if result.kind == nnkEmpty: error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, - -when not defined(booting): - template emit*(e: static[string]): untyped {.deprecated.} = - ## Accepts a single string argument and treats it as nim code - ## that should be inserted verbatim in the program - ## Example: - ## - ## .. code-block:: nim - ## emit("echo " & '"' & "hello world".toUpper & '"') - ## - ## Deprecated since version 0.15 since it's so rarely useful. - 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: @@ -1692,3 +1661,39 @@ when defined(nimMacrosSizealignof): proc isExported*(n: NimNode): bool {.noSideEffect.} = ## Returns whether the symbol is exported or not. + +proc extractDocCommentsAndRunnables*(n: NimNode): NimNode = + ## returns a `nnkStmtList` containing the top-level doc comments and + ## runnableExamples in `a`, stopping at the first child that is neither. + ## Example: + ## + ## .. code-block:: nim + ## import macros + ## macro transf(a): untyped = + ## result = quote do: + ## proc fun2*() = discard + ## let header = extractDocCommentsAndRunnables(a.body) + ## # correct usage: rest is appended + ## result.body = header + ## result.body.add quote do: discard # just an example + ## # incorrect usage: nesting inside a nnkStmtList: + ## # result.body = quote do: (`header`; discard) + ## + ## proc fun*() {.transf.} = + ## ## first comment + ## runnableExamples: discard + ## runnableExamples: discard + ## ## last comment + ## discard # first statement after doc comments + runnableExamples + ## ## not docgen'd + + result = newStmtList() + for ni in n: + case ni.kind + of nnkCommentStmt: + result.add ni + of nnkCall: + if ni[0].kind == nnkIdent and ni[0].strVal == "runnableExamples": + result.add ni + else: break + else: break diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index fe71f89fe..0817ca4ec 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -7,7 +7,7 @@ # when defined(js): - {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE.".} + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} ## What is NRE? ## ============ @@ -498,7 +498,7 @@ proc re*(pattern: string): Regex = initRegex(pattern, flags, study) proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] = - var myResult = RegexMatch(pattern : pattern, str : str) + var myResult = RegexMatch(pattern: pattern, str: str) # See PCRE man pages. # 2x capture count to make room for start-end pairs # 1x capture count as slack space for PCRE @@ -528,13 +528,13 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt of pcre.ERROR_NULL: raise newException(AccessViolationDefect, "Expected non-null parameters") of pcre.ERROR_BADOPTION: - raise RegexInternalError(msg : "Unknown pattern flag. Either a bug or " & + raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " & "outdated PCRE.") of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET: - raise InvalidUnicodeError(msg : "Invalid unicode byte sequence", - pos : myResult.pcreMatchBounds[0].a) + raise InvalidUnicodeError(msg: "Invalid unicode byte sequence", + pos: myResult.pcreMatchBounds[0].a) else: - raise RegexInternalError(msg : "Unknown internal error: " & $execRet) + raise RegexInternalError(msg: "Unknown internal error: " & $execRet) proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = ## Like ` ``find(...)`` <#proc-find>`_, but anchored to the start of the diff --git a/lib/impure/re.nim b/lib/impure/re.nim index 1a6622677..fe3ea96ff 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -8,7 +8,7 @@ # when defined(js): - {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE.".} + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} ## Regular expression support for Nim. ## diff --git a/lib/js/dom.nim b/lib/js/dom.nim index 48d32fc18..be676a813 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -106,7 +106,17 @@ type memory*: PerformanceMemory timing*: PerformanceTiming - Selection* {.importc.} = ref object ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Selection>`_ + Range* {.importc.} = ref object + ## see `docs{https://developer.mozilla.org/en-US/docs/Web/API/Range}`_ + collapsed*: bool + commonAncestorContainer*: Node + endContainer*: Node + endOffset*: int + startContainer*: Node + startOffset*: int + + Selection* {.importc.} = ref object + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Selection>`_ anchorNode*: Node anchorOffset*: int focusNode*: Node @@ -186,6 +196,9 @@ type innerText*: cstring textContent*: cstring style*: Style + baseURI*: cstring + parentElement*: Element + isConnected*: bool Document* = ref DocumentObj DocumentObj {.importc.} = object of NodeObj @@ -210,9 +223,11 @@ type applets*: seq[Element] embeds*: seq[EmbedElement] links*: seq[LinkElement] + fonts*: FontFaceSet Element* = ref ElementObj ElementObj {.importc.} = object of NodeObj + className*: cstring classList*: ClassList checked*: bool defaultChecked*: bool @@ -371,19 +386,73 @@ type Style* = ref StyleObj StyleObj {.importc.} = object of RootObj + alignContent*: cstring + alignItems*: cstring + alignSelf*: cstring + all*: cstring + animation*: cstring + animationDelay*: cstring + animationDirection*: cstring + animationDuration*: cstring + animationFillMode*: cstring + animationIterationCount*: cstring + animationName*: cstring + animationPlayState*: cstring + animationTimingFunction*: cstring + backdropFilter*: cstring + backfaceVisibility*: cstring background*: cstring backgroundAttachment*: cstring + backgroundBlendMode*: cstring + backgroundClip*: cstring backgroundColor*: cstring backgroundImage*: cstring + backgroundOrigin*: cstring backgroundPosition*: cstring backgroundRepeat*: cstring backgroundSize*: cstring + blockSize*: cstring border*: cstring + borderBlock*: cstring + borderBlockColor*: cstring + borderBlockEnd*: cstring + borderBlockEndColor*: cstring + borderBlockEndStyle*: cstring + borderBlockEndWidth*: cstring + borderBlockStart*: cstring + borderBlockStartColor*: cstring + borderBlockStartStyle*: cstring + borderBlockStartWidth*: cstring + borderBlockStyle*: cstring + borderBlockWidth*: cstring borderBottom*: cstring borderBottomColor*: cstring + borderBottomLeftRadius*: cstring + borderBottomRightRadius*: cstring borderBottomStyle*: cstring borderBottomWidth*: cstring + borderCollapse*: cstring borderColor*: cstring + borderEndEndRadius*: cstring + borderEndStartRadius*: cstring + borderImage*: cstring + borderImageOutset*: cstring + borderImageRepeat*: cstring + borderImageSlice*: cstring + borderImageSource*: cstring + borderImageWidth*: cstring + borderInline*: cstring + borderInlineColor*: cstring + borderInlineEnd*: cstring + borderInlineEndColor*: cstring + borderInlineEndStyle*: cstring + borderInlineEndWidth*: cstring + borderInlineStart*: cstring + borderInlineStartColor*: cstring + borderInlineStartStyle*: cstring + borderInlineStartWidth*: cstring + borderInlineStyle*: cstring + borderInlineWidth*: cstring borderLeft*: cstring borderLeftColor*: cstring borderLeftStyle*: cstring @@ -393,86 +462,298 @@ type borderRightColor*: cstring borderRightStyle*: cstring borderRightWidth*: cstring + borderSpacing*: cstring + borderStartEndRadius*: cstring + borderStartStartRadius*: cstring borderStyle*: cstring borderTop*: cstring borderTopColor*: cstring + borderTopLeftRadius*: cstring + borderTopRightRadius*: cstring borderTopStyle*: cstring borderTopWidth*: cstring borderWidth*: cstring bottom*: cstring - boxSizing*: cstring + boxDecorationBreak*: cstring boxShadow*: cstring + boxSizing*: cstring + breakAfter*: cstring + breakBefore*: cstring + breakInside*: cstring captionSide*: cstring + caretColor*: cstring clear*: cstring clip*: cstring clipPath*: cstring color*: cstring + colorAdjust*: cstring + columnCount*: cstring + columnFill*: cstring + columnGap*: cstring + columnRule*: cstring + columnRuleColor*: cstring + columnRuleStyle*: cstring + columnRuleWidth*: cstring + columnSpan*: cstring + columnWidth*: cstring + columns*: cstring + contain*: cstring + content*: cstring + counterIncrement*: cstring + counterReset*: cstring + counterSet*: cstring cursor*: cstring direction*: cstring display*: cstring emptyCells*: cstring + filter*: cstring + flex*: cstring + flexBasis*: cstring + flexDirection*: cstring + flexFlow*: cstring + flexGrow*: cstring + flexShrink*: cstring + flexWrap*: cstring cssFloat*: cstring font*: cstring fontFamily*: cstring + fontFeatureSettings*: cstring + fontKerning*: cstring + fontLanguageOverride*: cstring + fontOpticalSizing*: cstring fontSize*: cstring + fontSizeAdjust*: cstring fontStretch*: cstring fontStyle*: cstring + fontSynthesis*: cstring fontVariant*: cstring + fontVariantAlternates*: cstring + fontVariantCaps*: cstring + fontVariantEastAsian*: cstring + fontVariantLigatures*: cstring + fontVariantNumeric*: cstring + fontVariantPosition*: cstring + fontVariationSettings*: cstring fontWeight*: cstring + gap*: cstring + grid*: cstring + gridArea*: cstring + gridAutoColumns*: cstring + gridAutoFlow*: cstring + gridAutoRows*: cstring + gridColumn*: cstring + gridColumnEnd*: cstring + gridColumnStart*: cstring + gridRow*: cstring + gridRowEnd*: cstring + gridRowStart*: cstring + gridTemplate*: cstring + gridTemplateAreas*: cstring + gridTemplateColumns*: cstring + gridTemplateRows*: cstring + hangingPunctuation*: cstring height*: cstring + hyphens*: cstring + imageOrientation*: cstring + imageRendering*: cstring + inlineSize*: cstring + inset*: cstring + insetBlock*: cstring + insetBlockEnd*: cstring + insetBlockStart*: cstring + insetInline*: cstring + insetInlineEnd*: cstring + insetInlineStart*: cstring + isolation*: cstring + justifyContent*: cstring + justifyItems*: cstring + justifySelf*: cstring left*: cstring letterSpacing*: cstring + lineBreak*: cstring lineHeight*: cstring listStyle*: cstring listStyleImage*: cstring listStylePosition*: cstring listStyleType*: cstring margin*: cstring + marginBlock*: cstring + marginBlockEnd*: cstring + marginBlockStart*: cstring marginBottom*: cstring + marginInline*: cstring + marginInlineEnd*: cstring + marginInlineStart*: cstring marginLeft*: cstring marginRight*: cstring marginTop*: cstring + mask*: cstring + maskBorder*: cstring + maskBorderMode*: cstring + maskBorderOutset*: cstring + maskBorderRepeat*: cstring + maskBorderSlice*: cstring + maskBorderSource*: cstring + maskBorderWidth*: cstring + maskClip*: cstring + maskComposite*: cstring + maskImage*: cstring + maskMode*: cstring + maskOrigin*: cstring + maskPosition*: cstring + maskRepeat*: cstring + maskSize*: cstring + maskType*: cstring + maxBlockSize*: cstring maxHeight*: cstring + maxInlineSize*: cstring maxWidth*: cstring + minBlockSize*: cstring minHeight*: cstring + minInlineSize*: cstring minWidth*: cstring + mixBlendMode*: cstring + objectFit*: cstring + objectPosition*: cstring + offset*: cstring + offsetAnchor*: cstring + offsetDistance*: cstring + offsetPath*: cstring + offsetRotate*: cstring opacity*: cstring + order*: cstring + orphans*: cstring outline*: cstring + outlineColor*: cstring + outlineOffset*: cstring + outlineStyle*: cstring + outlineWidth*: cstring overflow*: cstring + overflowAnchor*: cstring + overflowBlock*: cstring + overflowInline*: cstring + overflowWrap*: cstring overflowX*: cstring overflowY*: cstring + overscrollBehavior*: cstring + overscrollBehaviorBlock*: cstring + overscrollBehaviorInline*: cstring + overscrollBehaviorX*: cstring + overscrollBehaviorY*: cstring padding*: cstring + paddingBlock*: cstring + paddingBlockEnd*: cstring + paddingBlockStart*: cstring paddingBottom*: cstring + paddingInline*: cstring + paddingInlineEnd*: cstring + paddingInlineStart*: cstring paddingLeft*: cstring paddingRight*: cstring paddingTop*: cstring pageBreakAfter*: cstring pageBreakBefore*: cstring + pageBreakInside*: cstring + paintOrder*: cstring + perspective*: cstring + perspectiveOrigin*: cstring + placeContent*: cstring + placeItems*: cstring + placeSelf*: cstring pointerEvents*: cstring position*: cstring + quotes*: cstring resize*: cstring right*: cstring + rotate*: cstring + rowGap*: cstring + scale*: cstring + scrollBehavior*: cstring + scrollMargin*: cstring + scrollMarginBlock*: cstring + scrollMarginBlockEnd*: cstring + scrollMarginBlockStart*: cstring + scrollMarginBottom*: cstring + scrollMarginInline*: cstring + scrollMarginInlineEnd*: cstring + scrollMarginInlineStart*: cstring + scrollMarginLeft*: cstring + scrollMarginRight*: cstring + scrollMarginTop*: cstring + scrollPadding*: cstring + scrollPaddingBlock*: cstring + scrollPaddingBlockEnd*: cstring + scrollPaddingBlockStart*: cstring + scrollPaddingBottom*: cstring + scrollPaddingInline*: cstring + scrollPaddingInlineEnd*: cstring + scrollPaddingInlineStart*: cstring + scrollPaddingLeft*: cstring + scrollPaddingRight*: cstring + scrollPaddingTop*: cstring + scrollSnapAlign*: cstring + scrollSnapStop*: cstring + scrollSnapType*: cstring scrollbar3dLightColor*: cstring scrollbarArrowColor*: cstring scrollbarBaseColor*: cstring + scrollbarColor*: cstring scrollbarDarkshadowColor*: cstring scrollbarFaceColor*: cstring scrollbarHighlightColor*: cstring scrollbarShadowColor*: cstring scrollbarTrackColor*: cstring + scrollbarWidth*: cstring + shapeImageThreshold*: cstring + shapeMargin*: cstring + shapeOutside*: cstring + tabSize*: cstring tableLayout*: cstring textAlign*: cstring + textAlignLast*: cstring + textCombineUpright*: cstring textDecoration*: cstring + textDecorationColor*: cstring + textDecorationLine*: cstring + textDecorationSkipInk*: cstring + textDecorationStyle*: cstring + textDecorationThickness*: cstring + textEmphasis*: cstring + textEmphasisColor*: cstring + textEmphasisPosition*: cstring + textEmphasisStyle*: cstring textIndent*: cstring + textJustify*: cstring + textOrientation*: cstring + textOverflow*: cstring + textRendering*: cstring + textShadow*: cstring textTransform*: cstring - transform*: cstring + textUnderlineOffset*: cstring + textUnderlinePosition*: cstring top*: cstring + touchAction*: cstring + transform*: cstring + transformBox*: cstring + transformOrigin*: cstring + transformStyle*: cstring + transition*: cstring + transitionDelay*: cstring + transitionDuration*: cstring + transitionProperty*: cstring + transitionTimingFunction*: cstring + translate*: cstring + unicodeBidi*: cstring verticalAlign*: cstring visibility*: cstring - width*: cstring whiteSpace*: cstring + widows*: cstring + width*: cstring + willChange*: cstring + wordBreak*: cstring wordSpacing*: cstring - zIndex*: int + writingMode*: cstring + zIndex*: cstring EventPhase* = enum None = 0, @@ -923,6 +1204,10 @@ type ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DragEvent>`_ dataTransfer*: DataTransfer + ClipboardEvent* {.importc.} = object of Event + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent>`_ + clipboardData*: DataTransfer + TouchList* {.importc.} = ref object of RootObj length*: int @@ -1002,6 +1287,15 @@ type once*: bool passive*: bool + FontFaceSetReady* {.importc.} = ref object + ## see: `docs<https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready>`_ + then*: proc(cb: proc()) + + FontFaceSet* {.importc.} = ref object + ## see: `docs<https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet>`_ + ready*: FontFaceSetReady + onloadingdone*: proc(event: Event) + since (1, 3): type DomParser* = ref object @@ -1127,6 +1421,7 @@ else: proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} proc appendChild*(n, child: Node) {.importcpp.} proc removeChild*(n, child: Node) {.importcpp.} + proc remove*(child: Node) {.importcpp.} proc replaceChild*(n, newNode, oldNode: Node) {.importcpp.} proc insertBefore*(n, newNode, before: Node) {.importcpp.} proc getElementById*(d: Document, id: cstring): Element {.importcpp.} @@ -1149,7 +1444,6 @@ proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event)) proc alert*(w: Window, msg: cstring) proc back*(w: Window) proc blur*(w: Window) -proc captureEvents*(w: Window, eventMask: int) {.deprecated.} proc clearInterval*(w: Window, interval: ref Interval) proc clearTimeout*(w: Window, timeout: ref TimeOut) proc close*(w: Window) @@ -1169,7 +1463,6 @@ proc open*(w: Window, uri, windowname: cstring, properties: cstring = nil): Window proc print*(w: Window) proc prompt*(w: Window, text, default: cstring): cstring -proc releaseEvents*(w: Window, eventMask: int) {.deprecated.} proc resizeBy*(w: Window, x, y: int) proc resizeTo*(w: Window, x, y: int) proc routeEvent*(w: Window, event: Event) @@ -1190,7 +1483,9 @@ proc deleteData*(n: Node, start, len: int) proc focus*(e: Node) proc getAttribute*(n: Node, attr: cstring): cstring proc getAttributeNode*(n: Node, attr: cstring): Node +proc hasAttribute*(n: Node, attr: cstring): bool proc hasChildNodes*(n: Node): bool +proc normalize*(n: Node) proc insertData*(n: Node, position: int, data: cstring) proc removeAttribute*(n: Node, attr: cstring) proc removeAttributeNode*(n, attr: Node) @@ -1200,17 +1495,23 @@ proc setAttribute*(n: Node, name, value: cstring) proc setAttributeNode*(n: Node, attr: Node) proc querySelector*(n: Node, selectors: cstring): Element proc querySelectorAll*(n: Node, selectors: cstring): seq[Element] +proc compareDocumentPosition*(n: Node, otherNode:Node): int +proc lookupPrefix*(n: Node): cstring +proc lookupNamespaceURI*(n: Node): cstring +proc isDefaultNamespace*(n: Node): bool +proc contains*(n: Node): bool +proc isEqualNode*(n: Node): bool +proc isSameNode*(n: Node): bool # Document "methods" -proc captureEvents*(d: Document, eventMask: int) {.deprecated.} proc createAttribute*(d: Document, identifier: cstring): Node proc getElementsByName*(d: Document, name: cstring): seq[Element] proc getElementsByTagName*(d: Document, name: cstring): seq[Element] proc getElementsByClassName*(d: Document, name: cstring): seq[Element] +proc insertNode*(range: Range, node: Node) proc getSelection*(d: Document): Selection proc handleEvent*(d: Document, event: Event) proc open*(d: Document) -proc releaseEvents*(d: Document, eventMask: int) {.deprecated.} proc routeEvent*(d: Document, event: Event) proc write*(d: Document, text: cstring) proc writeln*(d: Document, text: cstring) @@ -1299,6 +1600,8 @@ proc now*(p: Performance): float # Selection "methods" proc removeAllRanges*(s: Selection) +proc deleteFromDocument*(s: Selection) +proc getRangeAt*(s: Selection, index: int): Range converter toString*(s: Selection): cstring proc `$`*(s: Selection): string = $(s.toString()) diff --git a/lib/js/jsre.nim b/lib/js/jsre.nim new file mode 100644 index 000000000..31cfa3d0b --- /dev/null +++ b/lib/js/jsre.nim @@ -0,0 +1,43 @@ +## Regular Expressions for the JavaScript target. +## * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + +type RegExp* {.importjs.} = object ## Regular Expressions for JavaScript target. + flags* {.importjs.}: cstring ## cstring that contains the flags of the RegExp object. + dotAll* {.importjs.}: bool ## Whether `.` matches newlines or not. + global* {.importjs.}: bool ## Whether to test against all possible matches in a string, or only against the first. + ignoreCase* {.importjs.}: bool ## Whether to ignore case while attempting a match in a string. + multiline* {.importjs.}: bool ## Whether to search in strings across multiple lines. + source* {.importjs.}: cstring ## The text of the pattern. + sticky* {.importjs.}: bool ## Whether the search is sticky. + unicode* {.importjs.}: bool ## Whether Unicode features are enabled. + lastIndex* {.importjs.}: cint ## Index at which to start the next match (read/write property). + input* {.importjs.}: cstring ## Read-only and modified on successful match. + lastMatch* {.importjs.}: cstring ## Ditto. + lastParen* {.importjs.}: cstring ## Ditto. + leftContext* {.importjs.}: cstring ## Ditto. + rightContext* {.importjs.}: cstring ## Ditto. + +func newRegExp*(pattern: cstring; flags: cstring): RegExp {.importjs: "new RegExp(@)".} + ## Creates a new RegExp object. + +func compile*(self: RegExp; pattern: cstring; flags: cstring) {.importjs: "#.compile(@)".} + ## Recompiles a regular expression during execution of a script. + +func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "#.exec(#)".} + ## Executes a search for a match in its string parameter. + +func test*(self: RegExp; pattern: cstring): bool {.importjs: "#.test(#)".} + ## Tests for a match in its string parameter. + +func toString*(self: RegExp): cstring {.importjs: "#.toString()".} + ## Returns a string representing the RegExp object. + + +runnableExamples: + let jsregex: RegExp = newRegExp(r"\s+", r"i") + jsregex.compile(r"\w+", r"i") + doAssert jsregex.test(r"nim javascript") + doAssert jsregex.exec(r"nim javascript") == @["nim".cstring] + doAssert jsregex.toString() == r"/\w+/i" + jsregex.compile(r"[0-9]", r"i") + doAssert jsregex.test(r"0123456789abcd") diff --git a/lib/nimhcr.nim b/lib/nimhcr.nim index 372ee1973..89bece6dc 100644 --- a/lib/nimhcr.nim +++ b/lib/nimhcr.nim @@ -1,3 +1,7 @@ +discard """ +batchable: false +""" + # # # Nim's Runtime Library diff --git a/lib/nimrtl.nim b/lib/nimrtl.nim index 7ec5eb981..a2a10c6bd 100644 --- a/lib/nimrtl.nim +++ b/lib/nimrtl.nim @@ -1,3 +1,7 @@ +discard """ +batchable: false +""" + # # # Nim's Runtime Library diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index ca6a3e05e..796c17d7d 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -342,7 +342,7 @@ proc nimNextToken(g: var GeneralTokenizer) = inc(pos) g.kind = gtNone g.length = pos - g.pos - if g.kind != gtEof and g.length <= 0: + if g.kind != gtEof and g.state != gtNone and g.length <= 0: assert false, "nimNextToken: produced an empty token" g.pos = pos diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 07cb3eccc..05b58c56b 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -313,7 +313,7 @@ proc defaultMsgHandler*(filename: string, line, col: int, msgkind: MsgKind, else: writeLine(stdout, message) proc defaultFindFile*(filename: string): string = - if existsFile(filename): result = filename + if fileExists(filename): result = filename else: result = "" proc newSharedState(options: RstParseOptions, @@ -328,7 +328,7 @@ proc newSharedState(options: RstParseOptions, proc findRelativeFile(p: RstParser; filename: string): string = result = p.filename.splitFile.dir / filename - if not existsFile(result): + if not fileExists(result): result = p.s.findFile(filename) proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string) = diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index a15442109..6330a6ba9 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1228,14 +1228,19 @@ else: let newLength = max(len(curList), InitCallbackListSize) var newList = newSeqOfCap[Callback](newLength) + var eventsExtinguished = false for cb in curList: + if eventsExtinguished: + newList.add(cb) + continue if not cb(fd): # Callback wants to be called again. newList.add(cb) # This callback has returned with EAGAIN, so we don't need to # call any other callbacks as they are all waiting for the same event # on the same fd. - break + # We do need to ensure they are called again though. + eventsExtinguished = true withData(selector, fd.int, fdData) do: # Descriptor is still present in the queue. @@ -1601,11 +1606,16 @@ else: p.selector.registerEvent(SelectEvent(ev), data) proc drain*(timeout = 500) = - ## Waits for completion events and processes them. Raises ``ValueError`` + ## Waits for completion of **all** events and processes them. Raises ``ValueError`` ## if there are no pending operations. In contrast to ``poll`` this - ## processes as many events as are available. - if runOnce(timeout) or hasPendingOperations(): - while hasPendingOperations() and runOnce(timeout): discard + ## processes as many events as are available until the timeout has elapsed. + var curTimeout = timeout + let start = now() + while hasPendingOperations(): + discard runOnce(curTimeout) + curTimeout -= (now() - start).inMilliseconds.int + if curTimeout < 0: + break proc poll*(timeout = 500) = ## Waits for completion events and processes them. Raises ``ValueError`` @@ -1635,16 +1645,6 @@ proc createAsyncNativeSocket*(domain: Domain = Domain.AF_INET, inheritable = defined(nimInheritHandles)): AsyncFD = createAsyncNativeSocketImpl(domain, sockType, protocol, inheritable) -proc newAsyncNativeSocket*(domain: cint, sockType: cint, - protocol: cint): AsyncFD {.deprecated: "use createAsyncNativeSocket instead".} = - createAsyncNativeSocketImpl(domain, sockType, protocol) - -proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD - {.deprecated: "use createAsyncNativeSocket instead".} = - createAsyncNativeSocketImpl(domain, sockType, protocol) - when defined(windows) or defined(nimdoc): proc bindToDomain(handle: SocketHandle, domain: Domain) = # Extracted into a separate proc, because connect() on Windows requires diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim index 132682b23..4e62ecd97 100644 --- a/lib/pure/asyncftpclient.nim +++ b/lib/pure/asyncftpclient.nim @@ -134,7 +134,7 @@ proc expectReply(ftp: AsyncFtpClient): Future[TaintedString] {.async.} = var line = await ftp.csock.recvLine() result = TaintedString(line) var count = 0 - while line[3] == '-': + while line.len > 3 and line[3] == '-': ## Multi-line reply. line = await ftp.csock.recvLine() string(result).add("\n" & line) @@ -231,7 +231,7 @@ proc listDirs*(ftp: AsyncFtpClient, dir = ""): Future[seq[string]] {.async.} = result = splitLines(await ftp.getLines()) -proc existsFile*(ftp: AsyncFtpClient, file: string): Future[bool] {.async.} = +proc fileExists*(ftp: AsyncFtpClient, file: string): Future[bool] {.async.} = ## Determines whether ``file`` exists. var files = await ftp.listDirs() for f in items(files): diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 621a4b00c..219ef6c67 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -180,8 +180,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # Extract the documentation comment from the original procedure declaration. # Note that we're not removing it from the body in order not to make this # transformation even more complex. - if prc.body.len > 1 and prc.body[0].kind == nnkCommentStmt: - outerProcBody.add(prc.body[0]) + let body2 = extractDocCommentsAndRunnables(prc.body) # -> var retFuture = newFuture[T]() var retFutureSym = genSym(nskVar, "retFuture") @@ -276,9 +275,10 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = (cast[type(f)](internalTmpFuture)).read() if procBody.kind != nnkEmpty: - result.body = quote: + body2.add quote do: `awaitDefinition` `outerProcBody` + result.body = body2 #echo(treeRepr(result)) #if prcName == "recvLineInto": diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 3fc1a0177..410310e29 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -853,40 +853,49 @@ proc sendTo*(socket: AsyncSocket, address: string, port: Port, data: string, else: raise newException(IOError, "Couldn't resolve address: " & address) -proc recvFrom*(socket: AsyncSocket, size: int, - flags = {SocketFlag.SafeDisconn}): - owned(Future[tuple[data: string, address: string, port: Port]]) +proc recvFrom*(socket: AsyncSocket, data: FutureVar[string], size: int, + address: FutureVar[string], port: FutureVar[Port], + flags = {SocketFlag.SafeDisconn}): owned(Future[int]) {.async, since: (1, 3).} = - ## Receives a datagram data from ``socket``, which must be at least of size - ## ``size``. Returned future will complete once one datagram has been received - ## and will return tuple with: data of packet received; and address and port - ## of datagram's sender. - ## + ## Receives a datagram data from ``socket`` into ``data``, which must be at + ## least of size ``size``. The address and port of datagram's sender will be + ## stored into ``address`` and ``port``, respectively. Returned future will + ## complete once one datagram has been received, and will return size of + ## packet received. + ## ## If an error occurs an OSError exception will be raised. ## ## This proc is normally used with connectionless sockets (UDP sockets). + ## + ## **Notes** + ## * ``data`` must be initialized to the length of ``size``. + ## * ``address`` must be initialized to 46 in length. template adaptRecvFromToDomain(domain: Domain) = var lAddr = sizeof(sAddr).SockLen - let fut = await recvFromInto(AsyncFD(getFd(socket)), cstring(data), size, - cast[ptr SockAddr](addr sAddr), addr lAddr, - flags) + result = await recvFromInto(AsyncFD(getFd(socket)), cstring(data.mget()), size, + cast[ptr SockAddr](addr sAddr), addr lAddr, + flags) - data.setLen(fut) + data.mget().setLen(result) + data.complete() + + getAddrString(cast[ptr SockAddr](addr sAddr), address.mget()) - result.data = data - result.address = getAddrString(cast[ptr SockAddr](addr sAddr)) + address.complete() when domain == AF_INET6: - result.port = ntohs(sAddr.sin6_port).Port + port.complete(ntohs(sAddr.sin6_port).Port) else: - result.port = ntohs(sAddr.sin_port).Port + port.complete(ntohs(sAddr.sin_port).Port) assert(socket.protocol != IPPROTO_TCP, "Cannot `recvFrom` on a TCP socket. Use `recv` or `recvInto` instead") assert(not socket.closed, "Cannot `recvFrom` on a closed socket") - - var data = newString(size) + assert(size == len(data.mget()), + "`date` was not initialized correctly. `size` != `len(data.mget())`") + assert(46 == len(address.mget()), + "`address` was not initialized correctly. 46 != `len(address.mget())`") case socket.domain of AF_INET6: @@ -898,6 +907,30 @@ proc recvFrom*(socket: AsyncSocket, size: int, else: raise newException(ValueError, "Unknown socket address family") +proc recvFrom*(socket: AsyncSocket, size: int, + flags = {SocketFlag.SafeDisconn}): + owned(Future[tuple[data: string, address: string, port: Port]]) + {.async, since: (1, 3).} = + ## Receives a datagram data from ``socket``, which must be at least of size + ## ``size``. Returned future will complete once one datagram has been received + ## and will return tuple with: data of packet received; and address and port + ## of datagram's sender. + ## + ## If an error occurs an OSError exception will be raised. + ## + ## This proc is normally used with connectionless sockets (UDP sockets). + var + data = newFutureVar[string]() + address = newFutureVar[string]() + port = newFutureVar[Port]() + + data.mget().setLen(size) + address.mget().setLen(46) + + let read = await recvFrom(socket, data, size, address, port, flags) + + result = (data.mget(), address.mget(), port.mget()) + when not defined(testing) and isMainModule: type TestCases = enum diff --git a/lib/pure/asyncstreams.nim b/lib/pure/asyncstreams.nim index 44e73003e..393262c4f 100644 --- a/lib/pure/asyncstreams.nim +++ b/lib/pure/asyncstreams.nim @@ -96,7 +96,7 @@ proc read*[T](future: FutureStream[T]): owned(Future[(bool, T)]) = if resFut.finished: return # We don't want this callback called again. - future.cb = nil + #future.cb = nil # The return value depends on whether the FutureStream has finished. var res: (bool, T) diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index dd7c4d477..734ab9171 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -32,12 +32,7 @@ import strutils, os, strtabs, cookies, uri export uri.encodeUrl, uri.decodeUrl -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) +include includes/decode_helpers proc addXmlChar(dest: var string, c: char) {.inline.} = case c @@ -93,40 +88,27 @@ proc getEncodedData(allowedMethods: set[RequestMethod]): string = iterator decodeData*(data: string): tuple[key, value: TaintedString] = ## Reads and decodes CGI data and yields the (name, value) pairs the ## data consists of. + proc parseData(data: string, i: int, field: var string): int = + result = i + while result < data.len: + case data[result] + of '%': add(field, decodePercent(data, result)) + of '+': add(field, ' ') + of '=', '&': break + else: add(field, data[result]) + inc(result) + var i = 0 var name = "" var value = "" # decode everything in one pass: while i < data.len: setLen(name, 0) # reuse memory - while i < data.len: - case data[i] - of '%': - var x = 0 - handleHexChar(data[i+1], x) - handleHexChar(data[i+2], x) - inc(i, 2) - add(name, chr(x)) - of '+': add(name, ' ') - of '=', '&': break - else: add(name, data[i]) - inc(i) + i = parseData(data, i, name) if i >= data.len or data[i] != '=': cgiError("'=' expected") inc(i) # skip '=' setLen(value, 0) # reuse memory - while i < data.len: - case data[i] - of '%': - var x = 0 - if i+2 < data.len: - handleHexChar(data[i+1], x) - handleHexChar(data[i+2], x) - inc(i, 2) - add(value, chr(x)) - of '+': add(value, ' ') - of '&', '\0': break - else: add(value, data[i]) - inc(i) + i = parseData(data, i, value) yield (name.TaintedString, value.TaintedString) if i < data.len: if data[i] == '&': inc(i) diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index d096874a3..8150563cc 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -67,9 +67,9 @@ const defaultInitialSize* = 4 template initImpl(result: typed, initialSize: int) = - assert isPowerOfTwo(initialSize) - result.mask = initialSize-1 - newSeq(result.data, initialSize) + let correctSize = nextPowerOfTwo(initialSize) + result.mask = correctSize-1 + newSeq(result.data, correctSize) template checkIfInitialized(deq: typed) = when compiles(defaultInitialSize): @@ -82,11 +82,6 @@ proc initDeque*[T](initialSize: int = 4): Deque[T] = ## Optionally, the initial capacity can be reserved via `initialSize` ## as a performance optimization. ## The length of a newly created deque will still be 0. - ## - ## ``initialSize`` must be a power of two (default: 4). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_. result.initImpl(initialSize) proc len*[T](deq: Deque[T]): int {.inline.} = diff --git a/lib/pure/collections/hashcommon.nim b/lib/pure/collections/hashcommon.nim index e998145e7..336dbc07d 100644 --- a/lib/pure/collections/hashcommon.nim +++ b/lib/pure/collections/hashcommon.nim @@ -30,17 +30,23 @@ proc nextTry(h, maxHash: Hash): Hash {.inline.} = result = (h + 1) and maxHash proc mustRehash[T](t: T): bool {.inline.} = + # If this is changed, make sure to synchronize it with `slotsNeeded` below assert(t.dataLen > t.counter) result = (t.dataLen * 2 < t.counter * 3) or (t.dataLen - t.counter < 4) -proc rightSize*(count: Natural): int {.inline.} = +proc slotsNeeded(count: Natural): int {.inline.} = + # Make sure to synchronize with `mustRehash` above + result = nextPowerOfTwo(count * 3 div 2 + 4) + +proc rightSize*(count: Natural): int {.inline, deprecated: "Deprecated since 1.4.0".} = + ## **Deprecated since Nim v1.4.0**, it is not needed anymore + ## because picking the correct size is done internally. + ## ## Return the value of ``initialSize`` to support ``count`` items. ## ## If more items are expected to be added, simply add that ## expected extra amount to the parameter before calling this. - # - # Make sure to synchronize with `mustRehash` - result = nextPowerOfTwo(count * 3 div 2 + 4) + result = count template rawGetKnownHCImpl() {.dirty.} = if t.dataLen == 0: diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index f101d508e..51d8ade85 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -527,7 +527,8 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos = 0) = assert dest == outcome var j = len(dest) - 1 - var i = len(dest) + len(src) - 1 + var i = j + len(src) + if i == j: return dest.setLen(i + 1) # Move items after `pos` to the end of the sequence. diff --git a/lib/pure/collections/setimpl.nim b/lib/pure/collections/setimpl.nim index d798cbcb3..20da6b6c2 100644 --- a/lib/pure/collections/setimpl.nim +++ b/lib/pure/collections/setimpl.nim @@ -16,12 +16,12 @@ template dataLen(t): untyped = len(t.data) include hashcommon template initImpl(s: typed, size: int) = - assert isPowerOfTwo(size) + let correctSize = slotsNeeded(size) when s is OrderedSet: s.first = -1 s.last = -1 s.counter = 0 - newSeq(s.data, size) + newSeq(s.data, correctSize) template rawInsertImpl() {.dirty.} = if data.len == 0: diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 2b270c2cb..b019da2a7 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -94,11 +94,6 @@ include setimpl proc init*[A](s: var HashSet[A], initialSize = defaultInitialSize) = ## Initializes a hash set. ## - ## The `initialSize` parameter needs to be a power of two (default: 64). - ## If you need to accept runtime values for this, you can use - ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo,int>`_ or - ## `rightSize proc <#rightSize,Natural>`_ from this module. - ## ## Starting from Nim v0.20, sets are initialized by default and it is ## not necessary to call this function explicitly. ## @@ -222,7 +217,7 @@ proc toHashSet*[A](keys: openArray[A]): HashSet[A] = assert len(b) == 5 ## b == {'a', 'b', 'c', 'd', 'r'} - result = initHashSet[A](rightSize(keys.len)) + result = initHashSet[A](keys.len) for key in items(keys): result.incl(key) iterator items*[A](s: HashSet[A]): A = @@ -628,11 +623,6 @@ template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = proc init*[A](s: var OrderedSet[A], initialSize = defaultInitialSize) = ## Initializes an ordered hash set. ## - ## The `initialSize` parameter needs to be a power of two (default: 64). - ## If you need to accept runtime values for this, you can use - ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo,int>`_ or - ## `rightSize proc <#rightSize,Natural>`_ from this module. - ## ## Starting from Nim v0.20, sets are initialized by default and it is ## not necessary to call this function explicitly. ## @@ -685,7 +675,7 @@ proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] = assert len(b) == 5 ## b == {'a', 'b', 'r', 'c', 'd'} # different than in HashSet - result = initOrderedSet[A](rightSize(keys.len)) + result = initOrderedSet[A](keys.len) for key in items(keys): result.incl(key) proc contains*[A](s: OrderedSet[A], key: A): bool = @@ -980,7 +970,7 @@ when isMainModule and not defined(release): block toSeqAndString: var a = toHashSet([2, 7, 5]) - var b = initHashSet[int](rightSize(a.len)) + var b = initHashSet[int](a.len) for x in [2, 7, 5]: b.incl(x) assert($a == $b) #echo a @@ -1098,20 +1088,6 @@ when isMainModule and not defined(release): b.incl(2) assert b.len == 1 - block: - type FakeTable = object - dataLen: int - counter: int - countDeleted: int - - var t: FakeTable - for i in 0 .. 32: - var s = rightSize(i) - t.dataLen = s - t.counter = i - doAssert s > i and not mustRehash(t), - "performance issue: rightSize() will not elide enlarge() at: " & $i - block missingOrExcl: var s = toOrderedSet([2, 3, 6, 7]) assert s.missingOrExcl(4) == true diff --git a/lib/pure/collections/sharedlist.nim b/lib/pure/collections/sharedlist.nim index f9182acce..790529b79 100644 --- a/lib/pure/collections/sharedlist.nim +++ b/lib/pure/collections/sharedlist.nim @@ -94,10 +94,4 @@ proc deinitSharedList*[A](t: var SharedList[A]) = clear(t) deinitLock t.lock -proc initSharedList*[A](): SharedList[A] {.deprecated: "use 'init' instead".} = - ## This is not posix compliant, may introduce undefined behavior. - initLock result.lock - result.head = nil - result.tail = nil - {.pop.} diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index 23b653c82..cbd922db7 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -207,15 +207,11 @@ proc len*[A, B](t: var SharedTable[A, B]): int = withLock t: result = t.counter -proc init*[A, B](t: var SharedTable[A, B], initialSize = 64) = +proc init*[A, B](t: var SharedTable[A, B], initialSize = 32) = ## creates a new hash table that is empty. ## ## This proc must be called before any other usage of `t`. - ## - ## `initialSize` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` proc from this module. - assert isPowerOfTwo(initialSize) + let initialSize = slotsNeeded(initialSize) t.counter = 0 t.dataLen = initialSize t.data = cast[KeyValuePairSeq[A, B]](allocShared0( @@ -225,13 +221,3 @@ proc init*[A, B](t: var SharedTable[A, B], initialSize = 64) = proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) = deallocShared(t.data) deinitLock t.lock - -proc initSharedTable*[A, B](initialSize = 64): SharedTable[A, B] {.deprecated: - "use 'init' instead".} = - ## This is not posix compliant, may introduce undefined behavior. - assert isPowerOfTwo(initialSize) - result.counter = 0 - result.dataLen = initialSize - result.data = cast[KeyValuePairSeq[A, B]](allocShared0( - sizeof(KeyValuePair[A, B]) * initialSize)) - initLock result.lock diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 47c14af93..b9d7c70d9 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -121,12 +121,12 @@ template ctAnd(a, b): bool = else: false template initImpl(result: typed, size: int) = + let correctSize = slotsNeeded(size) when ctAnd(declared(SharedTable), type(result) is SharedTable): - init(result, size) + init(result, correctSize) else: - assert isPowerOfTwo(size) result.counter = 0 - newSeq(result.data, size) + newSeq(result.data, correctSize) when compiles(result.first): result.first = -1 result.last = -1 diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 2ea58ce1f..cf864c640 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -239,7 +239,7 @@ type ## <#newTable,int>`_. const - defaultInitialSize* = 64 + defaultInitialSize* = 32 # ------------------------------ helpers --------------------------------- @@ -288,12 +288,6 @@ proc enlarge[A, B](t: var Table[A, B]) = proc initTable*[A, B](initialSize = defaultInitialSize): Table[A, B] = ## Creates a new hash table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## Starting from Nim v0.20, tables are initialized by default and it is ## not necessary to call this function explicitly. ## @@ -335,7 +329,7 @@ proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = let b = toTable(a) assert b == {'a': 5, 'b': 9}.toTable - result = initTable[A, B](rightSize(pairs.len)) + result = initTable[A, B](pairs.len) for key, val in items(pairs): result[key] = val proc `[]`*[A, B](t: Table[A, B], key: A): B = @@ -780,12 +774,6 @@ iterator allValues*[A, B](t: Table[A, B]; key: A): B = proc newTable*[A, B](initialSize = defaultInitialSize): <//>TableRef[A, B] = ## Creates a new ref hash table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## See also: ## * `newTable proc<#newTable,openArray[]>`_ for creating a `TableRef` ## from a collection of `(key, value)` pairs @@ -1260,12 +1248,6 @@ template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = proc initOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTable[A, B] = ## Creates a new ordered hash table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## Starting from Nim v0.20, tables are initialized by default and it is ## not necessary to call this function explicitly. ## @@ -1309,7 +1291,7 @@ proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = let b = toOrderedTable(a) assert b == {'a': 5, 'b': 9}.toOrderedTable - result = initOrderedTable[A, B](rightSize(pairs.len)) + result = initOrderedTable[A, B](pairs.len) for key, val in items(pairs): result[key] = val proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = @@ -1771,12 +1753,6 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = proc newOrderedTable*[A, B](initialSize = defaultInitialSize): <//>OrderedTableRef[A, B] = ## Creates a new ordered ref hash table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## See also: ## * `newOrderedTable proc<#newOrderedTable,openArray[]>`_ for creating ## an `OrderedTableRef` from a collection of `(key, value)` pairs @@ -1803,7 +1779,7 @@ proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): <//>OrderedTableRef[A, B] let b = newOrderedTable(a) assert b == {'a': 5, 'b': 9}.newOrderedTable - result = newOrderedTable[A, B](rightSize(pairs.len)) + result = newOrderedTable[A, B](pairs.len) for key, val in items(pairs): result.add(key, val) @@ -2251,12 +2227,6 @@ proc inc*[A](t: var CountTable[A], key: A, val: Positive = 1) proc initCountTable*[A](initialSize = defaultInitialSize): CountTable[A] = ## Creates a new count table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## Starting from Nim v0.20, tables are initialized by default and it is ## not necessary to call this function explicitly. ## @@ -2269,7 +2239,7 @@ proc initCountTable*[A](initialSize = defaultInitialSize): CountTable[A] = proc toCountTable*[A](keys: openArray[A]): CountTable[A] = ## Creates a new count table with every member of a container ``keys`` ## having a count of how many times it occurs in that container. - result = initCountTable[A](rightSize(keys.len)) + result = initCountTable[A](keys.len) for key in items(keys): result.inc(key) proc `[]`*[A](t: CountTable[A], key: A): int = @@ -2617,12 +2587,6 @@ proc inc*[A](t: CountTableRef[A], key: A, val = 1) proc newCountTable*[A](initialSize = defaultInitialSize): <//>CountTableRef[A] = ## Creates a new ref count table that is empty. ## - ## ``initialSize`` must be a power of two (default: 64). - ## If you need to accept runtime values for this you could use the - ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the - ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ - ## from this module. - ## ## See also: ## * `newCountTable proc<#newCountTable,openArray[A]>`_ for creating ## a `CountTableRef` from a collection @@ -2634,7 +2598,7 @@ proc newCountTable*[A](initialSize = defaultInitialSize): <//>CountTableRef[A] = proc newCountTable*[A](keys: openArray[A]): <//>CountTableRef[A] = ## Creates a new ref count table with every member of a container ``keys`` ## having a count of how many times it occurs in that container. - result = newCountTable[A](rightSize(keys.len)) + result = newCountTable[A](keys.len) for key in items(keys): result.inc(key) proc `[]`*[A](t: CountTableRef[A], key: A): int = @@ -2670,14 +2634,14 @@ proc inc*[A](t: CountTableRef[A], key: A, val = 1) = doAssert a == newCountTable("aaabbbbbbbbbbb") t[].inc(key, val) -proc smallest*[A](t: CountTableRef[A]): (A, int) = +proc smallest*[A](t: CountTableRef[A]): tuple[key: A, val: int] = ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) ## ## See also: ## * `largest proc<#largest,CountTableRef[A]>`_ t[].smallest -proc largest*[A](t: CountTableRef[A]): (A, int) = +proc largest*[A](t: CountTableRef[A]): tuple[key: A, val: int] = ## Returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) ## ## See also: diff --git a/lib/pure/colors.nim b/lib/pure/colors.nim index d57e309e9..dbcb1b4a2 100644 --- a/lib/pure/colors.nim +++ b/lib/pure/colors.nim @@ -452,7 +452,7 @@ proc parseColor*(name: string): Color = assert parseColor(b) == Color(0x01_79_fc) doAssertRaises(ValueError): discard parseColor(c) - if name[0] == '#': + if name.len > 0 and name[0] == '#': result = Color(parseHexInt(name)) else: var idx = binarySearch(colorNames, name, colorNameCmp) @@ -472,6 +472,7 @@ proc isColor*(name: string): bool = assert b.isColor assert not c.isColor + if name.len == 0: return false if name[0] == '#': for i in 1 .. name.len-1: if name[i] notin {'0'..'9', 'a'..'f', 'A'..'F'}: return false diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index 2abcafb80..f0269f526 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -208,7 +208,7 @@ proc finished(fv: var FlowVarBaseObj) = # the worker thread waits for "data" to be set to nil before shutting down owner.data = nil -proc `=destroy`[T](fv: var FlowVarObj[T]) = +proc `=destroy`[T](fv: var FlowVarObj[T]) = finished(fv) `=destroy`(fv.blob) @@ -321,15 +321,15 @@ var currentPoolSize: int maxPoolSize = MaxThreadPoolSize minPoolSize = 4 - gSomeReady : Semaphore + gSomeReady: Semaphore readyWorker: ptr Worker # A workaround for recursion deadlock issue # https://github.com/nim-lang/Nim/issues/4597 var numSlavesLock: Lock - numSlavesRunning {.guard: numSlavesLock}: int - numSlavesWaiting {.guard: numSlavesLock}: int + numSlavesRunning {.guard: numSlavesLock.}: int + numSlavesWaiting {.guard: numSlavesLock.}: int isSlave {.threadvar.}: bool numSlavesLock.initLock @@ -464,7 +464,7 @@ proc pinnedSpawn*(id: ThreadId; call: sink typed): void {.magic: "Spawn".} ## ``call`` has to be proc call ``p(...)`` where ``p`` is gcsafe and has a ## return type that is either ``void`` or compatible with ``FlowVar[T]``. -template spawnX*(call): void = +template spawnX*(call) = ## Spawns a new task if a CPU core is ready, otherwise executes the ## call in the calling thread. ## diff --git a/lib/pure/includes/decode_helpers.nim b/lib/pure/includes/decode_helpers.nim new file mode 100644 index 000000000..74fe37d07 --- /dev/null +++ b/lib/pure/includes/decode_helpers.nim @@ -0,0 +1,24 @@ +# Include file that implements 'decodePercent' and friends. Do not import it! + +proc handleHexChar(c: char, x: var int, f: var bool) {.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: f = true + +proc decodePercent(s: string, i: var int): char = + ## Converts `%xx` hexadecimal to the charracter with ordinal number `xx`. + ## + ## If `xx` is not a valid hexadecimal value, it is left intact: only the + ## leading `%` is returned as-is, and `xx` characters will be processed in the + ## next step (e.g. in `uri.decodeUrl`) as regular characters. + result = '%' + if i+2 < s.len: + var x = 0 + var failed = false + handleHexChar(s[i+1], x, failed) + handleHexChar(s[i+2], x, failed) + if not failed: + result = chr(x) + inc(i, 2) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 2637fdf9d..fa6285a04 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -287,6 +287,7 @@ proc substituteLog*(frmt: string, level: Level, runnableExamples: doAssert substituteLog(defaultFmtStr, lvlInfo, "a message") == "INFO a message" doAssert substituteLog("$levelid - ", lvlError, "an error") == "E - an error" + doAssert substituteLog("$levelid", lvlDebug, "error") == "Derror" var msgLen = 0 for arg in args: msgLen += arg.len @@ -300,7 +301,7 @@ proc substituteLog*(frmt: string, level: Level, inc(i) var v = "" let app = when defined(js): "" else: getAppFilename() - while frmt[i] in IdentChars: + while i < frmt.len and frmt[i] in IdentChars: v.add(toLowerAscii(frmt[i])) inc(i) case v @@ -364,7 +365,12 @@ method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) = let ln = substituteLog(logger.fmtStr, level, args) when defined(js): let cln: cstring = ln - {.emit: "console.log(`cln`);".} + case level + of lvlDebug: {.emit: "console.debug(`cln`);".} + of lvlInfo: {.emit: "console.info(`cln`);".} + of lvlWarn: {.emit: "console.warn(`cln`);".} + of lvlError: {.emit: "console.error(`cln`);".} + else: {.emit: "console.log(`cln`);".} else: try: var handle = stdout @@ -504,7 +510,6 @@ when not defined(js): # ------ proc countLogLines(logger: RollingFileLogger): int = - result = 0 let fp = open(logger.baseName, fmRead) for line in fp.lines(): result.inc() @@ -531,7 +536,7 @@ when not defined(js): mode: FileMode = fmReadWrite, levelThreshold = lvlAll, fmtStr = defaultFmtStr, - maxLines = 1000, + maxLines: Positive = 1000, bufSize: int = -1): RollingFileLogger = ## Creates a new `RollingFileLogger<#RollingFileLogger>`_. ## diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 67e24eedc..49a1192df 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -461,6 +461,40 @@ proc getAddrString*(sockAddr: ptr SockAddr): string = return "unix" raise newException(IOError, "Unknown socket family in getAddrString") +proc getAddrString*(sockAddr: ptr SockAddr, strAddress: var string) = + ## Stores in ``strAddress`` the string representation of the address inside + ## ``sockAddr`` + ## + ## **Note** + ## * ``strAddress`` must be initialized to 46 in length. + assert(46 == len(strAddress), + "`strAddress` was not initialized correctly. 46 != `len(strAddress)`") + if sockAddr.sa_family.cint == nativeAfInet: + let addr4 = addr cast[ptr Sockaddr_in](sockAddr).sin_addr + when not useWinVersion: + if posix.inet_ntop(posix.AF_INET, addr4, addr strAddress[0], + strAddress.len.int32) == nil: + raiseOSError(osLastError()) + else: + if winlean.inet_ntop(winlean.AF_INET, addr4, addr strAddress[0], + strAddress.len.int32) == nil: + raiseOSError(osLastError()) + elif sockAddr.sa_family.cint == nativeAfInet6: + let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr + when not useWinVersion: + if posix.inet_ntop(posix.AF_INET6, addr6, addr strAddress[0], + strAddress.len.int32) == nil: + raiseOSError(osLastError()) + if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0: + strAddress = strAddress.substr("::ffff:".len) + else: + if winlean.inet_ntop(winlean.AF_INET6, addr6, addr strAddress[0], + strAddress.len.int32) == nil: + raiseOSError(osLastError()) + else: + raise newException(IOError, "Unknown socket family in getAddrString") + setLen(strAddress, len(cstring(strAddress))) + when defined(posix) and not defined(nimdoc): proc makeUnixAddr*(path: string): Sockaddr_un = result.sun_family = AF_UNIX.TSa_Family @@ -622,7 +656,7 @@ proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) = proc selectRead*(readfds: var seq[SocketHandle], timeout = 500): int = ## When a socket in ``readfds`` is ready to be read from then a non-zero ## value will be returned specifying the count of the sockets which can be - ## read from. The sockets which can be read from will also be removed + ## read from. The sockets which cannot be read from will also be removed ## from ``readfds``. ## ## ``timeout`` is specified in milliseconds and ``-1`` can be specified for @@ -644,7 +678,7 @@ proc selectWrite*(writefds: var seq[SocketHandle], timeout = 500): int {.tags: [ReadIOEffect].} = ## When a socket in ``writefds`` is ready to be written to then a non-zero ## value will be returned specifying the count of the sockets which can be - ## written to. The sockets which can be written to will also be removed + ## written to. The sockets which cannot be written to will also be removed ## from ``writefds``. ## ## ``timeout`` is specified in milliseconds and ``-1`` can be specified for diff --git a/lib/pure/net.nim b/lib/pure/net.nim index c1896dc1c..7aeffbc35 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -513,10 +513,10 @@ when defineSsl: # http://simplestcodings.blogspot.co.uk/2010/08/secure-server-client-using-openssl-in-c.html proc loadCertificates(ctx: SslCtx, certFile, keyFile: string) = - if certFile != "" and not existsFile(certFile): + if certFile != "" and not fileExists(certFile): raise newException(system.IOError, "Certificate file could not be found: " & certFile) - if keyFile != "" and not existsFile(keyFile): + if keyFile != "" and not fileExists(keyFile): raise newException(system.IOError, "Key file could not be found: " & keyFile) if certFile != "": @@ -614,7 +614,7 @@ when defineSsl: if verifyMode != CVerifyNone: # Use the caDir and caFile parameters if set if caDir != "" or caFile != "": - if newCTX.SSL_CTX_load_verify_locations(caDir, caFile) != 0: + if newCTX.SSL_CTX_load_verify_locations(caFile, caDir) != 0: raise newException(IOError, "Failed to load SSL/TLS CA certificate(s).") else: diff --git a/lib/pure/os.nim b/lib/pure/os.nim index dcb63458b..8bbceddeb 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1097,14 +1097,14 @@ when defined(windows) and not weirdTarget: result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or f.cFileName[1].int == dot and f.cFileName[2].int == 0) -proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", +proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], noNimJs.} = ## Returns true if `filename` exists and is a regular file or symlink. ## ## Directories, device files, named pipes and sockets return false. ## ## See also: - ## * `existsDir proc <#existsDir,string>`_ + ## * `dirExists proc <#dirExists,string>`_ ## * `symlinkExists proc <#symlinkExists,string>`_ when defined(windows): when useWinUnicode: @@ -1117,13 +1117,13 @@ proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", var res: Stat return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) -proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], +proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], noNimJs.} = ## Returns true if the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. ## ## See also: - ## * `existsFile proc <#existsFile,string>`_ + ## * `fileExists proc <#fileExists,string>`_ ## * `symlinkExists proc <#symlinkExists,string>`_ when defined(windows): when useWinUnicode: @@ -1143,8 +1143,8 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", ## regardless of whether the link points to a directory or file. ## ## See also: - ## * `existsFile proc <#existsFile,string>`_ - ## * `existsDir proc <#existsDir,string>`_ + ## * `fileExists proc <#fileExists,string>`_ + ## * `dirExists proc <#dirExists,string>`_ when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, link) @@ -1156,21 +1156,14 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", var res: Stat return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) -proc fileExists*(filename: string): bool {.inline, noNimJs.} = - ## Alias for `existsFile proc <#existsFile,string>`_. - ## - ## See also: - ## * `existsDir proc <#existsDir,string>`_ - ## * `symlinkExists proc <#symlinkExists,string>`_ - existsFile(filename) -proc dirExists*(dir: string): bool {.inline, noNimJs.} = - ## Alias for `existsDir proc <#existsDir,string>`_. - ## - ## See also: - ## * `existsFile proc <#existsFile,string>`_ - ## * `symlinkExists proc <#symlinkExists,string>`_ - existsDir(dir) +when not defined(nimscript): + when not defined(js): # `noNimJs` doesn't work with templates, this should improve. + template existsFile*(args: varargs[untyped]): untyped {.deprecated: "use fileExists".} = + fileExists(args) + template existsDir*(args: varargs[untyped]): untyped {.deprecated: "use dirExists".} = + dirExists(args) + # {.deprecated: [existsFile: fileExists].} # pending bug #14819; this would avoid above mentioned issue when not defined(windows) and not weirdTarget: proc checkSymlink(path: string): bool = @@ -1200,7 +1193,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; template checkCurrentDir() = for ext in extensions: result = addFileExt(exe, ext) - if existsFile(result): return + if fileExists(result): return when defined(posix): if '/' in exe: checkCurrentDir() else: @@ -1216,7 +1209,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; var x = expandTilde(candidate) / exe for ext in extensions: var x = addFileExt(x, ext) - if existsFile(x): + if fileExists(x): when not defined(windows): while followSymlinks: # doubles as if here if x.checkSymlink: @@ -2027,7 +2020,7 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", # way of retrieving the true filename for x in walkFiles(result): result = x - if not existsFile(result) and not existsDir(result): + if not fileExists(result) and not dirExists(result): # consider using: `raiseOSError(osLastError(), result)` raise newException(OSError, "file '" & result & "' does not exist") else: @@ -2324,7 +2317,7 @@ proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", result = not rawCreateDir(dir) if result: # path already exists - need to check that it is indeed a directory - if not existsDir(dir): + if not dirExists(dir): raise newException(IOError, "Failed to create '" & dir & "'") proc createDir*(dir: string) {.rtl, extern: "nos$1", @@ -2893,7 +2886,7 @@ when not weirdTarget and defined(openbsd): # search in path for p in split(string(getEnv("PATH")), {PathSep}): var x = joinPath(p, exePath) - if existsFile(x): + if fileExists(x): return expandFilename(x) else: result = "" @@ -2908,7 +2901,7 @@ when not (defined(windows) or defined(macosx) or weirdTarget): # iterate over any path in the $PATH environment variable for p in split(string(getEnv("PATH")), {PathSep}): var x = joinPath(p, result) - if existsFile(x): return x + if fileExists(x): return x else: result = "" diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 3e3391d52..5113695d8 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -18,7 +18,8 @@ include "system/inclrtl" import - strutils, os, strtabs, streams, cpuinfo + strutils, os, strtabs, streams, cpuinfo, streamwrapper, + std/private/since export quoteShell, quoteShellWindows, quoteShellPosix @@ -237,6 +238,10 @@ proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} proc outputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} ## Returns ``p``'s output stream for reading from. ## + ## You cannot perform peek/write/setOption operations to this stream. + ## Use `peekableOutputStream proc <#peekableOutputStream,Process>`_ + ## if you need to peek stream. + ## ## **WARNING**: The returned `Stream` should not be closed manually as it ## is closed when closing the Process ``p``. ## @@ -247,6 +252,10 @@ proc outputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} proc errorStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} ## Returns ``p``'s error stream for reading from. ## + ## You cannot perform peek/write/setOption operations to this stream. + ## Use `peekableErrorStream proc <#peekableErrorStream,Process>`_ + ## if you need to peek stream. + ## ## **WARNING**: The returned `Stream` should not be closed manually as it ## is closed when closing the Process ``p``. ## @@ -254,6 +263,30 @@ proc errorStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} ## * `inputStream proc <#inputStream,Process>`_ ## * `outputStream proc <#outputStream,Process>`_ +proc peekableOutputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [], since: (1, 3).} + ## Returns ``p``'s output stream for reading from. + ## + ## You can peek returned stream. + ## + ## **WARNING**: The returned `Stream` should not be closed manually as it + ## is closed when closing the Process ``p``. + ## + ## See also: + ## * `outputStream proc <#outputStream,Process>`_ + ## * `peekableErrorStream proc <#peekableErrorStream,Process>`_ + +proc peekableErrorStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [], since: (1, 3).} + ## Returns ``p``'s error stream for reading from. + ## + ## You can run peek operation to returned stream. + ## + ## **WARNING**: The returned `Stream` should not be closed manually as it + ## is closed when closing the Process ``p``. + ## + ## See also: + ## * `errorStream proc <#errorStream,Process>`_ + ## * `peekableOutputStream proc <#peekableOutputStream,Process>`_ + proc inputHandle*(p: Process): FileHandle {.rtl, extern: "nosp$1", tags: [].} = ## Returns ``p``'s input file handle for writing to. @@ -737,6 +770,18 @@ when defined(Windows) and not defined(useNimRtl): p.errStream = newFileHandleStream(p.errHandle) result = p.errStream + proc peekableOutputStream(p: Process): Stream = + streamAccess(p) + if p.outStream == nil: + p.outStream = newFileHandleStream(p.outHandle).newPipeOutStream + result = p.outStream + + proc peekableErrorStream(p: Process): Stream = + streamAccess(p) + if p.errStream == nil: + p.errStream = newFileHandleStream(p.errHandle).newPipeOutStream + result = p.errStream + proc execCmd(command: string): int = var si: STARTUPINFO @@ -1360,28 +1405,40 @@ elif not defined(useNimRtl): p.exitStatus = status result = exitStatusLikeShell(status) - proc createStream(stream: var owned(Stream), handle: var FileHandle, - fileMode: FileMode) = + proc createStream(handle: var FileHandle, + fileMode: FileMode): owned FileStream = var f: File if not open(f, handle, fileMode): raiseOSError(osLastError()) - stream = newFileStream(f) + return newFileStream(f) proc inputStream(p: Process): Stream = streamAccess(p) if p.inStream == nil: - createStream(p.inStream, p.inHandle, fmWrite) + p.inStream = createStream(p.inHandle, fmWrite) return p.inStream proc outputStream(p: Process): Stream = streamAccess(p) if p.outStream == nil: - createStream(p.outStream, p.outHandle, fmRead) + p.outStream = createStream(p.outHandle, fmRead) return p.outStream proc errorStream(p: Process): Stream = streamAccess(p) if p.errStream == nil: - createStream(p.errStream, p.errHandle, fmRead) + p.errStream = createStream(p.errHandle, fmRead) + return p.errStream + + proc peekableOutputStream(p: Process): Stream = + streamAccess(p) + if p.outStream == nil: + p.outStream = createStream(p.outHandle, fmRead).newPipeOutStream + return p.outStream + + proc peekableErrorStream(p: Process): Stream = + streamAccess(p) + if p.errStream == nil: + p.errStream = createStream(p.errHandle, fmRead).newPipeOutStream return p.errStream proc csystem(cmd: cstring): cint {.nodecl, importc: "system", @@ -1446,6 +1503,7 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = { ## A convenience proc that runs the `command`, and returns its `output` and ## `exitCode`. `env` and `workingDir` params behave as for `startProcess`. ## If `input.len > 0`, it is passed as stdin. + ## ## Note: this could block if `input.len` is greater than your OS's maximum ## pipe buffer size. ## @@ -1456,15 +1514,17 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = { ## * `execProcess proc ## <#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_ ## - runnableExamples: - var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4") - import strutils, strtabs - stripLineEnd(result[0]) ## portable way to remove trailing newline, if any - doAssert result == ("12", 0) - doAssert execCmdEx("ls --nonexistant").exitCode != 0 - when defined(posix): - assert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0) - assert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0) + ## Example: + ## + ## .. code-block:: Nim + ## var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4") + ## import strutils, strtabs + ## stripLineEnd(result[0]) ## portable way to remove trailing newline, if any + ## doAssert result == ("12", 0) + ## doAssert execCmdEx("ls --nonexistant").exitCode != 0 + ## when defined(posix): + ## assert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0) + ## assert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0) when (NimMajor, NimMinor, NimPatch) < (1, 3, 5): doAssert input.len == 0 diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index a95a5b48d..94df5ea40 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -280,8 +280,8 @@ proc handleShortOption(p: var OptParser; cmd: string) = while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i) p.inShortState = false - if i < cmd.len and cmd[i] in {':', '='} or - card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal: + if i < cmd.len and (cmd[i] in {':', '='} or + card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal): if i < cmd.len and cmd[i] in {':', '='}: inc(i) p.inShortState = false diff --git a/lib/pure/ssl_certs.nim b/lib/pure/ssl_certs.nim index 806316b02..d04244307 100644 --- a/lib/pure/ssl_certs.nim +++ b/lib/pure/ssl_certs.nim @@ -79,9 +79,9 @@ iterator scanSSLCertificates*(useEnvVars = false): string = when not defined(haiku): for p in certificate_paths: if p.endsWith(".pem") or p.endsWith(".crt"): - if existsFile(p): + if fileExists(p): yield p - elif existsDir(p): + elif dirExists(p): for fn in joinPath(p, "*").walkFiles(): yield fn else: diff --git a/lib/pure/streamwrapper.nim b/lib/pure/streamwrapper.nim new file mode 100644 index 000000000..b99982f1b --- /dev/null +++ b/lib/pure/streamwrapper.nim @@ -0,0 +1,117 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2020 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements stream wrapper. +## +## **Since** version 1.2. + +import deques, streams + +type + PipeOutStream*[T] = ref object of T + # When stream peek operation is called, it reads from base stream + # type using `baseReadDataImpl` and stores the content to this buffer. + # Next stream read operation returns data in the buffer so that previus peek + # operation looks like didn't changed read positon. + # When stream read operation that returns N byte data is called and the size is smaller than buffer size, + # first N elements are removed from buffer. + # Deque type can do such operation more efficiently than seq type. + buffer: Deque[char] + baseReadLineImpl: typeof(StreamObj.readLineImpl) + baseReadDataImpl: typeof(StreamObj.readDataImpl) + +proc posReadLine[T](s: Stream, line: var TaintedString): bool = + var s = PipeOutStream[T](s) + assert s.baseReadLineImpl != nil + + let n = s.buffer.len + line.string.setLen(0) + for i in 0..<n: + var c = s.buffer.popFirst + if c == '\c': + c = readChar(s) + return true + elif c == '\L': return true + elif c == '\0': + return line.len > 0 + line.string.add(c) + + var line2: string + result = s.baseReadLineImpl(s, line2) + line.add line2 + +proc posReadData[T](s: Stream, buffer: pointer, bufLen: int): int = + var s = PipeOutStream[T](s) + assert s.baseReadDataImpl != nil + + let + dest = cast[ptr UncheckedArray[char]](buffer) + n = min(s.buffer.len, bufLen) + result = n + for i in 0..<n: + dest[i] = s.buffer.popFirst + if bufLen > n: + result += s.baseReadDataImpl(s, addr dest[n], bufLen - n) + +proc posReadDataStr[T](s: Stream, buffer: var string, slice: Slice[int]): int = + posReadData[T](s, addr buffer[slice.a], slice.len) + +proc posPeekData[T](s: Stream, buffer: pointer, bufLen: int): int = + var s = PipeOutStream[T](s) + assert s.baseReadDataImpl != nil + + let + dest = cast[ptr UncheckedArray[char]](buffer) + n = min(s.buffer.len, bufLen) + + result = n + for i in 0..<n: + dest[i] = s.buffer[i] + + if bufLen > n: + let + newDataNeeded = bufLen - n + numRead = s.baseReadDataImpl(s, addr dest[n], newDataNeeded) + result += numRead + for i in 0..<numRead: + s.buffer.addLast dest[n + i] + +proc newPipeOutStream*[T](s: sink (ref T)): owned PipeOutStream[T] = + ## Wrap pipe for reading with PipeOutStream so that you can use peek* procs and generate runtime error + ## when setPosition/getPosition is called or write operation is performed. + ## + ## Example: + ## + ## .. code-block:: Nim + ## import osproc, streamwrapper + ## var + ## p = startProcess(exePath) + ## outStream = p.outputStream().newPipeOutStream() + ## echo outStream.peekChar + ## p.close() + + assert s.readDataImpl != nil + + new(result) + for dest, src in fields((ref T)(result)[], s[]): + dest = src + wasMoved(s[]) + if result.readLineImpl != nil: + result.baseReadLineImpl = result.readLineImpl + result.readLineImpl = posReadLine[T] + result.baseReadDataImpl = result.readDataImpl + result.readDataImpl = posReadData[T] + result.readDataStrImpl = posReadDataStr[T] + result.peekDataImpl = posPeekData[T] + + # Set nil to anything you may not call. + result.setPositionImpl = nil + result.getPositionImpl = nil + result.writeDataImpl = nil + result.flushImpl = nil diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 5f7ef380d..c37b6b1c0 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -63,7 +63,6 @@ Formatting floats .. code-block:: nim import strformat - doAssert fmt"{-12345:08}" == "-0012345" doAssert fmt"{-1:3}" == " -1" doAssert fmt"{-1:03}" == "-01" @@ -81,6 +80,52 @@ Formatting floats doAssert fmt"{123.456:13e}" == " 1.234560e+02" +Debugging strings +================= + +``fmt"{expr=}"`` expands to ``fmt"expr={expr}"`` namely the text of the expression, +an equal sign and the results of evaluated expression. + +.. code-block:: nim + + import strformat + doAssert fmt"{123.456=}" == "123.456=123.456" + doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456" + + let x = "hello" + doAssert fmt"{x=}" == "x=hello" + doAssert fmt"{x =}" == "x =hello" + + let y = 3.1415926 + doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}" + doAssert fmt"{y=}" == fmt"y={y}" + doAssert fmt"{y = : <8}" == fmt"y = 3.14159 " + + proc hello(a: string, b: float): int = 12 + let a = "hello" + let b = 3.1415926 + doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12" + doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12" + doAssert fmt"{hello x, y = }" == "hello x, y = 12" + + +Note that it is space sensitive: + +.. code-block:: nim + + import strformat + let x = "12" + doAssert fmt"{x=}" == "x=12" + doAssert fmt"{x =:}" == "x =12" + doAssert fmt"{x =}" == "x =12" + doAssert fmt"{x= :}" == "x= 12" + doAssert fmt"{x= }" == "x= 12" + doAssert fmt"{x = :}" == "x = 12" + doAssert fmt"{x = }" == "x = 12" + doAssert fmt"{x = :}" == "x = 12" + doAssert fmt"{x = }" == "x = 12" + + Implementation details ====================== @@ -552,8 +597,18 @@ proc strformatImpl(pattern: NimNode; openChar, closeChar: char): NimNode = var subexpr = "" while i < f.len and f[i] != closeChar and f[i] != ':': - subexpr.add f[i] - inc i + if f[i] == '=': + let start = i + inc i + i += f.skipWhitespace(i) + if f[i] == closeChar or f[i] == ':': + result.add newCall(bindSym"add", res, newLit(subexpr & f[start ..< i])) + else: + subexpr.add f[start ..< i] + else: + subexpr.add f[i] + inc i + var x: NimNode try: x = parseExpr(subexpr) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 1ffc8bf22..32d6ede1e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -2935,11 +2935,6 @@ proc isEmptyOrWhitespace*(s: string): bool {.noSideEffect, rtl, ## Checks if `s` is empty or consists entirely of whitespace characters. result = s.allCharsInSet(Whitespace) -proc isNilOrWhitespace*(s: string): bool {.noSideEffect, rtl, - extern: "nsuIsNilOrWhitespace", - deprecated: "use isEmptyOrWhitespace instead".} = - ## Alias for isEmptyOrWhitespace - result = isEmptyOrWhitespace(s) when isMainModule: proc nonStaticTests = diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index e798bbaf1..99a333cc6 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -37,7 +37,7 @@ type var gTerm {.threadvar.}: owned(PTerminal) -proc newTerminal(): owned(PTerminal) {.gcsafe.} +proc newTerminal(): owned(PTerminal) {.gcsafe, raises: [].} proc getTerminal(): PTerminal {.inline.} = if isNil(gTerm): diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 317376405..0e23077ac 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -93,6 +93,18 @@ since (1, 1): type StaticParam*[value: static type] = object ## used to wrap a static value in `genericParams` +since (1, 3, 5): + template elementType*(a: untyped): typedesc = + ## return element type of `a`, which can be any iterable (over which you + ## can iterate) + runnableExamples: + iterator myiter(n: int): auto = + for i in 0..<n: yield i + doAssert elementType(@[1,2]) is int + doAssert elementType("asdf") is char + doAssert elementType(myiter(3)) is int + typeof(block: (for ai in a: ai)) + import std/macros macro genericParamsImpl(T: typedesc): untyped = @@ -109,6 +121,9 @@ macro genericParamsImpl(T: typedesc): untyped = of nnkTypeDef: impl = impl[2] continue + of nnkTypeOfExpr: + impl = getTypeInst(impl[0]) + continue of nnkBracketExpr: for i in 1..<impl.len: let ai = impl[i] @@ -118,8 +133,32 @@ macro genericParamsImpl(T: typedesc): untyped = ret = ai of ntyStatic: doAssert false else: - since (1, 1): - ret = newTree(nnkBracketExpr, @[bindSym"StaticParam", ai]) + # getType from a resolved symbol might return a typedesc symbol. + # If so, use it directly instead of wrapping it in StaticParam. + if (ai.kind == nnkSym and ai.symKind == nskType) or + (ai.kind == nnkBracketExpr and ai[0].kind == nnkSym and + ai[0].symKind == nskType): + ret = ai + elif ai.kind == nnkInfix and ai[0].kind == nnkIdent and + ai[0].strVal == "..": + # For built-in array types, the "2" is translated to "0..1" then + # automagically translated to "range[0..1]". However this is not + # reflected in the AST, thus requiring manual transformation here. + # + # We will also be losing some context here: + # var a: array[10, int] + # will be translated to: + # var a: array[0..9, int] + # after typecheck. This means that we can't get the exact + # definition as typed by the user, which will cause confusion for + # users expecting: + # genericParams(typeof(a)) is (StaticParam(10), int) + # to be true while in fact the result will be: + # genericParams(typeof(a)) is (range[0..9], int) + ret = newTree(nnkBracketExpr, @[bindSym"range", ai]) + else: + since (1, 1): + ret = newTree(nnkBracketExpr, @[bindSym"StaticParam", ai]) result.add ret break else: @@ -129,32 +168,20 @@ since (1, 1): template genericParams*(T: typedesc): untyped = ## return tuple of generic params for generic `T` runnableExamples: - type Foo[T1, T2]=object + type Foo[T1, T2] = object doAssert genericParams(Foo[float, string]) is (float, string) type Bar[N: static float, T] = object doAssert genericParams(Bar[1.0, string]) is (StaticParam[1.0], string) doAssert genericParams(Bar[1.0, string]).get(0).value == 1.0 + doAssert genericParams(seq[Bar[2.0, string]]).get(0) is Bar[2.0, string] + var s: seq[Bar[3.0, string]] + doAssert genericParams(typeof(s)) is (Bar[3.0, string],) + + # NOTE: For the builtin array type, the index generic param will + # **always** become a range type after it's bound to a variable. + doAssert genericParams(array[10, int]) is (StaticParam[10], int) + var a: array[10, int] + doAssert genericParams(typeof(a)) is (range[0..9], int) type T2 = T genericParamsImpl(T2) - -when isMainModule: - static: - doAssert $type(42) == "int" - doAssert int.name == "int" - - const a1 = name(int) - const a2 = $(int) - const a3 = $int - doAssert a1 == "int" - doAssert a2 == "int" - doAssert a3 == "int" - - proc fun[T: typedesc](t: T) = - const a1 = name(t) - const a2 = $(t) - const a3 = $t - doAssert a1 == "int" - doAssert a2 == "int" - doAssert a3 == "int" - fun(int) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index bea7d9c44..98be959e3 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -94,6 +94,7 @@ ## echo "suite teardown: run once after the tests" import std/private/since +import std/exitprocs import macros, strutils, streams, times, sets, sequtils @@ -498,7 +499,7 @@ template test*(name, body) {.dirty.} = ## .. code-block:: ## ## [OK] roses are red - bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName + bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName, setProgramResult ensureInitialized() @@ -524,7 +525,7 @@ template test*(name, body) {.dirty.} = finally: if testStatusIMPL == TestStatus.FAILED: - programResult = 1 + setProgramResult 1 let testResult = TestResult( suiteName: when declared(testSuiteName): testSuiteName else: "", testName: name, @@ -560,12 +561,11 @@ template fail* = ## fail() ## ## outputs "Checkpoint A" before quitting. - bind ensureInitialized - + bind ensureInitialized, setProgramResult when declared(testStatusIMPL): testStatusIMPL = TestStatus.FAILED else: - programResult = 1 + setProgramResult 1 ensureInitialized() @@ -576,8 +576,7 @@ template fail* = else: formatter.failureOccurred(checkpoints, "") - when declared(programResult): - if abortOnError: quit(programResult) + if abortOnError: quit(1) checkpoints = @[] @@ -679,7 +678,7 @@ macro check*(conditions: untyped): untyped = result = newNimNode(nnkStmtList) for node in checked: if node.kind != nnkCommentStmt: - result.add(newCall(!"check", node)) + result.add(newCall(newIdentNode("check"), node)) else: let lineinfo = newStrLitNode(checked.lineInfo) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index b163a2ab4..04a9d97bd 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -47,6 +47,8 @@ import std/private/since import strutils, parseutils, base64 +include includes/decode_helpers + type Url* = distinct string @@ -90,6 +92,7 @@ proc decodeUrl*(s: string, decodePlus = true): string = ## This means that any ``%xx`` (where ``xx`` denotes a hexadecimal ## value) are converted to the character with ordinal number ``xx``, ## and every other character is carried over. + ## If ``xx`` is not a valid hexadecimal value, it is left intact. ## ## As a special rule, when the value of ``decodePlus`` is true, ``+`` ## characters are converted to a space. @@ -101,12 +104,7 @@ proc decodeUrl*(s: string, decodePlus = true): string = assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test") == "https://nim-lang.org/this is a test" assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test", false) == "https://nim-lang.org/this is a test" - 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) + assert decodeUrl("abc%xyz") == "abc%xyz" result = newString(s.len) var i = 0 @@ -114,11 +112,7 @@ proc decodeUrl*(s: string, decodePlus = true): string = 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) + result[j] = decodePercent(s, i) of '+': if decodePlus: result[j] = ' ' diff --git a/lib/std/exitprocs.nim b/lib/std/exitprocs.nim index b2811735c..c6537f7f8 100644 --- a/lib/std/exitprocs.nim +++ b/lib/std/exitprocs.nim @@ -63,3 +63,25 @@ proc addExitProc*(cl: proc() {.noconv.}) = withLock gFunsLock: fun() gFuns.add Fun(kind: kNoconv, fun2: cl) + +when not defined(nimscript): + proc getProgramResult*(): int = + when defined(js) and defined(nodejs): + asm """ +`result` = process.exitCode; +""" + elif not defined(js): + result = programResult + else: + doAssert false + + proc setProgramResult*(a: int) = + # pending https://github.com/nim-lang/Nim/issues/14674 + when defined(js) and defined(nodejs): + asm """ +process.exitCode = `a`; +""" + elif not defined(js): + programResult = a + else: + doAssert false diff --git a/lib/std/isolation.nim b/lib/std/isolation.nim new file mode 100644 index 000000000..7ca5f0080 --- /dev/null +++ b/lib/std/isolation.nim @@ -0,0 +1,31 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2020 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the `Isolated[T]` type for +## safe construction of isolated subgraphs that can be +## passed efficiently to different channels and threads. + +type + Isolated*[T] = object ## Isolated data can only be moved, not copied. + value: T + +proc `=`*[T](dest: var Isolated[T]; src: Isolated[T]) {.error.} + +proc `=sink`*[T](dest: var Isolated[T]; src: Isolated[T]) {.inline.} = + # delegate to value's sink operation + `=sink`(dest.value, src.value) + +proc `=destroy`*[T](dest: var Isolated[T]) {.inline.} = + # delegate to value's destroy operation + `=destroy`(dest.value) + +func isolate*[T](value: sink T): Isolated[T] {.magic: "Isolate".} + ## Create an isolated subgraph from the expression `value`. + ## Please read https://github.com/nim-lang/RFCs/issues/244 + ## for more details. diff --git a/lib/std/jsonutils.nim b/lib/std/jsonutils.nim index be3d7e7c8..22f2a7a89 100644 --- a/lib/std/jsonutils.nim +++ b/lib/std/jsonutils.nim @@ -30,10 +30,59 @@ add a way to customize serialization, for eg: * handle cyclic references, using a cache of already visited addresses ]# +import std/macros + proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".} proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".} template distinctBase[T](a: T): untyped = distinctBase(type(a))(a) +macro getDiscriminants(a: typedesc): seq[string] = + ## return the discriminant keys + # candidate for std/typetraits + var a = a.getTypeImpl + doAssert a.kind == nnkBracketExpr + let sym = a[1] + let t = sym.getTypeImpl + let t2 = t[2] + doAssert t2.kind == nnkRecList + result = newTree(nnkBracket) + for ti in t2: + if ti.kind == nnkRecCase: + let key = ti[0][0] + let typ = ti[0][1] + result.add newLit key.strVal + if result.len > 0: + result = quote do: + @`result` + else: + result = quote do: + seq[string].default + +macro initCaseObject(a: typedesc, fun: untyped): untyped = + ## does the minimum to construct a valid case object, only initializing + ## the discriminant fields; see also `getDiscriminants` + # maybe candidate for std/typetraits + var a = a.getTypeImpl + doAssert a.kind == nnkBracketExpr + let sym = a[1] + let t = sym.getTypeImpl + var t2: NimNode + case t.kind + of nnkObjectTy: t2 = t[2] + of nnkRefTy: t2 = t[0].getTypeImpl[2] + else: doAssert false, $t.kind # xxx `nnkPtrTy` could be handled too + doAssert t2.kind == nnkRecList + result = newTree(nnkObjConstr) + result.add sym + for ti in t2: + if ti.kind == nnkRecCase: + let key = ti[0][0] + let typ = ti[0][1] + let key2 = key.strVal + let val = quote do: + `fun`(`key2`, typedesc[`typ`]) + result.add newTree(nnkExprColonExpr, key, val) + proc checkJsonImpl(cond: bool, condStr: string, msg = "") = if not cond: # just pick 1 exception type for simplicity; other choices would be: @@ -43,6 +92,19 @@ proc checkJsonImpl(cond: bool, condStr: string, msg = "") = template checkJson(cond: untyped, msg = "") = checkJsonImpl(cond, astToStr(cond), msg) +template fromJsonFields(a, b, T, keys) = + checkJson b.kind == JObject, $(b.kind) # we could customize whether to allow JNull + var num = 0 + for key, val in fieldPairs(a): + num.inc + when key notin keys: + if b.hasKey key: + fromJson(val, b[key]) + else: + # we could customize to allow this + checkJson false, $($T, key, b) + checkJson b.len == num, $(b.len, num, $T, b) # could customize + proc fromJson*[T](a: var T, b: JsonNode) = ## inplace version of `jsonTo` #[ @@ -85,25 +147,22 @@ proc fromJson*[T](a: var T, b: JsonNode) = a.setLen b.len for i, val in b.getElems: fromJson(a[i], val) - elif T is object | tuple: - const isNamed = T is object or isNamedTuple(T) - when isNamed: - checkJson b.kind == JObject, $(b.kind) # we could customize whether to allow JNull - var num = 0 - for key, val in fieldPairs(a): - num.inc - if b.hasKey key: - fromJson(val, b[key]) - else: - # we could customize to allow this - checkJson false, $($T, key, b) - checkJson b.len == num, $(b.len, num, $T, b) # could customize + elif T is object: + template fun(key, typ): untyped = + jsonTo(b[key], typ) + a = initCaseObject(T, fun) + const keys = getDiscriminants(T) + fromJsonFields(a, b, T, keys) + elif T is tuple: + when isNamedTuple(T): + fromJsonFields(a, b, T, seq[string].default) else: checkJson b.kind == JArray, $(b.kind) # we could customize whether to allow JNull var i = 0 for val in fields(a): fromJson(val, b[i]) i.inc + checkJson b.len == i, $(b.len, i, $T, b) # could customize else: # checkJson not appropriate here static: doAssert false, "not yet implemented: " & $T @@ -120,8 +179,7 @@ proc toJson*[T](a: T): JsonNode = result = newJObject() for k, v in pairs(a): result[k] = toJson(v) elif T is object | tuple: - const isNamed = T is object or isNamedTuple(T) - when isNamed: + when T is object or isNamedTuple(T): result = newJObject() for k, v in a.fieldPairs: result[k] = toJson(v) else: diff --git a/lib/std/private/nimbleutils.nim b/lib/std/private/nimbleutils.nim deleted file mode 100644 index 297fb9695..000000000 --- a/lib/std/private/nimbleutils.nim +++ /dev/null @@ -1,32 +0,0 @@ -##[ -internal API for now, API subject to change -]## - -import std/[os,osproc,sugar,strutils] - -proc actionRetry*(maxRetry: int, backoffDuration: float, action: proc(): bool): bool = - ## retry `action` up to `maxRetry` times with exponential backoff and initial - ## duraton of `backoffDuration` seconds - var t = backoffDuration - for i in 0..<maxRetry: - if action(): return true - if i == maxRetry - 1: break - sleep(int(t * 1000)) - t = t * 2 # exponential backoff - return false - -proc nimbleInstall*(name: string, message: var string): bool = - let cmd = "nimble install -y " & name - let (outp, status) = execCmdEx(cmd) - if status != 0: - message = "'$1' failed:\n$2" % [cmd, outp] - result = false - else: result = true - -when isMainModule: - block: - var msg: string - let ok = actionRetry(maxRetry = 2, backoffDuration = 0.1): - (proc(): bool = nimbleInstall("nonexistant", msg)) - doAssert "Package not found" in msg - doAssert not ok diff --git a/lib/system.nim b/lib/system.nim index 822454626..71eed5bee 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1127,12 +1127,9 @@ const ## is the value that should be passed to `quit <#quit,int>`_ to indicate ## failure. -when defined(js) and defined(nodejs) and not defined(nimscript): - var programResult* {.importc: "process.exitCode".}: int - programResult = 0 -elif hostOS != "standalone": +when not defined(js) and hostOS != "standalone": var programResult* {.compilerproc, exportc: "nim_program_result".}: int - ## deprecated, prefer ``quit`` + ## deprecated, prefer `quit` or `exitprocs.getProgramResult`, `exitprocs.setProgramResult`. import std/private/since diff --git a/lib/system/io.nim b/lib/system/io.nim index 9f12a1767..3d5cf981a 100644 --- a/lib/system/io.nim +++ b/lib/system/io.nim @@ -282,7 +282,11 @@ elif defined(windows): proc getOsfhandle(fd: cint): int {. importc: "_get_osfhandle", header: "<io.h>".} - proc setHandleInformation(handle: int, mask, flags: culong): cint {. + type + IoHandle = distinct pointer + ## Windows' HANDLE type. Defined as an untyped pointer but is **not** + ## one. Named like this to avoid collision with other `system` modules. + proc setHandleInformation(handle: IoHandle, mask, flags: culong): cint {. importc: "SetHandleInformation", header: "<handleapi.h>".} const @@ -339,7 +343,8 @@ when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(w flags = if inheritable: flags and not FD_CLOEXEC else: flags or FD_CLOEXEC result = c_fcntl(f, F_SETFD, flags) != -1 else: - result = setHandleInformation(f.int, HANDLE_FLAG_INHERIT, culong inheritable) != 0 + result = setHandleInformation(cast[IoHandle](f), HANDLE_FLAG_INHERIT, + culong inheritable) != 0 proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect], benign.} = diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index ceaaef013..9e19fcfc4 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -136,13 +136,13 @@ proc dirExists*(dir: string): bool {. ## Checks if the directory `dir` exists. builtin -proc existsFile*(filename: string): bool = - ## An alias for ``fileExists``. - fileExists(filename) +template existsFile*(args: varargs[untyped]): untyped {.deprecated: "use fileExists".} = + # xxx: warning won't be shown for nimsscript because of current logic handling + # `foreignPackageNotes` + fileExists(args) -proc existsDir*(dir: string): bool = - ## An alias for ``dirExists``. - dirExists(dir) +template existsDir*(args: varargs[untyped]): untyped {.deprecated: "use dirExists".} = + dirExists(args) proc selfExe*(): string = ## Returns the currently running nim or nimble executable. diff --git a/lib/system/repr_v2.nim b/lib/system/repr_v2.nim index fcc187a42..d456f4454 100644 --- a/lib/system/repr_v2.nim +++ b/lib/system/repr_v2.nim @@ -29,21 +29,35 @@ proc repr*(x: bool): string {.magic: "BoolToStr", noSideEffect.} proc repr*(x: char): string {.noSideEffect.} = ## repr for a character argument. Returns `x` - ## converted to a string. + ## converted to an escaped string. ## ## .. code-block:: Nim ## assert repr('c') == "'c'" - '\'' & $x & '\'' - -proc repr*(x: cstring): string {.noSideEffect.} = - ## repr for a CString argument. Returns `x` - ## converted to a quoted string. - '"' & $x & '"' + result.add '\'' + # Elides string creations if not needed + if x in {'\\', '\0'..'\31', '\127'..'\255'}: + result.add '\\' + if x in {'\0'..'\31', '\127'..'\255'}: + result.add $x.uint8 + else: + result.add x + result.add '\'' -proc repr*(x: string): string {.noSideEffect.} = +proc repr*(x: string | cstring): string {.noSideEffect.} = ## repr for a string argument. Returns `x` - ## but quoted. - '"' & x & '"' + ## converted to a quoted and escaped string. + result.add '\"' + for i in 0..<x.len: + if x[i] in {'"', '\\', '\0'..'\31', '\127'..'\255'}: + result.add '\\' + case x[i]: + of '\n': + result.add "n\n" + of '\0'..'\9', '\11'..'\31', '\127'..'\255': + result.add $x[i].uint8 + else: + result.add x[i] + result.add '\"' proc repr*[Enum: enum](x: Enum): string {.magic: "EnumToStr", noSideEffect.} ## repr for an enumeration argument. This works for @@ -68,6 +82,10 @@ proc repr*(p: pointer): string = result[j] = HexChars[n and 0xF] n = n shr 4 +proc repr*(p: proc): string = + ## repr of a proc as its address + repr(cast[pointer](p)) + template repr*(x: distinct): string = repr(distinctBase(typeof(x))(x)) diff --git a/lib/system/seqs_v2.nim b/lib/system/seqs_v2.nim index 1b40c00ab..3c94a03f9 100644 --- a/lib/system/seqs_v2.nim +++ b/lib/system/seqs_v2.nim @@ -73,16 +73,16 @@ proc shrink*[T](x: var seq[T]; newLen: Natural) = when nimvm: setLen(x, newLen) else: - mixin `=destroy` #sysAssert newLen <= x.len, "invalid newLen parameter for 'shrink'" when not supportsCopyMem(T): for i in countdown(x.len - 1, newLen): - `=destroy`(x[i]) + reset x[i] # XXX This is wrong for const seqs that were moved into 'x'! cast[ptr NimSeqV2[T]](addr x).len = newLen proc grow*[T](x: var seq[T]; newLen: Natural; value: T) = let oldLen = x.len + #sysAssert newLen >= x.len, "invalid newLen parameter for 'grow'" if newLen <= oldLen: return var xu = cast[ptr NimSeqV2[T]](addr x) if xu.p == nil or xu.p.cap < newLen: diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim index cb26833ac..43a769b5f 100644 --- a/lib/system/strmantle.nim +++ b/lib/system/strmantle.nim @@ -66,10 +66,6 @@ proc addInt*(result: var string; x: int64) = for j in 0..i div 2 - 1: swap(result[base+j], result[base+i-j-1]) -proc add*(result: var string; x: int64) {.deprecated: - "Deprecated since v0.20, use 'addInt'".} = - addInt(result, x) - proc nimIntToStr(x: int): string {.compilerRtl.} = result = newStringOfCap(sizeof(x)*4) result.addInt x @@ -98,10 +94,6 @@ proc addFloat*(result: var string; x: float) = let n = writeFloatToBuffer(buffer, x) result.addCstringN(cstring(buffer[0].addr), n) -proc add*(result: var string; x: float) {.deprecated: - "Deprecated since v0.20, use 'addFloat'".} = - addFloat(result, x) - proc nimFloatToStr(f: float): string {.compilerproc.} = result = newStringOfCap(8) result.addFloat f diff --git a/lib/system/strs_v2.nim b/lib/system/strs_v2.nim index aa644522f..85c0af190 100644 --- a/lib/system/strs_v2.nim +++ b/lib/system/strs_v2.nim @@ -132,10 +132,13 @@ proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} = a.len = b.len copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1) -proc nimPrepareStrMutationV2(s: var NimStringV2) {.compilerRtl.} = +proc nimPrepareStrMutationImpl(s: var NimStringV2) = + let oldP = s.p + # can't mutate a literal, so we need a fresh copy here: + s.p = cast[ptr NimStrPayload](allocShared0(contentSize(s.len))) + s.p.cap = s.len + copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len+1) + +proc nimPrepareStrMutationV2(s: var NimStringV2) {.compilerRtl, inline.} = if s.p != nil and (s.p.cap and strlitFlag) == strlitFlag: - let oldP = s.p - # can't mutate a literal, so we need a fresh copy here: - s.p = cast[ptr NimStrPayload](allocShared0(contentSize(s.len))) - s.p.cap = s.len - copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len+1) + nimPrepareStrMutationImpl(s) diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index aabcbdc90..83c11eb79 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -28,7 +28,6 @@ when defined(nimv2): proc `=destroy`(a: var WideCStringObj) = if a.data != nil: deallocShared(a.data) - a.data = nil proc `=`(a: var WideCStringObj; b: WideCStringObj) {.error.} diff --git a/lib/system_overview.rst b/lib/system_overview.rst index 23284dabf..f0945873c 100644 --- a/lib/system_overview.rst +++ b/lib/system_overview.rst @@ -45,24 +45,27 @@ Proc Usage Seqs ---- -======================================== ========================================== -Proc Usage -======================================== ========================================== -`newSeq<#newSeq>`_ Create a new sequence of a given length -`newSeqOfCap<#newSeqOfCap,Natural>`_ Create a new sequence with zero length - and a given capacity -`setLen<#setLen,seq[T][T],Natural>`_ Set the length of a sequence -`len<#len,seq[T][T]>`_ Return the length of a sequence -`@<#@,array[IDX,T]>`_ Turn an array into a sequence -`add<#add,seq[T][T],T>`_ Add an item to the sequence -`insert<#insert,seq[T][T],T>`_ Insert an item at a specific position -`delete<#delete,seq[T][T],Natural>`_ Delete an item while preserving the - order of elements (`O(n)` operation) -`del<#del,seq[T][T],Natural>`_ `O(1)` removal, doesn't preserve the order -`pop<#pop,seq[T][T]>`_ Remove and return last item of a sequence -`x & y<#&,seq[T][T],seq[T][T]>`_ Concatenate two sequences -`x[a..b]<#[],openArray[T],HSlice[U,V]>`_ Slice of a sequence (both ends included) -======================================== ========================================== +=========================================== ========================================== +Proc Usage +=========================================== ========================================== +`newSeq<#newSeq>`_ Create a new sequence of a given length +`newSeqOfCap<#newSeqOfCap,Natural>`_ Create a new sequence with zero length + and a given capacity +`setLen<#setLen,seq[T][T],Natural>`_ Set the length of a sequence +`len<#len,seq[T][T]>`_ Return the length of a sequence +`@<#@,array[IDX,T]>`_ Turn an array into a sequence +`add<#add,seq[T][T],T>`_ Add an item to the sequence +`insert<#insert,seq[T][T],T>`_ Insert an item at a specific position +`delete<#delete,seq[T][T],Natural>`_ Delete an item while preserving the + order of elements (`O(n)` operation) +`del<#del,seq[T][T],Natural>`_ `O(1)` removal, doesn't preserve the order +`pop<#pop,seq[T][T]>`_ Remove and return last item of a sequence +`x & y<#&,seq[T][T],seq[T][T]>`_ Concatenate two sequences +`x[a..b]<#[],openArray[T],HSlice[U,V]>`_ Slice of a sequence (both ends included) +`x[a..^b]<#[],openArray[T],HSlice[U,V]>`_ Slice of a sequence but `b` is a + reversed index (both ends included) +`x[a..\<b]<#[],openArray[T],HSlice[U,V]>`_ Slice of a sequence (excluded upper bound) +=========================================== ========================================== **See also:** * `sequtils module <sequtils.html>`_ for operations on container @@ -160,8 +163,10 @@ Proc Usage `T or F<#or,bool,bool>`_ Boolean `or` `T xor F<#xor,bool,bool>`_ Boolean `xor` (exclusive or) `not T<#not,bool>`_ Boolean `not` +`a[^x]<#^.t,int>`_ Take the element at the reversed index `x` `a .. b<#..,T,U>`_ Binary slice that constructs an interval `[a, b]` +`a ..^ b<#..^.t,untyped,untyped>`_ Interval `[a, b]` but `b` as reversed index [a ..< b](#..<.t,untyped,untyped) Interval `[a, b)` (excluded upper bound) [runnableExamples](#runnableExamples,untyped) Create testable documentation ============================================= ============================================ diff --git a/lib/wrappers/odbcsql.nim b/lib/wrappers/odbcsql.nim index 393f29ad0..7836f087d 100644 --- a/lib/wrappers/odbcsql.nim +++ b/lib/wrappers/odbcsql.nim @@ -47,8 +47,8 @@ type SqlHDesc* = SqlHandle TSqlInteger* = int32 SqlUInteger* = int32 - TSqlLen* = int64 - TSqlULen* = uint64 + TSqlLen* = int + TSqlULen* = uint SqlPointer* = pointer TSqlReal* = cfloat TSqlDouble* = cdouble |