diff options
Diffstat (limited to 'lib/pure')
34 files changed, 1444 insertions, 1647 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 5ef791cfe..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().} @@ -199,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: @@ -243,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.} @@ -1079,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.} @@ -1640,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 965e70055..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``. @@ -369,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/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 e8ea675f5..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. ## @@ -1033,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 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/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/editdistance.nim b/lib/pure/editdistance.nim deleted file mode 100644 index 40beb7d93..000000000 --- a/lib/pure/editdistance.nim +++ /dev/null @@ -1,295 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2018 Nim contributors -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements an algorithm to compute the -## `edit distance`:idx: between two Unicode strings. - -import unicode - -proc editDistance*(a, b: string): int {.noSideEffect.} = - ## Returns the unicode-rune edit distance between ``a`` and ``b``. - ## - ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. - if len(a) > len(b): - # make ``b`` the longer string - return editDistance(b, a) - # strip common prefix - var - i_start = 0 ## The character starting index of the first rune in both strings ``a`` and ``b`` - i_next_a = 0 - i_next_b = 0 - rune_a, rune_b: Rune - len_runes_a = 0 ## The number of relevant runes in string ``a``. - len_runes_b = 0 ## The number of relevant runes in string ``b``. - block commonPrefix: - # ``a`` is the shorter string - while i_start < len(a): - i_next_a = i_start - a.fastRuneAt(i_next_a, rune_a, doInc = true) - i_next_b = i_start - b.fastRuneAt(i_next_b, rune_b, doInc = true) - if rune_a != rune_b: - inc(len_runes_a) - inc(len_runes_b) - break - i_start = i_next_a - var - # we know that we are either at the start of the strings - # or that the current value of rune_a is not equal to rune_b - # => start search for common suffix after the current rune (``i_next_*``) - i_end_a = i_next_a ## The exclusive upper index bound of string ``a``. - i_end_b = i_next_b ## The exclusive upper index bound of string ``b``. - i_current_a = i_next_a - i_current_b = i_next_b - block commonSuffix: - var - add_runes_a = 0 - add_runes_b = 0 - while i_current_a < len(a) and i_current_b < len(b): - i_next_a = i_current_a - a.fastRuneAt(i_next_a, rune_a) - i_next_b = i_current_b - b.fastRuneAt(i_next_b, rune_b) - inc(add_runes_a) - inc(add_runes_b) - if rune_a != rune_b: - i_end_a = i_next_a - i_end_b = i_next_b - inc(len_runes_a, add_runes_a) - inc(len_runes_b, add_runes_b) - add_runes_a = 0 - add_runes_b = 0 - i_current_a = i_next_a - i_current_b = i_next_b - if i_current_a >= len(a): # ``a`` exhausted - if i_current_b < len(b): # ``b`` not exhausted - i_end_a = i_current_a - i_end_b = i_current_b - inc(len_runes_a, add_runes_a) - inc(len_runes_b, add_runes_b) - while true: - b.fastRuneAt(i_end_b, rune_b) - inc(len_runes_b) - if i_end_b >= len(b): break - elif i_current_b >= len(b): # ``b`` exhausted and ``a`` not exhausted - i_end_a = i_current_a - i_end_b = i_current_b - inc(len_runes_a, add_runes_a) - inc(len_runes_b, add_runes_b) - while true: - a.fastRuneAt(i_end_a, rune_a) - inc(len_runes_a) - if i_end_a >= len(a): break - block specialCases: - # trivial cases: - if len_runes_a == 0: return len_runes_b - if len_runes_b == 0: return len_runes_a - # another special case: - if len_runes_a == 1: - a.fastRuneAt(i_start, rune_a, doInc = false) - var i_current_b = i_start - while i_current_b < i_end_b: - b.fastRuneAt(i_current_b, rune_b, doInc = true) - if rune_a == rune_b: return len_runes_b - 1 - return len_runes_b - # common case: - var - len1 = len_runes_a + 1 - len2 = len_runes_b + 1 - row: seq[int] - let half = len_runes_a div 2 - newSeq(row, len2) - var e = i_start + len2 - 1 # end marker - # initialize first row: - for i in 1 .. (len2 - half - 1): row[i] = i - row[0] = len1 - half - 1 - i_current_a = i_start - var - char2p_i = -1 - char2p_prev: int - for i in 1 .. (len1 - 1): - i_next_a = i_current_a - a.fastRuneAt(i_next_a, rune_a) - var - char2p: int - D, x: int - p: int - if i >= (len1 - half): - # skip the upper triangle: - let offset = i + half - len1 - if char2p_i == i: - b.fastRuneAt(char2p_prev, rune_b) - char2p = char2p_prev - char2p_i = i + 1 - else: - char2p = i_start - for j in 0 ..< offset: - rune_b = b.runeAt(char2p) - inc(char2p, rune_b.size) - char2p_i = i + 1 - char2p_prev = char2p - p = offset - rune_b = b.runeAt(char2p) - var c3 = row[p] + (if rune_a != rune_b: 1 else: 0) - inc(char2p, rune_b.size) - inc(p) - x = row[p] + 1 - D = x - if x > c3: x = c3 - row[p] = x - inc(p) - else: - p = 1 - char2p = i_start - D = i - x = i - if i <= (half + 1): - # skip the lower triangle: - e = len2 + i - half - 2 - # main: - while p <= e: - dec(D) - rune_b = b.runeAt(char2p) - var c3 = D + (if rune_a != rune_b: 1 else: 0) - inc(char2p, rune_b.size) - inc(x) - if x > c3: x = c3 - D = row[p] + 1 - if x > D: x = D - row[p] = x - inc(p) - # lower triangle sentinel: - if i <= half: - dec(D) - rune_b = b.runeAt(char2p) - var c3 = D + (if rune_a != rune_b: 1 else: 0) - inc(x) - if x > c3: x = c3 - row[p] = x - i_current_a = i_next_a - result = row[e] - -proc editDistanceAscii*(a, b: string): int {.noSideEffect.} = - ## Returns the edit distance between `a` and `b`. - ## - ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. - var len1 = a.len - var len2 = b.len - if len1 > len2: - # make `b` the longer string - return editDistanceAscii(b, a) - - # strip common prefix: - var s = 0 - while s < len1 and a[s] == b[s]: - inc(s) - dec(len1) - dec(len2) - # strip common suffix: - while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]: - dec(len1) - dec(len2) - # trivial cases: - if len1 == 0: return len2 - if len2 == 0: return len1 - - # another special case: - if len1 == 1: - for j in s..s+len2-1: - if a[s] == b[j]: return len2 - 1 - return len2 - - inc(len1) - inc(len2) - var half = len1 shr 1 - # initalize first row: - #var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2*sizeof(int))) - var row: seq[int] - newSeq(row, len2) - var e = s + len2 - 1 # end marker - for i in 1..len2 - half - 1: row[i] = i - row[0] = len1 - half - 1 - for i in 1 .. len1 - 1: - var char1 = a[i + s - 1] - var char2p: int - var D, x: int - var p: int - if i >= len1 - half: - # skip the upper triangle: - var offset = i - len1 + half - char2p = offset - p = offset - var c3 = row[p] + ord(char1 != b[s + char2p]) - inc(p) - inc(char2p) - x = row[p] + 1 - D = x - if x > c3: x = c3 - row[p] = x - inc(p) - else: - p = 1 - char2p = 0 - D = i - x = i - if i <= half + 1: - # skip the lower triangle: - e = len2 + i - half - 2 - # main: - while p <= e: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(char2p) - inc(x) - if x > c3: x = c3 - D = row[p] + 1 - if x > D: x = D - row[p] = x - inc(p) - # lower triangle sentinel: - if i <= half: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(x) - if x > c3: x = c3 - row[p] = x - result = row[e] - - -when isMainModule: - doAssert editDistance("", "") == 0 - doAssert editDistance("kitten", "sitting") == 3 # from Wikipedia - doAssert editDistance("flaw", "lawn") == 2 # from Wikipedia - - doAssert editDistance("привет", "превет") == 1 - doAssert editDistance("Åge", "Age") == 1 - # editDistance, one string is longer in bytes, but shorter in rune length - # first string: 4 bytes, second: 6 bytes, but only 3 runes - doAssert editDistance("aaaa", "×××") == 4 - - block veryLongStringEditDistanceTest: - const cap = 256 - var - s1 = newStringOfCap(cap) - s2 = newStringOfCap(cap) - while len(s1) < cap: - s1.add 'a' - while len(s2) < cap: - s2.add 'b' - doAssert editDistance(s1, s2) == cap - - block combiningCodePointsEditDistanceTest: - const s = "A\xCC\x8Age" - doAssert editDistance(s, "Age") == 1 - - doAssert editDistanceAscii("", "") == 0 - doAssert editDistanceAscii("kitten", "sitting") == 3 # from Wikipedia - doAssert editDistanceAscii("flaw", "lawn") == 2 # from Wikipedia diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim index 96f030c9b..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".} diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index dceac1763..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 = @@ -1163,7 +1161,7 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, {.multisync.} = # Helper that actually makes the request. Does not handle redirects. let requestUrl = parseUri(url) - + if requestUrl.scheme == "": raise newException(ValueError, "No uri scheme supplied.") 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/math.nim b/lib/pure/math.nim index e2ad626de..ee32772b1 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -479,7 +479,8 @@ when not defined(JS): # C ## (-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.} diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 9d74158fe..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 + + 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 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>".} +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" -when defined(windows): + 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,19 +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 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): @@ -347,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. @@ -383,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: @@ -409,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. @@ -449,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 @@ -492,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. @@ -524,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``. @@ -560,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 @@ -611,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 = @@ -621,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. @@ -644,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. @@ -654,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. @@ -675,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): @@ -689,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 @@ -704,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 = @@ -760,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. ## @@ -768,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. ## @@ -776,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. ## @@ -791,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 @@ -832,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: @@ -920,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) @@ -934,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). ## @@ -946,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 # @@ -991,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). @@ -1004,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. @@ -1027,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 @@ -1045,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. ## @@ -1068,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`. ## @@ -1176,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>`_, @@ -1200,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() @@ -1231,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 @@ -1240,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): @@ -1255,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. @@ -1320,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 @@ -1346,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") @@ -1397,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> @@ -1430,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) @@ -1439,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)) @@ -1483,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. @@ -1543,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)) @@ -1558,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): @@ -1574,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 @@ -1654,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. ## @@ -1675,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 @@ -1714,7 +2391,7 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, path, result) -proc isHidden*(path: string): bool = +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 ## @@ -1744,7 +2421,7 @@ proc isHidden*(path: string): bool = {.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): @@ -1760,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 7f7f9a425..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`: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 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 dfea4213d..ac455ce99 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): @@ -1324,6 +1323,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 4b841b9e1..20f02e815 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -566,9 +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 diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 3909d18f8..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 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/strutils.nim b/lib/pure/strutils.nim index e266275cf..4d0fe800e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -149,8 +149,8 @@ proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, ## in `s`. runnableExamples: doAssert isAlphaAscii("fooBar") == true - doAssert isAlphaAscii("fooBar1") == false - doAssert isAlphaAscii("foo Bar") == false + doAssert isAlphaAscii("fooBar1") == false + doAssert isAlphaAscii("foo Bar") == false isImpl isAlphaAscii proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, @@ -164,7 +164,7 @@ proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, ## in `s`. runnableExamples: doAssert isAlphaNumeric("fooBar") == true - doAssert isAlphaNumeric("fooBar") == true + doAssert isAlphaNumeric("fooBar") == true doAssert isAlphaNumeric("foo Bar") == false isImpl isAlphaNumeric @@ -179,7 +179,7 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, ## in `s`. runnableExamples: doAssert isDigit("1908") == true - doAssert isDigit("fooBar1") == false + doAssert isDigit("fooBar1") == false isImpl isDigit proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, @@ -191,7 +191,7 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, ## characters and there is at least one character in `s`. runnableExamples: doAssert isSpaceAscii(" ") == true - doAssert isSpaceAscii("") == false + doAssert isSpaceAscii("") == false isImpl isSpaceAscii template isCaseImpl(s, charProc, skipNonAlpha) = @@ -420,7 +420,7 @@ 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" + doAssert toOctal('!') == "041" result = newString(3) var val = ord(c) for i in countdown(2, 0): @@ -933,7 +933,7 @@ proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, ## achieved by adding leading zeros. runnableExamples: doAssert intToStr(1984) == "1984" - doAssert intToStr(1984, 6) == "001984" + doAssert intToStr(1984, 6) == "001984" result = $abs(x) for i in 1 .. minchars - len(result): result = '0' & result @@ -1211,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 @@ -1262,6 +1263,7 @@ 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: @@ -1525,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: @@ -1632,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: @@ -1691,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 @@ -1722,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 @@ -1983,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 {. @@ -2510,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 @@ -2612,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 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/terminal.nim b/lib/pure/terminal.nim index 974dc839d..35dc2483c 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -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 diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 62284c6cb..fd1a6acc5 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -298,9 +298,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 +745,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 +839,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() @@ -1050,7 +1051,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) @@ -1144,16 +1145,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, @@ -1385,7 +1386,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 @@ -1400,28 +1400,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() @@ -1549,7 +1541,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 # @@ -1813,7 +1804,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: '-' @@ -2026,9 +2017,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] != ':': @@ -2508,4 +2499,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 0107e2715..664765954 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 @@ -1963,12 +1966,12 @@ proc align*(s: string, count: Natural, padding = ' '.Rune): string {. ## 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) == "___×" + 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: 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, |