diff options
Diffstat (limited to 'lib/pure')
49 files changed, 2236 insertions, 1616 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index edd509f50..aef4f1ce6 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -16,7 +16,8 @@ import asyncfutures except callSoon import nativesockets, net, deques export Port, SocketFlag -export asyncfutures, asyncstreams +export asyncfutures except callSoon +export asyncstreams #{.injectStmt: newGcInvariant().} @@ -145,7 +146,9 @@ export asyncfutures, asyncstreams ## ## Futures should **never** be discarded. This is because they may contain ## errors. If you do not care for the result of a Future then you should -## use the ``asyncCheck`` procedure instead of the ``discard`` keyword. +## use the ``asyncCheck`` procedure instead of the ``discard`` keyword. Note +## however that this does not wait for completion, and you should use +## ``waitFor`` for that purpose. ## ## Examples ## -------- @@ -197,7 +200,7 @@ proc adjustTimeout(pollTimeout: int, nextTimer: Option[int]): int {.inline.} = if pollTimeout == -1: return result = min(pollTimeout, result) -proc callSoon(cbproc: proc ()) {.gcsafe.} +proc callSoon*(cbproc: proc ()) {.gcsafe.} proc initCallSoonProc = if asyncfutures.getCallSoonProc().isNil: @@ -241,8 +244,6 @@ when defined(windows) or defined(nimdoc): AsyncEvent* = ptr AsyncEventImpl Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.} - {.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD, - TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].} proc hash(x: AsyncFD): Hash {.borrow.} proc `==`*(x: AsyncFD, y: AsyncFD): bool {.borrow.} @@ -1077,7 +1078,6 @@ else: PDispatcher* = ref object of PDispatcherBase selector: Selector[AsyncData] - {.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].} proc `==`*(x, y: AsyncFD): bool {.borrow.} proc `==`*(x, y: AsyncEvent): bool {.borrow.} @@ -1638,7 +1638,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} = return add(result, c) -proc callSoon(cbproc: proc ()) = +proc callSoon*(cbproc: proc ()) = ## Schedule `cbproc` to be called as soon as possible. ## The callback is called when control returns to the event loop. getGlobalDispatcher().callbacks.addLast(cbproc) diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index df0e7c17e..5037c8a24 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -123,11 +123,17 @@ proc add(callbacks: var CallbackList, function: CallbackFunc) = callbacks.function = function assert callbacks.next == nil else: - let newNext = new(ref CallbackList) - newNext.function = callbacks.function - newNext.next = callbacks.next - callbacks.next = newNext - callbacks.function = function + let newCallback = new(ref CallbackList) + newCallback.function = function + newCallback.next = nil + + if callbacks.next == nil: + callbacks.next = newCallback + else: + var last = callbacks.next + while last.next != nil: + last = last.next + last.next = newCallback proc complete*[T](future: Future[T], val: T) = ## Completes ``future`` with value ``val``. @@ -339,7 +345,8 @@ proc asyncCheck*[T](future: Future[T]) = ## Sets a callback on ``future`` which raises an exception if the future ## finished with an error. ## - ## This should be used instead of ``discard`` to discard void futures. + ## This should be used instead of ``discard`` to discard void futures, + ## or use ``waitFor`` if you need to wait for the future's completion. assert(not future.isNil, "Future is nil") future.callback = proc () = @@ -368,8 +375,9 @@ proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = ## complete. var retFuture = newFuture[void]("asyncdispatch.`or`") proc cb[X](fut: Future[X]) = - if fut.failed: retFuture.fail(fut.error) - if not retFuture.finished: retFuture.complete() + if not retFuture.finished: + if fut.failed: retFuture.fail(fut.error) + else: retFuture.complete() fut1.callback = cb[T] fut2.callback = cb[Y] return retFuture diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 71a1600dc..e33ddeaf0 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -218,7 +218,7 @@ when defineSsl: var data = await recv(socket.fd.AsyncFD, BufferSize, flags) let length = len(data) if length > 0: - let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint) + let ret = bioWrite(socket.bioIn, addr data[0], length.cint) if ret < 0: raiseSSLError() elif length == 0: @@ -599,7 +599,7 @@ proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. ## - ## Raises an EOS error upon failure. + ## Raises an OSError error upon failure. if listen(socket.fd, backlog) < 0'i32: raiseOSError(osLastError()) proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {. diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 101146ace..869abc9cc 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -147,6 +147,12 @@ proc readData*(allowedMethods: set[RequestMethod] = for name, value in decodeData(allowedMethods): result[name.string] = value.string +proc readData*(data: string): StringTableRef = + ## Read CGI data from a string. + result = newStringTable() + for name, value in decodeData(data): + result[name.string] = value.string + proc validateData*(data: StringTableRef, validKeys: varargs[string]) = ## validates data; raises `ECgi` if this fails. This checks that each variable ## name of the CGI `data` occurs in the `validKeys` array. diff --git a/lib/pure/collections/LockFreeHash.nim b/lib/pure/collections/LockFreeHash.nim index 954d62491..28fa2a81b 100644 --- a/lib/pure/collections/LockFreeHash.nim +++ b/lib/pure/collections/LockFreeHash.nim @@ -49,13 +49,11 @@ when sizeof(int) == 4: # 32bit Raw = range[0..1073741823] ## The range of uint values that can be stored directly in a value slot ## when on a 32 bit platform - {.deprecated: [TRaw: Raw].} elif sizeof(int) == 8: # 64bit type Raw = range[0'i64..4611686018427387903'i64] ## The range of uint values that can be stored directly in a value slot ## when on a 64 bit platform - {.deprecated: [TRaw: Raw].} else: {.error: "unsupported platform".} @@ -74,7 +72,6 @@ type copyDone: int next: PConcTable[K,V] data: EntryArr -{.deprecated: [TEntry: Entry, TEntryArr: EntryArr].} proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int, expVal: int, match: bool): int @@ -544,7 +541,6 @@ when not defined(testing) and isMainModule: Data = tuple[k: string,v: TestObj] PDataArr = array[0..numTests-1, Data] Dict = PConcTable[string,TestObj] - {.deprecated: [TTestObj: TestObj, TData: Data].} var thr: array[0..numThreads-1, Thread[Dict]] diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index c94e08098..32e0299ba 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -33,8 +33,6 @@ type root: Node[T] count: int -{.deprecated: [TCritBitTree: CritBitTree].} - proc len*[T](c: CritBitTree[T]): int = ## returns the number of elements in `c` in O(1). result = c.count diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index 545958977..def96b8f7 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -44,8 +44,6 @@ type data: TrunkSeq a: array[0..33, int] # profiling shows that 34 elements are enough -{.deprecated: [TIntSet: IntSet, TTrunk: Trunk, TTrunkSeq: TrunkSeq].} - proc mustRehash(length, counter: int): bool {.inline.} = assert(length > counter) result = (length * 2 < counter * 3) or (length - counter < 4) diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index e69acc8d9..0b3708a7c 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -45,15 +45,6 @@ type SomeLinkedNode*[T] = SinglyLinkedNode[T] | DoublyLinkedNode[T] -{.deprecated: [TDoublyLinkedNode: DoublyLinkedNodeObj, - PDoublyLinkedNode: DoublyLinkedNode, - TSinglyLinkedNode: SinglyLinkedNodeObj, - PSinglyLinkedNode: SinglyLinkedNode, - TDoublyLinkedList: DoublyLinkedList, - TSinglyLinkedRing: SinglyLinkedRing, - TDoublyLinkedRing: DoublyLinkedRing, - TSinglyLinkedList: SinglyLinkedList].} - proc initSinglyLinkedList*[T](): SinglyLinkedList[T] = ## creates a new singly linked list that is empty. discard diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim index ce792d6da..9a1d169fb 100644 --- a/lib/pure/collections/queues.nim +++ b/lib/pure/collections/queues.nim @@ -46,8 +46,6 @@ type data: seq[T] rd, wr, count, mask: int -{.deprecated: [TQueue: Queue].} - proc initQueue*[T](initialSize: int = 4): Queue[T] = ## Create a new queue. ## Optionally, the initial capacity can be reserved via `initialSize` as a diff --git a/lib/pure/collections/rtarrays.nim b/lib/pure/collections/rtarrays.nim index 3849117a0..90dbf0049 100644 --- a/lib/pure/collections/rtarrays.nim +++ b/lib/pure/collections/rtarrays.nim @@ -19,7 +19,6 @@ type L: Natural spart: seq[T] apart: array[ArrayPartSize, T] - UncheckedArray* {.unchecked.}[T] = array[0, T] template usesSeqPart(x): untyped = x.L > ArrayPartSize diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 2e21786bb..be10780ff 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -504,36 +504,76 @@ template anyIt*(s, pred: untyped): bool = break result -template toSeq*(iter: untyped): untyped = - ## Transforms any iterator into a sequence. - ## - ## Example: - ## - ## .. code-block:: - ## let - ## numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - ## odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: - ## if x mod 2 == 1: - ## result = true) - ## assert odd_numbers == @[1, 3, 5, 7, 9] - - # Note: see also `mapIt` for explanation of some of the implementation - # subtleties. - when compiles(iter.len): +template toSeq1(s: not iterator): untyped = + # overload for typed but not iterator + type outType = type(items(s)) + when compiles(s.len): block: - evalOnceAs(iter2, iter, true) - var result = newSeq[type(iter)](iter2.len) + evalOnceAs(s2, s, compiles((let _ = s))) var i = 0 - for x in iter2: - result[i] = x - inc i + var result = newSeq[outType](s2.len) + for it in s2: + result[i] = it + i += 1 result else: - var result: seq[type(iter)] = @[] - for x in iter: - result.add(x) + var result: seq[outType] = @[] + for it in s: + result.add(it) + result + +template toSeq2(iter: iterator): untyped = + # overload for iterator + evalOnceAs(iter2, iter(), false) + when compiles(iter2.len): + var i = 0 + var result = newSeq[type(iter2)](iter2.len) + for x in iter2: + result[i] = x + inc i + result + else: + type outType = type(iter2()) + var result: seq[outType] = @[] + when compiles(iter2()): + evalOnceAs(iter4, iter, false) + let iter3=iter4() + for x in iter3(): + result.add(x) + else: + for x in iter2(): + result.add(x) result +template toSeq*(iter: untyped): untyped = + ## Transforms any iterable into a sequence. + runnableExamples: + let + numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] + odd_numbers = toSeq(filter(numeric, proc(x: int): bool = x mod 2 == 1)) + doAssert odd_numbers == @[1, 3, 5, 7, 9] + + when compiles(toSeq1(iter)): + toSeq1(iter) + elif compiles(toSeq2(iter)): + toSeq2(iter) + else: + # overload for untyped, eg: `toSeq(myInlineIterator(3))` + when compiles(iter.len): + block: + evalOnceAs(iter2, iter, true) + var result = newSeq[type(iter)](iter2.len) + var i = 0 + for x in iter2: + result[i] = x + inc i + result + else: + var result: seq[type(iter)] = @[] + for x in iter: + result.add(x) + result + template foldl*(sequence, operation: untyped): untyped = ## Template to fold a sequence from left to right, returning the accumulation. ## @@ -673,10 +713,16 @@ template mapIt*(s: typed, op: untyped): untyped = ## nums = @[1, 2, 3, 4] ## strings = nums.mapIt($(4 * it)) ## assert strings == @["4", "8", "12", "16"] - type outType = type(( - block: - var it{.inject.}: type(items(s)); - op)) + when defined(nimHasTypeof): + type outType = typeof(( + block: + var it{.inject.}: typeof(items(s), typeOfIter); + op), typeOfProc) + else: + type outType = type(( + block: + var it{.inject.}: type(items(s)); + op)) when compiles(s.len): block: # using a block avoids https://github.com/nim-lang/Nim/issues/8580 @@ -1027,12 +1073,72 @@ when isMainModule: assert anyIt(anumbers, it > 9) == false block: # toSeq test - let - numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: - if x mod 2 == 1: - result = true) - assert odd_numbers == @[1, 3, 5, 7, 9] + block: + let + numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] + odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: + if x mod 2 == 1: + result = true) + assert odd_numbers == @[1, 3, 5, 7, 9] + + block: + doAssert [1,2].toSeq == @[1,2] + doAssert @[1,2].toSeq == @[1,2] + + doAssert @[1,2].toSeq == @[1,2] + doAssert toSeq(@[1,2]) == @[1,2] + + block: + iterator myIter(seed:int):auto= + for i in 0..<seed: + yield i + doAssert toSeq(myIter(2)) == @[0, 1] + + block: + iterator myIter():auto{.inline.}= + yield 1 + yield 2 + + doAssert myIter.toSeq == @[1,2] + doAssert toSeq(myIter) == @[1,2] + + block: + iterator myIter():int {.closure.} = + yield 1 + yield 2 + + doAssert myIter.toSeq == @[1,2] + doAssert toSeq(myIter) == @[1,2] + + block: + proc myIter():auto= + iterator ret():int{.closure.}= + yield 1 + yield 2 + result = ret + + doAssert myIter().toSeq == @[1,2] + doAssert toSeq(myIter()) == @[1,2] + + block: + proc myIter(n:int):auto= + var counter = 0 + iterator ret():int{.closure.}= + while counter<n: + yield counter + counter.inc + result = ret + + block: + let myIter3 = myIter(3) + doAssert myIter3.toSeq == @[0,1,2] + block: + let myIter3 = myIter(3) + doAssert toSeq(myIter3) == @[0,1,2] + block: + # makes sure this does not hang forever + doAssert myIter(3).toSeq == @[0,1,2] + doAssert toSeq(myIter(3)) == @[0,1,2] block: # tests https://github.com/nim-lang/Nim/issues/7187 @@ -1135,5 +1241,13 @@ when isMainModule: A, B doAssert mapIt(X, $it) == @["A", "B"] + block: + # bug #9093 + let inp = "a:b,c:d" + + let outp = inp.split(",").mapIt(it.split(":")) + doAssert outp == @[@["a", "b"], @["c", "d"]] + + when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 7355aae02..1273cbc33 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -39,8 +39,6 @@ type data: KeyValuePairSeq[A] counter: int -{.deprecated: [TSet: HashSet].} - template default[T](t: typedesc[T]): T = ## Used by clear methods to get a default value. var v: T @@ -631,8 +629,6 @@ type data: OrderedKeyValuePairSeq[A] counter, first, last: int -{.deprecated: [TOrderedSet: OrderedSet].} - proc clear*[A](s: var OrderedSet[A]) = ## Clears the OrderedSet back to an empty state, without shrinking ## any of the existing storage. O(n) where n is the size of the hash bucket. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 9fdae33ed..f46a368b1 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -125,8 +125,6 @@ type counter: int TableRef*[A,B] = ref Table[A, B] -{.deprecated: [TTable: Table, PTable: TableRef].} - template maxHash(t): untyped = high(t.data) template dataLen(t): untyped = len(t.data) @@ -520,8 +518,6 @@ type counter, first, last: int OrderedTableRef*[A, B] = ref OrderedTable[A, B] -{.deprecated: [TOrderedTable: OrderedTable, POrderedTable: OrderedTableRef].} - proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = ## returns the number of keys in ``t``. result = t.counter @@ -795,7 +791,7 @@ proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = getOrDefault(t[], key) proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, + ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, ## ``default`` is returned. getOrDefault(t[], key, default) @@ -892,8 +888,6 @@ type counter: int CountTableRef*[A] = ref CountTable[A] -{.deprecated: [TCountTable: CountTable, PCountTable: CountTableRef].} - proc len*[A](t: CountTable[A]): int = ## returns the number of keys in ``t``. result = t.counter diff --git a/lib/pure/colors.nim b/lib/pure/colors.nim index 843f29a63..b58166b05 100644 --- a/lib/pure/colors.nim +++ b/lib/pure/colors.nim @@ -377,7 +377,7 @@ proc colorNameCmp(x: tuple[name: string, col: Color], y: string): int = proc parseColor*(name: string): Color = ## parses `name` to a color value. If no valid color could be - ## parsed ``EInvalidValue`` is raised. Case insensitive. + ## parsed ``ValueError`` is raised. Case insensitive. if name[0] == '#': result = Color(parseHexInt(name)) else: diff --git a/lib/pure/complex.nim b/lib/pure/complex.nim index ba5c571ce..69d9c0f7f 100644 --- a/lib/pure/complex.nim +++ b/lib/pure/complex.nim @@ -9,78 +9,109 @@ -## This module implements complex numbers. -{.push checks:off, line_dir:off, stack_trace:off, debugger:off.} -# the user does not want to trace a part -# of the standard library! -import - math +## This module implements complex numbers. +## Complex numbers are currently implemented as generic on a 64-bit or 32-bit float. + +{.push checks: off, line_dir: off, stack_trace: off, debugger: off.} +# the user does not want to trace a part of the standard library! -const - EPS = 1.0e-7 ## Epsilon used for float comparisons. +import math type - Complex* = tuple[re, im: float] - ## a complex number, consisting of a real and an imaginary part - -const - im*: Complex = (re: 0.0, im: 1.0) - ## The imaginary unit. √-1. + Complex*[T: SomeFloat] = object + re*, im*: T + ## A complex number, consisting of a real and an imaginary part. + Complex64* = Complex[float64] + ## Alias for a pair of 64-bit floats. + Complex32* = Complex[float32] + ## Alias for a pair of 32-bit floats. + +proc complex*[T: SomeFloat](re: T; im: T = 0.0): Complex[T] = + result.re = re + result.im = im + +proc complex32*(re: float32; im: float32 = 0.0): Complex[float32] = + result.re = re + result.im = im + +proc complex64*(re: float64; im: float64 = 0.0): Complex[float64] = + result.re = re + result.im = im + +template im*(arg: typedesc[float32]): Complex32 = complex[float32](0, 1) +template im*(arg: typedesc[float64]): Complex64 = complex[float64](0, 1) +template im*(arg : float32): Complex32 = complex[float32](0, arg) +template im*(arg : float64): Complex64 = complex[float64](0, arg) + +proc abs*[T](z: Complex[T]): T = + ## Return the distance from (0,0) to ``z``. + result = hypot(z.re, z.im) + +proc abs2*[T](z: Complex[T]): T = + ## Return the squared distance from (0,0) to ``z``. + result = z.re*z.re + z.im*z.im + +proc conjugate*[T](z: Complex[T]): Complex[T] = + ## Conjugate of complex number ``z``. + result.re = z.re + result.im = -z.im -proc toComplex*(x: SomeInteger): Complex = - ## Convert some integer ``x`` to a complex number. - result.re = x - result.im = 0 +proc inv*[T](z: Complex[T]): Complex[T] = + ## Multiplicative inverse of complex number ``z``. + conjugate(z) / abs2(z) -proc `==` *(x, y: Complex): bool = - ## Compare two complex numbers `x` and `y` for equality. +proc `==` *[T](x, y: Complex[T]): bool = + ## Compare two complex numbers ``x`` and ``y`` for equality. result = x.re == y.re and x.im == y.im -proc `=~` *(x, y: Complex): bool = - ## Compare two complex numbers `x` and `y` approximately. - result = abs(x.re-y.re)<EPS and abs(x.im-y.im)<EPS - -proc `+` *(x, y: Complex): Complex = - ## Add two complex numbers. - result.re = x.re + y.re - result.im = x.im + y.im +proc `+` *[T](x: T, y: Complex[T]): Complex[T] = + ## Add a real number to a complex number. + result.re = x + y.re + result.im = y.im -proc `+` *(x: Complex, y: float): Complex = - ## Add complex `x` to float `y`. +proc `+` *[T](x: Complex[T], y: T): Complex[T] = + ## Add a complex number to a real number. result.re = x.re + y result.im = x.im -proc `+` *(x: float, y: Complex): Complex = - ## Add float `x` to complex `y`. - result.re = x + y.re - result.im = y.im - +proc `+` *[T](x, y: Complex[T]): Complex[T] = + ## Add two complex numbers. + result.re = x.re + y.re + result.im = x.im + y.im -proc `-` *(z: Complex): Complex = +proc `-` *[T](z: Complex[T]): Complex[T] = ## Unary minus for complex numbers. result.re = -z.re result.im = -z.im -proc `-` *(x, y: Complex): Complex = +proc `-` *[T](x: T, y: Complex[T]): Complex[T] = + ## Subtract a complex number from a real number. + x + (-y) + +proc `-` *[T](x: Complex[T], y: T): Complex[T] = + ## Subtract a real number from a complex number. + result.re = x.re - y + result.im = x.im + +proc `-` *[T](x, y: Complex[T]): Complex[T] = ## Subtract two complex numbers. result.re = x.re - y.re result.im = x.im - y.im -proc `-` *(x: Complex, y: float): Complex = - ## Subtracts float `y` from complex `x`. - result = x + (-y) - -proc `-` *(x: float, y: Complex): Complex = - ## Subtracts complex `y` from float `x`. - result = x + (-y) +proc `/` *[T](x: Complex[T], y: T): Complex[T] = + ## Divide complex number ``x`` by real number ``y``. + result.re = x.re / y + result.im = x.im / y +proc `/` *[T](x: T, y: Complex[T]): Complex[T] = + ## Divide real number ``x`` by complex number ``y``. + result = x * inv(y) -proc `/` *(x, y: Complex): Complex = - ## Divide `x` by `y`. - var - r, den: float +proc `/` *[T](x, y: Complex[T]): Complex[T] = + ## Divide ``x`` by ``y``. + var r, den: T if abs(y.re) < abs(y.im): r = y.re / y.im den = y.im + r * y.re @@ -92,101 +123,46 @@ proc `/` *(x, y: Complex): Complex = result.re = (x.re + r * x.im) / den result.im = (x.im - r * x.re) / den -proc `/` *(x : Complex, y: float ): Complex = - ## Divide complex `x` by float `y`. - result.re = x.re/y - result.im = x.im/y - -proc `/` *(x : float, y: Complex ): Complex = - ## Divide float `x` by complex `y`. - var num : Complex = (x, 0.0) - result = num/y - - -proc `*` *(x, y: Complex): Complex = - ## Multiply `x` with `y`. - result.re = x.re * y.re - x.im * y.im - result.im = x.im * y.re + x.re * y.im - -proc `*` *(x: float, y: Complex): Complex = - ## Multiply float `x` with complex `y`. +proc `*` *[T](x: T, y: Complex[T]): Complex[T] = + ## Multiply a real number and a complex number. result.re = x * y.re result.im = x * y.im -proc `*` *(x: Complex, y: float): Complex = - ## Multiply complex `x` with float `y`. +proc `*` *[T](x: Complex[T], y: T): Complex[T] = + ## Multiply a complex number with a real number. result.re = x.re * y result.im = x.im * y +proc `*` *[T](x, y: Complex[T]): Complex[T] = + ## Multiply ``x`` with ``y``. + result.re = x.re * y.re - x.im * y.im + result.im = x.im * y.re + x.re * y.im -proc `+=` *(x: var Complex, y: Complex) = - ## Add `y` to `x`. + +proc `+=` *[T](x: var Complex[T], y: Complex[T]) = + ## Add ``y`` to ``x``. x.re += y.re x.im += y.im -proc `+=` *(x: var Complex, y: float) = - ## Add `y` to the complex number `x`. - x.re += y - -proc `-=` *(x: var Complex, y: Complex) = - ## Subtract `y` from `x`. +proc `-=` *[T](x: var Complex[T], y: Complex[T]) = + ## Subtract ``y`` from ``x``. x.re -= y.re x.im -= y.im -proc `-=` *(x: var Complex, y: float) = - ## Subtract `y` from the complex number `x`. - x.re -= y - -proc `*=` *(x: var Complex, y: Complex) = - ## Multiply `y` to `x`. +proc `*=` *[T](x: var Complex[T], y: Complex[T]) = + ## Multiply ``y`` to ``x``. let im = x.im * y.re + x.re * y.im x.re = x.re * y.re - x.im * y.im x.im = im -proc `*=` *(x: var Complex, y: float) = - ## Multiply `y` to the complex number `x`. - x.re *= y - x.im *= y - -proc `/=` *(x: var Complex, y: Complex) = - ## Divide `x` by `y` in place. +proc `/=` *[T](x: var Complex[T], y: Complex[T]) = + ## Divide ``x`` by ``y`` in place. x = x / y -proc `/=` *(x : var Complex, y: float) = - ## Divide complex `x` by float `y` in place. - x.re /= y - x.im /= y - - -proc abs*(z: Complex): float = - ## Return the distance from (0,0) to `z`. - # optimized by checking special cases (sqrt is expensive) - var x, y, temp: float - - x = abs(z.re) - y = abs(z.im) - if x == 0.0: - result = y - elif y == 0.0: - result = x - elif x > y: - temp = y / x - result = x * sqrt(1.0 + temp * temp) - else: - temp = x / y - result = y * sqrt(1.0 + temp * temp) - - -proc conjugate*(z: Complex): Complex = - ## Conjugate of complex number `z`. - result.re = z.re - result.im = -z.im - - -proc sqrt*(z: Complex): Complex = - ## Square root for a complex number `z`. - var x, y, w, r: float +proc sqrt*[T](z: Complex[T]): Complex[T] = + ## Square root for a complex number ``z``. + var x, y, w, r: T if z.re == 0.0 and z.im == 0.0: result = z @@ -199,247 +175,283 @@ proc sqrt*(z: Complex): Complex = else: r = x / y w = sqrt(y) * sqrt(0.5 * (r + sqrt(1.0 + r * r))) + if z.re >= 0.0: result.re = w result.im = z.im / (w * 2.0) else: - if z.im >= 0.0: result.im = w - else: result.im = -w + result.im = if z.im >= 0.0: w else: -w result.re = z.im / (result.im + result.im) +proc exp*[T](z: Complex[T]): Complex[T] = + ## ``e`` raised to the power ``z``. + var + rho = exp(z.re) + theta = z.im + result.re = rho * cos(theta) + result.im = rho * sin(theta) -proc exp*(z: Complex): Complex = - ## e raised to the power `z`. - var rho = exp(z.re) - var theta = z.im - result.re = rho*cos(theta) - result.im = rho*sin(theta) - - -proc ln*(z: Complex): Complex = - ## Returns the natural log of `z`. +proc ln*[T](z: Complex[T]): Complex[T] = + ## Returns the natural log of ``z``. result.re = ln(abs(z)) - result.im = arctan2(z.im,z.re) - -proc log10*(z: Complex): Complex = - ## Returns the log base 10 of `z`. - result = ln(z)/ln(10.0) + result.im = arctan2(z.im, z.re) -proc log2*(z: Complex): Complex = - ## Returns the log base 2 of `z`. - result = ln(z)/ln(2.0) +proc log10*[T](z: Complex[T]): Complex[T] = + ## Returns the log base 10 of ``z``. + result = ln(z) / ln(10.0) +proc log2*[T](z: Complex[T]): Complex[T] = + ## Returns the log base 2 of ``z``. + result = ln(z) / ln(2.0) -proc pow*(x, y: Complex): Complex = - ## `x` raised to the power `y`. - if x.re == 0.0 and x.im == 0.0: - if y.re == 0.0 and y.im == 0.0: +proc pow*[T](x, y: Complex[T]): Complex[T] = + ## ``x`` raised to the power ``y``. + if x.re == 0.0 and x.im == 0.0: + if y.re == 0.0 and y.im == 0.0: result.re = 1.0 result.im = 0.0 else: result.re = 0.0 result.im = 0.0 - elif y.re == 1.0 and y.im == 0.0: + elif y.re == 1.0 and y.im == 0.0: result = x - elif y.re == -1.0 and y.im == 0.0: - result = 1.0/x + elif y.re == -1.0 and y.im == 0.0: + result = T(1.0) / x else: - var rho = sqrt(x.re*x.re + x.im*x.im) - var theta = arctan2(x.im,x.re) - var s = pow(rho,y.re) * exp(-y.im*theta) - var r = y.re*theta + y.im*ln(rho) - result.re = s*cos(r) - result.im = s*sin(r) - - -proc sin*(z: Complex): Complex = - ## Returns the sine of `z`. - result.re = sin(z.re)*cosh(z.im) - result.im = cos(z.re)*sinh(z.im) - -proc arcsin*(z: Complex): Complex = - ## Returns the inverse sine of `z`. - var i: Complex = (0.0,1.0) - result = -i*ln(i*z + sqrt(1.0-z*z)) - -proc cos*(z: Complex): Complex = - ## Returns the cosine of `z`. - result.re = cos(z.re)*cosh(z.im) - result.im = -sin(z.re)*sinh(z.im) - -proc arccos*(z: Complex): Complex = - ## Returns the inverse cosine of `z`. - var i: Complex = (0.0,1.0) - result = -i*ln(z + sqrt(z*z-1.0)) - -proc tan*(z: Complex): Complex = - ## Returns the tangent of `z`. - result = sin(z)/cos(z) - -proc arctan*(z: Complex): Complex = - ## Returns the inverse tangent of `z`. - var i: Complex = (0.0,1.0) - result = 0.5*i*(ln(1-i*z)-ln(1+i*z)) - -proc cot*(z: Complex): Complex = - ## Returns the cotangent of `z`. + var + rho = abs(x) + theta = arctan2(x.im, x.re) + s = pow(rho, y.re) * exp(-y.im * theta) + r = y.re * theta + y.im * ln(rho) + result.re = s * cos(r) + result.im = s * sin(r) + +proc pow*[T](x: Complex[T], y: T): Complex[T] = + ## Complex number ``x`` raised to the power ``y``. + pow(x, complex[T](y)) + + +proc sin*[T](z: Complex[T]): Complex[T] = + ## Returns the sine of ``z``. + result.re = sin(z.re) * cosh(z.im) + result.im = cos(z.re) * sinh(z.im) + +proc arcsin*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse sine of ``z``. + result = -im(T) * ln(im(T) * z + sqrt(T(1.0) - z*z)) + +proc cos*[T](z: Complex[T]): Complex[T] = + ## Returns the cosine of ``z``. + result.re = cos(z.re) * cosh(z.im) + result.im = -sin(z.re) * sinh(z.im) + +proc arccos*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse cosine of ``z``. + result = -im(T) * ln(z + sqrt(z*z - T(1.0))) + +proc tan*[T](z: Complex[T]): Complex[T] = + ## Returns the tangent of ``z``. + result = sin(z) / cos(z) + +proc arctan*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse tangent of ``z``. + result = T(0.5)*im(T) * (ln(T(1.0) - im(T)*z) - ln(T(1.0) + im(T)*z)) + +proc cot*[T](z: Complex[T]): Complex[T] = + ## Returns the cotangent of ``z``. result = cos(z)/sin(z) -proc arccot*(z: Complex): Complex = - ## Returns the inverse cotangent of `z`. - var i: Complex = (0.0,1.0) - result = 0.5*i*(ln(1-i/z)-ln(1+i/z)) +proc arccot*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse cotangent of ``z``. + result = T(0.5)*im(T) * (ln(T(1.0) - im(T)/z) - ln(T(1.0) + im(T)/z)) -proc sec*(z: Complex): Complex = - ## Returns the secant of `z`. - result = 1.0/cos(z) +proc sec*[T](z: Complex[T]): Complex[T] = + ## Returns the secant of ``z``. + result = T(1.0) / cos(z) -proc arcsec*(z: Complex): Complex = - ## Returns the inverse secant of `z`. - var i: Complex = (0.0,1.0) - result = -i*ln(i*sqrt(1-1/(z*z))+1/z) +proc arcsec*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse secant of ``z``. + result = -im(T) * ln(im(T) * sqrt(1.0 - 1.0/(z*z)) + T(1.0)/z) -proc csc*(z: Complex): Complex = - ## Returns the cosecant of `z`. - result = 1.0/sin(z) +proc csc*[T](z: Complex[T]): Complex[T] = + ## Returns the cosecant of ``z``. + result = T(1.0) / sin(z) -proc arccsc*(z: Complex): Complex = - ## Returns the inverse cosecant of `z`. - var i: Complex = (0.0,1.0) - result = -i*ln(sqrt(1-1/(z*z))+i/z) +proc arccsc*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse cosecant of ``z``. + result = -im(T) * ln(sqrt(T(1.0) - T(1.0)/(z*z)) + im(T)/z) +proc sinh*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic sine of ``z``. + result = T(0.5) * (exp(z) - exp(-z)) -proc sinh*(z: Complex): Complex = - ## Returns the hyperbolic sine of `z`. - result = 0.5*(exp(z)-exp(-z)) +proc arcsinh*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic sine of ``z``. + result = ln(z + sqrt(z*z + 1.0)) -proc arcsinh*(z: Complex): Complex = - ## Returns the inverse hyperbolic sine of `z`. - result = ln(z+sqrt(z*z+1)) +proc cosh*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic cosine of ``z``. + result = T(0.5) * (exp(z) + exp(-z)) -proc cosh*(z: Complex): Complex = - ## Returns the hyperbolic cosine of `z`. - result = 0.5*(exp(z)+exp(-z)) +proc arccosh*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic cosine of ``z``. + result = ln(z + sqrt(z*z - T(1.0))) -proc arccosh*(z: Complex): Complex = - ## Returns the inverse hyperbolic cosine of `z`. - result = ln(z+sqrt(z*z-1)) +proc tanh*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic tangent of ``z``. + result = sinh(z) / cosh(z) -proc tanh*(z: Complex): Complex = - ## Returns the hyperbolic tangent of `z`. - result = sinh(z)/cosh(z) +proc arctanh*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic tangent of ``z``. + result = T(0.5) * (ln((T(1.0)+z) / (T(1.0)-z))) -proc arctanh*(z: Complex): Complex = - ## Returns the inverse hyperbolic tangent of `z`. - result = 0.5*(ln((1+z)/(1-z))) +proc sech*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic secant of ``z``. + result = T(2.0) / (exp(z) + exp(-z)) -proc sech*(z: Complex): Complex = - ## Returns the hyperbolic secant of `z`. - result = 2/(exp(z)+exp(-z)) +proc arcsech*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic secant of ``z``. + result = ln(1.0/z + sqrt(T(1.0)/z+T(1.0)) * sqrt(T(1.0)/z-T(1.0))) -proc arcsech*(z: Complex): Complex = - ## Returns the inverse hyperbolic secant of `z`. - result = ln(1/z+sqrt(1/z+1)*sqrt(1/z-1)) +proc csch*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic cosecant of ``z``. + result = T(2.0) / (exp(z) - exp(-z)) -proc csch*(z: Complex): Complex = - ## Returns the hyperbolic cosecant of `z`. - result = 2/(exp(z)-exp(-z)) +proc arccsch*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic cosecant of ``z``. + result = ln(T(1.0)/z + sqrt(T(1.0)/(z*z) + T(1.0))) -proc arccsch*(z: Complex): Complex = - ## Returns the inverse hyperbolic cosecant of `z`. - result = ln(1/z+sqrt(1/(z*z)+1)) +proc coth*[T](z: Complex[T]): Complex[T] = + ## Returns the hyperbolic cotangent of ``z``. + result = cosh(z) / sinh(z) -proc coth*(z: Complex): Complex = - ## Returns the hyperbolic cotangent of `z`. - result = cosh(z)/sinh(z) +proc arccoth*[T](z: Complex[T]): Complex[T] = + ## Returns the inverse hyperbolic cotangent of ``z``. + result = T(0.5) * (ln(T(1.0) + T(1.0)/z) - ln(T(1.0) - T(1.0)/z)) -proc arccoth*(z: Complex): Complex = - ## Returns the inverse hyperbolic cotangent of `z`. - result = 0.5*(ln(1+1/z)-ln(1-1/z)) - -proc phase*(z: Complex): float = - ## Returns the phase of `z`. +proc phase*[T](z: Complex[T]): T = + ## Returns the phase of ``z``. arctan2(z.im, z.re) -proc polar*(z: Complex): tuple[r, phi: float] = - ## Returns `z` in polar coordinates. - result.r = abs(z) - result.phi = phase(z) +proc polar*[T](z: Complex[T]): tuple[r, phi: T] = + ## Returns ``z`` in polar coordinates. + (r: abs(z), phi: phase(z)) -proc rect*(r: float, phi: float): Complex = - ## Returns the complex number with polar coordinates `r` and `phi`. - result.re = r * cos(phi) - result.im = r * sin(phi) +proc rect*[T](r, phi: T): Complex[T] = + ## Returns the complex number with polar coordinates ``r`` and ``phi``. + ## + ## | ``result.re = r * cos(phi)`` + ## | ``result.im = r * sin(phi)`` + complex(r * cos(phi), r * sin(phi)) proc `$`*(z: Complex): string = - ## Returns `z`'s string representation as ``"(re, im)"``. + ## Returns ``z``'s string representation as ``"(re, im)"``. result = "(" & $z.re & ", " & $z.im & ")" {.pop.} when isMainModule: - var z = (0.0, 0.0) - var oo = (1.0,1.0) - var a = (1.0, 2.0) - var b = (-1.0, -2.0) - var m1 = (-1.0, 0.0) - var i = (0.0,1.0) - var one = (1.0,0.0) - var tt = (10.0, 20.0) - var ipi = (0.0, -PI) - - assert( a == a ) - assert( (a-a) == z ) - assert( (a+b) == z ) - assert( (a/b) == m1 ) - assert( (1.0/a) == (0.2, -0.4) ) - assert( (a*b) == (3.0, -4.0) ) - assert( 10.0*a == tt ) - assert( a*10.0 == tt ) - assert( tt/10.0 == a ) - assert( oo+(-1.0) == i ) - assert( (-1.0)+oo == i ) - assert( abs(oo) == sqrt(2.0) ) - assert( conjugate(a) == (1.0, -2.0) ) - assert( sqrt(m1) == i ) - assert( exp(ipi) =~ m1 ) - - assert( pow(a,b) =~ (-3.72999124927876, -1.68815826725068) ) - assert( pow(z,a) =~ (0.0, 0.0) ) - assert( pow(z,z) =~ (1.0, 0.0) ) - assert( pow(a,one) =~ a ) - assert( pow(a,m1) =~ (0.2, -0.4) ) - - assert( ln(a) =~ (0.804718956217050, 1.107148717794090) ) - assert( log10(a) =~ (0.349485002168009, 0.480828578784234) ) - assert( log2(a) =~ (1.16096404744368, 1.59727796468811) ) - - assert( sin(a) =~ (3.16577851321617, 1.95960104142161) ) - assert( cos(a) =~ (2.03272300701967, -3.05189779915180) ) - assert( tan(a) =~ (0.0338128260798967, 1.0147936161466335) ) - assert( cot(a) =~ 1.0/tan(a) ) - assert( sec(a) =~ 1.0/cos(a) ) - assert( csc(a) =~ 1.0/sin(a) ) - assert( arcsin(a) =~ (0.427078586392476, 1.528570919480998) ) - assert( arccos(a) =~ (1.14371774040242, -1.52857091948100) ) - assert( arctan(a) =~ (1.338972522294494, 0.402359478108525) ) - - assert( cosh(a) =~ (-0.642148124715520, 1.068607421382778) ) - assert( sinh(a) =~ (-0.489056259041294, 1.403119250622040) ) - assert( tanh(a) =~ (1.1667362572409199,-0.243458201185725) ) - assert( sech(a) =~ 1/cosh(a) ) - assert( csch(a) =~ 1/sinh(a) ) - assert( coth(a) =~ 1/tanh(a) ) - assert( arccosh(a) =~ (1.528570919480998, 1.14371774040242) ) - assert( arcsinh(a) =~ (1.469351744368185, 1.06344002357775) ) - assert( arctanh(a) =~ (0.173286795139986, 1.17809724509617) ) - assert( arcsech(a) =~ arccosh(1/a) ) - assert( arccsch(a) =~ arcsinh(1/a) ) - assert( arccoth(a) =~ arctanh(1/a) ) - - assert( phase(a) == 1.1071487177940904 ) + proc `=~`[T](x, y: Complex[T]): bool = + result = abs(x.re-y.re) < 1e-6 and abs(x.im-y.im) < 1e-6 + + proc `=~`[T](x: Complex[T], y: T): bool = + result = abs(x.re-y) < 1e-6 and abs(x.im) < 1e-6 + + var + z: Complex64 = complex(0.0, 0.0) + oo: Complex64 = complex(1.0, 1.0) + a: Complex64 = complex(1.0, 2.0) + b: Complex64 = complex(-1.0, -2.0) + m1: Complex64 = complex(-1.0, 0.0) + i: Complex64 = complex(0.0, 1.0) + one: Complex64 = complex(1.0, 0.0) + tt: Complex64 = complex(10.0, 20.0) + ipi: Complex64 = complex(0.0, -PI) + + doAssert(a/2.0 =~ complex(0.5, 1.0)) + doAssert(a == a) + doAssert((a-a) == z) + doAssert((a+b) == z) + doAssert((a+b) =~ 0.0) + doAssert((a/b) == m1) + doAssert((1.0/a) == complex(0.2, -0.4)) + doAssert((a*b) == complex(3.0, -4.0)) + doAssert(10.0*a == tt) + doAssert(a*10.0 == tt) + doAssert(tt/10.0 == a) + doAssert(oo+(-1.0) == i) + doAssert( (-1.0)+oo == i) + doAssert(abs(oo) == sqrt(2.0)) + doAssert(conjugate(a) == complex(1.0, -2.0)) + doAssert(sqrt(m1) == i) + doAssert(exp(ipi) =~ m1) + + doAssert(pow(a, b) =~ complex(-3.72999124927876, -1.68815826725068)) + doAssert(pow(z, a) =~ complex(0.0, 0.0)) + doAssert(pow(z, z) =~ complex(1.0, 0.0)) + doAssert(pow(a, one) =~ a) + doAssert(pow(a, m1) =~ complex(0.2, -0.4)) + doAssert(pow(a, 2.0) =~ complex(-3.0, 4.0)) + doAssert(pow(a, 2) =~ complex(-3.0, 4.0)) + doAssert(not(pow(a, 2.0) =~ a)) + + doAssert(ln(a) =~ complex(0.804718956217050, 1.107148717794090)) + doAssert(log10(a) =~ complex(0.349485002168009, 0.480828578784234)) + doAssert(log2(a) =~ complex(1.16096404744368, 1.59727796468811)) + + doAssert(sin(a) =~ complex(3.16577851321617, 1.95960104142161)) + doAssert(cos(a) =~ complex(2.03272300701967, -3.05189779915180)) + doAssert(tan(a) =~ complex(0.0338128260798967, 1.0147936161466335)) + doAssert(cot(a) =~ 1.0 / tan(a)) + doAssert(sec(a) =~ 1.0 / cos(a)) + doAssert(csc(a) =~ 1.0 / sin(a)) + doAssert(arcsin(a) =~ complex(0.427078586392476, 1.528570919480998)) + doAssert(arccos(a) =~ complex(1.14371774040242, -1.52857091948100)) + doAssert(arctan(a) =~ complex(1.338972522294494, 0.402359478108525)) + doAssert(arccot(a) =~ complex(0.2318238045004031, -0.402359478108525)) + doAssert(arcsec(a) =~ complex(1.384478272687081, 0.3965682301123288)) + doAssert(arccsc(a) =~ complex(0.1863180541078155, -0.3965682301123291)) + + doAssert(cosh(a) =~ complex(-0.642148124715520, 1.068607421382778)) + doAssert(sinh(a) =~ complex(-0.489056259041294, 1.403119250622040)) + doAssert(tanh(a) =~ complex(1.1667362572409199, -0.243458201185725)) + doAssert(sech(a) =~ 1.0 / cosh(a)) + doAssert(csch(a) =~ 1.0 / sinh(a)) + doAssert(coth(a) =~ 1.0 / tanh(a)) + doAssert(arccosh(a) =~ complex(1.528570919480998, 1.14371774040242)) + doAssert(arcsinh(a) =~ complex(1.469351744368185, 1.06344002357775)) + doAssert(arctanh(a) =~ complex(0.173286795139986, 1.17809724509617)) + doAssert(arcsech(a) =~ arccosh(1.0/a)) + doAssert(arccsch(a) =~ arcsinh(1.0/a)) + doAssert(arccoth(a) =~ arctanh(1.0/a)) + + doAssert(phase(a) == 1.1071487177940904) var t = polar(a) - assert( rect(t.r, t.phi) =~ a ) - assert( rect(1.0, 2.0) =~ (-0.4161468365471424, 0.9092974268256817) ) + doAssert(rect(t.r, t.phi) =~ a) + doAssert(rect(1.0, 2.0) =~ complex(-0.4161468365471424, 0.9092974268256817)) + + + var + i64: Complex32 = complex(0.0f, 1.0f) + a64: Complex32 = 2.0f*i64 + 1.0.float32 + b64: Complex32 = complex(-1.0'f32, -2.0'f32) + + doAssert(a64 == a64) + doAssert(a64 == -b64) + doAssert(a64 + b64 =~ 0.0'f32) + doAssert(not(pow(a64, b64) =~ a64)) + doAssert(pow(a64, 0.5f) =~ sqrt(a64)) + doAssert(pow(a64, 2) =~ complex(-3.0'f32, 4.0'f32)) + doAssert(sin(arcsin(b64)) =~ b64) + doAssert(cosh(arccosh(a64)) =~ a64) + + doAssert(phase(a64) - 1.107149f < 1e-6) + var t64 = polar(a64) + doAssert(rect(t64.r, t64.phi) =~ a64) + doAssert(rect(1.0f, 2.0f) =~ complex(-0.4161468f, 0.90929742f)) + doAssert(sizeof(a64) == 8) + doAssert(sizeof(a) == 16) + + doAssert 123.0.im + 456.0 == complex64(456, 123) diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim index 2fe34ed40..d6a7ceec8 100644 --- a/lib/pure/coro.nim +++ b/lib/pure/coro.nim @@ -6,15 +6,17 @@ # See the file "copying.txt", included in this # distribution, for details about the copyright. # -## Nim coroutines implementation supports several context switching methods: -## ucontext: available on unix and alike (default) -## setjmp: available on unix and alike (x86/64 only) -## Fibers: available and required on windows. +## Nim coroutines implementation, supports several context switching methods: +## -------- ------------ +## ucontext available on unix and alike (default) +## setjmp available on unix and alike (x86/64 only) +## fibers available and required on windows. +## -------- ------------ ## -## -d:nimCoroutines Required to build this module. -## -d:nimCoroutinesUcontext Use ucontext backend. -## -d:nimCoroutinesSetjmp Use setjmp backend. -## -d:nimCoroutinesSetjmpBundled Use bundled setjmp implementation. +## -d:nimCoroutines Required to build this module. +## -d:nimCoroutinesUcontext Use ucontext backend. +## -d:nimCoroutinesSetjmp Use setjmp backend. +## -d:nimCoroutinesSetjmpBundled Use bundled setjmp implementation. when not nimCoroutines and not defined(nimdoc): when defined(noNimCoroutines): @@ -105,7 +107,7 @@ elif coroBackend == CORO_BACKEND_SETJMP: # Use setjmp/longjmp implementation provided by the system. type JmpBuf {.importc: "jmp_buf", header: "<setjmp.h>".} = object - + proc setjmp(ctx: var JmpBuf): int {.importc, header: "<setjmp.h>".} proc longjmp(ctx: JmpBuf, ret=1) {.importc, header: "<setjmp.h>".} @@ -241,7 +243,7 @@ proc start*(c: proc(), stacksize: int=defaultStackSize): CoroutineRef {.discarda ## Schedule coroutine for execution. It does not run immediately. if ctx == nil: initialize() - + var coro: CoroutinePtr when coroBackend == CORO_BACKEND_FIBERS: coro = cast[CoroutinePtr](alloc0(sizeof(Coroutine))) @@ -287,7 +289,7 @@ proc run*() = if current.state == CORO_FINISHED: var next = ctx.current.prev if next == nil: - # If first coroutine ends then `prev` is nil even if more coroutines + # If first coroutine ends then `prev` is nil even if more coroutines # are to be scheduled. next = ctx.current.next current.reference.coro = nil diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim index 2039a31be..e09b00221 100644 --- a/lib/pure/encodings.nim +++ b/lib/pure/encodings.nim @@ -213,7 +213,6 @@ when defined(windows): maxCharSize: int32 defaultChar: array[0..1, char] leadByte: array[0..12-1, char] - {.deprecated: [TCpInfo: CpInfo].} proc getCPInfo(codePage: CodePage, lpCPInfo: var CpInfo): int32 {. stdcall, importc: "GetCPInfo", dynlib: "kernel32".} @@ -302,7 +301,7 @@ proc getCurrentEncoding*(): string = proc open*(destEncoding = "UTF-8", srcEncoding = "CP1252"): EncodingConverter = ## opens a converter that can convert from `srcEncoding` to `destEncoding`. - ## Raises `EIO` if it cannot fulfill the request. + ## Raises `IOError` if it cannot fulfill the request. when not defined(windows): result = iconvOpen(destEncoding, srcEncoding) if result == nil: diff --git a/lib/pure/endians.nim b/lib/pure/endians.nim index 6f80d56ef..771ecaaca 100644 --- a/lib/pure/endians.nim +++ b/lib/pure/endians.nim @@ -44,20 +44,23 @@ else: const useBuiltinSwap = false when useBuiltinSwap: + template swapOpImpl(T: typedesc, op: untyped) = + ## We have to use `copyMem` here instead of a simple deference because they + ## may point to a unaligned address. A sufficiently smart compiler _should_ + ## be able to elide them when they're not necessary. + var tmp: T + copyMem(addr tmp, inp, sizeOf(T)) + tmp = op(tmp) + copyMem(outp, addr tmp, sizeOf(T)) + proc swapEndian64*(outp, inp: pointer) {.inline, nosideeffect.}= - var i = cast[ptr uint64](inp) - var o = cast[ptr uint64](outp) - o[] = builtin_bswap64(i[]) + swapOpImpl(uint64, builtin_bswap64) proc swapEndian32*(outp, inp: pointer) {.inline, nosideeffect.}= - var i = cast[ptr uint32](inp) - var o = cast[ptr uint32](outp) - o[] = builtin_bswap32(i[]) + swapOpImpl(uint32, builtin_bswap32) proc swapEndian16*(outp, inp: pointer) {.inline, nosideeffect.}= - var i = cast[ptr uint16](inp) - var o = cast[ptr uint16](outp) - o[] = builtin_bswap16(i[]) + swapOpImpl(uint16, builtin_bswap16) else: proc swapEndian64*(outp, inp: pointer) = diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index fbf2b8e73..2d24050f2 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -2014,7 +2014,8 @@ proc parseHtml*(s: Stream, filename: string, ## Parses the XML from stream `s` and returns a ``XmlNode``. Every ## occurred parsing error is added to the `errors` sequence. var x: XmlParser - open(x, s, filename, {reportComments, reportWhitespace}) + open(x, s, filename, {reportComments, reportWhitespace, allowUnquotedAttribs, + allowEmptyAttribs}) next(x) # skip the DOCTYPE: if x.kind == xmlSpecial: next(x) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 139d4bb50..b7498b1c5 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -848,8 +848,6 @@ proc newHttpClient*(userAgent = defUserAgent, type AsyncHttpClient* = HttpClientBase[AsyncSocket] -{.deprecated: [PAsyncHttpClient: AsyncHttpClient].} - proc newAsyncHttpClient*(userAgent = defUserAgent, maxRedirects = 5, sslContext = getDefaultSSL(), proxy: Proxy = nil): AsyncHttpClient = @@ -1164,6 +1162,9 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, # Helper that actually makes the request. Does not handle redirects. let requestUrl = parseUri(url) + if requestUrl.scheme == "": + raise newException(ValueError, "No uri scheme supplied.") + when client is AsyncHttpClient: if not client.parseBodyFut.isNil: # let the current operation finish before making another request @@ -1207,7 +1208,9 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, for i in 1..client.maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) - result = await client.requestAux(redirectTo, httpMethod, body, headers) + # Guarantee method for HTTP 307: see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 + var meth = if result.status == "307": httpMethod else: "GET" + result = await client.requestAux(redirectTo, meth, body, headers) lastURL = redirectTo @@ -1226,36 +1229,49 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, ## be closed. result = await request(client, url, $httpMethod, body, headers) +proc responseContent(resp: Response | AsyncResponse): Future[string] {.multisync.} = + ## Returns the content of a response as a string. + ## + ## A ``HttpRequestError`` will be raised if the server responds with a + ## client error (status code 4xx) or a server error (status code 5xx). + if resp.code.is4xx or resp.code.is5xx: + raise newException(HttpRequestError, resp.status) + else: + return await resp.bodyStream.readAll() + +proc head*(client: HttpClient | AsyncHttpClient, + url: string): Future[Response | AsyncResponse] {.multisync.} = + ## Connects to the hostname specified by the URL and performs a HEAD request. + ## + ## This procedure uses httpClient values such as ``client.maxRedirects``. + result = await client.request(url, HttpHEAD) + proc get*(client: HttpClient | AsyncHttpClient, url: string): Future[Response | AsyncResponse] {.multisync.} = ## Connects to the hostname specified by the URL and performs a GET request. ## - ## This procedure will follow redirects up to a maximum number of redirects - ## specified in ``client.maxRedirects``. + ## This procedure uses httpClient values such as ``client.maxRedirects``. result = await client.request(url, HttpGET) proc getContent*(client: HttpClient | AsyncHttpClient, url: string): Future[string] {.multisync.} = - ## Connects to the hostname specified by the URL and performs a GET request. - ## - ## This procedure will follow redirects up to a maximum number of redirects - ## specified in ``client.maxRedirects``. - ## - ## A ``HttpRequestError`` will be raised if the server responds with a - ## client error (status code 4xx) or a server error (status code 5xx). + ## Connects to the hostname specified by the URL and returns the content of a GET request. let resp = await get(client, url) - if resp.code.is4xx or resp.code.is5xx: - raise newException(HttpRequestError, resp.status) - else: - return await resp.bodyStream.readAll() + return await responseContent(resp) -proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", - multipart: MultipartData = nil): Future[Response | AsyncResponse] - {.multisync.} = - ## Connects to the hostname specified by the URL and performs a POST request. - ## - ## This procedure will follow redirects up to a maximum number of redirects - ## specified in ``client.maxRedirects``. +proc delete*(client: HttpClient | AsyncHttpClient, + url: string): Future[Response | AsyncResponse] {.multisync.} = + ## Connects to the hostname specified by the URL and performs a DELETE request. + ## This procedure uses httpClient values such as ``client.maxRedirects``. + result = await client.request(url, HttpDELETE) + +proc deleteContent*(client: HttpClient | AsyncHttpClient, + url: string): Future[string] {.multisync.} = + ## Connects to the hostname specified by the URL and returns the content of a DELETE request. + let resp = await delete(client, url) + return await responseContent(resp) + +proc makeRequestContent(body = "", multipart: MultipartData = nil): (string, HttpHeaders) = let (mpContentType, mpBody) = format(multipart) # TODO: Support FutureStream for `body` parameter. template withNewLine(x): untyped = @@ -1264,38 +1280,59 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", else: x var xb = mpBody.withNewLine() & body - var headers = newHttpHeaders() if multipart != nil: headers["Content-Type"] = mpContentType headers["Content-Length"] = $len(xb) + return (xb, headers) - result = await client.requestAux(url, $HttpPOST, xb, headers) - # Handle redirects. - var lastURL = url - for i in 1..client.maxRedirects: - if result.status.redirection(): - let redirectTo = getNewLocation(lastURL, result.headers) - var meth = if result.status != "307": HttpGet else: HttpPost - result = await client.requestAux(redirectTo, $meth, xb, headers) - lastURL = redirectTo +proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", + multipart: MultipartData = nil): Future[Response | AsyncResponse] + {.multisync.} = + ## Connects to the hostname specified by the URL and performs a POST request. + ## This procedure uses httpClient values such as ``client.maxRedirects``. + var (xb, headers) = makeRequestContent(body, multipart) + result = await client.request(url, $HttpPOST, xb, headers) proc postContent*(client: HttpClient | AsyncHttpClient, url: string, body = "", multipart: MultipartData = nil): Future[string] {.multisync.} = - ## Connects to the hostname specified by the URL and performs a POST request. - ## - ## This procedure will follow redirects up to a maximum number of redirects - ## specified in ``client.maxRedirects``. - ## - ## A ``HttpRequestError`` will be raised if the server responds with a - ## client error (status code 4xx) or a server error (status code 5xx). + ## Connects to the hostname specified by the URL and returns the content of a POST request. let resp = await post(client, url, body, multipart) - if resp.code.is4xx or resp.code.is5xx: - raise newException(HttpRequestError, resp.status) - else: - return await resp.bodyStream.readAll() + return await responseContent(resp) + +proc put*(client: HttpClient | AsyncHttpClient, url: string, body = "", + multipart: MultipartData = nil): Future[Response | AsyncResponse] + {.multisync.} = + ## Connects to the hostname specified by the URL and performs a PUT request. + ## This procedure uses httpClient values such as ``client.maxRedirects``. + var (xb, headers) = makeRequestContent(body, multipart) + result = await client.request(url, $HttpPUT, xb, headers) + +proc putContent*(client: HttpClient | AsyncHttpClient, url: string, + body = "", + multipart: MultipartData = nil): Future[string] + {.multisync.} = + ## Connects to the hostname specified by the URL andreturns the content of a PUT request. + let resp = await put(client, url, body, multipart) + return await responseContent(resp) + +proc patch*(client: HttpClient | AsyncHttpClient, url: string, body = "", + multipart: MultipartData = nil): Future[Response | AsyncResponse] + {.multisync.} = + ## Connects to the hostname specified by the URL and performs a PATCH request. + ## This procedure uses httpClient values such as ``client.maxRedirects``. + var (xb, headers) = makeRequestContent(body, multipart) + result = await client.request(url, $HttpPATCH, xb, headers) + +proc patchContent*(client: HttpClient | AsyncHttpClient, url: string, + body = "", + multipart: MultipartData = nil): Future[string] + {.multisync.} = + ## Connects to the hostname specified by the URL and returns the content of a PATCH request. + let resp = await patch(client, url, body, multipart) + return await responseContent(resp) proc downloadFile*(client: HttpClient, url: string, filename: string) = ## Downloads ``url`` and saves it to ``filename``. diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index ae62a5c4e..4acc36b93 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -1,7 +1,9 @@ ## Include file that implements 'getEnv' and friends. Do not import it! -when not declared(ospaths): - {.error: "This is an include file for ospaths.nim!".} +when not declared(os): + {.error: "This is an include file for os.nim!".} + +from parseutils import skipIgnoreCase proc c_getenv(env: cstring): cstring {. importc: "getenv", header: "<stdlib.h>".} @@ -91,7 +93,10 @@ proc findEnvVar(key: string): int = getEnvVarsC() var temp = key & '=' for i in 0..high(environment): - if startsWith(environment[i], temp): return i + when defined(windows): + if skipIgnoreCase(environment[i], temp) == len(temp): return i + else: + if startsWith(environment[i], temp): return i return -1 proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index 31212d0d1..72c3f4f49 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -1,7 +1,7 @@ ## Include file that implements 'osErrorMsg' and friends. Do not import it! -when not declared(ospaths): - {.error: "This is an include file for ospaths.nim!".} +when not declared(os): + {.error: "This is an include file for os.nim!".} when not defined(nimscript): var errno {.importc, header: "<errno.h>".}: cint diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 8b3f14f34..16d901ff0 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -383,14 +383,14 @@ proc selectInto*[T](s: Selector[T], timeout: int, if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0: if (pevents and EPOLLHUP) != 0: - rkey.errorCode = ECONNRESET.OSErrorCode + rkey.errorCode = OSErrorCode ECONNRESET else: # Try reading SO_ERROR from fd. var error: cint - var size = sizeof(error).SockLen - if getsockopt(fdi.SocketHandle, SOL_SOCKET, SO_ERROR, addr(error), + var size = SockLen sizeof(error) + if getsockopt(SocketHandle fdi, SOL_SOCKET, SO_ERROR, addr(error), addr(size)) == 0'i32: - rkey.errorCode = error.OSErrorCode + rkey.errorCode = OSErrorCode error rkey.events.incl(Event.Error) if (pevents and EPOLLOUT) != 0: diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index f2f5cac9e..cd13deec3 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -80,6 +80,7 @@ type ConsoleLogger* = ref object of Logger ## logger that writes the messages to the ## console + useStderr*: bool ## will send logs into Stderr if set when not defined(js): type @@ -150,16 +151,20 @@ method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) = {.emit: "console.log(`cln`);".} else: try: - writeLine(stdout, ln) - if level in {lvlError, lvlFatal}: flushFile(stdout) + var handle = stdout + if logger.useStderr: + handle = stderr + writeLine(handle, ln) + if level in {lvlError, lvlFatal}: flushFile(handle) except IOError: discard -proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger = +proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr, useStderr=false): ConsoleLogger = ## Creates a new console logger. This logger logs to the console. new result result.fmtStr = fmtStr result.levelThreshold = levelThreshold + result.useStderr = useStderr when not defined(js): method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) = diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 171b71493..6756107bb 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -254,12 +254,12 @@ proc loadAny(s: Stream, a: Any, t: var Table[BiggestInt, pointer]) = close(p) proc load*[T](s: Stream, data: var T) = - ## loads `data` from the stream `s`. Raises `EIO` in case of an error. + ## loads `data` from the stream `s`. Raises `IOError` in case of an error. var tab = initTable[BiggestInt, pointer]() loadAny(s, toAny(data), tab) proc store*[T](s: Stream, data: T) = - ## stores `data` into the stream `s`. Raises `EIO` in case of an error. + ## stores `data` into the stream `s`. Raises `IOError` in case of an error. var stored = initIntSet() var d: T shallowCopy(d, data) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index f04cb5050..ee32772b1 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -168,16 +168,19 @@ when not defined(JS): # C proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "<math.h>".} proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "<math.h>".} ## Computes the square root of ``x``. + ## ## .. code-block:: nim ## echo sqrt(1.44) ## 1.2 proc cbrt*(x: float32): float32 {.importc: "cbrtf", header: "<math.h>".} proc cbrt*(x: float64): float64 {.importc: "cbrt", header: "<math.h>".} ## Computes the cubic root of ``x``. + ## ## .. code-block:: nim ## echo cbrt(2.197) ## 1.3 proc ln*(x: float32): float32 {.importc: "logf", header: "<math.h>".} proc ln*(x: float64): float64 {.importc: "log", header: "<math.h>".} ## Computes the `natural logarithm <https://en.wikipedia.org/wiki/Natural_logarithm>`_ of ``x``. + ## ## .. code-block:: nim ## echo ln(exp(4.0)) ## 4.0 else: # JS @@ -189,6 +192,7 @@ else: # JS proc log*[T: SomeFloat](x, base: T): T = ## Computes the logarithm of ``x`` to base ``base``. + ## ## .. code-block:: nim ## echo log(9.0, 3.0) ## 2.0 ln(x) / ln(base) @@ -197,51 +201,60 @@ when not defined(JS): # C proc log10*(x: float32): float32 {.importc: "log10f", header: "<math.h>".} proc log10*(x: float64): float64 {.importc: "log10", header: "<math.h>".} ## Computes the common logarithm (base 10) of ``x``. + ## ## .. code-block:: nim ## echo log10(100.0) ## 2.0 proc exp*(x: float32): float32 {.importc: "expf", header: "<math.h>".} proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".} ## Computes the exponential function of ``x`` (pow(E, x)). + ## ## .. code-block:: nim ## echo exp(1.0) ## 2.718281828459045 ## echo ln(exp(4.0)) ## 4.0 proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".} proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".} ## Computes the sine of ``x``. + ## ## .. code-block:: nim ## echo sin(PI / 6) ## 0.4999999999999999 ## echo sin(degToRad(90.0)) ## 1.0 proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".} proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".} ## Computes the cosine of ``x``. + ## ## .. code-block:: nim ## echo cos(2 * PI) ## 1.0 ## echo cos(degToRad(60.0)) ## 0.5000000000000001 proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".} proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".} ## Computes the tangent of ``x``. + ## ## .. code-block:: nim ## echo tan(degToRad(45.0)) ## 0.9999999999999999 ## echo tan(PI / 4) ## 0.9999999999999999 proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".} proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".} ## Computes the `hyperbolic sine <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. + ## ## .. code-block:: nim ## echo sinh(1.0) ## 1.175201193643801 proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".} proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".} ## Computes the `hyperbolic cosine <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. + ## ## .. code-block:: nim ## echo cosh(1.0) ## 1.543080634815244 proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".} proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".} ## Computes the `hyperbolic tangent <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. + ## ## .. code-block:: nim ## echo tanh(1.0) ## 0.7615941559557649 proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".} proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".} ## Computes the arc cosine of ``x``. + ## ## .. code-block:: nim ## echo arccos(1.0) ## 0.0 proc arcsin*(x: float32): float32 {.importc: "asinf", header: "<math.h>".} @@ -250,6 +263,7 @@ when not defined(JS): # C proc arctan*(x: float32): float32 {.importc: "atanf", header: "<math.h>".} proc arctan*(x: float64): float64 {.importc: "atan", header: "<math.h>".} ## Calculate the arc tangent of ``x``. + ## ## .. code-block:: nim ## echo arctan(1.0) ## 0.7853981633974483 ## echo radToDeg(arctan(1.0)) ## 45.0 @@ -259,6 +273,7 @@ when not defined(JS): # C ## `arctan2` returns the arc tangent of ``y`` / ``x``; it produces correct ## results even when the resulting angle is near pi/2 or -pi/2 ## (``x`` near 0). + ## ## .. code-block:: nim ## echo arctan2(1.0, 0.0) ## 1.570796326794897 ## echo radToDeg(arctan2(1.0, 0.0)) ## 90.0 @@ -332,6 +347,7 @@ when not defined(JS): # C proc hypot*(x, y: float64): float64 {.importc: "hypot", header: "<math.h>".} ## Computes the hypotenuse of a right-angle triangle with ``x`` and ## ``y`` as its base and height. Equivalent to ``sqrt(x*x + y*y)``. + ## ## .. code-block:: nim ## echo hypot(4.0, 3.0) ## 5.0 proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".} @@ -339,6 +355,7 @@ when not defined(JS): # C ## computes x to power raised of y. ## ## To compute power between integers, use ``^`` e.g. 2 ^ 6 + ## ## .. code-block:: nim ## echo pow(16.0, 0.5) ## 4.0 @@ -361,7 +378,7 @@ when not defined(JS): # C ## **Deprecated since version 0.19.0**: Use ``gamma`` instead. proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".} proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".} - ## Computes the natural log of the gamma function for ``x``. + ## Computes the natural log of the gamma function for ``x``. proc floor*(x: float32): float32 {.importc: "floorf", header: "<math.h>".} proc floor*(x: float64): float64 {.importc: "floor", header: "<math.h>".} @@ -456,9 +473,14 @@ when not defined(JS): # C ## Computes the modulo operation for float values (the remainder of ``x`` divided by ``y``). ## ## .. code-block:: nim - ## echo 2.5 mod 0.3 ## 0.1 + ## ( 6.5 mod 2.5) == 1.5 + ## (-6.5 mod 2.5) == -1.5 + ## ( 6.5 mod -2.5) == 1.5 + ## (-6.5 mod -2.5) == -1.5 + else: # JS - proc hypot*[T: float32|float64](x, y: T): T = return sqrt(x*x + y*y) + proc hypot*(x, y: float32): float32 {.importc: "Math.hypot", varargs, nodecl.} + proc hypot*(x, y: float64): float64 {.importc: "Math.hypot", varargs, nodecl.} proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.} proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.} proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} @@ -474,7 +496,10 @@ else: # JS ## Computes the modulo operation for float values (the remainder of ``x`` divided by ``y``). ## ## .. code-block:: nim - ## echo 2.5 mod 0.3 ## 0.1 + ## ( 6.5 mod 2.5) == 1.5 + ## (-6.5 mod 2.5) == -1.5 + ## ( 6.5 mod -2.5) == 1.5 + ## (-6.5 mod -2.5) == -1.5 proc round*[T: float32|float64](x: T, places: int): T {.deprecated: "use format instead".} = ## Decimal rounding on a binary floating point number. @@ -498,19 +523,25 @@ proc floorDiv*[T: SomeInteger](x, y: T): T = ## This is different from the ``div`` operator, which is defined ## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` ## rounds down. + ## ## .. code-block:: nim - ## echo floorDiv(13, 3) # 4 - ## echo floorDiv(-13, 3) # -5 + ## echo floorDiv( 13, 3) # 4 + ## echo floorDiv(-13, 3) # -5 + ## echo floorDiv( 13, -3) # -5 + ## echo floorDiv(-13, -3) # 4 result = x div y let r = x mod y if (r > 0 and y < 0) or (r < 0 and y > 0): result.dec 1 proc floorMod*[T: SomeNumber](x, y: T): T = - ## Floor modulus is conceptually defined as ``x - (floorDiv(x, y) * y). + ## Floor modulus is conceptually defined as ``x - (floorDiv(x, y) * y)``. ## This proc behaves the same as the ``%`` operator in Python. + ## ## .. code-block:: nim - ## echo floorMod(13, 3) # 1 - ## echo floorMod(-13, 3) # 2 + ## echo floorMod( 13, 3) # 1 + ## echo floorMod(-13, 3) # 2 + ## echo floorMod( 13, -3) # -2 + ## echo floorMod(-13, -3) # -1 result = x mod y if (result > 0 and y < 0) or (result < 0 and y > 0): result += y @@ -525,6 +556,7 @@ when not defined(JS): ## and less than 1) and the integer value n such that ``x`` (the original ## float value) equals ``m * 2**n``. frexp stores n in `exponent` and returns ## m. + ## ## .. code-block:: nim ## var x : int ## echo frexp(5.0, x) # 0.625 @@ -579,6 +611,7 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## ## Both parts have the same sign as ``x``. Analogous to the ``modf`` ## function in C. + ## ## .. code-block:: nim ## echo splitDecimal(5.25) # (intpart: 5.0, floatpart: 0.25) var @@ -594,12 +627,14 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = proc degToRad*[T: float32|float64](d: T): T {.inline.} = ## Convert from degrees to radians + ## ## .. code-block:: nim ## echo degToRad(180.0) # 3.141592653589793 result = T(d) * RadPerDeg proc radToDeg*[T: float32|float64](d: T): T {.inline.} = ## Convert from radians to degrees + ## .. code-block:: nim ## echo degToRad(2 * PI) # 360.0 result = T(d) / RadPerDeg @@ -608,6 +643,7 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} = ## Sign function. Returns -1 for negative numbers and ``NegInf``, 1 for ## positive numbers and ``Inf``, and 0 for positive zero, negative zero and ## ``NaN``. + ## ## .. code-block:: nim ## echo sgn(-5) # 1 ## echo sgn(-4.1) # -1 @@ -619,6 +655,7 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} = proc `^`*[T](x: T, y: Natural): T = ## Computes ``x`` to the power ``y``. ``x`` must be non-negative, use ## `pow <#pow,float,float>`_ for negative exponents. + ## ## .. code-block:: nim ## echo 2 ^ 3 # 8 when compiles(y >= T(0)): @@ -650,6 +687,7 @@ proc gcd*[T](x, y: T): T = proc gcd*(x, y: SomeInteger): SomeInteger = ## Computes the greatest common (positive) divisor of ``x`` and ``y``. ## Using binary GCD (aka Stein's) algorithm. + ## ## .. code-block:: nim ## echo gcd(24, 30) # 6 when x is SomeSignedInt: @@ -677,6 +715,7 @@ proc gcd*(x, y: SomeInteger): SomeInteger = proc lcm*[T](x, y: T): T = ## Computes the least common multiple of ``x`` and ``y``. + ## ## .. code-block:: nim ## echo lcm(24, 30) # 120 x div gcd(x, y) * y diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 9fccd08d4..e5345e645 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -90,7 +90,7 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) = proc open*(filename: string, mode: FileMode = fmRead, mappedSize = -1, offset = 0, newFileSize = -1, allowRemap = false): MemFile = - ## opens a memory mapped file. If this fails, ``EOS`` is raised. + ## opens a memory mapped file. If this fails, ``OSError`` is raised. ## ## ``newFileSize`` can only be set if the file does not exist and is opened ## with write access (e.g., with fmReadWrite). @@ -141,7 +141,7 @@ proc open*(filename: string, mode: FileMode = fmRead, if result.mapHandle != 0: discard closeHandle(result.mapHandle) raiseOSError(errCode) # return false - #raise newException(EIO, msg) + #raise newException(IOError, msg) template callCreateFile(winApiProc, filename): untyped = winApiProc( @@ -465,7 +465,7 @@ proc mmsWriteData(s: Stream, buffer: pointer, bufLen: int) = proc newMemMapFileStream*(filename: string, mode: FileMode = fmRead, fileSize: int = -1): MemMapFileStream = ## creates a new stream from the file named `filename` with the mode `mode`. - ## Raises ## `EOS` if the file cannot be opened. See the `system + ## Raises ## `OSError` if the file cannot be opened. See the `system ## <system.html>`_ module for a list of available FileMode enums. ## ``fileSize`` can only be set if the file does not exist and is opened ## with write access (e.g., with fmReadWrite). diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 514b8d66a..b091f7310 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -221,7 +221,7 @@ proc close*(socket: SocketHandle) = discard winlean.closesocket(socket) else: discard posix.close(socket) - # TODO: These values should not be discarded. An EOS should be raised. + # TODO: These values should not be discarded. An OSError should be raised. # http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times proc bindAddr*(socket: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint = @@ -594,7 +594,7 @@ proc setSockOptInt*(socket: SocketHandle, level, optname, optval: int) {. proc setBlocking*(s: SocketHandle, blocking: bool) = ## Sets blocking mode on socket. ## - ## Raises EOS on error. + ## Raises OSError on error. when useWinVersion: var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking if ioctlsocket(s, FIONBIO, addr(mode)) == -1: diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 179fccaa3..23cd96b20 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -210,7 +210,7 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET, proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket = ## Creates a new socket. ## - ## If an error occurs EOS will be raised. + ## If an error occurs OSError will be raised. let fd = createNativeSocket(domain, sockType, protocol) if fd == osInvalidSocket: raiseOSError(osLastError()) @@ -221,7 +221,7 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, protocol: Protocol = IPPROTO_TCP, buffered = true): Socket = ## Creates a new socket. ## - ## If an error occurs EOS will be raised. + ## If an error occurs OSError will be raised. let fd = createNativeSocket(domain, sockType, protocol) if fd == osInvalidSocket: raiseOSError(osLastError()) @@ -229,7 +229,7 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, proc parseIPv4Address(addressStr: string): IpAddress = ## Parses IPv4 adresses - ## Raises EInvalidValue on errors + ## Raises ValueError on errors var byteCount = 0 currentByte:uint16 = 0 @@ -263,7 +263,7 @@ proc parseIPv4Address(addressStr: string): IpAddress = proc parseIPv6Address(addressStr: string): IpAddress = ## Parses IPv6 adresses - ## Raises EInvalidValue on errors + ## Raises ValueError on errors result.family = IpAddressFamily.IPv6 if addressStr.len < 2: raise newException(ValueError, "Invalid IP Address") @@ -384,7 +384,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = proc parseIpAddress*(addressStr: string): IpAddress = ## Parses an IP address - ## Raises EInvalidValue on error + ## Raises ValueError on error if addressStr.len == 0: raise newException(ValueError, "IP Address string is empty") if addressStr.contains(':'): @@ -746,7 +746,7 @@ proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. ## - ## Raises an EOS error upon failure. + ## Raises an OSError error upon failure. if nativesockets.listen(socket.fd, backlog) < 0'i32: raiseOSError(osLastError()) @@ -777,7 +777,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string, ## Blocks until a connection is being made from a client. When a connection ## is made sets ``client`` to the client socket and ``address`` to the address ## of the connecting client. - ## This function will raise EOS if an error occurs. + ## This function will raise OSError if an error occurs. ## ## The resulting client will inherit any properties of the server socket. For ## example: whether the socket is buffered or not. @@ -983,7 +983,7 @@ when defined(ssl): ## Returns ``False`` whenever the socket is not yet ready for a handshake, ## ``True`` whenever handshake completed successfully. ## - ## A ESSL error is raised on any other errors. + ## A SslError error is raised on any other errors. ## ## **Note:** This procedure is deprecated since version 0.14.0. result = true @@ -1011,7 +1011,7 @@ when defined(ssl): ## Determines whether a handshake has occurred between a client (``socket``) ## and the server that ``socket`` is connected to. ## - ## Throws ESSL if ``socket`` is not an SSL socket. + ## Throws SslError if ``socket`` is not an SSL socket. if socket.isSSL: return not socket.sslNoHandshake else: @@ -1203,7 +1203,7 @@ proc recv*(socket: Socket, size: int, timeout = -1, ## ## When ``""`` is returned the socket's connection has been closed. ## - ## This function will throw an EOS exception when an error occurs. + ## This function will throw an OSError exception when an error occurs. ## ## A timeout may be specified in milliseconds, if enough data is not received ## within the time specified an ETimeout exception will be raised. @@ -1244,7 +1244,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1, ## ## If the socket is disconnected, ``line`` will be set to ``""``. ## - ## An EOS exception will be raised in the case of a socket error. + ## An OSError exception will be raised in the case of a socket error. ## ## A timeout can be specified in milliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. @@ -1299,7 +1299,7 @@ proc recvLine*(socket: Socket, timeout = -1, ## ## If the socket is disconnected, the result will be set to ``""``. ## - ## An EOS exception will be raised in the case of a socket error. + ## An OSError exception will be raised in the case of a socket error. ## ## A timeout can be specified in milliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. @@ -1317,7 +1317,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int, ## Receives data from ``socket``. This function should normally be used with ## connection-less sockets (UDP sockets). ## - ## If an error occurs an EOS exception will be raised. Otherwise the return + ## If an error occurs an OSError exception will be raised. Otherwise the return ## value will be the length of data received. ## ## **Warning:** This function does not yet have a buffered implementation, @@ -1390,7 +1390,7 @@ template `&=`*(socket: Socket; data: typed) = send(socket, data) proc trySend*(socket: Socket, data: string): bool {.tags: [WriteIOEffect].} = - ## Safe alternative to ``send``. Does not raise an EOS when an error occurs, + ## Safe alternative to ``send``. Does not raise an OSError when an error occurs, ## and instead returns ``false`` on failure. result = send(socket, cstring(data), data.len) == data.len diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index 506c6bfaa..27b7d3eca 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -118,13 +118,13 @@ when defined(memProfiler): var gTicker {.threadvar.}: int - proc requestedHook(): bool {.nimcall.} = + proc requestedHook(): bool {.nimcall, locks: 0.} = if gTicker == 0: gTicker = SamplingInterval result = true dec gTicker - proc hook(st: StackTrace, size: int) {.nimcall.} = + proc hook(st: StackTrace, size: int) {.nimcall, locks: 0.} = when defined(ignoreAllocationSize): hookAux(st, 1) else: @@ -136,7 +136,7 @@ else: gTicker: int # we use an additional counter to # avoid calling 'getTicks' too frequently - proc requestedHook(): bool {.nimcall.} = + proc requestedHook(): bool {.nimcall, locks: 0.} = if interval == 0: result = true elif gTicker == 0: gTicker = 500 @@ -145,7 +145,7 @@ else: else: dec gTicker - proc hook(st: StackTrace) {.nimcall.} = + proc hook(st: StackTrace) {.nimcall, locks: 0.} = #echo "profiling! ", interval if interval == 0: hookAux(st, 1) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6d1af04c8..e2dd872e8 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -17,33 +17,701 @@ include "system/inclrtl" import - strutils, times + strutils -when defined(windows): - import winlean +when defined(nimscript): + discard +elif defined(windows): + import winlean, times elif defined(posix): - import posix + import posix, times proc toTime(ts: Timespec): times.Time {.inline.} = result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) - else: {.error: "OS module not ported to your operating system!".} -import ospaths -export ospaths +when defined(nimscript) and defined(nimErrorProcCanHaveBody): + {.pragma: noNimScript, error: "this proc is not available on the NimScript target".} +else: + {.pragma: noNimScript.} + +type + ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read + ## from an environment variable + WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write + ## to an environment variable -proc c_rename(oldname, newname: cstring): cint {. - importc: "rename", header: "<stdio.h>".} -proc c_system(cmd: cstring): cint {. - importc: "system", header: "<stdlib.h>".} -proc c_strlen(a: cstring): cint {. - importc: "strlen", header: "<string.h>", noSideEffect.} -proc c_free(p: pointer) {. - importc: "free", header: "<stdlib.h>".} + ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read + ## operation from the directory + ## structure + WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write + ## operation to + ## the directory structure + OSErrorCode* = distinct int32 ## Specifies an OS Error Code. -when defined(windows): +const + doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) + +when defined(Nimdoc): # only for proper documentation: + const + CurDir* = '.' + ## The constant string used by the operating system to refer to the + ## current directory. + ## + ## For example: '.' for POSIX or ':' for the classic Macintosh. + + ParDir* = ".." + ## The constant string used by the operating system to refer to the + ## parent directory. + ## + ## For example: ".." for POSIX or "::" for the classic Macintosh. + + DirSep* = '/' + ## The character used by the operating system to separate pathname + ## components, for example, '/' for POSIX or ':' for the classic + ## Macintosh. + + AltSep* = '/' + ## An alternative character used by the operating system to separate + ## pathname components, or the same as `DirSep` if only one separator + ## character exists. This is set to '/' on Windows systems + ## where `DirSep` is a backslash. + + PathSep* = ':' + ## The character conventionally used by the operating system to separate + ## search patch components (as in PATH), such as ':' for POSIX + ## or ';' for Windows. + + FileSystemCaseSensitive* = true + ## true if the file system is case sensitive, false otherwise. Used by + ## `cmpPaths` to compare filenames properly. + + ExeExt* = "" + ## The file extension of native executables. For example: + ## "" for POSIX, "exe" on Windows. + + ScriptExt* = "" + ## The file extension of a script file. For example: "" for POSIX, + ## "bat" on Windows. + + DynlibFormat* = "lib$1.so" + ## The format string to turn a filename into a `DLL`:idx: file (also + ## called `shared object`:idx: on some operating systems). + +elif defined(macos): + const + CurDir* = ':' + ParDir* = "::" + DirSep* = ':' + AltSep* = Dirsep + PathSep* = ',' + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.dylib" + + # MacOS paths + # =========== + # MacOS directory separator is a colon ":" which is the only character not + # allowed in filenames. + # + # A path containing no colon or which begins with a colon is a partial + # path. + # E.g. ":kalle:petter" ":kalle" "kalle" + # + # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" + # When generating paths, one is safe if one ensures that all partial paths + # begin with a colon, and all full paths end with a colon. + # In full paths the first name (e g HD above) is the name of a mounted + # volume. + # These names are not unique, because, for instance, two diskettes with the + # same names could be inserted. This means that paths on MacOS are not + # waterproof. In case of equal names the first volume found will do. + # Two colons "::" are the relative path to the parent. Three is to the + # grandparent etc. +elif doslikeFileSystem: + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '\\' # seperator within paths + AltSep* = '/' + PathSep* = ';' # seperator between paths + FileSystemCaseSensitive* = false + ExeExt* = "exe" + ScriptExt* = "bat" + DynlibFormat* = "$1.dll" +elif defined(PalmOS) or defined(MorphOS): + const + DirSep* = '/' + AltSep* = Dirsep + PathSep* = ';' + ParDir* = ".." + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.prc" +elif defined(RISCOS): + const + DirSep* = '.' + AltSep* = '.' + ParDir* = ".." # is this correct? + PathSep* = ',' + FileSystemCaseSensitive* = true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "lib$1.so" +else: # UNIX-like operating system + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '/' + AltSep* = DirSep + PathSep* = ':' + FileSystemCaseSensitive* = when defined(macosx): false else: true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" + +const + ExtSep* = '.' + ## The character which separates the base filename from the extension; + ## for example, the '.' in ``os.nim``. + +proc normalizePathEnd(path: var string, trailingSep = false) = + ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on + ## ``trailingSep``, and taking care of edge cases: it preservers whether + ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, + ## not `AltSep`. + if path.len == 0: return + var i = path.len + while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i) + if trailingSep: + # foo// => foo + path.setLen(i) + # foo => foo/ + path.add DirSep + elif i>0: + # foo// => foo + path.setLen(i) + else: + # // => / (empty case was already taken care of) + path = $DirSep + +proc normalizePathEnd(path: string, trailingSep = false): string = + result = path + result.normalizePathEnd(trailingSep) + +proc joinPath*(head, tail: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Joins two directory names to one. + ## + ## For example on Unix: + ## + ## .. code-block:: nim + ## joinPath("usr", "lib") + ## + ## results in: + ## + ## .. code-block:: nim + ## "usr/lib" + ## + ## If head is the empty string, tail is returned. If tail is the empty + ## string, head is returned with a trailing path separator. If tail starts + ## with a path separator it will be removed when concatenated to head. Other + ## path separators not located on boundaries won't be modified. More + ## examples on Unix: + ## + ## .. code-block:: nim + ## assert joinPath("usr", "") == "usr/" + ## assert joinPath("", "lib") == "lib" + ## assert joinPath("", "/lib") == "/lib" + ## assert joinPath("usr/", "/lib") == "usr/lib" + if len(head) == 0: + result = tail + elif head[len(head)-1] in {DirSep, AltSep}: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: + result = head & substr(tail, 1) + else: + result = head & tail + else: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: + result = head & tail + else: + result = head & DirSep & tail + +proc joinPath*(parts: varargs[string]): string {.noSideEffect, + rtl, extern: "nos$1OpenArray".} = + ## The same as `joinPath(head, tail)`, but works with any number of + ## directory parts. You need to pass at least one element or the proc + ## will assert in debug builds and crash on release builds. + result = parts[0] + for i in 1..high(parts): + result = joinPath(result, parts[i]) + +proc `/` * (head, tail: string): string {.noSideEffect.} = + ## The same as ``joinPath(head, tail)`` + ## + ## Here are some examples for Unix: + ## + ## .. code-block:: nim + ## assert "usr" / "" == "usr/" + ## assert "" / "lib" == "lib" + ## assert "" / "/lib" == "/lib" + ## assert "usr/" / "/lib" == "usr/lib" + return joinPath(head, tail) + +proc splitPath*(path: string): tuple[head, tail: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a directory into (head, tail), so that + ## ``head / tail == path`` (except for edge cases like "/usr"). + ## + ## Examples: + ## + ## .. code-block:: nim + ## splitPath("usr/local/bin") -> ("usr/local", "bin") + ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") + ## splitPath("bin") -> ("", "bin") + ## splitPath("/bin") -> ("", "bin") + ## splitPath("") -> ("", "") + var sepPos = -1 + for i in countdown(len(path)-1, 0): + if path[i] in {DirSep, AltSep}: + sepPos = i + break + if sepPos >= 0: + result.head = substr(path, 0, sepPos-1) + result.tail = substr(path, sepPos+1) + else: + result.head = "" + result.tail = path + +proc parentDirPos(path: string): int = + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in countdown(len(path)-q, 0): + if path[i] in {DirSep, AltSep}: return i + result = -1 + +proc parentDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the parent directory of `path`. + ## + ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end + ## in a dir separator. + ## The remainder can be obtained with ``lastPathPart(path)`` + runnableExamples: + doAssert parentDir("") == "" + when defined(posix): + doAssert parentDir("/usr/local/bin") == "/usr/local" + doAssert parentDir("foo/bar/") == "foo" + + let sepPos = parentDirPos(path) + if sepPos >= 0: + result = substr(path, 0, sepPos-1) + else: + result = "" + +proc tailDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the tail part of `path`.. + ## + ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. + ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. + ## | Example: ``tailDir("bin") == ""``. + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in 0..len(path)-q: + if path[i] in {DirSep, AltSep}: + return substr(path, i+1) + result = "" + +proc isRootDir*(path: string): bool {. + noSideEffect, rtl, extern: "nos$1".} = + ## Checks whether a given `path` is a root directory + result = parentDirPos(path) < 0 + +iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = + ## Walks over all parent directories of a given `path` + ## + ## If `fromRoot` is set, the traversal will start from the file system root + ## diretory. If `inclusive` is set, the original argument will be included + ## in the traversal. + ## + ## Relative paths won't be expanded by this proc. Instead, it will traverse + ## only the directories appearing in the relative path. + if not fromRoot: + var current = path + if inclusive: yield path + while true: + if current.isRootDir: break + current = current.parentDir + yield current + else: + for i in countup(0, path.len - 2): # ignore the last / + # deal with non-normalized paths such as /foo//bar//baz + if path[i] in {DirSep, AltSep} and + (i == 0 or path[i-1] notin {DirSep, AltSep}): + yield path.substr(0, i) + + if inclusive: yield path + +proc `/../`*(head, tail: string): string {.noSideEffect.} = + ## The same as ``parentDir(head) / tail`` unless there is no parent + ## directory. Then ``head / tail`` is performed instead. + let sepPos = parentDirPos(head) + if sepPos >= 0: + result = substr(head, 0, sepPos-1) / tail + else: + result = head / tail + +proc normExt(ext: string): string = + if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here + else: result = ExtSep & ext + +proc searchExtPos*(path: string): int = + ## Returns index of the '.' char in `path` if it signifies the beginning + ## of extension. Returns -1 otherwise. + # BUGFIX: do not search until 0! .DS_Store is no file extension! + result = -1 + for i in countdown(len(path)-1, 1): + if path[i] == ExtSep: + result = i + break + elif path[i] in {DirSep, AltSep}: + break # do not skip over path + +proc splitFile*(path: string): tuple[dir, name, ext: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a filename into (dir, filename, extension). + ## `dir` does not end in `DirSep`. + ## `extension` includes the leading dot. + ## + ## Example: + ## + ## .. code-block:: nim + ## var (dir, name, ext) = splitFile("usr/local/nimc.html") + ## assert dir == "usr/local" + ## assert name == "nimc" + ## assert ext == ".html" + ## + ## If `path` has no extension, `ext` is the empty string. + ## If `path` has no directory component, `dir` is the empty string. + ## If `path` has no filename component, `name` and `ext` are empty strings. + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = (path, "", "") + else: + var sepPos = -1 + var dotPos = path.len + for i in countdown(len(path)-1, 0): + if path[i] == ExtSep: + if dotPos == path.len and i > 0 and + path[i-1] notin {DirSep, AltSep}: dotPos = i + elif path[i] in {DirSep, AltSep}: + sepPos = i + break + result.dir = substr(path, 0, sepPos-1) + result.name = substr(path, sepPos+1, dotPos-1) + result.ext = substr(path, dotPos) + +proc extractFilename*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Extracts the filename of a given `path`. This is the same as + ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``. + runnableExamples: + when defined(posix): + doAssert extractFilename("foo/bar/") == "" + doAssert extractFilename("foo/bar") == "bar" + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = "" + else: + result = splitPath(path).tail + +proc lastPathPart*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## like ``extractFilename``, but ignores trailing dir separator; aka: `baseName`:idx: + ## in some other languages. + runnableExamples: + when defined(posix): + doAssert lastPathPart("foo/bar/") == "bar" + let path = path.normalizePathEnd(trailingSep = false) + result = extractFilename(path) + +proc changeFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Changes the file extension to `ext`. + ## + ## If the `filename` has no extension, `ext` will be added. + ## If `ext` == "" then any extension is removed. + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. (Although I know + ## of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = substr(filename, 0, extPos-1) & normExt(ext) + +proc addFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Adds the file extension `ext` to `filename`, unless + ## `filename` already has an extension. + ## + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. + ## (Although I know of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = filename + +proc cmpPaths*(pathA, pathB: string): int {. + noSideEffect, rtl, extern: "nos$1".} = + ## Compares two paths. + ## + ## On a case-sensitive filesystem this is done + ## case-sensitively otherwise case-insensitively. Returns: + ## + ## | 0 iff pathA == pathB + ## | < 0 iff pathA < pathB + ## | > 0 iff pathA > pathB + runnableExamples: + when defined(macosx): + doAssert cmpPaths("foo", "Foo") == 0 + elif defined(posix): + doAssert cmpPaths("foo", "Foo") > 0 + if FileSystemCaseSensitive: + result = cmp(pathA, pathB) + else: + when defined(nimscript): + result = cmpic(pathA, pathB) + elif defined(nimdoc): discard + else: + result = cmpIgnoreCase(pathA, pathB) + +proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = + ## Checks whether a given `path` is absolute. + ## + ## On Windows, network paths are considered absolute too. + runnableExamples: + doAssert(not "".isAbsolute) + doAssert(not ".".isAbsolute) + when defined(posix): + doAssert "/".isAbsolute + doAssert(not "a/".isAbsolute) + + if len(path) == 0: return false + + when doslikeFileSystem: + var len = len(path) + result = (path[0] in {'/', '\\'}) or + (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') + elif defined(macos): + # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path + result = path[0] != ':' + elif defined(RISCOS): + result = path[0] == '$' + elif defined(posix): + result = path[0] == '/' + +proc unixToNativePath*(path: string, drive=""): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Converts an UNIX-like path to a native one. + ## + ## On an UNIX system this does nothing. Else it converts + ## '/', '.', '..' to the appropriate things. + ## + ## On systems with a concept of "drives", `drive` is used to determine + ## which drive label to use during absolute path conversion. + ## `drive` defaults to the drive of the current working directory, and is + ## ignored on systems that do not have a concept of "drives". + + when defined(unix): + result = path + else: + if path.len == 0: + return "" + + var start: int + if path[0] == '/': + # an absolute path + when doslikeFileSystem: + if drive != "": + result = drive & ":" & DirSep + else: + result = $DirSep + elif defined(macos): + result = "" # must not start with ':' + else: + result = $DirSep + start = 1 + elif path[0] == '.' and (path.len == 1 or path[1] == '/'): + # current directory + result = $CurDir + start = when doslikeFileSystem: 1 else: 2 + else: + result = "" + start = 0 + + var i = start + while i < len(path): # ../../../ --> :::: + if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': + # parent directory + when defined(macos): + if result[high(result)] == ':': + add result, ':' + else: + add result, ParDir + else: + add result, ParDir & DirSep + inc(i, 3) + elif path[i] == '/': + add result, DirSep + inc(i) + else: + add result, path[i] + inc(i) + +include "includes/oserr" +when not defined(nimscript): + include "includes/osenv" + +proc getHomeDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the home directory of the current user. + ## + ## This proc is wrapped by the expandTilde proc for the convenience of + ## processing paths coming from user configuration files. + when defined(windows): return string(getEnv("USERPROFILE")) & "\\" + else: return string(getEnv("HOME")) & "/" + +proc getConfigDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the config directory of the current user for applications. + ## + ## On non-Windows OSs, this proc conforms to the XDG Base Directory + ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment + ## variable if it is set, and returns the default configuration directory, + ## "~/.config/", otherwise. + ## + ## An OS-dependent trailing slash is always present at the end of the + ## returned string; `\` on Windows and `/` on all other OSs. + when defined(windows): + result = getEnv("APPDATA").string + else: + result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string + result.normalizePathEnd(trailingSep = true) + +proc getTempDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the temporary directory of the current user for applications to + ## save temporary files in. + ## + ## **Please do not use this**: On Android, it currently + ## returns ``getHomeDir()``, and on other Unix based systems it can cause + ## security problems too. That said, you can override this implementation + ## by adding ``-d:tempDir=mytempname`` to your compiler invokation. + when defined(tempDir): + const tempDir {.strdefine.}: string = nil + return tempDir + elif defined(windows): return string(getEnv("TEMP")) & "\\" + elif defined(android): return getHomeDir() + else: return "/tmp/" + +proc expandTilde*(path: string): string {. + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing + ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). + ## + ## Windows: this is still supported despite Windows platform not having this + ## convention; also, both ``~/`` and ``~\`` are handled. + runnableExamples: + doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" + if len(path) == 0 or path[0] != '~': + result = path + elif len(path) == 1: + result = getHomeDir() + elif (path[1] in {DirSep, AltSep}): + result = getHomeDir() / path.substr(2) + else: + # TODO: handle `~bob` and `~bob/` which means home of bob + result = path + +# TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand +# belong in `strutils` instead; they are not specific to paths +proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote s, so it can be safely passed to Windows API. + ## Based on Python's subprocess.list2cmdline + ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + let needQuote = {' ', '\t'} in s or s.len == 0 + + result = "" + var backslashBuff = "" + if needQuote: + result.add("\"") + + for c in s: + if c == '\\': + backslashBuff.add(c) + elif c == '\"': + result.add(backslashBuff) + result.add(backslashBuff) + backslashBuff.setLen(0) + result.add("\\\"") + else: + if backslashBuff.len != 0: + result.add(backslashBuff) + backslashBuff.setLen(0) + result.add(c) + + if needQuote: + result.add("\"") + +proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to POSIX shell. + ## Based on Python's pipes.quote + const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', + '0'..'9', 'A'..'Z', 'a'..'z'} + if s.len == 0: + return "''" + + let safe = s.allCharsInSet(safeUnixChars) + + if safe: + return s + else: + return "'" & s.replace("'", "'\"'\"'") & "'" + +when defined(windows) or defined(posix) or defined(nintendoswitch): + proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to shell. + when defined(windows): + return quoteShellWindows(s) + else: + return quoteShellPosix(s) + + proc quoteShellCommand*(args: openArray[string]): string = + ## Concatenates and quotes shell arguments `args` + runnableExamples: + when defined(posix): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" + when defined(windows): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" + # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 + for i in 0..<args.len: + if i > 0: result.add " " + result.add quoteShell(args[i]) + +when not defined(nimscript): + proc c_rename(oldname, newname: cstring): cint {. + importc: "rename", header: "<stdio.h>".} + proc c_system(cmd: cstring): cint {. + importc: "system", header: "<stdlib.h>".} + proc c_strlen(a: cstring): cint {. + importc: "strlen", header: "<string.h>", noSideEffect.} + proc c_free(p: pointer) {. + importc: "free", header: "<stdlib.h>".} + + +when defined(windows) and not defined(nimscript): when useWinUnicode: template wrapUnary(varname, winApiProc, arg: untyped) = var varname = winApiProc(newWideCString(arg)) @@ -71,9 +739,10 @@ when defined(windows): f.cFileName[1].int == dot and f.cFileName[2].int == 0) proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns true if `filename` exists and is a regular file or symlink. ## (directories, device files, named pipes and sockets return false) + ## This proc is not available for NimScript. when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, filename) @@ -85,7 +754,8 @@ 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 existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], + noNimScript.} = ## Returns true iff the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. when defined(windows): @@ -100,7 +770,8 @@ proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect] return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], + noNimScript.} = ## Returns true iff the symlink `link` exists. Will return true ## regardless of whether the link points to a directory or file. when defined(windows): @@ -114,15 +785,15 @@ 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.} = +proc fileExists*(filename: string): bool {.inline, noNimScript.} = ## Synonym for existsFile existsFile(filename) -proc dirExists*(dir: string): bool {.inline.} = +proc dirExists*(dir: string): bool {.inline, noNimScript.} = ## Synonym for existsDir existsDir(dir) -when not defined(windows): +when not defined(windows) and not defined(nimscript): proc checkSymlink(path: string): bool = var rawInfo: Stat if lstat(path, rawInfo) < 0'i32: result = false @@ -135,7 +806,7 @@ const proc findExe*(exe: string, followSymlinks: bool = true; extensions: openarray[string]=ExeExts): string {. - tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = + tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimScript.} = ## Searches for `exe` in the current working directory and then ## in directories listed in the ``PATH`` environment variable. ## Returns "" if the `exe` cannot be found. `exe` @@ -183,7 +854,11 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +when defined(nimscript): + const times = "fake const" + template Time(x: untyped): untyped = string + +proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last modification time. when defined(posix): var res: Stat @@ -196,7 +871,7 @@ proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} result = fromWinTime(rdFileTime(f.ftLastWriteTime)) findClose(h) -proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: Stat @@ -209,7 +884,7 @@ proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = result = fromWinTime(rdFileTime(f.ftLastAccessTime)) findClose(h) -proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s creation time. ## ## **Note:** Under POSIX OS's, the returned time may actually be the time at @@ -226,7 +901,7 @@ proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = result = fromWinTime(rdFileTime(f.ftCreationTime)) findClose(h) -proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = +proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} = ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s ## modification time is later than `b`'s. when defined(posix): @@ -238,7 +913,7 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = else: result = getLastModificationTime(a) > getLastModificationTime(b) -proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = +proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns the `current working directory`:idx:. when defined(windows): var bufsize = MAX_PATH.int32 @@ -282,7 +957,7 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = else: raiseOSError(osLastError()) -proc setCurrentDir*(newDir: string) {.inline, tags: [].} = +proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = ## Sets the `current working directory`:idx:; `OSError` is raised if ## `newDir` cannot been set. when defined(Windows): @@ -294,28 +969,20 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -proc absolutePath*(path: string, root = getCurrentDir()): string = - ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) - ## if `path` is absolute, return it, ignoring `root` - runnableExamples: - doAssert absolutePath("a") == getCurrentDir() / "a" - if isAbsolute(path): path - else: - if not root.isAbsolute: - raise newException(ValueError, "The specified root is not absolute: " & root) - joinPath(root, path) - -when isMainModule: - doAssertRaises(ValueError): discard absolutePath("a", "b") - doAssert absolutePath("a") == getCurrentDir() / "a" - doAssert absolutePath("a", "/b") == "/b" / "a" - when defined(Posix): - doAssert absolutePath("a", "/b/") == "/b" / "a" - doAssert absolutePath("a", "/b/c") == "/b/c" / "a" - doAssert absolutePath("/a", "b/") == "/a" +when not defined(nimscript): + proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} = + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) + ## if `path` is absolute, return it, ignoring `root` + runnableExamples: + doAssert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path + else: + if not root.isAbsolute: + raise newException(ValueError, "The specified root is not absolute: " & root) + joinPath(root, path) proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns the full (`absolute`:idx:) path of an existing file `filename`, ## raises OSError in case of an error. Follows symlinks. when defined(windows): @@ -356,7 +1023,7 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", result = $r c_free(cast[pointer](r)) -proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = +proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Normalize a path. ## ## Consecutive directory separators are collapsed, including an initial double slash. @@ -392,12 +1059,12 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = else: path = "." -proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [].} = +proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns a normalized path for the current OS. See `<#normalizePath>`_ result = path normalizePath(result) -when defined(Windows): +when defined(Windows) and not defined(nimscript): proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: @@ -418,7 +1085,7 @@ when defined(Windows): ) proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns true if both pathname arguments refer to the same physical ## file or directory. Raises an exception if any of the files does not ## exist or information about it can not be obtained. @@ -458,7 +1125,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", result = a.st_dev == b.st_dev and a.st_ino == b.st_ino proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadIOEffect].} = + tags: [ReadIOEffect], noNimScript.} = ## Returns true if both pathname arguments refer to files with identical ## binary content. const @@ -501,7 +1168,7 @@ type fpOthersRead ## read access for others proc getFilePermissions*(filename: string): set[FilePermission] {. - rtl, extern: "nos$1", tags: [ReadDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = ## retrieves file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is checked, every other ## permission is available in any case. @@ -533,7 +1200,7 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. result = {fpUserExec..fpOthersRead} proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [WriteDirEffect].} = + rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## sets the file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is changed, depending on ## ``fpUserWrite``. @@ -569,7 +1236,7 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. if res2 == - 1'i32: raiseOSError(osLastError()) proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", - tags: [ReadIOEffect, WriteIOEffect].} = + tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Copies a file from `source` to `dest`. ## ## If this fails, `OSError` is raised. On the Windows platform this proc will @@ -620,7 +1287,7 @@ when not declared(ENOENT) and not defined(Windows): else: var ENOENT {.importc, header: "<errno.h>".}: cint -when defined(Windows): +when defined(Windows) and not defined(nimscript): when useWinUnicode: template deleteFile(file: untyped): untyped = deleteFileW(file) template setFileAttributes(file, attrs: untyped): untyped = @@ -630,7 +1297,7 @@ when defined(Windows): template setFileAttributes(file, attrs: untyped): untyped = setFileAttributesA(file, attrs) -proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect].} = +proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## Removes the `file`. If this fails, returns `false`. This does not fail ## if the file never existed in the first place. ## On Windows, ignores the read-only attribute. @@ -653,7 +1320,7 @@ proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirE if unlink(file) != 0'i32 and errno != ENOENT: result = false -proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} = +proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## Removes the `file`. If this fails, `OSError` is raised. This does not fail ## if the file never existed in the first place. ## On Windows, ignores the read-only attribute. @@ -663,7 +1330,7 @@ proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} else: raiseOSError(osLastError(), $strerror(errno)) -proc tryMoveFSObject(source, dest: string): bool = +proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} = ## Moves a file or directory from `source` to `dest`. Returns false in case ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns ## true in case of success. @@ -684,7 +1351,7 @@ proc tryMoveFSObject(source, dest: string): bool = return true proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", - tags: [ReadIOEffect, WriteIOEffect].} = + tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised. ## Can be used to `rename files`:idx: if not tryMoveFSObject(source, dest): @@ -698,7 +1365,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", raise proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", - tags: [ExecIOEffect].} = + tags: [ExecIOEffect], noNimScript.} = ## Executes a `shell command`:idx:. ## ## Command has the form 'program args' where args are the command @@ -713,7 +1380,7 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", result = c_system(command) # Templates for filtering directories and files -when defined(windows): +when defined(windows) and not defined(nimscript): template isDir(f: WIN32_FIND_DATA): bool = (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 template isFile(f: WIN32_FIND_DATA): bool = @@ -769,7 +1436,7 @@ template walkCommon(pattern: string, filter) = if filter(path): yield path -iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the files and directories that match the `pattern`. ## On POSIX this uses the `glob`:idx: call. ## @@ -777,7 +1444,7 @@ iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} = ## notation is supported. walkCommon(pattern, defaultWalkFilter) -iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the files that match the `pattern`. On POSIX this uses ## the `glob`:idx: call. ## @@ -785,7 +1452,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = ## notation is supported. walkCommon(pattern, isFile) -iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the directories that match the `pattern`. ## On POSIX this uses the `glob`:idx: call. ## @@ -800,7 +1467,7 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory -when defined(posix): +when defined(posix) and not defined(nimscript): proc getSymlinkFileKind(path: string): PathComponent = # Helper function. var s: Stat @@ -841,7 +1508,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) else: - when defined(windows): + when defined(nimscript): + for k, v in items(staticWalkDir(dir, relative)): + yield (k, v) + elif defined(windows): var f: WIN32_FIND_DATA var h = findFirstFile(dir / "*", f) if h != -1: @@ -929,7 +1599,7 @@ iterator walkDirRec*(dir: string, yieldFilter = {pcFile}, if k in yieldFilter: yield p -proc rawRemoveDir(dir: string) = +proc rawRemoveDir(dir: string) {.noNimScript.} = when defined(windows): when useWinUnicode: wrapUnary(res, removeDirectoryW, dir) @@ -943,7 +1613,7 @@ proc rawRemoveDir(dir: string) = if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError()) proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ - WriteDirEffect, ReadDirEffect], benign.} = + WriteDirEffect, ReadDirEffect], benign, noNimScript.} = ## Removes the directory `dir` including all subdirectories and files ## in `dir` (recursively). ## @@ -955,7 +1625,7 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ of pcDir: removeDir(path) rawRemoveDir(dir) -proc rawCreateDir(dir: string): bool = +proc rawCreateDir(dir: string): bool {.noNimScript.} = # Try to create one directory (not the whole path). # returns `true` for success, `false` if the path has previously existed # @@ -1000,7 +1670,7 @@ proc rawCreateDir(dir: string): bool = raiseOSError(osLastError(), dir) proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", - tags: [WriteDirEffect, ReadDirEffect].} = + tags: [WriteDirEffect, ReadDirEffect], noNimScript.} = ## Check if a `directory`:idx: `dir` exists, and create it otherwise. ## ## Does not create parent directories (fails if parent does not exist). @@ -1013,7 +1683,7 @@ proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", raise newException(IOError, "Failed to create '" & dir & "'") proc createDir*(dir: string) {.rtl, extern: "nos$1", - tags: [WriteDirEffect, ReadDirEffect].} = + tags: [WriteDirEffect, ReadDirEffect], noNimScript.} = ## Creates the `directory`:idx: `dir`. ## ## The directory may contain several subdirectories that do not exist yet. @@ -1036,7 +1706,7 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", discard existsOrCreateDir(dir) proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", - tags: [WriteIOEffect, ReadIOEffect], benign.} = + tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = ## Copies a directory from `source` to `dest`. ## ## If this fails, `OSError` is raised. On the Windows platform this proc will @@ -1054,7 +1724,7 @@ proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", copyDir(path, dest / noSource) else: discard -proc createSymlink*(src, dest: string) = +proc createSymlink*(src, dest: string) {.noNimScript.} = ## Create a symbolic link at `dest` which points to the item specified ## by `src`. On most operating systems, will fail if a link already exists. ## @@ -1077,7 +1747,7 @@ proc createSymlink*(src, dest: string) = if symlink(src, dest) != 0: raiseOSError(osLastError()) -proc createHardlink*(src, dest: string) = +proc createHardlink*(src, dest: string) {.noNimScript.} = ## Create a hard link at `dest` which points to the item specified ## by `src`. ## @@ -1185,7 +1855,7 @@ proc parseCmdLine*(c: string): seq[string] {. add(result, a) proc copyFileWithPermissions*(source, dest: string, - ignorePermissionErrors = true) = + ignorePermissionErrors = true) {.noNimScript.} = ## Copies a file from `source` to `dest` preserving file permissions. ## ## This is a wrapper proc around `copyFile() <#copyFile>`_, @@ -1209,7 +1879,7 @@ proc copyFileWithPermissions*(source, dest: string, proc copyDirWithPermissions*(source, dest: string, ignorePermissionErrors = true) {.rtl, extern: "nos$1", - tags: [WriteIOEffect, ReadIOEffect], benign.} = + tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = ## Copies a directory from `source` to `dest` preserving file permissions. ## ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir() @@ -1240,7 +1910,7 @@ proc copyDirWithPermissions*(source, dest: string, proc inclFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = ## a convenience procedure for: ## ## .. code-block:: nim @@ -1249,14 +1919,14 @@ proc inclFilePermissions*(filename: string, proc exclFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = ## a convenience procedure for: ## ## .. code-block:: nim ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) -proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} = +proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised. if not tryMoveFSObject(source, dest): when not defined(windows): @@ -1264,9 +1934,7 @@ proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} = copyDir(source, dest) removeDir(source) -#include ospaths - -proc expandSymlink*(symlinkPath: string): string = +proc expandSymlink*(symlinkPath: string): string {.noNimScript.} = ## Returns a string representing the path to which the symbolic link points. ## ## On Windows this is a noop, ``symlinkPath`` is simply returned. @@ -1329,6 +1997,13 @@ when defined(nimdoc): ## else: ## # Do something else! +elif defined(nintendoswitch) or defined(nimscript): + proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramStr is not implemented on Nintendo Switch") + + proc paramCount*(): int {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramCount is not implemented on Nintendo Switch") + elif defined(windows): # Since we support GUI applications with Nim, we sometimes generate # a WinMain entry proc. But a WinMain proc has no access to the parsed @@ -1355,13 +2030,6 @@ elif defined(windows): if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i]) raise newException(IndexError, "invalid index") -elif defined(nintendoswitch): - proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = - raise newException(OSError, "paramStr is not implemented on Nintendo Switch") - - proc paramCount*(): int {.tags: [ReadIOEffect].} = - raise newException(OSError, "paramCount is not implemented on Nintendo Switch") - elif defined(genode): proc paramStr*(i: int): TaintedString = raise newException(OSError, "paramStr is not implemented on Genode") @@ -1406,7 +2074,7 @@ when declared(paramCount) or defined(nimdoc): for i in 1..paramCount(): result.add(paramStr(i)) -when defined(freebsd) or defined(dragonfly): +when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include <sys/types.h> @@ -1439,7 +2107,7 @@ when defined(freebsd) or defined(dragonfly): result.setLen(pathLength) break -when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): +when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): proc getApplAux(procPath: string): string = result = newString(256) var len = readlink(procPath, result, 256) @@ -1448,7 +2116,7 @@ when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): len = readlink(procPath, result, len) setLen(result, len) -when not (defined(windows) or defined(macosx)): +when not (defined(windows) or defined(macosx) or defined(nimscript)): proc getApplHeuristic(): string = when declared(paramStr): result = string(paramStr(0)) @@ -1492,7 +2160,7 @@ when defined(haiku): else: result = "" -proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = +proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns the filename of the application's executable. ## ## This procedure will resolve symlinks. @@ -1552,11 +2220,11 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = if result.len == 0: result = getApplHeuristic() -proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = +proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns the directory of the application's executable. result = splitFile(getAppFilename()).dir -proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = +proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScript.} = ## sleeps `milsecs` milliseconds. when defined(windows): winlean.sleep(int32(milsecs)) @@ -1567,7 +2235,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = discard posix.nanosleep(a, b) proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", - tags: [ReadIOEffect].} = + tags: [ReadIOEffect], noNimScript.} = ## returns the file size of `file` (in bytes). An ``OSError`` exception is ## raised in case of an error. when defined(windows): @@ -1583,7 +2251,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -when defined(Windows): +when defined(Windows) or defined(nimscript): type DeviceId* = int32 FileId* = int64 @@ -1663,7 +2331,7 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = assert(path != "") # symlinks can't occur for file handles formalInfo.kind = getSymlinkFileKind(path) -proc getFileInfo*(handle: FileHandle): FileInfo = +proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = ## Retrieves file information for the file object represented by the given ## handle. ## @@ -1684,12 +2352,12 @@ proc getFileInfo*(handle: FileHandle): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, "", result) -proc getFileInfo*(file: File): FileInfo = +proc getFileInfo*(file: File): FileInfo {.noNimScript.} = if file.isNil: raise newException(IOError, "File is nil") result = getFileInfo(file.getFileHandle()) -proc getFileInfo*(path: string, followSymlink = true): FileInfo = +proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} = ## Retrieves file information for the file object pointed to by `path`. ## ## Due to intrinsic differences between operating systems, the information @@ -1723,14 +2391,23 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, path, result) -proc isHidden*(path: string): bool = - ## Determines whether a given path is hidden or not. Returns false if the - ## file doesn't exist. The given path must be accessible from the current - ## working directory of the program. +proc isHidden*(path: string): bool {.noNimScript.} = + ## Determines whether ``path`` is hidden or not, using this + ## reference https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory + ## + ## On Windows: returns true if it exists and its "hidden" attribute is set. ## - ## On Windows, a file is hidden if the file's 'hidden' attribute is set. - ## On Unix-like systems, a file is hidden if it starts with a '.' (period) - ## and is not *just* '.' or '..' ' ." + ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is + ## not ``.`` or ``..``. Note: paths are not normalized to determine `isHidden`. + runnableExamples: + when defined(posix): + doAssert ".foo".isHidden + doAssert: not ".foo/bar".isHidden + doAssert: not ".".isHidden + doAssert: not "..".isHidden + doAssert: not "".isHidden + doAssert ".foo/".isHidden + when defined(Windows): when useWinUnicode: wrapUnary(attributes, getFileAttributesW, path) @@ -1739,18 +2416,12 @@ proc isHidden*(path: string): bool = if attributes != -1'i32: result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32 else: - if fileExists(path): - let - fileName = extractFilename(path) - nameLen = len(fileName) - if nameLen == 2: - result = (fileName[0] == '.') and (fileName[1] != '.') - elif nameLen > 2: - result = (fileName[0] == '.') and (fileName[3] != '.') + let fileName = lastPathPart(path) + result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".." {.pop.} -proc setLastModificationTime*(file: string, t: times.Time) = +proc setLastModificationTime*(file: string, t: times.Time) {.noNimScript.} = ## Sets the `file`'s last modification time. `OSError` is raised in case of ## an error. when defined(posix): @@ -1766,3 +2437,37 @@ proc setLastModificationTime*(file: string, t: times.Time) = let res = setFileTime(h, nil, nil, ft.addr) discard h.closeHandle if res == 0'i32: raiseOSError(osLastError()) + +when isMainModule: + assert quoteShellWindows("aaa") == "aaa" + assert quoteShellWindows("aaa\"") == "aaa\\\"" + assert quoteShellWindows("") == "\"\"" + + assert quoteShellPosix("aaa") == "aaa" + assert quoteShellPosix("aaa a") == "'aaa a'" + assert quoteShellPosix("") == "''" + assert quoteShellPosix("a'a") == "'a'\"'\"'a'" + + when defined(posix): + assert quoteShell("") == "''" + + block normalizePathEndTest: + # handle edge cases correctly: shouldn't affect whether path is + # absolute/relative + doAssert "".normalizePathEnd(true) == "" + doAssert "".normalizePathEnd(false) == "" + doAssert "/".normalizePathEnd(true) == $DirSep + doAssert "/".normalizePathEnd(false) == $DirSep + + when defined(posix): + doAssert "//".normalizePathEnd(false) == "/" + doAssert "foo.bar//".normalizePathEnd == "foo.bar" + doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/" + when defined(Windows): + doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo" + doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\" + # this one is controversial: we could argue for returning `D:\` instead, + # but this is simplest. + doAssert r"D:\".normalizePathEnd == r"D:" + doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\" + doAssert "/".normalizePathEnd == r"\" diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim deleted file mode 100644 index 44b78e053..000000000 --- a/lib/pure/ospaths.nim +++ /dev/null @@ -1,713 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# Forwarded by the ``os`` module but a module in its own right for NimScript -# support. - -include "system/inclrtl" - -import strutils - -type - ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read - ## from an environment variable - WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write - ## to an environment variable - - ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read - ## operation from the directory - ## structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write - ## operation to - ## the directory structure - - OSErrorCode* = distinct int32 ## Specifies an OS Error Code. - -const - doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) - -when defined(Nimdoc): # only for proper documentation: - const - CurDir* = '.' - ## The constant string used by the operating system to refer to the - ## current directory. - ## - ## For example: '.' for POSIX or ':' for the classic Macintosh. - - ParDir* = ".." - ## The constant string used by the operating system to refer to the - ## parent directory. - ## - ## For example: ".." for POSIX or "::" for the classic Macintosh. - - DirSep* = '/' - ## The character used by the operating system to separate pathname - ## components, for example, '/' for POSIX or ':' for the classic - ## Macintosh. - - AltSep* = '/' - ## An alternative character used by the operating system to separate - ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems - ## where `DirSep` is a backslash. - - PathSep* = ':' - ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX - ## or ';' for Windows. - - FileSystemCaseSensitive* = true - ## true if the file system is case sensitive, false otherwise. Used by - ## `cmpPaths` to compare filenames properly. - - ExeExt* = "" - ## The file extension of native executables. For example: - ## "" for POSIX, "exe" on Windows. - - ScriptExt* = "" - ## The file extension of a script file. For example: "" for POSIX, - ## "bat" on Windows. - - DynlibFormat* = "lib$1.so" - ## The format string to turn a filename into a `DLL`:idx: file (also - ## called `shared object`:idx: on some operating systems). - -elif defined(macos): - const - CurDir* = ':' - ParDir* = "::" - DirSep* = ':' - AltSep* = Dirsep - PathSep* = ',' - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.dylib" - - # MacOS paths - # =========== - # MacOS directory separator is a colon ":" which is the only character not - # allowed in filenames. - # - # A path containing no colon or which begins with a colon is a partial - # path. - # E.g. ":kalle:petter" ":kalle" "kalle" - # - # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" - # When generating paths, one is safe if one ensures that all partial paths - # begin with a colon, and all full paths end with a colon. - # In full paths the first name (e g HD above) is the name of a mounted - # volume. - # These names are not unique, because, for instance, two diskettes with the - # same names could be inserted. This means that paths on MacOS are not - # waterproof. In case of equal names the first volume found will do. - # Two colons "::" are the relative path to the parent. Three is to the - # grandparent etc. -elif doslikeFileSystem: - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '\\' # seperator within paths - AltSep* = '/' - PathSep* = ';' # seperator between paths - FileSystemCaseSensitive* = false - ExeExt* = "exe" - ScriptExt* = "bat" - DynlibFormat* = "$1.dll" -elif defined(PalmOS) or defined(MorphOS): - const - DirSep* = '/' - AltSep* = Dirsep - PathSep* = ';' - ParDir* = ".." - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.prc" -elif defined(RISCOS): - const - DirSep* = '.' - AltSep* = '.' - ParDir* = ".." # is this correct? - PathSep* = ',' - FileSystemCaseSensitive* = true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "lib$1.so" -else: # UNIX-like operating system - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '/' - AltSep* = DirSep - PathSep* = ':' - FileSystemCaseSensitive* = when defined(macosx): false else: true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" - -const - ExtSep* = '.' - ## The character which separates the base filename from the extension; - ## for example, the '.' in ``os.nim``. - -proc normalizePathEnd(path: var string, trailingSep = false) = - ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on - ## ``trailingSep``, and taking care of edge cases: it preservers whether - ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, - ## not `AltSep`. - if path.len == 0: return - var i = path.len - while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i) - if trailingSep: - # foo// => foo - path.setLen(i) - # foo => foo/ - path.add DirSep - elif i>0: - # foo// => foo - path.setLen(i) - else: - # // => / (empty case was already taken care of) - path = $DirSep - -proc normalizePathEnd(path: string, trailingSep = false): string = - result = path - result.normalizePathEnd(trailingSep) - -proc joinPath*(head, tail: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Joins two directory names to one. - ## - ## For example on Unix: - ## - ## .. code-block:: nim - ## joinPath("usr", "lib") - ## - ## results in: - ## - ## .. code-block:: nim - ## "usr/lib" - ## - ## If head is the empty string, tail is returned. If tail is the empty - ## string, head is returned with a trailing path separator. If tail starts - ## with a path separator it will be removed when concatenated to head. Other - ## path separators not located on boundaries won't be modified. More - ## examples on Unix: - ## - ## .. code-block:: nim - ## assert joinPath("usr", "") == "usr/" - ## assert joinPath("", "lib") == "lib" - ## assert joinPath("", "/lib") == "/lib" - ## assert joinPath("usr/", "/lib") == "usr/lib" - if len(head) == 0: - result = tail - elif head[len(head)-1] in {DirSep, AltSep}: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & substr(tail, 1) - else: - result = head & tail - else: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & tail - else: - result = head & DirSep & tail - -proc joinPath*(parts: varargs[string]): string {.noSideEffect, - rtl, extern: "nos$1OpenArray".} = - ## The same as `joinPath(head, tail)`, but works with any number of - ## directory parts. You need to pass at least one element or the proc - ## will assert in debug builds and crash on release builds. - result = parts[0] - for i in 1..high(parts): - result = joinPath(result, parts[i]) - -proc `/` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``joinPath(head, tail)`` - ## - ## Here are some examples for Unix: - ## - ## .. code-block:: nim - ## assert "usr" / "" == "usr/" - ## assert "" / "lib" == "lib" - ## assert "" / "/lib" == "/lib" - ## assert "usr/" / "/lib" == "usr/lib" - return joinPath(head, tail) - -proc splitPath*(path: string): tuple[head, tail: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a directory into (head, tail), so that - ## ``head / tail == path`` (except for edge cases like "/usr"). - ## - ## Examples: - ## - ## .. code-block:: nim - ## splitPath("usr/local/bin") -> ("usr/local", "bin") - ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") - ## splitPath("bin") -> ("", "bin") - ## splitPath("/bin") -> ("", "bin") - ## splitPath("") -> ("", "") - var sepPos = -1 - for i in countdown(len(path)-1, 0): - if path[i] in {DirSep, AltSep}: - sepPos = i - break - if sepPos >= 0: - result.head = substr(path, 0, sepPos-1) - result.tail = substr(path, sepPos+1) - else: - result.head = "" - result.tail = path - -proc parentDirPos(path: string): int = - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in countdown(len(path)-q, 0): - if path[i] in {DirSep, AltSep}: return i - result = -1 - -proc parentDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the parent directory of `path`. - ## - ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end - ## in a dir separator. - ## The remainder can be obtained with ``lastPathPart(path)`` - runnableExamples: - doAssert parentDir("") == "" - when defined(posix): - doAssert parentDir("/usr/local/bin") == "/usr/local" - doAssert parentDir("foo/bar/") == "foo" - - let sepPos = parentDirPos(path) - if sepPos >= 0: - result = substr(path, 0, sepPos-1) - else: - result = "" - -proc tailDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the tail part of `path`.. - ## - ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. - ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. - ## | Example: ``tailDir("bin") == ""``. - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in 0..len(path)-q: - if path[i] in {DirSep, AltSep}: - return substr(path, i+1) - result = "" - -proc isRootDir*(path: string): bool {. - noSideEffect, rtl, extern: "nos$1".} = - ## Checks whether a given `path` is a root directory - result = parentDirPos(path) < 0 - -iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = - ## Walks over all parent directories of a given `path` - ## - ## If `fromRoot` is set, the traversal will start from the file system root - ## diretory. If `inclusive` is set, the original argument will be included - ## in the traversal. - ## - ## Relative paths won't be expanded by this proc. Instead, it will traverse - ## only the directories appearing in the relative path. - if not fromRoot: - var current = path - if inclusive: yield path - while true: - if current.isRootDir: break - current = current.parentDir - yield current - else: - for i in countup(0, path.len - 2): # ignore the last / - # deal with non-normalized paths such as /foo//bar//baz - if path[i] in {DirSep, AltSep} and - (i == 0 or path[i-1] notin {DirSep, AltSep}): - yield path.substr(0, i) - - if inclusive: yield path - -proc `/../`*(head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent - ## directory. Then ``head / tail`` is performed instead. - let sepPos = parentDirPos(head) - if sepPos >= 0: - result = substr(head, 0, sepPos-1) / tail - else: - result = head / tail - -proc normExt(ext: string): string = - if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here - else: result = ExtSep & ext - -proc searchExtPos*(path: string): int = - ## Returns index of the '.' char in `path` if it signifies the beginning - ## of extension. Returns -1 otherwise. - # BUGFIX: do not search until 0! .DS_Store is no file extension! - result = -1 - for i in countdown(len(path)-1, 1): - if path[i] == ExtSep: - result = i - break - elif path[i] in {DirSep, AltSep}: - break # do not skip over path - -proc splitFile*(path: string): tuple[dir, name, ext: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, filename, extension). - ## `dir` does not end in `DirSep`. - ## `extension` includes the leading dot. - ## - ## Example: - ## - ## .. code-block:: nim - ## var (dir, name, ext) = splitFile("usr/local/nimc.html") - ## assert dir == "usr/local" - ## assert name == "nimc" - ## assert ext == ".html" - ## - ## If `path` has no extension, `ext` is the empty string. - ## If `path` has no directory component, `dir` is the empty string. - ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = (path, "", "") - else: - var sepPos = -1 - var dotPos = path.len - for i in countdown(len(path)-1, 0): - if path[i] == ExtSep: - if dotPos == path.len and i > 0 and - path[i-1] notin {DirSep, AltSep}: dotPos = i - elif path[i] in {DirSep, AltSep}: - sepPos = i - break - result.dir = substr(path, 0, sepPos-1) - result.name = substr(path, sepPos+1, dotPos-1) - result.ext = substr(path, dotPos) - -proc extractFilename*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Extracts the filename of a given `path`. This is the same as - ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``. - runnableExamples: - when defined(posix): - doAssert extractFilename("foo/bar/") == "" - doAssert extractFilename("foo/bar") == "bar" - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = "" - else: - result = splitPath(path).tail - -proc lastPathPart*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## like ``extractFilename``, but ignores trailing dir separator; aka: baseName - ## in some other languages. - runnableExamples: - when defined(posix): - doAssert lastPathPart("foo/bar/") == "bar" - let path = path.normalizePathEnd(trailingSep = false) - result = extractFilename(path) - -proc changeFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Changes the file extension to `ext`. - ## - ## If the `filename` has no extension, `ext` will be added. - ## If `ext` == "" then any extension is removed. - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. (Although I know - ## of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = substr(filename, 0, extPos-1) & normExt(ext) - -proc addFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Adds the file extension `ext` to `filename`, unless - ## `filename` already has an extension. - ## - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. - ## (Although I know of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = filename - -proc cmpPaths*(pathA, pathB: string): int {. - noSideEffect, rtl, extern: "nos$1".} = - ## Compares two paths. - ## - ## On a case-sensitive filesystem this is done - ## case-sensitively otherwise case-insensitively. Returns: - ## - ## | 0 iff pathA == pathB - ## | < 0 iff pathA < pathB - ## | > 0 iff pathA > pathB - runnableExamples: - when defined(macosx): - doAssert cmpPaths("foo", "Foo") == 0 - elif defined(posix): - doAssert cmpPaths("foo", "Foo") > 0 - if FileSystemCaseSensitive: - result = cmp(pathA, pathB) - else: - when defined(nimscript): - result = cmpic(pathA, pathB) - elif defined(nimdoc): discard - else: - result = cmpIgnoreCase(pathA, pathB) - -proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = - ## Checks whether a given `path` is absolute. - ## - ## On Windows, network paths are considered absolute too. - runnableExamples: - doAssert(not "".isAbsolute) - doAssert(not ".".isAbsolute) - when defined(posix): - doAssert "/".isAbsolute - doAssert(not "a/".isAbsolute) - - if len(path) == 0: return false - - when doslikeFileSystem: - var len = len(path) - result = (path[0] in {'/', '\\'}) or - (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') - elif defined(macos): - # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path - result = path[0] != ':' - elif defined(RISCOS): - result = path[0] == '$' - elif defined(posix): - result = path[0] == '/' - -proc unixToNativePath*(path: string, drive=""): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Converts an UNIX-like path to a native one. - ## - ## On an UNIX system this does nothing. Else it converts - ## '/', '.', '..' to the appropriate things. - ## - ## On systems with a concept of "drives", `drive` is used to determine - ## which drive label to use during absolute path conversion. - ## `drive` defaults to the drive of the current working directory, and is - ## ignored on systems that do not have a concept of "drives". - - when defined(unix): - result = path - else: - if path.len == 0: - return "" - - var start: int - if path[0] == '/': - # an absolute path - when doslikeFileSystem: - if drive != "": - result = drive & ":" & DirSep - else: - result = $DirSep - elif defined(macos): - result = "" # must not start with ':' - else: - result = $DirSep - start = 1 - elif path[0] == '.' and (path.len == 1 or path[1] == '/'): - # current directory - result = $CurDir - start = when doslikeFileSystem: 1 else: 2 - else: - result = "" - start = 0 - - var i = start - while i < len(path): # ../../../ --> :::: - if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': - # parent directory - when defined(macos): - if result[high(result)] == ':': - add result, ':' - else: - add result, ParDir - else: - add result, ParDir & DirSep - inc(i, 3) - elif path[i] == '/': - add result, DirSep - inc(i) - else: - add result, path[i] - inc(i) - -include "includes/oserr" -when not defined(nimscript): - include "includes/osenv" - -proc getHomeDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the home directory of the current user. - ## - ## This proc is wrapped by the expandTilde proc for the convenience of - ## processing paths coming from user configuration files. - when defined(windows): return string(getEnv("USERPROFILE")) & "\\" - else: return string(getEnv("HOME")) & "/" - -proc getConfigDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the config directory of the current user for applications. - ## - ## On non-Windows OSs, this proc conforms to the XDG Base Directory - ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment - ## variable if it is set, and returns the default configuration directory, - ## "~/.config/", otherwise. - ## - ## An OS-dependent trailing slash is always present at the end of the - ## returned string; `\` on Windows and `/` on all other OSs. - when defined(windows): - result = getEnv("APPDATA").string - else: - result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string - result.normalizePathEnd(trailingSep = true) - -proc getTempDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the temporary directory of the current user for applications to - ## save temporary files in. - ## - ## **Please do not use this**: On Android, it currently - ## returns ``getHomeDir()``, and on other Unix based systems it can cause - ## security problems too. That said, you can override this implementation - ## by adding ``-d:tempDir=mytempname`` to your compiler invokation. - when defined(tempDir): - const tempDir {.strdefine.}: string = nil - return tempDir - elif defined(windows): return string(getEnv("TEMP")) & "\\" - elif defined(android): return getHomeDir() - else: return "/tmp/" - -proc expandTilde*(path: string): string {. - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing - ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). - ## - ## Windows: this is still supported despite Windows platform not having this - ## convention; also, both ``~/`` and ``~\`` are handled. - runnableExamples: - doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" - if len(path) == 0 or path[0] != '~': - result = path - elif len(path) == 1: - result = getHomeDir() - elif (path[1] in {DirSep, AltSep}): - result = getHomeDir() / path.substr(2) - else: - # TODO: handle `~bob` and `~bob/` which means home of bob - result = path - -# TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand -# belong in `strutils` instead; they are not specific to paths -proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote s, so it can be safely passed to Windows API. - ## Based on Python's subprocess.list2cmdline - ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx - let needQuote = {' ', '\t'} in s or s.len == 0 - - result = "" - var backslashBuff = "" - if needQuote: - result.add("\"") - - for c in s: - if c == '\\': - backslashBuff.add(c) - elif c == '\"': - result.add(backslashBuff) - result.add(backslashBuff) - backslashBuff.setLen(0) - result.add("\\\"") - else: - if backslashBuff.len != 0: - result.add(backslashBuff) - backslashBuff.setLen(0) - result.add(c) - - if needQuote: - result.add("\"") - -proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to POSIX shell. - ## Based on Python's pipes.quote - const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', - '0'..'9', 'A'..'Z', 'a'..'z'} - if s.len == 0: - return "''" - - let safe = s.allCharsInSet(safeUnixChars) - - if safe: - return s - else: - return "'" & s.replace("'", "'\"'\"'") & "'" - -when defined(windows) or defined(posix) or defined(nintendoswitch): - proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to shell. - when defined(windows): - return quoteShellWindows(s) - else: - return quoteShellPosix(s) - - proc quoteShellCommand*(args: openArray[string]): string = - ## Concatenates and quotes shell arguments `args` - runnableExamples: - when defined(posix): - assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" - when defined(windows): - assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" - # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 - for i in 0..<args.len: - if i > 0: result.add " " - result.add quoteShell(args[i]) - -when isMainModule: - assert quoteShellWindows("aaa") == "aaa" - assert quoteShellWindows("aaa\"") == "aaa\\\"" - assert quoteShellWindows("") == "\"\"" - - assert quoteShellPosix("aaa") == "aaa" - assert quoteShellPosix("aaa a") == "'aaa a'" - assert quoteShellPosix("") == "''" - assert quoteShellPosix("a'a") == "'a'\"'\"'a'" - - when defined(posix): - assert quoteShell("") == "''" - - block normalizePathEndTest: - # handle edge cases correctly: shouldn't affect whether path is - # absolute/relative - doAssert "".normalizePathEnd(true) == "" - doAssert "".normalizePathEnd(false) == "" - doAssert "/".normalizePathEnd(true) == $DirSep - doAssert "/".normalizePathEnd(false) == $DirSep - - when defined(posix): - doAssert "//".normalizePathEnd(false) == "/" - doAssert "foo.bar//".normalizePathEnd == "foo.bar" - doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/" - when defined(Windows): - doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo" - doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\" - # this one is controversial: we could argue for returning `D:\` instead, - # but this is simplest. - doAssert r"D:\".normalizePathEnd == r"D:" - doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\" - doAssert "/".normalizePathEnd == r"\" diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index e487a8975..a9f37412f 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -15,7 +15,6 @@ include "system/inclrtl" import strutils, os, strtabs, streams, cpuinfo -from ospaths import quoteShell, quoteShellWindows, quoteShellPosix export quoteShell, quoteShellWindows, quoteShellPosix when defined(windows): @@ -65,6 +64,7 @@ const poUseShell* {.deprecated.} = poUsePath ## Deprecated alias for poUsePath. proc execProcess*(command: string, + workingDir: string = "", args: openArray[string] = [], env: StringTableRef = nil, options: set[ProcessOption] = {poStdErrToStdOut, @@ -121,7 +121,7 @@ proc startProcess*(command: string, ## invocation if possible as it leads to non portable software. ## ## Return value: The newly created process object. Nil is never returned, - ## but ``EOS`` is raised in case of an error. + ## but ``OSError`` is raised in case of an error. proc startCmd*(command: string, options: set[ProcessOption] = { poStdErrToStdOut, poUsePath}): Process {. @@ -350,12 +350,13 @@ proc select*(readfds: var seq[Process], timeout = 500): int when not defined(useNimRtl): proc execProcess(command: string, + workingDir: string = "", args: openArray[string] = [], env: StringTableRef = nil, options: set[ProcessOption] = {poStdErrToStdOut, poUsePath, poEvalCommand}): TaintedString = - var p = startProcess(command, args=args, env=env, options=options) + var p = startProcess(command, workingDir=workingDir, args=args, env=env, options=options) var outp = outputStream(p) result = TaintedString"" var line = newStringOfCap(120).TaintedString @@ -1324,6 +1325,12 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = { ## let (outp, errC) = execCmdEx("nim c -r mytestfile.nim") var p = startProcess(command, options=options + {poEvalCommand}) var outp = outputStream(p) + + # There is no way to provide input for the child process + # anymore. Closing it will create EOF on stdin instead of eternal + # blocking. + close inputStream(p) + result = (TaintedString"", -1) var line = newStringOfCap(120).TaintedString while true: diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index c91134738..fe3d3186f 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -44,9 +44,9 @@ type cmdShortOption ## a short option ``-c`` detected OptParser* = object of RootObj ## this object implements the command line parser - cmd*: string # cmd,pos exported so caller can catch "--" as.. pos*: int # ..empty key or subcmd cmdArg & handle specially inShortState: bool + allowWhitespaceAfterColon: bool shortNoVal: set[char] longNoVal: seq[string] cmds: seq[string] @@ -95,7 +95,8 @@ when declared(os.paramCount): # access the command line arguments then! proc initOptParser*(cmdline = "", shortNoVal: set[char]={}, - longNoVal: seq[string] = @[]): OptParser = + longNoVal: seq[string] = @[]; + allowWhitespaceAfterColon = true): OptParser = ## inits the option parser. If ``cmdline == ""``, the real command line ## (as provided by the ``OS`` module) is taken. If ``shortNoVal`` is ## provided command users do not need to delimit short option keys and @@ -108,23 +109,21 @@ when declared(os.paramCount): result.inShortState = false result.shortNoVal = shortNoVal result.longNoVal = longNoVal + result.allowWhitespaceAfterColon = allowWhitespaceAfterColon if cmdline != "": - result.cmd = cmdline result.cmds = parseCmdLine(cmdline) else: - result.cmd = "" result.cmds = newSeq[string](paramCount()) for i in countup(1, paramCount()): result.cmds[i-1] = paramStr(i).string - result.cmd.add quote(result.cmds[i-1]) - result.cmd.add ' ' result.kind = cmdEnd result.key = TaintedString"" result.val = TaintedString"" proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={}, - longNoVal: seq[string] = @[]): OptParser = + longNoVal: seq[string] = @[]; + allowWhitespaceAfterColon = true): OptParser = ## inits the option parser. If ``cmdline.len == 0``, the real command line ## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and ## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``. @@ -133,19 +132,15 @@ when declared(os.paramCount): result.inShortState = false result.shortNoVal = shortNoVal result.longNoVal = longNoVal - result.cmd = "" + result.allowWhitespaceAfterColon = allowWhitespaceAfterColon if cmdline.len != 0: result.cmds = newSeq[string](cmdline.len) for i in 0..<cmdline.len: result.cmds[i] = cmdline[i].string - result.cmd.add quote(cmdline[i].string) - result.cmd.add ' ' else: result.cmds = newSeq[string](paramCount()) for i in countup(1, paramCount()): result.cmds[i-1] = paramStr(i).string - result.cmd.add quote(result.cmds[i-1]) - result.cmd.add ' ' result.kind = cmdEnd result.key = TaintedString"" result.val = TaintedString"" @@ -210,7 +205,7 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = inc(i) while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i) # if we're at the end, use the next command line option: - if i >= p.cmds[p.idx].len and p.idx < p.cmds.len: + if i >= p.cmds[p.idx].len and p.idx < p.cmds.len and p.allowWhitespaceAfterColon: inc p.idx i = 0 p.val = TaintedString p.cmds[p.idx].substr(i) diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 9aef43c1b..20f02e815 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -566,10 +566,6 @@ type SqlParser* = object of SqlLexer ## SQL parser object tok: Token - -{.deprecated: [EInvalidSql: SqlParseError, PSqlNode: SqlNode, - TSqlNode: SqlNodeObj, TSqlParser: SqlParser, TSqlNodeKind: SqlNodeKind].} - proc newNode*(k: SqlNodeKind): SqlNode = new(result) result.kind = k @@ -591,6 +587,7 @@ proc len*(n: SqlNode): int = result = n.sons.len proc `[]`*(n: SqlNode; i: int): SqlNode = n.sons[i] +proc `[]`*(n: SqlNode; i: BackwardsIndex): SqlNode = n.sons[n.len - int(i)] proc add*(father, n: SqlNode) = add(father.sons, n) @@ -674,7 +671,7 @@ proc getPrecedence(p: SqlParser): int = result = 5 elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or - isKeyw(p, "like"): + isKeyw(p, "like") or isKeyw(p, "in"): result = 4 elif isKeyw(p, "and"): result = 3 @@ -717,7 +714,10 @@ proc identOrLiteral(p: var SqlParser): SqlNode = of tkParLe: getTok(p) result = newNode(nkPrGroup) - result.add(parseExpr(p)) + while true: + result.add(parseExpr(p)) + if p.tok.kind != tkComma: break + getTok(p) eat(p, tkParRi) else: if p.tok.literal == "*": @@ -1465,6 +1465,22 @@ proc `$`*(n: SqlNode): string = ## an alias for `renderSQL`. renderSQL(n) +proc treeReprAux(s: SqlNode, level: int, result: var string) = + result.add('\n') + for i in 0 ..< level: result.add(" ") + + result.add($s.kind) + if s.kind in LiteralNodes: + result.add(' ') + result.add(s.strVal) + else: + for son in s.sons: + treeReprAux(son, level + 1, result) + +proc treeRepr*(s: SqlNode): string = + result = newStringOfCap(128) + treeReprAux(s, 0, result) + when not defined(js): import streams diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index e633d8cf7..f68baaf6b 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -267,7 +267,7 @@ proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. rtl, extern: "npuParseBiggestInt", noSideEffect.} = ## parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `EOverflow` is raised if an overflow occurs. + ## `OverflowError` is raised if an overflow occurs. var res: BiggestInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): @@ -278,7 +278,7 @@ proc parseInt*(s: string, number: var int, start = 0): int {. rtl, extern: "npuParseInt", noSideEffect.} = ## parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `EOverflow` is raised if an overflow occurs. + ## `OverflowError` is raised if an overflow occurs. var res: BiggestInt result = parseBiggestInt(s, res, start) if (sizeof(int) <= 4) and @@ -289,7 +289,7 @@ proc parseInt*(s: string, number: var int, start = 0): int {. proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = ## parses a natural number into ``b``. This cannot raise an overflow - ## error. Instead of an ``Overflow`` exception ``high(int)`` is returned. + ## error. ``high(int)`` is returned for an overflow. ## The number of processed character is returned. ## This is usually what you really want to use instead of `parseInt`:idx:. ## Example: diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index d8d5a7a2d..0967f7983 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -180,6 +180,7 @@ type errEqExpected, ## ``=`` expected errQuoteExpected, ## ``"`` or ``'`` expected errEndOfCommentExpected ## ``-->`` expected + errAttributeValueExpected ## non-empty attribute value expected ParserState = enum stateStart, stateNormal, stateAttr, stateEmptyElementTag, stateError @@ -187,6 +188,8 @@ type XmlParseOption* = enum ## options for the XML parser reportWhitespace, ## report whitespace reportComments ## report comments + allowUnquotedAttribs ## allow unquoted attribute values (for HTML) + allowEmptyAttribs ## allow empty attributes (without explicit value) XmlParser* = object of BaseLexer ## the parser object. a, b, c: string @@ -207,7 +210,8 @@ const "'>' expected", "'=' expected", "'\"' or \"'\" expected", - "'-->' expected" + "'-->' expected", + "attribute value expected" ] proc open*(my: var XmlParser, input: Stream, filename: string, @@ -618,10 +622,15 @@ proc parseAttribute(my: var XmlParser) = if my.a.len == 0: markError(my, errGtExpected) return + + let startPos = my.bufpos parseWhitespace(my, skip=true) if my.buf[my.bufpos] != '=': - markError(my, errEqExpected) + if allowEmptyAttribs notin my.options or + (my.buf[my.bufpos] != '>' and my.bufpos == startPos): + markError(my, errEqExpected) return + inc(my.bufpos) parseWhitespace(my, skip=true) @@ -669,6 +678,21 @@ proc parseAttribute(my: var XmlParser) = pendingSpace = false add(my.b, buf[pos]) inc(pos) + elif allowUnquotedAttribs in my.options: + const disallowedChars = {'"', '\'', '`', '=', '<', '>', ' ', + '\0', '\t', '\L', '\F', '\f'} + let startPos = pos + while (let c = buf[pos]; c notin disallowedChars): + if c == '&': + my.bufpos = pos + parseEntity(my, my.b) + my.kind = xmlAttribute # parseEntity overwrites my.kind! + pos = my.bufpos + else: + add(my.b, c) + inc(pos) + if pos == startPos: + markError(my, errAttributeValueExpected) else: markError(my, errQuoteExpected) # error corrections: guess what was meant diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 3ee82917d..957091918 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -90,19 +90,19 @@ proc kind*(p: Peg): PegKind = p.kind ## Returns the *PegKind* of a given *Peg* object. proc term*(p: Peg): string = p.term - ## Returns the *string* representation of a given *Peg* variant object + ## Returns the *string* representation of a given *Peg* variant object ## where present. proc ch*(p: Peg): char = p.ch - ## Returns the *char* representation of a given *Peg* variant object + ## Returns the *char* representation of a given *Peg* variant object ## where present. proc charChoice*(p: Peg): ref set[char] = p.charChoice - ## Returns the *charChoice* field of a given *Peg* variant object + ## Returns the *charChoice* field of a given *Peg* variant object ## where present. proc nt*(p: Peg): NonTerminal = p.nt - ## Returns the *NonTerminal* object of a given *Peg* variant object + ## Returns the *NonTerminal* object of a given *Peg* variant object ## where present. proc index*(p: Peg): range[0..MaxSubpatterns] = p.index @@ -137,7 +137,7 @@ proc flags*(nt: NonTerminal): set[NonTerminalFlag] = nt.flags proc rule*(nt: NonTerminal): Peg = nt.rule ## Gets the *Peg* object representing the rule definition of the parent *Peg* - ## object variant of a given *NonTerminal*. + ## object variant of a given *NonTerminal*. proc term*(t: string): Peg {.nosideEffect, rtl, extern: "npegs$1Str".} = ## constructs a PEG from a terminal string @@ -553,8 +553,6 @@ type ml: int origStart: int -{.deprecated: [TCaptures: Captures].} - proc bounds*(c: Captures, i: range[0..MaxSubpatterns-1]): tuple[first, last: int] = ## returns the bounds ``[first..last]`` of the `i`'th capture. @@ -885,7 +883,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int macro mkHandlerTplts(handlers: untyped): untyped = # Transforms the handler spec in *handlers* into handler templates. # The AST structure of *handlers[0]*: - # + # # .. code-block:: # StmtList # Call @@ -1009,7 +1007,7 @@ template eventParser*(pegAst, handlers: untyped): (proc(s: string): int) = ## echo opStack ## ## let pLen = parseArithExpr(txt) - ## + ## ## The *handlers* parameter consists of code blocks for *PegKinds*, ## which define the grammar elements of interest. Each block can contain ## handler code to be executed when the parser enters and leaves text @@ -1342,7 +1340,7 @@ when not defined(js): subs: varargs[tuple[pattern: Peg, repl: string]]) {. rtl, extern: "npegs$1".} = ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an + ## `parallelReplace`) and writes back to `outfile`. Raises ``IOError`` if an ## error occurs. This is supposed to be used for quick scripting. ## ## **Note**: this proc does not exist while using the JS backend. diff --git a/lib/pure/random.nim b/lib/pure/random.nim index a2c2c1f88..c458d51eb 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -231,4 +231,8 @@ when isMainModule: except RangeError: discard + + # don't use causes integer overflow + doAssert compiles(random[int](low(int) .. high(int))) + main() diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim index d9b863a52..5f4b09f80 100644 --- a/lib/pure/smtp.nim +++ b/lib/pure/smtp.nim @@ -71,7 +71,12 @@ when not defined(ssl): type PSSLContext = ref object let defaultSSLContext: PSSLContext = nil else: - let defaultSSLContext = newContext(verifyMode = CVerifyNone) + var defaultSSLContext {.threadvar.}: SSLContext + + proc getSSLContext(): SSLContext = + if defaultSSLContext == nil: + defaultSSLContext = newContext(verifyMode = CVerifyNone) + result = defaultSSLContext proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string], otherHeaders: openarray[tuple[name, value: string]]): Message = @@ -109,20 +114,22 @@ proc `$`*(msg: Message): string = result.add(msg.msgBody) proc newSmtp*(useSsl = false, debug=false, - sslContext = defaultSslContext): Smtp = + sslContext: SSLContext = nil): Smtp = ## Creates a new ``Smtp`` instance. new result result.debug = debug - result.sock = newSocket() if useSsl: when compiledWithSsl: - sslContext.wrapSocket(result.sock) + if sslContext == nil: + getSSLContext().wrapSocket(result.sock) + else: + sslContext.wrapSocket(result.sock) else: {.error: "SMTP module compiled without SSL support".} proc newAsyncSmtp*(useSsl = false, debug=false, - sslContext = defaultSslContext): AsyncSmtp = + sslContext: SSLContext = nil): AsyncSmtp = ## Creates a new ``AsyncSmtp`` instance. new result result.debug = debug @@ -130,7 +137,10 @@ proc newAsyncSmtp*(useSsl = false, debug=false, result.sock = newAsyncSocket() if useSsl: when compiledWithSsl: - sslContext.wrapSocket(result.sock) + if sslContext == nil: + getSSLContext().wrapSocket(result.sock) + else: + sslContext.wrapSocket(result.sock) else: {.error: "SMTP module compiled without SSL support".} diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 9950c5877..b0ac62525 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -157,112 +157,112 @@ proc peek[T](s: Stream, result: var T) = raise newEIO("cannot read from stream") proc readChar*(s: Stream): char = - ## reads a char from the stream `s`. Raises `EIO` if an error occurred. + ## reads a char from the stream `s`. Raises `IOError` if an error occurred. ## Returns '\\0' as an EOF marker. if readData(s, addr(result), sizeof(result)) != 1: result = '\0' proc peekChar*(s: Stream): char = - ## peeks a char from the stream `s`. Raises `EIO` if an error occurred. + ## peeks a char from the stream `s`. Raises `IOError` if an error occurred. ## Returns '\\0' as an EOF marker. if peekData(s, addr(result), sizeof(result)) != 1: result = '\0' proc readBool*(s: Stream): bool = - ## reads a bool from the stream `s`. Raises `EIO` if an error occurred. + ## reads a bool from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekBool*(s: Stream): bool = - ## peeks a bool from the stream `s`. Raises `EIO` if an error occurred. + ## peeks a bool from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readInt8*(s: Stream): int8 = - ## reads an int8 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an int8 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekInt8*(s: Stream): int8 = - ## peeks an int8 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an int8 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readInt16*(s: Stream): int16 = - ## reads an int16 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an int16 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekInt16*(s: Stream): int16 = - ## peeks an int16 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an int16 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readInt32*(s: Stream): int32 = - ## reads an int32 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an int32 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekInt32*(s: Stream): int32 = - ## peeks an int32 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an int32 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readInt64*(s: Stream): int64 = - ## reads an int64 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an int64 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekInt64*(s: Stream): int64 = - ## peeks an int64 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an int64 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readUint8*(s: Stream): uint8 = - ## reads an uint8 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an uint8 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekUint8*(s: Stream): uint8 = - ## peeks an uint8 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an uint8 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readUint16*(s: Stream): uint16 = - ## reads an uint16 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an uint16 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekUint16*(s: Stream): uint16 = - ## peeks an uint16 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an uint16 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readUint32*(s: Stream): uint32 = - ## reads an uint32 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an uint32 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekUint32*(s: Stream): uint32 = - ## peeks an uint32 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an uint32 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readUint64*(s: Stream): uint64 = - ## reads an uint64 from the stream `s`. Raises `EIO` if an error occurred. + ## reads an uint64 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekUint64*(s: Stream): uint64 = - ## peeks an uint64 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks an uint64 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readFloat32*(s: Stream): float32 = - ## reads a float32 from the stream `s`. Raises `EIO` if an error occurred. + ## reads a float32 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekFloat32*(s: Stream): float32 = - ## peeks a float32 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks a float32 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readFloat64*(s: Stream): float64 = - ## reads a float64 from the stream `s`. Raises `EIO` if an error occurred. + ## reads a float64 from the stream `s`. Raises `IOError` if an error occurred. read(s, result) proc peekFloat64*(s: Stream): float64 = - ## peeks a float64 from the stream `s`. Raises `EIO` if an error occurred. + ## peeks a float64 from the stream `s`. Raises `IOError` if an error occurred. peek(s, result) proc readStr*(s: Stream, length: int): TaintedString = - ## reads a string of length `length` from the stream `s`. Raises `EIO` if + ## reads a string of length `length` from the stream `s`. Raises `IOError` if ## an error occurred. result = newString(length).TaintedString var L = readData(s, cstring(result), length) if L != length: setLen(result.string, L) proc peekStr*(s: Stream, length: int): TaintedString = - ## peeks a string of length `length` from the stream `s`. Raises `EIO` if + ## peeks a string of length `length` from the stream `s`. Raises `IOError` if ## an error occurred. result = newString(length).TaintedString var L = peekData(s, cstring(result), length) @@ -301,7 +301,7 @@ proc peekLine*(s: Stream, line: var TaintedString): bool = proc readLine*(s: Stream): TaintedString = ## Reads a line from a stream `s`. Note: This is not very efficient. Raises - ## `EIO` if an error occurred. + ## `IOError` if an error occurred. result = TaintedString"" if s.atEnd: raise newEIO("cannot read from stream") @@ -317,7 +317,7 @@ proc readLine*(s: Stream): TaintedString = proc peekLine*(s: Stream): TaintedString = ## Peeks a line from a stream `s`. Note: This is not very efficient. Raises - ## `EIO` if an error occurred. + ## `IOError` if an error occurred. let pos = getPosition(s) defer: setPosition(s, pos) result = readLine(s) diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index 77763ff43..c1c535e55 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -129,7 +129,7 @@ to use prefix instead of postfix operators. ``+E`` One or more ``?E`` Zero or One ``E{n,m}`` From ``n`` up to ``m`` times ``E`` -``~Ε`` Not predicate +``~E`` Not predicate ``a ^* b`` Shortcut for ``?(a *(b a))``. Usually used for separators. ``a ^* b`` Shortcut for ``?(a +(b a))``. Usually used for separators. ``'a'`` Matches a single character @@ -456,10 +456,11 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b template atom*(input: string; idx: int; c: char): bool = ## Used in scanp for the matching of atoms (usually chars). - idx < input.len and input[idx] == c + ## EOF is matched as ``'\0'``. + (idx < input.len and input[idx] == c) or (idx == input.len and c == '\0') template atom*(input: string; idx: int; s: set[char]): bool = - idx < input.len and input[idx] in s + (idx < input.len and input[idx] in s) or (idx == input.len and '\0' in s) template hasNxt*(input: string; idx: int): bool = idx < input.len diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index d8122a181..4d0fe800e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -14,7 +14,7 @@ ## <backends.html#the-javascript-target>`_. import parseutils -from math import pow, round, floor, log10 +from math import pow, floor, log10 from algorithm import reverse when defined(nimVmExportFixed): @@ -75,6 +75,10 @@ proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. + runnableExamples: + doAssert isAlphaAscii('e') == true + doAssert isAlphaAscii('E') == true + doAssert isAlphaAscii('8') == false return c in Letters proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, @@ -82,6 +86,10 @@ proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. + runnableExamples: + doAssert isAlphaNumeric('n') == true + doAssert isAlphaNumeric('8') == true + doAssert isAlphaNumeric(' ') == false return c in Letters+Digits proc isDigit*(c: char): bool {.noSideEffect, procvar, @@ -89,11 +97,17 @@ proc isDigit*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is a number. ## ## This checks 0-9 ASCII characters only. + runnableExamples: + doAssert isDigit('n') == false + doAssert isDigit('8') == true return c in Digits proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsSpaceAsciiChar".} = ## Checks whether or not `c` is a whitespace character. + runnableExamples: + doAssert isSpaceAscii('n') == false + doAssert isSpaceAscii(' ') == true return c in Whitespace proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, @@ -101,6 +115,10 @@ proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. + runnableExamples: + doAssert isLowerAscii('e') == true + doAssert isLowerAscii('E') == false + doAssert isLowerAscii('7') == false return c in {'a'..'z'} proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, @@ -108,6 +126,10 @@ proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. + runnableExamples: + doAssert isUpperAscii('e') == false + doAssert isUpperAscii('E') == true + doAssert isUpperAscii('7') == false return c in {'A'..'Z'} template isImpl(call) = @@ -117,41 +139,59 @@ template isImpl(call) = if not call(c): return false proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaAsciiStr".} = + rtl, extern: "nsuIsAlphaAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether or not `s` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. ## Returns true if all characters in `s` are ## alphabetic and there is at least one character ## in `s`. + runnableExamples: + doAssert isAlphaAscii("fooBar") == true + doAssert isAlphaAscii("fooBar1") == false + doAssert isAlphaAscii("foo Bar") == false isImpl isAlphaAscii proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericStr".} = + rtl, extern: "nsuIsAlphaNumericStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether or not `s` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. ## Returns true if all characters in `s` are ## alpanumeric and there is at least one character ## in `s`. + runnableExamples: + doAssert isAlphaNumeric("fooBar") == true + doAssert isAlphaNumeric("fooBar") == true + doAssert isAlphaNumeric("foo Bar") == false isImpl isAlphaNumeric proc isDigit*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitStr".} = + rtl, extern: "nsuIsDigitStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether or not `s` is a numeric value. ## ## This checks 0-9 ASCII characters only. ## Returns true if all characters in `s` are ## numeric and there is at least one character ## in `s`. + runnableExamples: + doAssert isDigit("1908") == true + doAssert isDigit("fooBar1") == false isImpl isDigit proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiStr".} = + rtl, extern: "nsuIsSpaceAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether or not `s` is completely whitespace. ## ## Returns true if all characters in `s` are whitespace ## characters and there is at least one character in `s`. + runnableExamples: + doAssert isSpaceAscii(" ") == true + doAssert isSpaceAscii("") == false isImpl isSpaceAscii template isCaseImpl(s, charProc, skipNonAlpha) = @@ -169,7 +209,8 @@ template isCaseImpl(s, charProc, skipNonAlpha) = return false return if skipNonAlpha: hasAtleastOneAlphaChar else: true -proc isLowerAscii*(s: string, skipNonAlpha: bool): bool = +proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether ``s`` is lower case. ## ## This checks ASCII characters only. @@ -183,9 +224,14 @@ proc isLowerAscii*(s: string, skipNonAlpha: bool): bool = ## ## For either value of ``skipNonAlpha``, returns false if ``s`` is ## an empty string. + runnableExamples: + doAssert isLowerAscii("1foobar", false) == false + doAssert isLowerAscii("1foobar", true) == true + doAssert isLowerAscii("1fooBar", true) == false isCaseImpl(s, isLowerAscii, skipNonAlpha) -proc isUpperAscii*(s: string, skipNonAlpha: bool): bool = +proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether ``s`` is upper case. ## ## This checks ASCII characters only. @@ -199,15 +245,22 @@ proc isUpperAscii*(s: string, skipNonAlpha: bool): bool = ## ## For either value of ``skipNonAlpha``, returns false if ``s`` is ## an empty string. + runnableExamples: + doAssert isUpperAscii("1FOO", false) == false + doAssert isUpperAscii("1FOO", true) == true + doAssert isUpperAscii("1Foo", true) == false isCaseImpl(s, isUpperAscii, skipNonAlpha) proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = - ## Converts `c` into lower case. + ## Returns the lower case version of ``c``. ## ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. + runnableExamples: + doAssert toLowerAscii('A') == 'a' + doAssert toLowerAscii('e') == 'e' if c in {'A'..'Z'}: result = chr(ord(c) + (ord('a') - ord('A'))) else: @@ -225,6 +278,8 @@ proc toLowerAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. + runnableExamples: + doAssert toLowerAscii("FooBar!") == "foobar!" toImpl toLowerAscii proc toUpperAscii*(c: char): char {.noSideEffect, procvar, @@ -234,6 +289,9 @@ proc toUpperAscii*(c: char): char {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. + runnableExamples: + doAssert toUpperAscii('a') == 'A' + doAssert toUpperAscii('E') == 'E' if c in {'a'..'z'}: result = chr(ord(c) - (ord('a') - ord('A'))) else: @@ -246,6 +304,8 @@ proc toUpperAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. + runnableExamples: + doAssert toUpperAscii("FooBar!") == "FOOBAR!" toImpl toUpperAscii proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, @@ -253,6 +313,9 @@ proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, ## Converts the first character of `s` into upper case. ## ## This works only for the letters ``A-Z``. + runnableExamples: + doAssert capitalizeAscii("foo") == "Foo" + doAssert capitalizeAscii("-bar") == "-bar" if s.len == 0: result = "" else: result = toUpperAscii(s[0]) & substr(s, 1) @@ -262,6 +325,9 @@ proc normalize*(s: string): string {.noSideEffect, procvar, ## ## That means to convert it to lower case and remove any '_'. This ## should NOT be used to normalize Nim identifier names. + runnableExamples: + doAssert normalize("Foo_bar") == "foobar" + doAssert normalize("Foo Bar") == "foo bar" result = newString(s.len) var j = 0 for i in 0..len(s) - 1: @@ -280,6 +346,10 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, ## | 0 iff a == b ## | < 0 iff a < b ## | > 0 iff a > b + runnableExamples: + doAssert cmpIgnoreCase("FooBar", "foobar") == 0 + doAssert cmpIgnoreCase("bar", "Foo") < 0 + doAssert cmpIgnoreCase("Foo5", "foo4") > 0 var i = 0 var m = min(a.len, b.len) while i < m: @@ -301,6 +371,9 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, ## | 0 iff a == b ## | < 0 iff a < b ## | > 0 iff a > b + runnableExamples: + doAssert cmpIgnoreStyle("foo_bar", "FooBar") == 0 + doAssert cmpIgnoreStyle("foo_bar_5", "FooBar4") > 0 var i = 0 var j = 0 while true: @@ -330,6 +403,8 @@ proc strip*(s: string, leading = true, trailing = true, ## If `leading` is true, leading `chars` are stripped. ## If `trailing` is true, trailing `chars` are stripped. ## If both are false, the string is returned unchanged. + runnableExamples: + doAssert " vhellov ".strip().strip(trailing = false, chars = {'v'}) == "hellov" var first = 0 last = len(s)-1 @@ -344,6 +419,8 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = ## ## The resulting string may not have a leading zero. Its length is always ## exactly 3. + runnableExamples: + doAssert toOctal('!') == "041" result = newString(3) var val = ord(c) for i in countdown(2, 0): @@ -489,11 +566,15 @@ iterator splitWhitespace*(s: string, maxsplit: int = -1): string = ## oldSplit(s, Whitespace, maxsplit) +template accResult(iter: untyped) = + result = @[] + for x in iter: add(result, x) + proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitWhitespace".} = ## The same as the `splitWhitespace <#splitWhitespace.i,string,int>`_ ## iterator, but is a proc that returns a sequence of substrings. - accumulateResult(splitWhitespace(s, maxsplit)) + accResult(splitWhitespace(s, maxsplit)) iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. @@ -670,7 +751,7 @@ proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, rtl, extern: "nsuSplitLines".} = ## The same as the `splitLines <#splitLines.i,string>`_ iterator, but is a ## proc that returns a sequence of substrings. - accumulateResult(splitLines(s, keepEol=keepEol)) + accResult(splitLines(s, keepEol=keepEol)) proc countLines*(s: string): int {.noSideEffect, rtl, extern: "nsuCountLines".} = @@ -683,6 +764,8 @@ proc countLines*(s: string): int {.noSideEffect, ## ## In this context, a line is any string seperated by a newline combination. ## A line can be an empty string. + runnableExamples: + doAssert countLines("First line\l and second line.") == 2 result = 1 var i = 0 while i < s.len: @@ -701,7 +784,7 @@ proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[st runnableExamples: doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] doAssert "".split({' '}) == @[""] - accumulateResult(split(s, seps, maxsplit)) + accResult(split(s, seps, maxsplit)) proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = @@ -710,7 +793,7 @@ proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffec runnableExamples: doAssert "a,b,c".split(',') == @["a", "b", "c"] doAssert "".split(' ') == @[""] - accumulateResult(split(s, sep, maxsplit)) + accResult(split(s, sep, maxsplit)) proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = @@ -727,7 +810,7 @@ proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEff doAssert "a largely spaced sentence".split(" ", maxsplit=1) == @["a", " largely spaced sentence"] doAssert(sep.len > 0) - accumulateResult(split(s, sep, maxsplit)) + accResult(split(s, sep, maxsplit)) proc rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] @@ -749,7 +832,7 @@ proc rsplit*(s: string, seps: set[char] = Whitespace, ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accumulateResult(rsplit(s, seps, maxsplit)) + accResult(rsplit(s, seps, maxsplit)) result.reverse() proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] @@ -771,7 +854,7 @@ proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accumulateResult(rsplit(s, sep, maxsplit)) + accResult(rsplit(s, sep, maxsplit)) result.reverse() proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] @@ -800,7 +883,7 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", "plan ", "canal panama"] doAssert "".rsplit("Elon Musk") == @[""] doAssert "a largely spaced sentence".rsplit(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] - accumulateResult(rsplit(s, sep, maxsplit)) + accResult(rsplit(s, sep, maxsplit)) result.reverse() proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, @@ -809,6 +892,9 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, ## ## The resulting string will be exactly `len` characters long. No prefix like ## ``0x`` is generated. `x` is treated as an unsigned value. + runnableExamples: + doAssert toHex(1984, 6) == "0007C0" + doAssert toHex(1984, 2) == "C0" const HexChars = "0123456789ABCDEF" var @@ -822,6 +908,8 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, proc toHex*[T: SomeInteger](x: T): string = ## Shortcut for ``toHex(x, T.sizeOf * 2)`` + runnableExamples: + doAssert toHex(1984'i64) == "00000000000007C0" toHex(BiggestInt(x), T.sizeOf * 2) proc toHex*(s: string): string {.noSideEffect, rtl.} = @@ -843,6 +931,9 @@ proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, ## ## The resulting string will be minimally `minchars` characters long. This is ## achieved by adding leading zeros. + runnableExamples: + doAssert intToStr(1984) == "1984" + doAssert intToStr(1984, 6) == "001984" result = $abs(x) for i in 1 .. minchars - len(result): result = '0' & result @@ -854,6 +945,8 @@ proc parseInt*(s: string): int {.noSideEffect, procvar, ## Parses a decimal integer value contained in `s`. ## ## If `s` is not a valid integer, `ValueError` is raised. + runnableExamples: + doAssert parseInt("-0042") == -42 let L = parseutils.parseInt(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid integer: " & s) @@ -890,6 +983,9 @@ proc parseFloat*(s: string): float {.noSideEffect, procvar, ## Parses a decimal floating point value contained in `s`. If `s` is not ## a valid floating point number, `ValueError` is raised. ``NAN``, ## ``INF``, ``-INF`` are also supported (case insensitive comparison). + runnableExamples: + doAssert parseFloat("3.14") == 3.14 + doAssert parseFloat("inf") == 1.0/0 let L = parseutils.parseFloat(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid float: " & s) @@ -1115,7 +1211,8 @@ proc wordWrap*(s: string, maxLineWidth = 80, splitLongWords = true, seps: set[char] = Whitespace, newLine = "\n"): string {. - noSideEffect, rtl, extern: "nsuWordWrap".} = + noSideEffect, rtl, extern: "nsuWordWrap", + deprecated: "use wrapWords in std/wordwrap instead".} = ## Word wraps `s`. result = newStringOfCap(s.len + s.len shr 6) var spaceLeft = maxLineWidth @@ -1151,6 +1248,8 @@ proc indent*(s: string, count: Natural, padding: string = " "): string ## Indents each line in ``s`` by ``count`` amount of ``padding``. ## ## **Note:** This does not preserve the new line characters used in ``s``. + runnableExamples: + doAssert indent("First line\c\l and second line.", 2) == " First line\l and second line." result = "" var i = 0 for line in s.splitLines(): @@ -1164,8 +1263,11 @@ proc indent*(s: string, count: Natural, padding: string = " "): string proc unindent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuUnindent".} = ## Unindents each line in ``s`` by ``count`` amount of ``padding``. + ## Sometimes called `dedent`:idx: ## ## **Note:** This does not preserve the new line characters used in ``s``. + runnableExamples: + doAssert unindent(" First line\l and second line", 3) == "First line\land second line" result = "" var i = 0 for line in s.splitLines(): @@ -1260,14 +1362,22 @@ proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) proc allCharsInSet*(s: string, theSet: set[char]): bool = ## Returns true iff each character of `s` is in the set `theSet`. + runnableExamples: + doAssert allCharsInSet("aeea", {'a', 'e'}) == true + doAssert allCharsInSet("", {'a', 'e'}) == true for c in items(s): if c notin theSet: return false return true proc abbrev*(s: string, possibilities: openArray[string]): int = - ## Returns the index of the first item in `possibilities` if not ambiguous. + ## Returns the index of the first item in ``possibilities`` which starts with ``s``, if not ambiguous. ## ## Returns -1 if no item has been found and -2 if multiple items match. + runnableExamples: + doAssert abbrev("fac", ["college", "faculty", "industry"]) == 1 + doAssert abbrev("foo", ["college", "faculty", "industry"]) == -1 # Not found + doAssert abbrev("fac", ["college", "faculty", "faculties"]) == -2 # Ambiguous + doAssert abbrev("college", ["college", "colleges", "industry"]) == 0 result = -1 # none found for i in 0..possibilities.len-1: if possibilities[i].startsWith(s): @@ -1282,6 +1392,8 @@ proc abbrev*(s: string, possibilities: openArray[string]): int = proc join*(a: openArray[string], sep: string = ""): string {. noSideEffect, rtl, extern: "nsuJoinSep".} = ## Concatenates all strings in `a` separating them with `sep`. + runnableExamples: + doAssert join(["A", "B", "Conclusion"], " -> ") == "A -> B -> Conclusion" if len(a) > 0: var L = sep.len * (a.len-1) for i in 0..high(a): inc(L, a[i].len) @@ -1297,6 +1409,8 @@ proc join*[T: not string](a: openArray[T], sep: string = ""): string {. noSideEffect, rtl.} = ## Converts all elements in `a` to strings using `$` and concatenates them ## with `sep`. + runnableExamples: + doAssert join([1, 2, 3], " -> ") == "1 -> 2 -> 3" result = "" for i, x in a: if i > 0: @@ -1413,6 +1527,8 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = ## backwards to 0. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + if sub.len == 0: + return -1 let realStart = if start == -1: s.len else: start for i in countdown(realStart-sub.len, 0): for j in 0..sub.len-1: @@ -1520,11 +1636,7 @@ proc replace*(s, sub: string, by = ""): string {.noSideEffect, result = "" let subLen = sub.len if subLen == 0: - for c in s: - add result, by - add result, c - add result, by - return + result = s elif subLen == 1: # when the pattern is a single char, we use a faster # char-based search that doesn't need a skip table: @@ -1579,21 +1691,22 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, initSkipTable(a, sub) var i = 0 let last = s.high - let sublen = max(sub.len, 1) - while true: - var j = find(a, s, sub, i, last) - if j < 0: break - # word boundary? - if (j == 0 or s[j-1] notin wordChars) and - (j+sub.len >= s.len or s[j+sub.len] notin wordChars): - add result, substr(s, i, j - 1) - add result, by - i = j + sublen - else: - add result, substr(s, i, j) - i = j + 1 - # copy the rest: - add result, substr(s, i) + let sublen = sub.len + if sublen > 0: + while true: + var j = find(a, s, sub, i, last) + if j < 0: break + # word boundary? + if (j == 0 or s[j-1] notin wordChars) and + (j+sub.len >= s.len or s[j+sub.len] notin wordChars): + add result, substr(s, i, j - 1) + add result, by + i = j + sublen + else: + add result, substr(s, i, j) + i = j + 1 + # copy the rest: + add result, substr(s, i) proc multiReplace*(s: string, replacements: varargs[(string, string)]): string {.noSideEffect.} = ## Same as replace, but specialized for doing multiple replacements in a single @@ -1610,15 +1723,18 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { result = newStringOfCap(s.len) var i = 0 var fastChk: set[char] = {} - for tup in replacements: fastChk.incl(tup[0][0]) # Include first character of all replacements + for sub, by in replacements.items: + if sub.len > 0: + # Include first character of all replacements + fastChk.incl sub[0] while i < s.len: block sIteration: # Assume most chars in s are not candidates for any replacement operation if s[i] in fastChk: - for tup in replacements: - if s.continuesWith(tup[0], i): - add result, tup[1] - inc(i, tup[0].len) + for sub, by in replacements.items: + if sub.len > 0 and s.continuesWith(sub, i): + add result, by + inc(i, sub.len) break sIteration # No matching replacement found # copy current character from s @@ -1769,8 +1885,10 @@ proc validIdentifier*(s: string): bool {.noSideEffect, if s[i] notin IdentChars: return false return true +{.push warning[Deprecated]: off.} proc editDistance*(a, b: string): int {.noSideEffect, - rtl, extern: "nsuEditDistance".} = + rtl, extern: "nsuEditDistance", + deprecated: "use editdistance.editDistanceAscii instead".} = ## Returns the edit distance between `a` and `b`. ## ## This uses the `Levenshtein`:idx: distance algorithm with only a linear @@ -1856,6 +1974,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, if x > c3: x = c3 row[p] = x result = row[e] +{.pop.} # floating point formating: when not defined(js): @@ -1868,8 +1987,6 @@ type ffDecimal, ## use decimal floating point notation ffScientific ## use scientific notation (using ``e`` character) -{.deprecated: [TFloatFormat: FloatFormatMode].} - proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[-1..32] = 16; decimalSep = '.'): string {. @@ -2126,14 +2243,13 @@ proc formatEng*(f: BiggestFloat, result = significand.formatBiggestFloat(ffDecimal, precision, decimalSep='.') else: # Find the best exponent that's a multiple of 3 - fexponent = round(floor(log10(absolute))) - fexponent = 3.0 * round(floor(fexponent / 3.0)) + fexponent = floor(log10(absolute)) + fexponent = 3.0 * floor(fexponent / 3.0) # Adjust the significand for the new exponent significand /= pow(10.0, fexponent) - # Round the significand and check whether it has affected + # Adjust the significand and check whether it has affected # the exponent - significand = round(significand, precision) absolute = abs(significand) if absolute >= 1000.0: significand *= 0.001 @@ -2396,6 +2512,7 @@ proc stripLineEnd*(s: var string) = ## Returns ``s`` stripped from one of these suffixes: ## ``\r, \n, \r\n, \f, \v`` (at most once instance). ## For example, can be useful in conjunction with ``osproc.execCmdEx``. + ## aka: `chomp`:idx: runnableExamples: var s = "foo\n\n" s.stripLineEnd @@ -2498,7 +2615,7 @@ when isMainModule: doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc" - doAssert "oo".replace("", "abc") == "abcoabcoabc" + doAssert "oo".replace("", "abc") == "oo" type MyEnum = enum enA, enB, enC, enuD, enE doAssert parseEnum[MyEnum]("enu_D") == enuD @@ -2534,35 +2651,18 @@ when isMainModule: doAssert isAlphaAscii('A') doAssert(not isAlphaAscii('$')) - doAssert isAlphaAscii("Rasp") - doAssert isAlphaAscii("Args") - doAssert(not isAlphaAscii("$Tomato")) - doAssert isAlphaNumeric('3') doAssert isAlphaNumeric('R') doAssert(not isAlphaNumeric('!')) - doAssert isAlphaNumeric("34ABc") - doAssert isAlphaNumeric("Rad") - doAssert isAlphaNumeric("1234") - doAssert(not isAlphaNumeric("@nose")) - doAssert isDigit('3') doAssert(not isDigit('a')) doAssert(not isDigit('%')) - doAssert isDigit("12533") - doAssert(not isDigit("12.33")) - doAssert(not isDigit("A45b")) - doAssert isSpaceAscii('\t') doAssert isSpaceAscii('\l') doAssert(not isSpaceAscii('A')) - doAssert isSpaceAscii("\t\l \v\r\f") - doAssert isSpaceAscii(" ") - doAssert(not isSpaceAscii("ABc \td")) - doAssert(isNilOrWhitespace("")) doAssert(isNilOrWhitespace(" ")) doAssert(isNilOrWhitespace("\t\l \v\r\f")) @@ -2575,33 +2675,11 @@ when isMainModule: doAssert(not isLowerAscii('&')) doAssert(not isLowerAscii(' ')) - doAssert isLowerAscii("abcd", false) - doAssert(not isLowerAscii("33aa", false)) - doAssert(not isLowerAscii("a b", false)) - - doAssert(not isLowerAscii("abCD", true)) - doAssert isLowerAscii("33aa", true) - doAssert isLowerAscii("a b", true) - doAssert isLowerAscii("1, 2, 3 go!", true) - doAssert(not isLowerAscii(" ", true)) - doAssert(not isLowerAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets - doAssert isUpperAscii('A') doAssert(not isUpperAscii('b')) doAssert(not isUpperAscii('5')) doAssert(not isUpperAscii('%')) - doAssert isUpperAscii("ABC", false) - doAssert(not isUpperAscii("A#$", false)) - doAssert(not isUpperAscii("A B", false)) - - doAssert(not isUpperAscii("AAcc", true)) - doAssert isUpperAscii("A#$", true) - doAssert isUpperAscii("A B", true) - doAssert isUpperAscii("1, 2, 3 GO!", true) - doAssert(not isUpperAscii(" ", true)) - doAssert(not isUpperAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets - doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 8149c72cc..d103af710 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -300,8 +300,6 @@ proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) = type Subex* = distinct string ## string that contains a substitution expression -{.deprecated: [TSubex: Subex].} - proc subex*(s: string): Subex = ## constructs a *substitution expression* from `s`. Currently this performs ## no syntax checking but this may change in later versions. diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim index 8ded552d9..53c31e8c9 100644 --- a/lib/pure/sugar.nim +++ b/lib/pure/sugar.nim @@ -125,6 +125,8 @@ macro `->`*(p, b: untyped): untyped = type ListComprehension = object var lc*: ListComprehension +template `|`*(lc: ListComprehension, comp: untyped): untyped = lc + macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = ## List comprehension, returns a sequence. `comp` is the actual list ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is @@ -140,8 +142,7 @@ macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = expectLen(comp, 3) expectKind(comp, nnkInfix) - expectKind(comp[0], nnkIdent) - assert($comp[0].ident == "|") + assert($comp[0] == "|") result = newCall( newDotExpr( diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 2e138b27e..35dc2483c 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -18,7 +18,7 @@ import macros import strformat -from strutils import toLowerAscii +from strutils import toLowerAscii, `%` import colors, tables when defined(windows): @@ -481,9 +481,6 @@ type styleHidden, ## hidden text styleStrikethrough ## strikethrough -{.deprecated: [TStyle: Style].} -{.deprecated: [styleUnknown: styleItalic].} - when not defined(windows): var gFG {.threadvar.}: int @@ -556,9 +553,6 @@ type bg8Bit, ## 256-color (not supported, see ``enableTrueColors`` instead.) bgDefault ## default terminal background color -{.deprecated: [TForegroundColor: ForegroundColor, - TBackgroundColor: BackgroundColor].} - when defined(windows): var defaultForegroundColor, defaultBackgroundColor: int16 = 0xFFFF'i16 # Default to an invalid value 0xFFFF @@ -635,7 +629,8 @@ proc ansiForegroundColorCode*(color: Color): string = template ansiForegroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) - (static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) + # no usage of `fmt`, see issue #7632 + (static("$1$2;$3;$4m" % [$fgPrefix, $(rgb.r), $(rgb.g), $(rgb.b)])) proc ansiBackgroundColorCode*(color: Color): string = let rgb = extractRGB(color) @@ -643,7 +638,8 @@ proc ansiBackgroundColorCode*(color: Color): string = template ansiBackgroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) - (static(fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) + # no usage of `fmt`, see issue #7632 + (static("$1$2;$3;$4m" % [$bgPrefix, $(rgb.r), $(rgb.g), $(rgb.b)])) proc setForegroundColor*(f: File, color: Color) = ## Sets the terminal's foreground true color. diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a7ccbf6ee..71934a466 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -158,7 +158,9 @@ when defined(posix): type CTime = posix.Time - var CLOCK_REALTIME {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid + var + realTimeClockId {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid + cpuClockId {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "<time.h>".}: Clockid proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "<sys/time.h>".} @@ -298,9 +300,6 @@ type TimeIntervalParts* = array[TimeUnit, int] # Array of Duration parts starts TimesMutableTypes = DateTime | Time | Duration | TimeInterval -{.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, - TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].} - const secondsInMin = 60 secondsInHour = 60*60 @@ -748,8 +747,7 @@ proc abs*(a: Duration): Duration = initDuration(seconds = abs(a.seconds), nanoseconds = -a.nanosecond) proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = - ## Converts a broken-down time structure to - ## calendar time representation. + ## Converts a ``DateTime`` to a ``Time`` representing the same point in time. let epochDay = toEpochday(dt.monthday, dt.month, dt.year) var seconds = epochDay * secondsInDay seconds.inc dt.hour * secondsInHour @@ -843,6 +841,11 @@ proc `$`*(zone: Timezone): string = proc `==`*(zone1, zone2: Timezone): bool = ## Two ``Timezone``'s are considered equal if their name is equal. + if system.`==`(zone1, zone2): + return true + if zone1.isNil or zone2.isNil: + return false + runnableExamples: doAssert local() == local() doAssert local() != utc() @@ -967,11 +970,13 @@ else: return ((0 - tm.toAdjUnix).int, false) return (0, false) - var a = unix.CTime + # In case of a 32-bit time_t, we fallback to the closest available + # timezone information. + var a = clamp(unix, low(CTime), high(CTime)).CTime let tmPtr = localtime(addr(a)) if not tmPtr.isNil: let tm = tmPtr[] - return ((unix - tm.toAdjUnix).int, tm.isdst > 0) + return ((a.int64 - tm.toAdjUnix).int, tm.isdst > 0) return (0, false) proc localZonedTimeFromTime(time: Time): ZonedTime = @@ -1048,7 +1053,7 @@ proc local*(t: Time): DateTime = t.inZone(local()) proc getTime*(): Time {.tags: [TimeEffect], benign.} = - ## Gets the current time as a ``Time`` with nanosecond resolution. + ## Gets the current time as a ``Time`` with up to nanosecond resolution. when defined(JS): let millis = newDate().getTime() let seconds = convert(Milliseconds, Seconds, millis) @@ -1062,7 +1067,7 @@ proc getTime*(): Time {.tags: [TimeEffect], benign.} = result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) elif defined(posix): var ts: Timespec - discard clock_gettime(CLOCK_REALTIME, ts) + discard clock_gettime(realTimeClockId, ts) result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) elif defined(windows): var f: FILETIME @@ -1142,16 +1147,16 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval = result = ti1 + (-ti2) proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## Gets the current date as a string of the format ``YYYY-MM-DD``. - var ti = now() - result = $ti.year & '-' & intToStr(ord(ti.month), 2) & - '-' & intToStr(ti.monthday, 2) + ## Gets the current local date as a string of the format ``YYYY-MM-DD``. + var dt = now() + result = $dt.year & '-' & intToStr(ord(dt.month), 2) & + '-' & intToStr(dt.monthday, 2) proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## Gets the current clock time as a string of the format ``HH:MM:SS``. - var ti = now() - result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) & - ':' & intToStr(ti.second, 2) + ## Gets the current local clock time as a string of the format ``HH:MM:SS``. + var dt = now() + result = intToStr(dt.hour, 2) & ':' & intToStr(dt.minute, 2) & + ':' & intToStr(dt.second, 2) proc toParts* (ti: TimeInterval): TimeIntervalParts = ## Converts a `TimeInterval` into an array consisting of its time units, @@ -1383,7 +1388,6 @@ proc `==`*(a, b: DateTime): bool = ## Returns true if ``a == b``, that is if both dates represent the same point in time. return a.toTime == b.toTime - proc isStaticInterval(interval: TimeInterval): bool = interval.years == 0 and interval.months == 0 and interval.days == 0 and interval.weeks == 0 @@ -1398,28 +1402,20 @@ proc evaluateStaticInterval(interval: TimeInterval): Duration = hours = interval.hours) proc between*(startDt, endDt: DateTime): TimeInterval = - ## Evaluate difference between two dates in ``TimeInterval`` format, so, it - ## will be relative. + ## Gives the difference between ``startDt`` and ``endDt`` as a + ## ``TimeInterval``. ## - ## **Warning:** It's not recommended to use ``between`` for ``DateTime's`` in - ## different ``TimeZone's``. - ## ``a + between(a, b) == b`` is only guaranteed when ``a`` and ``b`` are in UTC. + ## **Warning:** This proc currently gives very few guarantees about the + ## result. ``a + between(a, b) == b`` is **not** true in general + ## (it's always true when UTC is used however). Neither is it guaranteed that + ## all components in the result will have the same sign. The behavior of this + ## proc might change in the future. runnableExamples: - var a = initDateTime(year = 2018, month = Month(3), monthday = 25, - hour = 0, minute = 59, second = 59, nanosecond = 1, - zone = utc()).local - var b = initDateTime(year = 2018, month = Month(3), monthday = 25, - hour = 1, minute = 1, second = 1, nanosecond = 0, - zone = utc()).local - doAssert between(a, b) == initTimeInterval( - nanoseconds=999, milliseconds=999, microseconds=999, seconds=1, minutes=1) - - a = parse("2018-01-09T00:00:00+00:00", "yyyy-MM-dd'T'HH:mm:sszzz", utc()) - b = parse("2018-01-10T23:00:00-02:00", "yyyy-MM-dd'T'HH:mm:sszzz") - doAssert between(a, b) == initTimeInterval(hours=1, days=2) - ## Though, here correct answer should be 1 day 25 hours (cause this day in - ## this tz is actually 26 hours). That's why operating different TZ is - ## discouraged + var a = initDateTime(25, mMar, 2015, 12, 0, 0, utc()) + var b = initDateTime(1, mApr, 2017, 15, 0, 15, utc()) + var ti = initTimeInterval(years = 2, days = 7, hours = 3, seconds = 15) + doAssert between(a, b) == ti + doAssert between(a, b) == -between(b, a) var startDt = startDt.utc() var endDt = endDt.utc() @@ -1547,7 +1543,6 @@ proc `*=`*[T: TimesMutableTypes, U](a: var T, b: U) = var dur = initDuration(seconds = 1) dur *= 5 doAssert dur == initDuration(seconds = 5) - a = a * b # @@ -1811,7 +1806,7 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = of UUUU: result.add $dt.year of z, zz, zzz, zzzz: - if dt.timezone.name == "Etc/UTC": + if dt.timezone != nil and dt.timezone.name == "Etc/UTC": result.add 'Z' else: result.add if -dt.utcOffset >= 0: '+' else: '-' @@ -2024,9 +2019,9 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, var offset = 0 case pattern of z: - offset = takeInt(1..2) * -3600 + offset = takeInt(1..2) * 3600 of zz: - offset = takeInt(2..2) * -3600 + offset = takeInt(2..2) * 3600 of zzz: offset.inc takeInt(2..2) * 3600 if input[i] != ':': @@ -2344,7 +2339,15 @@ when not defined(JS): fib.add(fib[^1] + fib[^2]) echo "CPU time [s] ", cpuTime() - t0 echo "Fib is [s] ", fib - result = toFloat(int(getClock())) / toFloat(clocksPerSec) + when defined(posix): + # 'clocksPerSec' is a compile-time constant, possibly a + # rather awful one, so use clock_gettime instead + var ts: Timespec + discard clock_gettime(cpuClockId, ts) + result = toFloat(ts.tv_sec.int) + + toFloat(ts.tv_nsec.int) / 1_000_000_000 + else: + result = toFloat(int(getClock())) / toFloat(clocksPerSec) proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} = ## gets time after the UNIX epoch (1970) in seconds. It is a float @@ -2506,4 +2509,4 @@ proc zoneInfoFromUtc*(zone: Timezone, time: Time): ZonedTime proc zoneInfoFromTz*(zone: Timezone, adjTime: Time): ZonedTime {.deprecated: "Use zonedTimeFromAdjTime instead".} = ## **Deprecated since v0.19.0:** use the ``zonedTimeFromAdjTime`` instead. - zone.zonedTimeFromAdjTime(adjTime) \ No newline at end of file + zone.zonedTimeFromAdjTime(adjTime) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 978f569ac..712cc46c8 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -18,8 +18,6 @@ type Rune* = distinct RuneImpl ## type that can hold any Unicode character Rune16* = distinct int16 ## 16 bit Unicode character -{.deprecated: [TRune: Rune, TRune16: Rune16].} - proc `<=%`*(a, b: Rune): bool = return int(a) <=% int(b) proc `<%`*(a, b: Rune): bool = return int(a) <% int(b) proc `==`*(a, b: Rune): bool = return int(a) == int(b) @@ -213,6 +211,10 @@ proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} = result = "" fastToUTF8Copy(c, result, 0, false) +proc add*(s: var string; c: Rune) = + let pos = s.len + fastToUTF8Copy(c, s, pos, false) + proc `$`*(rune: Rune): string = ## Converts a Rune to a string rune.toUTF8 @@ -220,7 +222,8 @@ proc `$`*(rune: Rune): string = proc `$`*(runes: seq[Rune]): string = ## Converts a sequence of Runes to a string result = "" - for rune in runes: result.add(rune.toUTF8) + for rune in runes: + result.add rune proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = ## Returns the byte position of unicode character @@ -228,7 +231,7 @@ proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = ## returns the special value -1 if it runs out of the string ## ## Beware: This can lead to unoptimized code and slow execution! - ## Most problems are solve more efficient by using an iterator + ## Most problems can be solved more efficiently by using an iterator ## or conversion to a seq of Rune. var i = 0 @@ -244,7 +247,7 @@ proc runeAtPos*(s: string, pos: int): Rune = ## Returns the unicode character at position pos ## ## Beware: This can lead to unoptimized code and slow execution! - ## Most problems are solve more efficient by using an iterator + ## Most problems can be solved more efficiently by using an iterator ## or conversion to a seq of Rune. fastRuneAt(s, runeOffset(s, pos), result, false) @@ -252,7 +255,7 @@ proc runeStrAtPos*(s: string, pos: Natural): string = ## Returns the unicode character at position pos as UTF8 String ## ## Beware: This can lead to unoptimized code and slow execution! - ## Most problems are solve more efficient by using an iterator + ## Most problems can be solved more efficiently by using an iterator ## or conversion to a seq of Rune. let o = runeOffset(s, pos) s[o.. (o+runeLenAt(s, o)-1)] @@ -266,7 +269,7 @@ proc runeReverseOffset*(s: string, rev:Positive): (int, int) = ## satisfy the request. ## ## Beware: This can lead to unoptimized code and slow execution! - ## Most problems are solve more efficient by using an iterator + ## Most problems can be solved more efficiently by using an iterator ## or conversion to a seq of Rune. var a = rev.int @@ -524,6 +527,22 @@ const 0x3000, 0x3000, # ideographic space 0xfeff, 0xfeff] # + unicodeSpaces = [ + Rune 0x0009, # tab + Rune 0x000a, # LF + Rune 0x000d, # CR + Rune 0x0020, # space + Rune 0x0085, # next line + Rune 0x00a0, # unknown + Rune 0x1680, # Ogham space mark + Rune 0x2000, # en dash .. zero-width space + Rune 0x200e, Rune 0x200f, # LTR mark .. RTL mark (pattern whitespace) + Rune 0x2028, Rune 0x2029, # - 0x3000, 0x3000, # + Rune 0x202f, # narrow no-break space + Rune 0x205f, # medium mathematical space + Rune 0x3000, # ideographic space + Rune 0xfeff] # unknown + toupperRanges = [ 0x0061, 0x007a, 468, # a-z A-Z 0x00e0, 0x00f6, 468, # - - @@ -1435,7 +1454,8 @@ template runeCaseCheck(s, runeProc, skipNonAlpha) = return false return if skipNonAlpha: hasAtleastOneAlphaRune else: true -proc isLower*(s: string, skipNonAlpha: bool): bool = +proc isLower*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether ``s`` is lower case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical @@ -1449,7 +1469,8 @@ proc isLower*(s: string, skipNonAlpha: bool): bool = ## an empty string. runeCaseCheck(s, isLower, skipNonAlpha) -proc isUpper*(s: string, skipNonAlpha: bool): bool = +proc isUpper*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = ## Checks whether ``s`` is upper case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical @@ -1608,12 +1629,13 @@ proc title*(s: string): string {.noSideEffect, procvar, rune.fastToUTF8Copy(result, lastIndex) proc isTitle*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nuc$1Str".}= + rtl, extern: "nuc$1Str", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".}= ## Checks whether or not `s` is a unicode title. ## ## Returns true if the first character in each word inside `s` ## are upper case and there is at least one character in `s`. - if s.len() == 0: + if s.len == 0: return false result = true @@ -1733,7 +1755,272 @@ proc lastRune*(s: string; last: int): (Rune, int) = fastRuneAt(s, last-L, r, false) result = (r, L+1) +proc size*(r: Rune): int {.noSideEffect.} = + ## Returns the number of bytes the rune ``r`` takes. + let v = r.uint32 + if v <= 0x007F: result = 1 + elif v <= 0x07FF: result = 2 + elif v <= 0xFFFF: result = 3 + elif v <= 0x1FFFFF: result = 4 + elif v <= 0x3FFFFFF: result = 5 + elif v <= 0x7FFFFFFF: result = 6 + else: result = 1 + +# --------- Private templates for different split separators ----------- +proc stringHasSep(s: string, index: int, seps: openarray[Rune]): bool = + var rune: Rune + fastRuneAt(s, index, rune, false) + return seps.contains(rune) + +proc stringHasSep(s: string, index: int, sep: Rune): bool = + var rune: Rune + fastRuneAt(s, index, rune, false) + return sep == rune + +template splitCommon(s, sep, maxsplit: untyped, sepLen: int = -1) = + ## Common code for split procedures + var + last = 0 + splits = maxsplit + if len(s) > 0: + while last <= len(s): + var first = last + while last < len(s) and not stringHasSep(s, last, sep): + when sep is Rune: + inc(last, sepLen) + else: + inc(last, runeLenAt(s, last)) + if splits == 0: last = len(s) + yield s[first .. (last - 1)] + if splits == 0: break + dec(splits) + when sep is Rune: + inc(last, sepLen) + else: + inc(last, if last < len(s): runeLenAt(s, last) else: 1) + +iterator split*(s: string, seps: openarray[Rune] = unicodeSpaces, + maxsplit: int = -1): string = + ## Splits the unicode string `s` into substrings using a group of separators. + ## + ## Substrings are separated by a substring containing only `seps`. + ## + ## .. code-block:: nim + ## for word in split("this\lis an\texample"): + ## writeLine(stdout, word) + ## + ## ...generates this output: + ## + ## .. code-block:: + ## "this" + ## "is" + ## "an" + ## "example" + ## + ## And the following code: + ## + ## .. code-block:: nim + ## for word in split("this:is;an$example", {';', ':', '$'}): + ## writeLine(stdout, word) + ## + ## ...produces the same output as the first example. The code: + ## + ## .. code-block:: nim + ## let date = "2012-11-20T22:08:08.398990" + ## let separators = {' ', '-', ':', 'T'} + ## for number in split(date, separators): + ## writeLine(stdout, number) + ## + ## ...results in: + ## + ## .. code-block:: + ## "2012" + ## "11" + ## "20" + ## "22" + ## "08" + ## "08.398990" + ## + splitCommon(s, seps, maxsplit) + +iterator splitWhitespace*(s: string): string = + ## Splits a unicode string at whitespace runes + splitCommon(s, unicodeSpaces, -1) + +template accResult(iter: untyped) = + result = @[] + for x in iter: add(result, x) + +proc splitWhitespace*(s: string): seq[string] {.noSideEffect, + rtl, extern: "ncuSplitWhitespace".} = + ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_ + ## iterator, but is a proc that returns a sequence of substrings. + accResult(splitWhitespace(s)) + +iterator split*(s: string, sep: Rune, maxsplit: int = -1): string = + ## Splits the unicode string `s` into substrings using a single separator. + ## + ## Substrings are separated by the rune `sep`. + ## The code: + ## + ## .. code-block:: nim + ## for word in split(";;this;is;an;;example;;;", ';'): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: + ## "" + ## "" + ## "this" + ## "is" + ## "an" + ## "" + ## "example" + ## "" + ## "" + ## "" + ## + splitCommon(s, sep, maxsplit, sep.size) + +proc split*(s: string, seps: openarray[Rune] = unicodeSpaces, maxsplit: int = -1): seq[string] {. + noSideEffect, rtl, extern: "nucSplitRunes".} = + ## The same as the `split iterator <#split.i,string,openarray[Rune]>`_, but is a + ## proc that returns a sequence of substrings. + accResult(split(s, seps, maxsplit)) + +proc split*(s: string, sep: Rune, maxsplit: int = -1): seq[string] {.noSideEffect, + rtl, extern: "nucSplitRune".} = + ## The same as the `split iterator <#split.i,string,Rune>`_, but is a proc + ## that returns a sequence of substrings. + accResult(split(s, sep, maxsplit)) + +proc strip*(s: string, leading = true, trailing = true, + runes: openarray[Rune] = unicodeSpaces): string {.noSideEffect, + rtl, extern: "nucStrip".} = + ## Strips leading or trailing `runes` from `s` and returns + ## the resulting string. + ## + ## If `leading` is true, leading `runes` are stripped. + ## If `trailing` is true, trailing `runes` are stripped. + ## If both are false, the string is returned unchanged. + var + s_i = 0 ## starting index into string ``s`` + e_i = len(s) - 1 ## ending index into ``s``, where the last ``Rune`` starts + if leading: + var + i = 0 + l_i: int ## value of ``s_i`` at the beginning of the iteration + rune: Rune + while i < len(s): + l_i = i + fastRuneAt(s, i, rune) + s_i = i # Assume to start from next rune + if not runes.contains(rune): + s_i = l_i # Go back to where the current rune starts + break + if trailing: + var + i = e_i + l_i: int + rune: Rune + while i >= 0: + l_i = i + fastRuneAt(s, l_i, rune) + var p_i = i - 1 + while p_i >= 0: + var + p_i_end = p_i + p_rune: Rune + fastRuneAt(s, p_i_end, p_rune) + if p_i_end < l_i: break + i = p_i + rune = p_rune + dec(p_i) + if not runes.contains(rune): + e_i = l_i - 1 + break + dec(i) + let newLen = e_i - s_i + 1 + result = newStringOfCap(newLen) + if newLen > 0: + result.add s[s_i .. e_i] + +proc repeat*(c: Rune, count: Natural): string {.noSideEffect, + rtl, extern: "nucRepeatRune".} = + ## Returns a string of `count` Runes `c`. + ## + ## The returned string will have a rune-length of `count`. + let s = $c + result = newStringOfCap(count * s.len) + for i in 0 ..< count: + result.add s + +proc align*(s: string, count: Natural, padding = ' '.Rune): string {. + noSideEffect, rtl, extern: "nucAlignString".} = + ## Aligns a unicode string `s` with `padding`, so that it has a rune-length + ## of `count`. + ## + ## `padding` characters (by default spaces) are added before `s` resulting in + ## right alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## returned unchanged. If you need to left align a string use the `alignLeft + ## proc <#alignLeft>`_. + runnableExamples: + assert align("abc", 4) == " abc" + assert align("a", 0) == "a" + assert align("1232", 6) == " 1232" + assert align("1232", 6, '#'.Rune) == "##1232" + assert align("Åge", 5) == " Åge" + assert align("×", 4, '_'.Rune) == "___×" + + let sLen = s.runeLen + if sLen < count: + let padStr = $padding + result = newStringOfCap(padStr.len * count) + let spaces = count - sLen + for i in 0 ..< spaces: result.add padStr + result.add s + else: + result = s + +proc alignLeft*(s: string, count: Natural, padding = ' '.Rune): string {. + noSideEffect.} = + ## Left-Aligns a unicode string `s` with `padding`, so that it has a + ## rune-length of `count`. + ## + ## `padding` characters (by default spaces) are added after `s` resulting in + ## left alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## returned unchanged. If you need to right align a string use the `align + ## proc <#align>`_. + runnableExamples: + assert alignLeft("abc", 4) == "abc " + assert alignLeft("a", 0) == "a" + assert alignLeft("1232", 6) == "1232 " + assert alignLeft("1232", 6, '#'.Rune) == "1232##" + assert alignLeft("Åge", 5) == "Åge " + assert alignLeft("×", 4, '_'.Rune) == "×___" + let sLen = s.runeLen + if sLen < count: + let padStr = $padding + result = newStringOfCap(s.len + (count - sLen) * padStr.len) + result.add s + for i in sLen ..< count: + result.add padStr + else: + result = s + + when isMainModule: + + proc asRune(s: static[string]): Rune = + ## Compile-time conversion proc for converting string literals to a Rune + ## value. Returns the first Rune of the specified string. + ## + ## Shortcuts code like ``"å".runeAt(0)`` to ``"å".asRune`` and returns a + ## compile-time constant. + if s.len == 0: Rune(0) + else: s.runeAt(0) + let someString = "öÑ" someRunes = @[runeAt(someString, 0), runeAt(someString, 2)] @@ -1764,12 +2051,6 @@ when isMainModule: doAssert capitalize("foo") == "Foo" doAssert capitalize("") == "" - doAssert isTitle("Foo") - doAssert(not isTitle("Foo bar")) - doAssert(not isTitle("αlpha Βeta")) - doAssert(isTitle("Αlpha Βeta Γamma")) - doAssert(not isTitle("fFoo")) - doAssert swapCase("FooBar") == "fOObAR" doAssert swapCase(" ") == " " doAssert swapCase("Αlpha Βeta Γamma") == "αLPHA βETA γAMMA" @@ -1797,38 +2078,8 @@ when isMainModule: doAssert(not isLower(' '.Rune)) - doAssert isLower("a", false) - doAssert isLower("γ", true) - doAssert(not isLower("Γ", false)) - doAssert(not isLower("4", true)) - doAssert(not isLower("", false)) - doAssert isLower("abcdγ", false) - doAssert(not isLower("33aaΓ", false)) - doAssert(not isLower("a b", false)) - - doAssert(not isLower("abCDΓ", true)) - doAssert isLower("a b", true) - doAssert isLower("1, 2, 3 go!", true) - doAssert(not isLower(" ", true)) - doAssert(not isLower("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets - doAssert(not isUpper(' '.Rune)) - doAssert isUpper("Γ", false) - doAssert(not isUpper("α", false)) - doAssert(not isUpper("", false)) - doAssert isUpper("ΑΒΓ", false) - doAssert(not isUpper("A#$β", false)) - doAssert(not isUpper("A B", false)) - - doAssert(not isUpper("b", true)) - doAssert(not isUpper("✓", true)) - doAssert(not isUpper("AAccβ", true)) - doAssert isUpper("A B", true) - doAssert isUpper("1, 2, 3 GO!", true) - doAssert(not isUpper(" ", true)) - doAssert(not isUpper("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets - doAssert toUpper("Γ") == "Γ" doAssert toUpper("b") == "B" doAssert toUpper("α") == "Α" @@ -1898,3 +2149,50 @@ when isMainModule: doAssert(runeSubStr(s, -100, 100) == "Hänsel ««: 10,00€") doAssert(runeSubStr(s, 0, -100) == "") doAssert(runeSubStr(s, 100, -100) == "") + + block splitTests: + let s = " this is an example " + let s2 = ":this;is;an:example;;" + let s3 = ":this×is×an:example××" + doAssert s.split() == @["", "this", "is", "an", "example", "", ""] + doAssert s2.split(seps = [':'.Rune, ';'.Rune]) == @["", "this", "is", "an", "example", "", ""] + doAssert s3.split(seps = [':'.Rune, "×".asRune]) == @["", "this", "is", "an", "example", "", ""] + doAssert s.split(maxsplit = 4) == @["", "this", "is", "an", "example "] + doAssert s.split(' '.Rune, maxsplit = 1) == @["", "this is an example "] + + block stripTests: + doAssert(strip("") == "") + doAssert(strip(" ") == "") + doAssert(strip("y") == "y") + doAssert(strip(" foofoofoo ") == "foofoofoo") + doAssert(strip("sfoofoofoos", runes = ['s'.Rune]) == "foofoofoo") + + block: + let stripTestRunes = ['b'.Rune, 'a'.Rune, 'r'.Rune] + doAssert(strip("barfoofoofoobar", runes = stripTestRunes) == "foofoofoo") + doAssert(strip("sfoofoofoos", leading = false, runes = ['s'.Rune]) == "sfoofoofoo") + doAssert(strip("sfoofoofoos", trailing = false, runes = ['s'.Rune]) == "foofoofoos") + + block: + let stripTestRunes = ["«".asRune, "»".asRune] + doAssert(strip("«TEXT»", runes = stripTestRunes) == "TEXT") + doAssert(strip("copyright©", leading = false, runes = ["©".asRune]) == "copyright") + doAssert(strip("¿Question?", trailing = false, runes = ["¿".asRune]) == "Question?") + doAssert(strip("×text×", leading = false, runes = ["×".asRune]) == "×text") + doAssert(strip("×text×", trailing = false, runes = ["×".asRune]) == "text×") + + block repeatTests: + doAssert repeat('c'.Rune, 5) == "ccccc" + doAssert repeat("×".asRune, 5) == "×××××" + + block alignTests: + doAssert align("abc", 4) == " abc" + doAssert align("a", 0) == "a" + doAssert align("1232", 6) == " 1232" + doAssert align("1232", 6, '#'.Rune) == "##1232" + doAssert align("1232", 6, "×".asRune) == "××1232" + doAssert alignLeft("abc", 4) == "abc " + doAssert alignLeft("a", 0) == "a" + doAssert alignLeft("1232", 6) == "1232 " + doAssert alignLeft("1232", 6, '#'.Rune) == "1232##" + doAssert alignLeft("1232", 6, "×".asRune) == "1232××" diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 757bf4745..837072be2 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -145,8 +145,6 @@ type testStartTime: float testStackTrace: string -{.deprecated: [TTestStatus: TestStatus, TOutputLevel: OutputLevel]} - var abortOnError* {.threadvar.}: bool ## Set to true in order to quit ## immediately on fail. Default is false, |