diff options
Diffstat (limited to 'lib')
124 files changed, 6550 insertions, 6580 deletions
diff --git a/lib/core/allocators.nim b/lib/core/allocators.nim new file mode 100644 index 000000000..d6608a203 --- /dev/null +++ b/lib/core/allocators.nim @@ -0,0 +1,35 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +type + Allocator* {.inheritable.} = ptr object + alloc*: proc (a: Allocator; size: int; alignment = 8): pointer {.nimcall.} + dealloc*: proc (a: Allocator; p: pointer; size: int) {.nimcall.} + realloc*: proc (a: Allocator; p: pointer; oldSize, newSize: int): pointer {.nimcall.} + +var + currentAllocator {.threadvar.}: Allocator + +proc getCurrentAllocator*(): Allocator = + result = currentAllocator + +proc setCurrentAllocator*(a: Allocator) = + currentAllocator = a + +proc alloc*(size: int): pointer = + let a = getCurrentAllocator() + result = a.alloc(a, size) + +proc dealloc*(p: pointer; size: int) = + let a = getCurrentAllocator() + a.dealloc(a, size) + +proc realloc*(p: pointer; oldSize, newSize: int): pointer = + let a = getCurrentAllocator() + result = a.realloc(a, oldSize, newSize) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 8c70d2b47..b08a2198e 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -21,7 +21,7 @@ type nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, nnkUInt16Lit, nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit, - nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall, + nnkTripleStrLit, nnkNilLit, nnkComesFrom, nnkDotCall, nnkCommand, nnkCall, nnkCallStrLit, nnkInfix, nnkPrefix, nnkPostfix, nnkHiddenCallConv, nnkExprEqExpr, @@ -113,7 +113,9 @@ type type NimIdent* = object of RootObj - ## represents a Nim identifier in the AST + ## represents a Nim identifier in the AST. **Note**: This is only + ## rarely useful, for identifier construction from a string + ## use ``ident"abc"``. NimSymObj = object # hidden NimSym* = ref NimSymObj @@ -129,14 +131,11 @@ const nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit} -proc `[]`*(n: NimNode, i: int): NimNode {.magic: "NChild", noSideEffect.} - ## get `n`'s `i`'th child. - -proc `[]=`*(n: NimNode, i: int, child: NimNode) {.magic: "NSetChild", - noSideEffect.} - ## set `n`'s `i`'th child to `child`. +proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.} + ## constructs an identifier from the string `s` + ## **Deprecated since version 0.18.0**: Use ``toNimIdent`` instead. -proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.} +proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.} ## constructs an identifier from the string `s` proc `$`*(i: NimIdent): string {.magic: "IdentToStr", noSideEffect.} @@ -162,6 +161,20 @@ proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} = proc len*(n: NimNode): int {.magic: "NLen", noSideEffect.} ## returns the number of children of `n`. +proc `[]`*(n: NimNode, i: int): NimNode {.magic: "NChild", noSideEffect.} + ## get `n`'s `i`'th child. + +proc `[]`*(n: NimNode, i: BackwardsIndex): NimNode = n[n.len - i.int] + ## get `n`'s `i`'th child. + +proc `[]=`*(n: NimNode, i: int, child: NimNode) {.magic: "NSetChild", + noSideEffect.} + ## set `n`'s `i`'th child to `child`. + +proc `[]=`*(n: NimNode, i: BackwardsIndex, child: NimNode) = + ## set `n`'s `i`'th child to `child`. + n[n.len - i.int] = child + proc add*(father, child: NimNode): NimNode {.magic: "NAdd", discardable, noSideEffect, locks: 0.} ## Adds the `child` to the `father` node. Returns the @@ -230,7 +243,7 @@ proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect.} proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.} proc newNimNode*(kind: NimNodeKind, - lineInfoFrom: NimNode=nil): NimNode + lineInfoFrom: NimNode = nil): NimNode {.magic: "NNewNimNode", noSideEffect.} ## Creates a new AST node of the specified kind. ## @@ -283,7 +296,7 @@ proc newIdentNode*(i: NimIdent): NimNode {.compileTime.} = proc newIdentNode*(i: string): NimNode {.compileTime.} = ## creates an identifier node from `i` result = newNimNode(nnkIdent) - result.ident = !i + result.ident = toNimIdent i type @@ -393,7 +406,7 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.} ## ## .. code-block:: nim ## - ## macro check(ex: expr): stmt = + ## macro check(ex: untyped): typed = ## # this is a simplified version of the check macro from the ## # unittest module. ## @@ -428,6 +441,13 @@ proc expectLen*(n: NimNode, len: int) {.compileTime.} = ## macros that check its number of arguments. if n.len != len: error("macro expects a node with " & $len & " children", n) +proc expectLen*(n: NimNode, min, max: int) {.compileTime.} = + ## checks that `n` has a number of children in the range ``min..max``. + ## If this is not the case, compilation aborts with an error message. + ## This is useful for writing macros that check its number of arguments. + if n.len < min or n.len > max: + error("macro expects a node with " & $min & ".." & $max " children", n) + proc newTree*(kind: NimNodeKind, children: varargs[NimNode]): NimNode {.compileTime.} = ## produces a new node with children. @@ -463,7 +483,6 @@ proc newLit*(c: char): NimNode {.compileTime.} = result = newNimNode(nnkCharLit) result.intVal = ord(c) - proc newLit*(i: int): NimNode {.compileTime.} = ## produces a new integer literal node. result = newNimNode(nnkIntLit) @@ -567,7 +586,7 @@ proc newLit*[T](arg: seq[T]): NimNode {.compileTime.} = proc newLit*(arg: tuple): NimNode {.compileTime.} = result = nnkPar.newTree for a,b in arg.fieldPairs: - result.add nnkExprColonExpr.newTree( newIdentNode(a), newLit(b) ) + result.add nnkExprColonExpr.newTree(newIdentNode(a), newLit(b)) proc newLit*(s: string): NimNode {.compileTime.} = ## produces a new string literal node. @@ -601,7 +620,7 @@ proc treeRepr*(n: NimNode): string {.compileTime, benign.} = of nnkCharLit..nnkInt64Lit: res.add(" " & $n.intVal) of nnkFloatLit..nnkFloat64Lit: res.add(" " & $n.floatVal) of nnkStrLit..nnkTripleStrLit: res.add(" " & $n.strVal) - of nnkIdent: res.add(" !\"" & $n.ident & '"') + of nnkIdent: res.add(" ident\"" & $n.ident & '"') of nnkSym: res.add(" \"" & $n.symbol & '"') of nnkNone: assert false else: @@ -626,7 +645,7 @@ proc lispRepr*(n: NimNode): string {.compileTime, benign.} = of nnkCharLit..nnkInt64Lit: add(result, $n.intVal) of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal) of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal) - of nnkIdent: add(result, "!\"" & $n.ident & '"') + of nnkIdent: add(result, "ident\"" & $n.ident & '"') of nnkSym: add(result, $n.symbol) of nnkNone: assert false else: @@ -650,7 +669,7 @@ proc astGenRepr*(n: NimNode): string {.compileTime, benign.} = ## .. code-block:: nim ## nnkStmtList.newTree( ## nnkCommand.newTree( - ## newIdentNode(!"echo"), + ## newIdentNode("echo"), ## newLit("Hello world") ## ) ## ) @@ -704,7 +723,7 @@ proc astGenRepr*(n: NimNode): string {.compileTime, benign.} = of nnkIntLit..nnkInt64Lit: res.add($n.intVal) of nnkFloatLit..nnkFloat64Lit: res.add($n.floatVal) of nnkStrLit..nnkTripleStrLit: res.add($n.strVal.escape()) - of nnkIdent: res.add("!" & ($n.ident).escape()) + of nnkIdent: res.add(($n.ident).escape()) of nnkSym: res.add(($n.symbol).escape()) of nnkNone: assert false else: @@ -839,7 +858,7 @@ proc newNilLit*(): NimNode {.compileTime.} = ## New nil literal shortcut result = newNimNode(nnkNilLit) -proc last*(node: NimNode): NimNode {.compileTime.} = node[<node.len] +proc last*(node: NimNode): NimNode {.compileTime.} = node[node.len-1] ## Return the last item in nodes children. Same as `node[^1]` @@ -884,10 +903,52 @@ proc newIfStmt*(branches: varargs[tuple[cond, body: NimNode]]): for i in branches: result.add(newNimNode(nnkElifBranch).add(i.cond, i.body)) +proc newEnum*(name: NimNode, fields: openArray[NimNode], + public, pure: bool): NimNode {.compileTime.} = + + ## Creates a new enum. `name` must be an ident. Fields are allowed to be + ## either idents or EnumFieldDef + ## + ## .. code-block:: nim + ## + ## newEnum( + ## name = ident("Colors"), + ## fields = [ident("Blue"), ident("Red")], + ## public = true, pure = false) + ## + ## # type Colors* = Blue Red + ## + + expectKind name, nnkIdent + doAssert len(fields) > 0, "Enum must contain at least one field" + for field in fields: + expectKind field, {nnkIdent, nnkEnumFieldDef} + + let enumBody = newNimNode(nnkEnumTy).add(newEmptyNode()).add(fields) + var typeDefArgs = [name, newEmptyNode(), enumBody] + + if public: + let postNode = newNimNode(nnkPostfix).add( + newIdentNode("*"), typeDefArgs[0]) + + typeDefArgs[0] = postNode + + if pure: + let pragmaNode = newNimNode(nnkPragmaExpr).add( + typeDefArgs[0], + add(newNimNode(nnkPragma), newIdentNode("pure"))) + + typeDefArgs[0] = pragmaNode + + let + typeDef = add(newNimNode(nnkTypeDef), typeDefArgs) + typeSect = add(newNimNode(nnkTypeSection), typeDef) + + return typeSect proc copyChildrenTo*(src, dest: NimNode) {.compileTime.}= ## Copy all children from `src` to `dest` - for i in 0 .. < src.len: + for i in 0 ..< src.len: dest.add src[i].copyNimTree template expectRoutine(node: NimNode) = @@ -986,6 +1047,11 @@ iterator items*(n: NimNode): NimNode {.inline.} = for i in 0 ..< n.len: yield n[i] +iterator pairs*(n: NimNode): (int, NimNode) {.inline.} = + ## Iterates over the children of the NimNode ``n`` and its indices. + for i in 0 ..< n.len: + yield (i, n[i]) + iterator children*(n: NimNode): NimNode {.inline.} = ## Iterates over the children of the NimNode ``n``. for i in 0 ..< n.len: @@ -996,7 +1062,7 @@ template findChild*(n: NimNode; cond: untyped): NimNode {.dirty.} = ## ## .. code-block:: nim ## var res = findChild(n, it.kind == nnkPostfix and - ## it.basename.ident == !"foo") + ## it.basename.ident == toNimIdent"foo") block: var res: NimNode for it in n.children: @@ -1030,7 +1096,7 @@ proc basename*(a: NimNode): NimNode = proc `basename=`*(a: NimNode; val: string) {.compileTime.}= case a.kind - of nnkIdent: macros.`ident=`(a, !val) + of nnkIdent: macros.`ident=`(a, toNimIdent val) of nnkPostfix, nnkPrefix: a[1] = ident(val) else: quit "Do not know how to get basename of (" & treeRepr(a) & ")\n" & repr(a) @@ -1091,7 +1157,7 @@ proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} = ## other ways like ``node.ident`` are much more error-prone, unfortunately. case node.kind of nnkIdent: - result = node.ident == !s + result = node.ident == toNimIdent s of nnkSym: result = eqIdent($node.symbol, s) of nnkOpenSymChoice, nnkClosedSymChoice: @@ -1099,10 +1165,10 @@ proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} = else: result = false -proc hasArgOfName* (params: NimNode; name: string): bool {.compiletime.}= +proc hasArgOfName*(params: NimNode; name: string): bool {.compiletime.}= ## Search nnkFormalParams for an argument. assert params.kind == nnkFormalParams - for i in 1 .. <params.len: + for i in 1 ..< params.len: template node: untyped = params[i] if name.eqIdent( $ node[0]): return true @@ -1160,3 +1226,8 @@ when not defined(booting): macro payload: untyped {.gensym.} = result = parseStmt(e) payload() + +macro unpackVarargs*(callee: untyped; args: varargs[untyped]): untyped = + result = newCall(callee) + for i in 0 ..< args.len: + result.add args[i] diff --git a/lib/core/refs.nim b/lib/core/refs.nim new file mode 100644 index 000000000..e1575b68c --- /dev/null +++ b/lib/core/refs.nim @@ -0,0 +1,97 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Default ref implementation used by Nim's core. + +# We cannot use the allocator interface here as we require a heap walker to +# exist. Thus we import 'alloc' directly here to get our own heap that is +# all under the GC's control and can use the ``allObjects`` iterator which +# is crucial for the "sweep" phase. +import typelayouts, alloc + +type + TracingGc = ptr object of Allocator + visit*: proc (fieldAddr: ptr pointer; a: Allocator) {.nimcall.} + + GcColor = enum + white = 0, black = 1, grey = 2 ## to flip the meaning of white/black + ## perform (1 - col) + + GcHeader = object + t: ptr TypeLayout + color: GcColor + Cell = ptr GcHeader + + GcFrame {.core.} = object + prev: ptr GcFrame + marker: proc (self: GcFrame; a: Allocator) + + Phase = enum + None, Marking, Sweeping + + GcHeap = object + r: MemRegion + phase: Phase + currBlack, currWhite: GcColor + greyStack: seq[Cell] + +var + gch {.threadvar.}: GcHeap + +proc `=trace`[T](a: ref T) = + if not marked(a): + mark(a) + `=trace`(a[]) + +template usrToCell(p: pointer): Cell = + +template cellToUsr(cell: Cell): pointer = + cast[pointer](cast[ByteAddress](cell)+%ByteAddress(sizeof(GcHeader))) + +template usrToCell(usr: pointer): Cell = + cast[Cell](cast[ByteAddress](usr)-%ByteAddress(sizeof(GcHeader))) + +template markGrey(x: Cell) = + if x.color == gch.currWhite and phase == Marking: + x.color = grey + add(gch.greyStack, x) + +proc `=`[T](dest: var ref T; src: ref T) = + ## full write barrier implementation. + if src != nil: + let s = usrToCell(src) + markGrey(s) + system.`=`(dest, src) + +proc linkGcFrame(f: ptr GcFrame) {.core.} +proc unlinkGcFrame() {.core.} + +proc setGcFrame(f: ptr GcFrame) {.core.} + +proc registerGlobal(p: pointer; t: ptr TypeLayout) {.core.} +proc unregisterGlobal(p: pointer; t: ptr TypeLayout) {.core.} + +proc registerThreadvar(p: pointer; t: ptr TypeLayout) {.core.} +proc unregisterThreadvar(p: pointer; t: ptr TypeLayout) {.core.} + +proc newImpl(t: ptr TypeLayout): pointer = + let r = cast[Cell](rawAlloc(t.size + sizeof(GcHeader))) + r.typ = t + result = r +! sizeof(GcHeader) + +template new*[T](x: var ref T) = + x = newImpl(getTypeLayout(x)) + + +when false: + # implement these if your GC requires them: + proc writeBarrierLocal() {.core.} + proc writeBarrierGlobal() {.core.} + + proc writeBarrierGeneric() {.core.} diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim new file mode 100644 index 000000000..6be95a3bc --- /dev/null +++ b/lib/core/seqs.nim @@ -0,0 +1,117 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import allocators + +## Default seq implementation used by Nim's core. +type + seq*[T] = object + len, cap: int + data: ptr UncheckedArray[T] + +template frees(s) = dealloc(s.data, s.cap * sizeof(T)) + +# XXX make code memory safe for overflows in '*' +proc nimSeqLiteral[T](x: openArray[T]): seq[T] {.core.} = + seq[T](len: x.len, cap: x.len, data: x) + +when defined(nimHasTrace): + proc `=trace`[T](s: seq[T]; a: Allocator) = + for i in 0 ..< s.len: `=trace`(s.data[i], a) + +proc `=destroy`[T](x: var seq[T]) = + if x.data != nil: + when not supportsCopyMem(T): + for i in 0..<x.len: `=destroy`(x[i]) + frees(x) + x.data = nil + x.len = 0 + x.cap = 0 + +proc `=`[T](a: var seq[T]; b: seq[T]) = + if a.data == b.data: return + if a.data != nil: + frees(a) + a.data = nil + a.len = b.len + a.cap = b.cap + if b.data != nil: + a.data = cast[type(a.data)](alloc(a.cap * sizeof(T))) + when supportsCopyMem(T): + copyMem(a.data, b.data, a.cap * sizeof(T)) + else: + for i in 0..<a.len: + a.data[i] = b.data[i] + +proc `=sink`[T](a: var seq[T]; b: seq[T]) = + if a.data != nil and a.data != b.data: + frees(a) + a.len = b.len + a.cap = b.cap + a.data = b.data + +proc resize[T](s: var seq[T]) = + let old = s.cap + if old == 0: s.cap = 8 + else: s.cap = (s.cap * 3) shr 1 + s.data = cast[type(s.data)](realloc(s.data, old * sizeof(T), s.cap * sizeof(T))) + +proc reserveSlot[T](x: var seq[T]): ptr T = + if x.len >= x.cap: resize(x) + result = addr(x.data[x.len]) + inc x.len + +template add*[T](x: var seq[T]; y: T) = + reserveSlot(x)[] = y + +proc shrink*[T](x: var seq[T]; newLen: int) = + assert newLen <= x.len + assert newLen >= 0 + when not supportsCopyMem(T): + for i in countdown(x.len - 1, newLen - 1): + `=destroy`(x.data[i]) + x.len = newLen + +proc grow*[T](x: var seq[T]; newLen: int; value: T) = + if newLen <= x.len: return + assert newLen >= 0 + if x.cap == 0: x.cap = newLen + else: x.cap = max(newLen, (x.cap * 3) shr 1) + x.data = cast[type(x.data)](realloc(x.data, x.cap * sizeof(T))) + for i in x.len..<newLen: + x.data[i] = value + x.len = newLen + +template default[T](t: typedesc[T]): T = + var v: T + v + +proc setLen*[T](x: var seq[T]; newLen: int) {.deprecated.} = + if newlen < x.len: shrink(x, newLen) + else: grow(x, newLen, default(T)) + +template `[]`*[T](x: seq[T]; i: Natural): T = + assert i < x.len + x.data[i] + +template `[]=`*[T](x: seq[T]; i: Natural; y: T) = + assert i < x.len + x.data[i] = y + +proc `@`*[T](elems: openArray[T]): seq[T] = + result.cap = elems.len + result.len = elems.len + result.data = cast[type(result.data)](alloc(result.cap * sizeof(T))) + when supportsCopyMem(T): + copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T)) + else: + for i in 0..<result.len: + result.data[i] = elems[i] + +proc len*[T](x: seq[T]): int {.inline.} = x.len diff --git a/lib/core/strs.nim b/lib/core/strs.nim new file mode 100644 index 000000000..1958f4974 --- /dev/null +++ b/lib/core/strs.nim @@ -0,0 +1,111 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Default string implementation used by Nim's core. + +import allocators + +type + string {.core.} = object + len, cap: int + data: ptr UncheckedArray[char] + +proc nimStringLiteral(x: cstring; len: int): string {.core.} = + string(len: len, cap: len, data: x) + +template frees(s) = dealloc(s.data, s.cap + 1) + +proc `=destroy`(s: var string) = + if s.data != nil: + frees(s) + s.data = nil + s.len = 0 + s.cap = 0 + +proc `=sink`(a: var string, b: string) = + # we hope this is optimized away for not yet alive objects: + if a.data != nil and a.data != b.data: + frees(a) + a.len = b.len + a.cap = b.cap + a.data = b.data + +proc `=`(a: var string; b: string) = + if a.data != nil and a.data != b.data: + frees(a) + a.data = nil + a.len = b.len + a.cap = b.cap + if b.data != nil: + a.data = cast[type(a.data)](alloc(a.cap + 1)) + copyMem(a.data, b.data, a.cap+1) + +proc resize(s: var string) = + let old = s.cap + if old == 0: s.cap = 8 + else: s.cap = (s.cap * 3) shr 1 + s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1)) + +proc add*(s: var string; c: char) = + if s.len >= s.cap: resize(s) + s.data[s.len] = c + s.data[s.len+1] = '\0' + inc s.len + +proc ensure(s: var string; newLen: int) = + let old = s.cap + if newLen >= old: + s.cap = max((old * 3) shr 1, newLen) + if s.cap > 0: + s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1)) + +proc add*(s: var string; y: string) = + if y.len != 0: + let newLen = s.len + y.len + ensure(s, newLen) + copyMem(addr s.data[len], y.data, y.data.len + 1) + s.len = newLen + +proc len*(s: string): int {.inline.} = s.len + +proc newString*(len: int): string = + result.len = len + result.cap = len + if len > 0: + result.data = alloc0(len+1) + +converter toCString(x: string): cstring {.core.} = + if x.len == 0: cstring"" else: cast[cstring](x.data) + +proc newStringOfCap*(cap: int): string = + result.len = 0 + result.cap = cap + if cap > 0: + result.data = alloc(cap+1) + +proc `&`*(a, b: string): string = + let sum = a.len + b.len + result = newStringOfCap(sum) + result.len = sum + copyMem(addr result.data[0], a.data, a.len) + copyMem(addr result.data[a.len], b.data, b.len) + if sum > 0: + result.data[sum] = '\0' + +proc concat(x: openArray[string]): string {.core.} = + ## used be the code generator to optimize 'x & y & z ...' + var sum = 0 + for i in 0 ..< x.len: inc(sum, x[i].len) + result = newStringOfCap(sum) + sum = 0 + for i in 0 ..< x.len: + let L = x[i].len + copyMem(addr result.data[sum], x[i].data, L) + inc(sum, L) + diff --git a/lib/core/typelayouts.nim b/lib/core/typelayouts.nim new file mode 100644 index 000000000..445ce77c4 --- /dev/null +++ b/lib/core/typelayouts.nim @@ -0,0 +1,19 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +type + TypeLayout* = object + size*, alignment*: int + destructor*: proc (self: pointer; a: Allocator) {.nimcall.} + trace*: proc (self: pointer; a: Allocator) {.nimcall.} + when false: + construct*: proc (self: pointer; a: Allocator) {.nimcall.} + copy*, deepcopy*, sink*: proc (self, other: pointer; a: Allocator) {.nimcall.} + +proc getTypeLayout(t: typedesc): ptr TypeLayout {.magic: "getTypeLayout".} diff --git a/lib/deprecated/pure/actors.nim b/lib/deprecated/pure/actors.nim index 36bd41e9e..17321cc0e 100644 --- a/lib/deprecated/pure/actors.nim +++ b/lib/deprecated/pure/actors.nim @@ -18,7 +18,7 @@ ## var ## a: ActorPool[int, void] ## createActorPool(a) -## for i in 0 .. < 300: +## for i in 0 ..< 300: ## a.spawn(i, proc (x: int) {.thread.} = echo x) ## a.join() ## @@ -133,7 +133,7 @@ proc createActorPool*[In, Out](a: var ActorPool[In, Out], poolSize = 4) = newSeq(a.actors, poolSize) when Out isnot void: open(a.outputs) - for i in 0 .. < a.actors.len: + for i in 0 ..< a.actors.len: a.actors[i] = spawn(poolWorker[In, Out]) proc sync*[In, Out](a: var ActorPool[In, Out], polling=50) = @@ -164,8 +164,8 @@ proc terminate*[In, Out](a: var ActorPool[In, Out]) = ## resources attached to `a`. var t: Task[In, Out] t.shutdown = true - for i in 0.. <a.actors.len: send(a.actors[i].i, t) - for i in 0.. <a.actors.len: join(a.actors[i]) + for i in 0..<a.actors.len: send(a.actors[i].i, t) + for i in 0..<a.actors.len: join(a.actors[i]) when Out isnot void: close(a.outputs) a.actors = nil @@ -227,7 +227,7 @@ when not defined(testing) and isMainModule: var a: ActorPool[int, void] createActorPool(a) - for i in 0 .. < 300: + for i in 0 ..< 300: a.spawn(i, proc (x: int) {.thread.} = echo x) when false: diff --git a/lib/deprecated/pure/rawsockets.nim b/lib/deprecated/pure/rawsockets.nim index ee77b232e..876334f9e 100644 --- a/lib/deprecated/pure/rawsockets.nim +++ b/lib/deprecated/pure/rawsockets.nim @@ -3,12 +3,12 @@ export nativesockets {.warning: "rawsockets module is deprecated, use nativesockets instead".} -template newRawSocket*(domain, sockType, protocol: cint): expr = +template newRawSocket*(domain, sockType, protocol: cint): untyped = {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} newNativeSocket(domain, sockType, protocol) template newRawSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): expr = + protocol: Protocol = IPPROTO_TCP): untyped = {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} newNativeSocket(domain, sockType, protocol) diff --git a/lib/deprecated/pure/sockets.nim b/lib/deprecated/pure/sockets.nim index 153db9ed8..f068c7d56 100644 --- a/lib/deprecated/pure/sockets.nim +++ b/lib/deprecated/pure/sockets.nim @@ -262,7 +262,7 @@ proc socket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM, # TODO: Perhaps this should just raise EOS when an error occurs. when defined(Windows): - result = newTSocket(winlean.socket(ord(domain), ord(typ), ord(protocol)), buffered) + result = newTSocket(winlean.socket(cint(domain), cint(typ), cint(protocol)), buffered) else: result = newTSocket(posix.socket(toInt(domain), toInt(typ), toInt(protocol)), buffered) diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index a42950557..1459f0d7e 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -74,7 +74,7 @@ type ## converted to nil. InstantRow* = object ## a handle that can be res: PPGresult ## used to get a row's - line: int ## column text on demand + line: int ## column text on demand SqlPrepared* = distinct string ## a identifier for the prepared queries {.deprecated: [TRow: Row, TDbConn: DbConn, TSqlPrepared: SqlPrepared].} @@ -98,15 +98,18 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = var a = 0 if args.len > 0 and not string(formatstr).contains("?"): dbError("""parameter substitution expects "?" """) - for c in items(string(formatstr)): - if c == '?': - if args[a] == nil: - add(result, "NULL") + if args.len == 0: + return string(formatstr) + else: + for c in items(string(formatstr)): + if c == '?': + if args[a] == nil: + add(result, "NULL") + else: + add(result, dbQuote(args[a])) + inc(a) else: - add(result, dbQuote(args[a])) - inc(a) - else: - add(result, c) + add(result, c) proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.tags: [ReadDbEffect, WriteDbEffect].} = @@ -172,7 +175,7 @@ proc prepare*(db: DbConn; stmtName: string, query: SqlQuery; return SqlPrepared(stmtName) proc setRow(res: PPGresult, r: var Row, line, cols: int32) = - for col in 0..cols-1: + for col in 0'i32..cols-1: setLen(r[col], 0) let x = pqgetvalue(res, line, col) if x.isNil: @@ -188,7 +191,7 @@ iterator fastRows*(db: DbConn, query: SqlQuery, var res = setupQuery(db, query, args) var L = pqnfields(res) var result = newRow(L) - for i in 0..pqntuples(res)-1: + for i in 0'i32..pqntuples(res)-1: setRow(res, result, i, L) yield result pqclear(res) @@ -199,7 +202,7 @@ iterator fastRows*(db: DbConn, stmtName: SqlPrepared, var res = setupQuery(db, stmtName, args) var L = pqNfields(res) var result = newRow(L) - for i in 0..pqNtuples(res)-1: + for i in 0'i32..pqNtuples(res)-1: setRow(res, result, i, L) yield result pqClear(res) @@ -210,7 +213,7 @@ iterator instantRows*(db: DbConn, query: SqlQuery, ## same as fastRows but returns a handle that can be used to get column text ## on demand using []. Returned handle is valid only within iterator body. var res = setupQuery(db, query, args) - for i in 0..pqNtuples(res)-1: + for i in 0'i32..pqNtuples(res)-1: yield InstantRow(res: res, line: i) pqClear(res) @@ -220,7 +223,7 @@ iterator instantRows*(db: DbConn, stmtName: SqlPrepared, ## same as fastRows but returns a handle that can be used to get column text ## on demand using []. Returned handle is valid only within iterator body. var res = setupQuery(db, stmtName, args) - for i in 0..pqNtuples(res)-1: + for i in 0'i32..pqNtuples(res)-1: yield InstantRow(res: res, line: i) pqClear(res) @@ -237,7 +240,7 @@ proc getColumnType(res: PPGresult, col: int) : DbType = of 21: return DbType(kind: DbTypeKind.dbInt, name: "int2", size: 2) of 23: return DbType(kind: DbTypeKind.dbInt, name: "int4", size: 4) of 20: return DbType(kind: DbTypeKind.dbInt, name: "int8", size: 8) - of 1560: return DbType(kind: DbTypeKind.dbBit, name: "bit") + of 1560: return DbType(kind: DbTypeKind.dbBit, name: "bit") of 1562: return DbType(kind: DbTypeKind.dbInt, name: "varbit") of 18: return DbType(kind: DbTypeKind.dbFixedChar, name: "char") @@ -251,7 +254,7 @@ proc getColumnType(res: PPGresult, col: int) : DbType = of 700: return DbType(kind: DbTypeKind.dbFloat, name: "float4") of 701: return DbType(kind: DbTypeKind.dbFloat, name: "float8") - of 790: return DbType(kind: DbTypeKind.dbDecimal, name: "money") + of 790: return DbType(kind: DbTypeKind.dbDecimal, name: "money") of 1700: return DbType(kind: DbTypeKind.dbDecimal, name: "numeric") of 704: return DbType(kind: DbTypeKind.dbTimeInterval, name: "tinterval") @@ -274,12 +277,12 @@ proc getColumnType(res: PPGresult, col: int) : DbType = of 603: return DbType(kind: DbTypeKind.dbBox, name: "box") of 604: return DbType(kind: DbTypeKind.dbPolygon, name: "polygon") of 628: return DbType(kind: DbTypeKind.dbLine, name: "line") - of 718: return DbType(kind: DbTypeKind.dbCircle, name: "circle") + of 718: return DbType(kind: DbTypeKind.dbCircle, name: "circle") of 650: return DbType(kind: DbTypeKind.dbInet, name: "cidr") of 829: return DbType(kind: DbTypeKind.dbMacAddress, name: "macaddr") of 869: return DbType(kind: DbTypeKind.dbInet, name: "inet") - + of 2950: return DbType(kind: DbTypeKind.dbVarchar, name: "uuid") of 3614: return DbType(kind: DbTypeKind.dbVarchar, name: "tsvector") of 3615: return DbType(kind: DbTypeKind.dbVarchar, name: "tsquery") @@ -361,11 +364,11 @@ proc getColumnType(res: PPGresult, col: int) : DbType = proc setColumnInfo(columns: var DbColumns; res: PPGresult; L: int32) = setLen(columns, L) - for i in 0..<L: + for i in 0'i32..<L: columns[i].name = $pqfname(res, i) columns[i].typ = getColumnType(res, i) columns[i].tableName = $(pqftable(res, i)) ## Returns the OID of the table from which the given column was fetched. - ## Query the system table pg_class to determine exactly which table is referenced. + ## Query the system table pg_class to determine exactly which table is referenced. #columns[i].primaryKey = libpq does not have a function for that #columns[i].foreignKey = libpq does not have a function for that @@ -374,7 +377,7 @@ iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery; {.tags: [ReadDbEffect].} = var res = setupQuery(db, query, args) setColumnInfo(columns, res, pqnfields(res)) - for i in 0..<pqntuples(res): + for i in 0'i32..<pqntuples(res): yield InstantRow(res: res, line: i) pqClear(res) diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index 53dafdda7..21049571f 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -148,7 +148,7 @@ proc setupQuery(db: DbConn, query: SqlQuery, if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db) proc setRow(stmt: Pstmt, r: var Row, cols: cint) = - for col in 0..cols-1: + for col in 0'i32..cols-1: setLen(r[col], column_bytes(stmt, col)) # set capacity setLen(r[col], 0) let x = column_text(stmt, col) diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 4013182af..3d4afc0ae 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -155,7 +155,7 @@ type ## - ``"abc".match(re"(?<letter>\w)").captures["letter"] == "a"`` ## - ``"abc".match(re"(\w)\w").captures[-1] == "ab"`` ## - ## ``captureBounds[]: Option[Slice[int]]`` + ## ``captureBounds[]: Option[HSlice[int, int]]`` ## gets the bounds of the given capture according to the same rules as ## the above. If the capture is not filled, then ``None`` is returned. ## The bounds are both inclusive. @@ -167,7 +167,7 @@ type ## ``match: string`` ## the full text of the match. ## - ## ``matchBounds: Slice[int]`` + ## ``matchBounds: HSlice[int, int]`` ## the bounds of the match, as in ``captureBounds[]`` ## ## ``(captureBounds|captures).toTable`` @@ -182,9 +182,9 @@ type ## Not nil. str*: string ## The string that was matched against. ## Not nil. - pcreMatchBounds: seq[Slice[cint]] ## First item is the bounds of the match - ## Other items are the captures - ## `a` is inclusive start, `b` is exclusive end + pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match + ## Other items are the captures + ## `a` is inclusive start, `b` is exclusive end Captures* = distinct RegexMatch CaptureBounds* = distinct RegexMatch @@ -251,13 +251,13 @@ proc captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(p proc captures*(pattern: RegexMatch): Captures = return Captures(pattern) -proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[int]] = +proc `[]`*(pattern: CaptureBounds, i: int): Option[HSlice[int, int]] = let pattern = RegexMatch(pattern) if pattern.pcreMatchBounds[i + 1].a != -1: let bounds = pattern.pcreMatchBounds[i + 1] return some(int(bounds.a) .. int(bounds.b-1)) else: - return none(Slice[int]) + return none(HSlice[int, int]) proc `[]`*(pattern: Captures, i: int): string = let pattern = RegexMatch(pattern) @@ -272,10 +272,10 @@ proc `[]`*(pattern: Captures, i: int): string = proc match*(pattern: RegexMatch): string = return pattern.captures[-1] -proc matchBounds*(pattern: RegexMatch): Slice[int] = +proc matchBounds*(pattern: RegexMatch): HSlice[int, int] = return pattern.captureBounds[-1].get -proc `[]`*(pattern: CaptureBounds, name: string): Option[Slice[int]] = +proc `[]`*(pattern: CaptureBounds, name: string): Option[HSlice[int, int]] = let pattern = RegexMatch(pattern) return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)] @@ -295,13 +295,13 @@ proc toTable*(pattern: Captures, default: string = nil): Table[string, string] = result = initTable[string, string]() toTableImpl(nextVal == nil) -proc toTable*(pattern: CaptureBounds, default = none(Slice[int])): - Table[string, Option[Slice[int]]] = - result = initTable[string, Option[Slice[int]]]() +proc toTable*(pattern: CaptureBounds, default = none(HSlice[int, int])): + Table[string, Option[HSlice[int, int]]] = + result = initTable[string, Option[HSlice[int, int]]]() toTableImpl(nextVal.isNone) template itemsImpl(cond: untyped) {.dirty.} = - for i in 0 .. <RegexMatch(pattern).pattern.captureCount: + for i in 0 ..< RegexMatch(pattern).pattern.captureCount: let nextVal = pattern[i] # done in this roundabout way to avoid multiple yields (potential code # bloat) @@ -309,13 +309,13 @@ template itemsImpl(cond: untyped) {.dirty.} = yield nextYieldVal -iterator items*(pattern: CaptureBounds, default = none(Slice[int])): Option[Slice[int]] = +iterator items*(pattern: CaptureBounds, default = none(HSlice[int, int])): Option[HSlice[int, int]] = itemsImpl(nextVal.isNone) iterator items*(pattern: Captures, default: string = nil): string = itemsImpl(nextVal == nil) -proc toSeq*(pattern: CaptureBounds, default = none(Slice[int])): seq[Option[Slice[int]]] = +proc toSeq*(pattern: CaptureBounds, default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] = accumulateResult(pattern.items(default)) proc toSeq*(pattern: Captures, default: string = nil): seq[string] = @@ -396,8 +396,6 @@ proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: # }}} -type UncheckedArray {.unchecked.}[T] = array[0 .. 0, T] - proc destroyRegex(pattern: Regex) = pcre.free_substring(cast[cstring](pattern.pcreObj)) pattern.pcreObj = nil @@ -412,7 +410,7 @@ proc getNameToNumberTable(pattern: Regex): Table[string, int] = result = initTable[string, int]() - for i in 0 .. <entryCount: + for i in 0 ..< entryCount: let pos = i * entrySize let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1 var name = "" @@ -464,7 +462,7 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt # 1x capture count as slack space for PCRE let vecsize = (pattern.captureCount() + 1) * 3 # div 2 because each element is 2 cints long - myResult.pcreMatchBounds = newSeq[Slice[cint]](ceil(vecsize / 2).int) + myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]](ceil(vecsize / 2).int) myResult.pcreMatchBounds.setLen(vecsize div 3) let strlen = if endpos == int.high: str.len else: endpos+1 diff --git a/lib/impure/re.nim b/lib/impure/re.nim index e00f91de1..c7f8f336b 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -13,10 +13,6 @@ ## We had to de-deprecate this module since too much code relies on it ## and many people prefer its API over ``nre``'s. ## -## **Note:** The 're' proc defaults to the **extended regular expression -## syntax** which lets you use whitespace freely to make your regexes readable. -## However, this means matching whitespace requires ``\s`` or something similar. -## ## This module is implemented by providing a wrapper around the ## `PRCE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ ## C library. This means that your application will depend on the PRCE @@ -53,9 +49,6 @@ type RegexError* = object of ValueError ## is raised if the pattern is no valid regular expression. -{.deprecated: [TRegexFlag: RegexFlag, TRegexDesc: RegexDesc, TRegex: Regex, - EInvalidRegEx: RegexError].} - proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} = var e: ref RegexError new(e) @@ -78,7 +71,7 @@ proc finalizeRegEx(x: Regex) = if not isNil(x.e): pcre.free_substring(cast[cstring](x.e)) -proc re*(s: string, flags = {reExtended, reStudy}): Regex = +proc re*(s: string, flags = {reStudy}): Regex = ## Constructor of regular expressions. ## ## Note that Nim's @@ -96,6 +89,13 @@ proc re*(s: string, flags = {reExtended, reStudy}): Regex = result.e = pcre.study(result.h, options, addr msg) if not isNil(msg): raiseInvalidRegex($msg) +proc rex*(s: string, flags = {reStudy, reExtended}): Regex = + ## Constructor for extended regular expressions. + ## + ## The extended means that comments starting with `#` and + ## whitespace are ignored. + result = re(s, flags) + proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} = ## Return a Nim string built from a slice of a cstring buffer. ## Don't assume cstring is '\0' terminated @@ -467,8 +467,8 @@ proc replacef*(s: string, sub: Regex, by: string): string = prev = match.last + 1 add(result, substr(s, prev)) -proc parallelReplace*(s: string, subs: openArray[ - tuple[pattern: Regex, repl: string]]): string = +proc multiReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string = ## Returns a modified copy of ``s`` with the substitutions in ``subs`` ## applied in parallel. result = "" @@ -487,13 +487,20 @@ proc parallelReplace*(s: string, subs: openArray[ # copy the rest: add(result, substr(s, i)) +proc parallelReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string {.deprecated.} = + ## Returns a modified copy of ``s`` with the substitutions in ``subs`` + ## applied in parallel. + ## **Deprecated since version 0.18.0**: Use ``multiReplace`` instead. + result = multiReplace(s, subs) + proc transformFile*(infile, outfile: string, subs: openArray[tuple[pattern: Regex, repl: string]]) = ## reads in the file ``infile``, performs a parallel replacement (calls ## ``parallelReplace``) and writes back to ``outfile``. Raises ``IOError`` if an ## error occurs. This is supposed to be used for quick scripting. var x = readFile(infile).string - writeFile(outfile, x.parallelReplace(subs)) + writeFile(outfile, x.multiReplace(subs)) iterator split*(s: string, sep: Regex): string = ## Splits the string ``s`` into substrings. @@ -576,12 +583,12 @@ const ## common regular expressions ## describes an URL when isMainModule: - doAssert match("(a b c)", re"\( .* \)") + doAssert match("(a b c)", rex"\( .* \)") doAssert match("WHiLe", re("while", {reIgnoreCase})) doAssert "0158787".match(re"\d+") doAssert "ABC 0232".match(re"\w+\s+\d+") - doAssert "ABC".match(re"\d+ | \w+") + doAssert "ABC".match(rex"\d+ | \w+") {.push warnings:off.} doAssert matchLen("key", re(reIdentifier)) == 3 diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim new file mode 100644 index 000000000..ec410ee39 --- /dev/null +++ b/lib/js/asyncjs.nim @@ -0,0 +1,141 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim Authors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. + +## This module implements types and macros for writing asynchronous code +## for the JS backend. It provides tools for interaction with JavaScript async API-s +## and libraries, writing async procedures in Nim and converting callback-based code +## to promises. +## +## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It +## should always have a ``Future[T]`` return type or not have a return type at all. +## A ``Future[void]`` return type is assumed by default. +## +## This is roughly equivalent to the ``async`` keyword in JavaScript code. +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] {.async.} = +## # code +## +## should be equivalent to +## +## .. code-block:: javascript +## async function loadGame(name) { +## // code +## } +## +## A call to an asynchronous procedure usually needs ``await`` to wait for +## the completion of the ``Future``. +## +## .. code-block:: nim +## var game = await loadGame(name) +## +## Often, you might work with callback-based API-s. You can wrap them with +## asynchronous procedures using promises and ``newPromise``: +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] = +## var promise = newPromise() do (resolve: proc(response: Game)): +## cbBasedLoadGame(name) do (game: Game): +## resolve(game) +## return promise +## +## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma: +## +## .. code-block:: nim +## proc loadGame(name: string): Future[Game] {.async.} +## +## JavaScript compatibility +## ~~~~~~~~~~~~~~~~~~~~~~~~~ +## +## Nim currently generates `async/await` JavaScript code which is supported in modern +## EcmaScript and most modern versions of browsers, Node.js and Electron. +## If you need to use this module with older versions of JavaScript, you can +## use a tool that backports the resulting JavaScript code, as babel. + +import jsffi +import macros + +when not defined(js) and not defined(nimdoc) and not defined(nimsuggest): + {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".} + +type + Future*[T] = ref object + future*: T + ## Wraps the return type of an asynchronous procedure. + + PromiseJs* {.importcpp: "Promise".} = ref object + ## A JavaScript Promise + +proc replaceReturn(node: var NimNode) = + var z = 0 + for s in node: + var son = node[z] + if son.kind == nnkReturnStmt: + node[z] = nnkReturnStmt.newTree(nnkCall.newTree(ident("jsResolve"), son[0])) + elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result": + node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(ident("jsResolve"), son[1])) + else: + replaceReturn(son) + inc z + +proc isFutureVoid(node: NimNode): bool = + result = node.kind == nnkBracketExpr and + node[0].kind == nnkIdent and $node[0] == "Future" and + node[1].kind == nnkIdent and $node[1] == "void" + +proc generateJsasync(arg: NimNode): NimNode = + assert arg.kind == nnkProcDef + result = arg + var isVoid = false + var jsResolveNode = ident("jsResolve") + + if arg.params[0].kind == nnkEmpty: + result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void")) + isVoid = true + elif isFutureVoid(arg.params[0]): + isVoid = true + + var code = result.body + replaceReturn(code) + result.body = nnkStmtList.newTree() + + if len(code) > 0: + var awaitFunction = quote: + proc await[T](f: Future[T]): T {.importcpp: "(await #)".} + result.body.add(awaitFunction) + + var resolve: NimNode + if isVoid: + resolve = quote: + var `jsResolveNode` {.importcpp: "undefined".}: Future[void] + else: + resolve = quote: + proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} + result.body.add(resolve) + else: + result.body = newEmptyNode() + for child in code: + result.body.add(child) + + if len(code) > 0 and isVoid: + var voidFix = quote: + return `jsResolveNode` + result.body.add(voidFix) + + result.pragma = quote: + {.codegenDecl: "async function $2($3)".} + + +macro async*(arg: untyped): untyped = + ## Macro which converts normal procedures into + ## javascript-compatible async procedures + generateJsasync(arg) + +proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".} + ## A helper for wrapping callback-based functions + ## into promises and async procedures diff --git a/lib/js/dom.nim b/lib/js/dom.nim index cdefc772c..aa7f5d839 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -134,9 +134,9 @@ type # https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement HtmlElement* = ref object of Element - contentEditable*: string + contentEditable*: cstring isContentEditable*: bool - dir*: string + dir*: cstring offsetHeight*: int offsetWidth*: int offsetLeft*: int diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index 13eb1e759..f34efe9a2 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -177,7 +177,7 @@ proc `==`*(x, y: JsRoot): bool {. importcpp: "(# === #)" .} ## and not strings or numbers, this is a *comparison of references*. {. experimental .} -macro `.`*(obj: JsObject, field: static[cstring]): JsObject = +macro `.`*(obj: JsObject, field: untyped): JsObject = ## Experimental dot accessor (get) for type JsObject. ## Returns the value of a property of name `field` from a JsObject `x`. ## @@ -196,14 +196,14 @@ macro `.`*(obj: JsObject, field: static[cstring]): JsObject = helper(`obj`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) let importString = "#." & mangledNames[$field] result = quote do: proc helper(o: JsObject): JsObject {. importcpp: `importString`, gensym .} helper(`obj`) -macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = +macro `.=`*(obj: JsObject, field, value: untyped): untyped = ## Experimental dot accessor (set) for type JsObject. ## Sets the value of a property of name `field` in a JsObject `x` to `value`. if validJsName($field): @@ -214,7 +214,7 @@ macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = helper(`obj`, `value`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) let importString = "#." & mangledNames[$field] & " = #" result = quote do: proc helper(o: JsObject, v: auto) @@ -222,7 +222,7 @@ macro `.=`*(obj: JsObject, field: static[cstring], value: untyped): untyped = helper(`obj`, `value`) macro `.()`*(obj: JsObject, - field: static[cstring], + field: untyped, args: varargs[JsObject, jsFromAst]): JsObject = ## Experimental "method call" operator for type JsObject. ## Takes the name of a method of the JavaScript object (`field`) and calls @@ -245,7 +245,7 @@ macro `.()`*(obj: JsObject, importString = "#." & $field & "(@)" else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] & "(@)" result = quote: proc helper(o: JsObject): JsObject @@ -257,7 +257,7 @@ macro `.()`*(obj: JsObject, result[1].add args[idx].copyNimTree macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], - field: static[cstring]): V = + field: untyped): V = ## Experimental dot accessor (get) for type JsAssoc. ## Returns the value of a property of name `field` from a JsObject `x`. var importString: string @@ -265,7 +265,7 @@ macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], importString = "#." & $field else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] result = quote do: proc helper(o: type(`obj`)): `obj`.V @@ -273,7 +273,7 @@ macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V], helper(`obj`) macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], - field: static[cstring], + field: untyped, value: V): untyped = ## Experimental dot accessor (set) for type JsAssoc. ## Sets the value of a property of name `field` in a JsObject `x` to `value`. @@ -282,7 +282,7 @@ macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], importString = "#." & $field & " = #" else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName(field) + mangledNames[$field] = $mangleJsName($field) importString = "#." & mangledNames[$field] & " = #" result = quote do: proc helper(o: type(`obj`), v: `obj`.V) @@ -290,7 +290,7 @@ macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V], helper(`obj`, `value`) macro `.()`*[K: string | cstring, V: proc](obj: JsAssoc[K, V], - field: static[cstring], + field: untyped, args: varargs[untyped]): auto = ## Experimental "method call" operator for type JsAssoc. ## Takes the name of a method of the JavaScript object (`field`) and calls diff --git a/lib/nimbase.h b/lib/nimbase.h index 34a2e43e7..31075bbd2 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -70,7 +70,7 @@ __clang__ #if defined(_MSC_VER) # pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309) # pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706) -# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090) +# pragma warning(disable: 4710 4711 4774 4800 4820 4996 4090 4297) #endif /* ------------------------------------------------------------------------- */ @@ -159,6 +159,7 @@ __clang__ /* ------------------------------------------------------------------- */ #if defined(WIN32) || defined(_WIN32) /* only Windows has this mess... */ +# define N_LIB_PRIVATE # define N_CDECL(rettype, name) rettype __cdecl name # define N_STDCALL(rettype, name) rettype __stdcall name # define N_SYSCALL(rettype, name) rettype __syscall name @@ -178,6 +179,7 @@ __clang__ # endif # define N_LIB_IMPORT extern __declspec(dllimport) #else +# define N_LIB_PRIVATE __attribute__((visibility("hidden"))) # if defined(__GNUC__) # define N_CDECL(rettype, name) rettype name # define N_STDCALL(rettype, name) rettype name @@ -396,15 +398,13 @@ typedef struct TStringDesc* string; #define GenericSeqSize sizeof(TGenericSeq) #define paramCount() cmdCount -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__i386__) -# ifndef NAN -static unsigned long nimNaN[2]={0xffffffff, 0x7fffffff}; -# define NAN (*(double*) nimNaN) -# endif -#endif - +// NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 #ifndef NAN -# define NAN (0.0 / 0.0) +# ifndef _HUGE_ENUF +# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow +# endif +# define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) +# define NAN ((float)(NAN_INFINITY * 0.0F)) #endif #ifndef INF @@ -482,7 +482,6 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } On disagreement, your C compiler will say something like: "error: 'Nim_and_C_compiler_disagree_on_target_architecture' declared as an array with a negative size" */ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1]; -#endif #ifdef __cplusplus # define NIM_EXTERNC extern "C" @@ -509,3 +508,5 @@ extern Libc::Env *genodeEnv; /* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */ #define NIM_CHECK_SIZE(typ, sz) \ _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") + +#endif /* NIMBASE_H */ diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 1d396d9e0..2a58854a6 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -31,14 +31,12 @@ type state: TokenClass SourceLanguage* = enum - langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava, + langNone, langNim, langCpp, langCsharp, langC, langJava, langYaml -{.deprecated: [TSourceLanguage: SourceLanguage, TTokenClass: TokenClass, - TGeneralTokenizer: GeneralTokenizer].} const sourceLanguageToStr*: array[SourceLanguage, string] = ["none", - "Nim", "Nimrod", "C++", "C#", "C", "Java", "Yaml"] + "Nim", "C++", "C#", "C", "Java", "Yaml"] tokenClassToStr*: array[TokenClass, string] = ["Eof", "None", "Whitespace", "DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber", "Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit", @@ -49,12 +47,12 @@ const # The following list comes from doc/keywords.txt, make sure it is # synchronized with this array by running the module itself as a test case. - nimKeywords = ["addr", "and", "as", "asm", "atomic", "bind", "block", + nimKeywords = ["addr", "and", "as", "asm", "bind", "block", "break", "case", "cast", "concept", "const", "continue", "converter", "defer", "discard", "distinct", "div", "do", "elif", "else", "end", "enum", "except", "export", "finally", "for", "from", "func", - "generic", "if", "import", "in", "include", + "if", "import", "in", "include", "interface", "is", "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", @@ -398,7 +396,6 @@ type TokenizerFlag = enum hasPreprocessor, hasNestedComments TokenizerFlags = set[TokenizerFlag] -{.deprecated: [TTokenizerFlag: TokenizerFlag, TTokenizerFlags: TokenizerFlags].} proc clikeNextToken(g: var GeneralTokenizer, keywords: openArray[string], flags: TokenizerFlags) = @@ -888,7 +885,7 @@ proc yamlNextToken(g: var GeneralTokenizer) = proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) = case lang of langNone: assert false - of langNim, langNimrod: nimNextToken(g) + of langNim: nimNextToken(g) of langCpp: cppNextToken(g) of langCsharp: csharpNextToken(g) of langC: cNextToken(g) @@ -901,12 +898,11 @@ when isMainModule: for filename in ["doc/keywords.txt", "../../../doc/keywords.txt"]: try: let input = string(readFile(filename)) - keywords = input.split() + keywords = input.splitWhitespace() break except: echo filename, " not found" doAssert(not keywords.isNil, "Couldn't read any keywords.txt file!") - doAssert keywords.len == nimKeywords.len, "No matching lengths" - for i in 0..keywords.len-1: - #echo keywords[i], " == ", nimKeywords[i] + for i in 0..min(keywords.len, nimKeywords.len)-1: doAssert keywords[i] == nimKeywords[i], "Unexpected keyword" + doAssert keywords.len == nimKeywords.len, "No matching lengths" diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 53699166f..223fc836a 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -45,8 +45,6 @@ type MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind, arg: string) {.nimcall.} ## what to do in case of an error FindFileHandler* = proc (filename: string): string {.nimcall.} -{.deprecated: [TRstParseOptions: RstParseOptions, TRstParseOption: RstParseOption, - TMsgKind: MsgKind].} const messages: array[MsgKind, string] = [ @@ -127,8 +125,6 @@ type bufpos*: int line*, col*, baseIndent*: int skipPounds*: bool -{.deprecated: [TTokType: TokType, TToken: Token, TTokenSeq: TokenSeq, - TLexer: Lexer].} proc getThing(L: var Lexer, tok: var Token, s: set[char]) = tok.kind = tkWord @@ -288,10 +284,6 @@ type hasToc*: bool EParseError* = object of ValueError -{.deprecated: [TLevelMap: LevelMap, TSubstitution: Substitution, - TSharedState: SharedState, TRstParser: RstParser, - TMsgHandler: MsgHandler, TFindFileHandler: FindFileHandler, - TMsgClass: MsgClass].} proc whichMsgClass*(k: MsgKind): MsgClass = ## returns which message class `k` belongs to. @@ -341,11 +333,6 @@ proc rstMessage(p: RstParser, msgKind: MsgKind) = p.col + p.tok[p.idx].col, msgKind, p.tok[p.idx].symbol) -when false: - proc corrupt(p: RstParser) = - assert p.indentStack[0] == 0 - for i in 1 .. high(p.indentStack): assert p.indentStack[i] < 1_000 - proc currInd(p: RstParser): int = result = p.indentStack[high(p.indentStack)] diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 13016bfc0..e6c95b59e 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -46,7 +46,7 @@ type target*: OutputTarget config*: StringTableRef splitAfter*: int # split too long entries in the TOC - listingCounter: int + listingCounter*: int tocPart*: seq[TocEntry] hasToc*: bool theIndex: string # Contents of the index file to be dumped at the end. @@ -61,6 +61,9 @@ type seenIndexTerms: Table[string, int] ## \ ## Keeps count of same text index terms to generate different identifiers ## for hyperlinks. See renderIndexTerm proc for details. + id*: int ## A counter useful for generating IDs. + onTestSnippet*: proc (d: var RstGenerator; filename, cmd: string; status: int; + content: string) PDoc = var RstGenerator ## Alias to type less. @@ -69,8 +72,9 @@ type startLine: int ## The starting line of the code block, by default 1. langStr: string ## Input string used to specify the language. lang: SourceLanguage ## Type of highlighting, by default none. -{.deprecated: [TRstGenerator: RstGenerator, TTocEntry: TocEntry, - TOutputTarget: OutputTarget, TMetaEnum: MetaEnum].} + filename: string + testCmd: string + status: int proc init(p: var CodeBlockParams) = ## Default initialisation of CodeBlockParams to sane values. @@ -133,6 +137,7 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget, g.options = options g.findFile = findFile g.currentSection = "" + g.id = 0 let fileParts = filename.splitFile if fileParts.ext == ".nim": g.currentSection = "Module " & fileParts.name @@ -368,7 +373,6 @@ type ## ## The value indexed by this IndexEntry is a sequence with the real index ## entries found in the ``.idx`` file. -{.deprecated: [TIndexEntry: IndexEntry, TIndexedDocs: IndexedDocs].} proc cmp(a, b: IndexEntry): int = ## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`. @@ -798,7 +802,8 @@ proc renderImage(d: PDoc, n: PRstNode, result: var string) = if arg.valid: let htmlOut = if isObject: "<object data=\"$1\" type=\"image/svg+xml\"$2 >" & content & "</object>" - else: "<img src=\"$1\"$2 />" + else: + "<img src=\"$1\"$2 />" dispA(d.target, result, htmlOut, "\\includegraphics$2{$1}", [arg, options]) if len(n) >= 3: renderRstToOut(d, n.sons[2], result) @@ -822,13 +827,20 @@ proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) = var number: int if parseInt(n.getFieldValue, number) > 0: params.startLine = number - of "file": + of "file", "filename": # The ``file`` option is a Nim extension to the official spec, it acts # like it would for other directives like ``raw`` or ``cvs-table``. This # field is dealt with in ``rst.nim`` which replaces the existing block with # the referenced file, so we only need to ignore it here to avoid incorrect # warning messages. - discard + params.filename = n.getFieldValue.strip + of "test": + params.testCmd = n.getFieldValue.strip + if params.testCmd.len == 0: params.testCmd = "nim c -r $1" + of "status": + var status: int + if parseInt(n.getFieldValue, status) > 0: + params.status = status of "default-language": params.langStr = n.getFieldValue.strip params.lang = params.langStr.getSourceLanguage @@ -866,7 +878,7 @@ proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string): inc d.listingCounter let id = $d.listingCounter if not params.numberLines: - result = (d.config.getOrDefault"doc.listing_start" % id, + result = (d.config.getOrDefault"doc.listing_start" % [id, $params.lang], d.config.getOrDefault"doc.listing_end" % id) return @@ -879,7 +891,7 @@ proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string): line.inc codeLines.dec result.beginTable.add("</pre></td><td>" & ( - d.config.getOrDefault"doc.listing_start" % id)) + d.config.getOrDefault"doc.listing_start" % [id, $params.lang])) result.endTable = (d.config.getOrDefault"doc.listing_end" % id) & "</td></tr></tbody></table>" & ( d.config.getOrDefault"doc.listing_button" % id) @@ -900,6 +912,9 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = var m = n.sons[2].sons[0] assert m.kind == rnLeaf + if params.testCmd.len > 0 and d.onTestSnippet != nil: + d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text) + let (blockStart, blockEnd) = buildLinesHTMLTable(d, params, m.text) dispA(d.target, result, blockStart, "\\begin{rstpre}\n", []) diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index 86b977576..c5ed1a873 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -35,18 +35,13 @@ const EPOLL_CTL_MOD* = 3 # Change file descriptor epoll_event structure. type - epoll_data* {.importc: "union epoll_data", + EpollData* {.importc: "union epoll_data", header: "<sys/epoll.h>", pure, final.} = object # TODO: This is actually a union. - #thePtr* {.importc: "ptr".}: pointer - fd* {.importc: "fd".}: cint - when defined(linux) and defined(amd64): - u32: uint32 # this field ensures that binary size is right - it cannot be - # used because its offset is wrong - #u64*: uint64 + u64* {.importc: "u64".}: uint64 - epoll_event* {.importc: "struct epoll_event", header: "<sys/epoll.h>", pure, final.} = object + EpollEvent* {.importc: "struct epoll_event", header: "<sys/epoll.h>", pure, final.} = object events*: uint32 # Epoll events - data*: epoll_data # User data variable + data*: EpollData # User data variable proc epoll_create*(size: cint): cint {.importc: "epoll_create", header: "<sys/epoll.h>".} @@ -60,7 +55,7 @@ proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1", ## Same as epoll_create but with an FLAGS parameter. The unused SIZE ## parameter has been dropped. -proc epoll_ctl*(epfd: cint; op: cint; fd: cint | SocketHandle; event: ptr epoll_event): cint {. +proc epoll_ctl*(epfd: cint; op: cint; fd: cint | SocketHandle; event: ptr EpollEvent): cint {. importc: "epoll_ctl", header: "<sys/epoll.h>".} ## Manipulate an epoll instance "epfd". Returns 0 in case of success, ## -1 in case of error ( the "errno" variable will contain the @@ -69,7 +64,7 @@ proc epoll_ctl*(epfd: cint; op: cint; fd: cint | SocketHandle; event: ptr epoll_ ## operation. The "event" parameter describes which events the caller ## is interested in and any associated user data. -proc epoll_wait*(epfd: cint; events: ptr epoll_event; maxevents: cint; +proc epoll_wait*(epfd: cint; events: ptr EpollEvent; maxevents: cint; timeout: cint): cint {.importc: "epoll_wait", header: "<sys/epoll.h>".} ## Wait for events on an epoll instance "epfd". Returns the number of @@ -84,7 +79,7 @@ proc epoll_wait*(epfd: cint; events: ptr epoll_event; maxevents: cint; ## __THROW. -#proc epoll_pwait*(epfd: cint; events: ptr epoll_event; maxevents: cint; +#proc epoll_pwait*(epfd: cint; events: ptr EpollEvent; maxevents: cint; # timeout: cint; ss: ptr sigset_t): cint {. # importc: "epoll_pwait", header: "<sys/epoll.h>".} # Same as epoll_wait, but the thread's signal mask is temporarily diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim index 01d5e57de..8786ab535 100644 --- a/lib/posix/linux.nim +++ b/lib/posix/linux.nim @@ -26,3 +26,5 @@ const proc clone*(fn: pointer; child_stack: pointer; flags: cint; arg: pointer; ptid: ptr Pid; tls: pointer; ctid: ptr Pid): cint {.importc, header: "<sched.h>".} + +proc pipe2*(a: array[0..1, cint], flags: cint): cint {.importc, header: "<unistd.h>".} \ No newline at end of file diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index b635c0b0b..fba35868c 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -609,11 +609,12 @@ proc clock_nanosleep*(a1: ClockId, a2: cint, a3: var Timespec, proc clock_settime*(a1: ClockId, a2: var Timespec): cint {. importc, header: "<time.h>".} +proc `==`*(a, b: Time): bool {.borrow.} +proc `-`*(a, b: Time): Time {.borrow.} proc ctime*(a1: var Time): cstring {.importc, header: "<time.h>".} proc ctime_r*(a1: var Time, a2: cstring): cstring {.importc, header: "<time.h>".} proc difftime*(a1, a2: Time): cdouble {.importc, header: "<time.h>".} proc getdate*(a1: cstring): ptr Tm {.importc, header: "<time.h>".} - proc gmtime*(a1: var Time): ptr Tm {.importc, header: "<time.h>".} proc gmtime_r*(a1: var Time, a2: var Tm): ptr Tm {.importc, header: "<time.h>".} proc localtime*(a1: var Time): ptr Tm {.importc, header: "<time.h>".} diff --git a/lib/posix/posix_linux_amd64.nim b/lib/posix/posix_linux_amd64.nim index c44128b16..9e6211b63 100644 --- a/lib/posix/posix_linux_amd64.nim +++ b/lib/posix/posix_linux_amd64.nim @@ -12,8 +12,6 @@ # To be included from posix.nim! -from times import Time - const hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasAioH = defined(linux) @@ -40,13 +38,15 @@ type const SIG_HOLD* = cast[SigHandler](2) type + Time* {.importc: "time_t", header: "<time.h>".} = distinct clong + Timespec* {.importc: "struct timespec", header: "<time.h>", final, pure.} = object ## struct timespec tv_sec*: Time ## Seconds. tv_nsec*: clong ## Nanoseconds. Dirent* {.importc: "struct dirent", - header: "<dirent.h>", final, pure.} = object ## dirent_t struct + header: "<dirent.h>", final, pure.} = object ## dirent_t struct d_ino*: Ino d_off*: Off d_reclen*: cushort diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim index 7321889a8..01bc1c1e5 100644 --- a/lib/posix/posix_other.nim +++ b/lib/posix/posix_other.nim @@ -9,8 +9,6 @@ {.deadCodeElim:on.} -from times import Time - const hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasAioH = defined(linux) @@ -36,6 +34,8 @@ type {.deprecated: [TSocketHandle: SocketHandle].} type + Time* {.importc: "time_t", header: "<time.h>".} = distinct clong + Timespec* {.importc: "struct timespec", header: "<time.h>", final, pure.} = object ## struct timespec tv_sec*: Time ## Seconds. @@ -209,24 +209,24 @@ type st_gid*: Gid ## Group ID of file. st_rdev*: Dev ## Device ID (if file is character or block special). st_size*: Off ## For regular files, the file size in bytes. - ## For symbolic links, the length in bytes of the - ## pathname contained in the symbolic link. - ## For a shared memory object, the length in bytes. - ## For a typed memory object, the length in bytes. - ## For other file types, the use of this field is - ## unspecified. + ## For symbolic links, the length in bytes of the + ## pathname contained in the symbolic link. + ## For a shared memory object, the length in bytes. + ## For a typed memory object, the length in bytes. + ## For other file types, the use of this field is + ## unspecified. when defined(macosx) or defined(android): - st_atime*: Time ## Time of last access. - st_mtime*: Time ## Time of last data modification. - st_ctime*: Time ## Time of last status change. + st_atime*: Time ## Time of last access. + st_mtime*: Time ## Time of last data modification. + st_ctime*: Time ## Time of last status change. else: - st_atim*: Timespec ## Time of last access. - st_mtim*: Timespec ## Time of last data modification. - st_ctim*: Timespec ## Time of last status change. - st_blksize*: Blksize ## A file system-specific preferred I/O block size - ## for this object. In some file system types, this - ## may vary from file to file. - st_blocks*: Blkcnt ## Number of blocks allocated for this object. + st_atim*: Timespec ## Time of last access. + st_mtim*: Timespec ## Time of last data modification. + st_ctim*: Timespec ## Time of last status change. + st_blksize*: Blksize ## A file system-specific preferred I/O block size + ## for this object. In some file system types, this + ## may vary from file to file. + st_blocks*: Blkcnt ## Number of blocks allocated for this object. Statvfs* {.importc: "struct statvfs", header: "<sys/statvfs.h>", diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim index 9eee04404..fdf2d7cbb 100644 --- a/lib/pure/algorithm.nim +++ b/lib/pure/algorithm.nim @@ -371,3 +371,126 @@ when isMainModule: for i in 0 .. high(arr1): assert arr1.reversed(0, i) == arr1.reversed()[high(arr1) - i .. high(arr1)] assert arr1.reversed(i, high(arr1)) == arr1.reversed()[0 .. high(arr1) - i] + + +proc rotateInternal[T](arg: var openarray[T]; first, middle, last: int): int = + ## A port of std::rotate from c++. Ported from `this reference <http://www.cplusplus.com/reference/algorithm/rotate/>`_. + result = first + last - middle + + if first == middle or middle == last: + return + + assert first < middle + assert middle < last + + # m prefix for mutable + var + mFirst = first + mMiddle = middle + next = middle + + swap(arg[mFirst], arg[next]) + mFirst += 1 + next += 1 + if mFirst == mMiddle: + mMiddle = next + + while next != last: + swap(arg[mFirst], arg[next]) + mFirst += 1 + next += 1 + if mFirst == mMiddle: + mMiddle = next + + next = mMiddle + while next != last: + swap(arg[mFirst], arg[next]) + mFirst += 1 + next += 1 + if mFirst == mMiddle: + mMiddle = next + elif next == last: + next = mMiddle + +proc rotatedInternal[T](arg: openarray[T]; first, middle, last: int): seq[T] = + result = newSeq[T](arg.len) + for i in 0 ..< first: + result[i] = arg[i] + let N = last - middle + let M = middle - first + for i in 0 ..< N: + result[first+i] = arg[middle+i] + for i in 0 ..< M: + result[first+N+i] = arg[first+i] + for i in last ..< arg.len: + result[i] = arg[i] + +proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int): int = + ## Performs a left rotation on a range of elements. If you want to rotate right, use a negative ``dist``. + ## Specifically, ``rotateLeft`` rotates the elements at ``slice`` by ``dist`` positions. + ## The element at index ``slice.a + dist`` will be at index ``slice.a``. + ## The element at index ``slice.b`` will be at ``slice.a + dist -1``. + ## The element at index ``slice.a`` will be at ``slice.b + 1 - dist``. + ## The element at index ``slice.a + dist - 1`` will be at ``slice.b``. + # + ## Elements outsize of ``slice`` will be left unchanged. + ## The time complexity is linear to ``slice.b - slice.a + 1``. + ## + ## ``slice`` + ## the indices of the element range that should be rotated. + ## + ## ``dist`` + ## the distance in amount of elements that the data should be rotated. Can be negative, can be any number. + ## + ## .. code-block:: nim + ## var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ## list.rotateLeft(1 .. 8, 3) + ## doAssert list == [0, 4, 5, 6, 7, 8, 1, 2, 3, 9, 10] + let sliceLen = slice.b + 1 - slice.a + let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen + arg.rotateInternal(slice.a, slice.a+distLeft, slice.b + 1) + +proc rotateLeft*[T](arg: var openarray[T]; dist: int): int = + ## default arguments for slice, so that this procedure operates on the entire + ## ``arg``, and not just on a part of it. + let arglen = arg.len + let distLeft = ((dist mod arglen) + arglen) mod arglen + arg.rotateInternal(0, distLeft, arglen) + +proc rotatedLeft*[T](arg: openarray[T]; slice: HSlice[int, int], dist: int): seq[T] = + ## same as ``rotateLeft``, just with the difference that it does + ## not modify the argument. It creates a new ``seq`` instead + let sliceLen = slice.b + 1 - slice.a + let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen + arg.rotatedInternal(slice.a, slice.a+distLeft, slice.b+1) + +proc rotatedLeft*[T](arg: openarray[T]; dist: int): seq[T] = + ## same as ``rotateLeft``, just with the difference that it does + ## not modify the argument. It creates a new ``seq`` instead + let arglen = arg.len + let distLeft = ((dist mod arglen) + arglen) mod arglen + arg.rotatedInternal(0, distLeft, arg.len) + +when isMainModule: + var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + let list2 = list.rotatedLeft(1 ..< 9, 3) + let expected = [0, 4, 5, 6, 7, 8, 1, 2, 3, 9, 10] + + doAssert list.rotateLeft(1 ..< 9, 3) == 6 + doAssert list == expected + doAssert list2 == @expected + + var s0,s1,s2,s3,s4,s5 = "xxxabcdefgxxx" + + doAssert s0.rotateLeft(3 ..< 10, 3) == 7 + doAssert s0 == "xxxdefgabcxxx" + doAssert s1.rotateLeft(3 ..< 10, 2) == 8 + doAssert s1 == "xxxcdefgabxxx" + doAssert s2.rotateLeft(3 ..< 10, 4) == 6 + doAssert s2 == "xxxefgabcdxxx" + doAssert s3.rotateLeft(3 ..< 10, -3) == 6 + doAssert s3 == "xxxefgabcdxxx" + doAssert s4.rotateLeft(3 ..< 10, -10) == 6 + doAssert s4 == "xxxefgabcdxxx" + doAssert s5.rotateLeft(3 ..< 10, 11) == 6 + doAssert s5 == "xxxefgabcdxxx" diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 281d5b848..42ffa236c 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -9,8 +9,9 @@ include "system/inclrtl" -import os, tables, strutils, times, heapqueue, options, asyncstreams +import os, tables, strutils, times, heapqueue, lists, options, asyncstreams import asyncfutures except callSoon + import nativesockets, net, deques export Port, SocketFlag @@ -58,9 +59,10 @@ export asyncfutures, asyncstreams ## ## .. code-block::nim ## var future = socket.recv(100) -## future.callback = +## future.addCallback( ## proc () = ## echo(future.read) +## ) ## ## All asynchronous functions returning a ``Future`` will not block. They ## will not however return immediately. An asynchronous function will have @@ -136,6 +138,7 @@ export asyncfutures, asyncstreams ## and occasionally the compilation may fail altogether. ## As such it is better to use the former style when possible. ## +## ## Discarding futures ## ------------------ ## @@ -165,18 +168,20 @@ type timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]] callbacks*: Deque[proc ()] -proc processTimers(p: PDispatcherBase) {.inline.} = +proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} = #Process just part if timers at a step var count = p.timers.len let t = epochTime() while count > 0 and t >= p.timers[0].finishAt: p.timers.pop().fut.complete() dec count + didSomeWork = true -proc processPendingCallbacks(p: PDispatcherBase) = +proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) = while p.callbacks.len > 0: var cb = p.callbacks.popFirst() cb() + didSomeWork = true proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = # If dispatcher has active timers this proc returns the timeout @@ -226,6 +231,12 @@ when defined(windows) or defined(nimdoc): ovl: PCustomOverlapped PostCallbackDataPtr = ptr PostCallbackData + AsyncEventImpl = object + hEvent: Handle + hWaiter: Handle + pcd: PostCallbackDataPtr + AsyncEvent* = ptr AsyncEventImpl + Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.} {.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD, TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].} @@ -275,14 +286,13 @@ when defined(windows) or defined(nimdoc): let p = getGlobalDispatcher() p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0 - proc poll*(timeout = 500) = - ## Waits for completion events and processes them. Raises ``ValueError`` - ## if there are no pending operations. + proc runOnce(timeout = 500): bool = let p = getGlobalDispatcher() if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0: raise newException(ValueError, "No handles or timers registered in dispatcher.") + result = false if p.handles.len != 0: let at = p.adjustedTimeout(timeout) var llTimeout = @@ -295,6 +305,7 @@ when defined(windows) or defined(nimdoc): let res = getQueuedCompletionStatus(p.ioPort, addr lpNumberOfBytesTransferred, addr lpCompletionKey, cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool + result = true # http://stackoverflow.com/a/12277264/492186 # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html @@ -324,17 +335,18 @@ when defined(windows) or defined(nimdoc): else: if errCode.int32 == WAIT_TIMEOUT: # Timed out - discard + result = false else: raiseOSError(errCode) # Timer processing. - processTimers(p) + processTimers(p, result) # Callback queue processing - processPendingCallbacks(p) + processPendingCallbacks(p, result) + - var connectExPtr: pointer = nil - var acceptExPtr: pointer = nil - var getAcceptExSockAddrsPtr: pointer = nil + var acceptEx: WSAPROC_ACCEPTEX + var connectEx: WSAPROC_CONNECTEX + var getAcceptExSockAddrs: WSAPROC_GETACCEPTEXSOCKADDRS proc initPointer(s: SocketHandle, fun: var pointer, guid: var GUID): bool = # Ref: https://github.com/powdahound/twisted/blob/master/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c @@ -346,56 +358,19 @@ when defined(windows) or defined(nimdoc): proc initAll() = let dummySock = newNativeSocket() - if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX): + if dummySock == INVALID_SOCKET: raiseOSError(osLastError()) - if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX): + var fun: pointer = nil + if not initPointer(dummySock, fun, WSAID_CONNECTEX): raiseOSError(osLastError()) - if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS): + connectEx = cast[WSAPROC_CONNECTEX](fun) + if not initPointer(dummySock, fun, WSAID_ACCEPTEX): raiseOSError(osLastError()) - - proc connectEx(s: SocketHandle, name: ptr SockAddr, namelen: cint, - lpSendBuffer: pointer, dwSendDataLength: Dword, - lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool = - if connectExPtr.isNil: raise newException(ValueError, "Need to initialise ConnectEx().") - let fun = - cast[proc (s: SocketHandle, name: ptr SockAddr, namelen: cint, - lpSendBuffer: pointer, dwSendDataLength: Dword, - lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool {.stdcall,gcsafe.}](connectExPtr) - - result = fun(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent, - lpOverlapped) - - proc acceptEx(listenSock, acceptSock: SocketHandle, lpOutputBuffer: pointer, - dwReceiveDataLength, dwLocalAddressLength, - dwRemoteAddressLength: Dword, lpdwBytesReceived: PDword, - lpOverlapped: POVERLAPPED): bool = - if acceptExPtr.isNil: raise newException(ValueError, "Need to initialise AcceptEx().") - let fun = - cast[proc (listenSock, acceptSock: SocketHandle, lpOutputBuffer: pointer, - dwReceiveDataLength, dwLocalAddressLength, - dwRemoteAddressLength: Dword, lpdwBytesReceived: PDword, - lpOverlapped: POVERLAPPED): bool {.stdcall,gcsafe.}](acceptExPtr) - result = fun(listenSock, acceptSock, lpOutputBuffer, dwReceiveDataLength, - dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived, - lpOverlapped) - - proc getAcceptExSockaddrs(lpOutputBuffer: pointer, - dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: Dword, - LocalSockaddr: ptr ptr SockAddr, LocalSockaddrLength: LPInt, - RemoteSockaddr: ptr ptr SockAddr, RemoteSockaddrLength: LPInt) = - if getAcceptExSockAddrsPtr.isNil: - raise newException(ValueError, "Need to initialise getAcceptExSockAddrs().") - - let fun = - cast[proc (lpOutputBuffer: pointer, - dwReceiveDataLength, dwLocalAddressLength, - dwRemoteAddressLength: Dword, LocalSockaddr: ptr ptr SockAddr, - LocalSockaddrLength: LPInt, RemoteSockaddr: ptr ptr SockAddr, - RemoteSockaddrLength: LPInt) {.stdcall,gcsafe.}](getAcceptExSockAddrsPtr) - - fun(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength, - dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, - RemoteSockaddr, RemoteSockaddrLength) + acceptEx = cast[WSAPROC_ACCEPTEX](fun) + if not initPointer(dummySock, fun, WSAID_GETACCEPTEXSOCKADDRS): + raiseOSError(osLastError()) + getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun) + close(dummySock) proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = @@ -506,10 +481,7 @@ when defined(windows) or defined(nimdoc): proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = if not retFuture.finished: if errcode == OSErrorCode(-1): - if bytesCount == 0 and dataBuf.buf[0] == '\0': - retFuture.complete(0) - else: - retFuture.complete(bytesCount) + retFuture.complete(bytesCount) else: if flags.isDisconnectionError(errcode): retFuture.complete(0) @@ -543,10 +515,11 @@ when defined(windows) or defined(nimdoc): proc send*(socket: AsyncFD, buf: pointer, size: int, flags = {SocketFlag.SafeDisconn}): Future[void] = - ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all - ## data has been sent. - ## **WARNING**: Use it with caution. If ``buf`` refers to GC'ed object, you must use GC_ref/GC_unref calls - ## to avoid early freeing of the buffer + ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future + ## will complete once all data has been sent. + ## + ## **WARNING**: Use it with caution. If ``buf`` refers to GC'ed object, + ## you must use GC_ref/GC_unref calls to avoid early freeing of the buffer. verifyPresence(socket) var retFuture = newFuture[void]("send") @@ -793,7 +766,7 @@ when defined(windows) or defined(nimdoc): cast[pointer](p.ovl)) {.pop.} - template registerWaitableEvent(mask) = + proc registerWaitableEvent(fd: AsyncFD, cb: Callback; mask: Dword) = let p = getGlobalDispatcher() var flags = (WT_EXECUTEINWAITTHREAD or WT_EXECUTEONLYONCE).Dword var hEvent = wsaCreateEvent() @@ -843,8 +816,8 @@ when defined(windows) or defined(nimdoc): cast[pointer](pcd), INFINITE, flags): # pcd.ovl will be unrefed in poll() let err = osLastError() - discard wsaCloseEvent(hEvent) deallocShared(cast[pointer](pcd)) + discard wsaCloseEvent(hEvent) raiseOSError(err) else: # we incref `pcd.ovl` and `protect` callback one more time, @@ -883,16 +856,17 @@ when defined(windows) or defined(nimdoc): ## ## This is not ``pure`` mechanism for Windows Completion Ports (IOCP), ## so if you can avoid it, please do it. Use `addRead` only if really - ## need it (main usecase is adaptation of `unix like` libraries to be + ## need it (main usecase is adaptation of unix-like libraries to be ## asynchronous on Windows). - ## If you use this function, you dont need to use asyncdispatch.recv() + ## + ## If you use this function, you don't need to use asyncdispatch.recv() ## or asyncdispatch.accept(), because they are using IOCP, please use ## nativesockets.recv() and nativesockets.accept() instead. ## ## Be sure your callback ``cb`` returns ``true``, if you want to remove ## watch of `read` notifications, and ``false``, if you want to continue - ## receiving notifies. - registerWaitableEvent(FD_READ or FD_ACCEPT or FD_OOB or FD_CLOSE) + ## receiving notifications. + registerWaitableEvent(fd, cb, FD_READ or FD_ACCEPT or FD_OOB or FD_CLOSE) proc addWrite*(fd: AsyncFD, cb: Callback) = ## Start watching the file descriptor for write availability and then call @@ -900,43 +874,213 @@ when defined(windows) or defined(nimdoc): ## ## This is not ``pure`` mechanism for Windows Completion Ports (IOCP), ## so if you can avoid it, please do it. Use `addWrite` only if really - ## need it (main usecase is adaptation of `unix like` libraries to be + ## need it (main usecase is adaptation of unix-like libraries to be ## asynchronous on Windows). - ## If you use this function, you dont need to use asyncdispatch.send() + ## + ## If you use this function, you don't need to use asyncdispatch.send() ## or asyncdispatch.connect(), because they are using IOCP, please use ## nativesockets.send() and nativesockets.connect() instead. ## ## Be sure your callback ``cb`` returns ``true``, if you want to remove ## watch of `write` notifications, and ``false``, if you want to continue - ## receiving notifies. - registerWaitableEvent(FD_WRITE or FD_CONNECT or FD_CLOSE) + ## receiving notifications. + registerWaitableEvent(fd, cb, FD_WRITE or FD_CONNECT or FD_CLOSE) + + template registerWaitableHandle(p, hEvent, flags, pcd, timeout, + handleCallback) = + let handleFD = AsyncFD(hEvent) + pcd.ioPort = p.ioPort + pcd.handleFd = handleFD + var ol = PCustomOverlapped() + GC_ref(ol) + ol.data.fd = handleFD + ol.data.cb = handleCallback + # We need to protect our callback environment value, so GC will not free it + # accidentally. + ol.data.cell = system.protect(rawEnv(ol.data.cb)) + + pcd.ovl = ol + if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent, + cast[WAITORTIMERCALLBACK](waitableCallback), + cast[pointer](pcd), timeout.Dword, flags): + let err = osLastError() + GC_unref(ol) + deallocShared(cast[pointer](pcd)) + discard closeHandle(hEvent) + raiseOSError(err) + p.handles.incl(handleFD) + + template closeWaitable(handle: untyped) = + let waitFd = pcd.waitFd + deallocShared(cast[pointer](pcd)) + p.handles.excl(fd) + if unregisterWait(waitFd) == 0: + let err = osLastError() + if err.int32 != ERROR_IO_PENDING: + discard closeHandle(handle) + raiseOSError(err) + if closeHandle(handle) == 0: + raiseOSError(osLastError()) + + proc addTimer*(timeout: int, oneshot: bool, cb: Callback) = + ## Registers callback ``cb`` to be called when timer expired. + ## + ## Parameters: + ## + ## * ``timeout`` - timeout value in milliseconds. + ## * ``oneshot`` + ## * `true` - generate only one timeout event + ## * `false` - generate timeout events periodically + + doAssert(timeout > 0) + let p = getGlobalDispatcher() + + var hEvent = createEvent(nil, 1, 0, nil) + if hEvent == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + + var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData))) + var flags = WT_EXECUTEINWAITTHREAD.Dword + if oneshot: flags = flags or WT_EXECUTEONLYONCE + + proc timercb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = + let res = cb(fd) + if res or oneshot: + closeWaitable(hEvent) + else: + # if callback returned `false`, then it wants to be called again, so + # we need to ref and protect `pcd.ovl` again, because it will be + # unrefed and disposed in `poll()`. + GC_ref(pcd.ovl) + pcd.ovl.data.cell = system.protect(rawEnv(pcd.ovl.data.cb)) + + registerWaitableHandle(p, hEvent, flags, pcd, timeout, timercb) + + proc addProcess*(pid: int, cb: Callback) = + ## Registers callback ``cb`` to be called when process with process ID + ## ``pid`` exited. + let p = getGlobalDispatcher() + let procFlags = SYNCHRONIZE + var hProcess = openProcess(procFlags, 0, pid.Dword) + if hProcess == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + + var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData))) + var flags = WT_EXECUTEINWAITTHREAD.Dword + + proc proccb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = + closeWaitable(hProcess) + discard cb(fd) + + registerWaitableHandle(p, hProcess, flags, pcd, INFINITE, proccb) + + proc newAsyncEvent*(): AsyncEvent = + ## Creates a new thread-safe ``AsyncEvent`` object. + ## + ## New ``AsyncEvent`` object is not automatically registered with # TODO: Why? -- DP + ## dispatcher like ``AsyncSocket``. + var sa = SECURITY_ATTRIBUTES( + nLength: sizeof(SECURITY_ATTRIBUTES).cint, + bInheritHandle: 1 + ) + var event = createEvent(addr(sa), 0'i32, 0'i32, nil) + if event == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + result = cast[AsyncEvent](allocShared0(sizeof(AsyncEventImpl))) + result.hEvent = event + + proc trigger*(ev: AsyncEvent) = + ## Set event ``ev`` to signaled state. + if setEvent(ev.hEvent) == 0: + raiseOSError(osLastError()) + + proc unregister*(ev: AsyncEvent) = + ## Unregisters event ``ev``. + doAssert(ev.hWaiter != 0, "Event is not registered in the queue!") + let p = getGlobalDispatcher() + p.handles.excl(AsyncFD(ev.hEvent)) + if unregisterWait(ev.hWaiter) == 0: + let err = osLastError() + if err.int32 != ERROR_IO_PENDING: + raiseOSError(err) + ev.hWaiter = 0 + + proc close*(ev: AsyncEvent) = + ## Closes event ``ev``. + let res = closeHandle(ev.hEvent) + deallocShared(cast[pointer](ev)) + if res == 0: + raiseOSError(osLastError()) + + proc addEvent*(ev: AsyncEvent, cb: Callback) = + ## Registers callback ``cb`` to be called when ``ev`` will be signaled + doAssert(ev.hWaiter == 0, "Event is already registered in the queue!") + + let p = getGlobalDispatcher() + let hEvent = ev.hEvent + + var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData))) + var flags = WT_EXECUTEINWAITTHREAD.Dword + + proc eventcb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = + if ev.hWaiter != 0: + if cb(fd): + # we need this check to avoid exception, if `unregister(event)` was + # called in callback. + deallocShared(cast[pointer](pcd)) + if ev.hWaiter != 0: + unregister(ev) + else: + # if callback returned `false`, then it wants to be called again, so + # we need to ref and protect `pcd.ovl` again, because it will be + # unrefed and disposed in `poll()`. + GC_ref(pcd.ovl) + pcd.ovl.data.cell = system.protect(rawEnv(pcd.ovl.data.cb)) + else: + # if ev.hWaiter == 0, then event was unregistered before `poll()` call. + deallocShared(cast[pointer](pcd)) + + registerWaitableHandle(p, hEvent, flags, pcd, INFINITE, eventcb) + ev.hWaiter = pcd.waitFd initAll() else: import selectors from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, MSG_NOSIGNAL - + const + InitCallbackListSize = 4 # initial size of callbacks sequence, + # associated with file/socket descriptor. + InitDelayedCallbackListSize = 64 # initial size of delayed callbacks + # queue. type AsyncFD* = distinct cint Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.} - PData* = ref object of RootRef - fd: AsyncFD - readCBs: seq[Callback] - writeCBs: seq[Callback] + AsyncData = object + readList: seq[Callback] + writeList: seq[Callback] + + AsyncEvent* = distinct SelectEvent PDispatcher* = ref object of PDispatcherBase - selector: Selector + selector: Selector[AsyncData] {.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].} proc `==`*(x, y: AsyncFD): bool {.borrow.} + proc `==`*(x, y: AsyncEvent): bool {.borrow.} + + template newAsyncData(): AsyncData = + AsyncData( + readList: newSeqOfCap[Callback](InitCallbackListSize), + writeList: newSeqOfCap[Callback](InitCallbackListSize) + ) proc newDispatcher*(): PDispatcher = new result - result.selector = newSelector() + result.selector = newSelector[AsyncData]() result.timers.newHeapQueue() - result.callbacks = initDeque[proc ()](64) + result.callbacks = initDeque[proc ()](InitDelayedCallbackListSize) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher @@ -951,15 +1095,10 @@ else: setGlobalDispatcher(newDispatcher()) result = gDisp - proc update(fd: AsyncFD, events: set[Event]) = - let p = getGlobalDispatcher() - assert fd.SocketHandle in p.selector - p.selector.update(fd.SocketHandle, events) - proc register*(fd: AsyncFD) = let p = getGlobalDispatcher() - var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) - p.selector.register(fd.SocketHandle, {}, data.RootRef) + var data = newAsyncData() + p.selector.registerHandle(fd.SocketHandle, {}, data) proc closeSocket*(sock: AsyncFD) = let disp = getGlobalDispatcher() @@ -969,80 +1108,158 @@ else: proc unregister*(fd: AsyncFD) = getGlobalDispatcher().selector.unregister(fd.SocketHandle) + proc unregister*(ev: AsyncEvent) = + getGlobalDispatcher().selector.unregister(SelectEvent(ev)) + proc addRead*(fd: AsyncFD, cb: Callback) = let p = getGlobalDispatcher() - if fd.SocketHandle notin p.selector: + var newEvents = {Event.Read} + withData(p.selector, fd.SocketHandle, adata) do: + adata.readList.add(cb) + newEvents.incl(Event.Read) + if len(adata.writeList) != 0: newEvents.incl(Event.Write) + do: raise newException(ValueError, "File descriptor not registered.") - p.selector[fd.SocketHandle].data.PData.readCBs.add(cb) - update(fd, p.selector[fd.SocketHandle].events + {EvRead}) + p.selector.updateHandle(fd.SocketHandle, newEvents) proc addWrite*(fd: AsyncFD, cb: Callback) = let p = getGlobalDispatcher() - if fd.SocketHandle notin p.selector: + var newEvents = {Event.Write} + withData(p.selector, fd.SocketHandle, adata) do: + adata.writeList.add(cb) + newEvents.incl(Event.Write) + if len(adata.readList) != 0: newEvents.incl(Event.Read) + do: raise newException(ValueError, "File descriptor not registered.") - p.selector[fd.SocketHandle].data.PData.writeCBs.add(cb) - update(fd, p.selector[fd.SocketHandle].events + {EvWrite}) - - template processCallbacks(callbacks: untyped) = - # Callback may add items to ``callbacks`` which causes issues if - # we are iterating over it at the same time. We therefore - # make a copy to iterate over. - let currentCBs = callbacks - callbacks = @[] - # Using another sequence because callbacks themselves can add - # other callbacks. - var newCBs: seq[Callback] = @[] - for cb in currentCBs: - if newCBs.len > 0: - # A callback has already returned with EAGAIN, don't call any - # others until next `poll`. - newCBs.add(cb) - else: - if not cb(data.fd): - # Callback wants to be called again. - newCBs.add(cb) - callbacks = newCBs & callbacks + p.selector.updateHandle(fd.SocketHandle, newEvents) proc hasPendingOperations*(): bool = let p = getGlobalDispatcher() - p.selector.len != 0 or p.timers.len != 0 or p.callbacks.len != 0 - - proc poll*(timeout = 500) = + not p.selector.isEmpty() or p.timers.len != 0 or p.callbacks.len != 0 + + template processBasicCallbacks(ident, rwlist: untyped) = + # Process pending descriptor and AsyncEvent callbacks. + # + # Invoke every callback stored in `rwlist`, until one + # returns `false` (which means callback wants to stay + # alive). In such case all remaining callbacks will be added + # to `rwlist` again, in the order they have been inserted. + # + # `rwlist` associated with file descriptor MUST BE emptied before + # dispatching callback (See https://github.com/nim-lang/Nim/issues/5128), + # or it can be possible to fall into endless cycle. + var curList: seq[Callback] + + withData(p.selector, ident, adata) do: + shallowCopy(curList, adata.rwlist) + adata.rwlist = newSeqOfCap[Callback](InitCallbackListSize) + + let newLength = max(len(curList), InitCallbackListSize) + var newList = newSeqOfCap[Callback](newLength) + + for cb in curList: + if len(newList) > 0: + # A callback has already returned with EAGAIN, don't call any others + # until next `poll`. + newList.add(cb) + else: + if not cb(fd.AsyncFD): + # Callback wants to be called again. + newList.add(cb) + + withData(p.selector, ident, adata) do: + # descriptor still present in queue. + adata.rwlist = newList & adata.rwlist + rLength = len(adata.readList) + wLength = len(adata.writeList) + do: + # descriptor was unregistered in callback via `unregister()`. + rLength = -1 + wLength = -1 + + template processCustomCallbacks(ident: untyped) = + # Process pending custom event callbacks. Custom events are + # {Event.Timer, Event.Signal, Event.Process, Event.Vnode}. + # There can be only one callback registered with one descriptor, + # so there is no need to iterate over list. + var curList: seq[Callback] + + withData(p.selector, ident, adata) do: + shallowCopy(curList, adata.readList) + adata.readList = newSeqOfCap[Callback](InitCallbackListSize) + + let newLength = len(curList) + var newList = newSeqOfCap[Callback](newLength) + + var cb = curList[0] + if not cb(fd.AsyncFD): + newList.add(cb) + + withData(p.selector, ident, adata) do: + # descriptor still present in queue. + adata.readList = newList & adata.readList + if len(adata.readList) == 0: + # if no callbacks registered with descriptor, unregister it. + p.selector.unregister(fd) + do: + # descriptor was unregistered in callback via `unregister()`. + discard + + proc runOnce(timeout = 500): bool = let p = getGlobalDispatcher() - if p.selector.len == 0 and p.timers.len == 0 and p.callbacks.len == 0: + when ioselSupportedPlatform: + let customSet = {Event.Timer, Event.Signal, Event.Process, + Event.Vnode} + + if p.selector.isEmpty() and p.timers.len == 0 and p.callbacks.len == 0: raise newException(ValueError, "No handles or timers registered in dispatcher.") - if p.selector.len > 0: - for info in p.selector.select(p.adjustedTimeout(timeout)): - let data = PData(info.key.data) - assert data.fd == info.key.fd.AsyncFD - #echo("In poll ", data.fd.cint) - # There may be EvError here, but we handle them in callbacks, - # so that exceptions can be raised from `send(...)` and - # `recv(...)` routines. - - if EvRead in info.events or info.events == {EvError}: - processCallbacks(data.readCBs) - - if EvWrite in info.events or info.events == {EvError}: - processCallbacks(data.writeCBs) - - if info.key in p.selector: - var newEvents: set[Event] - if data.readCBs.len != 0: newEvents = {EvRead} - if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} - if newEvents != info.key.events: - update(data.fd, newEvents) - else: - # FD no longer a part of the selector. Likely been closed - # (e.g. socket disconnected). - discard + result = false + if not p.selector.isEmpty(): + var keys: array[64, ReadyKey] + var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys) + for i in 0..<count: + var custom = false + let fd = keys[i].fd + let events = keys[i].events + var rLength = 0 # len(data.readList) after callback + var wLength = 0 # len(data.writeList) after callback + + if Event.Read in events or events == {Event.Error}: + processBasicCallbacks(fd, readList) + result = true + + if Event.Write in events or events == {Event.Error}: + processBasicCallbacks(fd, writeList) + result = true + + if Event.User in events: + processBasicCallbacks(fd, readList) + custom = true + if rLength == 0: + p.selector.unregister(fd) + result = true + + when ioselSupportedPlatform: + if (customSet * events) != {}: + custom = true + processCustomCallbacks(fd) + result = true + + # because state `data` can be modified in callback we need to update + # descriptor events with currently registered callbacks. + if not custom: + var newEvents: set[Event] = {} + if rLength != -1 and wLength != -1: + if rLength > 0: incl(newEvents, Event.Read) + if wLength > 0: incl(newEvents, Event.Write) + p.selector.updateHandle(SocketHandle(fd), newEvents) # Timer processing. - processTimers(p) + processTimers(p, result) # Callback queue processing - processPendingCallbacks(p) + processPendingCallbacks(p, result) proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = @@ -1075,7 +1292,7 @@ else: return retFuture proc recvInto*(socket: AsyncFD, buf: pointer, size: int, - flags = {SocketFlag.SafeDisconn}): Future[int] = + flags = {SocketFlag.SafeDisconn}): Future[int] = var retFuture = newFuture[int]("recvInto") proc cb(sock: AsyncFD): bool = @@ -1216,6 +1433,68 @@ else: addRead(socket, cb) return retFuture + when ioselSupportedPlatform: + + proc addTimer*(timeout: int, oneshot: bool, cb: Callback) = + ## Start watching for timeout expiration, and then call the + ## callback ``cb``. + ## ``timeout`` - time in milliseconds, + ## ``oneshot`` - if ``true`` only one event will be dispatched, + ## if ``false`` continuous events every ``timeout`` milliseconds. + let p = getGlobalDispatcher() + var data = newAsyncData() + data.readList.add(cb) + p.selector.registerTimer(timeout, oneshot, data) + + proc addSignal*(signal: int, cb: Callback) = + ## Start watching signal ``signal``, and when signal appears, call the + ## callback ``cb``. + let p = getGlobalDispatcher() + var data = newAsyncData() + data.readList.add(cb) + p.selector.registerSignal(signal, data) + + proc addProcess*(pid: int, cb: Callback) = + ## Start watching for process exit with pid ``pid``, and then call + ## the callback ``cb``. + let p = getGlobalDispatcher() + var data = newAsyncData() + data.readList.add(cb) + p.selector.registerProcess(pid, data) + + proc newAsyncEvent*(): AsyncEvent = + ## Creates new ``AsyncEvent``. + result = AsyncEvent(newSelectEvent()) + + proc trigger*(ev: AsyncEvent) = + ## Sets new ``AsyncEvent`` to signaled state. + trigger(SelectEvent(ev)) + + proc close*(ev: AsyncEvent) = + ## Closes ``AsyncEvent`` + close(SelectEvent(ev)) + + proc addEvent*(ev: AsyncEvent, cb: Callback) = + ## Start watching for event ``ev``, and call callback ``cb``, when + ## ev will be set to signaled state. + let p = getGlobalDispatcher() + var data = newAsyncData() + data.readList.add(cb) + p.selector.registerEvent(SelectEvent(ev), data) + +proc drain*(timeout = 500) = + ## Waits for completion events and processes them. Raises ``ValueError`` + ## if there are no pending operations. In contrast to ``poll`` this + ## processes as many events as are available. + if runOnce(timeout): + while hasPendingOperations() and runOnce(0): discard + +proc poll*(timeout = 500) = + ## Waits for completion events and processes them. Raises ``ValueError`` + ## if there are no pending operations. This runs the underlying OS + ## `epoll`:idx: or `kqueue`:idx: primitive only once. + discard runOnce(timeout) + # Common procedures between current and upcoming asyncdispatch include includes.asynccommon @@ -1269,7 +1548,7 @@ proc send*(socket: AsyncFD, data: string, var copiedData = data GC_ref(copiedData) # we need to protect data until send operation is completed - # or failed. + # or failed. let sendFut = socket.send(addr copiedData[0], data.len, flags) sendFut.callback = @@ -1317,7 +1596,7 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} = ## ## **Deprecated since version 0.15.0**: Use ``asyncnet.recvLine()`` instead. - template addNLIfEmpty(): untyped = + template addNLIfEmpty(): typed = if result.len == 0: result.add("\c\L") @@ -1353,3 +1632,5 @@ proc waitFor*[T](fut: Future[T]): T = poll() fut.read + +{.deprecated: [setEvent: trigger].} diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index bebd19611..bcc3ab613 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -1,4 +1,4 @@ -import os, tables, strutils, times, heapqueue, options, deques +import os, tables, strutils, times, heapqueue, options, deques, cstrutils # TODO: This shouldn't need to be included, but should ideally be exported. type @@ -217,17 +217,78 @@ proc `callback=`*[T](future: Future[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) +proc getHint(entry: StackTraceEntry): string = + ## We try to provide some hints about stack trace entries that the user + ## may not be familiar with, in particular calls inside the stdlib. + result = "" + if entry.procname == "processPendingCallbacks": + if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: + return "Executes pending callbacks" + elif entry.procname == "poll": + if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: + return "Processes asynchronous completion events" + + if entry.procname.endsWith("_continue"): + if cmpIgnoreStyle(entry.filename, "asyncmacro.nim") == 0: + return "Resumes an async procedure" + +proc `$`*(entries: seq[StackTraceEntry]): string = + result = "" + # Find longest filename & line number combo for alignment purposes. + var longestLeft = 0 + for entry in entries: + if entry.procName.isNil: continue + + let left = $entry.filename & $entry.line + if left.len > longestLeft: + longestLeft = left.len + + var indent = 2 + # Format the entries. + for entry in entries: + if entry.procName.isNil: + if entry.line == -10: + result.add(spaces(indent) & "#[\n") + indent.inc(2) + else: + indent.dec(2) + result.add(spaces(indent)& "]#\n") + continue + + let left = "$#($#)" % [$entry.filename, $entry.line] + result.add((spaces(indent) & "$#$# $#\n") % [ + left, + spaces(longestLeft - left.len + 2), + $entry.procName + ]) + let hint = getHint(entry) + if hint.len > 0: + result.add(spaces(indent+2) & "## " & hint & "\n") + proc injectStacktrace[T](future: Future[T]) = - # TODO: Come up with something better. when not defined(release): - var msg = "" - msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:") + const header = "\nAsync traceback:\n" - if not future.errorStackTrace.isNil and future.errorStackTrace != "": - msg.add("\n" & indent(future.errorStackTrace.strip(), 4)) - else: - msg.add("\n Empty or nil stack trace.") - future.error.msg.add(msg) + var exceptionMsg = future.error.msg + if header in exceptionMsg: + # This is messy: extract the original exception message from the msg + # containing the async traceback. + let start = exceptionMsg.find(header) + exceptionMsg = exceptionMsg[0..<start] + + + var newMsg = exceptionMsg & header + + let entries = getStackTraceEntries(future.error) + newMsg.add($entries) + + newMsg.add("Exception message: " & exceptionMsg & "\n") + newMsg.add("Exception type:") + + # # For debugging purposes + # for entry in getStackTraceEntries(future.error): + # newMsg.add "\n" & $entry + future.error.msg = newMsg proc read*[T](future: Future[T] | FutureVar[T]): T = ## Retrieves the value of ``future``. Future must be finished otherwise @@ -333,7 +394,7 @@ proc all*[T](futs: varargs[Future[T]]): auto = let totalFutures = len(futs) for fut in futs: - fut.callback = proc(f: Future[T]) = + fut.addCallback proc (f: Future[T]) = inc(completedFutures) if not retFuture.finished: if f.failed: @@ -355,7 +416,7 @@ proc all*[T](futs: varargs[Future[T]]): auto = for i, fut in futs: proc setCallback(i: int) = - fut.callback = proc(f: Future[T]) = + fut.addCallback proc (f: Future[T]) = inc(completedFutures) if not retFuture.finished: if f.failed: diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 6d4b85145..ba1615651 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -58,15 +58,18 @@ type socket: AsyncSocket reuseAddr: bool reusePort: bool + maxBody: int ## The maximum content-length that will be read for the body. {.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer, THttpCode: HttpCode, THttpVersion: HttpVersion].} -proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer = +proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, + maxBody = 8388608): AsyncHttpServer = ## Creates a new ``AsyncHttpServer`` instance. new result result.reuseAddr = reuseAddr result.reusePort = reusePort + result.maxBody = maxBody proc addHeaders(msg: var string, headers: HttpHeaders) = for k, v in headers: @@ -122,144 +125,157 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = raise newException(ValueError, "Invalid request protocol. Got: " & protocol) result.orig = protocol - i.inc protocol.parseInt(result.major, i) + i.inc protocol.parseSaturatedNatural(result.major, i) i.inc # Skip . - i.inc protocol.parseInt(result.minor, i) + i.inc protocol.parseSaturatedNatural(result.minor, i) proc sendStatus(client: AsyncSocket, status: string): Future[void] = client.send("HTTP/1.1 " & status & "\c\L\c\L") -proc processClient(client: AsyncSocket, address: string, - callback: proc (request: Request): +proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], + client: AsyncSocket, + address: string, lineFut: FutureVar[string], + callback: proc (request: Request): Future[void] {.closure, gcsafe.}) {.async.} = - var request: Request - request.url = initUri() - request.headers = newHttpHeaders() - var lineFut = newFutureVar[string]("asynchttpserver.processClient") - lineFut.mget() = newStringOfCap(80) - var key, value = "" - while not client.isClosed: - # GET /path HTTP/1.1 - # Header: val - # \n - request.headers.clear() - request.body = "" - request.hostname.shallowCopy(address) - assert client != nil - request.client = client - - # We should skip at least one empty line before the request - # https://tools.ietf.org/html/rfc7230#section-3.5 - for i in 0..1: - lineFut.mget().setLen(0) - lineFut.clean() - await client.recvLineInto(lineFut, maxLength=maxLine) # TODO: Timeouts. - - if lineFut.mget == "": - client.close() - return + # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere. + template request(): Request = + req.mget() + + # GET /path HTTP/1.1 + # Header: val + # \n + request.headers.clear() + request.body = "" + request.hostname.shallowCopy(address) + assert client != nil + request.client = client + + # We should skip at least one empty line before the request + # https://tools.ietf.org/html/rfc7230#section-3.5 + for i in 0..1: + lineFut.mget().setLen(0) + lineFut.clean() + await client.recvLineInto(lineFut, maxLength=maxLine) # TODO: Timeouts. + + if lineFut.mget == "": + client.close() + return - if lineFut.mget.len > maxLine: - await request.respondError(Http413) - client.close() + if lineFut.mget.len > maxLine: + await request.respondError(Http413) + client.close() + return + if lineFut.mget != "\c\L": + break + + # First line - GET /path HTTP/1.1 + var i = 0 + for linePart in lineFut.mget.split(' '): + case i + of 0: + try: + # TODO: this is likely slow. + request.reqMethod = parseEnum[HttpMethod]("http" & linePart) + except ValueError: + asyncCheck request.respondError(Http400) return - if lineFut.mget != "\c\L": - break - - # First line - GET /path HTTP/1.1 - var i = 0 - for linePart in lineFut.mget.split(' '): - case i - of 0: - try: - # TODO: this is likely slow. - request.reqMethod = parseEnum[HttpMethod]("http" & linePart) - except ValueError: - asyncCheck request.respondError(Http400) - continue - of 1: - try: - parseUri(linePart, request.url) - except ValueError: - asyncCheck request.respondError(Http400) - continue - of 2: - try: - request.protocol = parseProtocol(linePart) - except ValueError: - asyncCheck request.respondError(Http400) - continue - else: - await request.respondError(Http400) - continue - inc i - - # Headers - while true: - i = 0 - lineFut.mget.setLen(0) - lineFut.clean() - await client.recvLineInto(lineFut, maxLength=maxLine) - - if lineFut.mget == "": - client.close(); return - if lineFut.mget.len > maxLine: - await request.respondError(Http413) - client.close(); return - if lineFut.mget == "\c\L": break - let (key, value) = parseHeader(lineFut.mget) - request.headers[key] = value - # Ensure the client isn't trying to DoS us. - if request.headers.len > headerLimit: - await client.sendStatus("400 Bad Request") - request.client.close() + of 1: + try: + parseUri(linePart, request.url) + except ValueError: + asyncCheck request.respondError(Http400) return + of 2: + try: + request.protocol = parseProtocol(linePart) + except ValueError: + asyncCheck request.respondError(Http400) + return + else: + await request.respondError(Http400) + return + inc i - if request.reqMethod == HttpPost: - # Check for Expect header - if request.headers.hasKey("Expect"): - if "100-continue" in request.headers["Expect"]: - await client.sendStatus("100 Continue") - else: - await client.sendStatus("417 Expectation Failed") - - # Read the body - # - Check for Content-length header - if request.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(request.headers["Content-Length"], - contentLength) == 0: - await request.respond(Http400, "Bad Request. Invalid Content-Length.") - continue - else: - request.body = await client.recv(contentLength) - if request.body.len != contentLength: - await request.respond(Http400, "Bad Request. Content-Length does not match actual.") - continue - elif request.reqMethod == HttpPost: - await request.respond(Http411, "Content-Length required.") - continue - - # Call the user's callback. - await callback(request) - - if "upgrade" in request.headers.getOrDefault("connection"): + # Headers + while true: + i = 0 + lineFut.mget.setLen(0) + lineFut.clean() + await client.recvLineInto(lineFut, maxLength=maxLine) + + if lineFut.mget == "": + client.close(); return + if lineFut.mget.len > maxLine: + await request.respondError(Http413) + client.close(); return + if lineFut.mget == "\c\L": break + let (key, value) = parseHeader(lineFut.mget) + request.headers[key] = value + # Ensure the client isn't trying to DoS us. + if request.headers.len > headerLimit: + await client.sendStatus("400 Bad Request") + request.client.close() return - # Persistent connections - if (request.protocol == HttpVer11 and - request.headers.getOrDefault("connection").normalize != "close") or - (request.protocol == HttpVer10 and - request.headers.getOrDefault("connection").normalize == "keep-alive"): - # In HTTP 1.1 we assume that connection is persistent. Unless connection - # header states otherwise. - # In HTTP 1.0 we assume that the connection should not be persistent. - # Unless the connection header states otherwise. - discard + if request.reqMethod == HttpPost: + # Check for Expect header + if request.headers.hasKey("Expect"): + if "100-continue" in request.headers["Expect"]: + await client.sendStatus("100 Continue") + else: + await client.sendStatus("417 Expectation Failed") + + # Read the body + # - Check for Content-length header + if request.headers.hasKey("Content-Length"): + var contentLength = 0 + if parseSaturatedNatural(request.headers["Content-Length"], contentLength) == 0: + await request.respond(Http400, "Bad Request. Invalid Content-Length.") + return else: - request.client.close() - break + if contentLength > server.maxBody: + await request.respondError(Http413) + return + request.body = await client.recv(contentLength) + if request.body.len != contentLength: + await request.respond(Http400, "Bad Request. Content-Length does not match actual.") + return + elif request.reqMethod == HttpPost: + await request.respond(Http411, "Content-Length required.") + return + + # Call the user's callback. + await callback(request) + + if "upgrade" in request.headers.getOrDefault("connection"): + return + + # Persistent connections + if (request.protocol == HttpVer11 and + cmpIgnoreCase(request.headers.getOrDefault("connection"), "close") != 0) or + (request.protocol == HttpVer10 and + cmpIgnoreCase(request.headers.getOrDefault("connection"), "keep-alive") == 0): + # In HTTP 1.1 we assume that connection is persistent. Unless connection + # header states otherwise. + # In HTTP 1.0 we assume that the connection should not be persistent. + # Unless the connection header states otherwise. + discard + else: + request.client.close() + return + +proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string, + callback: proc (request: Request): + Future[void] {.closure, gcsafe.}) {.async.} = + var request = newFutureVar[Request]("asynchttpserver.processClient") + request.mget().url = initUri() + request.mget().headers = newHttpHeaders() + var lineFut = newFutureVar[string]("asynchttpserver.processClient") + lineFut.mget() = newStringOfCap(80) + + while not client.isClosed: + await processRequest(server, request, client, address, lineFut, callback) proc serve*(server: AsyncHttpServer, port: Port, callback: proc (request: Request): Future[void] {.closure,gcsafe.}, @@ -280,7 +296,7 @@ proc serve*(server: AsyncHttpServer, port: Port, # TODO: Causes compiler crash. #var (address, client) = await server.socket.acceptAddr() var fut = await server.socket.acceptAddr() - asyncCheck processClient(fut.client, fut.address, callback) + asyncCheck processClient(server, fut.client, fut.address, callback) #echo(f.isNil) #echo(f.repr) diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 6e7d7993f..8c679929d 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -25,10 +25,10 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} = result = node[0] template createCb(retFutureSym, iteratorNameSym, - name, futureVarCompletions: untyped) = + strName, identName, futureVarCompletions: untyped) = var nameIterVar = iteratorNameSym #{.push stackTrace: off.} - proc cb0 {.closure.} = + proc identName {.closure.} = try: if not nameIterVar.finished: var next = nameIterVar() @@ -36,11 +36,11 @@ template createCb(retFutureSym, iteratorNameSym, if not retFutureSym.finished: let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " & "`nil` Future?" - raise newException(AssertionError, msg % name) + raise newException(AssertionError, msg % strName) else: {.gcsafe.}: {.push hint[ConvFromXtoItselfNotNeeded]: off.} - next.callback = (proc() {.closure, gcsafe.})(cb0) + next.callback = (proc() {.closure, gcsafe.})(identName) {.pop.} except: futureVarCompletions @@ -52,7 +52,7 @@ template createCb(retFutureSym, iteratorNameSym, else: retFutureSym.fail(getCurrentException()) - cb0() + identName() #{.pop.} proc generateExceptionCheck(futSym, tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} = @@ -61,14 +61,14 @@ proc generateExceptionCheck(futSym, else: var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[] let errorNode = newDotExpr(futSym, newIdentNode("error")) - for i in 1 .. <tryStmt.len: + for i in 1 ..< tryStmt.len: let exceptBranch = tryStmt[i] if exceptBranch[0].kind == nnkStmtList: exceptionChecks.add((newIdentNode("true"), exceptBranch[0])) else: var exceptIdentCount = 0 var ifCond: NimNode - for i in 0 .. <exceptBranch.len: + for i in 0 ..< exceptBranch.len: let child = exceptBranch[i] if child.kind == nnkIdent: let cond = infix(errorNode, "of", child) @@ -270,7 +270,7 @@ proc processBody(node, retFutureSym: NimNode, return else: discard - for i in 0 .. <result.len: + for i in 0 ..< result.len: result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, futureVarIdents, nil) @@ -287,7 +287,7 @@ proc getName(node: NimNode): string {.compileTime.} = proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} = result = @[] - for i in 1 .. <len(params): + for i in 1 ..< len(params): expectKind(params[i], nnkIdentDefs) if params[i][1].kind == nnkBracketExpr and ($params[i][1][0].ident).normalize == "futurevar": @@ -389,9 +389,12 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = outerProcBody.add(closureIterator) # -> createCb(retFuture) - #var cbName = newIdentNode("cb") + # NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce + # friendlier stack traces: + var cbName = genSym(nskProc, prcName & "_continue") var procCb = getAst createCb(retFutureSym, iteratorNameSym, newStrLitNode(prcName), + cbName, createFutureVarCompletions(futureVarIdents, nil)) outerProcBody.add procCb @@ -466,33 +469,19 @@ proc stripAwait(node: NimNode): NimNode = node[0][0] = emptyNoopSym else: discard - for i in 0 .. <result.len: + for i in 0 ..< result.len: result[i] = stripAwait(result[i]) proc splitParamType(paramType: NimNode, async: bool): NimNode = result = paramType if paramType.kind == nnkInfix and $paramType[0].ident in ["|", "or"]: - let firstType = paramType[1] - let firstTypeName = $firstType.ident - let secondType = paramType[2] - let secondTypeName = $secondType.ident - - # Make sure that at least one has the name `async`, otherwise we shouldn't - # touch it. - if not ("async" in firstTypeName.normalize or - "async" in secondTypeName.normalize): - return - - if async: - if firstTypeName.normalize.startsWith("async"): - result = paramType[1] - elif secondTypeName.normalize.startsWith("async"): - result = paramType[2] - else: - if not firstTypeName.normalize.startsWith("async"): - result = paramType[1] - elif not secondTypeName.normalize.startsWith("async"): - result = paramType[2] + let firstAsync = "async" in ($paramType[1].ident).normalize + let secondAsync = "async" in ($paramType[2].ident).normalize + + if firstAsync: + result = paramType[if async: 1 else: 2] + elif secondAsync: + result = paramType[if async: 2 else: 1] proc stripReturnType(returnType: NimNode): NimNode = # Strip out the 'Future' from 'Future[T]'. @@ -512,7 +501,7 @@ proc splitProc(prc: NimNode): (NimNode, NimNode) = # Retrieve the `T` inside `Future[T]`. let returnType = stripReturnType(result[0][3][0]) result[0][3][0] = splitParamType(returnType, async=false) - for i in 1 .. <result[0][3].len: + for i in 1 ..< result[0][3].len: # Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) -> # parameter type (1). result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false) @@ -521,7 +510,7 @@ proc splitProc(prc: NimNode): (NimNode, NimNode) = result[1] = prc.copyNimTree() if result[1][3][0].kind == nnkBracketExpr: result[1][3][0][1] = splitParamType(result[1][3][0][1], async=true) - for i in 1 .. <result[1][3].len: + for i in 1 ..< result[1][3].len: # Async proc (1) -> FormalParams (3) -> IdentDefs, the parameter (i) -> # parameter type (1). result[1][3][i][1] = splitParamType(result[1][3][i][1], async=true) @@ -535,4 +524,4 @@ macro multisync*(prc: untyped): untyped = let (sync, asyncPrc) = splitProc(prc) result = newStmtList() result.add(asyncSingleProc(asyncPrc)) - result.add(sync) \ No newline at end of file + result.add(sync) diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim index d1207603d..3f213c5ea 100644 --- a/lib/pure/bitops.nim +++ b/lib/pure/bitops.nim @@ -181,7 +181,7 @@ elif useICC_builtins: proc countSetBits*(x: SomeInteger): int {.inline, nosideeffect.} = - ## Counts the set bits in integer. (also called Hamming weight.) + ## Counts the set bits in integer. (also called `Hamming weight`:idx:.) # TODO: figure out if ICC support _popcnt32/_popcnt64 on platform without POPCNT. # like GCC and MSVC when nimvm: diff --git a/lib/pure/browsers.nim b/lib/pure/browsers.nim index c6a603318..6912b893c 100644 --- a/lib/pure/browsers.nim +++ b/lib/pure/browsers.nim @@ -21,24 +21,18 @@ proc openDefaultBrowser*(url: string) = ## opens `url` with the user's default browser. This does not block. ## ## Under Windows, ``ShellExecute`` is used. Under Mac OS X the ``open`` - ## command is used. Under Unix, it is checked if ``gnome-open`` exists and - ## used if it does. Next attempt is ``kde-open``, then ``xdg-open``. - ## Otherwise the environment variable ``BROWSER`` is used to determine the - ## default browser to use. + ## command is used. Under Unix, it is checked if ``xdg-open`` exists and + ## used if it does. Otherwise the environment variable ``BROWSER`` is + ## used to determine the default browser to use. when defined(windows): - when useWinUnicode: - var o = newWideCString("open") - var u = newWideCString(url) - discard shellExecuteW(0'i32, o, u, nil, nil, SW_SHOWNORMAL) - else: - discard shellExecuteA(0'i32, "open", url, nil, nil, SW_SHOWNORMAL) + var o = newWideCString("open") + var u = newWideCString(url) + discard shellExecuteW(0'i32, o, u, nil, nil, SW_SHOWNORMAL) elif defined(macosx): discard execShellCmd("open " & quoteShell(url)) else: - const attempts = ["gnome-open ", "kde-open ", "xdg-open "] var u = quoteShell(url) - for a in items(attempts): - if execShellCmd(a & u) == 0: return + if execShellCmd("xdg-open " & u) == 0: return for b in getEnv("BROWSER").string.split(PathSep): try: # we use ``startProcess`` here because we don't want to block! diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 200a4adf1..5de6aa487 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -29,21 +29,8 @@ ## writeLine(stdout, "your password: " & myData["password"]) ## writeLine(stdout, "</body></html>") -import strutils, os, strtabs, cookies - -proc encodeUrl*(s: string): string = - ## Encodes a value to be HTTP safe: This means that characters in the set - ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result, - ## a space is converted to ``'+'`` and every other character is encoded as - ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. - result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars - for i in 0..s.len-1: - case s[i] - of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i]) - of ' ': add(result, '+') - else: - add(result, '%') - add(result, toHex(ord(s[i]), 2)) +import strutils, os, strtabs, cookies, uri +export uri.encodeUrl, uri.decodeUrl proc handleHexChar(c: char, x: var int) {.inline.} = case c @@ -52,30 +39,6 @@ proc handleHexChar(c: char, x: var int) {.inline.} = of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) else: assert(false) -proc decodeUrl*(s: string): string = - ## Decodes a value from its HTTP representation: This means that a ``'+'`` - ## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal - ## value) is converted to the character with ordinal number ``xx``, and - ## and every other character is carried over. - result = newString(s.len) - var i = 0 - var j = 0 - while i < s.len: - case s[i] - of '%': - var x = 0 - handleHexChar(s[i+1], x) - handleHexChar(s[i+2], x) - inc(i, 2) - result[j] = chr(x) - of '+': result[j] = ' ' - else: result[j] = s[i] - inc(i) - inc(j) - setLen(result, j) - -{.deprecated: [URLDecode: decodeUrl, URLEncode: encodeUrl].} - proc addXmlChar(dest: var string, c: char) {.inline.} = case c of '&': add(dest, "&") @@ -101,8 +64,7 @@ type methodPost, ## query uses the POST method methodGet ## query uses the GET method -{.deprecated: [TRequestMethod: RequestMethod, ECgi: CgiError, - XMLencode: xmlEncode].} +{.deprecated: [TRequestMethod: RequestMethod, ECgi: CgiError].} proc cgiError*(msg: string) {.noreturn.} = ## raises an ECgi exception with message `msg`. @@ -393,8 +355,3 @@ proc existsCookie*(name: string): bool = ## Checks if a cookie of `name` exists. if gcookies == nil: gcookies = parseCookies(getHttpCookie()) result = hasKey(gcookies, name) - -when isMainModule: - const test1 = "abc\L+def xyz" - assert encodeUrl(test1) == "abc%0A%2Bdef+xyz" - assert decodeUrl(encodeUrl(test1)) == test1 diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index f70a12843..34f5c5470 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -141,8 +141,8 @@ proc excl*[T](c: var CritBitTree[T], key: string) = proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool = ## Returns true iff `c` does not contain the given `key`. If the key - ## does exist, c.excl(key) is performed. - let oldCount = c.count + ## does exist, c.excl(key) is performed. + let oldCount = c.count var n = exclImpl(c, key) result = c.count == oldCount @@ -257,7 +257,7 @@ proc allprefixedAux[T](c: CritBitTree[T], key: string; longestMatch: bool): Node p = p.child[dir] if q.byte < key.len: top = p if not longestMatch: - for i in 0 .. <key.len: + for i in 0 ..< key.len: if p.key[i] != key[i]: return result = top @@ -326,7 +326,7 @@ proc `$`*[T](c: CritBitTree[T]): string = result.add($key) when T isnot void: result.add(": ") - result.add($val) + result.addQuoted(val) result.add("}") when isMainModule: diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 1bbe9f1ad..328308a9b 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -185,7 +185,7 @@ proc `$`*[T](deq: Deque[T]): string = result = "[" for x in deq: if result.len > 1: result.add(", ") - result.add($x) + result.addQuoted(x) result.add("]") when isMainModule: @@ -207,9 +207,9 @@ when isMainModule: assert($deq == "[4, 56, 6, 789]") assert deq[0] == deq.peekFirst and deq.peekFirst == 4 - assert deq[^1] == deq.peekLast and deq.peekLast == 789 + #assert deq[^1] == deq.peekLast and deq.peekLast == 789 deq[0] = 42 - deq[^1] = 7 + deq[deq.len - 1] = 7 assert 6 in deq and 789 notin deq assert deq.find(6) >= 0 diff --git a/lib/pure/collections/heapqueue.nim b/lib/pure/collections/heapqueue.nim index f86ba1d3f..60869142e 100644 --- a/lib/pure/collections/heapqueue.nim +++ b/lib/pure/collections/heapqueue.nim @@ -1,3 +1,12 @@ + +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Yuriy Glukhov +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. + ##[ Heap queue algorithm (a.k.a. priority queue). Ported from Python heapq. Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index f847ddd58..e69acc8d9 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -37,6 +37,14 @@ type DoublyLinkedRing*[T] = object ## a doubly linked ring head*: DoublyLinkedNode[T] + SomeLinkedList*[T] = SinglyLinkedList[T] | DoublyLinkedList[T] + + SomeLinkedRing*[T] = SinglyLinkedRing[T] | DoublyLinkedRing[T] + + SomeLinkedCollection*[T] = SomeLinkedList[T] | SomeLinkedRing[T] + + SomeLinkedNode*[T] = SinglyLinkedNode[T] | DoublyLinkedNode[T] + {.deprecated: [TDoublyLinkedNode: DoublyLinkedNodeObj, PDoublyLinkedNode: DoublyLinkedNode, TSinglyLinkedNode: SinglyLinkedNodeObj, @@ -86,137 +94,57 @@ template itemsRingImpl() {.dirty.} = it = it.next if it == L.head: break -template nodesListImpl() {.dirty.} = - var it = L.head - while it != nil: - var nxt = it.next - yield it - it = nxt - -template nodesRingImpl() {.dirty.} = - var it = L.head - if it != nil: - while true: - var nxt = it.next - yield it - it = nxt - if it == L.head: break - -template findImpl() {.dirty.} = - for x in nodes(L): - if x.value == value: return x - -iterator items*[T](L: DoublyLinkedList[T]): T = +iterator items*[T](L: SomeLinkedList[T]): T = ## yields every value of `L`. itemsListImpl() -iterator items*[T](L: SinglyLinkedList[T]): T = - ## yields every value of `L`. - itemsListImpl() - -iterator items*[T](L: SinglyLinkedRing[T]): T = - ## yields every value of `L`. - itemsRingImpl() - -iterator items*[T](L: DoublyLinkedRing[T]): T = +iterator items*[T](L: SomeLinkedRing[T]): T = ## yields every value of `L`. itemsRingImpl() -iterator mitems*[T](L: var DoublyLinkedList[T]): var T = +iterator mitems*[T](L: var SomeLinkedList[T]): var T = ## yields every value of `L` so that you can modify it. itemsListImpl() -iterator mitems*[T](L: var SinglyLinkedList[T]): var T = - ## yields every value of `L` so that you can modify it. - itemsListImpl() - -iterator mitems*[T](L: var SinglyLinkedRing[T]): var T = +iterator mitems*[T](L: var SomeLinkedRing[T]): var T = ## yields every value of `L` so that you can modify it. itemsRingImpl() -iterator mitems*[T](L: var DoublyLinkedRing[T]): var T = - ## yields every value of `L` so that you can modify it. - itemsRingImpl() - -iterator nodes*[T](L: SinglyLinkedList[T]): SinglyLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the - ## list during traversal is supported. - nodesListImpl() - -iterator nodes*[T](L: DoublyLinkedList[T]): DoublyLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the - ## list during traversal is supported. - nodesListImpl() - -iterator nodes*[T](L: SinglyLinkedRing[T]): SinglyLinkedNode[T] = +iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] = ## iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. - nodesRingImpl() + var it = L.head + while it != nil: + var nxt = it.next + yield it + it = nxt -iterator nodes*[T](L: DoublyLinkedRing[T]): DoublyLinkedNode[T] = +iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] = ## iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. - nodesRingImpl() + var it = L.head + if it != nil: + while true: + var nxt = it.next + yield it + it = nxt + if it == L.head: break -template dollarImpl() {.dirty.} = +proc `$`*[T](L: SomeLinkedCollection[T]): string = + ## turns a list into its string representation. result = "[" for x in nodes(L): if result.len > 1: result.add(", ") - result.add($x.value) + result.addQuoted(x.value) result.add("]") -proc `$`*[T](L: SinglyLinkedList[T]): string = - ## turns a list into its string representation. - dollarImpl() - -proc `$`*[T](L: DoublyLinkedList[T]): string = - ## turns a list into its string representation. - dollarImpl() - -proc `$`*[T](L: SinglyLinkedRing[T]): string = - ## turns a list into its string representation. - dollarImpl() - -proc `$`*[T](L: DoublyLinkedRing[T]): string = - ## turns a list into its string representation. - dollarImpl() - -proc find*[T](L: SinglyLinkedList[T], value: T): SinglyLinkedNode[T] = - ## searches in the list for a value. Returns nil if the value does not - ## exist. - findImpl() - -proc find*[T](L: DoublyLinkedList[T], value: T): DoublyLinkedNode[T] = +proc find*[T](L: SomeLinkedCollection[T], value: T): SomeLinkedNode[T] = ## searches in the list for a value. Returns nil if the value does not ## exist. - findImpl() - -proc find*[T](L: SinglyLinkedRing[T], value: T): SinglyLinkedNode[T] = - ## searches in the list for a value. Returns nil if the value does not - ## exist. - findImpl() - -proc find*[T](L: DoublyLinkedRing[T], value: T): DoublyLinkedNode[T] = - ## searches in the list for a value. Returns nil if the value does not - ## exist. - findImpl() - -proc contains*[T](L: SinglyLinkedList[T], value: T): bool {.inline.} = - ## searches in the list for a value. Returns false if the value does not - ## exist, true otherwise. - result = find(L, value) != nil - -proc contains*[T](L: DoublyLinkedList[T], value: T): bool {.inline.} = - ## searches in the list for a value. Returns false if the value does not - ## exist, true otherwise. - result = find(L, value) != nil - -proc contains*[T](L: SinglyLinkedRing[T], value: T): bool {.inline.} = - ## searches in the list for a value. Returns false if the value does not - ## exist, true otherwise. - result = find(L, value) != nil + for x in nodes(L): + if x.value == value: return x -proc contains*[T](L: DoublyLinkedRing[T], value: T): bool {.inline.} = +proc contains*[T](L: SomeLinkedCollection[T], value: T): bool {.inline.} = ## searches in the list for a value. Returns false if the value does not ## exist, true otherwise. result = find(L, value) != nil @@ -266,7 +194,6 @@ proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = if n.next != nil: n.next.prev = n.prev if n.prev != nil: n.prev.next = n.next - proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = ## appends a node `n` to `L`. Efficiency: O(1). if L.head != nil: diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim index 401422162..ce792d6da 100644 --- a/lib/pure/collections/queues.nim +++ b/lib/pure/collections/queues.nim @@ -198,9 +198,8 @@ when isMainModule: assert($q == "[4, 56, 6, 789]") assert q[0] == q.front and q.front == 4 - assert q[^1] == q.back and q.back == 789 q[0] = 42 - q[^1] = 7 + q[q.len - 1] = 7 assert 6 in q and 789 notin q assert q.find(6) >= 0 diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index e8e725aa3..06e96ca36 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -13,12 +13,15 @@ ## were inspired by functional programming languages. ## ## For functional style programming you may want to pass `anonymous procs -## <manual.html#anonymous-procs>`_ to procs like ``filter`` to reduce typing. -## Anonymous procs can use `the special do notation <manual.html#do-notation>`_ +## <manual.html#procedures-anonymous-procs>`_ to procs like ``filter`` to +## reduce typing. Anonymous procs can use `the special do notation +## <manual.html#procedures-do-notation>`_ ## which is more convenient in certain situations. include "system/inclrtl" +import macros + when not defined(nimhygiene): {.pragma: dirty.} @@ -43,12 +46,27 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = result[i] = itm inc(i) -proc cycle*[T](s: seq[T], n: Natural): seq[T] = - ## Returns a new sequence with the items of `s` repeated `n` times. +proc count*[T](s: openArray[T], x: T): int = + ## Returns the number of occurrences of the item `x` in the container `s`. + ## + ## Example: + ## + ## .. code-block:: + ## let + ## s = @[1, 2, 2, 3, 2, 4, 2] + ## c = count(s, 2) + ## assert c == 4 + for itm in items(s): + if itm == x: + inc result + +proc cycle*[T](s: openArray[T], n: Natural): seq[T] = + ## Returns a new sequence with the items of the container `s` repeated + ## `n` times. ## ## Example: ## - ## .. code-block: + ## .. code-block:: ## ## let ## s = @[1, 2, 3] @@ -56,7 +74,7 @@ proc cycle*[T](s: seq[T], n: Natural): seq[T] = ## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] result = newSeq[T](n * s.len) var o = 0 - for x in 0..<n: + for x in 0 ..< n: for e in s: result[o] = e inc o @@ -66,18 +84,20 @@ proc repeat*[T](x: T, n: Natural): seq[T] = ## ## Example: ## - ## .. code-block: + ## .. code-block:: ## ## let ## total = repeat(5, 3) ## assert total == @[5, 5, 5] result = newSeq[T](n) - for i in 0..<n: + for i in 0 ..< n: result[i] = x -proc deduplicate*[T](seq1: seq[T]): seq[T] = +proc deduplicate*[T](s: openArray[T]): seq[T] = ## Returns a new sequence without duplicates. ## + ## Example: + ## ## .. code-block:: ## let ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] @@ -87,17 +107,17 @@ proc deduplicate*[T](seq1: seq[T]): seq[T] = ## assert unique1 == @[1, 3, 4, 2, 8] ## assert unique2 == @["a", "c", "d"] result = @[] - for itm in items(seq1): + for itm in items(s): if not result.contains(itm): result.add(itm) -{.deprecated: [distnct: deduplicate].} - -proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] = - ## Returns a new sequence with a combination of the two input sequences. +proc zip*[S, T](s1: openArray[S], s2: openArray[T]): seq[tuple[a: S, b: T]] = + ## Returns a new sequence with a combination of the two input containers. ## ## For convenience you can access the returned tuples through the named - ## fields `a` and `b`. If one sequence is shorter, the remaining items in the - ## longer sequence are discarded. Example: + ## fields `a` and `b`. If one container is shorter, the remaining items in + ## the longer container are discarded. + ## + ## Example: ## ## .. code-block:: ## let @@ -110,15 +130,16 @@ proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] = ## assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] ## assert zip1[2].b == 4 ## assert zip2[2].b == "three" - var m = min(seq1.len, seq2.len) + var m = min(s1.len, s2.len) newSeq(result, m) - for i in 0 .. m-1: result[i] = (seq1[i], seq2[i]) + for i in 0 ..< m: + result[i] = (s1[i], s2[i]) proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = ## Splits and distributes a sequence `s` into `num` sub sequences. ## ## Returns a sequence of `num` sequences. For some input values this is the - ## inverse of the `concat <#concat>`_ proc. The proc will assert in debug + ## inverse of the `concat <#concat>`_ proc. The proc will assert in debug ## builds if `s` is nil or `num` is less than one, and will likely crash on ## release builds. The input sequence `s` can be empty, which will produce ## `num` empty sequences. @@ -159,48 +180,52 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = # Use an algorithm which overcounts the stride and minimizes reading limits. if extra > 0: inc(stride) - for i in 0 .. <num: + for i in 0 ..< num: result[i] = newSeq[T]() - for g in first .. <min(s.len, first + stride): + for g in first ..< min(s.len, first + stride): result[i].add(s[g]) first += stride else: # Use an undercounting algorithm which *adds* the remainder each iteration. - for i in 0 .. <num: + for i in 0 ..< num: last = first + stride if extra > 0: extra -= 1 inc(last) result[i] = newSeq[T]() - for g in first .. <last: + for g in first ..< last: result[i].add(s[g]) first = last - -proc map*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): +proc map*[T, S](s: openArray[T], op: proc (x: T): S {.closure.}): seq[S]{.inline.} = ## Returns a new sequence with the results of `op` applied to every item in - ## `data`. + ## the container `s`. ## ## Since the input is not modified you can use this version of ``map`` to - ## transform the type of the elements in the input sequence. Example: + ## transform the type of the elements in the input container. + ## + ## Example: ## ## .. code-block:: nim ## let ## a = @[1, 2, 3, 4] ## b = map(a, proc(x: int): string = $x) ## assert b == @["1", "2", "3", "4"] - newSeq(result, data.len) - for i in 0..data.len-1: result[i] = op(data[i]) + newSeq(result, s.len) + for i in 0 ..< s.len: + result[i] = op(s[i]) -proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) +proc map*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) {.deprecated.} = - ## Applies `op` to every item in `data` modifying it directly. + ## Applies `op` to every item in `s` modifying it directly. ## ## Note that this version of ``map`` requires your input and output types to - ## be the same, since they are modified in-place. Example: + ## be the same, since they are modified in-place. + ## + ## Example: ## ## .. code-block:: nim ## var a = @["1", "2", "3", "4"] @@ -210,15 +235,16 @@ proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) ## echo repr(a) ## # --> ["142", "242", "342", "442"] ## **Deprecated since version 0.12.0:** Use the ``apply`` proc instead. - for i in 0..data.len-1: op(data[i]) + for i in 0 ..< s.len: op(s[i]) -proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.}) +proc apply*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) {.inline.} = - ## Applies `op` to every item in `data` modifying it directly. + ## Applies `op` to every item in `s` modifying it directly. ## ## Note that this requires your input and output types to ## be the same, since they are modified in-place. ## The parameter function takes a ``var T`` type parameter. + ## ## Example: ## ## .. code-block:: nim @@ -229,15 +255,16 @@ proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.}) ## echo repr(a) ## # --> ["142", "242", "342", "442"] ## - for i in 0..data.len-1: op(data[i]) + for i in 0 ..< s.len: op(s[i]) -proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.}) +proc apply*[T](s: var openArray[T], op: proc (x: T): T {.closure.}) {.inline.} = - ## Applies `op` to every item in `data` modifying it directly. + ## Applies `op` to every item in `s` modifying it directly. ## ## Note that this requires your input and output types to ## be the same, since they are modified in-place. ## The parameter function takes and returns a ``T`` type variable. + ## ## Example: ## ## .. code-block:: nim @@ -248,11 +275,10 @@ proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.}) ## echo repr(a) ## # --> ["142", "242", "342", "442"] ## - for i in 0..data.len-1: data[i] = op(data[i]) - + for i in 0 ..< s.len: s[i] = op(s[i]) -iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = - ## Iterates through a sequence and yields every item that fulfills the +iterator filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): T = + ## Iterates through a container and yields every item that fulfills the ## predicate. ## ## Example: @@ -262,11 +288,11 @@ iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): ## echo($n) ## # echoes 4, 8, 4 in separate lines - for i in 0..<seq1.len: - if pred(seq1[i]): - yield seq1[i] + for i in 0 ..< s.len: + if pred(s[i]): + yield s[i] -proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] +proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] {.inline.} = ## Returns a new sequence with all the items that fulfilled the predicate. ## @@ -280,11 +306,11 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] ## assert f1 == @["red", "black"] ## assert f2 == @["yellow"] result = newSeq[T]() - for i in 0..<seq1.len: - if pred(seq1[i]): - result.add(seq1[i]) + for i in 0 ..< s.len: + if pred(s[i]): + result.add(s[i]) -proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) +proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) {.inline.} = ## Keeps the items in the passed sequence if they fulfilled the predicate. ## Same as the ``filter`` proc, but modifies the sequence directly. @@ -296,12 +322,12 @@ proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) ## keepIf(floats, proc(x: float): bool = x > 10) ## assert floats == @[13.0, 12.5, 10.1] var pos = 0 - for i in 0 .. <len(seq1): - if pred(seq1[i]): + for i in 0 ..< len(s): + if pred(s[i]): if pos != i: - shallowCopy(seq1[pos], seq1[i]) + shallowCopy(s[pos], s[i]) inc(pos) - setLen(seq1, pos) + setLen(s, pos) proc delete*[T](s: var seq[T]; first, last: Natural) = ## Deletes in `s` the items at position `first` .. `last`. This modifies @@ -354,11 +380,12 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = inc(j) -template filterIt*(seq1, pred: untyped): untyped = +template filterIt*(s, pred: untyped): untyped = ## Returns a new sequence with all the items that fulfilled the predicate. ## ## Unlike the `proc` version, the predicate needs to be an expression using ## the ``it`` variable for testing, like: ``filterIt("abcxyz", it == 'x')``. + ## ## Example: ## ## .. code-block:: @@ -368,8 +395,8 @@ template filterIt*(seq1, pred: untyped): untyped = ## notAcceptable = filterIt(temperatures, it > 50 or it < -10) ## assert acceptable == @[-2.0, 24.5, 44.31] ## assert notAcceptable == @[-272.15, 99.9, -113.44] - var result = newSeq[type(seq1[0])]() - for it {.inject.} in items(seq1): + var result = newSeq[type(s[0])]() + for it {.inject.} in items(s): if pred: result.add(it) result @@ -378,6 +405,7 @@ template keepItIf*(varSeq: seq, pred: untyped) = ## ## Unlike the `proc` version, the predicate needs to be an expression using ## the ``it`` variable for testing, like: ``keepItIf("abcxyz", it == 'x')``. + ## ## Example: ## ## .. code-block:: @@ -385,7 +413,7 @@ template keepItIf*(varSeq: seq, pred: untyped) = ## keepItIf(candidates, it.len == 3 and it[0] == 'b') ## assert candidates == @["bar", "baz"] var pos = 0 - for i in 0 .. <len(varSeq): + for i in 0 ..< len(varSeq): let it {.inject.} = varSeq[i] if pred: if pos != i: @@ -393,8 +421,8 @@ template keepItIf*(varSeq: seq, pred: untyped) = inc(pos) setLen(varSeq, pos) -proc all*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = - ## Iterates through a sequence and checks if every item fulfills the +proc all*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = + ## Iterates through a container and checks if every item fulfills the ## predicate. ## ## Example: @@ -403,12 +431,12 @@ proc all*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = ## let numbers = @[1, 4, 5, 8, 9, 7, 4] ## assert all(numbers, proc (x: int): bool = return x < 10) == true ## assert all(numbers, proc (x: int): bool = return x < 9) == false - for i in seq1: + for i in s: if not pred(i): return false return true -template allIt*(seq1, pred: untyped): bool = +template allIt*(s, pred: untyped): bool = ## Checks if every item fulfills the predicate. ## ## Example: @@ -418,14 +446,14 @@ template allIt*(seq1, pred: untyped): bool = ## assert allIt(numbers, it < 10) == true ## assert allIt(numbers, it < 9) == false var result = true - for it {.inject.} in items(seq1): + for it {.inject.} in items(s): if not pred: result = false break result -proc any*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = - ## Iterates through a sequence and checks if some item fulfills the +proc any*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = + ## Iterates through a container and checks if some item fulfills the ## predicate. ## ## Example: @@ -434,12 +462,12 @@ proc any*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = ## let numbers = @[1, 4, 5, 8, 9, 7, 4] ## assert any(numbers, proc (x: int): bool = return x > 8) == true ## assert any(numbers, proc (x: int): bool = return x > 9) == false - for i in seq1: + for i in s: if pred(i): return true return false -template anyIt*(seq1, pred: untyped): bool = +template anyIt*(s, pred: untyped): bool = ## Checks if some item fulfills the predicate. ## ## Example: @@ -449,7 +477,7 @@ template anyIt*(seq1, pred: untyped): bool = ## assert anyIt(numbers, it > 8) == true ## assert anyIt(numbers, it > 9) == false var result = false - for it {.inject.} in items(seq1): + for it {.inject.} in items(s): if pred: result = true break @@ -493,7 +521,9 @@ template foldl*(sequence, operation: untyped): untyped = ## variables ``a`` and ``b`` for each step of the fold. Since this is a left ## fold, for non associative binary operations like subtraction think that ## the sequence of numbers 1, 2 and 3 will be parenthesized as (((1) - 2) - - ## 3). Example: + ## 3). + ## + ## Example: ## ## .. code-block:: ## let @@ -527,6 +557,7 @@ template foldl*(sequence, operation, first): untyped = ## The ``operation`` parameter should be an expression which uses the variables ## ``a`` and ``b`` for each step of the fold. The ``first`` parameter is the ## start value (the first ``a``) and therefor defines the type of the result. + ## ## Example: ## ## .. code-block:: @@ -555,7 +586,9 @@ template foldr*(sequence, operation: untyped): untyped = ## variables ``a`` and ``b`` for each step of the fold. Since this is a right ## fold, for non associative binary operations like subtraction think that ## the sequence of numbers 1, 2 and 3 will be parenthesized as (1 - (2 - - ## (3))). Example: + ## (3))). + ## + ## Example: ## ## .. code-block:: ## let @@ -580,13 +613,15 @@ template foldr*(sequence, operation: untyped): untyped = result = operation result -template mapIt*(seq1, typ, op: untyped): untyped = +template mapIt*(s, typ, op: untyped): untyped = ## Convenience template around the ``map`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an ## expression. You also need to pass as `typ` the type of the expression, ## since the new returned sequence can have a different type than the - ## original. Example: + ## original. + ## + ## Example: ## ## .. code-block:: ## let @@ -596,16 +631,18 @@ template mapIt*(seq1, typ, op: untyped): untyped = ## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)`` ## template instead. var result: seq[typ] = @[] - for it {.inject.} in items(seq1): + for it {.inject.} in items(s): result.add(op) result -template mapIt*(seq1, op: untyped): untyped = +template mapIt*(s, op: untyped): untyped = ## Convenience template around the ``map`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an - ## expression. Example: + ## expression. + ## + ## Example: ## ## .. code-block:: ## let @@ -614,19 +651,19 @@ template mapIt*(seq1, op: untyped): untyped = ## assert strings == @["4", "8", "12", "16"] type outType = type(( block: - var it{.inject.}: type(items(seq1)); + var it{.inject.}: type(items(s)); op)) var result: seq[outType] - when compiles(seq1.len): - let s = seq1 + when compiles(s.len): + let t = s var i = 0 result = newSeq[outType](s.len) - for it {.inject.} in s: + for it {.inject.} in t: result[i] = op i += 1 else: result = @[] - for it {.inject.} in seq1: + for it {.inject.} in s: result.add(op) result @@ -635,20 +672,23 @@ template applyIt*(varSeq, op: untyped) = ## ## The template injects the ``it`` variable which you can use directly in an ## expression. The expression has to return the same type as the sequence you - ## are mutating. Example: + ## are mutating. + ## + ## Example: ## ## .. code-block:: ## var nums = @[1, 2, 3, 4] ## nums.applyIt(it * 3) ## assert nums[0] + nums[3] == 15 - for i in 0 .. <varSeq.len: + for i in 0 ..< varSeq.len: let it {.inject.} = varSeq[i] varSeq[i] = op - template newSeqWith*(len: int, init: untyped): untyped = - ## creates a new sequence, calling `init` to initialize each value. Example: + ## creates a new sequence, calling `init` to initialize each value. + ## + ## Example: ## ## .. code-block:: ## var seq2D = newSeqWith(20, newSeq[bool](10)) @@ -660,10 +700,56 @@ template newSeqWith*(len: int, init: untyped): untyped = ## var seqRand = newSeqWith(20, random(10)) ## echo seqRand var result = newSeq[type(init)](len) - for i in 0 .. <len: + for i in 0 ..< len: result[i] = init result +proc mapLitsImpl(constructor: NimNode; op: NimNode; nested: bool; + filter = nnkLiterals): NimNode = + if constructor.kind in filter: + result = newNimNode(nnkCall, lineInfoFrom=constructor) + result.add op + result.add constructor + else: + result = newNimNode(constructor.kind, lineInfoFrom=constructor) + for v in constructor: + if nested or v.kind in filter: + result.add mapLitsImpl(v, op, nested, filter) + else: + result.add v + +macro mapLiterals*(constructor, op: untyped; + nested = true): untyped = + ## applies ``op`` to each of the **atomic** literals like ``3`` + ## or ``"abc"`` in the specified ``constructor`` AST. This can + ## be used to map every array element to some target type: + ## + ## Example: + ## + ## .. code-block:: + ## let x = mapLiterals([0.1, 1.2, 2.3, 3.4], int) + ## doAssert x is array[4, int] + ## + ## Short notation for: + ## + ## .. code-block:: + ## let x = [int(0.1), int(1.2), int(2.3), int(3.4)] + ## + ## If ``nested`` is true, the literals are replaced everywhere + ## in the ``constructor`` AST, otherwise only the first level + ## is considered: + ## + ## .. code-block:: + ## mapLiterals((1, ("abc"), 2), float, nested=false) + ## + ## Produces:: + ## + ## (float(1), ("abc"), float(2)) + ## + ## There are no constraints for the ``constructor`` AST, it + ## works for nested tuples of arrays of sets etc. + result = mapLitsImpl(constructor, op, nested.boolVal) + when isMainModule: import strutils block: # concat test @@ -674,45 +760,178 @@ when isMainModule: total = concat(s1, s2, s3) assert total == @[1, 2, 3, 4, 5, 6, 7] - block: # duplicates test + block: # count test + let + s1 = @[1, 2, 3, 2] + s2 = @['a', 'b', 'x', 'a'] + a1 = [1, 2, 3, 2] + a2 = ['a', 'b', 'x', 'a'] + r0 = count(s1, 0) + r1 = count(s1, 1) + r2 = count(s1, 2) + r3 = count(s2, 'y') + r4 = count(s2, 'x') + r5 = count(s2, 'a') + ar0 = count(a1, 0) + ar1 = count(a1, 1) + ar2 = count(a1, 2) + ar3 = count(a2, 'y') + ar4 = count(a2, 'x') + ar5 = count(a2, 'a') + assert r0 == 0 + assert r1 == 1 + assert r2 == 2 + assert r3 == 0 + assert r4 == 1 + assert r5 == 2 + assert ar0 == 0 + assert ar1 == 1 + assert ar2 == 2 + assert ar3 == 0 + assert ar4 == 1 + assert ar5 == 2 + + block: # cycle tests + let + a = @[1, 2, 3] + b: seq[int] = @[] + c = [1, 2, 3] + + doAssert a.cycle(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + doAssert a.cycle(0) == @[] + #doAssert a.cycle(-1) == @[] # will not compile! + doAssert b.cycle(3) == @[] + doAssert c.cycle(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + doAssert c.cycle(0) == @[] + + block: # repeat tests + assert repeat(10, 5) == @[10, 10, 10, 10, 10] + assert repeat(@[1,2,3], 2) == @[@[1,2,3], @[1,2,3]] + assert repeat([1,2,3], 2) == @[[1,2,3], [1,2,3]] + + block: # deduplicates test let dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] dup2 = @["a", "a", "c", "d", "d"] + dup3 = [1, 1, 3, 4, 2, 2, 8, 1, 4] + dup4 = ["a", "a", "c", "d", "d"] unique1 = deduplicate(dup1) unique2 = deduplicate(dup2) + unique3 = deduplicate(dup3) + unique4 = deduplicate(dup4) assert unique1 == @[1, 3, 4, 2, 8] assert unique2 == @["a", "c", "d"] + assert unique3 == @[1, 3, 4, 2, 8] + assert unique4 == @["a", "c", "d"] block: # zip test let short = @[1, 2, 3] long = @[6, 5, 4, 3, 2, 1] words = @["one", "two", "three"] + ashort = [1, 2, 3] + along = [6, 5, 4, 3, 2, 1] + awords = ["one", "two", "three"] zip1 = zip(short, long) zip2 = zip(short, words) + zip3 = zip(ashort, along) + zip4 = zip(ashort, awords) + zip5 = zip(ashort, words) assert zip1 == @[(1, 6), (2, 5), (3, 4)] assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip3 == @[(1, 6), (2, 5), (3, 4)] + assert zip4 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip5 == @[(1, "one"), (2, "two"), (3, "three")] assert zip1[2].b == 4 assert zip2[2].b == "three" + assert zip3[2].b == 4 + assert zip4[2].b == "three" + assert zip5[2].b == "three" + + block: # distribute tests + let numbers = @[1, 2, 3, 4, 5, 6, 7] + doAssert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] + doAssert numbers.distribute(6)[0] == @[1, 2] + doAssert numbers.distribute(6)[5] == @[7] + let a = @[1, 2, 3, 4, 5, 6, 7] + doAssert a.distribute(1, true) == @[@[1, 2, 3, 4, 5, 6, 7]] + doAssert a.distribute(1, false) == @[@[1, 2, 3, 4, 5, 6, 7]] + doAssert a.distribute(2, true) == @[@[1, 2, 3, 4], @[5, 6, 7]] + doAssert a.distribute(2, false) == @[@[1, 2, 3, 4], @[5, 6, 7]] + doAssert a.distribute(3, true) == @[@[1, 2, 3], @[4, 5], @[6, 7]] + doAssert a.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] + doAssert a.distribute(4, true) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] + doAssert a.distribute(4, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] + doAssert a.distribute(5, true) == @[@[1, 2], @[3, 4], @[5], @[6], @[7]] + doAssert a.distribute(5, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7], @[]] + doAssert a.distribute(6, true) == @[@[1, 2], @[3], @[4], @[5], @[6], @[7]] + doAssert a.distribute(6, false) == @[ + @[1, 2], @[3, 4], @[5, 6], @[7], @[], @[]] + doAssert a.distribute(8, false) == a.distribute(8, true) + doAssert a.distribute(90, false) == a.distribute(90, true) + var b = @[0] + for f in 1 .. 25: b.add(f) + doAssert b.distribute(5, true)[4].len == 5 + doAssert b.distribute(5, false)[4].len == 2 + + block: # map test + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] + m1 = map(numbers, proc(x: int): int = 2*x) + m2 = map(anumbers, proc(x: int): int = 2*x) + assert m1 == @[2, 8, 10, 16, 18, 14, 8] + assert m2 == @[2, 8, 10, 16, 18, 14, 8] + + block: # apply test + var a = @["1", "2", "3", "4"] + apply(a, proc(x: var string) = x &= "42") + assert a == @["142", "242", "342", "442"] block: # filter proc test let colors = @["red", "yellow", "black"] + acolors = ["red", "yellow", "black"] f1 = filter(colors, proc(x: string): bool = x.len < 6) f2 = filter(colors) do (x: string) -> bool : x.len > 5 + f3 = filter(acolors, proc(x: string): bool = x.len < 6) + f4 = filter(acolors) do (x: string) -> bool : x.len > 5 assert f1 == @["red", "black"] assert f2 == @["yellow"] + assert f3 == @["red", "black"] + assert f4 == @["yellow"] block: # filter iterator test let numbers = @[1, 4, 5, 8, 9, 7, 4] + let anumbers = [1, 4, 5, 8, 9, 7, 4] assert toSeq(filter(numbers, proc (x: int): bool = x mod 2 == 0)) == @[4, 8, 4] + assert toSeq(filter(anumbers, proc (x: int): bool = x mod 2 == 0)) == + @[4, 8, 4] block: # keepIf test var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] keepIf(floats, proc(x: float): bool = x > 10) assert floats == @[13.0, 12.5, 10.1] + block: # delete tests + let outcome = @[1,1,1,1,1,1,1,1] + var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.delete(3, 8) + assert outcome == dest, """\ + Deleting range 3-9 from [1,1,1,2,2,2,2,2,2,1,1,1,1,1] + is [1,1,1,1,1,1,1,1]""" + + block: # insert tests + var dest = @[1,1,1,1,1,1,1,1] + let + src = @[2,2,2,2,2,2] + outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.insert(src, 3) + assert dest == outcome, """\ + Inserting [2,2,2,2,2,2] into [1,1,1,1,1,1,1,1] + at 3 is [1,1,1,2,2,2,2,2,2,1,1,1,1,1]""" + block: # filterIt test let temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] @@ -726,37 +945,49 @@ when isMainModule: keepItIf(candidates, it.len == 3 and it[0] == 'b') assert candidates == @["bar", "baz"] - block: # any - let - numbers = @[1, 4, 5, 8, 9, 7, 4] - len0seq : seq[int] = @[] - assert any(numbers, proc (x: int): bool = return x > 8) == true - assert any(numbers, proc (x: int): bool = return x > 9) == false - assert any(len0seq, proc (x: int): bool = return true) == false - - block: # anyIt - let - numbers = @[1, 4, 5, 8, 9, 7, 4] - len0seq : seq[int] = @[] - assert anyIt(numbers, it > 8) == true - assert anyIt(numbers, it > 9) == false - assert anyIt(len0seq, true) == false - block: # all let numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] len0seq : seq[int] = @[] assert all(numbers, proc (x: int): bool = return x < 10) == true assert all(numbers, proc (x: int): bool = return x < 9) == false assert all(len0seq, proc (x: int): bool = return false) == true + assert all(anumbers, proc (x: int): bool = return x < 10) == true + assert all(anumbers, proc (x: int): bool = return x < 9) == false block: # allIt let numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] len0seq : seq[int] = @[] assert allIt(numbers, it < 10) == true assert allIt(numbers, it < 9) == false assert allIt(len0seq, false) == true + assert allIt(anumbers, it < 10) == true + assert allIt(anumbers, it < 9) == false + + block: # any + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert any(numbers, proc (x: int): bool = return x > 8) == true + assert any(numbers, proc (x: int): bool = return x > 9) == false + assert any(len0seq, proc (x: int): bool = return true) == false + assert any(anumbers, proc (x: int): bool = return x > 8) == true + assert any(anumbers, proc (x: int): bool = return x > 9) == false + + block: # anyIt + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + anumbers = [1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert anyIt(numbers, it > 8) == true + assert anyIt(numbers, it > 9) == false + assert anyIt(len0seq, true) == false + assert anyIt(anumbers, it > 8) == true + assert anyIt(anumbers, it > 9) == false block: # toSeq test let @@ -792,56 +1023,13 @@ when isMainModule: assert multiplication == 495, "Multiplication is (5*(9*(11)))" assert concatenation == "nimiscool" - block: # delete tests - let outcome = @[1,1,1,1,1,1,1,1] - var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - dest.delete(3, 8) - assert outcome == dest, """\ - Deleting range 3-9 from [1,1,1,2,2,2,2,2,2,1,1,1,1,1] - is [1,1,1,1,1,1,1,1]""" - - block: # insert tests - var dest = @[1,1,1,1,1,1,1,1] - let - src = @[2,2,2,2,2,2] - outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - dest.insert(src, 3) - assert dest == outcome, """\ - Inserting [2,2,2,2,2,2] into [1,1,1,1,1,1,1,1] - at 3 is [1,1,1,2,2,2,2,2,2,1,1,1,1,1]""" - block: # mapIt tests var nums = @[1, 2, 3, 4] strings = nums.mapIt($(4 * it)) nums.applyIt(it * 3) assert nums[0] + nums[3] == 15 - - block: # distribute tests - let numbers = @[1, 2, 3, 4, 5, 6, 7] - doAssert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] - doAssert numbers.distribute(6)[0] == @[1, 2] - doAssert numbers.distribute(6)[5] == @[7] - let a = @[1, 2, 3, 4, 5, 6, 7] - doAssert a.distribute(1, true) == @[@[1, 2, 3, 4, 5, 6, 7]] - doAssert a.distribute(1, false) == @[@[1, 2, 3, 4, 5, 6, 7]] - doAssert a.distribute(2, true) == @[@[1, 2, 3, 4], @[5, 6, 7]] - doAssert a.distribute(2, false) == @[@[1, 2, 3, 4], @[5, 6, 7]] - doAssert a.distribute(3, true) == @[@[1, 2, 3], @[4, 5], @[6, 7]] - doAssert a.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] - doAssert a.distribute(4, true) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] - doAssert a.distribute(4, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] - doAssert a.distribute(5, true) == @[@[1, 2], @[3, 4], @[5], @[6], @[7]] - doAssert a.distribute(5, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7], @[]] - doAssert a.distribute(6, true) == @[@[1, 2], @[3], @[4], @[5], @[6], @[7]] - doAssert a.distribute(6, false) == @[ - @[1, 2], @[3, 4], @[5, 6], @[7], @[], @[]] - doAssert a.distribute(8, false) == a.distribute(8, true) - doAssert a.distribute(90, false) == a.distribute(90, true) - var b = @[0] - for f in 1 .. 25: b.add(f) - doAssert b.distribute(5, true)[4].len == 5 - doAssert b.distribute(5, false)[4].len == 2 + assert strings[2] == "12" block: # newSeqWith tests var seq2D = newSeqWith(4, newSeq[bool](2)) @@ -850,19 +1038,11 @@ when isMainModule: seq2D[0][1] = true doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]] - block: # cycle tests - let - a = @[1, 2, 3] - b: seq[int] = @[] - - doAssert a.cycle(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] - doAssert a.cycle(0) == @[] - #doAssert a.cycle(-1) == @[] # will not compile! - doAssert b.cycle(3) == @[] - - block: # repeat tests - assert repeat(10, 5) == @[10, 10, 10, 10, 10] - assert repeat(@[1,2,3], 2) == @[@[1,2,3], @[1,2,3]] + block: # mapLiterals tests + let x = mapLiterals([0.1, 1.2, 2.3, 3.4], int) + doAssert x is array[4, int] + doAssert mapLiterals((1, ("abc"), 2), float, nested=false) == (float(1), "abc", float(2)) + doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2") when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index dbdf17514..9e9152fc8 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -46,7 +46,7 @@ template default[T](t: typedesc[T]): T = var v: T v -proc clear*[A](s: var HashSet[A]) = +proc clear*[A](s: var HashSet[A]) = ## Clears the HashSet back to an empty state, without shrinking ## any of the existing storage. O(n) where n is the size of the hash bucket. s.counter = 0 @@ -406,7 +406,7 @@ template dollarImpl() {.dirty.} = result = "{" for key in items(s): if result.len > 1: result.add(", ") - result.add($key) + result.addQuoted(key) result.add("}") proc `$`*[A](s: HashSet[A]): string = @@ -610,7 +610,7 @@ type {.deprecated: [TOrderedSet: OrderedSet].} -proc clear*[A](s: var OrderedSet[A]) = +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. s.counter = 0 @@ -911,13 +911,13 @@ proc `==`*[A](s, t: OrderedSet[A]): bool = ## Equality for ordered sets. if s.counter != t.counter: return false var h = s.first - var g = s.first + var g = t.first var compared = 0 while h >= 0 and g >= 0: var nxh = s.data[h].next var nxg = t.data[g].next - if isFilled(s.data[h].hcode) and isFilled(s.data[g].hcode): - if s.data[h].key == s.data[g].key: + if isFilled(s.data[h].hcode) and isFilled(t.data[g].hcode): + if s.data[h].key == t.data[g].key: inc compared else: return false @@ -1120,6 +1120,22 @@ when isMainModule and not defined(release): assert s.missingOrExcl(4) == true assert s.missingOrExcl(6) == false + block orderedSetEquality: + type pair = tuple[a, b: int] + + var aa = initOrderedSet[pair]() + var bb = initOrderedSet[pair]() + + var x = (a:1,b:2) + var y = (a:3,b:4) + + aa.incl(x) + aa.incl(y) + + bb.incl(x) + bb.incl(y) + assert aa == bb + when not defined(testing): echo "Micro tests run successfully." diff --git a/lib/pure/collections/sharedlist.nim b/lib/pure/collections/sharedlist.nim index e93ceb02f..b3e677b79 100644 --- a/lib/pure/collections/sharedlist.nim +++ b/lib/pure/collections/sharedlist.nim @@ -73,10 +73,10 @@ proc add*[A](x: var SharedList[A]; y: A) = node.d[node.dataLen] = y inc(node.dataLen) -proc initSharedList*[A](): SharedList[A] = - initLock result.lock - result.head = nil - result.tail = nil +proc init*[A](t: var SharedList[A]) = + initLock t.lock + t.head = nil + t.tail = nil proc clear*[A](t: var SharedList[A]) = withLock(t): @@ -92,4 +92,11 @@ proc deinitSharedList*[A](t: var SharedList[A]) = clear(t) deinitLock t.lock +proc initSharedList*[A](): SharedList[A] {.deprecated.} = + ## Deprecated. Use `init` instead. + ## This is not posix compliant, may introduce undefined behavior. + initLock result.lock + result.head = nil + result.tail = nil + {.pop.} diff --git a/lib/pure/collections/sharedstrings.nim b/lib/pure/collections/sharedstrings.nim index a9e194fb4..7e9de4b73 100644 --- a/lib/pure/collections/sharedstrings.nim +++ b/lib/pure/collections/sharedstrings.nim @@ -55,7 +55,7 @@ proc `[]=`*(s: var SharedString; i: Natural; value: char) = if i < s.len: s.buffer.data[i+s.first] = value else: raise newException(IndexError, "index out of bounds") -proc `[]`*(s: SharedString; ab: Slice[int]): SharedString = +proc `[]`*(s: SharedString; ab: HSlice[int, int]): SharedString = #incRef(src.buffer) if ab.a < s.len: result.buffer = s.buffer @@ -87,10 +87,10 @@ proc newSharedString*(s: string): SharedString = result.len = len when declared(atomicLoadN): - template load(x): expr = atomicLoadN(addr x, ATOMIC_SEQ_CST) + template load(x): untyped = atomicLoadN(addr x, ATOMIC_SEQ_CST) else: # XXX Fixme - template load(x): expr = x + template load(x): untyped = x proc add*(s: var SharedString; t: cstring; len: Natural) = if len == 0: return diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index fc50ea41c..4f311af87 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -183,6 +183,7 @@ proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) = proc add*[A, B](t: var SharedTable[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! withLock t: addImpl(enlarge) @@ -191,19 +192,29 @@ proc del*[A, B](t: var SharedTable[A, B], key: A) = withLock t: delImpl() -proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] = +proc init*[A, B](t: var SharedTable[A, B], initialSize=64) = ## creates a new hash table that is empty. ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the ## `math <math.html>`_ module or the ``rightSize`` proc from this module. assert isPowerOfTwo(initialSize) - result.counter = 0 - result.dataLen = initialSize - result.data = cast[KeyValuePairSeq[A, B]](allocShared0( + t.counter = 0 + t.dataLen = initialSize + t.data = cast[KeyValuePairSeq[A, B]](allocShared0( sizeof(KeyValuePair[A, B]) * initialSize)) - initLock result.lock + initLock t.lock proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) = deallocShared(t.data) deinitLock t.lock + +proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] {.deprecated.} = + ## Deprecated. Use `init` instead. + ## This is not posix compliant, may introduce undefined behavior. + assert isPowerOfTwo(initialSize) + result.counter = 0 + result.dataLen = initialSize + result.data = cast[KeyValuePairSeq[A, B]](allocShared0( + sizeof(KeyValuePair[A, B]) * initialSize)) + initLock result.lock diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index eec98fcaf..9a5bffcef 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -149,7 +149,7 @@ template delImpl() {.dirty.} = delImplIdx(t, i) template clearImpl() {.dirty.} = - for i in 0 .. <t.data.len: + for i in 0 ..< t.data.len: when compiles(t.data[i].hcode): # CountTable records don't contain a hcode t.data[i].hcode = 0 t.data[i].key = default(type(t.data[i].key)) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 01a42efab..777beabc3 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -308,6 +308,7 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = proc add*[A, B](t: var Table[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! addImpl(enlarge) proc len*[A, B](t: TableRef[A, B]): int = @@ -337,9 +338,9 @@ template dollarImpl(): untyped {.dirty.} = result = "{" for key, val in pairs(t): if result.len > 1: result.add(", ") - result.add($key) + result.addQuoted(key) result.add(": ") - result.add($val) + result.addQuoted(val) result.add("}") proc `$`*[A, B](t: Table[A, B]): string = @@ -430,6 +431,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = proc add*[A, B](t: TableRef[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! t[].add(key, val) proc del*[A, B](t: TableRef[A, B], key: A) = @@ -604,6 +606,7 @@ proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! addImpl(enlarge) proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = @@ -770,6 +773,7 @@ proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + ## This can introduce duplicate keys into the table! t[].add(key, val) proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = @@ -962,9 +966,10 @@ proc initCountTable*[A](initialSize=64): CountTable[A] = newSeq(result.data, initialSize) proc toCountTable*[A](keys: openArray[A]): CountTable[A] = - ## creates a new count table with every key in `keys` having a count of 1. + ## creates a new count table with every key in `keys` having a count + ## of how many times it occurs in `keys`. result = initCountTable[A](rightSize(keys.len)) - for key in items(keys): result[key] = 1 + for key in items(keys): result.inc key proc `$`*[A](t: CountTable[A]): string = ## The `$` operator for count tables. @@ -989,9 +994,10 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) = proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = ## returns the (key,val)-pair with the smallest `val`. Efficiency: O(n) assert t.len > 0 - var minIdx = 0 - for h in 1..high(t.data): - if t.data[h].val > 0 and t.data[minIdx].val > t.data[h].val: minIdx = h + var minIdx = -1 + for h in 0..high(t.data): + if t.data[h].val > 0 and (minIdx == -1 or t.data[minIdx].val > t.data[h].val): + minIdx = h result.key = t.data[minIdx].key result.val = t.data[minIdx].val @@ -1325,3 +1331,7 @@ when isMainModule: assert((a == b) == true) assert((b == a) == true) + block: # CountTable.smallest + var t = initCountTable[int]() + for v in items([0, 0, 5, 5, 5]): t.inc(v) + doAssert t.smallest == (0, 2) diff --git a/lib/pure/concurrency/cpuinfo.nim b/lib/pure/concurrency/cpuinfo.nim index 603fee080..f01488811 100644 --- a/lib/pure/concurrency/cpuinfo.nim +++ b/lib/pure/concurrency/cpuinfo.nim @@ -45,8 +45,25 @@ proc countProcessors*(): int {.rtl, extern: "ncpi$1".} = ## returns the numer of the processors/cores the machine has. ## Returns 0 if it cannot be detected. when defined(windows): - var x = getEnv("NUMBER_OF_PROCESSORS") - if x.len > 0: result = parseInt(x.string) + type + SYSTEM_INFO {.final, pure.} = object + u1: int32 + dwPageSize: int32 + lpMinimumApplicationAddress: pointer + lpMaximumApplicationAddress: pointer + dwActiveProcessorMask: ptr int32 + dwNumberOfProcessors: int32 + dwProcessorType: int32 + dwAllocationGranularity: int32 + wProcessorLevel: int16 + wProcessorRevision: int16 + + proc GetSystemInfo(lpSystemInfo: var SYSTEM_INFO) {.stdcall, dynlib: "kernel32", importc: "GetSystemInfo".} + + var + si: SYSTEM_INFO + GetSystemInfo(si) + result = si.dwNumberOfProcessors elif defined(macosx) or defined(bsd): var mib: array[0..3, cint] diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index 0f23b7e85..a5eaec86e 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -149,7 +149,7 @@ proc selectWorker(w: ptr Worker; fn: WorkerProc; data: pointer): bool = proc cleanFlowVars(w: ptr Worker) = let q = addr(w.q) acquire(q.lock) - for i in 0 .. <q.len: + for i in 0 ..< q.len: GC_unref(cast[RootRef](q.data[i])) #echo "GC_unref" q.len = 0 @@ -401,7 +401,7 @@ proc setup() = gCpus = p currentPoolSize = min(p, MaxThreadPoolSize) readyWorker = addr(workersData[0]) - for i in 0.. <currentPoolSize: activateWorkerThread(i) + for i in 0..<currentPoolSize: activateWorkerThread(i) proc preferSpawn*(): bool = ## Use this proc to determine quickly if a 'spawn' or a direct call is @@ -446,7 +446,7 @@ proc nimSpawn3(fn: WorkerProc; data: pointer) {.compilerProc.} = # implementation of 'spawn' that is used by the code generator. while true: if selectWorker(readyWorker, fn, data): return - for i in 0.. <currentPoolSize: + for i in 0..<currentPoolSize: if selectWorker(addr(workersData[i]), fn, data): return # determine what to do, but keep in mind this is expensive too: @@ -543,7 +543,7 @@ proc sync*() = var toRelease = 0 while true: var allReady = true - for i in 0 .. <currentPoolSize: + for i in 0 ..< currentPoolSize: if not allReady: break allReady = allReady and workersData[i].ready if allReady: break diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index 7d850798c..8f16717ac 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -13,6 +13,15 @@ import strtabs, times proc parseCookies*(s: string): StringTableRef = ## parses cookies into a string table. + ## + ## The proc is meant to parse the Cookie header set by a client, not the + ## "Set-Cookie" header set by servers. + ## + ## Example: + ## + ## .. code-block::Nim + ## doAssert parseCookies("a=1; foo=bar") == {"a": 1, "foo": "bar"}.newStringTable + result = newStringTable(modeCaseInsensitive) var i = 0 while true: @@ -42,7 +51,7 @@ proc setCookie*(key, value: string, domain = "", path = "", if secure: result.add("; Secure") if httpOnly: result.add("; HttpOnly") -proc setCookie*(key, value: string, expires: TimeInfo, +proc setCookie*(key, value: string, expires: DateTime, domain = "", path = "", noName = false, secure = false, httpOnly = false): string = ## Creates a command in the format of @@ -54,9 +63,9 @@ proc setCookie*(key, value: string, expires: TimeInfo, noname, secure, httpOnly) when isMainModule: - var tim = Time(int(getTime()) + 76 * (60 * 60 * 24)) + var tim = fromUnix(getTime().toUnix + 76 * (60 * 60 * 24)) - let cookie = setCookie("test", "value", tim.getGMTime()) + let cookie = setCookie("test", "value", tim.utc) when not defined(testing): echo cookie let start = "Set-Cookie: test=value; Expires=" diff --git a/lib/pure/cstrutils.nim b/lib/pure/cstrutils.nim new file mode 100644 index 000000000..437140892 --- /dev/null +++ b/lib/pure/cstrutils.nim @@ -0,0 +1,79 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module supports helper routines for working with ``cstring`` +## without having to convert ``cstring`` to ``string`` in order to +## save allocations. + +include "system/inclrtl" + +proc toLowerAscii(c: char): char {.inline.} = + if c in {'A'..'Z'}: + result = chr(ord(c) + (ord('a') - ord('A'))) + else: + result = c + +proc startsWith*(s, prefix: cstring): bool {.noSideEffect, + rtl, extern: "csuStartsWith".} = + ## Returns true iff ``s`` starts with ``prefix``. + ## + ## If ``prefix == ""`` true is returned. + var i = 0 + while true: + if prefix[i] == '\0': return true + if s[i] != prefix[i]: return false + inc(i) + +proc endsWith*(s, suffix: cstring): bool {.noSideEffect, + rtl, extern: "csuEndsWith".} = + ## Returns true iff ``s`` ends with ``suffix``. + ## + ## If ``suffix == ""`` true is returned. + let slen = s.len + var i = 0 + var j = slen - len(suffix) + while i+j <% slen: + if s[i+j] != suffix[i]: return false + inc(i) + if suffix[i] == '\0': return true + +proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect, + rtl, extern: "csuCmpIgnoreStyle".} = + ## Compares two strings normalized (i.e. case and + ## underscores do not matter). Returns: + ## + ## | 0 iff a == b + ## | < 0 iff a < b + ## | > 0 iff a > b + var i = 0 + var j = 0 + while true: + while a[i] == '_': inc(i) + while b[j] == '_': inc(j) # BUGFIX: typo + var aa = toLowerAscii(a[i]) + var bb = toLowerAscii(b[j]) + result = ord(aa) - ord(bb) + if result != 0 or aa == '\0': break + inc(i) + inc(j) + +proc cmpIgnoreCase*(a, b: cstring): int {.noSideEffect, + rtl, extern: "csuCmpIgnoreCase".} = + ## Compares two strings in a case insensitive manner. Returns: + ## + ## | 0 iff a == b + ## | < 0 iff a < b + ## | > 0 iff a > b + var i = 0 + while true: + var aa = toLowerAscii(a[i]) + var bb = toLowerAscii(b[i]) + result = ord(aa) - ord(bb) + if result != 0 or aa == '\0': break + inc(i) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 2a6d29933..1a3ab688d 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -22,7 +22,7 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} = case p.kind of nnkPar: - for i in 0 .. <p.len: + for i in 0 ..< p.len: let ident = p[i] var identDefs = newNimNode(nnkIdentDefs) case ident.kind @@ -77,7 +77,7 @@ macro `=>`*(p, b: untyped): untyped = if c[0].kind == nnkIdent and c[0].ident == !"->": var procTy = createProcType(c[1], c[2]) params[0] = procTy[0][0] - for i in 1 .. <procTy[0].len: + for i in 1 ..< procTy[0].len: params.add(procTy[0][i]) else: error("Expected proc type (->) got (" & $c[0].ident & ").") @@ -96,7 +96,7 @@ macro `=>`*(p, b: untyped): untyped = if p[0].kind == nnkIdent and p[0].ident == !"->": var procTy = createProcType(p[1], p[2]) params[0] = procTy[0][0] - for i in 1 .. <procTy[0].len: + for i in 1 ..< procTy[0].len: params.add(procTy[0][i]) else: error("Expected proc type (->) got (" & $p[0].ident & ").") @@ -197,4 +197,4 @@ macro dump*(x: typed): untyped = let s = x.toStrLit let r = quote do: debugEcho `s`, " = ", `x` - return r \ No newline at end of file + return r diff --git a/lib/pure/gentabs.nim b/lib/pure/gentabs.nim deleted file mode 100644 index 928ff8fe0..000000000 --- a/lib/pure/gentabs.nim +++ /dev/null @@ -1,211 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## The ``gentabs`` module implements an efficient hash table that is a -## key-value mapping. The keys are required to be strings, but the values -## may be any Nim or user defined type. This module supports matching -## of keys in case-sensitive, case-insensitive and style-insensitive modes. -## -## **Warning:** This module is deprecated, new code shouldn't use it! - -{.deprecated.} - -import - os, hashes, strutils - -type - GenTableMode* = enum ## describes the table's key matching mode - modeCaseSensitive, ## case sensitive matching of keys - modeCaseInsensitive, ## case insensitive matching of keys - modeStyleInsensitive ## style sensitive matching of keys - - GenKeyValuePair[T] = tuple[key: string, val: T] - GenKeyValuePairSeq[T] = seq[GenKeyValuePair[T]] - GenTable*[T] = object of RootObj - counter: int - data: GenKeyValuePairSeq[T] - mode: GenTableMode - - PGenTable*[T] = ref GenTable[T] ## use this type to declare hash tables - -{.deprecated: [TGenTableMode: GenTableMode, TGenKeyValuePair: GenKeyValuePair, - TGenKeyValuePairSeq: GenKeyValuePairSeq, TGenTable: GenTable].} - -const - growthFactor = 2 - startSize = 64 - - -proc len*[T](tbl: PGenTable[T]): int {.inline.} = - ## returns the number of keys in `tbl`. - result = tbl.counter - -iterator pairs*[T](tbl: PGenTable[T]): tuple[key: string, value: T] = - ## iterates over any (key, value) pair in the table `tbl`. - for h in 0..high(tbl.data): - if not isNil(tbl.data[h].key): - yield (tbl.data[h].key, tbl.data[h].val) - -proc myhash[T](tbl: PGenTable[T], key: string): Hash = - case tbl.mode - of modeCaseSensitive: result = hashes.hash(key) - of modeCaseInsensitive: result = hashes.hashIgnoreCase(key) - of modeStyleInsensitive: result = hashes.hashIgnoreStyle(key) - -proc myCmp[T](tbl: PGenTable[T], a, b: string): bool = - case tbl.mode - of modeCaseSensitive: result = cmp(a, b) == 0 - of modeCaseInsensitive: result = cmpIgnoreCase(a, b) == 0 - of modeStyleInsensitive: result = cmpIgnoreStyle(a, b) == 0 - -proc mustRehash(length, counter: int): bool = - assert(length > counter) - result = (length * 2 < counter * 3) or (length - counter < 4) - -proc newGenTable*[T](mode: GenTableMode): PGenTable[T] = - ## creates a new generic hash table that is empty. - new(result) - result.mode = mode - result.counter = 0 - newSeq(result.data, startSize) - -proc nextTry(h, maxHash: Hash): Hash {.inline.} = - result = ((5 * h) + 1) and maxHash - -proc rawGet[T](tbl: PGenTable[T], key: string): int = - var h: Hash - h = myhash(tbl, key) and high(tbl.data) # start with real hash value - while not isNil(tbl.data[h].key): - if myCmp(tbl, tbl.data[h].key, key): - return h - h = nextTry(h, high(tbl.data)) - result = - 1 - -proc rawInsert[T](tbl: PGenTable[T], data: var GenKeyValuePairSeq[T], - key: string, val: T) = - var h: Hash - h = myhash(tbl, key) and high(data) - while not isNil(data[h].key): - h = nextTry(h, high(data)) - data[h].key = key - data[h].val = val - -proc enlarge[T](tbl: PGenTable[T]) = - var n: GenKeyValuePairSeq[T] - newSeq(n, len(tbl.data) * growthFactor) - for i in countup(0, high(tbl.data)): - if not isNil(tbl.data[i].key): - rawInsert[T](tbl, n, tbl.data[i].key, tbl.data[i].val) - swap(tbl.data, n) - -proc hasKey*[T](tbl: PGenTable[T], key: string): bool = - ## returns true iff `key` is in the table `tbl`. - result = rawGet(tbl, key) >= 0 - -proc `[]`*[T](tbl: PGenTable[T], key: string): T = - ## retrieves the value at ``tbl[key]``. If `key` is not in `tbl`, - ## default(T) is returned and no exception is raised. One can check - ## with ``hasKey`` whether the key exists. - var index = rawGet(tbl, key) - if index >= 0: result = tbl.data[index].val - -proc `[]=`*[T](tbl: PGenTable[T], key: string, val: T) = - ## puts a (key, value)-pair into `tbl`. - var index = rawGet(tbl, key) - if index >= 0: - tbl.data[index].val = val - else: - if mustRehash(len(tbl.data), tbl.counter): enlarge(tbl) - rawInsert(tbl, tbl.data, key, val) - inc(tbl.counter) - - -when isMainModule: - # - # Verify tables of integer values (string keys) - # - var x = newGenTable[int](modeCaseInsensitive) - x["one"] = 1 - x["two"] = 2 - x["three"] = 3 - x["four"] = 4 - x["five"] = 5 - assert(len(x) == 5) # length procedure works - assert(x["one"] == 1) # case-sensitive lookup works - assert(x["ONE"] == 1) # case-insensitive should work for this table - assert(x["one"]+x["two"] == 3) # make sure we're getting back ints - assert(x.hasKey("one")) # hasKey should return 'true' for a key - # of "one"... - assert(not x.hasKey("NOPE")) # ...but key "NOPE" is not in the table. - for k,v in pairs(x): # make sure the 'pairs' iterator works - assert(x[k]==v) - - # - # Verify a table of user-defined types - # - type - MyType = tuple[first, second: string] # a pair of strings - {.deprecated: [TMyType: MyType].} - - var y = newGenTable[MyType](modeCaseInsensitive) # hash table where each - # value is MyType tuple - - #var junk: MyType = ("OK", "Here") - - #echo junk.first, " ", junk.second - - y["Hello"] = ("Hello", "World") - y["Goodbye"] = ("Goodbye", "Everyone") - #y["Hello"] = MyType( ("Hello", "World") ) - #y["Goodbye"] = MyType( ("Goodbye", "Everyone") ) - - assert( not isNil(y["Hello"].first) ) - assert( y["Hello"].first == "Hello" ) - assert( y["Hello"].second == "World" ) - - # - # Verify table of tables - # - var z: PGenTable[ PGenTable[int] ] # hash table where each value is - # a hash table of ints - - z = newGenTable[PGenTable[int]](modeCaseInsensitive) - z["first"] = newGenTable[int](modeCaseInsensitive) - z["first"]["one"] = 1 - z["first"]["two"] = 2 - z["first"]["three"] = 3 - - z["second"] = newGenTable[int](modeCaseInsensitive) - z["second"]["red"] = 10 - z["second"]["blue"] = 20 - - assert(len(z) == 2) # length of outer table - assert(len(z["first"]) == 3) # length of "first" table - assert(len(z["second"]) == 2) # length of "second" table - assert( z["first"]["one"] == 1) # retrieve from first inner table - assert( z["second"]["red"] == 10) # retrieve from second inner table - - when false: - # disabled: depends on hash order: - var output = "" - for k, v in pairs(z): - output.add( "$# ($#) ->\L" % [k,$len(v)] ) - for k2,v2 in pairs(v): - output.add( " $# <-> $#\L" % [k2,$v2] ) - - let expected = unindent """ - first (3) -> - two <-> 2 - three <-> 3 - one <-> 1 - second (2) -> - red <-> 10 - blue <-> 20 - """ - assert output == expected diff --git a/lib/pure/htmlgen.nim b/lib/pure/htmlgen.nim index ad199a215..c0934a45b 100644 --- a/lib/pure/htmlgen.nim +++ b/lib/pure/htmlgen.nim @@ -59,8 +59,8 @@ proc xmlCheckedTag*(e: NimNode, tag: string, optAttr = "", reqAttr = "", # copy the attributes; when iterating over them these lists # will be modified, so that each attribute is only given one value - var req = split(reqAttr) - var opt = split(optAttr) + var req = splitWhitespace(reqAttr) + var opt = splitWhitespace(optAttr) result = newNimNode(nnkBracket, e) result.add(newStrLitNode("<")) result.add(newStrLitNode(tag)) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index de1d332a3..54a8498fa 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -469,7 +469,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead. var r = if proxy == nil: parseUri(url) else: proxy.url var hostUrl = if proxy == nil: r else: parseUri(url) - var headers = httpMethod.toUpper() + var headers = httpMethod.toUpperAscii() # TODO: Use generateHeaders further down once it supports proxies. var s = newSocket() @@ -713,10 +713,10 @@ proc downloadFile*(url: string, outputFilename: string, proc generateHeaders(requestUrl: Uri, httpMethod: string, headers: HttpHeaders, body: string, proxy: Proxy): string = # GET - result = httpMethod.toUpper() + result = httpMethod.toUpperAscii() result.add ' ' - if proxy.isNil: + if proxy.isNil or (not proxy.isNil and requestUrl.scheme == "https"): # /path?query if requestUrl.path[0] != '/': result.add '/' result.add(requestUrl.path) @@ -1048,7 +1048,11 @@ proc newConnection(client: HttpClient | AsyncHttpClient, client.currentURL.scheme != url.scheme or client.currentURL.port != url.port or (not client.connected): - let isSsl = url.scheme.toLowerAscii() == "https" + # Connect to proxy if specified + let connectionUrl = + if client.proxy.isNil: url else: client.proxy.url + + let isSsl = connectionUrl.scheme.toLowerAscii() == "https" if isSsl and not defined(ssl): raise newException(HttpRequestError, @@ -1056,31 +1060,55 @@ proc newConnection(client: HttpClient | AsyncHttpClient, if client.connected: client.close() + client.connected = false # TODO: I should be able to write 'net.Port' here... let port = - if url.port == "": + if connectionUrl.port == "": if isSsl: nativesockets.Port(443) else: nativesockets.Port(80) - else: nativesockets.Port(url.port.parseInt) + else: nativesockets.Port(connectionUrl.port.parseInt) when client is HttpClient: - client.socket = await net.dial(url.hostname, port) + client.socket = await net.dial(connectionUrl.hostname, port) elif client is AsyncHttpClient: - client.socket = await asyncnet.dial(url.hostname, port) + client.socket = await asyncnet.dial(connectionUrl.hostname, port) else: {.fatal: "Unsupported client type".} when defined(ssl): if isSsl: try: client.sslContext.wrapConnectedSocket( - client.socket, handshakeAsClient, url.hostname) + client.socket, handshakeAsClient, connectionUrl.hostname) except: client.socket.close() raise getCurrentException() + # If need to CONNECT through proxy + if url.scheme == "https" and not client.proxy.isNil: + when defined(ssl): + # Pass only host:port for CONNECT + var connectUrl = initUri() + connectUrl.hostname = url.hostname + connectUrl.port = if url.port != "": url.port else: "443" + + let proxyHeaderString = generateHeaders(connectUrl, $HttpConnect, newHttpHeaders(), "", client.proxy) + await client.socket.send(proxyHeaderString) + let proxyResp = await parseResponse(client, false) + + if not proxyResp.status.startsWith("200"): + raise newException(HttpRequestError, + "The proxy server rejected a CONNECT request, " & + "so a secure connection could not be established.") + client.sslContext.wrapConnectedSocket( + client.socket, handshakeAsClient, url.hostname) + else: + raise newException(HttpRequestError, + "SSL support is not available. Cannot connect over SSL.") + + # May be connected through proxy but remember actual URL being accessed client.currentURL = url client.connected = true @@ -1100,32 +1128,9 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, headers: HttpHeaders = nil): Future[Response | AsyncResponse] {.multisync.} = # Helper that actually makes the request. Does not handle redirects. - let connectionUrl = - if client.proxy.isNil: parseUri(url) else: client.proxy.url let requestUrl = parseUri(url) - let savedProxy = client.proxy # client's proxy may be overwritten. - - if requestUrl.scheme == "https" and not client.proxy.isNil: - when defined(ssl): - client.proxy.url = connectionUrl - var connectUrl = requestUrl - connectUrl.scheme = "http" - connectUrl.port = "443" - let proxyResp = await requestAux(client, $connectUrl, $HttpConnect) - - if not proxyResp.status.startsWith("200"): - raise newException(HttpRequestError, - "The proxy server rejected a CONNECT request, " & - "so a secure connection could not be established.") - client.sslContext.wrapConnectedSocket( - client.socket, handshakeAsClient, requestUrl.hostname) - client.proxy = nil - else: - raise newException(HttpRequestError, - "SSL support not available. Cannot connect to https site over proxy.") - else: - await newConnection(client, connectionUrl) + await newConnection(client, requestUrl) let effectiveHeaders = client.headers.override(headers) @@ -1143,9 +1148,6 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, client.getBody result = await parseResponse(client, getBody) - # Restore the clients proxy in case it was overwritten. - client.proxy = savedProxy - proc request*(client: HttpClient | AsyncHttpClient, url: string, httpMethod: string, body = "", headers: HttpHeaders = nil): Future[Response | AsyncResponse] diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim index a5ab40ca4..f150fa1c1 100644 --- a/lib/pure/httpcore.nim +++ b/lib/pure/httpcore.nim @@ -113,6 +113,9 @@ proc newHttpHeaders*(keyValuePairs: new result result.table = newTable[string, seq[string]](pairs) +proc `$`*(headers: HttpHeaders): string = + return $headers.table + proc clear*(headers: HttpHeaders) = headers.table.clear() diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index 8d2fc235a..ae62a5c4e 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -94,7 +94,7 @@ proc findEnvVar(key: string): int = if startsWith(environment[i], temp): return i return -1 -proc getEnv*(key: string): TaintedString {.tags: [ReadEnvEffect].} = +proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = ## Returns the value of the `environment variable`:idx: named `key`. ## ## If the variable does not exist, "" is returned. To distinguish @@ -108,7 +108,7 @@ proc getEnv*(key: string): TaintedString {.tags: [ReadEnvEffect].} = return TaintedString(substr(environment[i], find(environment[i], '=')+1)) else: var env = c_getenv(key) - if env == nil: return TaintedString("") + if env == nil: return TaintedString(default) result = TaintedString($env) proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index dbb709f1b..0889d7383 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -56,9 +56,6 @@ proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1", raise newException(OSError, msg) {.pop.} -when not defined(nimfix): - {.deprecated: [osError: raiseOSError].} - proc `==`*(err1, err2: OSErrorCode): bool {.borrow.} proc `$`*(err: OSErrorCode): string {.borrow.} diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim deleted file mode 100644 index ef8072221..000000000 --- a/lib/pure/ioselectors.nim +++ /dev/null @@ -1,293 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2016 Eugene Kabanov -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module allows high-level and efficient I/O multiplexing. -## -## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and -## Windows ``select``. -## -## To use threadsafe version of this module, it needs to be compiled -## with both ``-d:threadsafe`` and ``--threads:on`` options. -## -## Supported features: files, sockets, pipes, timers, processes, signals -## and user events. -## -## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux (except -## for Android). -## -## Partially supported OS: Windows (only sockets and user events), -## Solaris (files, sockets, handles and user events). -## Android (files, sockets, handles and user events). -## -## TODO: ``/dev/poll``, ``event ports`` and filesystem events. - -import os - -const hasThreadSupport = compileOption("threads") and defined(threadsafe) - -const ioselSupportedPlatform* = defined(macosx) or defined(freebsd) or - defined(netbsd) or defined(openbsd) or - defined(dragonfly) or - (defined(linux) and not defined(android)) - ## This constant is used to determine whether the destination platform is - ## fully supported by ``ioselectors`` module. - -const bsdPlatform = defined(macosx) or defined(freebsd) or - defined(netbsd) or defined(openbsd) or - defined(dragonfly) - -when defined(nimdoc): - type - Selector*[T] = ref object - ## An object which holds descriptors to be checked for read/write status - - Event* {.pure.} = enum - ## An enum which hold event types - Read, ## Descriptor is available for read - Write, ## Descriptor is available for write - Timer, ## Timer descriptor is completed - Signal, ## Signal is raised - Process, ## Process is finished - Vnode, ## BSD specific file change happens - User, ## User event is raised - Error, ## Error happens while waiting, for descriptor - VnodeWrite, ## NOTE_WRITE (BSD specific, write to file occurred) - VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occurred) - VnodeExtend, ## NOTE_EXTEND (BSD specific, file extended) - VnodeAttrib, ## NOTE_ATTRIB (BSD specific, file attributes changed) - VnodeLink, ## NOTE_LINK (BSD specific, file link count changed) - VnodeRename, ## NOTE_RENAME (BSD specific, file renamed) - VnodeRevoke ## NOTE_REVOKE (BSD specific, file revoke occurred) - - ReadyKey* = object - ## An object which holds result for descriptor - fd* : int ## file/socket descriptor - events*: set[Event] ## set of events - - SelectEvent* = object - ## An object which holds user defined event - - proc newSelector*[T](): Selector[T] = - ## Creates a new selector - - proc close*[T](s: Selector[T]) = - ## Closes selector - - proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event], - data: T) = - ## Registers file/socket descriptor ``fd`` to selector ``s`` - ## with events set in ``events``. The ``data`` is application-defined - ## data, which to be passed when event happens. - - proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) = - ## Update file/socket descriptor ``fd``, registered in selector - ## ``s`` with new events set ``event``. - - proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, - data: T): int {.discardable.} = - ## Registers timer notification with ``timeout`` in milliseconds - ## to selector ``s``. - ## If ``oneshot`` is ``true`` timer will be notified only once. - ## Set ``oneshot`` to ``false`` if your want periodic notifications. - ## The ``data`` is application-defined data, which to be passed, when - ## time limit expired. - - proc registerSignal*[T](s: Selector[T], signal: int, - data: T): int {.discardable.} = - ## Registers Unix signal notification with ``signal`` to selector - ## ``s``. The ``data`` is application-defined data, which to be - ## passed, when signal raises. - ## - ## This function is not supported for ``Windows``. - - proc registerProcess*[T](s: Selector[T], pid: int, - data: T): int {.discardable.} = - ## Registers process id (pid) notification when process has - ## exited to selector ``s``. - ## The ``data`` is application-defined data, which to be passed, when - ## process with ``pid`` has exited. - - proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = - ## Registers selector event ``ev`` to selector ``s``. - ## ``data`` application-defined data, which to be passed, when - ## ``ev`` happens. - - proc registerVnode*[T](s: Selector[T], fd: cint, events: set[Event], - data: T) = - ## Registers selector BSD/MacOSX specific vnode events for file - ## descriptor ``fd`` and events ``events``. - ## ``data`` application-defined data, which to be passed, when - ## vnode event happens. - ## - ## This function is supported only by BSD and MacOSX. - - proc newSelectEvent*(): SelectEvent = - ## Creates new event ``SelectEvent``. - - proc setEvent*(ev: SelectEvent) = - ## Trigger event ``ev``. - - proc close*(ev: SelectEvent) = - ## Closes selector event ``ev``. - - proc unregister*[T](s: Selector[T], ev: SelectEvent) = - ## Unregisters event ``ev`` from selector ``s``. - - proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = - ## Unregisters file/socket descriptor ``fd`` from selector ``s``. - - proc selectInto*[T](s: Selector[T], timeout: int, - results: var openarray[ReadyKey]): int = - ## Process call waiting for events registered in selector ``s``. - ## The ``timeout`` argument specifies the minimum number of milliseconds - ## the function will be blocked, if no events are not ready. Specifying a - ## timeout of ``-1`` causes function to block indefinitely. - ## All available events will be stored in ``results`` array. - ## - ## Function returns number of triggered events. - - proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] = - ## Process call waiting for events registered in selector ``s``. - ## The ``timeout`` argument specifies the minimum number of milliseconds - ## the function will be blocked, if no events are not ready. Specifying a - ## timeout of -1 causes function to block indefinitely. - ## - ## Function returns sequence of triggered events. - - proc getData*[T](s: Selector[T], fd: SocketHandle|int): T = - ## Retrieves application-defined ``data`` associated with descriptor ``fd``. - ## If specified descriptor ``fd`` is not registered, empty/default value - ## will be returned. - - proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: var T): bool = - ## Associate application-defined ``data`` with descriptor ``fd``. - ## - ## Returns ``true``, if data was succesfully updated, ``false`` otherwise. - - template isEmpty*[T](s: Selector[T]): bool = - ## Returns ``true``, if there no registered events or descriptors - ## in selector. - - template withData*[T](s: Selector[T], fd: SocketHandle|int, value, - body: untyped) = - ## Retrieves the application-data assigned with descriptor ``fd`` - ## to ``value``. This ``value`` can be modified in the scope of - ## the ``withData`` call. - ## - ## .. code-block:: nim - ## - ## s.withData(fd, value) do: - ## # block is executed only if ``fd`` registered in selector ``s`` - ## value.uid = 1000 - ## - - template withData*[T](s: Selector[T], fd: SocketHandle|int, value, - body1, body2: untyped) = - ## Retrieves the application-data assigned with descriptor ``fd`` - ## to ``value``. This ``value`` can be modified in the scope of - ## the ``withData`` call. - ## - ## .. code-block:: nim - ## - ## s.withData(fd, value) do: - ## # block is executed only if ``fd`` registered in selector ``s``. - ## value.uid = 1000 - ## do: - ## # block is executed if ``fd`` not registered in selector ``s``. - ## raise - ## - -else: - when hasThreadSupport: - import locks - - type - SharedArray[T] = UncheckedArray[T] - - proc allocSharedArray[T](nsize: int): ptr SharedArray[T] = - result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize)) - - proc deallocSharedArray[T](sa: ptr SharedArray[T]) = - deallocShared(cast[pointer](sa)) - type - Event* {.pure.} = enum - Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot, - Finished, VnodeWrite, VnodeDelete, VnodeExtend, VnodeAttrib, VnodeLink, - VnodeRename, VnodeRevoke - - type - IOSelectorsException* = object of Exception - - ReadyKey* = object - fd* : int - events*: set[Event] - - SelectorKey[T] = object - ident: int - events: set[Event] - param: int - data: T - - proc raiseIOSelectorsError[T](message: T) = - var msg = "" - when T is string: - msg.add(message) - elif T is OSErrorCode: - msg.add(osErrorMsg(message) & " (code: " & $int(message) & ")") - else: - msg.add("Internal Error\n") - var err = newException(IOSelectorsException, msg) - raise err - - when not defined(windows): - import posix - - proc setNonBlocking(fd: cint) {.inline.} = - var x = fcntl(fd, F_GETFL, 0) - if x == -1: - raiseIOSelectorsError(osLastError()) - else: - var mode = x or O_NONBLOCK - if fcntl(fd, F_SETFL, mode) == -1: - raiseIOSelectorsError(osLastError()) - - template setKey(s, pident, pevents, pparam, pdata: untyped) = - var skey = addr(s.fds[pident]) - skey.ident = pident - skey.events = pevents - skey.param = pparam - skey.data = data - - when ioselSupportedPlatform: - template blockSignals(newmask: var Sigset, oldmask: var Sigset) = - when hasThreadSupport: - if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1: - raiseIOSelectorsError(osLastError()) - else: - if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1: - raiseIOSelectorsError(osLastError()) - - template unblockSignals(newmask: var Sigset, oldmask: var Sigset) = - when hasThreadSupport: - if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1: - raiseIOSelectorsError(osLastError()) - else: - if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1: - raiseIOSelectorsError(osLastError()) - - when defined(linux): - include ioselects/ioselectors_epoll - elif bsdPlatform: - include ioselects/ioselectors_kqueue - elif defined(windows): - include ioselects/ioselectors_select - elif defined(solaris): - include ioselects/ioselectors_poll # need to replace it with event ports - else: - include ioselects/ioselectors_poll diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 3a5cbc87a..8827f239f 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -9,7 +9,7 @@ # This module implements Linux epoll(). -import posix, times +import posix, times, epoll # Maximum number of events that can be returned const MAX_EPOLL_EVENTS = 64 @@ -36,35 +36,6 @@ when not defined(android): ssi_addr*: uint64 pad* {.importc: "__pad".}: array[0..47, uint8] -type - eventFdData {.importc: "eventfd_t", - header: "<sys/eventfd.h>", pure, final.} = uint64 - epoll_data {.importc: "union epoll_data", header: "<sys/epoll.h>", - pure, final.} = object - u64 {.importc: "u64".}: uint64 - epoll_event {.importc: "struct epoll_event", - header: "<sys/epoll.h>", pure, final.} = object - events: uint32 # Epoll events - data: epoll_data # User data variable - -const - EPOLL_CTL_ADD = 1 # Add a file descriptor to the interface. - EPOLL_CTL_DEL = 2 # Remove a file descriptor from the interface. - EPOLL_CTL_MOD = 3 # Change file descriptor epoll_event structure. - EPOLLIN = 0x00000001 - EPOLLOUT = 0x00000004 - EPOLLERR = 0x00000008 - EPOLLHUP = 0x00000010 - EPOLLRDHUP = 0x00002000 - EPOLLONESHOT = 1 shl 30 - -proc epoll_create(size: cint): cint - {.importc: "epoll_create", header: "<sys/epoll.h>".} -proc epoll_ctl(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint - {.importc: "epoll_ctl", header: "<sys/epoll.h>".} -proc epoll_wait(epfd: cint; events: ptr epoll_event; maxevents: cint; - timeout: cint): cint - {.importc: "epoll_wait", header: "<sys/epoll.h>".} proc timerfd_create(clock_id: ClockId, flags: cint): cint {.cdecl, importc: "timerfd_create", header: "<sys/timerfd.h>".} proc timerfd_settime(ufd: cint, flags: cint, @@ -80,26 +51,26 @@ when not defined(android): var RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE", header: "<sys/resource.h>".}: cint type - rlimit {.importc: "struct rlimit", + RLimit {.importc: "struct rlimit", header: "<sys/resource.h>", pure, final.} = object rlim_cur: int rlim_max: int -proc getrlimit(resource: cint, rlp: var rlimit): cint +proc getrlimit(resource: cint, rlp: var RLimit): cint {.importc: "getrlimit",header: "<sys/resource.h>".} when hasThreadSupport: type SelectorImpl[T] = object - epollFD : cint - maxFD : int + epollFD: cint + maxFD: int fds: ptr SharedArray[SelectorKey[T]] count: int Selector*[T] = ptr SelectorImpl[T] else: type SelectorImpl[T] = object - epollFD : cint - maxFD : int + epollFD: cint + maxFD: int fds: seq[SelectorKey[T]] count: int Selector*[T] = ref SelectorImpl[T] @@ -109,7 +80,8 @@ type SelectEvent* = ptr SelectEventImpl proc newSelector*[T](): Selector[T] = - var a = rlimit() + # Retrieve the maximum fd count (for current OS) via getrlimit() + var a = RLimit() if getrlimit(RLIMIT_NOFILE, a) != 0: raiseOsError(osLastError()) var maxFD = int(a.rlim_max) @@ -152,8 +124,8 @@ proc newSelectEvent*(): SelectEvent = result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl))) result.efd = fdci -proc setEvent*(ev: SelectEvent) = - var data : uint64 = 1 +proc trigger*(ev: SelectEvent) = + var data: uint64 = 1 if posix.write(ev.efd, addr data, sizeof(uint64)) == -1: raiseIOSelectorsError(osLastError()) @@ -164,6 +136,8 @@ proc close*(ev: SelectEvent) = raiseIOSelectorsError(osLastError()) template checkFd(s, f) = + # TODO: I don't see how this can ever happen. You won't be able to create an + # FD if there is too many. -- DP if f >= s.maxFD: raiseIOSelectorsError("Maximum number of descriptors is exhausted!") @@ -171,10 +145,10 @@ proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event], data: T) = let fdi = int(fd) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == 0, "Descriptor $# already registered" % $fdi) s.setKey(fdi, events, 0, data) if events != {}: - var epv = epoll_event(events: EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLRDHUP) epv.data.u64 = fdi.uint if Event.Read in events: epv.events = epv.events or EPOLLIN if Event.Write in events: epv.events = epv.events or EPOLLOUT @@ -189,10 +163,10 @@ proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) = s.checkFd(fdi) var pkey = addr(s.fds[fdi]) doAssert(pkey.ident != 0, - "Descriptor [" & $fdi & "] is not registered in the queue!") + "Descriptor $# is not registered in the selector!" % $fdi) doAssert(pkey.events * maskEvents == {}) if pkey.events != events: - var epv = epoll_event(events: EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLRDHUP) epv.data.u64 = fdi.uint if Event.Read in events: epv.events = epv.events or EPOLLIN @@ -217,24 +191,25 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = s.checkFd(fdi) var pkey = addr(s.fds[fdi]) doAssert(pkey.ident != 0, - "Descriptor [" & $fdi & "] is not registered in the queue!") + "Descriptor $# is not registered in the selector!" % $fdi) if pkey.events != {}: when not defined(android): if pkey.events * {Event.Read, Event.Write} != {}: - var epv = epoll_event() + var epv = EpollEvent() + # TODO: Refactor all these EPOLL_CTL_DEL + dec(s.count) into a proc. if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) dec(s.count) elif Event.Timer in pkey.events: if Event.Finished notin pkey.events: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) dec(s.count) if posix.close(cint(fdi)) != 0: raiseIOSelectorsError(osLastError()) elif Event.Signal in pkey.events: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) var nmask, omask: Sigset @@ -247,7 +222,7 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = raiseIOSelectorsError(osLastError()) elif Event.Process in pkey.events: if Event.Finished notin pkey.events: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) var nmask, omask: Sigset @@ -260,13 +235,13 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = raiseIOSelectorsError(osLastError()) else: if pkey.events * {Event.Read, Event.Write} != {}: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) dec(s.count) elif Event.Timer in pkey.events: if Event.Finished notin pkey.events: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) dec(s.count) @@ -280,7 +255,7 @@ proc unregister*[T](s: Selector[T], ev: SelectEvent) = var pkey = addr(s.fds[fdi]) doAssert(pkey.ident != 0, "Event is not registered in the queue!") doAssert(Event.User in pkey.events) - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) dec(s.count) @@ -300,17 +275,18 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, doAssert(s.fds[fdi].ident == 0) var events = {Event.Timer} - var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint + if oneshot: - new_ts.it_interval.tv_sec = 0.Time + new_ts.it_interval.tv_sec = posix.Time(0) new_ts.it_interval.tv_nsec = 0 - new_ts.it_value.tv_sec = (timeout div 1_000).Time + new_ts.it_value.tv_sec = posix.Time(timeout div 1_000) new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000 incl(events, Event.Oneshot) epv.events = epv.events or EPOLLONESHOT else: - new_ts.it_interval.tv_sec = (timeout div 1000).Time + new_ts.it_interval.tv_sec = posix.Time(timeout div 1000) new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000 new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec @@ -343,7 +319,7 @@ when not defined(android): s.checkFd(fdi) doAssert(s.fds[fdi].ident == 0) - var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0: raiseIOSelectorsError(osLastError()) @@ -370,7 +346,7 @@ when not defined(android): s.checkFd(fdi) doAssert(s.fds[fdi].ident == 0) - var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint epv.events = EPOLLIN or EPOLLRDHUP if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0: @@ -383,7 +359,7 @@ proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = let fdi = int(ev.efd) doAssert(s.fds[fdi].ident == 0, "Event is already registered in the queue!") s.setKey(fdi, {Event.User}, 0, data) - var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP) + var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = ev.efd.uint if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) != 0: raiseIOSelectorsError(osLastError()) @@ -392,7 +368,7 @@ proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = proc selectInto*[T](s: Selector[T], timeout: int, results: var openarray[ReadyKey]): int = var - resTable: array[MAX_EPOLL_EVENTS, epoll_event] + resTable: array[MAX_EPOLL_EVENTS, EpollEvent] maxres = MAX_EPOLL_EVENTS i, k: int @@ -482,7 +458,7 @@ proc selectInto*[T](s: Selector[T], timeout: int, rkey.events.incl(Event.User) if Event.Oneshot in pkey.events: - var epv = epoll_event() + var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, cint(fdi), addr epv) != 0: raiseIOSelectorsError(osLastError()) # we will not clear key until it will be unregistered, so @@ -505,16 +481,19 @@ proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] = template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) -proc getData*[T](s: Selector[T], fd: SocketHandle|int): T = +proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = + return s.fds[fd].ident != 0 + +proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: result = s.fds[fdi].data proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: T): bool = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: s.fds[fdi].data = data result = true @@ -523,8 +502,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, @@ -532,8 +511,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body1 else: body2 diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim index 7786de46a..af5aa15df 100644 --- a/lib/pure/ioselects/ioselectors_kqueue.nim +++ b/lib/pure/ioselects/ioselectors_kqueue.nim @@ -144,7 +144,7 @@ proc newSelectEvent*(): SelectEvent = result.rfd = fds[0] result.wfd = fds[1] -proc setEvent*(ev: SelectEvent) = +proc trigger*(ev: SelectEvent) = var data: uint64 = 1 if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64): raiseIOSelectorsError(osLastError()) @@ -243,7 +243,7 @@ proc updateHandle*[T](s: Selector[T], fd: SocketHandle, s.checkFd(fdi) var pkey = addr(s.fds[fdi]) doAssert(pkey.ident != 0, - "Descriptor [" & $fdi & "] is not registered in the queue!") + "Descriptor $# is not registered in the queue!" % $fdi) doAssert(pkey.events * maskEvents == {}) if pkey.events != events: @@ -452,10 +452,10 @@ proc selectInto*[T](s: Selector[T], timeout: int, if timeout != -1: if timeout >= 1000: - tv.tv_sec = (timeout div 1_000).Time + tv.tv_sec = posix.Time(timeout div 1_000) tv.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tv.tv_sec = 0.Time + tv.tv_sec = posix.Time(0) tv.tv_nsec = timeout * 1_000_000 else: ptv = nil @@ -584,16 +584,19 @@ proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] = template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) -proc getData*[T](s: Selector[T], fd: SocketHandle|int): T = +proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = + return s.fds[fd].ident != 0 + +proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: result = s.fds[fdi].data proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: T): bool = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: s.fds[fdi].data = data result = true @@ -602,8 +605,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, @@ -611,8 +614,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body1 else: body2 diff --git a/lib/pure/ioselects/ioselectors_poll.nim b/lib/pure/ioselects/ioselectors_poll.nim index 1b90e0806..cc06aa592 100644 --- a/lib/pure/ioselects/ioselectors_poll.nim +++ b/lib/pure/ioselects/ioselectors_poll.nim @@ -208,7 +208,7 @@ proc newSelectEvent*(): SelectEvent = result.rfd = fds[0] result.wfd = fds[1] -proc setEvent*(ev: SelectEvent) = +proc trigger*(ev: SelectEvent) = var data: uint64 = 1 if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64): raiseIOSelectorsError(osLastError()) @@ -279,16 +279,19 @@ proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] = template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) -proc getData*[T](s: Selector[T], fd: SocketHandle|int): T = +proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = + return s.fds[fd].ident != 0 + +proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: result = s.fds[fdi].data proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: T): bool = let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: + if fdi in s: s.fds[fdi].data = data result = true @@ -297,8 +300,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, @@ -306,8 +309,8 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, mixin checkFd let fdi = int(fd) s.checkFd(fdi) - if s.fds[fdi].ident != 0: - var value = addr(s.fds[fdi].data) + if fdi in s: + var value = addr(s.getData(fdi)) body1 else: body2 diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim index dc3451d52..c787f0070 100644 --- a/lib/pure/ioselects/ioselectors_select.nim +++ b/lib/pure/ioselects/ioselectors_select.nim @@ -154,7 +154,7 @@ when defined(windows): result.rsock = rsock result.wsock = wsock - proc setEvent*(ev: SelectEvent) = + proc trigger*(ev: SelectEvent) = var data: uint64 = 1 if winlean.send(ev.wsock, cast[pointer](addr data), cint(sizeof(uint64)), 0) != sizeof(uint64): @@ -178,7 +178,7 @@ else: result.rsock = SocketHandle(fds[0]) result.wsock = SocketHandle(fds[1]) - proc setEvent*(ev: SelectEvent) = + proc trigger*(ev: SelectEvent) = var data: uint64 = 1 if posix.write(cint(ev.wsock), addr data, sizeof(uint64)) != sizeof(uint64): raiseIOSelectorsError(osLastError()) @@ -279,8 +279,9 @@ proc updateHandle*[T](s: Selector[T], fd: SocketHandle, inc(s.count) pkey.events = events -proc unregister*[T](s: Selector[T], fd: SocketHandle) = +proc unregister*[T](s: Selector[T], fd: SocketHandle|int) = s.withSelectLock(): + let fd = fd.SocketHandle var pkey = s.getKey(fd) if Event.Read in pkey.events: IOFD_CLR(fd, addr s.rSet) @@ -379,6 +380,16 @@ proc flush*[T](s: Selector[T]) = discard template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) +proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = + s.withSelectLock(): + result = false + + let fdi = int(fd) + for i in 0..<FD_SETSIZE: + if s.fds[i].ident == fdi: + return true + inc(i) + when hasThreadSupport: template withSelectLock[T](s: Selector[T], body: untyped) = acquire(s.lock) @@ -391,15 +402,12 @@ else: template withSelectLock[T](s: Selector[T], body: untyped) = body -proc getData*[T](s: Selector[T], fd: SocketHandle|int): T = +proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = s.withSelectLock(): let fdi = int(fd) - var i = 0 - while i < FD_SETSIZE: + for i in 0..<FD_SETSIZE: if s.fds[i].ident == fdi: - result = s.fds[i].data - break - inc(i) + return s.fds[i].data proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: T): bool = s.withSelectLock(): @@ -431,16 +439,17 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, body2: untyped) = mixin withSelectLock s.withSelectLock(): - var value: ptr T - let fdi = int(fd) - var i = 0 - while i < FD_SETSIZE: - if s.fds[i].ident == fdi: - value = addr(s.fds[i].data) - break - inc(i) - if i != FD_SETSIZE: - body1 - else: - body2 + block: + var value: ptr T + let fdi = int(fd) + var i = 0 + while i < FD_SETSIZE: + if s.fds[i].ident == fdi: + value = addr(s.fds[i].data) + break + inc(i) + if i != FD_SETSIZE: + body1 + else: + body2 diff --git a/lib/pure/json.nim b/lib/pure/json.nim index fd7a3af03..b5b84863a 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -823,13 +823,13 @@ proc toJson(x: NimNode): NimNode {.compiletime.} = of nnkBracket: # array if x.len == 0: return newCall(bindSym"newJArray") result = newNimNode(nnkBracket) - for i in 0 .. <x.len: + for i in 0 ..< x.len: result.add(toJson(x[i])) result = newCall(bindSym"%", result) of nnkTableConstr: # object if x.len == 0: return newCall(bindSym"newJObject") result = newNimNode(nnkTableConstr) - for i in 0 .. <x.len: + for i in 0 ..< x.len: x[i].expectKind nnkExprColonExpr result.add newTree(nnkExprColonExpr, x[i][0], toJson(x[i][1])) result = newCall(bindSym"%", result) @@ -1303,7 +1303,7 @@ else: case getVarType(x) of JArray: result = newJArray() - for i in 0 .. <x.len: + for i in 0 ..< x.len: result.add(x[i].convertObject()) of JObject: result = newJObject() @@ -1346,6 +1346,16 @@ proc createJsonIndexer(jsonNode: NimNode, indexNode ) +proc transformJsonIndexer(jsonNode: NimNode): NimNode = + case jsonNode.kind + of nnkBracketExpr: + result = newNimNode(nnkCurlyExpr) + else: + result = jsonNode.copy() + + for child in jsonNode: + result.add(transformJsonIndexer(child)) + template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind], ast: string) = if node.kind notin kinds: @@ -1449,7 +1459,7 @@ proc processElseBranch(recCaseNode, elseBranch, jsonNode, kindType, # We need to build up a list of conditions from each ``of`` branch so that # we can then negate it to get ``else``. var cond = newIdentNode("false") - for i in 1 .. <len(recCaseNode): + for i in 1 ..< len(recCaseNode): if recCaseNode[i].kind == nnkElse: break @@ -1511,7 +1521,7 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = exprColonExpr.add(getEnumCall) # Iterate through each `of` branch. - for i in 1 .. <field.len: + for i in 1 ..< field.len: case field[i].kind of nnkOfBranch: result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode) @@ -1524,6 +1534,35 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = doAssert result.len > 0 +proc processFields(obj: NimNode, + jsonNode: NimNode): seq[NimNode] {.compileTime.} = + ## Process all the fields of an ``ObjectTy`` and any of its + ## parent type's fields (via inheritance). + result = @[] + case obj.kind + of nnkObjectTy: + expectKind(obj[2], nnkRecList) + for field in obj[2]: + let nodes = processObjField(field, jsonNode) + result.add(nodes) + + # process parent type fields + case obj[1].kind + of nnkBracketExpr: + assert $obj[1][0] == "ref" + result.add(processFields(getType(obj[1][1]), jsonNode)) + of nnkSym: + result.add(processFields(getType(obj[1]), jsonNode)) + else: + discard + of nnkTupleTy: + for identDefs in obj: + expectKind(identDefs, nnkIdentDefs) + let nodes = processObjField(identDefs[0], jsonNode) + result.add(nodes) + else: + doAssert false, "Unable to process field type: " & $obj.kind + proc processType(typeName: NimNode, obj: NimNode, jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} = ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``. @@ -1533,20 +1572,21 @@ proc processType(typeName: NimNode, obj: NimNode, ## .. code-block::plain ## ObjectTy ## Empty - ## Empty + ## InheritanceInformation ## RecList ## Sym "events" case obj.kind - of nnkObjectTy: + of nnkObjectTy, nnkTupleTy: # Create object constructor. - result = newNimNode(nnkObjConstr) - result.add(typeName) # Name of the type to construct. + result = + if obj.kind == nnkObjectTy: newNimNode(nnkObjConstr) + else: newNimNode(nnkPar) - # Process each object field and add it as an exprColonExpr - expectKind(obj[2], nnkRecList) - for field in obj[2]: - let nodes = processObjField(field, jsonNode) - result.add(nodes) + if obj.kind == nnkObjectTy: + result.add(typeName) # Name of the type to construct. + + # Process each object/tuple field and add it as an exprColonExpr + result.add(processFields(obj, jsonNode)) # Object might be null. So we need to check for that. if isRef: @@ -1569,25 +1609,14 @@ proc processType(typeName: NimNode, obj: NimNode, `getEnumCall` ) of nnkSym: - case ($typeName).normalize - of "float": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JFloat, JInt}, astToStr(`jsonNode`)); - if `jsonNode`.kind == JFloat: `jsonNode`.fnum else: `jsonNode`.num.float - ) + let name = ($typeName).normalize + case name of "string": result = quote do: ( verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`)); if `jsonNode`.kind == JNull: nil else: `jsonNode`.str ) - of "int": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); - `jsonNode`.num.int - ) of "biggestint": result = quote do: ( @@ -1601,12 +1630,36 @@ proc processType(typeName: NimNode, obj: NimNode, `jsonNode`.bval ) else: - doAssert false, "Unable to process nnkSym " & $typeName + if name.startsWith("int") or name.startsWith("uint"): + result = quote do: + ( + verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); + `jsonNode`.num.`obj` + ) + elif name.startsWith("float"): + result = quote do: + ( + verifyJsonKind(`jsonNode`, {JInt, JFloat}, astToStr(`jsonNode`)); + if `jsonNode`.kind == JFloat: `jsonNode`.fnum.`obj` else: `jsonNode`.num.`obj` + ) + else: + doAssert false, "Unable to process nnkSym " & $typeName else: doAssert false, "Unable to process type: " & $obj.kind doAssert(not result.isNil(), "processType not initialised.") +import options +proc workaroundMacroNone[T](): Option[T] = + none(T) + +proc depth(n: NimNode, current = 0): int = + result = 1 + for child in n: + let d = 1 + child.depth(current + 1) + if d > result: + result = d + proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ## Accepts a type description, i.e. "ref Type", "seq[Type]", "Type" etc. ## @@ -1616,10 +1669,50 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = # echo("--createConsuctor-- \n", treeRepr(typeSym)) # echo() + if depth(jsonNode) > 150: + error("The `to` macro does not support ref objects with cycles.", jsonNode) + case typeSym.kind of nnkBracketExpr: var bracketName = ($typeSym[0]).normalize case bracketName + of "option": + # TODO: Would be good to verify that this is Option[T] from + # options module I suppose. + let lenientJsonNode = transformJsonIndexer(jsonNode) + + let optionGeneric = typeSym[1] + let value = createConstructor(typeSym[1], jsonNode) + let workaround = bindSym("workaroundMacroNone") # TODO: Nim Bug: This shouldn't be necessary. + + result = quote do: + ( + if `lenientJsonNode`.isNil: `workaround`[`optionGeneric`]() else: some[`optionGeneric`](`value`) + ) + of "table", "orderedtable": + let tableKeyType = typeSym[1] + if ($tableKeyType).cmpIgnoreStyle("string") != 0: + error("JSON doesn't support keys of type " & $tableKeyType) + let tableValueType = typeSym[2] + + let forLoopKey = genSym(nskForVar, "key") + let indexerNode = createJsonIndexer(jsonNode, forLoopKey) + let constructorNode = createConstructor(tableValueType, indexerNode) + + let tableInit = + if bracketName == "table": + bindSym("initTable") + else: + bindSym("initOrderedTable") + + # Create a statement expression containing a for loop. + result = quote do: + ( + var map = `tableInit`[`tableKeyType`, `tableValueType`](); + verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`)); + for `forLoopKey` in keys(`jsonNode`.fields): map[`forLoopKey`] = `constructorNode`; + map + ) of "ref": # Ref type. var typeName = $typeSym[1] @@ -1640,7 +1733,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ( var list: `typeSym` = @[]; verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 .. <`jsonNode`.len: list.add(`constructorNode`); + for `forLoopI` in 0 ..< `jsonNode`.len: list.add(`constructorNode`); list ) of "array": @@ -1654,7 +1747,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ( var list: `typeSym`; verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 .. <`jsonNode`.len: list[`forLoopI`] =`constructorNode`; + for `forLoopI` in 0 ..< `jsonNode`.len: list[`forLoopI`] =`constructorNode`; list ) @@ -1663,12 +1756,23 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = let obj = getType(typeSym) result = processType(typeSym, obj, jsonNode, false) of nnkSym: + # Handle JsonNode. + if ($typeSym).cmpIgnoreStyle("jsonnode") == 0: + return jsonNode + + # Handle all other types. let obj = getType(typeSym) if obj.kind == nnkBracketExpr: # When `Sym "Foo"` turns out to be a `ref object`. result = createConstructor(obj, jsonNode) else: result = processType(typeSym, obj, jsonNode, false) + of nnkTupleTy: + result = processType(typeSym, typeSym, jsonNode, false) + of nnkPar: + # TODO: The fact that `jsonNode` here works to give a good line number + # is weird. Specifying typeSym should work but doesn't. + error("Use a named tuple instead of: " & $toStrLit(typeSym), jsonNode) else: doAssert false, "Unable to create constructor for: " & $typeSym.kind @@ -1683,7 +1787,7 @@ proc postProcessValue(value: NimNode): NimNode = result = postProcess(value) else: result = value - for i in 0 .. <len(result): + for i in 0 ..< len(result): result[i] = postProcessValue(result[i]) proc postProcessExprColonExpr(exprColonExpr, resIdent: NimNode): NimNode = @@ -1796,10 +1900,18 @@ macro to*(node: JsonNode, T: typedesc): untyped = expectKind(typeNode, nnkBracketExpr) doAssert(($typeNode[0]).normalize == "typedesc") - result = createConstructor(typeNode[1], node) + # Create `temp` variable to store the result in case the user calls this + # on `parseJson` (see bug #6604). + result = newNimNode(nnkStmtListExpr) + let temp = genSym(nskLet, "temp") + result.add quote do: + let `temp` = `node` + + let constructor = createConstructor(typeNode[1], temp) # TODO: Rename postProcessValue and move it (?) - result = postProcessValue(result) + result.add(postProcessValue(constructor)) + # echo(treeRepr(result)) # echo(toStrLit(result)) when false: @@ -1849,8 +1961,8 @@ when isMainModule: discard parseJson"""{ invalid""" except: discard - # memory diff should less than 2M - doAssert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024) + # memory diff should less than 4M + doAssert(abs(getOccupiedMem() - startMemory) < 4 * 1024 * 1024) # test `$` diff --git a/lib/pure/lenientops.nim b/lib/pure/lenientops.nim new file mode 100644 index 000000000..b124a9512 --- /dev/null +++ b/lib/pure/lenientops.nim @@ -0,0 +1,60 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module offers implementations of common binary operations +## like ``+``, ``-``, ``*``, ``/`` and comparison operations, +## which work for mixed float/int operands. +## All operations convert the integer operand into the +## type of the float operand. For numerical expressions, the return +## type is always the type of the float involved in the expresssion, +## i.e., there is no auto conversion from float32 to float64. +## +## Note: In general, auto-converting from int to float loses +## information, which is why these operators live in a separate +## module. Use with care. +## +## Regarding binary comparison, this module only provides unequal operators. +## The equality operator ``==`` is omitted, because depending on the use case +## either casting to float or rounding to int might be preferred, and users +## should make an explicit choice. + +import typetraits + +proc `+`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = + F(i) + f +proc `+`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = + f + F(i) + +proc `-`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = + F(i) - f +proc `-`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = + f - F(i) + +proc `*`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = + F(i) * f +proc `*`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = + f * F(i) + +proc `/`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = + F(i) / f +proc `/`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = + f / F(i) + +proc `<`*[I: SomeInteger, F: SomeReal](i: I, f: F): bool {.noSideEffect, inline.} = + F(i) < f +proc `<`*[I: SomeInteger, F: SomeReal](f: F, i: I): bool {.noSideEffect, inline.} = + f < F(i) +proc `<=`*[I: SomeInteger, F: SomeReal](i: I, f: F): bool {.noSideEffect, inline.} = + F(i) <= f +proc `<=`*[I: SomeInteger, F: SomeReal](f: F, i: I): bool {.noSideEffect, inline.} = + f <= F(i) + +# Note that we must not defined `>=` and `>`, because system.nim already has a +# template with signature (x, y: untyped): untyped, which would lead to +# ambigous calls. diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index e2a5bed96..830820fd1 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -202,13 +202,17 @@ when not defined(js): proc countLogLines(logger: RollingFileLogger): int = result = 0 - for line in logger.file.lines(): + let fp = open(logger.baseName, fmRead) + for line in fp.lines(): result.inc() + fp.close() proc countFiles(filename: string): int = # Example: file.log.1 result = 0 - let (dir, name, ext) = splitFile(filename) + var (dir, name, ext) = splitFile(filename) + if dir == "": + dir = "." for kind, path in walkDir(dir): if kind == pcFile: let llfn = name & ext & ExtSep diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index c4c731acf..6ee830786 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -283,7 +283,7 @@ proc to*[T](data: string): T = loadAny(newStringStream(data), toAny(result), tab) when not defined(testing) and isMainModule: - template testit(x: expr) = echo($$to[type(x)]($$x)) + template testit(x: untyped) = echo($$to[type(x)]($$x)) var x: array[0..4, array[0..4, string]] = [ ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim index 7022c21d9..6366fee1a 100644 --- a/lib/pure/matchers.nim +++ b/lib/pure/matchers.nim @@ -48,7 +48,7 @@ proc validEmailAddress*(s: string): bool {.noSideEffect, "aero", "jobs", "museum": return true else: return false -proc parseInt*(s: string, value: var int, validRange: Slice[int]) {. +proc parseInt*(s: string, value: var int, validRange: HSlice[int, int]) {. noSideEffect, rtl, extern: "nmatchParseInt".} = ## parses `s` into an integer in the range `validRange`. If successful, ## `value` is modified to contain the result. Otherwise no exception is diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 8037b31b0..cbd04a145 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -184,6 +184,8 @@ when not defined(JS): proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".} proc pow*(x, y: float64): float64 {.importc: "pow", header: "<math.h>".} ## computes x to power raised of y. + ## + ## To compute power between integers, use `^` e.g. 2 ^ 6 proc erf*(x: float32): float32 {.importc: "erff", header: "<math.h>".} proc erf*(x: float64): float64 {.importc: "erf", header: "<math.h>".} @@ -289,6 +291,8 @@ when not defined(JS): ## echo fmod(-2.5, 0.3) ## -0.1 else: + proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.} + proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.} proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.} proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.} @@ -347,15 +351,19 @@ proc round*[T: float32|float64](x: T, places: int = 0): T = result = round0(x*mult)/mult when not defined(JS): - proc frexp*(x: float32, exponent: var int): float32 {. + proc c_frexp*(x: float32, exponent: var int32): float32 {. importc: "frexp", header: "<math.h>".} - proc frexp*(x: float64, exponent: var int): float64 {. + proc c_frexp*(x: float64, exponent: var int32): float64 {. importc: "frexp", header: "<math.h>".} + proc frexp*[T, U](x: T, exponent: var U): T = ## Split a number into mantissa and exponent. ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5 ## and less than 1) and the integer value n such that `x` (the original ## float value) equals m * 2**n. frexp stores n in `exponent` and returns ## m. + var exp: int32 + result = c_frexp(x, exp) + exponent = exp else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = if x == 0.0: @@ -364,9 +372,14 @@ else: elif x < 0.0: result = -frexp(-x, exponent) else: - var ex = floor(log2(x)) - exponent = round(ex) + var ex = trunc(log2(x)) + exponent = int(ex) result = x / pow(2.0, ex) + if abs(result) >= 1: + inc(exponent) + result = result / 2 + if exponent == 1024 and result == 0.0: + result = 0.99999999999999988898 proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## Breaks `x` into an integral and a fractional part. diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 9b2d25267..5c73381ff 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -257,10 +257,13 @@ proc close*(f: var MemFile) = when defined(windows): if f.wasOpened: error = unmapViewOfFile(f.mem) == 0 - lastErr = osLastError() - error = (closeHandle(f.mapHandle) == 0) or error - if f.fHandle != INVALID_HANDLE_VALUE: - error = (closeHandle(f.fHandle) == 0) or error + if not error: + error = closeHandle(f.mapHandle) == 0 + if not error and f.fHandle != INVALID_HANDLE_VALUE: + discard closeHandle(f.fHandle) + f.fHandle = INVALID_HANDLE_VALUE + if error: + lastErr = osLastError() else: error = munmap(f.mem, f.size) != 0 lastErr = osLastError() diff --git a/lib/pure/mersenne.nim b/lib/pure/mersenne.nim index f18cf5b90..6ac0c4e56 100644 --- a/lib/pure/mersenne.nim +++ b/lib/pure/mersenne.nim @@ -17,7 +17,7 @@ type proc newMersenneTwister*(seed: uint32): MersenneTwister = result.index = 0 result.mt[0] = seed - for i in 1..623'u32: + for i in 1'u32 .. 623'u32: result.mt[i] = (0x6c078965'u32 * (result.mt[i-1] xor (result.mt[i-1] shr 30'u32)) + i) proc generateNumbers(m: var MersenneTwister) = diff --git a/lib/pure/mimetypes.nim b/lib/pure/mimetypes.nim index 1e315afb4..b397ef47b 100644 --- a/lib/pure/mimetypes.nim +++ b/lib/pure/mimetypes.nim @@ -491,6 +491,8 @@ const mimes* = { "vrml": "x-world/x-vrml", "wrl": "x-world/x-vrml"} +from strutils import startsWith + proc newMimetypes*(): MimeDB = ## Creates a new Mimetypes database. The database will contain the most ## common mimetypes. @@ -498,8 +500,11 @@ proc newMimetypes*(): MimeDB = proc getMimetype*(mimedb: MimeDB, ext: string, default = "text/plain"): string = ## Gets mimetype which corresponds to ``ext``. Returns ``default`` if ``ext`` - ## could not be found. - result = mimedb.mimes.getOrDefault(ext) + ## could not be found. ``ext`` can start with an optional dot which is ignored. + if ext.startsWith("."): + result = mimedb.mimes.getOrDefault(ext.substr(1)) + else: + result = mimedb.mimes.getOrDefault(ext) if result == "": return default diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 215a301b6..aad6ab3e8 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -58,7 +58,7 @@ ## You can then begin accepting connections using the ``accept`` procedure. ## ## .. code-block:: Nim -## var client = newSocket() +## var client = new Socket ## var address = "" ## while true: ## socket.acceptAddr(client, address) @@ -145,7 +145,7 @@ type SOBool* = enum ## Boolean socket options. OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive, - OptOOBInline, OptReuseAddr, OptReusePort + OptOOBInline, OptReuseAddr, OptReusePort, OptNoDelay ReadLineResult* = enum ## result for readLineAsync ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone @@ -857,15 +857,23 @@ proc close*(socket: Socket) = # shutdown i.e not wait for the peers "close notify" alert with a second # call to SSLShutdown let res = SSLShutdown(socket.sslHandle) - SSLFree(socket.sslHandle) - socket.sslHandle = nil if res == 0: discard elif res != 1: socketError(socket, res) finally: + when defineSsl: + if socket.isSSL and socket.sslHandle != nil: + SSLFree(socket.sslHandle) + socket.sslHandle = nil + socket.fd.close() +when defined(posix): + from posix import TCP_NODELAY +else: + from winlean import TCP_NODELAY + proc toCInt*(opt: SOBool): cint = ## Converts a ``SOBool`` into its Socket Option cint representation. case opt @@ -877,6 +885,7 @@ proc toCInt*(opt: SOBool): cint = of OptOOBInline: SO_OOBINLINE of OptReuseAddr: SO_REUSEADDR of OptReusePort: SO_REUSEPORT + of OptNoDelay: TCP_NODELAY proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {. tags: [ReadIOEffect].} = @@ -899,6 +908,12 @@ proc getPeerAddr*(socket: Socket): (string, Port) = proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {. tags: [WriteIOEffect].} = ## Sets option ``opt`` to a boolean value specified by ``value``. + ## + ## .. code-block:: Nim + ## var socket = newSocket() + ## socket.setSockOpt(OptReusePort, true) + ## socket.setSockOpt(OptNoDelay, true, level=IPPROTO_TCP.toInt) + ## var valuei = cint(if value: 1 else: 0) setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei) @@ -1120,11 +1135,11 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1, ## ## When 0 is returned the socket's connection has been closed. ## - ## This function will throw an EOS exception when an error occurs. A value + ## This function will throw an OSError exception when an error occurs. A value ## lower than 0 is never returned. ## ## A timeout may be specified in milliseconds, if enough data is not received - ## within the time specified an ETimeout exception will be raised. + ## within the time specified an TimeoutError exception will be raised. ## ## **Note**: ``data`` must be initialised. ## diff --git a/lib/pure/numeric.nim b/lib/pure/numeric.nim deleted file mode 100644 index ccda3a146..000000000 --- a/lib/pure/numeric.nim +++ /dev/null @@ -1,87 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Robert Persson -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warning:** This module will be moved out of the stdlib and into a -## Nimble package, don't use it. - -type OneVarFunction* = proc (x: float): float - -{.deprecated: [TOneVarFunction: OneVarFunction].} - -proc brent*(xmin,xmax:float, function:OneVarFunction, tol:float,maxiter=1000): - tuple[rootx, rooty: float, success: bool]= - ## Searches `function` for a root between `xmin` and `xmax` - ## using brents method. If the function value at `xmin`and `xmax` has the - ## same sign, `rootx`/`rooty` is set too the extrema value closest to x-axis - ## and succes is set to false. - ## Otherwise there exists at least one root and success is set to true. - ## This root is searched for at most `maxiter` iterations. - ## If `tol` tolerance is reached within `maxiter` iterations - ## the root refinement stops and success=true. - - # see http://en.wikipedia.org/wiki/Brent%27s_method - var - a=xmin - b=xmax - c=a - d=1.0e308 - fa=function(a) - fb=function(b) - fc=fa - s=0.0 - fs=0.0 - mflag:bool - i=0 - tmp2:float - - if fa*fb>=0: - if abs(fa)<abs(fb): - return (a,fa,false) - else: - return (b,fb,false) - - if abs(fa)<abs(fb): - swap(fa,fb) - swap(a,b) - - while fb!=0.0 and abs(a-b)>tol: - if fa!=fc and fb!=fc: # inverse quadratic interpolation - s = a * fb * fc / (fa - fb) / (fa - fc) + b * fa * fc / (fb - fa) / (fb - fc) + c * fa * fb / (fc - fa) / (fc - fb) - else: #secant rule - s = b - fb * (b - a) / (fb - fa) - tmp2 = (3.0 * a + b) / 4.0 - if not((s > tmp2 and s < b) or (s < tmp2 and s > b)) or - (mflag and abs(s - b) >= (abs(b - c) / 2.0)) or - (not mflag and abs(s - b) >= abs(c - d) / 2.0): - s=(a+b)/2.0 - mflag=true - else: - if (mflag and (abs(b - c) < tol)) or (not mflag and (abs(c - d) < tol)): - s=(a+b)/2.0 - mflag=true - else: - mflag=false - fs = function(s) - d = c - c = b - fc = fb - if fa * fs<0.0: - b=s - fb=fs - else: - a=s - fa=fs - if abs(fa)<abs(fb): - swap(a,b) - swap(fa,fb) - inc i - if i>maxiter: - break - - return (b,fb,true) diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index 60b53dbe0..427a68964 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -88,7 +88,7 @@ proc generatedTime*(oid: Oid): Time = var tmp: int32 var dummy = oid.time bigEndian32(addr(tmp), addr(dummy)) - result = Time(tmp) + result = fromUnix(tmp) when not defined(testing) and isMainModule: let xo = genOid() diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a1ae4e250..c18d03289 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -173,33 +173,33 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last modification time. when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_mtime + return fromUnix(res.st_mtime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64) findClose(h) -proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_atime + return fromUnix(res.st_atime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64) findClose(h) -proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = +proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. ## ## **Note:** Under POSIX OS's, the returned time may actually be the time at @@ -208,12 +208,12 @@ proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return res.st_ctime + return fromUnix(res.st_ctime.int64) else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) + result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64) findClose(h) proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = @@ -630,7 +630,7 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## the process has finished. To execute a program without having a ## shell involved, use the `execProcess` proc of the `osproc` ## module. - when defined(linux): + when defined(posix): result = c_system(command) shr 8 else: result = c_system(command) @@ -816,32 +816,40 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: k = getSymlinkFileKind(y) yield (k, y) -iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {. - tags: [ReadDirEffect].} = - ## Recursively walks over the directory `dir` and yields for each file in `dir`. - ## The full path for each file is returned. Directories are not returned. +iterator walkDirRec*(dir: string, yieldFilter = {pcFile}, + followFilter = {pcDir}): string {.tags: [ReadDirEffect].} = + ## Recursively walks over the directory `dir` and yields for each file + ## or directory in `dir`. + ## The full path for each file or directory is returned. ## **Warning**: ## Modifying the directory structure while the iterator ## is traversing may result in undefined behavior! ## - ## Walking is recursive. `filter` controls the behaviour of the iterator: + ## Walking is recursive. `filters` controls the behaviour of the iterator: ## ## --------------------- --------------------------------------------- - ## filter meaning + ## yieldFilter meaning ## --------------------- --------------------------------------------- ## ``pcFile`` yield real files ## ``pcLinkToFile`` yield symbolic links to files + ## ``pcDir`` yield real directories + ## ``pcLinkToDir`` yield symbolic links to directories + ## --------------------- --------------------------------------------- + ## + ## --------------------- --------------------------------------------- + ## followFilter meaning + ## --------------------- --------------------------------------------- ## ``pcDir`` follow real directories ## ``pcLinkToDir`` follow symbolic links to directories ## --------------------- --------------------------------------------- ## var stack = @[dir] while stack.len > 0: - for k,p in walkDir(stack.pop()): - if k in filter: - case k - of pcFile, pcLinkToFile: yield p - of pcDir, pcLinkToDir: stack.add(p) + for k, p in walkDir(stack.pop()): + if k in {pcDir, pcLinkToDir} and k in followFilter: + stack.add(p) + if k in yieldFilter: + yield p proc rawRemoveDir(dir: string) = when defined(windows): @@ -1195,14 +1203,15 @@ when defined(nimdoc): ## Returns the number of `command line arguments`:idx: given to the ## application. ## - ## If your binary was called without parameters this will return zero. You - ## can later query each individual paramater with `paramStr() <#paramStr>`_ + ## Unlike `argc`:idx: in C, if your binary was called without parameters this + ## will return zero. + ## You can query each individual paramater with `paramStr() <#paramStr>`_ ## or retrieve all of them in one go with `commandLineParams() ## <#commandLineParams>`_. ## - ## **Availability**: On Posix there is no portable way to get the command - ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `declared() <system.html#declared>`_. + ## **Availability**: When generating a dynamic library (see --app:lib) on + ## Posix this proc is not defined. + ## Test for availability using `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nim @@ -1219,13 +1228,14 @@ when defined(nimdoc): ## `paramCount() <#paramCount>`_ with this proc you can call the ## convenience `commandLineParams() <#commandLineParams>`_. ## - ## It is possible to call ``paramStr(0)`` but this will return OS specific + ## Similarly to `argv`:idx: in C, + ## it is possible to call ``paramStr(0)`` but this will return OS specific ## contents (usually the name of the invoked executable). You should avoid ## this and call `getAppFilename() <#getAppFilename>`_ instead. ## - ## **Availability**: On Posix there is no portable way to get the command - ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `declared() <system.html#declared>`_. + ## **Availability**: When generating a dynamic library (see --app:lib) on + ## Posix this proc is not defined. + ## Test for availability using `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nim @@ -1441,7 +1451,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = winlean.sleep(int32(milsecs)) else: var a, b: Timespec - a.tv_sec = Time(milsecs div 1000) + a.tv_sec = posix.Time(milsecs div 1000) a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 discard posix.nanosleep(a, b) @@ -1479,16 +1489,17 @@ type size*: BiggestInt # Size of file. permissions*: set[FilePermission] # File permissions linkCount*: BiggestInt # Number of hard links the file object has. - lastAccessTime*: Time # Time file was last accessed. - lastWriteTime*: Time # Time file was last modified/written to. - creationTime*: Time # Time file was created. Not supported on all systems! + lastAccessTime*: times.Time # Time file was last accessed. + lastWriteTime*: times.Time # Time file was last modified/written to. + creationTime*: times.Time # Time file was created. Not supported on all systems! template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = ## Transforms the native file info structure into the one nim uses. ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, ## or a 'Stat' structure on posix when defined(Windows): - template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics + template toTime(e: FILETIME): untyped {.gensym.} = + fromUnix(winTimeToUnixTime(rdFileTime(e)).int64) # local templates default to bind semantics template merge(a, b): untyped = a or (b shl 32) formalInfo.id.device = rawInfo.dwVolumeSerialNumber formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) @@ -1520,9 +1531,9 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) formalInfo.size = rawInfo.st_size formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt - formalInfo.lastAccessTime = rawInfo.st_atime - formalInfo.lastWriteTime = rawInfo.st_mtime - formalInfo.creationTime = rawInfo.st_ctime + formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64) + formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64) + formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64) result.permissions = {} checkAndIncludeMode(S_IRUSR, fpUserRead) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index dcb785c83..0d638abb9 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -558,3 +558,67 @@ proc expandTilde*(path: string): string {. result = getHomeDir() / path.substr(2) else: result = path + +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): + proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to shell. + when defined(windows): + return quoteShellWindows(s) + else: + return quoteShellPosix(s) + +when isMainModule: + assert quoteShellWindows("aaa") == "aaa" + 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("") == "''" diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 07429b9a9..1625845d1 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -15,6 +15,9 @@ include "system/inclrtl" import strutils, os, strtabs, streams, cpuinfo +from ospaths import quoteShell, quoteShellWindows, quoteShellPosix +export quoteShell, quoteShellWindows, quoteShellPosix + when defined(windows): import winlean else: @@ -38,10 +41,13 @@ type ## Windows: Named pipes are used so that you can peek ## at the process' output streams. poDemon ## Windows: The program creates no Window. + ## Unix: Start the program as a demon. This is still + ## work in progress! ProcessObj = object of RootObj when defined(windows): fProcessHandle: Handle + fThreadHandle: Handle inHandle, outHandle, errHandle: FileHandle id: Handle else: @@ -49,6 +55,7 @@ type inStream, outStream, errStream: Stream id: Pid exitStatus: cint + exitFlag: bool options: set[ProcessOption] Process* = ref ProcessObj ## represents an operating system process @@ -60,58 +67,6 @@ type const poUseShell* {.deprecated.} = poUsePath ## Deprecated alias for poUsePath. -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("'", "'\"'\"'") & "'" - -proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to shell. - when defined(Windows): - return quoteShellWindows(s) - elif defined(posix): - return quoteShellPosix(s) - else: - {.error:"quoteShell is not supported on your system".} - proc execProcess*(command: string, args: openArray[string] = [], env: StringTableRef = nil, @@ -216,8 +171,7 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl, ## On posix, if the process has exited because of a signal, 128 + signal ## number will be returned. - -proc peekExitCode*(p: Process): int {.tags: [].} +proc peekExitCode*(p: Process): int {.rtl, extern: "nosp$1", tags: [].} ## return -1 if the process is still running. Otherwise the process' exit code ## ## On posix, if the process has exited because of a signal, 128 + signal @@ -280,55 +234,98 @@ proc execProcesses*(cmds: openArray[string], ## executes the commands `cmds` in parallel. Creates `n` processes ## that execute in parallel. The highest return value of all processes ## is returned. Runs `beforeRunEvent` before running each command. - when defined(posix): - # poParentStreams causes problems on Posix, so we simply disable it: - var options = options - {poParentStreams} assert n > 0 if n > 1: - var q: seq[Process] - newSeq(q, n) - var m = min(n, cmds.len) - for i in 0..m-1: + var i = 0 + var q = newSeq[Process](n) + + when defined(windows): + var w: WOHandleArray + var m = min(min(n, MAXIMUM_WAIT_OBJECTS), cmds.len) + var wcount = m + else: + var m = min(n, cmds.len) + + while i < m: if beforeRunEvent != nil: beforeRunEvent(i) - q[i] = startProcess(cmds[i], options=options + {poEvalCommand}) - when defined(noBusyWaiting): - var r = 0 - for i in m..high(cmds): - when defined(debugExecProcesses): - var err = "" - var outp = outputStream(q[r]) - while running(q[r]) or not atEnd(outp): - err.add(outp.readLine()) - err.add("\n") - echo(err) - result = max(waitForExit(q[r]), result) - if afterRunEvent != nil: afterRunEvent(r, q[r]) - if q[r] != nil: close(q[r]) - if beforeRunEvent != nil: - beforeRunEvent(i) - q[r] = startProcess(cmds[i], options=options + {poEvalCommand}) - r = (r + 1) mod n - else: - var i = m - while i <= high(cmds): - sleep(50) - for r in 0..n-1: - if not running(q[r]): - #echo(outputStream(q[r]).readLine()) - result = max(waitForExit(q[r]), result) - if afterRunEvent != nil: afterRunEvent(r, q[r]) - if q[r] != nil: close(q[r]) - if beforeRunEvent != nil: - beforeRunEvent(i) - q[r] = startProcess(cmds[i], options=options + {poEvalCommand}) - inc(i) - if i > high(cmds): break - for j in 0..m-1: - result = max(waitForExit(q[j]), result) - if afterRunEvent != nil: afterRunEvent(j, q[j]) - if q[j] != nil: close(q[j]) + q[i] = startProcess(cmds[i], options = options + {poEvalCommand}) + when defined(windows): + w[i] = q[i].fProcessHandle + inc(i) + + var ecount = len(cmds) + while ecount > 0: + var rexit = -1 + when defined(windows): + # waiting for all children, get result if any child exits + var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32, + INFINITE) + if ret == WAIT_TIMEOUT: + # must not be happen + discard + elif ret == WAIT_FAILED: + raiseOSError(osLastError()) + else: + var status: int32 + for r in 0..m-1: + if not isNil(q[r]) and q[r].fProcessHandle == w[ret]: + discard getExitCodeProcess(q[r].fProcessHandle, status) + q[r].exitFlag = true + q[r].exitStatus = status + rexit = r + break + else: + var status: cint = 1 + # waiting for all children, get result if any child exits + let res = waitpid(-1, status, 0) + if res > 0: + for r in 0..m-1: + if not isNil(q[r]) and q[r].id == res: + if WIFEXITED(status) or WIFSIGNALED(status): + q[r].exitFlag = true + q[r].exitStatus = status + rexit = r + break + else: + let err = osLastError() + if err == OSErrorCode(ECHILD): + # some child exits, we need to check our childs exit codes + for r in 0..m-1: + if (not isNil(q[r])) and (not running(q[r])): + q[r].exitFlag = true + q[r].exitStatus = status + rexit = r + break + elif err == OSErrorCode(EINTR): + # signal interrupted our syscall, lets repeat it + continue + else: + # all other errors are exceptions + raiseOSError(err) + + if rexit >= 0: + result = max(result, q[rexit].peekExitCode()) + if afterRunEvent != nil: afterRunEvent(rexit, q[rexit]) + close(q[rexit]) + if i < len(cmds): + if beforeRunEvent != nil: beforeRunEvent(i) + q[rexit] = startProcess(cmds[i], + options = options + {poEvalCommand}) + when defined(windows): + w[rexit] = q[rexit].fProcessHandle + inc(i) + else: + when defined(windows): + for k in 0..wcount - 1: + if w[k] == q[rexit].fProcessHandle: + w[k] = w[wcount - 1] + w[wcount - 1] = 0 + dec(wcount) + break + q[rexit] = nil + dec(ecount) else: for i in 0..high(cmds): if beforeRunEvent != nil: @@ -370,6 +367,8 @@ when not defined(useNimRtl): elif not running(p): break close(p) +template streamAccess(p) = + assert poParentStreams notin p.options, "API usage error: stream access not allowed when you use poParentStreams" when defined(Windows) and not defined(useNimRtl): # We need to implement a handle stream for Windows: @@ -513,6 +512,7 @@ when defined(Windows) and not defined(useNimRtl): hi, ho, he: Handle new(result) result.options = options + result.exitFlag = true si.cb = sizeof(si).cint if poParentStreams notin options: si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or @@ -581,28 +581,31 @@ when defined(Windows) and not defined(useNimRtl): "Requested command not found: '$1'. OS error:" % command) else: raiseOSError(lastError, command) - # Close the handle now so anyone waiting is woken: - discard closeHandle(procInfo.hThread) result.fProcessHandle = procInfo.hProcess + result.fThreadHandle = procInfo.hThread result.id = procInfo.dwProcessId + result.exitFlag = false proc close(p: Process) = - if poInteractive in p.options: - # somehow this is not always required on Windows: + if poParentStreams notin p.options: discard closeHandle(p.inHandle) discard closeHandle(p.outHandle) discard closeHandle(p.errHandle) - #discard closeHandle(p.FProcessHandle) + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) proc suspend(p: Process) = - discard suspendThread(p.fProcessHandle) + discard suspendThread(p.fThreadHandle) proc resume(p: Process) = - discard resumeThread(p.fProcessHandle) + discard resumeThread(p.fThreadHandle) proc running(p: Process): bool = - var x = waitForSingleObject(p.fProcessHandle, 50) - return x == WAIT_TIMEOUT + if p.exitFlag: + return false + else: + var x = waitForSingleObject(p.fProcessHandle, 0) + return x == WAIT_TIMEOUT proc terminate(p: Process) = if running(p): @@ -612,28 +615,48 @@ when defined(Windows) and not defined(useNimRtl): terminate(p) proc waitForExit(p: Process, timeout: int = -1): int = - discard waitForSingleObject(p.fProcessHandle, timeout.int32) - - var res: int32 - discard getExitCodeProcess(p.fProcessHandle, res) - result = res - discard closeHandle(p.fProcessHandle) + if p.exitFlag: + return p.exitStatus + + let res = waitForSingleObject(p.fProcessHandle, timeout.int32) + if res == WAIT_TIMEOUT: + terminate(p) + var status: int32 + discard getExitCodeProcess(p.fProcessHandle, status) + if status != STILL_ACTIVE: + p.exitFlag = true + p.exitStatus = status + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) + result = status + else: + result = -1 proc peekExitCode(p: Process): int = - var b = waitForSingleObject(p.fProcessHandle, 50) == WAIT_TIMEOUT - if b: result = -1 - else: - var res: int32 - discard getExitCodeProcess(p.fProcessHandle, res) - return res + if p.exitFlag: + return p.exitStatus + + result = -1 + var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT + if not b: + var status: int32 + discard getExitCodeProcess(p.fProcessHandle, status) + p.exitFlag = true + p.exitStatus = status + discard closeHandle(p.fThreadHandle) + discard closeHandle(p.fProcessHandle) + result = status proc inputStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.inHandle) proc outputStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.outHandle) proc errorStream(p: Process): Stream = + streamAccess(p) result = newFileHandleStream(p.errHandle) proc execCmd(command: string): int = @@ -729,9 +752,7 @@ elif not defined(useNimRtl): sysEnv: cstringArray workingDir: cstring pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] - optionPoUsePath: bool - optionPoParentStreams: bool - optionPoStdErrToStdOut: bool + options: set[ProcessOption] {.deprecated: [TStartProcessData: StartProcessData].} const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and @@ -756,7 +777,8 @@ elif not defined(useNimRtl): pStdin, pStdout, pStderr: array[0..1, cint] new(result) result.options = options - result.exitStatus = -3 # for ``waitForExit`` + result.exitFlag = true + if poParentStreams notin options: if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or pipe(pStderr) != 0'i32: @@ -765,7 +787,9 @@ elif not defined(useNimRtl): var sysCommand: string var sysArgsRaw: seq[string] if poEvalCommand in options: - const useShPath {.strdefine.} = "/bin/sh" + const useShPath {.strdefine.} = + when not defined(android): "/bin/sh" + else: "/system/bin/sh" sysCommand = useShPath sysArgsRaw = @[sysCommand, "-c", command] assert args.len == 0, "`args` has to be empty when using poEvalCommand." @@ -794,10 +818,8 @@ elif not defined(useNimRtl): data.pStdin = pStdin data.pStdout = pStdout data.pStderr = pStderr - data.optionPoParentStreams = poParentStreams in options - data.optionPoUsePath = poUsePath in options - data.optionPoStdErrToStdOut = poStdErrToStdOut in options data.workingDir = workingDir + data.options = options when useProcessAuxSpawn: var currentDir = getCurrentDir() @@ -811,6 +833,7 @@ elif not defined(useNimRtl): if poEchoCmd in options: echo(command, " ", join(args, " ")) result.id = pid + result.exitFlag = false if poParentStreams in options: # does not make much sense, but better than nothing: @@ -837,7 +860,7 @@ elif not defined(useNimRtl): var attr: Tposix_spawnattr var fops: Tposix_spawn_file_actions - template chck(e: expr) = + template chck(e: untyped) = if e != 0'i32: raiseOSError(osLastError()) chck posix_spawn_file_actions_init(fops) @@ -846,19 +869,22 @@ elif not defined(useNimRtl): var mask: Sigset chck sigemptyset(mask) chck posix_spawnattr_setsigmask(attr, mask) - chck posix_spawnattr_setpgroup(attr, 0'i32) + if poDemon in data.options: + chck posix_spawnattr_setpgroup(attr, 0'i32) - chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or - POSIX_SPAWN_SETSIGMASK or - POSIX_SPAWN_SETPGROUP) + var flags = POSIX_SPAWN_USEVFORK or + POSIX_SPAWN_SETSIGMASK + if poDemon in data.options: + flags = flags or POSIX_SPAWN_SETPGROUP + chck posix_spawnattr_setflags(attr, flags) - if not data.optionPoParentStreams: + if not (poParentStreams in data.options): chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) - if data.optionPoStdErrToStdOut: + if (poStdErrToStdOut in data.options): chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) else: chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) @@ -868,7 +894,7 @@ elif not defined(useNimRtl): setCurrentDir($data.workingDir) var pid: Pid - if data.optionPoUsePath: + if (poUsePath in data.options): res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) else: res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) @@ -930,7 +956,7 @@ elif not defined(useNimRtl): # Warning: no GC here! # Or anything that touches global structures - all called nim procs # must be marked with stackTrace:off. Inspect C code after making changes. - if not data.optionPoParentStreams: + if not (poParentStreams in data.options): discard close(data.pStdin[writeIdx]) if dup2(data.pStdin[readIdx], readIdx) < 0: startProcessFail(data) @@ -938,7 +964,7 @@ elif not defined(useNimRtl): if dup2(data.pStdout[writeIdx], writeIdx) < 0: startProcessFail(data) discard close(data.pStderr[readIdx]) - if data.optionPoStdErrToStdOut: + if (poStdErrToStdOut in data.options): if dup2(data.pStdout[writeIdx], 2) < 0: startProcessFail(data) else: @@ -952,7 +978,7 @@ elif not defined(useNimRtl): discard close(data.pErrorPipe[readIdx]) discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) - if data.optionPoUsePath: + if (poUsePath in data.options): when defined(uClibc) or defined(linux): # uClibc environment (OpenWrt included) doesn't have the full execvpe let exe = findExe(data.sysCommand) @@ -984,19 +1010,22 @@ elif not defined(useNimRtl): if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError()) proc running(p: Process): bool = - var ret : int - var status : cint = 1 - ret = waitpid(p.id, status, WNOHANG) - if ret == int(p.id): - if isExitStatus(status): - p.exitStatus = status - return false - else: - return true - elif ret == 0: - return true # Can't establish status. Assume running. - else: + if p.exitFlag: return false + else: + var status: cint = 1 + let ret = waitpid(p.id, status, WNOHANG) + if ret == int(p.id): + if isExitStatus(status): + p.exitFlag = true + p.exitStatus = status + return false + else: + return true + elif ret == 0: + return true # Can't establish status. Assume running. + else: + raiseOSError(osLastError()) proc terminate(p: Process) = if kill(p.id, SIGTERM) != 0'i32: @@ -1011,13 +1040,14 @@ elif not defined(useNimRtl): import kqueue, times proc waitForExit(p: Process, timeout: int = -1): int = - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) if timeout == -1: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status else: var kqFD = kqueue() @@ -1030,15 +1060,15 @@ elif not defined(useNimRtl): var tmspec: Timespec if timeout >= 1000: - tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_sec = posix.Time(timeout div 1_000) tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tmspec.tv_sec = 0.Time + tmspec.tv_sec = posix.Time(0) tmspec.tv_nsec = (timeout * 1_000_000) try: while true: - var status : cint = 1 + var status: cint = 1 var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1, addr(tmspec)) if count < 0: @@ -1051,12 +1081,14 @@ elif not defined(useNimRtl): raiseOSError(osLastError()) if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC: if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1077,36 +1109,33 @@ elif not defined(useNimRtl): var b: Timespec b.tv_sec = e.tv_sec b.tv_nsec = e.tv_nsec - e.tv_sec = (e.tv_sec - s.tv_sec).Time + e.tv_sec = e.tv_sec - s.tv_sec if e.tv_nsec >= s.tv_nsec: e.tv_nsec -= s.tv_nsec else: - if e.tv_sec == 0.Time: + if e.tv_sec == posix.Time(0): raise newException(ValueError, "System time was modified") else: diff = s.tv_nsec - e.tv_nsec e.tv_nsec = 1_000_000_000 - diff - t.tv_sec = (t.tv_sec - e.tv_sec).Time + t.tv_sec = t.tv_sec - e.tv_sec if t.tv_nsec >= e.tv_nsec: t.tv_nsec -= e.tv_nsec else: - t.tv_sec = (int(t.tv_sec) - 1).Time + t.tv_sec = t.tv_sec - posix.Time(1) diff = e.tv_nsec - t.tv_nsec t.tv_nsec = 1_000_000_000 - diff s.tv_sec = b.tv_sec s.tv_nsec = b.tv_nsec - #if waitPid(p.id, p.exitStatus, 0) == int(p.id): - # ``waitPid`` fails if the process is not running anymore. But then - # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is - # initialized with -3, wrong success exit codes are prevented. - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) if timeout == -1: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status else: var nmask, omask: Sigset @@ -1125,10 +1154,10 @@ elif not defined(useNimRtl): raiseOSError(osLastError()) if timeout >= 1000: - tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_sec = posix.Time(timeout div 1_000) tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 else: - tmspec.tv_sec = 0.Time + tmspec.tv_sec = posix.Time(0) tmspec.tv_nsec = (timeout * 1_000_000) try: @@ -1138,9 +1167,10 @@ elif not defined(useNimRtl): let res = sigtimedwait(nmask, sinfo, tmspec) if res == SIGCHLD: if sinfo.si_pid == p.id: - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1161,9 +1191,10 @@ elif not defined(useNimRtl): # timeout expired, so we trying to kill process if posix.kill(p.id, SIGKILL) == -1: raiseOSError(osLastError()) - var status : cint = 1 + var status: cint = 1 if waitpid(p.id, status, 0) < 0: raiseOSError(osLastError()) + p.exitFlag = true p.exitStatus = status break else: @@ -1181,12 +1212,13 @@ elif not defined(useNimRtl): proc peekExitCode(p: Process): int = var status = cint(0) result = -1 - if p.exitStatus != -3: + if p.exitFlag: return exitStatus(p.exitStatus) var ret = waitpid(p.id, status, WNOHANG) if ret > 0: if isExitStatus(status): + p.exitFlag = true p.exitStatus = status result = exitStatus(status) @@ -1197,16 +1229,19 @@ elif not defined(useNimRtl): stream = newFileStream(f) proc inputStream(p: Process): Stream = + streamAccess(p) if p.inStream == nil: createStream(p.inStream, p.inHandle, fmWrite) return p.inStream proc outputStream(p: Process): Stream = + streamAccess(p) if p.outStream == nil: createStream(p.outStream, p.outHandle, fmRead) return p.outStream proc errorStream(p: Process): Stream = + streamAccess(p) if p.errStream == nil: createStream(p.errStream, p.errHandle, fmRead) return p.errStream @@ -1287,16 +1322,3 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = { result[1] = peekExitCode(p) if result[1] != -1: break close(p) - -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("") == "''" diff --git a/lib/pure/parsecsv.nim b/lib/pure/parsecsv.nim index 77b145a73..071858b7c 100644 --- a/lib/pure/parsecsv.nim +++ b/lib/pure/parsecsv.nim @@ -32,7 +32,7 @@ ## import parsecsv ## import os ## # Prepare a file -## var csv_content = """One,Two,Three,Four +## var content = """One,Two,Three,Four ## 1,2,3,4 ## 10,20,30,40 ## 100,200,300,400 @@ -72,7 +72,10 @@ proc raiseEInvalidCsv(filename: string, line, col: int, msg: string) {.noreturn.} = var e: ref CsvError new(e) - e.msg = filename & "(" & $line & ", " & $col & ") Error: " & msg + if filename.len == 0: + e.msg = "Error: " & msg + else: + e.msg = filename & "(" & $line & ", " & $col & ") Error: " & msg raise e proc error(my: CsvParser, pos: int, msg: string) = diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 2e8dbe140..a2ff9bf0c 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -35,7 +35,7 @@ type cmd: seq[string] pos: int remainingShortOptions: string - kind*: CmdLineKind ## the dected command line token + kind*: CmdLineKind ## the detected command line token key*, val*: TaintedString ## key and value pair; ``key`` is the option ## or the argument, ``value`` is not "" if ## the option was given a value diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 00d007d01..ae192ab9a 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -55,6 +55,13 @@ const ";", ":", ",", "(", ")", "[", "]", "." ] + reservedKeywords = @[ + # statements + "select", "from", "where", "group", "limit", "having", + # functions + "count", + ] + proc open(L: var SqlLexer, input: Stream, filename: string) = lexbase.open(L, input) L.filename = filename @@ -274,16 +281,16 @@ proc getSymbol(c: var SqlLexer, tok: var Token) = c.bufpos = pos tok.kind = tkIdentifier -proc getQuotedIdentifier(c: var SqlLexer, tok: var Token) = +proc getQuotedIdentifier(c: var SqlLexer, tok: var Token, quote='\"') = var pos = c.bufpos + 1 var buf = c.buf tok.kind = tkQuotedIdentifier while true: var ch = buf[pos] - if ch == '\"': - if buf[pos+1] == '\"': + if ch == quote: + if buf[pos+1] == quote: inc(pos, 2) - add(tok.literal, '\"') + add(tok.literal, quote) else: inc(pos) break @@ -442,7 +449,8 @@ proc getTok(c: var SqlLexer, tok: var Token) = add(tok.literal, '.') of '0'..'9': getNumeric(c, tok) of '\'': getString(c, tok, tkStringConstant) - of '"': getQuotedIdentifier(c, tok) + of '"': getQuotedIdentifier(c, tok, '"') + of '`': getQuotedIdentifier(c, tok, '`') of lexbase.EndOfFile: tok.kind = tkEof tok.literal = "[EOF]" @@ -450,7 +458,7 @@ proc getTok(c: var SqlLexer, tok: var Token) = '\128'..'\255': getSymbol(c, tok) of '+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', - '^', '&', '|', '`', '?': + '^', '&', '|', '?': getOperator(c, tok) else: add(tok.literal, c.buf[c.bufpos]) @@ -462,27 +470,27 @@ proc errorStr(L: SqlLexer, msg: string): string = # ----------------------------- parser ---------------------------------------- -# Operator/Element Associativity Description -# . left table/column name separator -# :: left PostgreSQL-style typecast -# [ ] left array element selection -# - right unary minus -# ^ left exponentiation -# * / % left multiplication, division, modulo -# + - left addition, subtraction -# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL -# ISNULL test for null -# NOTNULL test for not null -# (any other) left all other native and user-defined oprs -# IN set membership -# BETWEEN range containment -# OVERLAPS time interval overlap -# LIKE ILIKE SIMILAR string pattern matching -# < > less than, greater than -# = right equality, assignment -# NOT right logical negation -# AND left logical conjunction -# OR left logical disjunction +# Operator/Element Associativity Description +# . left table/column name separator +# :: left PostgreSQL-style typecast +# [ ] left array element selection +# - right unary minus +# ^ left exponentiation +# * / % left multiplication, division, modulo +# + - left addition, subtraction +# IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL +# ISNULL test for null +# NOTNULL test for not null +# (any other) left all other native and user-defined oprs +# IN set membership +# BETWEEN range containment +# OVERLAPS time interval overlap +# LIKE ILIKE SIMILAR string pattern matching +# < > less than, greater than +# = right equality, assignment +# NOT right logical negation +# AND left logical conjunction +# OR left logical disjunction type SqlNodeKind* = enum ## kind of SQL abstract syntax tree @@ -504,6 +512,7 @@ type nkPrefix, nkInfix, nkCall, + nkPrGroup, nkColumnReference, nkReferences, nkDefault, @@ -518,11 +527,15 @@ type nkSelect, nkSelectDistinct, nkSelectColumns, + nkSelectPair, nkAsgn, nkFrom, + nkFromItemPair, nkGroup, + nkLimit, nkHaving, nkOrder, + nkJoin, nkDesc, nkUnion, nkIntersect, @@ -658,10 +671,12 @@ proc getPrecedence(p: SqlParser): int = elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or isKeyw(p, "like"): - result = 3 + result = 4 elif isKeyw(p, "and"): - result = 2 + result = 3 elif isKeyw(p, "or"): + result = 2 + elif isKeyw(p, "between"): result = 1 elif p.tok.kind == tkOperator: # user-defined operator: @@ -670,6 +685,7 @@ proc getPrecedence(p: SqlParser): int = result = - 1 proc parseExpr(p: var SqlParser): SqlNode +proc parseSelect(p: var SqlParser): SqlNode proc identOrLiteral(p: var SqlParser): SqlNode = case p.tok.kind @@ -693,7 +709,8 @@ proc identOrLiteral(p: var SqlParser): SqlNode = getTok(p) of tkParLe: getTok(p) - result = parseExpr(p) + result = newNode(nkPrGroup) + result.add(parseExpr(p)) eat(p, tkParRi) else: sqlError(p, "expression expected") @@ -745,7 +762,7 @@ proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = result = opPred while opPred > limit: node = newNode(nkInfix) - opNode = newNode(nkIdent, p.tok.literal) + opNode = newNode(nkIdent, p.tok.literal.toLower()) getTok(p) result = lowestExprAux(p, v2, opPred) node.add(opNode) @@ -921,6 +938,19 @@ proc parseWhere(p: var SqlParser): SqlNode = result = newNode(nkWhere) result.add(parseExpr(p)) +proc parseFromItem(p: var SqlParser): SqlNode = + result = newNode(nkFromItemPair) + if p.tok.kind == tkParLe: + getTok(p) + var select = parseSelect(p) + result.add(select) + eat(p, tkParRi) + else: + result.add(parseExpr(p)) + if isKeyw(p, "as"): + getTok(p) + result.add(parseExpr(p)) + proc parseIndexDef(p: var SqlParser): SqlNode = result = parseIfNotExists(p, nkCreateIndex) if isKeyw(p, "primary"): @@ -956,6 +986,7 @@ proc parseInsert(p: var SqlParser): SqlNode = if p.tok.kind == tkParLe: var n = newNode(nkColumnList) parseParIdentList(p, n) + result.add n else: result.add(nil) if isKeyw(p, "default"): @@ -996,6 +1027,8 @@ proc parseUpdate(p: var SqlParser): SqlNode = proc parseDelete(p: var SqlParser): SqlNode = getTok(p) + if isOpr(p, "*"): + getTok(p) result = newNode(nkDelete) eat(p, "from") result.add(primary(p)) @@ -1018,7 +1051,12 @@ proc parseSelect(p: var SqlParser): SqlNode = a.add(newNode(nkIdent, "*")) getTok(p) else: - a.add(parseExpr(p)) + var pair = newNode(nkSelectPair) + pair.add(parseExpr(p)) + a.add(pair) + if isKeyw(p, "as"): + getTok(p) + pair.add(parseExpr(p)) if p.tok.kind != tkComma: break getTok(p) result.add(a) @@ -1026,7 +1064,7 @@ proc parseSelect(p: var SqlParser): SqlNode = var f = newNode(nkFrom) while true: getTok(p) - f.add(parseExpr(p)) + f.add(parseFromItem(p)) if p.tok.kind != tkComma: break result.add(f) if isKeyw(p, "where"): @@ -1040,6 +1078,11 @@ proc parseSelect(p: var SqlParser): SqlNode = if p.tok.kind != tkComma: break getTok(p) result.add(g) + if isKeyw(p, "limit"): + getTok(p) + var l = newNode(nkLimit) + l.add(parseExpr(p)) + result.add(l) if isKeyw(p, "having"): var h = newNode(nkHaving) while true: @@ -1072,6 +1115,19 @@ proc parseSelect(p: var SqlParser): SqlNode = if p.tok.kind != tkComma: break getTok(p) result.add(n) + if isKeyw(p, "join") or isKeyw(p, "inner") or isKeyw(p, "outer") or isKeyw(p, "cross"): + var join = newNode(nkJoin) + result.add(join) + if isKeyw(p, "join"): + join.add(newNode(nkIdent, "")) + getTok(p) + else: + join.add(newNode(nkIdent, p.tok.literal.toLower())) + getTok(p) + eat(p, "join") + join.add(parseFromItem(p)) + eat(p, "on") + join.add(parseExpr(p)) proc parseStmt(p: var SqlParser; parent: SqlNode) = if isKeyw(p, "create"): @@ -1103,7 +1159,7 @@ proc parseStmt(p: var SqlParser; parent: SqlNode) = elif isKeyw(p, "begin"): getTok(p) else: - sqlError(p, "CREATE expected") + sqlError(p, "SELECT, CREATE, UPDATE or DELETE expected") proc open(p: var SqlParser, input: Stream, filename: string) = ## opens the parser `p` and assigns the input stream `input` to it. @@ -1115,13 +1171,13 @@ proc open(p: var SqlParser, input: Stream, filename: string) = proc parse(p: var SqlParser): SqlNode = ## parses the content of `p`'s input stream and returns the SQL AST. - ## Syntax errors raise an `EInvalidSql` exception. + ## Syntax errors raise an `SqlParseError` exception. result = newNode(nkStmtList) while p.tok.kind != tkEof: parseStmt(p, result) + if p.tok.kind == tkEof: + break eat(p, tkSemicolon) - if result.len == 1: - result = result.sons[0] proc close(p: var SqlParser) = ## closes the parser `p`. The associated input stream is closed too. @@ -1130,7 +1186,7 @@ proc close(p: var SqlParser) = proc parseSQL*(input: Stream, filename: string): SqlNode = ## parses the SQL from `input` into an AST and returns the AST. ## `filename` is only used for error messages. - ## Syntax errors raise an `EInvalidSql` exception. + ## Syntax errors raise an `SqlParseError` exception. var p: SqlParser open(p, input, filename) try: @@ -1138,29 +1194,74 @@ proc parseSQL*(input: Stream, filename: string): SqlNode = finally: close(p) -proc ra(n: SqlNode, s: var string, indent: int) +proc parseSQL*(input: string, filename=""): SqlNode = + ## parses the SQL from `input` into an AST and returns the AST. + ## `filename` is only used for error messages. + ## Syntax errors raise an `SqlParseError` exception. + parseSQL(newStringStream(input), "") + + +type + SqlWriter = object + indent: int + upperCase: bool + buffer: string + +proc add(s: var SqlWriter, thing: char) = + s.buffer.add(thing) + +proc add(s: var SqlWriter, thing: string) = + if s.buffer.len > 0 and s.buffer[^1] notin {' ', '\L', '(', '.'}: + s.buffer.add(" ") + s.buffer.add(thing) + +proc addKeyw(s: var SqlWriter, thing: string) = + var keyw = thing + if s.upperCase: + keyw = keyw.toUpper() + s.add(keyw) + +proc addIden(s: var SqlWriter, thing: string) = + var iden = thing + if iden.toLower() in reservedKeywords: + iden = '"' & iden & '"' + s.add(iden) + +proc ra(n: SqlNode, s: var SqlWriter) + +proc rs(n: SqlNode, s: var SqlWriter, prefix = "(", suffix = ")", sep = ", ") = + if n.len > 0: + s.add(prefix) + for i in 0 .. n.len-1: + if i > 0: s.add(sep) + ra(n.sons[i], s) + s.add(suffix) + +proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',') = + if n.len > 0: + for i in 0 .. n.len-1: + if i > 0: s.add(sep) + ra(n.sons[i], s) -proc rs(n: SqlNode, s: var string, indent: int, - prefix = "(", suffix = ")", - sep = ", ") = +proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',', prefix, suffix: char) = if n.len > 0: s.add(prefix) for i in 0 .. n.len-1: if i > 0: s.add(sep) - ra(n.sons[i], s, indent) + ra(n.sons[i], s) s.add(suffix) -proc ra(n: SqlNode, s: var string, indent: int) = +proc ra(n: SqlNode, s: var SqlWriter) = if n == nil: return case n.kind of nkNone: discard of nkIdent: - if allCharsInSet(n.strVal, {'\33'..'\127'}): + if allCharsInSet(n.strVal, {'\33'..'\127'}) and n.strVal.toLower() notin reservedKeywords: s.add(n.strVal) else: s.add("\"" & replace(n.strVal, "\"", "\"\"") & "\"") of nkStringLit: - s.add(escape(n.strVal, "e'", "'")) + s.add(escape(n.strVal, "'", "'")) of nkBitStringLit: s.add("b'" & n.strVal & "'") of nkHexStringLit: @@ -1168,217 +1269,206 @@ proc ra(n: SqlNode, s: var string, indent: int) = of nkIntegerLit, nkNumericLit: s.add(n.strVal) of nkPrimaryKey: - s.add(" primary key") - rs(n, s, indent) + s.addKeyw("primary key") + rs(n, s) of nkForeignKey: - s.add(" foreign key") - rs(n, s, indent) + s.addKeyw("foreign key") + rs(n, s) of nkNotNull: - s.add(" not null") + s.addKeyw("not null") of nkNull: - s.add(" null") + s.addKeyw("null") of nkDot: - ra(n.sons[0], s, indent) - s.add(".") - ra(n.sons[1], s, indent) + ra(n.sons[0], s) + s.add('.') + ra(n.sons[1], s) of nkDotDot: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(". .") - ra(n.sons[1], s, indent) + ra(n.sons[1], s) of nkPrefix: - s.add('(') - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(' ') - ra(n.sons[1], s, indent) - s.add(')') + ra(n.sons[1], s) of nkInfix: - s.add('(') - ra(n.sons[1], s, indent) + ra(n.sons[1], s) s.add(' ') - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(' ') - ra(n.sons[2], s, indent) - s.add(')') + ra(n.sons[2], s) of nkCall, nkColumnReference: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add('(') for i in 1..n.len-1: - if i > 1: s.add(", ") - ra(n.sons[i], s, indent) + if i > 1: s.add(',') + ra(n.sons[i], s) + s.add(')') + of nkPrGroup: + s.add('(') + s.addMulti(n) s.add(')') of nkReferences: - s.add(" references ") - ra(n.sons[0], s, indent) + s.addKeyw("references") + ra(n.sons[0], s) of nkDefault: - s.add(" default ") - ra(n.sons[0], s, indent) + s.addKeyw("default") + ra(n.sons[0], s) of nkCheck: - s.add(" check ") - ra(n.sons[0], s, indent) + s.addKeyw("check") + ra(n.sons[0], s) of nkConstraint: - s.add(" constraint ") - ra(n.sons[0], s, indent) - s.add(" check ") - ra(n.sons[1], s, indent) + s.addKeyw("constraint") + ra(n.sons[0], s) + s.addKeyw("check") + ra(n.sons[1], s) of nkUnique: - s.add(" unique") - rs(n, s, indent) + s.addKeyw("unique") + rs(n, s) of nkIdentity: - s.add(" identity") + s.addKeyw("identity") of nkColumnDef: - s.add("\n ") - rs(n, s, indent, "", "", " ") + rs(n, s, "", "", " ") of nkStmtList: for i in 0..n.len-1: - ra(n.sons[i], s, indent) - s.add("\n") + ra(n.sons[i], s) + s.add(';') of nkInsert: assert n.len == 3 - s.add("insert into ") - ra(n.sons[0], s, indent) - ra(n.sons[1], s, indent) + s.addKeyw("insert into") + ra(n.sons[0], s) + s.add(' ') + ra(n.sons[1], s) if n.sons[2].kind == nkDefault: - s.add("default values") + s.addKeyw("default values") else: - s.add("\nvalues ") - ra(n.sons[2], s, indent) - s.add(';') + ra(n.sons[2], s) of nkUpdate: - s.add("update ") - ra(n.sons[0], s, indent) - s.add(" set ") + s.addKeyw("update") + ra(n.sons[0], s) + s.addKeyw("set") var L = n.len for i in 1 .. L-2: if i > 1: s.add(", ") var it = n.sons[i] assert it.kind == nkAsgn - ra(it, s, indent) - ra(n.sons[L-1], s, indent) - s.add(';') + ra(it, s) + ra(n.sons[L-1], s) of nkDelete: - s.add("delete from ") - ra(n.sons[0], s, indent) - ra(n.sons[1], s, indent) - s.add(';') + s.addKeyw("delete from") + ra(n.sons[0], s) + ra(n.sons[1], s) of nkSelect, nkSelectDistinct: - s.add("select ") + s.addKeyw("select") if n.kind == nkSelectDistinct: - s.add("distinct ") - rs(n.sons[0], s, indent, "", "", ", ") - for i in 1 .. n.len-1: ra(n.sons[i], s, indent) - s.add(';') + s.addKeyw("distinct") + s.addMulti(n.sons[0]) + for i in 1 .. n.len-1: + ra(n.sons[i], s) of nkSelectColumns: assert(false) + of nkSelectPair: + ra(n.sons[0], s) + if n.sons.len == 2: + s.addKeyw("as") + ra(n.sons[1], s) + of nkFromItemPair: + if n.sons[0].kind == nkIdent: + ra(n.sons[0], s) + else: + assert n.sons[0].kind == nkSelect + s.add('(') + ra(n.sons[0], s) + s.add(')') + if n.sons.len == 2: + s.addKeyw("as") + ra(n.sons[1], s) of nkAsgn: - ra(n.sons[0], s, indent) + ra(n.sons[0], s) s.add(" = ") - ra(n.sons[1], s, indent) + ra(n.sons[1], s) of nkFrom: - s.add("\nfrom ") - rs(n, s, indent, "", "", ", ") + s.addKeyw("from") + s.addMulti(n) of nkGroup: - s.add("\ngroup by") - rs(n, s, indent, "", "", ", ") + s.addKeyw("group by") + s.addMulti(n) + of nkLimit: + s.addKeyw("limit") + s.addMulti(n) of nkHaving: - s.add("\nhaving") - rs(n, s, indent, "", "", ", ") + s.addKeyw("having") + s.addMulti(n) of nkOrder: - s.add("\norder by ") - rs(n, s, indent, "", "", ", ") + s.addKeyw("order by") + s.addMulti(n) + of nkJoin: + var joinType = n.sons[0].strVal + if joinType == "": + joinType = "join" + else: + joinType &= " " & "join" + s.addKeyw(joinType) + ra(n.sons[1], s) + s.addKeyw("on") + ra(n.sons[2], s) of nkDesc: - ra(n.sons[0], s, indent) - s.add(" desc") + ra(n.sons[0], s) + s.addKeyw("desc") of nkUnion: - s.add(" union") + s.addKeyw("union") of nkIntersect: - s.add(" intersect") + s.addKeyw("intersect") of nkExcept: - s.add(" except") + s.addKeyw("except") of nkColumnList: - rs(n, s, indent) + rs(n, s) of nkValueList: - s.add("values ") - rs(n, s, indent) + s.addKeyw("values") + rs(n, s) of nkWhere: - s.add("\nwhere ") - ra(n.sons[0], s, indent) + s.addKeyw("where") + ra(n.sons[0], s) of nkCreateTable, nkCreateTableIfNotExists: - s.add("create table ") + s.addKeyw("create table") if n.kind == nkCreateTableIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) + s.addKeyw("if not exists") + ra(n.sons[0], s) s.add('(') for i in 1..n.len-1: - if i > 1: s.add(", ") - ra(n.sons[i], s, indent) + if i > 1: s.add(',') + ra(n.sons[i], s) s.add(");") of nkCreateType, nkCreateTypeIfNotExists: - s.add("create type ") + s.addKeyw("create type") if n.kind == nkCreateTypeIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) - s.add(" as ") - ra(n.sons[1], s, indent) - s.add(';') + s.addKeyw("if not exists") + ra(n.sons[0], s) + s.addKeyw("as") + ra(n.sons[1], s) of nkCreateIndex, nkCreateIndexIfNotExists: - s.add("create index ") + s.addKeyw("create index") if n.kind == nkCreateIndexIfNotExists: - s.add("if not exists ") - ra(n.sons[0], s, indent) - s.add(" on ") - ra(n.sons[1], s, indent) + s.addKeyw("if not exists") + ra(n.sons[0], s) + s.addKeyw("on") + ra(n.sons[1], s) s.add('(') for i in 2..n.len-1: if i > 2: s.add(", ") - ra(n.sons[i], s, indent) + ra(n.sons[i], s) s.add(");") of nkEnumDef: - s.add("enum ") - rs(n, s, indent) + s.addKeyw("enum") + rs(n, s) -# What I want: -# -#select(columns = [T1.all, T2.name], -# fromm = [T1, T2], -# where = T1.name ==. T2.name, -# orderby = [name]): -# -#for row in dbQuery(db, """select x, y, z -# from a, b -# where a.name = b.name"""): -# - -#select x, y, z: -# fromm: Table1, Table2 -# where: x.name == y.name -#db.select(fromm = [t1, t2], where = t1.name == t2.name): -#for x, y, z in db.select(fromm = a, b where = a.name == b.name): -# writeLine x, y, z - -proc renderSQL*(n: SqlNode): string = +proc renderSQL*(n: SqlNode, upperCase=false): string = ## Converts an SQL abstract syntax tree to its string representation. - result = "" - ra(n, result, 0) + var s: SqlWriter + s.buffer = "" + s.upperCase = upperCase + ra(n, s) + return s.buffer proc `$`*(n: SqlNode): string = ## an alias for `renderSQL`. renderSQL(n) - -when not defined(testing) and isMainModule: - echo(renderSQL(parseSQL(newStringStream(""" - CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); - CREATE TABLE holidays ( - num_weeks int, - happiness happiness - ); - CREATE INDEX table1_attr1 ON table1(attr1); - - SELECT * FROM myTab WHERE col1 = 'happy'; - """), "stdin"))) - -# CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); -# CREATE TABLE holidays ( -# num_weeks int, -# happiness happiness -# ); -# CREATE INDEX table1_attr1 ON table1(attr1) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index b78e8d000..57387e62e 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -8,6 +8,8 @@ # ## This module contains helpers for parsing tokens, numbers, identifiers, etc. +## +## To unpack raw bytes look at the `streams <streams.html>`_ module. {.deadCodeElim: on.} @@ -85,6 +87,23 @@ proc parseOct*(s: string, number: var int, start = 0): int {. inc(i) if foundDigit: result = i-start +proc parseBin*(s: string, number: var int, start = 0): int {. + rtl, extern: "npuParseBin", noSideEffect.} = + ## parses an binary number and stores its value in ``number``. Returns + ## the number of the parsed characters or 0 in case of an error. + var i = start + var foundDigit = false + if s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2) + while true: + case s[i] + of '_': discard + of '0'..'1': + number = number shl 1 or (ord(s[i]) - ord('0')) + foundDigit = true + else: break + inc(i) + if foundDigit: result = i-start + proc parseIdent*(s: string, ident: var string, start = 0): int = ## parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. @@ -250,6 +269,31 @@ proc parseInt*(s: string, number: var int, start = 0): int {. elif result != 0: number = int(res) +proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = + ## parses a natural number into ``b``. This cannot raise an overflow + ## error. Instead of an ``Overflow`` exception ``high(int)`` is returned. + ## The number of processed character is returned. + ## This is usually what you really want to use instead of `parseInt`:idx:. + ## Example: + ## + ## .. code-block:: nim + ## var res = 0 + ## discard parseSaturatedNatural("848", res) + ## doAssert res == 848 + var i = start + if s[i] == '+': inc(i) + if s[i] in {'0'..'9'}: + b = 0 + while s[i] in {'0'..'9'}: + let c = ord(s[i]) - ord('0') + if b <= (high(int) - c) div 10: + b = b * 10 + c + else: + b = high(int) + inc(i) + while s[i] == '_': inc(i) # underscores are allowed and ignored + result = i - start + # overflowChecks doesn't work with BiggestUInt proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = var @@ -391,16 +435,43 @@ when isMainModule: let input = "$test{} $this is ${an{ example}} " let expected = @[(ikVar, "test"), (ikStr, "{} "), (ikVar, "this"), (ikStr, " is "), (ikExpr, "an{ example}"), (ikStr, " ")] - assert toSeq(interpolatedFragments(input)) == expected + doAssert toSeq(interpolatedFragments(input)) == expected var value = 0 discard parseHex("0x38", value) - assert value == 56 + doAssert value == 56 discard parseHex("0x34", value) - assert value == 56 * 256 + 52 + doAssert value == 56 * 256 + 52 value = -1 discard parseHex("0x38", value) - assert value == -200 + doAssert value == -200 + + value = -1 + doAssert(parseSaturatedNatural("848", value) == 3) + doAssert value == 848 + + value = -1 + discard parseSaturatedNatural("84899999999999999999324234243143142342135435342532453", value) + doAssert value == high(int) + value = -1 + discard parseSaturatedNatural("9223372036854775808", value) + doAssert value == high(int) + + value = -1 + discard parseSaturatedNatural("9223372036854775807", value) + doAssert value == high(int) + + value = -1 + discard parseSaturatedNatural("18446744073709551616", value) + doAssert value == high(int) + + value = -1 + discard parseSaturatedNatural("18446744073709551615", value) + doAssert value == high(int) + + value = -1 + doAssert(parseSaturatedNatural("1_000_000", value) == 9) + doAssert value == 1_000_000 {.pop.} diff --git a/lib/pure/poly.nim b/lib/pure/poly.nim deleted file mode 100644 index e286c5d17..000000000 --- a/lib/pure/poly.nim +++ /dev/null @@ -1,371 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Robert Persson -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warning:** This module will be moved out of the stdlib and into a -## Nimble package, don't use it. - -import math -import strutils -import numeric - -type - Poly* = object - cofs:seq[float] - -{.deprecated: [TPoly: Poly].} - -proc degree*(p:Poly):int= - ## Returns the degree of the polynomial, - ## that is the number of coefficients-1 - return p.cofs.len-1 - - -proc eval*(p:Poly,x:float):float= - ## Evaluates a polynomial function value for `x` - ## quickly using Horners method - var n=p.degree - result=p.cofs[n] - dec n - while n>=0: - result = result*x+p.cofs[n] - dec n - -proc `[]` *(p:Poly;idx:int):float= - ## Gets a coefficient of the polynomial. - ## p[2] will returns the quadric term, p[3] the cubic etc. - ## Out of bounds index will return 0.0. - if idx<0 or idx>p.degree: - return 0.0 - return p.cofs[idx] - -proc `[]=` *(p:var Poly;idx:int,v:float)= - ## Sets an coefficient of the polynomial by index. - ## p[2] set the quadric term, p[3] the cubic etc. - ## If index is out of range for the coefficients, - ## the polynomial grows to the smallest needed degree. - assert(idx>=0) - - if idx>p.degree: #polynomial must grow - var oldlen=p.cofs.len - p.cofs.setLen(idx+1) - for q in oldlen.. <high(p.cofs): - p.cofs[q]=0.0 #new-grown coefficients set to zero - - p.cofs[idx]=v - - -iterator items*(p:Poly):float= - ## Iterates through the coefficients of the polynomial. - var i=p.degree - while i>=0: - yield p[i] - dec i - -proc clean*(p:var Poly;zerotol=0.0)= - ## Removes leading zero coefficients of the polynomial. - ## An optional tolerance can be given for what's considered zero. - var n=p.degree - var relen=false - - while n>0 and abs(p[n])<=zerotol: # >0 => keep at least one coefficient - dec n - relen=true - - if relen: p.cofs.setLen(n+1) - - -proc `$` *(p:Poly):string = - ## Gets a somewhat reasonable string representation of the polynomial - ## The format should be compatible with most online function plotters, - ## for example directly in google search - result="" - var first=true #might skip + sign if first coefficient - - for idx in countdown(p.degree,0): - let a=p[idx] - - if a==0.0: - continue - - if a>= 0.0 and not first: - result.add('+') - first=false - - if a!=1.0 or idx==0: - result.add(formatFloat(a,ffDefault,0)) - if idx>=2: - result.add("x^" & $idx) - elif idx==1: - result.add("x") - - if result=="": - result="0" - - -proc derivative*(p: Poly): Poly= - ## Returns a new polynomial, which is the derivative of `p` - newSeq[float](result.cofs,p.degree) - for idx in 0..high(result.cofs): - result.cofs[idx]=p.cofs[idx+1]*float(idx+1) - -proc diff*(p:Poly,x:float):float= - ## Evaluates the differentiation of a polynomial with - ## respect to `x` quickly using a modifed Horners method - var n=p.degree - result=p[n]*float(n) - dec n - while n>=1: - result = result*x+p[n]*float(n) - dec n - -proc integral*(p:Poly):Poly= - ## Returns a new polynomial which is the indefinite - ## integral of `p`. The constant term is set to 0.0 - newSeq(result.cofs,p.cofs.len+1) - result.cofs[0]=0.0 #constant arbitrary term, use 0.0 - for i in 1..high(result.cofs): - result.cofs[i]=p.cofs[i-1]/float(i) - - -proc integrate*(p:Poly;xmin,xmax:float):float= - ## Computes the definite integral of `p` between `xmin` and `xmax` - ## quickly using a modified version of Horners method - var - n=p.degree - s1=p[n]/float(n+1) - s2=s1 - fac:float - - dec n - while n>=0: - fac=p[n]/float(n+1) - s1 = s1*xmin+fac - s2 = s2*xmax+fac - dec n - - result=s2*xmax-s1*xmin - -proc initPoly*(cofs:varargs[float]):Poly= - ## Initializes a polynomial with given coefficients. - ## The most significant coefficient is first, so to create x^2-2x+3: - ## intiPoly(1.0,-2.0,3.0) - if len(cofs)<=0: - result.cofs= @[0.0] #need at least one coefficient - else: - # reverse order of coefficients so indexing matches degree of - # coefficient... - result.cofs= @[] - for idx in countdown(cofs.len-1,0): - result.cofs.add(cofs[idx]) - - result.clean #remove leading zero terms - - -proc divMod*(p,d:Poly;q,r:var Poly)= - ## Divides `p` with `d`, and stores the quotinent in `q` and - ## the remainder in `d` - var - pdeg=p.degree - ddeg=d.degree - power=p.degree-d.degree - ratio:float - - r.cofs = p.cofs #initial remainder=numerator - if power<0: #denominator is larger than numerator - q.cofs= @ [0.0] #quotinent is 0.0 - return # keep remainder as numerator - - q.cofs=newSeq[float](power+1) - - for i in countdown(pdeg,ddeg): - ratio=r.cofs[i]/d.cofs[ddeg] - - q.cofs[i-ddeg]=ratio - r.cofs[i]=0.0 - - for j in countup(0,<ddeg): - var idx=i-ddeg+j - r.cofs[idx] = r.cofs[idx] - d.cofs[j]*ratio - - r.clean # drop zero coefficients in remainder - -proc `+` *(p1:Poly,p2:Poly):Poly= - ## Adds two polynomials - var n=max(p1.cofs.len,p2.cofs.len) - newSeq(result.cofs,n) - - for idx in countup(0,n-1): - result[idx]=p1[idx]+p2[idx] - - result.clean # drop zero coefficients in remainder - -proc `*` *(p1:Poly,p2:Poly):Poly= - ## Multiplies the polynomial `p1` with `p2` - var - d1=p1.degree - d2=p2.degree - n=d1+d2 - idx:int - - newSeq(result.cofs,n) - - for i1 in countup(0,d1): - for i2 in countup(0,d2): - idx=i1+i2 - result[idx]=result[idx]+p1[i1]*p2[i2] - - result.clean - -proc `*` *(p:Poly,f:float):Poly= - ## Multiplies the polynomial `p` with a real number - newSeq(result.cofs,p.cofs.len) - for i in 0..high(p.cofs): - result[i]=p.cofs[i]*f - result.clean - -proc `*` *(f:float,p:Poly):Poly= - ## Multiplies a real number with a polynomial - return p*f - -proc `-`*(p:Poly):Poly= - ## Negates a polynomial - result=p - for i in countup(0,<result.cofs.len): - result.cofs[i]= -result.cofs[i] - -proc `-` *(p1:Poly,p2:Poly):Poly= - ## Subtract `p1` with `p2` - var n=max(p1.cofs.len,p2.cofs.len) - newSeq(result.cofs,n) - - for idx in countup(0,n-1): - result[idx]=p1[idx]-p2[idx] - - result.clean # drop zero coefficients in remainder - -proc `/`*(p:Poly,f:float):Poly= - ## Divides polynomial `p` with a real number `f` - newSeq(result.cofs,p.cofs.len) - for i in 0..high(p.cofs): - result[i]=p.cofs[i]/f - result.clean - -proc `/` *(p,q:Poly):Poly= - ## Divides polynomial `p` with polynomial `q` - var dummy:Poly - p.divMod(q,result,dummy) - -proc `mod` *(p,q:Poly):Poly= - ## Computes the polynomial modulo operation, - ## that is the remainder of `p`/`q` - var dummy:Poly - p.divMod(q,dummy,result) - - -proc normalize*(p:var Poly)= - ## Multiplies the polynomial inplace by a term so that - ## the leading term is 1.0. - ## This might lead to an unstable polynomial - ## if the leading term is zero. - p=p/p[p.degree] - - -proc solveQuadric*(a,b,c:float;zerotol=0.0):seq[float]= - ## Solves the quadric equation `ax^2+bx+c`, with a possible - ## tolerance `zerotol` to find roots of curves just 'touching' - ## the x axis. Returns sequence with 0,1 or 2 solutions. - - var p,q,d:float - - p=b/(2.0*a) - - if p==Inf or p==NegInf: #linear equation.. - var linrt= -c/b - if linrt==Inf or linrt==NegInf: #constant only - return @[] - return @[linrt] - - q=c/a - d=p*p-q - - if d<0.0: - #check for inside zerotol range for neg. roots - var err=a*p*p-b*p+c #evaluate error at parabola center axis - if(err<=zerotol): return @[-p] - return @[] - else: - var sr=sqrt(d) - result= @[-sr-p,sr-p] - -proc getRangeForRoots(p:Poly):tuple[xmin,xmax:float]= - ## helper function for `roots` function - ## quickly computes a range, guaranteed to contain - ## all the real roots of the polynomial - # see http://www.mathsisfun.com/algebra/polynomials-bounds-zeros.html - - var deg=p.degree - var d=p[deg] - var bound1,bound2:float - - for i in countup(0,deg): - var c=abs(p.cofs[i]/d) - bound1=max(bound1,c+1.0) - bound2=bound2+c - - bound2=max(1.0,bound2) - result.xmax=min(bound1,bound2) - result.xmin= -result.xmax - - -proc addRoot(p:Poly,res:var seq[float],xp0,xp1,tol,zerotol,mergetol:float,maxiter:int)= - ## helper function for `roots` function - ## try to do a numeric search for a single root in range xp0-xp1, - ## adding it to `res` (allocating `res` if nil) - var br=brent(xp0,xp1, proc(x:float):float=p.eval(x),tol) - if br.success: - if res.len==0 or br.rootx>=res[high(res)]+mergetol: #dont add equal roots. - res.add(br.rootx) - else: - #this might be a 'touching' case, check function value against - #zero tolerance - if abs(br.rooty)<=zerotol: - if res.len==0 or br.rootx>=res[high(res)]+mergetol: #dont add equal roots. - res.add(br.rootx) - - -proc roots*(p:Poly,tol=1.0e-9,zerotol=1.0e-6,mergetol=1.0e-12,maxiter=1000):seq[float]= - ## Computes the real roots of the polynomial `p` - ## `tol` is the tolerance used to break searching for each root when reached. - ## `zerotol` is the tolerance, which is 'close enough' to zero to be considered a root - ## and is used to find roots for curves that only 'touch' the x-axis. - ## `mergetol` is the tolerance, of which two x-values are considered being the same root. - ## `maxiter` can be used to limit the number of iterations for each root. - ## Returns a (possibly empty) sorted sequence with the solutions. - var deg=p.degree - if deg<=0: #constant only => no roots - return @[] - elif p.degree==1: #linear - var linrt= -p.cofs[0]/p.cofs[1] - if linrt==Inf or linrt==NegInf: - return @[] #constant only => no roots - return @[linrt] - elif p.degree==2: - return solveQuadric(p.cofs[2],p.cofs[1],p.cofs[0],zerotol) - else: - # degree >=3 , find min/max points of polynomial with recursive - # derivative and do a numerical search for root between each min/max - var range=p.getRangeForRoots() - var minmax=p.derivative.roots(tol,zerotol,mergetol) - result= @[] - if minmax!=nil: #ie. we have minimas/maximas in this function - for x in minmax.items: - addRoot(p,result,range.xmin,x,tol,zerotol,mergetol,maxiter) - range.xmin=x - addRoot(p,result,range.xmin,range.xmax,tol,zerotol,mergetol,maxiter) - diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 27fbfad45..de419b9fb 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -7,16 +7,16 @@ # distribution, for details about the copyright. # -## Nim's standard random number generator. Based on the ``xoroshiro128+`` (xor/rotate/shift/rotate) library. +## Nim's standard random number generator. Based on +## the ``xoroshiro128+`` (xor/rotate/shift/rotate) library. ## * More information: http://xoroshiro.di.unimi.it/ ## * C implementation: http://xoroshiro.di.unimi.it/xoroshiro128plus.c ## -## Do not use this module for cryptographic use! +## **Do not use this module for cryptographic purposes!** include "system/inclrtl" {.push debugger:off.} -# XXX Expose RandomGenState when defined(JS): type ui = uint32 @@ -27,31 +27,34 @@ else: const randMax = 18_446_744_073_709_551_615u64 type - RandomGenState = object + Rand* = object ## State of the random number generator. + ## The procs that use the default state + ## are **not** thread-safe! a0, a1: ui when defined(JS): - var state = RandomGenState( + var state = Rand( a0: 0x69B4C98Cu32, a1: 0xFED1DD30u32) # global for backwards compatibility else: # racy for multi-threading but good enough for now: - var state = RandomGenState( + var state = Rand( a0: 0x69B4C98CB8530805u64, a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility proc rotl(x, k: ui): ui = result = (x shl k) or (x shr (ui(64) - k)) -proc next(s: var RandomGenState): uint64 = - let s0 = s.a0 - var s1 = s.a1 +proc next*(r: var Rand): uint64 = + ## Uses the state to compute a new ``uint64`` random number. + let s0 = r.a0 + var s1 = r.a1 result = s0 + s1 s1 = s1 xor s0 - s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b - s.a1 = rotl(s1, 36) # c + r.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b + r.a1 = rotl(s1, 36) # c -proc skipRandomNumbers(s: var RandomGenState) = +proc skipRandomNumbers*(s: var Rand) = ## This is the jump function for the generator. It is equivalent ## to 2^64 calls to next(); it can be used to generate 2^64 ## non-overlapping subsequences for parallel computations. @@ -71,21 +74,23 @@ proc skipRandomNumbers(s: var RandomGenState) = s.a0 = s0 s.a1 = s1 -proc random*(max: int): int {.benign.} = +proc random*(max: int): int {.benign, deprecated.} = ## Returns a random number in the range 0..max-1. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. + ## number, i.e. a tickcount. **Deprecated since version 0.18.0**. + ## Use ``rand`` instead. while true: let x = next(state) if x < randMax - (randMax mod ui(max)): return int(x mod uint64(max)) -proc random*(max: float): float {.benign.} = +proc random*(max: float): float {.benign, deprecated.} = ## Returns a random number in the range 0..<max. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. + ## number, i.e. a tickcount. **Deprecated since version 0.18.0**. + ## Use ``rand`` instead. let x = next(state) when defined(JS): result = (float(x) / float(high(uint32))) * max @@ -93,38 +98,100 @@ proc random*(max: float): float {.benign.} = let u = (0x3FFu64 shl 52u64) or (x shr 12u64) result = (cast[float](u) - 1.0) * max -proc random*[T](x: Slice[T]): T = +proc random*[T](x: HSlice[T, T]): T {.deprecated.} = ## For a slice `a .. b` returns a value in the range `a .. b-1`. + ## **Deprecated since version 0.18.0**. + ## Use ``rand`` instead. result = T(random(x.b - x.a)) + x.a -proc random*[T](a: openArray[T]): T = +proc random*[T](a: openArray[T]): T {.deprecated.} = ## returns a random element from the openarray `a`. + ## **Deprecated since version 0.18.0**. + ## Use ``rand`` instead. result = a[random(a.low..a.len)] +proc rand*(r: var Rand; max: int): int {.benign.} = + ## Returns a random number in the range 0..max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + while true: + let x = next(r) + if x <= randMax - (randMax mod ui(max)): + return int(x mod (uint64(max)+1u64)) + +proc rand*(max: int): int {.benign.} = + ## Returns a random number in the range 0..max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + rand(state, max) + +proc rand*(r: var Rand; max: float): float {.benign.} = + ## Returns a random number in the range 0..max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + let x = next(r) + when defined(JS): + result = (float(x) / float(high(uint32))) * max + else: + let u = (0x3FFu64 shl 52u64) or (x shr 12u64) + result = (cast[float](u) - 1.0) * max + +proc rand*(max: float): float {.benign.} = + ## Returns a random number in the range 0..max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + rand(state, max) + +proc rand*[T](r: var Rand; x: HSlice[T, T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b`. + result = T(rand(r, x.b - x.a)) + x.a + +proc rand*[T](x: HSlice[T, T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b`. + result = rand(state, x) + +proc rand*[T](r: var Rand; a: openArray[T]): T = + ## returns a random element from the openarray `a`. + result = a[rand(r, a.low..a.high)] + +proc rand*[T](a: openArray[T]): T = + ## returns a random element from the openarray `a`. + result = a[rand(a.low..a.high)] + + +proc initRand*(seed: int64): Rand = + ## Creates a new ``Rand`` state from ``seed``. + result.a0 = ui(seed shr 16) + result.a1 = ui(seed and 0xffff) + discard next(result) + proc randomize*(seed: int64) {.benign.} = - ## Initializes the random number generator with a specific seed. - state.a0 = ui(seed shr 16) - state.a1 = ui(seed and 0xffff) - discard next(state) + ## Initializes the default random number generator + ## with a specific seed. + state = initRand(seed) -proc shuffle*[T](x: var openArray[T]) = - ## Will randomly swap the positions of elements in a sequence. +proc shuffle*[T](r: var Rand; x: var openArray[T]) = + ## Swaps the positions of elements in a sequence randomly. for i in countdown(x.high, 1): - let j = random(i + 1) + let j = r.rand(i) swap(x[i], x[j]) +proc shuffle*[T](x: var openArray[T]) = + ## Swaps the positions of elements in a sequence randomly. + shuffle(state, x) + when not defined(nimscript): import times proc randomize*() {.benign.} = ## Initializes the random number generator with a "random" ## number, i.e. a tickcount. Note: Does not work for NimScript. - when defined(JS): - proc getMil(t: Time): int {.importcpp: "getTime", nodecl.} - randomize(getMil times.getTime()) - else: - let time = int64(times.epochTime() * 1_000_000_000) - randomize(time) + let time = int64(times.epochTime() * 1_000_000_000) + randomize(time) {.pop.} @@ -134,12 +201,12 @@ when isMainModule: var x = 8234 for i in 0..100_000: - x = random(len(occur)) # myrand(x) + x = rand(high(occur)) inc occur[x] for i, oc in occur: if oc < 69: doAssert false, "too few occurrences of " & $i - elif oc > 130: + elif oc > 150: doAssert false, "too many occurrences of " & $i var a = [0, 1] diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim index c2ba2b1f3..7907b4e6c 100644 --- a/lib/pure/rationals.nim +++ b/lib/pure/rationals.nim @@ -39,47 +39,13 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] = result.num = x result.den = 1 -proc toRationalSub(x: float, n: int): Rational[int] = - var - a = 0'i64 - b, c, d = 1'i64 - result = 0 // 1 # rational 0 - while b <= n and d <= n: - let ac = (a+c) - let bd = (b+d) - # scale by 1000 so not overflow for high precision - let mediant = (ac.float/1000) / (bd.float/1000) - if x == mediant: - if bd <= n: - result.num = ac.int - result.den = bd.int - return result - elif d > b: - result.num = c.int - result.den = d.int - return result - else: - result.num = a.int - result.den = b.int - return result - elif x > mediant: - a = ac - b = bd - else: - c = ac - d = bd - if (b > n): - return initRational(c.int, d.int) - return initRational(a.int, b.int) - -proc toRational*(x: float, n: int = high(int)): Rational[int] = - ## Calculate the best rational numerator and denominator +proc toRational*(x: float, n: int = high(int) shr (sizeof(int) div 2 * 8)): Rational[int] = + ## Calculates the best rational numerator and denominator ## that approximates to `x`, where the denominator is ## smaller than `n` (default is the largest possible - ## int to give maximum resolution) + ## int to give maximum resolution). ## - ## The algorithm is based on the Farey sequence named - ## after John Farey + ## The algorithm is based on the theory of continued fractions. ## ## .. code-block:: Nim ## import math, rationals @@ -88,13 +54,24 @@ proc toRational*(x: float, n: int = high(int)): Rational[int] = ## let x = toRational(PI, t) ## let newPI = x.num / x.den ## echo x, " ", newPI, " error: ", PI - newPI, " ", t - if x > 1: - result = toRationalSub(1.0/x, n) - swap(result.num, result.den) - elif x == 1.0: - result = 1 // 1 - else: - result = toRationalSub(x, n) + + # David Eppstein / UC Irvine / 8 Aug 1993 + # With corrections from Arno Formella, May 2008 + var + m11, m22 = 1 + m12, m21 = 0 + ai = int(x) + x = x + while m21 * ai + m22 <= n: + swap m12, m11 + swap m22, m21 + m11 = m12 * ai + m11 + m21 = m22 * ai + m21 + if x == float(ai): break # division by zero + x = 1/(x - float(ai)) + if x > float(high(int32)): break # representation failure + ai = int(x) + result = m11 // m21 proc toFloat*[T](x: Rational[T]): float = ## Convert a rational number `x` to a float. @@ -346,7 +323,19 @@ when isMainModule: assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7 assert toInt(z) == 0 - assert toRational(0.98765432) == 12345679 // 12500000 - assert toRational(0.1, 1000000) == 1 // 10 - assert toRational(0.9, 1000000) == 9 // 10 - #assert toRational(PI) == 80143857 // 25510582 + when sizeof(int) == 8: + assert toRational(0.98765432) == 2111111029 // 2137499919 + assert toRational(PI) == 817696623 // 260280919 + when sizeof(int) == 4: + assert toRational(0.98765432) == 80 // 81 + assert toRational(PI) == 355 // 113 + + assert toRational(0.1) == 1 // 10 + assert toRational(0.9) == 9 // 10 + + assert toRational(0.0) == 0 // 1 + assert toRational(-0.25) == 1 // -4 + assert toRational(3.2) == 16 // 5 + assert toRational(0.33) == 33 // 100 + assert toRational(0.22) == 11 // 50 + assert toRational(10.0) == 10 // 1 diff --git a/lib/pure/romans.nim b/lib/pure/romans.nim deleted file mode 100644 index aa047d1cc..000000000 --- a/lib/pure/romans.nim +++ /dev/null @@ -1,59 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2011 Philippe Lhoste -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Module for converting an integer to a Roman numeral. -## See http://en.wikipedia.org/wiki/Roman_numerals for reference. -## -## **Warning:** This module will be moved out of the stdlib and into a -## Nimble package, don't use it. - -const - RomanNumeralDigits* = {'I', 'i', 'V', 'v', 'X', 'x', 'L', 'l', 'C', 'c', - 'D', 'd', 'M', 'm'} ## set of all characters a Roman numeral may consist of - -proc romanToDecimal*(romanVal: string): int = - ## Converts a Roman numeral to its int representation. - result = 0 - var prevVal = 0 - for i in countdown(romanVal.len - 1, 0): - var val = 0 - case romanVal[i] - of 'I', 'i': val = 1 - of 'V', 'v': val = 5 - of 'X', 'x': val = 10 - of 'L', 'l': val = 50 - of 'C', 'c': val = 100 - of 'D', 'd': val = 500 - of 'M', 'm': val = 1000 - else: - raise newException(EInvalidValue, "invalid roman numeral: " & $romanVal) - if val >= prevVal: - inc(result, val) - else: - dec(result, val) - prevVal = val - -proc decimalToRoman*(number: range[1..3_999]): string = - ## Converts a number to a Roman numeral. - const romanComposites = [ - ("M", 1000), ("CM", 900), - ("D", 500), ("CD", 400), ("C", 100), - ("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9), - ("V", 5), ("IV", 4), ("I", 1)] - result = "" - var decVal: int = number - for key, val in items(romanComposites): - while decVal >= val: - dec(decVal, val) - result.add(key) - -when isMainModule: - for i in 1 .. 3_999: - assert i == i.decimalToRoman.romanToDecimal - diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim index 6e97237e0..6ddd61afa 100644 --- a/lib/pure/ropes.nim +++ b/lib/pure/ropes.nim @@ -17,6 +17,7 @@ ## runtime efficiency. include "system/inclrtl" +import streams {.deadCodeElim: on.} @@ -130,7 +131,7 @@ proc insertInCache(s: string, tree: Rope): Rope = result.left = t t.right = nil -proc rope*(s: string): Rope {.rtl, extern: "nro$1Str".} = +proc rope*(s: string = nil): Rope {.rtl, extern: "nro$1Str".} = ## Converts a string to a rope. if s.len == 0: result = nil @@ -242,10 +243,13 @@ proc write*(f: File, r: Rope) {.rtl, extern: "nro$1".} = ## writes a rope to a file. for s in leaves(r): write(f, s) +proc write*(s: Stream, r: Rope) {.rtl, extern: "nroWriteStream".} = + ## writes a rope to a stream. + for rs in leaves(r): write(s, rs) + proc `$`*(r: Rope): string {.rtl, extern: "nroToString".}= ## converts a rope back to a string. - result = newString(r.len) - setLen(result, 0) + result = newStringOfCap(r.len) for s in leaves(r): add(result, s) when false: diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 711e4a897..1ff26954e 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -95,7 +95,7 @@ type AsyncScgiState* = ref AsyncScgiStateObj {.deprecated: [EScgi: ScgiError, TScgiState: ScgiState, - PAsyncScgiState: AsyncScgiState, scgiError: raiseScgiError].} + PAsyncScgiState: AsyncScgiState].} proc recvBuffer(s: var ScgiState, L: int) = if L > s.bufLen: diff --git a/lib/pure/securehash.nim b/lib/pure/securehash.nim index c19146669..57c1f3631 100644 --- a/lib/pure/securehash.nim +++ b/lib/pure/securehash.nim @@ -181,7 +181,7 @@ proc `$`*(self: SecureHash): string = result.add(toHex(int(v), 2)) proc parseSecureHash*(hash: string): SecureHash = - for i in 0.. <Sha1DigestSize: + for i in 0 ..< Sha1DigestSize: Sha1Digest(result)[i] = uint8(parseHexInt(hash[i*2] & hash[i*2 + 1])) proc `==`*(a, b: SecureHash): bool = diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 506b2cec0..518cc4bd5 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -1,420 +1,313 @@ # # # Nim's Runtime Library -# (c) Copyright 2015 Dominik Picheta +# (c) Copyright 2016 Eugene Kabanov # # See the file "copying.txt", included in this # distribution, for details about the copyright. # -# TODO: Docs. - -import os, hashes - -when defined(linux): - import posix, epoll -elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd): - import posix, kqueue, times -elif defined(windows): - import winlean -else: - import posix - -const MultiThreaded = defined(useStdlibThreading) - -when MultiThreaded: - import sharedtables - - type SelectorData = pointer -else: - import tables - - type SelectorData = RootRef - -proc hash*(x: SocketHandle): Hash {.borrow.} -proc `$`*(x: SocketHandle): string {.borrow.} - -type - Event* = enum - EvRead, EvWrite, EvError - - SelectorKey* = object - fd*: SocketHandle - events*: set[Event] ## The events which ``fd`` listens for. - data*: SelectorData ## User object. - - ReadyInfo* = tuple[key: SelectorKey, events: set[Event]] +## This module allows high-level and efficient I/O multiplexing. +## +## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and +## Windows ``select``. +## +## To use threadsafe version of this module, it needs to be compiled +## with both ``-d:threadsafe`` and ``--threads:on`` options. +## +## Supported features: files, sockets, pipes, timers, processes, signals +## and user events. +## +## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux (except +## for Android). +## +## Partially supported OS: Windows (only sockets and user events), +## Solaris (files, sockets, handles and user events). +## Android (files, sockets, handles and user events). +## +## TODO: ``/dev/poll``, ``event ports`` and filesystem events. + +import os, strutils, nativesockets + +const hasThreadSupport = compileOption("threads") and defined(threadsafe) + +const ioselSupportedPlatform* = defined(macosx) or defined(freebsd) or + defined(netbsd) or defined(openbsd) or + defined(dragonfly) or + (defined(linux) and not defined(android)) + ## This constant is used to determine whether the destination platform is + ## fully supported by ``ioselectors`` module. + +const bsdPlatform = defined(macosx) or defined(freebsd) or + defined(netbsd) or defined(openbsd) or + defined(dragonfly) when defined(nimdoc): type - Selector* = ref object - ## An object which holds file descriptors to be checked for read/write - ## status. - - proc register*(s: Selector, fd: SocketHandle, events: set[Event], - data: SelectorData): SelectorKey {.discardable.} = - ## Registers file descriptor ``fd`` to selector ``s`` with a set of Event - ## ``events``. - - proc update*(s: Selector, fd: SocketHandle, - events: set[Event]): SelectorKey {.discardable.} = - ## Updates the events which ``fd`` wants notifications for. - - proc unregister*(s: Selector, fd: SocketHandle): SelectorKey {.discardable.} = - ## Unregisters file descriptor ``fd`` from selector ``s``. - - proc close*(s: Selector) = - ## Closes the selector - - proc select*(s: Selector, timeout: int): seq[ReadyInfo] = - ## The ``events`` field of the returned ``key`` contains the original events - ## for which the ``fd`` was bound. This is contrary to the ``events`` field - ## of the ``ReadyInfo`` tuple which determines which events are ready - ## on the ``fd``. - - proc newSelector*(): Selector = + Selector*[T] = ref object + ## An object which holds descriptors to be checked for read/write status + + Event* {.pure.} = enum + ## An enum which hold event types + Read, ## Descriptor is available for read + Write, ## Descriptor is available for write + Timer, ## Timer descriptor is completed + Signal, ## Signal is raised + Process, ## Process is finished + Vnode, ## BSD specific file change happens + User, ## User event is raised + Error, ## Error happens while waiting, for descriptor + VnodeWrite, ## NOTE_WRITE (BSD specific, write to file occurred) + VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occurred) + VnodeExtend, ## NOTE_EXTEND (BSD specific, file extended) + VnodeAttrib, ## NOTE_ATTRIB (BSD specific, file attributes changed) + VnodeLink, ## NOTE_LINK (BSD specific, file link count changed) + VnodeRename, ## NOTE_RENAME (BSD specific, file renamed) + VnodeRevoke ## NOTE_REVOKE (BSD specific, file revoke occurred) + + ReadyKey* = object + ## An object which holds result for descriptor + fd* : int ## file/socket descriptor + events*: set[Event] ## set of events + + SelectEvent* = object + ## An object which holds user defined event + + proc newSelector*[T](): Selector[T] = ## Creates a new selector - proc contains*(s: Selector, fd: SocketHandle): bool = + proc close*[T](s: Selector[T]) = + ## Closes the selector. + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event], + data: T) = + ## Registers file/socket descriptor ``fd`` to selector ``s`` + ## with events set in ``events``. The ``data`` is application-defined + ## data, which will be passed when an event is triggered. + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) = + ## Update file/socket descriptor ``fd``, registered in selector + ## ``s`` with new events set ``event``. + + proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, + data: T): int {.discardable.} = + ## Registers timer notification with ``timeout`` (in milliseconds) + ## to selector ``s``. + ## + ## If ``oneshot`` is ``true``, timer will be notified only once. + ## + ## Set ``oneshot`` to ``false`` if you want periodic notifications. + ## + ## The ``data`` is application-defined data, which will be passed, when + ## the timer is triggered. + ## + ## Returns the file descriptor for the registered timer. + + proc registerSignal*[T](s: Selector[T], signal: int, + data: T): int {.discardable.} = + ## Registers Unix signal notification with ``signal`` to selector + ## ``s``. + ## + ## The ``data`` is application-defined data, which will be + ## passed when signal raises. + ## + ## Returns the file descriptor for the registered signal. + ## + ## **Note:** This function is not supported on ``Windows``. + + proc registerProcess*[T](s: Selector[T], pid: int, + data: T): int {.discardable.} = + ## Registers a process id (pid) notification (when process has + ## exited) in selector ``s``. + ## + ## The ``data`` is application-defined data, which will be passed when + ## process with ``pid`` has exited. + ## + ## Returns the file descriptor for the registered signal. + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + ## Registers selector event ``ev`` in selector ``s``. + ## + ## The ``data`` is application-defined data, which will be passed when + ## ``ev`` happens. + + proc registerVnode*[T](s: Selector[T], fd: cint, events: set[Event], + data: T) = + ## Registers selector BSD/MacOSX specific vnode events for file + ## descriptor ``fd`` and events ``events``. + ## ``data`` application-defined data, which to be passed, when + ## vnode event happens. + ## + ## **Note:** This function is supported only by BSD and MacOSX. + + proc newSelectEvent*(): SelectEvent = + ## Creates a new user-defined event. + + proc trigger*(ev: SelectEvent) = + ## Trigger event ``ev``. + + proc close*(ev: SelectEvent) = + ## Closes user-defined event ``ev``. + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + ## Unregisters user-defined event ``ev`` from selector ``s``. + + proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = + ## Unregisters file/socket descriptor ``fd`` from selector ``s``. + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey]): int = + ## Waits for events registered in selector ``s``. + ## + ## The ``timeout`` argument specifies the maximum number of milliseconds + ## the function will be blocked for if no events are ready. Specifying a + ## timeout of ``-1`` causes the function to block indefinitely. + ## All available events will be stored in ``results`` array. + ## + ## Returns number of triggered events. + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] = + ## Waits for events registered in selector ``s``. + ## + ## The ``timeout`` argument specifies the maximum number of milliseconds + ## the function will be blocked for if no events are ready. Specifying a + ## timeout of ``-1`` causes the function to block indefinitely. + ## + ## Returns a list of triggered events. + + proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = + ## Retrieves application-defined ``data`` associated with descriptor ``fd``. + ## If specified descriptor ``fd`` is not registered, empty/default value + ## will be returned. + + proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: var T): bool = + ## Associate application-defined ``data`` with descriptor ``fd``. + ## + ## Returns ``true``, if data was succesfully updated, ``false`` otherwise. + + template isEmpty*[T](s: Selector[T]): bool = # TODO: Why is this a template? + ## Returns ``true``, if there are no registered events or descriptors + ## in selector. + + template withData*[T](s: Selector[T], fd: SocketHandle|int, value, + body: untyped) = + ## Retrieves the application-data assigned with descriptor ``fd`` + ## to ``value``. This ``value`` can be modified in the scope of + ## the ``withData`` call. + ## + ## .. code-block:: nim + ## + ## s.withData(fd, value) do: + ## # block is executed only if ``fd`` registered in selector ``s`` + ## value.uid = 1000 + ## + + template withData*[T](s: Selector[T], fd: SocketHandle|int, value, + body1, body2: untyped) = + ## Retrieves the application-data assigned with descriptor ``fd`` + ## to ``value``. This ``value`` can be modified in the scope of + ## the ``withData`` call. + ## + ## .. code-block:: nim + ## + ## s.withData(fd, value) do: + ## # block is executed only if ``fd`` registered in selector ``s``. + ## value.uid = 1000 + ## do: + ## # block is executed if ``fd`` not registered in selector ``s``. + ## raise + ## + + proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = ## Determines whether selector contains a file descriptor. - proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = - ## Retrieves the selector key for ``fd``. - -elif defined(linux): - type - Selector* = object - epollFD: cint - events: array[64, epoll_event] - when MultiThreaded: - fds: SharedTable[SocketHandle, SelectorKey] - else: - fds: Table[SocketHandle, SelectorKey] - - proc createEventStruct(events: set[Event], fd: SocketHandle): epoll_event = - if EvRead in events: - result.events = EPOLLIN - if EvWrite in events: - result.events = result.events or EPOLLOUT - result.events = result.events or EPOLLRDHUP - result.data.fd = fd.cint - - proc register*(s: var Selector, fd: SocketHandle, events: set[Event], - data: SelectorData) = - var event = createEventStruct(events, fd) - if events != {}: - if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: - raiseOSError(osLastError()) - - s.fds[fd] = SelectorKey(fd: fd, events: events, data: data) - - proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = - if s.fds[fd].events != events: - if events == {}: - # This fd is idle -- it should not be registered to epoll. - # But it should remain a part of this selector instance. - # This is to prevent epoll_wait from returning immediately - # because its got fds which are waiting for no events and - # are therefore constantly ready. (leading to 100% CPU usage). - if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: - raiseOSError(osLastError()) - s.fds[fd].events = events - else: - var event = createEventStruct(events, fd) - if s.fds[fd].events == {}: - # This fd is idle. It's not a member of this epoll instance and must - # be re-registered. - if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: - raiseOSError(osLastError()) - else: - if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: - raiseOSError(osLastError()) - s.fds[fd].events = events - - proc unregister*(s: var Selector, fd: SocketHandle) = - if s.fds[fd].events != {}: - if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: - let err = osLastError() - if err.cint notin {ENOENT, EBADF}: - # TODO: Why do we sometimes get an EBADF? Is this normal? - raiseOSError(err) - s.fds.del(fd) - - proc close*(s: var Selector) = - when MultiThreaded: deinitSharedTable(s.fds) - if s.epollFD.close() != 0: raiseOSError(osLastError()) - - proc epollHasFd(s: Selector, fd: SocketHandle): bool = - result = true - var event = createEventStruct(s.fds[fd].events, fd) - if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: - let err = osLastError() - if err.cint in {ENOENT, EBADF}: - return false - raiseOSError(err) - - proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = - result = @[] - let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint) - if evNum < 0: - let err = osLastError() - if err.cint == EINTR: - return @[] - raiseOSError(err) - if evNum == 0: return @[] - for i in 0 .. <evNum: - let fd = s.events[i].data.fd.SocketHandle - - var evSet: set[Event] = {} - if (s.events[i].events and EPOLLERR) != 0 or (s.events[i].events and EPOLLHUP) != 0: evSet = evSet + {EvError} - if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead} - if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite} - let selectorKey = s.fds[fd] - assert selectorKey.fd != 0.SocketHandle - result.add((selectorKey, evSet)) - - #echo("Epoll: ", result[i].key.fd, " ", result[i].events, " ", result[i].key.events) - - proc newSelector*(): Selector = - result.epollFD = epoll_create(64) - if result.epollFD < 0: - raiseOSError(osLastError()) - when MultiThreaded: - result.fds = initSharedTable[SocketHandle, SelectorKey]() - else: - result.fds = initTable[SocketHandle, SelectorKey]() - - proc contains*(s: Selector, fd: SocketHandle): bool = - ## Determines whether selector contains a file descriptor. - if s.fds.hasKey(fd): - # Ensure the underlying epoll instance still contains this fd. - if s.fds[fd].events != {}: - result = epollHasFd(s, fd) - else: - result = true - else: - return false +else: + when hasThreadSupport: + import locks - proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = - ## Retrieves the selector key for ``fd``. - return s.fds[fd] -elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd): - type - Selector* = object - kqFD: cint - events: array[64, KEvent] - when MultiThreaded: - fds: SharedTable[SocketHandle, SelectorKey] - else: - fds: Table[SocketHandle, SelectorKey] - - template modifyKQueue(kqFD: cint, fd: SocketHandle, event: Event, - op: cushort) = - var kev = KEvent(ident: fd.cuint, - filter: if event == EvRead: EVFILT_READ else: EVFILT_WRITE, - flags: op) - if kevent(kqFD, addr kev, 1, nil, 0, nil) == -1: - raiseOSError(osLastError()) - - proc register*(s: var Selector, fd: SocketHandle, events: set[Event], - data: SelectorData) = - for event in events: - modifyKQueue(s.kqFD, fd, event, EV_ADD) - s.fds[fd] = SelectorKey(fd: fd, events: events, data: data) - - proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = - let previousEvents = s.fds[fd].events - if previousEvents != events: - for event in events-previousEvents: - modifyKQueue(s.kqFD, fd, event, EV_ADD) - for event in previousEvents-events: - modifyKQueue(s.kqFD, fd, event, EV_DELETE) - s.fds[fd].events = events - - proc unregister*(s: var Selector, fd: SocketHandle) = - for event in s.fds[fd].events: - modifyKQueue(s.kqFD, fd, event, EV_DELETE) - s.fds.del(fd) - - proc close*(s: var Selector) = - when MultiThreaded: deinitSharedTable(s.fds) - if s.kqFD.close() != 0: raiseOSError(osLastError()) - - proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = - result = @[] - var tv = - if timeout >= 1000: Timespec(tv_sec: (timeout div 1000).Time, tv_nsec: 0) - else: Timespec(tv_sec: 0.Time, tv_nsec: timeout * 1000000) - let evNum = kevent(s.kqFD, nil, 0, addr s.events[0], 64.cint, addr tv) - if evNum < 0: - let err = osLastError() - if err.cint == EINTR: - return @[] - raiseOSError(err) - if evNum == 0: return @[] - for i in 0 .. <evNum: - let fd = s.events[i].ident.SocketHandle - - var evSet: set[Event] = {} - if (s.events[i].flags and EV_EOF) != 0: evSet = evSet + {EvError} - if s.events[i].filter == EVFILT_READ: evSet = evSet + {EvRead} - elif s.events[i].filter == EVFILT_WRITE: evSet = evSet + {EvWrite} - let selectorKey = s.fds[fd] - assert selectorKey.fd != 0.SocketHandle - result.add((selectorKey, evSet)) - - proc newSelector*(): Selector = - result.kqFD = kqueue() - if result.kqFD < 0: - raiseOSError(osLastError()) - when MultiThreaded: - result.fds = initSharedTable[SocketHandle, SelectorKey]() - else: - result.fds = initTable[SocketHandle, SelectorKey]() + type + SharedArray[T] = UncheckedArray[T] - proc contains*(s: Selector, fd: SocketHandle): bool = - ## Determines whether selector contains a file descriptor. - s.fds.hasKey(fd) # and s.fds[fd].events != {} + proc allocSharedArray[T](nsize: int): ptr SharedArray[T] = + result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize)) - proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = - ## Retrieves the selector key for ``fd``. - return s.fds[fd] + proc deallocSharedArray[T](sa: ptr SharedArray[T]) = + deallocShared(cast[pointer](sa)) -elif not defined(nimdoc): - # TODO: kqueue for bsd/mac os x. type - Selector* = object - when MultiThreaded: - fds: SharedTable[SocketHandle, SelectorKey] - else: - fds: Table[SocketHandle, SelectorKey] - - proc register*(s: var Selector, fd: SocketHandle, events: set[Event], - data: SelectorData) = - let result = SelectorKey(fd: fd, events: events, data: data) - if s.fds.hasKeyOrPut(fd, result): - raise newException(ValueError, "File descriptor already exists.") - - proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = - #if not s.fds.hasKey(fd): - # raise newException(ValueError, "File descriptor not found.") - s.fds[fd].events = events - - proc unregister*(s: var Selector, fd: SocketHandle) = - s.fds.del(fd) - - proc close*(s: var Selector) = - when MultiThreaded: deinitSharedTable(s.fds) - - proc timeValFromMilliseconds(timeout: int): TimeVal = - if timeout != -1: - var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 - - proc createFdSet(rd, wr: var TFdSet, s: Selector, m: var int) = - FD_ZERO(rd); FD_ZERO(wr) - for k, v in pairs(s.fds): - if EvRead in v.events: - m = max(m, int(k)) - FD_SET(k, rd) - if EvWrite in v.events: - m = max(m, int(k)) - FD_SET(k, wr) - - proc getReadyFDs(rd, wr: var TFdSet, - s: var Selector): seq[ReadyInfo] = - result = @[] - for k, v in pairs(s.fds): - var events: set[Event] = {} - if FD_ISSET(k, rd) != 0'i32: - events = events + {EvRead} - if FD_ISSET(k, wr) != 0'i32: - events = events + {EvWrite} - result.add((v, events)) - - proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = - var tv {.noInit.}: TimeVal = timeValFromMilliseconds(timeout) - - var rd, wr: TFdSet - var m = 0 - createFdSet(rd, wr, s, m) - - var retCode = 0 - if timeout != -1: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) - else: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) - - if retCode < 0: - raiseOSError(osLastError()) - elif retCode == 0: - return @[] - else: - return getReadyFDs(rd, wr, s) + Event* {.pure.} = enum + Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot, + Finished, VnodeWrite, VnodeDelete, VnodeExtend, VnodeAttrib, VnodeLink, + VnodeRename, VnodeRevoke - proc newSelector*(): Selector = - when MultiThreaded: - result.fds = initSharedTable[SocketHandle, SelectorKey]() + type + IOSelectorsException* = object of Exception + + ReadyKey* = object + fd* : int + events*: set[Event] + + SelectorKey[T] = object + ident: int + events: set[Event] + param: int + data: T + + proc raiseIOSelectorsError[T](message: T) = + var msg = "" + when T is string: + msg.add(message) + elif T is OSErrorCode: + msg.add(osErrorMsg(message) & " (code: " & $int(message) & ")") else: - result.fds = initTable[SocketHandle, SelectorKey]() - - proc contains*(s: Selector, fd: SocketHandle): bool = - return s.fds.hasKey(fd) - - proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = - return s.fds[fd] - -proc contains*(s: Selector, key: SelectorKey): bool = - ## Determines whether selector contains this selector key. More accurate - ## than checking if the file descriptor is in the selector because it - ## ensures that the keys are equal. File descriptors may not always be - ## unique especially when an fd is closed and then a new one is opened, - ## the new one may have the same value. - when not defined(nimdoc): - return key.fd in s and s.fds[key.fd] == key - -proc len*(s: Selector): int = - ## Retrieves the number of registered file descriptors in this Selector. - when not defined(nimdoc): - return s.fds.len - -{.deprecated: [TEvent: Event, PSelectorKey: SelectorKey, - TReadyInfo: ReadyInfo, PSelector: Selector].} - - -when not defined(testing) and isMainModule and not defined(nimdoc): - # Select() - import sockets + msg.add("Internal Error\n") + var err = newException(IOSelectorsException, msg) + raise err + + proc setNonBlocking(fd: cint) {.inline.} = + setBlocking(fd.SocketHandle, false) + + when not defined(windows): + import posix + + template setKey(s, pident, pevents, pparam, pdata: untyped) = + var skey = addr(s.fds[pident]) + skey.ident = pident + skey.events = pevents + skey.param = pparam + skey.data = data + + when ioselSupportedPlatform: + template blockSignals(newmask: var Sigset, oldmask: var Sigset) = + when hasThreadSupport: + if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1: + raiseIOSelectorsError(osLastError()) + else: + if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1: + raiseIOSelectorsError(osLastError()) - when MultiThreaded: - type - SockWrapper = object - sock: Socket - else: - type - SockWrapper = ref object of RootObj - sock: Socket - - var sock = socket() - if sock == sockets.invalidSocket: raiseOSError(osLastError()) - #sock.setBlocking(false) - sock.connect("irc.freenode.net", Port(6667)) - - var selector = newSelector() - var data = SockWrapper(sock: sock) - when MultiThreaded: - selector.register(sock.getFD, {EvWrite}, addr data) + template unblockSignals(newmask: var Sigset, oldmask: var Sigset) = + when hasThreadSupport: + if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1: + raiseIOSelectorsError(osLastError()) + else: + if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1: + raiseIOSelectorsError(osLastError()) + + when defined(linux): + include ioselects/ioselectors_epoll + elif bsdPlatform: + include ioselects/ioselectors_kqueue + elif defined(windows): + include ioselects/ioselectors_select + elif defined(solaris): + include ioselects/ioselectors_poll # need to replace it with event ports + elif defined(genode): + include ioselects/ioselectors_select # TODO: use the native VFS layer else: - selector.register(sock.getFD, {EvWrite}, data) - var i = 0 - while true: - let ready = selector.select(1000) - echo ready.len - if ready.len > 0: echo ready[0].events - i.inc - if i == 6: - selector.unregister(sock.getFD) - selector.close() - break + include ioselects/ioselectors_poll + +{.deprecated: [setEvent: trigger].} +{.deprecated: [register: registerHandle].} +{.deprecated: [update: updateHandle].} diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 69f673990..354e07da3 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -224,6 +224,38 @@ proc peekInt64*(s: Stream): int64 = ## peeks an int64 from the stream `s`. Raises `EIO` if an error occurred. peek(s, result) +proc readUint8*(s: Stream): uint8 = + ## reads an uint8 from the stream `s`. Raises `EIO` if an error occurred. + read(s, result) + +proc peekUint8*(s: Stream): uint8 = + ## peeks an uint8 from the stream `s`. Raises `EIO` if an error occurred. + peek(s, result) + +proc readUint16*(s: Stream): uint16 = + ## reads an uint16 from the stream `s`. Raises `EIO` if an error occurred. + read(s, result) + +proc peekUint16*(s: Stream): uint16 = + ## peeks an uint16 from the stream `s`. Raises `EIO` if an error occurred. + peek(s, result) + +proc readUint32*(s: Stream): uint32 = + ## reads an uint32 from the stream `s`. Raises `EIO` if an error occurred. + read(s, result) + +proc peekUint32*(s: Stream): uint32 = + ## peeks an uint32 from the stream `s`. Raises `EIO` if an error occurred. + peek(s, result) + +proc readUint64*(s: Stream): uint64 = + ## reads an uint64 from the stream `s`. Raises `EIO` if an error occurred. + read(s, result) + +proc peekUint64*(s: Stream): uint64 = + ## peeks an uint64 from the stream `s`. Raises `EIO` if an error occurred. + peek(s, result) + proc readFloat32*(s: Stream): float32 = ## reads a float32 from the stream `s`. Raises `EIO` if an error occurred. read(s, result) diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim new file mode 100644 index 000000000..180cbcbec --- /dev/null +++ b/lib/pure/strformat.nim @@ -0,0 +1,619 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ +String `interpolation`:idx: / `format`:idx: inspired by +Python's ``f``-strings. + +Examples: + +.. code-block:: nim + + doAssert fmt"""{"abc":>4}""" == " abc" + doAssert fmt"""{"abc":<4}""" == "abc " + + doAssert fmt"{-12345:08}" == "-0012345" + doAssert fmt"{-1:3}" == " -1" + doAssert fmt"{-1:03}" == "-01" + doAssert fmt"{16:#X}" == "0x10" + + doAssert fmt"{123.456}" == "123.456" + doAssert fmt"{123.456:>9.3f}" == " 123.456" + doAssert fmt"{123.456:9.3f}" == " 123.456" + doAssert fmt"{123.456:9.4f}" == " 123.4560" + doAssert fmt"{123.456:>9.0f}" == " 123." + doAssert fmt"{123.456:<9.4f}" == "123.4560 " + + doAssert fmt"{123.456:e}" == "1.234560e+02" + doAssert fmt"{123.456:>13e}" == " 1.234560e+02" + doAssert fmt"{123.456:13e}" == " 1.234560e+02" + + +An expression like ``fmt"{key} is {value:arg} {{z}}"`` is transformed into: + +.. code-block:: nim + var temp = newStringOfCap(educatedCapGuess) + format(key, temp) + format(" is ", temp) + format(value, arg, temp) + format(" {z}", temp) + temp + +Parts of the string that are enclosed in the curly braces are interpreted +as Nim code, to escape an ``{`` or ``}`` double it. + +``fmt`` delegates most of the work to an open overloaded set +of ``format`` procs. The required signature for a type ``T`` that supports +formatting is usually ``proc format(x: T; result: var string)`` for efficiency +but can also be ``proc format(x: T): string``. ``add`` and ``$`` procs are +used as the fallback implementation. + +This is the concrete lookup algorithm that ``fmt`` uses: + +.. code-block:: nim + + when compiles(format(arg, res)): + format(arg, res) + elif compiles(format(arg)): + res.add format(arg) + elif compiles(add(res, arg)): + res.add(arg) + else: + res.add($arg) + + +The subexpression after the colon +(``arg`` in ``fmt"{key} is {value:arg} {{z}}"``) is an optional argument +passed to ``format``. + +If an optional argument is present the following lookup algorithm is used: + +.. code-block:: nim + + when compiles(format(arg, option, res)): + format(arg, option, res) + else: + res.add format(arg, option) + + +For strings and numeric types the optional argument is a so-called +"standard format specifier". + + +Standard format specifier +========================= + + +The general form of a standard format specifier is:: + + [[fill]align][sign][#][0][minimumwidth][.precision][type] + +The square brackets ``[]`` indicate an optional element. + +The optional align flag can be one of the following: + +'<' + Forces the field to be left-aligned within the available + space. (This is the default for strings.) + +'>' + Forces the field to be right-aligned within the available space. + (This is the default for numbers.) + +'^' + Forces the field to be centered within the available space. + +Note that unless a minimum field width is defined, the field width +will always be the same size as the data to fill it, so that the alignment +option has no meaning in this case. + +The optional 'fill' character defines the character to be used to pad +the field to the minimum width. The fill character, if present, must be +followed by an alignment flag. + +The 'sign' option is only valid for numeric types, and can be one of the following: + +================= ==================================================== + Sign Meaning +================= ==================================================== +``+`` Indicates that a sign should be used for both + positive as well as negative numbers. +``-`` Indicates that a sign should be used only for + negative numbers (this is the default behavior). +(space) Indicates that a leading space should be used on + positive numbers. +================= ==================================================== + +If the '#' character is present, integers use the 'alternate form' for formatting. +This means that binary, octal, and hexadecimal output will be prefixed +with '0b', '0o', and '0x', respectively. + +'width' is a decimal integer defining the minimum field width. If not specified, +then the field width will be determined by the content. + +If the width field is preceded by a zero ('0') character, this enables +zero-padding. + +The 'precision' is a decimal number indicating how many digits should be displayed +after the decimal point in a floating point conversion. For non-numeric types the +field indicates the maximum field size - in other words, how many characters will +be used from the field content. The precision is ignored for integer conversions. + +Finally, the 'type' determines how the data should be presented. + +The available integer presentation types are: + + +================= ==================================================== + Type Result +================= ==================================================== +``b`` Binary. Outputs the number in base 2. +``d`` Decimal Integer. Outputs the number in base 10. +``o`` Octal format. Outputs the number in base 8. +``x`` Hex format. Outputs the number in base 16, using + lower-case letters for the digits above 9. +``X`` Hex format. Outputs the number in base 16, using + uppercase letters for the digits above 9. +(None) the same as 'd' +================= ==================================================== + + +The available floating point presentation types are: + +================= ==================================================== + Type Result +================= ==================================================== +``e`` Exponent notation. Prints the number in scientific + notation using the letter 'e' to indicate the + exponent. +``E`` Exponent notation. Same as 'e' except it converts + the number to uppercase. +``f`` Fixed point. Displays the number as a fixed-point + number. +``F`` Fixed point. Same as 'f' except it converts the + number to uppercase. +``g`` General format. This prints the number as a + fixed-point number, unless the number is too + large, in which case it switches to 'e' + exponent notation. +``G`` General format. Same as 'g' except switches to 'E' + if the number gets to large. +(None) similar to 'g', except that it prints at least one + digit after the decimal point. +================= ==================================================== + + +Future directions +================= + +A curly expression with commas in it like ``{x, argA, argB}`` could be +transformed to ``format(x, argA, argB, res)`` in order to support +formatters that do not need to parse a custom language within a custom +language but instead prefer to use Nim's existing syntax. This also +helps in readability since there is only so much you can cram into +single letter DSLs. + +]## + +import macros, parseutils, unicode +import strutils + +template callFormat(res, arg) {.dirty.} = + when arg is string: + # workaround in order to circumvent 'strutils.format' which matches + # too but doesn't adhere to our protocol. + res.add arg + elif compiles(format(arg, res)): + format(arg, res) + elif compiles(format(arg)): + res.add format(arg) + elif compiles(add(res, arg)): + res.add(arg) + else: + res.add($arg) + +template callFormatOption(res, arg, option) {.dirty.} = + when compiles(format(arg, option, res)): + format(arg, option, res) + else: + res.add format(arg, option) + +macro fmt*(pattern: string): untyped = + ## For a specification of the ``fmt`` macro, see the module level documentation. + runnableExamples: + template check(actual, expected: string) = + doAssert actual == expected + + from strutils import toUpperAscii, repeat + + # Basic tests + let s = "string" + check fmt"{0} {s}", "0 string" + check fmt"{s[0..2].toUpperAscii}", "STR" + check fmt"{-10:04}", "-010" + check fmt"{-10:<04}", "-010" + check fmt"{-10:>04}", "-010" + check fmt"0x{10:02X}", "0x0A" + + check fmt"{10:#04X}", "0x0A" + + check fmt"""{"test":#>5}""", "#test" + check fmt"""{"test":>5}""", " test" + + check fmt"""{"test":#^7}""", "#test##" + + check fmt"""{"test": <5}""", "test " + check fmt"""{"test":<5}""", "test " + check fmt"{1f:.3f}", "1.000" + check fmt"Hello, {s}!", "Hello, string!" + + # Tests for identifers without parenthesis + check fmt"{s} works{s}", "string worksstring" + check fmt"{s:>7}", " string" + doAssert(not compiles(fmt"{s_works}")) # parsed as identifier `s_works` + + # Misc general tests + check fmt"{{}}", "{}" + check fmt"{0}%", "0%" + check fmt"{0}%asdf", "0%asdf" + check fmt("\n{\"\\n\"}\n"), "\n\n\n" + check fmt"""{"abc"}s""", "abcs" + + # String tests + check fmt"""{"abc"}""", "abc" + check fmt"""{"abc":>4}""", " abc" + check fmt"""{"abc":<4}""", "abc " + check fmt"""{"":>4}""", " " + check fmt"""{"":<4}""", " " + + # Int tests + check fmt"{12345}", "12345" + check fmt"{ - 12345}", "-12345" + check fmt"{12345:6}", " 12345" + check fmt"{12345:>6}", " 12345" + check fmt"{12345:4}", "12345" + check fmt"{12345:08}", "00012345" + check fmt"{-12345:08}", "-0012345" + check fmt"{0:0}", "0" + check fmt"{0:02}", "00" + check fmt"{-1:3}", " -1" + check fmt"{-1:03}", "-01" + check fmt"{10}", "10" + check fmt"{16:#X}", "0x10" + check fmt"{16:^#7X}", " 0x10 " + check fmt"{16:^+#7X}", " +0x10 " + + # Hex tests + check fmt"{0:x}", "0" + check fmt"{-0:x}", "0" + check fmt"{255:x}", "ff" + check fmt"{255:X}", "FF" + check fmt"{-255:x}", "-ff" + check fmt"{-255:X}", "-FF" + check fmt"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" + check fmt"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe" + check fmt"{255:4x}", " ff" + check fmt"{255:04x}", "00ff" + check fmt"{-255:4x}", " -ff" + check fmt"{-255:04x}", "-0ff" + + # Float tests + check fmt"{123.456}", "123.456" + check fmt"{-123.456}", "-123.456" + check fmt"{123.456:.3f}", "123.456" + check fmt"{123.456:+.3f}", "+123.456" + check fmt"{-123.456:+.3f}", "-123.456" + check fmt"{-123.456:.3f}", "-123.456" + check fmt"{123.456:1g}", "123.456" + check fmt"{123.456:.1f}", "123.5" + check fmt"{123.456:.0f}", "123." + #check fmt"{123.456:.0f}", "123." + check fmt"{123.456:>9.3f}", " 123.456" + check fmt"{123.456:9.3f}", " 123.456" + check fmt"{123.456:>9.4f}", " 123.4560" + check fmt"{123.456:>9.0f}", " 123." + check fmt"{123.456:<9.4f}", "123.4560 " + + # Float (scientific) tests + check fmt"{123.456:e}", "1.234560e+02" + check fmt"{123.456:>13e}", " 1.234560e+02" + check fmt"{123.456:<13e}", "1.234560e+02 " + check fmt"{123.456:.1e}", "1.2e+02" + check fmt"{123.456:.2e}", "1.23e+02" + check fmt"{123.456:.3e}", "1.235e+02" + + # Note: times.format adheres to the format protocol. Test that this + # works: + import times + + var nullTime: TimeInfo + check fmt"{nullTime:yyyy-mm-dd}", "0000-00-00" + + # Unicode string tests + check fmt"""{"αβγ"}""", "αβγ" + check fmt"""{"αβγ":>5}""", " αβγ" + check fmt"""{"αβγ":<5}""", "αβγ " + check fmt"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈" + check fmt"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 " + # Invalid unicode sequences should be handled as plain strings. + # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173 + let invalidUtf8 = [ + "\xc3\x28", "\xa0\xa1", + "\xe2\x28\xa1", "\xe2\x82\x28", + "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28" + ] + for s in invalidUtf8: + check fmt"{s:>5}", repeat(" ", 5-s.len) & s + + if pattern.kind notin {nnkStrLit..nnkTripleStrLit}: + error "fmt only works with string literals", pattern + let f = pattern.strVal + var i = 0 + let res = genSym(nskVar, "fmtRes") + result = newNimNode(nnkStmtListExpr, lineInfoFrom=pattern) + result.add newVarStmt(res, newCall(bindSym"newStringOfCap", newLit(f.len + count(f, '{')*10))) + var strlit = "" + while i < f.len: + if f[i] == '{': + inc i + if f[i] == '{': + inc i + strlit.add '{' + else: + if strlit.len > 0: + result.add newCall(bindSym"add", res, newLit(strlit)) + strlit = "" + + var subexpr = "" + while i < f.len and f[i] != '}' and f[i] != ':': + subexpr.add f[i] + inc i + let x = parseExpr(subexpr) + + if f[i] == ':': + inc i + var options = "" + while i < f.len and f[i] != '}': + options.add f[i] + inc i + result.add getAst(callFormatOption(res, x, newLit(options))) + else: + result.add getAst(callFormat(res, x)) + if f[i] == '}': + inc i + else: + doAssert false, "invalid format string: missing '}'" + elif f[i] == '}': + if f[i+1] == '}': + strlit.add '}' + inc i, 2 + else: + doAssert false, "invalid format string: '}' instead of '}}'" + inc i + else: + strlit.add f[i] + inc i + if strlit.len > 0: + result.add newCall(bindSym"add", res, newLit(strlit)) + result.add res + when defined(debugFmtDsl): + echo repr result + +proc mkDigit(v: int, typ: char): string {.inline.} = + assert(v < 26) + if v < 10: + result = $chr(ord('0') + v) + else: + result = $chr(ord(if typ == 'x': 'a' else: 'A') + v - 10) + +proc alignString*(s: string, minimumWidth: int; align = '\0'; fill = ' '): string = + ## Aligns ``s`` using ``fill`` char. + ## This is only of interest if you want to write a custom ``format`` proc that + ## should support the standard format specifiers. + if minimumWidth == 0: + result = s + else: + let sRuneLen = if s.validateUtf8 == -1: s.runeLen else: s.len + let toFill = minimumWidth - sRuneLen + if toFill <= 0: + result = s + elif align == '<' or align == '\0': + result = s & repeat(fill, toFill) + elif align == '^': + let half = toFill div 2 + result = repeat(fill, half) & s & repeat(fill, toFill - half) + else: + result = repeat(fill, toFill) & s + +type + StandardFormatSpecifier* = object ## Type that describes "standard format specifiers". + fill*, align*: char ## Desired fill and alignment. + sign*: char ## Desired sign. + alternateForm*: bool ## Whether to prefix binary, octal and hex numbers + ## with ``0b``, ``0o``, ``0x``. + padWithZero*: bool ## Whether to pad with zeros rather than spaces. + minimumWidth*, precision*: int ## Desired minium width and precision. + typ*: char ## Type like 'f', 'g' or 'd'. + endPosition*: int ## End position in the format specifier after + ## ``parseStandardFormatSpecifier`` returned. + +proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string = + ## Converts ``n`` to string. If ``n`` is `SomeReal`, it casts to `int64`. + ## Conversion is done using ``radix``. If result's length is lesser than + ## ``minimumWidth``, it aligns result to the right or left (depending on ``a``) + ## with ``fill`` char. + when n is SomeUnsignedInt: + var v = n.uint64 + let negative = false + else: + var v = n.int64 + let negative = v.int64 < 0 + if negative: + # FIXME: overflow error for low(int64) + v = v * -1 + + var xx = "" + if spec.alternateForm: + case spec.typ + of 'X': xx = "0x" + of 'x': xx = "0x" + of 'b': xx = "0b" + of 'o': xx = "0o" + else: discard + + if v == 0: + result = "0" + else: + result = "" + while v > type(v)(0): + let d = v mod type(v)(radix) + v = v div type(v)(radix) + result.add(mkDigit(d.int, spec.typ)) + for idx in 0..<(result.len div 2): + swap result[idx], result[result.len - idx - 1] + if spec.padWithZero: + let sign = negative or spec.sign != '-' + let toFill = spec.minimumWidth - result.len - xx.len - ord(sign) + if toFill > 0: + result = repeat('0', toFill) & result + + if negative: + result = "-" & xx & result + elif spec.sign != '-': + result = spec.sign & xx & result + else: + result = xx & result + + if spec.align == '<': + for i in result.len..<spec.minimumWidth: + result.add(spec.fill) + else: + let toFill = spec.minimumWidth - result.len + if spec.align == '^': + let half = toFill div 2 + result = repeat(spec.fill, half) & result & repeat(spec.fill, toFill - half) + else: + if toFill > 0: + result = repeat(spec.fill, toFill) & result + +proc parseStandardFormatSpecifier*(s: string; start = 0; + ignoreUnknownSuffix = false): StandardFormatSpecifier = + ## An exported helper proc that parses the "standard format specifiers", + ## as specified by the grammar:: + ## + ## [[fill]align][sign][#][0][minimumwidth][.precision][type] + ## + ## This is only of interest if you want to write a custom ``format`` proc that + ## should support the standard format specifiers. If ``ignoreUnknownSuffix`` is true, + ## an unknown suffix after the ``type`` field is not an error. + const alignChars = {'<', '>', '^'} + result.fill = ' ' + result.align = '\0' + result.sign = '-' + var i = start + if i + 1 < s.len and s[i+1] in alignChars: + result.fill = s[i] + result.align = s[i+1] + inc i, 2 + elif i < s.len and s[i] in alignChars: + result.align = s[i] + inc i + + if i < s.len and s[i] in {'-', '+', ' '}: + result.sign = s[i] + inc i + + if i < s.len and s[i] == '#': + result.alternateForm = true + inc i + + if i+1 < s.len and s[i] == '0' and s[i+1] in {'0'..'9'}: + result.padWithZero = true + inc i + + let parsedLength = parseSaturatedNatural(s, result.minimumWidth, i) + inc i, parsedLength + if i < s.len and s[i] == '.': + inc i + let parsedLengthB = parseSaturatedNatural(s, result.precision, i) + inc i, parsedLengthB + else: + result.precision = -1 + + if i < s.len and s[i] in {'A'..'Z', 'a'..'z'}: + result.typ = s[i] + inc i + result.endPosition = i + if i != s.len and not ignoreUnknownSuffix: + raise newException(ValueError, + "invalid format string, cannot parse: " & s[i..^1]) + + +proc format*(value: SomeInteger; specifier: string; res: var string) = + ## Standard format implementation for ``SomeInteger``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``fmt`` macro. + let spec = parseStandardFormatSpecifier(specifier) + var radix = 10 + case spec.typ + of 'x', 'X': radix = 16 + of 'd', '\0': discard + of 'b': radix = 2 + of 'o': radix = 8 + else: + raise newException(ValueError, + "invalid type in format string for number, expected one " & + " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ) + res.add formatInt(value, radix, spec) + +proc format*(value: SomeReal; specifier: string; res: var string) = + ## Standard format implementation for ``SomeReal``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``fmt`` macro. + let spec = parseStandardFormatSpecifier(specifier) + + var fmode = ffDefault + case spec.typ + of 'e', 'E': + fmode = ffScientific + of 'f', 'F': + fmode = ffDecimal + of 'g', 'G': + fmode = ffDefault + of '\0': discard + else: + raise newException(ValueError, + "invalid type in format string for number, expected one " & + " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ) + + var f = formatBiggestFloat(value, fmode, spec.precision) + if value >= 0.0 and spec.sign != '-': + f = spec.sign & f + # the default for numbers is right-alignment: + let align = if spec.align == '\0': '>' else: spec.align + let result = alignString(f, spec.minimumWidth, + align, spec.fill) + if spec.typ in {'A'..'Z'}: + res.add toUpperAscii(result) + else: + res.add result + +proc format*(value: string; specifier: string; res: var string) = + ## Standard format implementation for ``string``. It makes little + ## sense to call this directly, but it is required to exist + ## by the ``fmt`` macro. + let spec = parseStandardFormatSpecifier(specifier) + var fmode = ffDefault + case spec.typ + of 's', '\0': discard + else: + raise newException(ValueError, + "invalid type in format string for string, expected 's', but got " & + spec.typ) + res.add alignString(value, spec.minimumWidth, spec.align, spec.fill) diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index a54556915..2bd87837f 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -31,7 +31,10 @@ As can be seen from the examples, strings are matched verbatim except for substrings starting with ``$``. These constructions are available: ================= ======================================================== -``$i`` Matches an integer. This uses ``parseutils.parseInt``. +``$b`` Matches a binary integer. This uses ``parseutils.parseBin``. +``$o`` Matches an octal integer. This uses ``parseutils.parseOct``. +``$i`` Matches a decimal integer. This uses ``parseutils.parseInt``. +``$h`` Matches a hex integer. This uses ``parseutils.parseHex``. ``$f`` Matches a floating pointer number. Uses ``parseFloat``. ``$w`` Matches an ASCII identifier: ``[A-Z-a-z_][A-Za-z_0-9]*``. ``$s`` Skips optional whitespace. @@ -330,19 +333,37 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b conds.add resLen.notZero conds.add resLen of 'w': - if i < results.len or getType(results[i]).typeKind != ntyString: + if i < results.len and getType(results[i]).typeKind == ntyString: matchBind "parseIdent" else: error("no string var given for $w") inc i + of 'b': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseBin" + else: + error("no int var given for $b") + inc i + of 'o': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseOct" + else: + error("no int var given for $o") + inc i of 'i': - if i < results.len or getType(results[i]).typeKind != ntyInt: + if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseInt" else: - error("no int var given for $d") + error("no int var given for $i") + inc i + of 'h': + if i < results.len and getType(results[i]).typeKind == ntyInt: + matchBind "parseHex" + else: + error("no int var given for $h") inc i of 'f': - if i < results.len or getType(results[i]).typeKind != ntyFloat: + if i < results.len and getType(results[i]).typeKind == ntyFloat: matchBind "parseFloat" else: error("no float var given for $f") @@ -357,7 +378,7 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b else: error("invalid format string") of '*', '+': - if i < results.len or getType(results[i]).typeKind != ntyString: + if i < results.len and getType(results[i]).typeKind == ntyString: var min = ord(pattern[p] == '+') var q=p+1 var token = "" @@ -441,7 +462,7 @@ template success*(x: int): bool = x != 0 template nxt*(input: string; idx, step: int = 1) = inc(idx, step) macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = - ## See top level documentation of his module of how ``scanp`` works. + ## ``scanp`` is currently undocumented. type StmtTriple = tuple[init, cond, action: NimNode] template interf(x): untyped = bindSym(x, brForceOpen) @@ -563,12 +584,12 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = of nnkCurlyExpr: if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit: var h = newTree(nnkPar, it[0]) - for count in 2..it[1].intVal: h.add(it[0]) + for count in 2i64 .. it[1].intVal: h.add(it[0]) for count in it[1].intVal .. it[2].intVal-1: h.add(newTree(nnkPrefix, ident"?", it[0])) result = atm(h, input, idx, attached) elif it.len == 2 and it[1].kind == nnkIntLit: var h = newTree(nnkPar, it[0]) - for count in 2..it[1].intVal: h.add(it[0]) + for count in 2i64 .. it[1].intVal: h.add(it[0]) result = atm(h, input, idx, attached) else: error("invalid pattern") @@ -645,6 +666,14 @@ when isMainModule: doAssert intval == 89 doAssert floatVal == 33.25 + var binval: int + var octval: int + var hexval: int + doAssert scanf("0b0101 0o1234 0xabcd", "$b$s$o$s$h", binval, octval, hexval) + doAssert binval == 0b0101 + doAssert octval == 0o1234 + doAssert hexval == 0xabcd + let xx = scanf("$abc", "$$$i", intval) doAssert xx == false diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index cc0f474f4..dbb4db781 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -32,10 +32,6 @@ when defined(nimOldSplit): else: {.pragma: deprecatedSplit.} -type - CharSet* {.deprecated.} = set[char] # for compatibility with Nim -{.deprecated: [TCharSet: CharSet].} - const Whitespace* = {' ', '\t', '\v', '\r', '\l', '\f'} ## All the characters that count as whitespace. @@ -78,40 +74,40 @@ proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, return c in Letters proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericChar".}= + rtl, extern: "nsuIsAlphaNumericChar".} = ## Checks whether or not `c` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. - return c in Letters or c in Digits + return c in Letters+Digits proc isDigit*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitChar".}= + rtl, extern: "nsuIsDigitChar".} = ## Checks whether or not `c` is a number. ## ## This checks 0-9 ASCII characters only. return c in Digits proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiChar".}= + rtl, extern: "nsuIsSpaceAsciiChar".} = ## Checks whether or not `c` is a whitespace character. return c in Whitespace proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerAsciiChar".}= + rtl, extern: "nsuIsLowerAsciiChar".} = ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. return c in {'a'..'z'} proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperAsciiChar".}= + rtl, extern: "nsuIsUpperAsciiChar".} = ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. return c in {'A'..'Z'} proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaAsciiStr".}= + rtl, extern: "nsuIsAlphaAsciiStr".} = ## Checks whether or not `s` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. @@ -123,10 +119,10 @@ proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isAlphaAscii() and result + if not c.isAlphaAscii(): return false proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericStr".}= + rtl, extern: "nsuIsAlphaNumericStr".} = ## Checks whether or not `s` is alphanumeric. ## ## This checks a-z, A-Z, 0-9 ASCII characters only. @@ -138,10 +134,11 @@ proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isAlphaNumeric() and result + if not c.isAlphaNumeric(): + return false proc isDigit*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitStr".}= + rtl, extern: "nsuIsDigitStr".} = ## Checks whether or not `s` is a numeric value. ## ## This checks 0-9 ASCII characters only. @@ -153,10 +150,11 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isDigit() and result + if not c.isDigit(): + return false proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiStr".}= + rtl, extern: "nsuIsSpaceAsciiStr".} = ## Checks whether or not `s` is completely whitespace. ## ## Returns true if all characters in `s` are whitespace @@ -170,7 +168,7 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, return false proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerAsciiStr".}= + rtl, extern: "nsuIsLowerAsciiStr".} = ## Checks whether or not `s` contains all lower case characters. ## ## This checks ASCII characters only. @@ -185,7 +183,7 @@ proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, true proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperAsciiStr".}= + rtl, extern: "nsuIsUpperAsciiStr".} = ## Checks whether or not `s` contains all upper case characters. ## ## This checks ASCII characters only. @@ -504,16 +502,15 @@ template splitCommon(s, sep, maxsplit, sepLen) = var last = 0 var splits = maxsplit - if len(s) > 0: - while last <= len(s): - var first = last - while last < len(s) and not stringHasSep(s, last, sep): - inc(last) - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) - inc(last, sepLen) + while last <= len(s): + var first = last + while last < len(s) and not stringHasSep(s, last, sep): + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) template oldSplit(s, seps, maxsplit) = var last = 0 @@ -576,15 +573,46 @@ iterator split*(s: string, seps: set[char] = Whitespace, else: splitCommon(s, seps, maxsplit, 1) -iterator splitWhitespace*(s: string): string = - ## Splits at whitespace. - oldSplit(s, Whitespace, -1) +iterator splitWhitespace*(s: string, maxsplit: int = -1): string = + ## Splits the string ``s`` at whitespace stripping leading and trailing + ## whitespace if necessary. If ``maxsplit`` is specified and is positive, + ## no more than ``maxsplit`` splits is made. + ## + ## The following code: + ## + ## .. code-block:: nim + ## let s = " foo \t bar baz " + ## for ms in [-1, 1, 2, 3]: + ## echo "------ maxsplit = ", ms, ":" + ## for item in s.splitWhitespace(maxsplit=ms): + ## echo '"', item, '"' + ## + ## ...results in: + ## + ## .. code-block:: + ## ------ maxsplit = -1: + ## "foo" + ## "bar" + ## "baz" + ## ------ maxsplit = 1: + ## "foo" + ## "bar baz " + ## ------ maxsplit = 2: + ## "foo" + ## "bar" + ## "baz " + ## ------ maxsplit = 3: + ## "foo" + ## "bar" + ## "baz" + ## + oldSplit(s, Whitespace, maxsplit) -proc splitWhitespace*(s: string): seq[string] {.noSideEffect, +proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitWhitespace".} = - ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_ + ## The same as the `splitWhitespace <#splitWhitespace.i,string,int>`_ ## iterator, but is a proc that returns a sequence of substrings. - accumulateResult(splitWhitespace(s)) + accumulateResult(splitWhitespace(s, maxsplit)) iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. @@ -640,36 +668,35 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = splits = maxsplit startPos = 0 - if len(s) > 0: - # go to -1 in order to get separators at the beginning - while first >= -1: - while first >= 0 and not stringHasSep(s, first, sep): - dec(first) + # go to -1 in order to get separators at the beginning + while first >= -1: + while first >= 0 and not stringHasSep(s, first, sep): + dec(first) - if splits == 0: - # No more splits means set first to the beginning - first = -1 + if splits == 0: + # No more splits means set first to the beginning + first = -1 - if first == -1: - startPos = 0 - else: - startPos = first + sepLen + if first == -1: + startPos = 0 + else: + startPos = first + sepLen - yield substr(s, startPos, last) + yield substr(s, startPos, last) - if splits == 0: - break + if splits == 0: + break - dec(splits) - dec(first) + dec(splits) + dec(first) - last = first + last = first iterator rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator - ## <#split.i,string,char>`_ except in reverse order. + ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim ## for piece in "foo bar".rsplit(WhiteSpace): @@ -689,7 +716,7 @@ iterator rsplit*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator - ## <#split.i,string,char>`_ except in reverse order. + ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim ## for piece in "foo:bar".rsplit(':'): @@ -708,7 +735,7 @@ iterator rsplit*(s: string, sep: string, maxsplit: int = -1, keepSeparators: bool = false): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator - ## <#split.i,string,string>`_ except in reverse order. + ## <#split.i,string,string,int>`_ except in reverse order. ## ## .. code-block:: nim ## for piece in "foothebar".rsplit("the"): @@ -789,14 +816,20 @@ proc countLines*(s: string): int {.noSideEffect, proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. noSideEffect, rtl, extern: "nsuSplitCharSet".} = - ## The same as the `split iterator <#split.i,string,set[char]>`_, but is a + ## The same as the `split iterator <#split.i,string,set[char],int>`_, but is a ## proc that returns a sequence of substrings. + runnableExamples: + doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] + doAssert "".split({' '}) == @[""] accumulateResult(split(s, seps, maxsplit)) proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = - ## The same as the `split iterator <#split.i,string,char>`_, but is a proc + ## The same as the `split iterator <#split.i,string,char,int>`_, but is a proc ## that returns a sequence of substrings. + runnableExamples: + doAssert "a,b,c".split(',') == @["a", "b", "c"] + doAssert "".split(' ') == @[""] accumulateResult(split(s, sep, maxsplit)) proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, @@ -804,7 +837,14 @@ proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEff ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. This is a wrapper around the - ## `split iterator <#split.i,string,string>`_. + ## `split iterator <#split.i,string,string,int>`_. + runnableExamples: + doAssert "a,b,c".split(",") == @["a", "b", "c"] + doAssert "a man a plan a canal panama".split("a ") == @["", "man ", "plan ", "canal panama"] + doAssert "".split("Elon Musk") == @[""] + doAssert "a largely spaced sentence".split(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] + + doAssert "a largely spaced sentence".split(" ", maxsplit=1) == @["a", " largely spaced sentence"] doAssert(sep.len > 0) accumulateResult(split(s, sep, maxsplit)) @@ -812,7 +852,7 @@ proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEff proc rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = - ## The same as the `rsplit iterator <#rsplit.i,string,set[char]>`_, but is a + ## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a ## proc that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, @@ -834,7 +874,7 @@ proc rsplit*(s: string, seps: set[char] = Whitespace, proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitChar".} = - ## The same as the `split iterator <#rsplit.i,string,char>`_, but is a proc + ## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, @@ -856,7 +896,7 @@ proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitString".} = - ## The same as the `split iterator <#rsplit.i,string,string>`_, but is a proc + ## The same as the `rsplit iterator <#rsplit.i,string,string,int>`_, but is a proc ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, @@ -873,6 +913,13 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## + runnableExamples: + doAssert "a largely spaced sentence".rsplit(" ", maxsplit=1) == @["a largely spaced", "sentence"] + + doAssert "a,b,c".rsplit(",") == @["a", "b", "c"] + doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", "plan ", "canal panama"] + doAssert "".rsplit("Elon Musk") == @[""] + doAssert "a largely spaced sentence".rsplit(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] accumulateResult(rsplit(s, sep, maxsplit)) result.reverse() @@ -1062,8 +1109,8 @@ proc align*(s: string, count: Natural, padding = ' '): string {. ## ## `padding` characters (by default spaces) are added before `s` resulting in ## right alignment. If ``s.len >= count``, no spaces are added and `s` is - ## returned unchanged. If you need to left align a string use the `repeatChar - ## proc <#repeatChar>`_. Example: + ## returned unchanged. If you need to left align a string use the `alignLeft + ## proc <#alignLeft>`_. Example: ## ## .. code-block:: nim ## assert align("abc", 4) == " abc" @@ -1078,6 +1125,28 @@ proc align*(s: string, count: Natural, padding = ' '): string {. else: result = s +proc alignLeft*(s: string, count: Natural, padding = ' '): string {.noSideEffect.} = + ## Left-Aligns a string `s` with `padding`, so that it is of length `count`. + ## + ## `padding` characters (by default spaces) are added after `s` resulting in + ## left alignment. If ``s.len >= count``, no spaces are added and `s` is + ## returned unchanged. If you need to right align a string use the `align + ## proc <#align>`_. Example: + ## + ## .. code-block:: nim + ## assert alignLeft("abc", 4) == "abc " + ## assert alignLeft("a", 0) == "a" + ## assert alignLeft("1232", 6) == "1232 " + ## assert alignLeft("1232", 6, '#') == "1232##" + if s.len < count: + result = newString(count) + if s.len > 0: + result[0 .. (s.len - 1)] = s + for i in s.len ..< count: + result[i] = padding + else: + result = s + iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ token: string, isSep: bool] = ## Tokenizes the string `s` into substrings. @@ -1175,7 +1244,7 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string var indentCount = 0 for j in 0..<count.int: indentCount.inc - if line[j .. j + <padding.len] != padding: + if line[j .. j + padding.len-1] != padding: indentCount = j break result.add(line[indentCount*padding.len .. ^1]) @@ -1250,14 +1319,13 @@ proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) ## This is often useful for generating some code where the items need to ## be *separated* by `sep`. `sep` is only added if `dest` is longer than ## `startLen`. The following example creates a string describing - ## an array of integers: - ## - ## .. code-block:: nim - ## var arr = "[" - ## for x in items([2, 3, 5, 7, 11]): - ## addSep(arr, startLen=len("[")) - ## add(arr, $x) - ## add(arr, "]") + ## an array of integers. + runnableExamples: + var arr = "[" + for x in items([2, 3, 5, 7, 11]): + addSep(arr, startLen=len("[")) + add(arr, $x) + add(arr, "]") if dest.len > startLen: add(dest, sep) proc allCharsInSet*(s: string, theSet: set[char]): bool = @@ -1306,18 +1374,36 @@ proc join*[T: not string](a: openArray[T], sep: string = ""): string {. add(result, $x) type - SkipTable = array[char, int] - -{.push profiler: off.} -proc preprocessSub(sub: string, a: var SkipTable) = - var m = len(sub) - for i in 0..0xff: a[chr(i)] = m+1 - for i in 0..m-1: a[sub[i]] = m-i -{.pop.} + SkipTable* = array[char, int] -proc findAux(s, sub: string, start, last: int, a: SkipTable): int = - # Fast "quick search" algorithm: - var +proc initSkipTable*(a: var SkipTable, sub: string) + {.noSideEffect, rtl, extern: "nsuInitSkipTable".} = + ## Preprocess table `a` for `sub`. + let m = len(sub) + let m1 = m + 1 + var i = 0 + while i <= 0xff-7: + a[chr(i + 0)] = m1 + a[chr(i + 1)] = m1 + a[chr(i + 2)] = m1 + a[chr(i + 3)] = m1 + a[chr(i + 4)] = m1 + a[chr(i + 5)] = m1 + a[chr(i + 6)] = m1 + a[chr(i + 7)] = m1 + i += 8 + + for i in 0..m-1: + a[sub[i]] = m-i + +proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int + {.noSideEffect, rtl, extern: "nsuFindStrA".} = + ## Searches for `sub` in `s` inside range `start`..`last` using preprocessed table `a`. + ## If `last` is unspecified, it defaults to `s.high`. + ## + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + let + last = if last==0: s.high else: last m = len(sub) n = last + 1 # search: @@ -1337,17 +1423,6 @@ when not (defined(js) or defined(nimdoc) or defined(nimscript)): else: const hasCStringBuiltin = false -proc find*(s, sub: string, start: Natural = 0, last: Natural = 0): int {.noSideEffect, - rtl, extern: "nsuFindStr".} = - ## Searches for `sub` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. - ## - ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - var a {.noinit.}: SkipTable - let last = if last==0: s.high else: last - preprocessSub(sub, a) - result = findAux(s, sub, start, last, a) - proc find*(s: string, sub: char, start: Natural = 0, last: Natural = 0): int {.noSideEffect, rtl, extern: "nsuFindChar".} = ## Searches for `sub` in `s` inside range `start`..`last`. @@ -1366,9 +1441,24 @@ proc find*(s: string, sub: char, start: Natural = 0, last: Natural = 0): int {.n else: for i in start..last: if sub == s[i]: return i - return -1 +proc find*(s, sub: string, start: Natural = 0, last: Natural = 0): int {.noSideEffect, + rtl, extern: "nsuFindStr".} = + ## Searches for `sub` in `s` inside range `start`..`last`. + ## If `last` is unspecified, it defaults to `s.high`. + ## + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + if sub.len > s.len: + return -1 + + if sub.len == 1: + return find(s, sub[0], start, last) + + var a {.noinit.}: SkipTable + initSkipTable(a, sub) + result = find(a, s, sub, start, last) + proc find*(s: string, chars: set[char], start: Natural = 0, last: Natural = 0): int {.noSideEffect, rtl, extern: "nsuFindCharSet".} = ## Searches for `chars` in `s` inside range `start`..`last`. @@ -1500,11 +1590,11 @@ proc replace*(s, sub: string, by = ""): string {.noSideEffect, ## Replaces `sub` in `s` by the string `by`. var a {.noinit.}: SkipTable result = "" - preprocessSub(sub, a) + initSkipTable(a, sub) let last = s.high var i = 0 while true: - var j = findAux(s, sub, i, last, a) + var j = find(a, s, sub, i, last) if j < 0: break add result, substr(s, i, j - 1) add result, by @@ -1534,11 +1624,11 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} var a {.noinit.}: SkipTable result = "" - preprocessSub(sub, a) + initSkipTable(a, sub) var i = 0 let last = s.high while true: - var j = findAux(s, sub, i, last, a) + var j = find(a, s, sub, i, last) if j < 0: break # word boundary? if (j == 0 or s[j-1] notin wordChars) and @@ -1653,7 +1743,9 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, ## ## Even though the algorithm works with any string `s`, it is only useful ## if `s` contains a number. - ## Example: ``insertSep("1000000") == "1_000_000"`` + runnableExamples: + doAssert insertSep("1000000") == "1_000_000" + var L = (s.len-1) div digits + s.len result = newString(L) var j = 0 @@ -1669,29 +1761,15 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuEscape".} = - ## Escapes a string `s`. - ## - ## This does these operations (at the same time): - ## * replaces any ``\`` by ``\\`` - ## * replaces any ``'`` by ``\'`` - ## * replaces any ``"`` by ``\"`` - ## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}`` - ## by ``\xHH`` where ``HH`` is its hexadecimal value. - ## The procedure has been designed so that its output is usable for many - ## different common syntaxes. The resulting string is prefixed with - ## `prefix` and suffixed with `suffix`. Both may be empty strings. - ## **Note**: This is not correct for producing Ansi C code! + ## Escapes a string `s`. See `system.addEscapedChar <system.html#addEscapedChar>`_ + ## for the escaping scheme. + ## + ## The resulting string is prefixed with `prefix` and suffixed with `suffix`. + ## Both may be empty strings. result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): - case c - of '\0'..'\31', '\127'..'\255': - add(result, "\\x") - add(result, toHex(ord(c), 2)) - of '\\': add(result, "\\\\") - of '\'': add(result, "\\'") - of '\"': add(result, "\\\"") - else: add(result, c) + result.addEscapedChar(c) add(result, suffix) proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, @@ -1741,6 +1819,8 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## ## A valid identifier starts with a character of the set `IdentStartChars` ## and is followed by any number of characters of the set `IdentChars`. + runnableExamples: + doAssert "abc_def08".validIdentifier if s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false @@ -1751,7 +1831,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, ## Returns the edit distance between `a` and `b`. ## ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. This implementation is highly optimized! + ## memory overhead. var len1 = a.len var len2 = b.len if len1 > len2: @@ -1850,7 +1930,7 @@ type {.deprecated: [TFloatFormat: FloatFormatMode].} proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16; + precision: range[-1..32] = 16; decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. @@ -1862,7 +1942,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## `precision`'s default value is the maximum number of meaningful digits ## after the decimal point for Nim's ``biggestFloat`` type. ## - ## If ``precision == 0``, it tries to format it nicely. + ## If ``precision == -1``, it tries to format it nicely. when defined(js): var res: cstring case format @@ -1884,7 +1964,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, buf {.noinit.}: array[0..2500, char] L: cint frmtstr[0] = '%' - if precision > 0: + if precision >= 0: frmtstr[1] = '#' frmtstr[2] = '.' frmtstr[3] = '*' @@ -1907,9 +1987,18 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, # but nothing else is possible: if buf[i] in {'.', ','}: result[i] = decimalsep else: result[i] = buf[i] + when defined(windows): + # VS pre 2015 violates the C standard: "The exponent always contains at + # least two digits, and only as many more digits as necessary to + # represent the exponent." [C11 §7.21.6.1] + # The following post-processing fixes this behavior. + if result.len > 4 and result[^4] == '+' and result[^3] == '0': + result[^3] = result[^2] + result[^2] = result[^1] + result.setLen(result.len - 1) proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16; decimalSep = '.'): string {. + precision: range[-1..32] = 16; decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1920,7 +2009,12 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## `precision`'s default value is the maximum number of meaningful digits ## after the decimal point for Nim's ``float`` type. ## - ## If ``precision == 0``, it tries to format it nicely. + ## If ``precision == -1``, it tries to format it nicely. + runnableExamples: + let x = 123.456 + doAssert x.formatFloat() == "123.4560000000000" + doAssert x.formatFloat(ffDecimal, 4) == "123.4560" + doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" result = formatBiggestFloat(f, format, precision, decimalSep) proc trimZeros*(x: var string) {.noSideEffect.} = @@ -1955,18 +2049,13 @@ proc formatSize*(bytes: int64, ## ## `includeSpace` can be set to true to include the (SI preferred) space ## between the number and the unit (e.g. 1 KiB). - ## - ## Examples: - ## - ## .. code-block:: nim - ## - ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" - ## formatSize((2.234*1024*1024).int) == "2.234MiB" - ## formatSize(4096, includeSpace=true) == "4 KiB" - ## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" - ## formatSize(4096) == "4KiB" - ## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" - ## + runnableExamples: + doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" + doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" + doAssert formatSize(4096, includeSpace=true) == "4 KiB" + doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + doAssert formatSize(4096) == "4KiB" + doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] var @@ -2060,7 +2149,7 @@ proc formatEng*(f: BiggestFloat, ## formatEng(4100, unit="V") == "4.1e3 V" ## formatEng(4100, unit="") == "4.1e3 " # Space with unit="" ## - ## `decimalSep` is used as the decimal separator + ## `decimalSep` is used as the decimal separator. var absolute: BiggestFloat significand: BiggestFloat @@ -2271,69 +2360,116 @@ proc format*(formatstr: string, a: varargs[string, `$`]): string {.noSideEffect, proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. rtl, extern: "nsuRemoveSuffixCharSet".} = - ## Removes the first matching character from the string (in-place) given a - ## set of characters. If the set of characters is only equal to `Newlines` - ## then it will remove both the newline and return feed. - ## .. code-block:: nim - ## var - ## userInput = "Hello World!\r\n" - ## otherInput = "Hello!?!" - ## userInput.removeSuffix - ## userInput == "Hello World!" - ## userInput.removeSuffix({'!', '?'}) - ## userInput == "Hello World" - ## otherInput.removeSuffix({'!', '?'}) - ## otherInput == "Hello!?" + ## Removes all characters from `chars` from the end of the string `s` + ## (in-place). + runnableExamples: + var userInput = "Hello World!*~\r\n" + userInput.removeSuffix + doAssert userInput == "Hello World!*~" + userInput.removeSuffix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "Hello!?!" + otherInput.removeSuffix({'!', '?'}) + doAssert otherInput == "Hello" if s.len == 0: return - var last = len(s) - 1 - if chars == Newlines: - if s[last] == '\10': - last -= 1 - if s[last] == '\13': - last -= 1 - else: - if s[last] in chars: - last -= 1 + var last = s.high + while last > -1 and s[last] in chars: last -= 1 s.setLen(last + 1) proc removeSuffix*(s: var string, c: char) {. rtl, extern: "nsuRemoveSuffixChar".} = - ## Removes a single character (in-place) from a string. - ## .. code-block:: nim - ## var - ## table = "users" - ## table.removeSuffix('s') - ## table == "user" + ## Removes all occurrences of a single character (in-place) from the end + ## of a string. + ## + runnableExamples: + var table = "users" + table.removeSuffix('s') + doAssert table == "user" + + var dots = "Trailing dots......." + dots.removeSuffix('.') + doAssert dots == "Trailing dots" removeSuffix(s, chars = {c}) proc removeSuffix*(s: var string, suffix: string) {. rtl, extern: "nsuRemoveSuffixString".} = ## Remove the first matching suffix (in-place) from a string. - ## .. code-block:: nim - ## var - ## answers = "yeses" - ## answers.removeSuffix("es") - ## answers == "yes" + runnableExamples: + var answers = "yeses" + answers.removeSuffix("es") + doAssert answers == "yes" var newLen = s.len if s.endsWith(suffix): newLen -= len(suffix) s.setLen(newLen) +proc removePrefix*(s: var string, chars: set[char] = Newlines) {. + rtl, extern: "nsuRemovePrefixCharSet".} = + ## Removes all characters from `chars` from the start of the string `s` + ## (in-place). + ## + runnableExamples: + var userInput = "\r\n*~Hello World!" + userInput.removePrefix + doAssert userInput == "*~Hello World!" + userInput.removePrefix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "?!?Hello!?!" + otherInput.removePrefix({'!', '?'}) + doAssert otherInput == "Hello!?!" + var start = 0 + while start < s.len and s[start] in chars: start += 1 + if start > 0: s.delete(0, start - 1) + +proc removePrefix*(s: var string, c: char) {. + rtl, extern: "nsuRemovePrefixChar".} = + ## Removes all occurrences of a single character (in-place) from the start + ## of a string. + ## + runnableExamples: + var ident = "pControl" + ident.removePrefix('p') + doAssert ident == "Control" + removePrefix(s, chars = {c}) + +proc removePrefix*(s: var string, prefix: string) {. + rtl, extern: "nsuRemovePrefixString".} = + ## Remove the first matching prefix (in-place) from a string. + ## + runnableExamples: + var answers = "yesyes" + answers.removePrefix("yes") + doAssert answers == "yes" + if s.startsWith(prefix): + s.delete(0, prefix.len - 1) + when isMainModule: doAssert align("abc", 4) == " abc" doAssert align("a", 0) == "a" doAssert align("1232", 6) == " 1232" doAssert align("1232", 6, '#') == "##1232" + doAssert alignLeft("abc", 4) == "abc " + doAssert alignLeft("a", 0) == "a" + doAssert alignLeft("1232", 6) == "1232 " + doAssert alignLeft("1232", 6, '#') == "1232##" + let inp = """ this is a long text -- muchlongerthan10chars and here it goes""" outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes" doAssert wordWrap(inp, 10, false) == outp + doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" + doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235." + doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6" doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in ["1,0e-11", "1,0e-011"] + # bug #6589 + doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02" doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb" @@ -2497,6 +2633,12 @@ bar doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] + doAssert s.splitWhitespace() == @["this", "is", "an", "example"] + doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "] + doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "] + doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "] + doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"] + block: # formatEng tests doAssert formatEng(0, 2, trim=false) == "0.00" doAssert formatEng(0, 2) == "0" diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 871ac5d39..f15cee66a 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -391,8 +391,8 @@ proc eraseLine*(f: File) = origin.X = 0'i16 if setConsoleCursorPosition(h, origin) == 0: raiseOSError(osLastError()) - var ht = scrbuf.dwSize.Y - origin.Y - var wt = scrbuf.dwSize.X - origin.X + var ht: DWORD = scrbuf.dwSize.Y - origin.Y + var wt: DWORD = scrbuf.dwSize.X - origin.X if fillConsoleOutputCharacter(h, ' ', ht*wt, origin, addr(numwrote)) == 0: raiseOSError(osLastError()) @@ -634,7 +634,10 @@ proc getch*(): char = doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0) if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0: continue - return char(keyEvent.uChar) + if keyEvent.uChar == 0: + return char(keyEvent.wVirtualKeyCode) + else: + return char(keyEvent.uChar) else: let fd = getFileHandle(stdin) var oldMode: Termios @@ -650,10 +653,10 @@ template setCursorPos*(x, y: int) = setCursorPos(stdout, x, y) template setCursorXPos*(x: int) = setCursorXPos(stdout, x) when defined(windows): template setCursorYPos(x: int) = setCursorYPos(stdout, x) -template cursorUp*(count=1) = cursorUp(stdout, f) -template cursorDown*(count=1) = cursorDown(stdout, f) -template cursorForward*(count=1) = cursorForward(stdout, f) -template cursorBackward*(count=1) = cursorBackward(stdout, f) +template cursorUp*(count=1) = cursorUp(stdout, count) +template cursorDown*(count=1) = cursorDown(stdout, count) +template cursorForward*(count=1) = cursorForward(stdout, count) +template cursorBackward*(count=1) = cursorBackward(stdout, count) template eraseLine*() = eraseLine(stdout) template eraseScreen*() = eraseScreen(stdout) template setStyle*(style: set[Style]) = diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 96668c4f8..42e89e7ce 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -10,27 +10,25 @@ ## This module contains routines and types for dealing with time. ## This module is available for the `JavaScript target -## <backends.html#the-javascript-target>`_. +## <backends.html#the-javascript-target>`_. The proleptic Gregorian calendar is the only calendar supported. ## ## Examples: ## ## .. code-block:: nim ## ## import times, os -## var -## t = cpuTime() +## let time = cpuTime() ## ## sleep(100) # replace this with something to be timed -## echo "Time taken: ",cpuTime() - t +## echo "Time taken: ",cpuTime() - time ## -## echo "My formatted time: ", format(getLocalTime(getTime()), "d MMMM yyyy HH:mm") +## echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") ## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() ## ## echo "epochTime() float value: ", epochTime() -## echo "getTime() float value: ", toSeconds(getTime()) ## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", getLocalTime(getTime()) + 1.hours -## echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1) +## echo "An hour from now : ", now() + 1.hours +## echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! @@ -40,132 +38,85 @@ import include "system/inclrtl" -type - Month* = enum ## represents a month - mJan, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec - WeekDay* = enum ## represents a weekday - dMon, dTue, dWed, dThu, dFri, dSat, dSun - -when defined(posix) and not defined(JS): - when defined(linux) and defined(amd64): - type - TimeImpl {.importc: "time_t", header: "<time.h>".} = clong - Time* = distinct TimeImpl ## distinct type that represents a time - ## measured as number of seconds since the epoch - - Timeval {.importc: "struct timeval", - header: "<sys/select.h>".} = object ## struct timeval - tv_sec: clong ## Seconds. - tv_usec: clong ## Microseconds. - else: - type - TimeImpl {.importc: "time_t", header: "<time.h>".} = int - Time* = distinct TimeImpl ## distinct type that represents a time - ## measured as number of seconds since the epoch - - Timeval {.importc: "struct timeval", - header: "<sys/select.h>".} = object ## struct timeval - tv_sec: int ## Seconds. - tv_usec: int ## Microseconds. +when defined(posix): + import posix - # we cannot import posix.nim here, because posix.nim depends on times.nim. - # Ok, we could, but I don't want circular dependencies. - # And gettimeofday() is not defined in the posix module anyway. Sigh. + type CTime = posix.Time proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "<sys/time.h>".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "<time.h>".}: int - proc tzset(): void {.importc, header: "<time.h>".} tzset() elif defined(windows): import winlean # newest version of Visual C++ defines time_t to be of 64 bits - type TimeImpl {.importc: "time_t", header: "<time.h>".} = int64 + type CTime {.importc: "time_t", header: "<time.h>".} = distinct int64 # visual c's c runtime exposes these under a different name - var - timezone {.importc: "_timezone", header: "<time.h>".}: int - - type - Time* = distinct TimeImpl + var timezone {.importc: "_timezone", header: "<time.h>".}: int +type + Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give + ## the month number in the range ``[1..12]``. + mJan = 1, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec -elif defined(JS): - type - TimeBase = float - Time* = distinct TimeBase - - proc getDay(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getHours(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getTime(t: Time): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} - proc getTimezoneOffset(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getDate(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDate(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCHours(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDay(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc getYear(t: Time): int {.tags: [], raises: [], benign, importcpp.} - proc parse(t: Time; s: cstring): Time {.tags: [], raises: [], benign, importcpp.} - proc setDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setTime(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setUTCSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc setYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.} - proc toGMTString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.} - proc toLocaleString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.} + WeekDay* = enum ## Represents a weekday. + dMon, dTue, dWed, dThu, dFri, dSat, dSun -type - TimeInfo* = object of RootObj ## represents a time in different parts - second*: range[0..61] ## The number of seconds after the minute, + MonthdayRange* = range[1..31] + HourRange* = range[0..23] + MinuteRange* = range[0..59] + SecondRange* = range[0..60] + YeardayRange* = range[0..365] + + TimeImpl = int64 + + Time* = distinct TimeImpl ## Represents a point in time. + ## This is currently implemented as a ``int64`` representing + ## seconds since ``1970-01-01T00:00:00Z``, but don't + ## rely on this knowledge because it might change + ## in the future to allow for higher precision. + ## Use the procs ``toUnix`` and ``fromUnix`` to + ## work with unix timestamps instead. + + DateTime* = object of RootObj ## Represents a time in different parts. + ## Although this type can represent leap + ## seconds, they are generally not supported + ## in this module. They are not ignored, + ## but the ``DateTime``'s returned by + ## procedures in this module will never have + ## a leap second. + second*: SecondRange ## The number of seconds after the minute, ## normally in the range 0 to 59, but can - ## be up to 61 to allow for leap seconds. - minute*: range[0..59] ## The number of minutes after the hour, + ## be up to 60 to allow for a leap second. + minute*: MinuteRange ## The number of minutes after the hour, ## in the range 0 to 59. - hour*: range[0..23] ## The number of hours past midnight, + hour*: HourRange ## The number of hours past midnight, ## in the range 0 to 23. - monthday*: range[1..31] ## The day of the month, in the range 1 to 31. + monthday*: MonthdayRange ## The day of the month, in the range 1 to 31. month*: Month ## The current month. - year*: int ## The current year. + year*: int ## The current year, using astronomical year numbering + ## (meaning that before year 1 is year 0, then year -1 and so on). weekday*: WeekDay ## The current day of the week. - yearday*: range[0..365] ## The number of days since January 1, + yearday*: YeardayRange ## The number of days since January 1, ## in the range 0 to 365. - ## Always 0 if the target is JS. - isDST*: bool ## Determines whether DST is in effect. - ## Semantically, this adds another negative hour - ## offset to the time in addition to the timezone. - timezone*: int ## The offset of the (non-DST) timezone in seconds - ## west of UTC. Note that the sign of this number - ## is the opposite of the one in a formatted - ## timezone string like ``+01:00`` (which would be - ## parsed into the timezone ``-3600``). - - ## I make some assumptions about the data in here. Either - ## everything should be positive or everything negative. Zero is - ## fine too. Mixed signs will lead to unexpected results. - TimeInterval* = object ## a time interval + isDst*: bool ## Determines whether DST is in effect. + ## Always false for the JavaScript backend. + timezone*: Timezone ## The timezone represented as an implementation of ``Timezone``. + utcOffset*: int ## The offset in seconds west of UTC, including any offset due to DST. + ## Note that the sign of this number is the opposite + ## of the one in a formatted offset string like ``+01:00`` + ## (which would be parsed into the UTC offset ``-3600``). + + TimeInterval* = object ## Represents a duration of time. Can be used to add and subtract + ## from a ``DateTime`` or ``Time``. + ## Note that a ``TimeInterval`` doesn't represent a fixed duration of time, + ## since the duration of some units depend on the context (e.g a year + ## can be either 365 or 366 days long). The non-fixed time units are years, + ## months and days. milliseconds*: int ## The number of milliseconds seconds*: int ## The number of seconds minutes*: int ## The number of minutes @@ -174,92 +125,394 @@ type months*: int ## The number of months years*: int ## The number of years + Timezone* = object ## Timezone interface for supporting ``DateTime``'s of arbritary timezones. + ## The ``times`` module only supplies implementations for the systems local time and UTC. + ## The members ``zoneInfoFromUtc`` and ``zoneInfoFromTz`` should not be accessed directly + ## and are only exported so that ``Timezone`` can be implemented by other modules. + zoneInfoFromUtc*: proc (time: Time): ZonedTime {.nimcall, tags: [], raises: [], benign .} + zoneInfoFromTz*: proc (adjTime: Time): ZonedTime {.nimcall, tags: [], raises: [], benign .} + name*: string ## The name of the timezone, f.ex 'Europe/Stockholm' or 'Etc/UTC'. Used for checking equality. + ## Se also: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + ZonedTime* = object ## Represents a zooned instant in time that is not associated with any calendar. + ## This type is only used for implementing timezones. + adjTime*: Time ## Time adjusted to a timezone. + utcOffset*: int + isDst*: bool + {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, - TTimeInterval: TimeInterval, TTimeInfo: TimeInfo].} + TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].} -proc getTime*(): Time {.tags: [TimeEffect], benign.} - ## gets the current calendar time as a UNIX epoch value (number of seconds - ## elapsed since 1970) with integer precission. Use epochTime for higher - ## resolution. -proc getLocalTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.} - ## converts the calendar time `t` to broken-time representation, - ## expressed relative to the user's specified time zone. -proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.} - ## converts the calendar time `t` to broken-down time representation, - ## expressed in Coordinated Universal Time (UTC). - -proc timeInfoToTime*(timeInfo: TimeInfo): Time - {.tags: [TimeEffect], benign, deprecated.} - ## converts a broken-down time structure to - ## calendar time representation. The function ignores the specified - ## contents of the structure members `weekday` and `yearday` and recomputes - ## them from the other information in the broken-down time structure. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTime`` instead. +const + secondsInMin = 60 + secondsInHour = 60*60 + secondsInDay = 60*60*24 + minutesInHour = 60 -proc toTime*(timeInfo: TimeInfo): Time {.tags: [TimeEffect], benign.} - ## converts a broken-down time structure to - ## calendar time representation. The function ignores the specified - ## contents of the structure members `weekday` and `yearday` and recomputes - ## them from the other information in the broken-down time structure. +proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. + Time(unix) -proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign.} - ## Takes a float which contains the number of seconds since the unix epoch and - ## returns a time object. +proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert ``t`` to a unix timestamp (seconds since ``1970-01-01T00:00:00Z``). + t.int64 -proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign.} = - ## Takes an int which contains the number of seconds since the unix epoch and - ## returns a time object. - fromSeconds(float(since1970)) +proc isLeapYear*(year: int): bool = + ## Returns true if ``year`` is a leap year. + year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0) -proc toSeconds*(time: Time): float {.tags: [], raises: [], benign.} - ## Returns the time in seconds since the unix epoch. +proc getDaysInMonth*(month: Month, year: int): int = + ## Get the number of days in a ``month`` of a ``year``. + # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month + case month + of mFeb: result = if isLeapYear(year): 29 else: 28 + of mApr, mJun, mSep, mNov: result = 30 + else: result = 31 + +proc getDaysInYear*(year: int): int = + ## Get the number of days in a ``year`` + result = 365 + (if isLeapYear(year): 1 else: 0) + +proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = + assert monthday <= getDaysInMonth(month, year), + $year & "-" & $ord(month) & "-" & $monthday & " is not a valid date" + +proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = + ## Get the epoch day from a year/month/day date. + ## The epoch day is the number of days since 1970/01/01 (it might be negative). + assertValidDate monthday, month, year + # Based on http://howardhinnant.github.io/date_algorithms.html + var (y, m, d) = (year, ord(month), monthday.int) + if m <= 2: + y.dec + + let era = (if y >= 0: y else: y-399) div 400 + let yoe = y - era * 400 + let doy = (153 * (m + (if m > 2: -3 else: 9)) + 2) div 5 + d-1 + let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy + return era * 146097 + doe - 719468 + +proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] = + ## Get the year/month/day date from a epoch day. + ## The epoch day is the number of days since 1970/01/01 (it might be negative). + # Based on http://howardhinnant.github.io/date_algorithms.html + var z = epochday + z.inc 719468 + let era = (if z >= 0: z else: z - 146096) div 146097 + let doe = z - era * 146097 + let yoe = (doe - doe div 1460 + doe div 36524 - doe div 146096) div 365 + let y = yoe + era * 400; + let doy = doe - (365 * yoe + yoe div 4 - yoe div 100) + let mp = (5 * doy + 2) div 153 + let d = doy - (153 * mp + 2) div 5 + 1 + let m = mp + (if mp < 10: 3 else: -9) + return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) + +proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = + ## Returns the day of the year. + ## Equivalent with ``initDateTime(day, month, year).yearday``. + assertValidDate monthday, month, year + const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] + const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] + + if isLeapYear(year): + result = daysUntilMonthLeap[month] + monthday - 1 + else: + result = daysUntilMonth[month] + monthday - 1 + +proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = + ## Returns the day of the week enum from day, month and year. + ## Equivalent with ``initDateTime(day, month, year).weekday``. + assertValidDate monthday, month, year + # 1970-01-01 is a Thursday, we adjust to the previous Monday + let days = toEpochday(monthday, month, year) - 3 + let weeks = (if days >= 0: days else: days - 6) div 7 + let wd = days - weeks * 7 + # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. + # so we must correct for the WeekDay type. + result = if wd == 0: dSun else: WeekDay(wd - 1) + +# Forward declarations +proc utcZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc utcZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} proc `-`*(a, b: Time): int64 {. - rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign.} - ## computes the difference of two calendar times. Result is in seconds. + rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign, deprecated.} = + ## Computes the difference of two calendar times. Result is in seconds. + ## This is deprecated because it will need to change when sub second time resolution is implemented. + ## Use ``a.toUnix - b.toUnix`` instead. ## ## .. code-block:: nim ## let a = fromSeconds(1_000_000_000) ## let b = fromSeconds(1_500_000_000) ## echo initInterval(seconds=int(b - a)) - ## # (milliseconds: 0, seconds: 20, minutes: 53, hours: 0, days: 5787, months: 0, years: 0) + ## # (milliseconds: 0, seconds: 20, minutes: 53, hours: 0, days: 5787, months: 0, years: 0) + a.toUnix - b.toUnix proc `<`*(a, b: Time): bool {. - rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect.} = - ## returns true iff ``a < b``, that is iff a happened before b. - when defined(js): - result = TimeBase(a) < TimeBase(b) - else: - result = a - b < 0 + rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true iff ``a < b``, that is iff a happened before b. proc `<=` * (a, b: Time): bool {. - rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect.}= - ## returns true iff ``a <= b``. - when defined(js): - result = TimeBase(a) <= TimeBase(b) - else: - result = a - b <= 0 + rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true iff ``a <= b``. proc `==`*(a, b: Time): bool {. - rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect.} = - ## returns true if ``a == b``, that is if both times represent the same value - when defined(js): - result = TimeBase(a) == TimeBase(b) + rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect, borrow.} + ## Returns true if ``a == b``, that is if both times represent the same point in time. + +proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = + ## Converts a broken-down time structure to + ## calendar time representation. + let epochDay = toEpochday(dt.monthday, dt.month, dt.year) + result = Time(epochDay * secondsInDay) + result.inc dt.hour * secondsInHour + result.inc dt.minute * 60 + result.inc dt.second + # The code above ignores the UTC offset of `timeInfo`, + # so we need to compensate for that here. + result.inc dt.utcOffset + +proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = + let adjTime = zt.adjTime.int64 + let epochday = (if adjTime >= 0: adjTime else: adjTime - (secondsInDay - 1)) div secondsInDay + var rem = zt.adjTime.int64 - epochday * secondsInDay + let hour = rem div secondsInHour + rem = rem - hour * secondsInHour + let minute = rem div secondsInMin + rem = rem - minute * secondsInMin + let second = rem + + let (d, m, y) = fromEpochday(epochday) + + DateTime( + year: y, + month: m, + monthday: d, + hour: hour, + minute: minute, + second: second, + weekday: getDayOfWeek(d, m, y), + yearday: getDayOfYear(d, m, y), + isDst: zt.isDst, + timezone: zone, + utcOffset: zt.utcOffset + ) + +proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Break down ``time`` into a ``DateTime`` using ``zone`` as the timezone. + let zoneInfo = zone.zoneInfoFromUtc(time) + result = initDateTime(zoneInfo, zone) + +proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Convert ``dt`` into a ``DateTime`` using ``zone`` as the timezone. + dt.toTime.inZone(zone) + +proc `$`*(zone: Timezone): string = + ## Returns the name of the timezone. + zone.name + +proc `==`*(zone1, zone2: Timezone): bool = + ## Two ``Timezone``'s are considered equal if their name is equal. + zone1.name == zone2.name + +proc toAdjTime(dt: DateTime): Time = + let epochDay = toEpochday(dt.monthday, dt.month, dt.year) + result = Time(epochDay * secondsInDay) + result.inc dt.hour * secondsInHour + result.inc dt.minute * secondsInMin + result.inc dt.second + +when defined(JS): + type JsDate = object + proc newDate(year, month, date, hours, minutes, seconds, milliseconds: int): JsDate {.tags: [], raises: [], importc: "new Date".} + proc newDate(): JsDate {.importc: "new Date".} + proc newDate(value: float): JsDate {.importc: "new Date".} + proc getTimezoneOffset(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getTime(js: JsDate): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} + proc getDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getUTCDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} + proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} + + proc localZoneInfoFromUtc(time: Time): ZonedTime = + let jsDate = newDate(time.float * 1000) + let offset = jsDate.getTimezoneOffset() * secondsInMin + result.adjTime = Time(time.int64 - offset) + result.utcOffset = offset + result.isDst = false + + proc localZoneInfoFromTz(adjTime: Time): ZonedTime = + let utcDate = newDate(adjTime.float * 1000) + let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), + utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) + + # This is as dumb as it looks - JS doesn't support years in the range 0-99 in the constructor + # because they are assumed to be 19xx... + # Because JS doesn't support timezone history, it doesn't really matter in practice. + if utcDate.getUTCFullYear() in 0 .. 99: + localDate.setFullYear(utcDate.getUTCFullYear()) + + result.adjTime = adjTime + result.utcOffset = localDate.getTimezoneOffset() * secondsInMin + result.isDst = false + +else: + when defined(freebsd) or defined(netbsd) or defined(openbsd) or + defined(macosx): + type + StructTm {.importc: "struct tm".} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint + gmtoff {.importc: "tm_gmtoff".}: clong else: - result = a - b == 0 + type + StructTm {.importc: "struct tm".} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint + when defined(linux) and defined(amd64): + gmtoff {.importc: "tm_gmtoff".}: clong + zone {.importc: "tm_zone".}: cstring + type + StructTmPtr = ptr StructTm + + proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].} + + proc toAdjTime(tm: StructTm): Time = + let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) + result = Time(epochDay * secondsInDay) + result.inc tm.hour * secondsInHour + result.inc tm.minute * 60 + result.inc tm.second + + proc getStructTm(time: Time | int64): StructTm = + let timei64 = time.int64 + var a = + if timei64 < low(CTime): + CTime(low(CTime)) + elif timei64 > high(CTime): + CTime(high(CTime)) + else: + CTime(timei64) + result = localtime(addr(a))[] + + proc localZoneInfoFromUtc(time: Time): ZonedTime = + let tm = getStructTm(time) + let adjTime = tm.toAdjTime + result.adjTime = adjTime + result.utcOffset = (time.toUnix - adjTime.toUnix).int + result.isDst = tm.isdst > 0 + + proc localZoneInfoFromTz(adjTime: Time): ZonedTime = + var adjTimei64 = adjTime.int64 + let past = adjTimei64 - secondsInDay + var tm = getStructTm(past) + let pastOffset = past - tm.toAdjTime.int64 + + let future = adjTimei64 + secondsInDay + tm = getStructTm(future) + let futureOffset = future - tm.toAdjTime.int64 + + var utcOffset: int + if pastOffset == futureOffset: + utcOffset = pastOffset.int + else: + if pastOffset > futureOffset: + adjTimei64 -= secondsInHour + + adjTimei64 += pastOffset + utcOffset = (adjTimei64 - getStructTm(adjTimei64).toAdjTime.int64).int + + # This extra roundtrip is needed to normalize any impossible datetimes + # as a result of offset changes (normally due to dst) + let utcTime = adjTime.int64 + utcOffset + tm = getStructTm(utcTime) + result.adjTime = tm.toAdjTime + result.utcOffset = (utcTime - result.adjTime.int64).int + result.isDst = tm.isdst > 0 + +proc utcZoneInfoFromUtc(time: Time): ZonedTime = + result.adjTime = time + result.utcOffset = 0 + result.isDst = false + +proc utcZoneInfoFromTz(adjTime: Time): ZonedTime = + utcZoneInfoFromUtc(adjTime) # adjTime == time since we are in UTC + +proc utc*(): TimeZone = + ## Get the ``Timezone`` implementation for the UTC timezone. + ## + ## .. code-block:: nim + ## doAssert now().utc.timezone == utc() + ## doAssert utc().name == "Etc/UTC" + Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC") + +proc local*(): TimeZone = + ## Get the ``Timezone`` implementation for the local timezone. + ## + ## .. code-block:: nim + ## doAssert now().timezone == local() + ## doAssert local().name == "LOCAL" + Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL") -proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign.} - ## returns the offset of the local (non-DST) timezone in seconds west of UTC. +proc utc*(dt: DateTime): DateTime = + ## Shorthand for ``dt.inZone(utc())``. + dt.inZone(utc()) -proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} - ## get the milliseconds from the start of the program. **Deprecated since - ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. +proc local*(dt: DateTime): DateTime = + ## Shorthand for ``dt.inZone(local())``. + dt.inZone(local()) + +proc utc*(t: Time): DateTime = + ## Shorthand for ``t.inZone(utc())``. + t.inZone(utc()) + +proc local*(t: Time): DateTime = + ## Shorthand for ``t.inZone(local())``. + t.inZone(local()) + +proc getTime*(): Time {.tags: [TimeEffect], benign.} + ## Gets the current time as a ``Time`` with second resolution. Use epochTime for higher + ## resolution. + +proc now*(): DateTime {.tags: [TimeEffect], benign.} = + ## Get the current time as a ``DateTime`` in the local timezone. + ## + ## Shorthand for ``getTime().local``. + getTime().local proc initInterval*(milliseconds, seconds, minutes, hours, days, months, years: int = 0): TimeInterval = - ## creates a new ``TimeInterval``. + ## Creates a new ``TimeInterval``. ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. @@ -269,46 +522,33 @@ proc initInterval*(milliseconds, seconds, minutes, hours, days, months, ## .. code-block:: nim ## ## let day = initInterval(hours=24) - ## let tomorrow = getTime() + day - ## echo(tomorrow) - var carryO = 0 - result.milliseconds = `mod`(milliseconds, 1000) - carryO = `div`(milliseconds, 1000) - result.seconds = `mod`(carryO + seconds, 60) - carryO = `div`(carryO + seconds, 60) - result.minutes = `mod`(carryO + minutes, 60) - carryO = `div`(carryO + minutes, 60) - result.hours = `mod`(carryO + hours, 24) - carryO = `div`(carryO + hours, 24) - result.days = carryO + days - - result.months = `mod`(months, 12) - carryO = `div`(months, 12) - result.years = carryO + years + ## let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) + ## doAssert $(dt + day) == "2000-01-02T12-00-00+00:00" + result.milliseconds = milliseconds + result.seconds = seconds + result.minutes = minutes + result.hours = hours + result.days = days + result.months = months + result.years = years proc `+`*(ti1, ti2: TimeInterval): TimeInterval = ## Adds two ``TimeInterval`` objects together. - var carryO = 0 - result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000) - carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000) - result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60) - carryO = `div`(carryO + ti1.seconds + ti2.seconds, 60) - result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60) - carryO = `div`(carryO + ti1.minutes + ti2.minutes, 60) - result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24) - carryO = `div`(carryO + ti1.hours + ti2.hours, 24) - result.days = carryO + ti1.days + ti2.days - - result.months = `mod`(ti1.months + ti2.months, 12) - carryO = `div`(ti1.months + ti2.months, 12) - result.years = carryO + ti1.years + ti2.years + result.milliseconds = ti1.milliseconds + ti2.milliseconds + result.seconds = ti1.seconds + ti2.seconds + result.minutes = ti1.minutes + ti2.minutes + result.hours = ti1.hours + ti2.hours + result.days = ti1.days + ti2.days + result.months = ti1.months + ti2.months + result.years = ti1.years + ti2.years proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval + ## ## .. code-block:: nim ## ## let day = -initInterval(hours=24) - ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: 0, days: -1, months: 0, years: 0) + ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: -24, days: 0, months: 0, years: 0) result = TimeInterval( milliseconds: -ti.milliseconds, seconds: -ti.seconds, @@ -325,123 +565,100 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## Time components are compared one-by-one, see output: ## ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) + ## let a = fromUnix(1_000_000_000) + ## let b = fromUnix(1_500_000_000) ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: -2, months: -2, years: 16) + ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) result = ti1 + (-ti2) -proc isLeapYear*(year: int): bool = - ## returns true if ``year`` is a leap year - - if year mod 400 == 0: - return true - elif year mod 100 == 0: - return false - elif year mod 4 == 0: - return true - else: - return false - -proc getDaysInMonth*(month: Month, year: int): int = - ## Get the number of days in a ``month`` of a ``year`` +proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDiff, absDiff: int64] = + ## Evaluates how many seconds the interval is worth + ## in the context of ``dt``. + ## The result in split into an adjusted diff and an absolute diff. - # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month - case month - of mFeb: result = if isLeapYear(year): 29 else: 28 - of mApr, mJun, mSep, mNov: result = 30 - else: result = 31 - -proc getDaysInYear*(year: int): int = - ## Get the number of days in a ``year`` - result = 365 + (if isLeapYear(year): 1 else: 0) - -proc toSeconds(a: TimeInfo, interval: TimeInterval): float = - ## Calculates how many seconds the interval is worth by adding up - ## all the fields - - var anew = a + var anew = dt var newinterv = interval - result = 0 newinterv.months += interval.years * 12 var curMonth = anew.month - if newinterv.months < 0: # subtracting + # Subtracting + if newinterv.months < 0: for mth in countDown(-1 * newinterv.months, 1): - result -= float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) if curMonth == mJan: curMonth = mDec anew.year.dec() else: curMonth.dec() - else: # adding + result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay + # Adding + else: for mth in 1 .. newinterv.months: - result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) + result.adjDiff += getDaysInMonth(curMonth, anew.year) * secondsInDay if curMonth == mDec: curMonth = mJan anew.year.inc() else: curMonth.inc() - result += float(newinterv.days * 24 * 60 * 60) - result += float(newinterv.hours * 60 * 60) - result += float(newinterv.minutes * 60) - result += float(newinterv.seconds) - result += newinterv.milliseconds / 1000 - -proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## adds ``interval`` time from TimeInfo ``a``. + result.adjDiff += newinterv.days * secondsInDay + result.absDiff += newinterv.hours * secondsInHour + result.absDiff += newinterv.minutes * secondsInMin + result.absDiff += newinterv.seconds + result.absDiff += newinterv.milliseconds div 1000 + +proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Adds ``interval`` to ``dt``. Components from ``interval`` are added + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. ## - ## **Note:** This has been only briefly tested and it may not be - ## very accurate. - let t = toSeconds(toTime(a)) - let secs = toSeconds(a, interval) - if a.timezone == 0: - result = getGMTime(fromSeconds(t + secs)) - else: - result = getLocalTime(fromSeconds(t + secs)) - -proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## subtracts ``interval`` time from TimeInfo ``a``. + ## Note that when adding months, monthday overflow is allowed. This means that if the resulting + ## month doesn't have enough days it, the month will be incremented and the monthday will be + ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, + ## which will overflow and result in `1 December`. ## - ## **Note:** This has been only briefly tested, it is inaccurate especially - ## when you subtract so much that you reach the Julian calendar. - let - t = toSeconds(toTime(a)) - secs = toSeconds(a, -interval) - if a.timezone == 0: - result = getGMTime(fromSeconds(t + secs)) + ## .. code-block:: nim + ## let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + ## doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" + ## # This is correct and happens due to monthday overflow. + ## doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" + let (adjDiff, absDiff) = evaluateInterval(dt, interval) + + if adjDiff.int64 != 0: + let zInfo = dt.timezone.zoneInfoFromTz(Time(dt.toAdjTime.int64 + adjDiff)) + + if absDiff != 0: + let time = Time(zInfo.adjTime.int64 + zInfo.utcOffset + absDiff) + result = initDateTime(dt.timezone.zoneInfoFromUtc(time), dt.timezone) + else: + result = initDateTime(zInfo, dt.timezone) else: - result = getLocalTime(fromSeconds(t + secs)) + result = initDateTime(dt.timezone.zoneInfoFromUtc(Time(dt.toTime.int64 + absDiff)), dt.timezone) -proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds - -proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} = - ## An alias for a misspelled field in ``TimeInterval``. - ## - ## **Warning:** This should not be used! It will be removed in the next - ## version. - t.milliseconds = milliseconds +proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + dt + (-interval) proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## gets the current date as a string of the format ``YYYY-MM-DD``. - var ti = getLocalTime(getTime()) - result = $ti.year & '-' & intToStr(ord(ti.month)+1, 2) & + ## 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) 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 = getLocalTime(getTime()) + ## 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) proc `$`*(day: WeekDay): string = - ## stingify operator for ``WeekDay``. + ## Stringify operator for ``WeekDay``. const lookup: array[WeekDay, string] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] return lookup[day] proc `$`*(m: Month): string = - ## stingify operator for ``Month``. + ## Stringify operator for ``Month``. const lookup: array[Month, string] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] @@ -450,74 +667,68 @@ proc `$`*(m: Month): string = proc milliseconds*(ms: int): TimeInterval {.inline.} = ## TimeInterval of `ms` milliseconds ## - ## Note: not all time functions have millisecond resolution - initInterval(`mod`(ms,1000), `div`(ms,1000)) + ## Note: not all time procedures have millisecond resolution + initInterval(milliseconds = ms) proc seconds*(s: int): TimeInterval {.inline.} = ## TimeInterval of `s` seconds ## ## ``echo getTime() + 5.second`` - initInterval(0,`mod`(s,60), `div`(s,60)) + initInterval(seconds = s) proc minutes*(m: int): TimeInterval {.inline.} = ## TimeInterval of `m` minutes ## ## ``echo getTime() + 5.minutes`` - initInterval(0,0,`mod`(m,60), `div`(m,60)) + initInterval(minutes = m) proc hours*(h: int): TimeInterval {.inline.} = ## TimeInterval of `h` hours ## ## ``echo getTime() + 2.hours`` - initInterval(0,0,0,`mod`(h,24),`div`(h,24)) + initInterval(hours = h) proc days*(d: int): TimeInterval {.inline.} = ## TimeInterval of `d` days ## ## ``echo getTime() + 2.days`` - initInterval(0,0,0,0,d) + initInterval(days = d) proc months*(m: int): TimeInterval {.inline.} = ## TimeInterval of `m` months ## ## ``echo getTime() + 2.months`` - initInterval(0,0,0,0,0,`mod`(m,12),`div`(m,12)) + initInterval(months = m) proc years*(y: int): TimeInterval {.inline.} = ## TimeInterval of `y` years ## ## ``echo getTime() + 2.years`` - initInterval(0,0,0,0,0,0,y) + initInterval(years = y) -proc `+=`*(t: var Time, ti: TimeInterval) = - ## modifies `t` by adding the interval `ti` - t = toTime(getLocalTime(t) + ti) +proc `+=`*(time: var Time, interval: TimeInterval) = + ## Modifies `time` by adding `interval`. + time = toTime(time.local + interval) -proc `+`*(t: Time, ti: TimeInterval): Time = - ## adds the interval `ti` to Time `t` - ## by converting to localTime, adding the interval, and converting back +proc `+`*(time: Time, interval: TimeInterval): Time = + ## Adds `interval` to `time` + ## by converting to a ``DateTime`` in the local timezone, + ## adding the interval, and converting back to ``Time``. ## ## ``echo getTime() + 1.day`` - result = toTime(getLocalTime(t) + ti) + result = toTime(time.local + interval) -proc `-=`*(t: var Time, ti: TimeInterval) = - ## modifies `t` by subtracting the interval `ti` - t = toTime(getLocalTime(t) - ti) +proc `-=`*(time: var Time, interval: TimeInterval) = + ## Modifies `time` by subtracting `interval`. + time = toTime(time.local - interval) -proc `-`*(t: Time, ti: TimeInterval): Time = - ## subtracts the interval `ti` from Time `t` +proc `-`*(time: Time, interval: TimeInterval): Time = + ## Subtracts `interval` from Time `time`. ## ## ``echo getTime() - 1.day`` - result = toTime(getLocalTime(t) - ti) + result = toTime(time.local - interval) -const - secondsInMin = 60 - secondsInHour = 60*60 - secondsInDay = 60*60*24 - minutesInHour = 60 - epochStartYear = 1970 - -proc formatToken(info: TimeInfo, token: string, buf: var string) = +proc formatToken(dt: DateTime, token: string, buf: var string) = ## Helper of the format proc to parse individual tokens. ## ## Pass the found token in the user input string, and the buffer where the @@ -525,96 +736,96 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = ## formatting tokens require modifying the previous characters. case token of "d": - buf.add($info.monthday) + buf.add($dt.monthday) of "dd": - if info.monthday < 10: + if dt.monthday < 10: buf.add("0") - buf.add($info.monthday) + buf.add($dt.monthday) of "ddd": - buf.add(($info.weekday)[0 .. 2]) + buf.add(($dt.weekday)[0 .. 2]) of "dddd": - buf.add($info.weekday) + buf.add($dt.weekday) of "h": - buf.add($(if info.hour > 12: info.hour - 12 else: info.hour)) + buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour)) of "hh": - let amerHour = if info.hour > 12: info.hour - 12 else: info.hour + let amerHour = if dt.hour > 12: dt.hour - 12 else: dt.hour if amerHour < 10: buf.add('0') buf.add($amerHour) of "H": - buf.add($info.hour) + buf.add($dt.hour) of "HH": - if info.hour < 10: + if dt.hour < 10: buf.add('0') - buf.add($info.hour) + buf.add($dt.hour) of "m": - buf.add($info.minute) + buf.add($dt.minute) of "mm": - if info.minute < 10: + if dt.minute < 10: buf.add('0') - buf.add($info.minute) + buf.add($dt.minute) of "M": - buf.add($(int(info.month)+1)) + buf.add($ord(dt.month)) of "MM": - if info.month < mOct: + if dt.month < mOct: buf.add('0') - buf.add($(int(info.month)+1)) + buf.add($ord(dt.month)) of "MMM": - buf.add(($info.month)[0..2]) + buf.add(($dt.month)[0..2]) of "MMMM": - buf.add($info.month) + buf.add($dt.month) of "s": - buf.add($info.second) + buf.add($dt.second) of "ss": - if info.second < 10: + if dt.second < 10: buf.add('0') - buf.add($info.second) + buf.add($dt.second) of "t": - if info.hour >= 12: + if dt.hour >= 12: buf.add('P') else: buf.add('A') of "tt": - if info.hour >= 12: + if dt.hour >= 12: buf.add("PM") else: buf.add("AM") of "y": - var fr = ($info.year).len()-1 + var fr = ($dt.year).len()-1 if fr < 0: fr = 0 - buf.add(($info.year)[fr .. ($info.year).len()-1]) + buf.add(($dt.year)[fr .. ($dt.year).len()-1]) of "yy": - var fr = ($info.year).len()-2 + var fr = ($dt.year).len()-2 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + var fyear = ($dt.year)[fr .. ($dt.year).len()-1] if fyear.len != 2: fyear = repeat('0', 2-fyear.len()) & fyear buf.add(fyear) of "yyy": - var fr = ($info.year).len()-3 + var fr = ($dt.year).len()-3 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + var fyear = ($dt.year)[fr .. ($dt.year).len()-1] if fyear.len != 3: fyear = repeat('0', 3-fyear.len()) & fyear buf.add(fyear) of "yyyy": - var fr = ($info.year).len()-4 + var fr = ($dt.year).len()-4 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + var fyear = ($dt.year)[fr .. ($dt.year).len()-1] if fyear.len != 4: fyear = repeat('0', 4-fyear.len()) & fyear buf.add(fyear) of "yyyyy": - var fr = ($info.year).len()-5 + var fr = ($dt.year).len()-5 if fr < 0: fr = 0 - var fyear = ($info.year)[fr .. ($info.year).len()-1] + var fyear = ($dt.year)[fr .. ($dt.year).len()-1] if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear buf.add(fyear) of "z": let - nonDstTz = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour if nonDstTz <= 0: buf.add('+') else: buf.add('-') buf.add($hours) of "zz": let - nonDstTz = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour if nonDstTz <= 0: buf.add('+') else: buf.add('-') @@ -622,7 +833,7 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = buf.add($hours) of "zzz": let - nonDstTz = info.timezone - int(info.isDst) * secondsInHour + nonDstTz = dt.utcOffset hours = abs(nonDstTz) div secondsInHour minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour if nonDstTz <= 0: buf.add('+') @@ -638,8 +849,8 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) = raise newException(ValueError, "Invalid format string: " & token) -proc format*(info: TimeInfo, f: string): string = - ## This function formats `info` as specified by `f`. The following format +proc format*(dt: DateTime, f: string): string {.tags: [].}= + ## This procedure formats `dt` as specified by `f`. The following format ## specifiers are available: ## ## ========== ================================================================================= ================================================ @@ -683,7 +894,7 @@ proc format*(info: TimeInfo, f: string): string = while true: case f[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - formatToken(info, currentF, result) + formatToken(dt, currentF, result) currentF = "" if f[i] == '\0': break @@ -700,187 +911,187 @@ proc format*(info: TimeInfo, f: string): string = if currentF.len < 1 or currentF[high(currentF)] == f[i]: currentF.add(f[i]) else: - formatToken(info, currentF, result) + formatToken(dt, currentF, result) dec(i) # Move position back to re-process the character separately. currentF = "" inc(i) -proc `$`*(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} = - ## converts a `TimeInfo` object to a string representation. +proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = + ## Converts a `DateTime` object to a string representation. ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. try: - result = format(timeInfo, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this + result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this except ValueError: assert false # cannot happen because format string is valid -proc `$`*(time: Time): string {.tags: [TimeEffect], raises: [], benign.} = +proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = ## converts a `Time` value to a string representation. It will use the local ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. - $getLocalTime(time) + $time.local {.pop.} -proc parseToken(info: var TimeInfo; token, value: string; j: var int) = +proc parseToken(dt: var DateTime; token, value: string; j: var int) = ## Helper of the parse proc to parse individual tokens. var sv: int case token of "d": var pd = parseInt(value[j..j+1], sv) - info.monthday = sv + dt.monthday = sv j += pd of "dd": - info.monthday = value[j..j+1].parseInt() + dt.monthday = value[j..j+1].parseInt() j += 2 of "ddd": case value[j..j+2].toLowerAscii() - of "sun": info.weekday = dSun - of "mon": info.weekday = dMon - of "tue": info.weekday = dTue - of "wed": info.weekday = dWed - of "thu": info.weekday = dThu - of "fri": info.weekday = dFri - of "sat": info.weekday = dSat + of "sun": dt.weekday = dSun + of "mon": dt.weekday = dMon + of "tue": dt.weekday = dTue + of "wed": dt.weekday = dWed + of "thu": dt.weekday = dThu + of "fri": dt.weekday = dFri + of "sat": dt.weekday = dSat else: raise newException(ValueError, "Couldn't parse day of week (ddd), got: " & value[j..j+2]) j += 3 of "dddd": if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: - info.weekday = dSun + dt.weekday = dSun j += 6 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: - info.weekday = dMon + dt.weekday = dMon j += 6 elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: - info.weekday = dTue + dt.weekday = dTue j += 7 elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: - info.weekday = dWed + dt.weekday = dWed j += 9 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: - info.weekday = dThu + dt.weekday = dThu j += 8 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: - info.weekday = dFri + dt.weekday = dFri j += 6 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: - info.weekday = dSat + dt.weekday = dSat j += 8 else: raise newException(ValueError, "Couldn't parse day of week (dddd), got: " & value) of "h", "H": var pd = parseInt(value[j..j+1], sv) - info.hour = sv + dt.hour = sv j += pd of "hh", "HH": - info.hour = value[j..j+1].parseInt() + dt.hour = value[j..j+1].parseInt() j += 2 of "m": var pd = parseInt(value[j..j+1], sv) - info.minute = sv + dt.minute = sv j += pd of "mm": - info.minute = value[j..j+1].parseInt() + dt.minute = value[j..j+1].parseInt() j += 2 of "M": var pd = parseInt(value[j..j+1], sv) - info.month = Month(sv-1) + dt.month = sv.Month j += pd of "MM": var month = value[j..j+1].parseInt() j += 2 - info.month = Month(month-1) + dt.month = month.Month of "MMM": case value[j..j+2].toLowerAscii(): - of "jan": info.month = mJan - of "feb": info.month = mFeb - of "mar": info.month = mMar - of "apr": info.month = mApr - of "may": info.month = mMay - of "jun": info.month = mJun - of "jul": info.month = mJul - of "aug": info.month = mAug - of "sep": info.month = mSep - of "oct": info.month = mOct - of "nov": info.month = mNov - of "dec": info.month = mDec + of "jan": dt.month = mJan + of "feb": dt.month = mFeb + of "mar": dt.month = mMar + of "apr": dt.month = mApr + of "may": dt.month = mMay + of "jun": dt.month = mJun + of "jul": dt.month = mJul + of "aug": dt.month = mAug + of "sep": dt.month = mSep + of "oct": dt.month = mOct + of "nov": dt.month = mNov + of "dec": dt.month = mDec else: raise newException(ValueError, "Couldn't parse month (MMM), got: " & value) j += 3 of "MMMM": if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: - info.month = mJan + dt.month = mJan j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - info.month = mFeb + dt.month = mFeb j += 8 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - info.month = mMar + dt.month = mMar j += 5 elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - info.month = mApr + dt.month = mApr j += 5 elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - info.month = mMay + dt.month = mMay j += 3 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - info.month = mJun + dt.month = mJun j += 4 elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - info.month = mJul + dt.month = mJul j += 4 elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - info.month = mAug + dt.month = mAug j += 6 elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - info.month = mSep + dt.month = mSep j += 9 elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - info.month = mOct + dt.month = mOct j += 7 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - info.month = mNov + dt.month = mNov j += 8 elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - info.month = mDec + dt.month = mDec j += 8 else: raise newException(ValueError, "Couldn't parse month (MMMM), got: " & value) of "s": var pd = parseInt(value[j..j+1], sv) - info.second = sv + dt.second = sv j += pd of "ss": - info.second = value[j..j+1].parseInt() + dt.second = value[j..j+1].parseInt() j += 2 of "t": - if value[j] == 'P' and info.hour > 0 and info.hour < 12: - info.hour += 12 + if value[j] == 'P' and dt.hour > 0 and dt.hour < 12: + dt.hour += 12 j += 1 of "tt": - if value[j..j+1] == "PM" and info.hour > 0 and info.hour < 12: - info.hour += 12 + if value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12: + dt.hour += 12 j += 2 of "yy": # Assumes current century var year = value[j..j+1].parseInt() - var thisCen = getLocalTime(getTime()).year div 100 - info.year = thisCen*100 + year + var thisCen = now().year div 100 + dt.year = thisCen*100 + year j += 2 of "yyyy": - info.year = value[j..j+3].parseInt() + dt.year = value[j..j+3].parseInt() j += 4 of "z": - info.isDST = false + dt.isDst = false if value[j] == '+': - info.timezone = 0 - parseInt($value[j+1]) * secondsInHour + dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour elif value[j] == '-': - info.timezone = parseInt($value[j+1]) * secondsInHour + dt.utcOffset = parseInt($value[j+1]) * secondsInHour elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: @@ -888,13 +1099,13 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = "Couldn't parse timezone offset (z), got: " & value[j]) j += 2 of "zz": - info.isDST = false + dt.isDst = false if value[j] == '+': - info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour elif value[j] == '-': - info.timezone = value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: @@ -902,31 +1113,33 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = "Couldn't parse timezone offset (zz), got: " & value[j]) j += 3 of "zzz": - info.isDST = false + dt.isDst = false var factor = 0 if value[j] == '+': factor = -1 elif value[j] == '-': factor = 1 elif value[j] == 'Z': - info.timezone = 0 + dt.utcOffset = 0 j += 1 return else: raise newException(ValueError, "Couldn't parse timezone offset (zzz), got: " & value[j]) - info.timezone = factor * value[j+1..j+2].parseInt() * secondsInHour + dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour j += 4 - info.timezone += factor * value[j..j+1].parseInt() * 60 + dt.utcOffset += factor * value[j..j+1].parseInt() * 60 j += 2 else: # Ignore the token and move forward in the value string by the same length j += token.len -proc parse*(value, layout: string): TimeInfo = - ## This function parses a date/time string using the standard format - ## identifiers as listed below. The function defaults information not provided - ## in the format string from the running program (timezone, month, year, etc). - ## Daylight saving time is only set if no timezone is given and the given date - ## lies within the DST period of the current locale. +proc parse*(value, layout: string, zone: Timezone = local()): DateTime = + ## This procedure parses a date/time string using the standard format + ## identifiers as listed below. The procedure defaults information not provided + ## in the format string from the running program (month, year, etc). + ## + ## The return value will always be in the `zone` timezone. If no UTC offset was + ## parsed, then the input will be assumed to be specified in the `zone` timezone + ## already, so no timezone conversion will be done in that case. ## ## ========== ================================================================================= ================================================ ## Specifier Description Example @@ -965,17 +1178,17 @@ proc parse*(value, layout: string): TimeInfo = var j = 0 # pointer for value string var token = "" # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing. - var info = getLocalTime(getTime()) - info.hour = 0 - info.minute = 0 - info.second = 0 - info.isDST = true # using this is flag for checking whether a timezone has \ + var dt = now() + dt.hour = 0 + dt.minute = 0 + dt.second = 0 + dt.isDst = true # using this is flag for checking whether a timezone has \ # been read (because DST is always false when a tz is parsed) while true: case layout[i] of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': if token.len > 0: - parseToken(info, token, value, j) + parseToken(dt, token, value, j) # Reset token token = "" # Break if at end of line @@ -997,26 +1210,15 @@ proc parse*(value, layout: string): TimeInfo = token.add(layout[i]) inc(i) else: - parseToken(info, token, value, j) + parseToken(dt, token, value, j) token = "" - if info.isDST: - # means that no timezone has been parsed. In this case, we need to check - # whether the date is within DST of the local time. - let tmp = getLocalTime(toTime(info)) - # correctly set isDST so that the following step works on the correct time - info.isDST = tmp.isDST - - # Correct weekday and yearday; transform timestamp to local time. - # There currently is no way of returning this with the original (parsed) - # timezone while also setting weekday and yearday (we are depending on stdlib - # to provide this calculation). - return getLocalTime(toTime(info)) - -# Leap year calculations are adapted from: -# http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years -# The dayOfTheWeek procs are adapated from: -# http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html + if dt.isDst: + # No timezone parsed - assume timezone is `zone` + result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) + else: + # Otherwise convert to `zone` + result = dt.toTime.inZone(zone) proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. @@ -1025,7 +1227,7 @@ proc countLeapYears*(yearSpan: int): int = ## counts the number of leap years up to January 1st of a given year. ## Keep in mind that if specified year is a leap year, the leap day ## has not happened before January 1st of that year. - (((yearSpan - 1) / 4) - ((yearSpan - 1) / 100) + ((yearSpan - 1) / 400)).int + (yearSpan - 1) div 4 - (yearSpan - 1) div 100 + (yearSpan - 1) div 400 proc countDays*(yearSpan: int): int = ## Returns the number of days spanned by a given number of years. @@ -1042,75 +1244,7 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = result.years = days div 365 result.days = days mod 365 -proc getDayOfWeek*(day, month, year: int): WeekDay = - ## Returns the day of the week enum from day, month and year. - # Day & month start from one. - let - a = (14 - month) div 12 - y = year - a - m = month + (12*a) - 2 - d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7 - # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. - # so we must correct for the WeekDay type. - if d == 0: return dSun - result = (d-1).WeekDay - -proc getDayOfWeekJulian*(day, month, year: int): WeekDay = - ## Returns the day of the week enum from day, month and year, - ## according to the Julian calendar. - # Day & month start from one. - let - a = (14 - month) div 12 - y = year - a - m = month + (12*a) - 2 - d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 - result = d.WeekDay - -proc timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} = - ## Converts a Time to TimeInfo. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``getLocalTime`` or ``getGMTime`` instead. - let - secs = t.toSeconds().int - daysSinceEpoch = secs div secondsInDay - (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) - daySeconds = secs mod secondsInDay - - y = yearsSinceEpoch + epochStartYear - - var - mon = mJan - days = daysRemaining - daysInMonth = getDaysInMonth(mon, y) - - # calculate month and day remainder - while days > daysInMonth and mon <= mDec: - days -= daysInMonth - mon.inc - daysInMonth = getDaysInMonth(mon, y) - - let - yd = daysRemaining - m = mon # month is zero indexed enum - md = days - # NB: month is zero indexed but dayOfWeek expects 1 indexed. - wd = getDayOfWeek(days, mon.int + 1, y).Weekday - h = daySeconds div secondsInHour + 1 - mi = (daySeconds mod secondsInHour) div secondsInMin - s = daySeconds mod secondsInMin - result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) - -proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = - ## Converts a Time to a TimeInterval. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTimeInterval`` instead. - # Milliseconds not available from Time - var tInfo = t.getLocalTime() - initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year) - -proc toTimeInterval*(t: Time): TimeInterval = +proc toTimeInterval*(time: Time): TimeInterval = ## Converts a Time to a TimeInterval. ## ## To be used when diffing times. @@ -1121,10 +1255,25 @@ proc toTimeInterval*(t: Time): TimeInterval = ## echo a, " ", b # real dates ## echo a.toTimeInterval # meaningless value, don't use it by itself ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: -2, months: -2, years: 16) + ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) # Milliseconds not available from Time - var tInfo = t.getLocalTime() - initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year) + var dt = time.local + initInterval(0, dt.second, dt.minute, dt.hour, dt.monthday, dt.month.ord - 1, dt.year) + +proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, + hour: HourRange, minute: MinuteRange, second: SecondRange, zone: Timezone = local()): DateTime = + ## Create a new ``DateTime`` in the specified timezone. + assertValidDate monthday, month, year + doAssert monthday <= getDaysInMonth(month, year), "Invalid date: " & $month & " " & $monthday & ", " & $year + let dt = DateTime( + monthday: monthday, + year: year, + month: month, + hour: hour, + minute: minute, + second: second + ) + result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) when not defined(JS): proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} @@ -1145,160 +1294,39 @@ when not defined(JS): ## doWork() ## echo "CPU time [s] ", cpuTime() - t0 -when not defined(JS): - # C wrapper: - when defined(freebsd) or defined(netbsd) or defined(openbsd) or - defined(macosx): - type - StructTM {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - gmtoff {.importc: "tm_gmtoff".}: clong - else: - type - StructTM {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - when defined(linux) and defined(amd64): - gmtoff {.importc: "tm_gmtoff".}: clong - zone {.importc: "tm_zone".}: cstring +when defined(JS): + proc getTime(): Time = + (newDate().getTime() div 1000).Time + + proc epochTime*(): float {.tags: [TimeEffect].} = + newDate().getTime() / 1000 + +else: type - TimeInfoPtr = ptr StructTM Clock {.importc: "clock_t".} = distinct int - when not defined(windows): - # This is not ANSI C, but common enough - proc timegm(t: StructTM): Time {. - importc: "timegm", header: "<time.h>", tags: [].} - - proc localtime(timer: ptr Time): TimeInfoPtr {. - importc: "localtime", header: "<time.h>", tags: [].} - proc gmtime(timer: ptr Time): TimeInfoPtr {. - importc: "gmtime", header: "<time.h>", tags: [].} - proc timec(timer: ptr Time): Time {. + proc timec(timer: ptr CTime): CTime {. importc: "time", header: "<time.h>", tags: [].} - proc mktime(t: StructTM): Time {. - importc: "mktime", header: "<time.h>", tags: [].} + proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} - proc difftime(a, b: Time): float {.importc: "difftime", header: "<time.h>", - tags: [].} var clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int - # our own procs on top of that: - proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo = - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - timezone: if local: getTimezone() else: 0 - ) - - - proc timeInfoToTM(t: TimeInfo): StructTM = - const - weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8] - result.second = t.second - result.minute = t.minute - result.hour = t.hour - result.monthday = t.monthday - result.month = ord(t.month) - result.year = cint(t.year - 1900) - result.weekday = weekDays[t.weekday] - result.yearday = t.yearday - result.isdst = if t.isDST: 1 else: 0 - - when not defined(useNimRtl): - proc `-` (a, b: Time): int64 = - return toBiggestInt(difftime(a, b)) - - proc getStartMilsecs(): int = - #echo "clocks per sec: ", clocksPerSec, "clock: ", int(getClock()) - #return getClock() div (clocksPerSec div 1000) - when defined(macosx): - result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) - else: - result = int(getClock()) div (clocksPerSec div 1000) - when false: - var a: Timeval - posix_gettimeofday(a) - result = a.tv_sec * 1000'i64 + a.tv_usec div 1000'i64 - #echo "result: ", result - - proc getTime(): Time = return timec(nil) - proc getLocalTime(t: Time): TimeInfo = - var a = t - let lt = localtime(addr(a)) - assert(not lt.isNil) - result = tmToTimeInfo(lt[], true) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc getGMTime(t: Time): TimeInfo = - var a = t - result = tmToTimeInfo(gmtime(addr(a))[], false) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc toTime(timeInfo: TimeInfo): Time = - var cTimeInfo = timeInfo # for C++ we have to make a copy - # because the header of mktime is broken in my version of libc - - result = mktime(timeInfoToTM(cTimeInfo)) - # mktime is defined to interpret the input as local time. As timeInfoToTM - # does ignore the timezone, we need to adjust this here. - result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) - - proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo) + proc getTime(): Time = + timec(nil).Time const epochDiff = 116444736000000000'i64 rateDiff = 10000000'i64 # 100 nsecs - proc unixTimeToWinTime*(t: Time): int64 = + proc unixTimeToWinTime*(time: CTime): int64 = ## converts a UNIX `Time` (``time_t``) to a Windows file time - result = int64(t) * rateDiff + epochDiff + result = int64(time) * rateDiff + epochDiff - proc winTimeToUnixTime*(t: int64): Time = + proc winTimeToUnixTime*(time: int64): CTime = ## converts a Windows time to a UNIX `Time` (``time_t``) - result = Time((t - epochDiff) div rateDiff) - - proc getTimezone(): int = - when defined(freebsd) or defined(netbsd) or defined(openbsd): - var a = timec(nil) - let lt = localtime(addr(a)) - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - return -(lt.gmtoff) - else: - return timezone - - proc fromSeconds(since1970: float): Time = Time(since1970) - - proc toSeconds(time: Time): float = float(time) + result = CTime((time - epochDiff) div rateDiff) when not defined(useNimRtl): proc epochTime(): float = @@ -1319,83 +1347,138 @@ when not defined(JS): proc cpuTime(): float = result = toFloat(int(getClock())) / toFloat(clocksPerSec) -elif defined(JS): - proc newDate(): Time {.importc: "new Date".} - proc internGetTime(): Time {.importc: "new Date", tags: [].} +# Deprecated procs - proc newDate(value: float): Time {.importc: "new Date".} - proc newDate(value: cstring): Time {.importc: "new Date".} - proc getTime(): Time = - # Warning: This is something different in JS. - return newDate() +proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = + ## Takes a float which contains the number of seconds since the unix epoch and + ## returns a time object. + ## + ## **Deprecated since v0.18.0:** use ``fromUnix`` instead + Time(since1970) - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - - proc getLocalTime(t: Time): TimeInfo = - result.second = t.getSeconds() - result.minute = t.getMinutes() - result.hour = t.getHours() - result.monthday = t.getDate() - result.month = Month(t.getMonth()) - result.year = t.getFullYear() - result.weekday = weekDays[t.getDay()] - result.timezone = getTimezone() - - result.yearday = result.monthday - 1 - for month in mJan..<result.month: - result.yearday += getDaysInMonth(month, result.year) - - proc getGMTime(t: Time): TimeInfo = - result.second = t.getUTCSeconds() - result.minute = t.getUTCMinutes() - result.hour = t.getUTCHours() - result.monthday = t.getUTCDate() - result.month = Month(t.getUTCMonth()) - result.year = t.getUTCFullYear() - result.weekday = weekDays[t.getUTCDay()] - - result.yearday = result.monthday - 1 - for month in mJan..<result.month: - result.yearday += getDaysInMonth(month, result.year) - - proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo) - - proc toTime*(timeInfo: TimeInfo): Time = newDate($timeInfo) - - proc `-` (a, b: Time): int64 = - return a.getTime() - b.getTime() +proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = + ## Takes an int which contains the number of seconds since the unix epoch and + ## returns a time object. + ## + ## **Deprecated since v0.18.0:** use ``fromUnix`` instead + Time(since1970) - var - startMilsecs = getTime() +proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = + ## Returns the time in seconds since the unix epoch. + ## + ## **Deprecated since v0.18.0:** use ``toUnix`` instead + float(time) + +proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = + ## Converts the calendar time `time` to broken-time representation, + ## expressed relative to the user's specified time zone. + ## + ## **Deprecated since v0.18.0:** use ``local`` instead + time.local - proc getStartMilsecs(): int = - ## get the milliseconds from the start of the program - return int(getTime() - startMilsecs) +proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = + ## Converts the calendar time `time` to broken-down time representation, + ## expressed in Coordinated Universal Time (UTC). + ## + ## **Deprecated since v0.18.0:** use ``utc`` instead + time.utc - proc fromSeconds(since1970: float): Time = result = newDate(since1970 * 1000) +proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} = + ## Returns the offset of the local (non-DST) timezone in seconds west of UTC. + ## + ## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current + ## utc offset (including DST). + when defined(JS): + return newDate().getTimezoneOffset() * 60 + elif defined(freebsd) or defined(netbsd) or defined(openbsd): + var a = timec(nil) + let lt = localtime(addr(a)) + # BSD stores in `gmtoff` offset east of UTC in seconds, + # but posix systems using west of UTC in seconds + return -(lt.gmtoff) + else: + return timezone - proc toSeconds(time: Time): float = result = time.getTime() / 1000 +proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = + ## Converts a broken-down time structure to calendar time representation. + ## + ## **Warning:** This procedure is deprecated since version 0.14.0. + ## Use ``toTime`` instead. + dt.toTime + +when defined(JS): + var startMilsecs = getTime() + proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = + ## get the milliseconds from the start of the program. **Deprecated since + ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. + when defined(JS): + ## get the milliseconds from the start of the program + return int(getTime() - startMilsecs) +else: + proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = + when defined(macosx): + result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) + else: + result = int(getClock()) div (clocksPerSec div 1000) + +proc miliseconds*(t: TimeInterval): int {.deprecated.} = + t.milliseconds + +proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = + ## Converts a Time to a TimeInterval. + ## + ## **Warning:** This procedure is deprecated since version 0.14.0. + ## Use ``toTimeInterval`` instead. + # Milliseconds not available from Time + t.toTimeInterval() - proc getTimezone(): int = result = newDate().getTimezoneOffset() * 60 +proc timeToTimeInfo*(t: Time): DateTime {.deprecated.} = + ## Converts a Time to DateTime. + ## + ## **Warning:** This procedure is deprecated since version 0.14.0. + ## Use ``inZone`` instead. + const epochStartYear = 1970 - proc epochTime*(): float {.tags: [TimeEffect].} = newDate().toSeconds() + let + secs = t.toSeconds().int + daysSinceEpoch = secs div secondsInDay + (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) + daySeconds = secs mod secondsInDay + y = yearsSinceEpoch + epochStartYear -when isMainModule: - # this is testing non-exported function var - t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 - t4L = getLocalTime(fromSeconds(876124714)) - assert toSeconds(t4, initInterval(seconds=0)) == 0.0 - assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1)) - assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1)) - assert toSeconds(t4L, initInterval(minutes=1)) == toSeconds(t4, initInterval(minutes=1)) - assert toSeconds(t4L, initInterval(hours=1)) == toSeconds(t4, initInterval(hours=1)) - assert toSeconds(t4L, initInterval(days=1)) == toSeconds(t4, initInterval(days=1)) - assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1)) - assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1)) - - # Further tests are in tests/stdlib/ttime.nim - # koch test c stdlib + mon = mJan + days = daysRemaining + daysInMonth = getDaysInMonth(mon, y) + + # calculate month and day remainder + while days > daysInMonth and mon <= mDec: + days -= daysInMonth + mon.inc + daysInMonth = getDaysInMonth(mon, y) + + let + yd = daysRemaining + m = mon # month is zero indexed enum + md = days + # NB: month is zero indexed but dayOfWeek expects 1 indexed. + wd = getDayOfWeek(days, mon, y).Weekday + h = daySeconds div secondsInHour + 1 + mi = (daySeconds mod secondsInHour) div secondsInMin + s = daySeconds mod secondsInMin + result = DateTime(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) + +proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = + getDayOfWeek(day, month.Month, year) + +proc getDayOfWeekJulian*(day, month, year: int): WeekDay {.deprecated.} = + ## Returns the day of the week enum from day, month and year, + ## according to the Julian calendar. + # Day & month start from one. + let + a = (14 - month) div 12 + y = year - a + m = month + (12*a) - 2 + d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 + result = d.WeekDay diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 55c4bf038..2047abda4 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -21,7 +21,7 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} ## ## proc `$`*(T: typedesc): string = name(T) ## - ## template test(x): stmt = + ## template test(x): typed = ## echo "type: ", type(x), ", value: ", x ## ## test 42 @@ -31,6 +31,10 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} ## test(@['A','B']) ## # --> type: seq[char], value: @[A, B] +proc `$`*(t: typedesc): string = + ## An alias for `name`. + name(t) + proc arity*(t: typedesc): int {.magic: "TypeTrait".} ## Returns the arity of the given type @@ -49,3 +53,15 @@ proc stripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} ## This trait is similar to `genericHead`, but instead of producing ## error for non-generic types, it will just return them unmodified +proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".} + ## This trait returns true iff the type ``t`` is safe to use for + ## `copyMem`:idx:. Other languages name a type like these `blob`:idx:. + + +when isMainModule: + # echo type(42) + import streams + var ss = newStringStream() + ss.write($type(42)) # needs `$` + ss.setPosition(0) + doAssert ss.readAll() == "int" diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 7d9c3108b..257c620f7 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -293,33 +293,33 @@ proc runeSubStr*(s: string, pos:int, len:int = int.high): string = if pos < 0: let (o, rl) = runeReverseOffset(s, -pos) if len >= rl: - result = s[o.. s.len-1] + result = s.substr(o, s.len-1) elif len < 0: let e = rl + len if e < 0: result = "" else: - result = s[o.. runeOffset(s, e-(rl+pos) , o)-1] + result = s.substr(o, runeOffset(s, e-(rl+pos) , o)-1) else: - result = s[o.. runeOffset(s, len, o)-1] + result = s.substr(o, runeOffset(s, len, o)-1) else: let o = runeOffset(s, pos) if o < 0: result = "" elif len == int.high: - result = s[o.. s.len-1] + result = s.substr(o, s.len-1) elif len < 0: let (e, rl) = runeReverseOffset(s, -len) discard rl if e <= 0: result = "" else: - result = s[o.. e-1] + result = s.substr(o, e-1) else: var e = runeOffset(s, len, o) if e < 0: e = s.len - result = s[o.. e-1] + result = s.substr(o, e-1) const alphaRanges = [ diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 3772a213a..fbce087ff 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -21,13 +21,41 @@ ## ``nim c -r <testfile.nim>`` exits with 0 or 1 ## ## Running a single test -## --------------------- +## ===================== ## -## Simply specify the test name as a command line argument. +## Specify the test name as a command line argument. ## ## .. code:: ## -## nim c -r test "my super awesome test name" +## nim c -r test "my test name" "another test" +## +## Multiple arguments can be used. +## +## Running a single test suite +## =========================== +## +## Specify the suite name delimited by ``"::"``. +## +## .. code:: +## +## nim c -r test "my test name::" +## +## Selecting tests by pattern +## ========================== +## +## A single ``"*"`` can be used for globbing. +## +## Delimit the end of a suite name with ``"::"``. +## +## Tests matching **any** of the arguments are executed. +## +## .. code:: +## +## nim c -r test fast_suite::mytest1 fast_suite::mytest2 +## nim c -r test "fast_suite::mytest*" +## nim c -r test "auth*::" "crypto::hashing*" +## # Run suites starting with 'bug #' and standalone tests starting with '#' +## nim c -r test 'bug #*::' '::#*' ## ## Example ## ------- @@ -121,7 +149,7 @@ var checkpoints {.threadvar.}: seq[string] formatters {.threadvar.}: seq[OutputFormatter] - testsToRun {.threadvar.}: HashSet[string] + testsFilters {.threadvar.}: HashSet[string] when declared(stdout): abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") @@ -300,22 +328,63 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) = method suiteEnded*(formatter: JUnitOutputFormatter) = formatter.stream.writeLine("\t</testsuite>") -proc shouldRun(testName: string): bool = - if testsToRun.len == 0: +proc glob(matcher, filter: string): bool = + ## Globbing using a single `*`. Empty `filter` matches everything. + if filter.len == 0: return true - result = testName in testsToRun + if not filter.contains('*'): + return matcher == filter + + let beforeAndAfter = filter.split('*', maxsplit=1) + if beforeAndAfter.len == 1: + # "foo*" + return matcher.startswith(beforeAndAfter[0]) + + if matcher.len < filter.len - 1: + return false # "12345" should not match "123*345" + + return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1]) + +proc matchFilter(suiteName, testName, filter: string): bool = + if filter == "": + return true + if testName == filter: + # corner case for tests containing "::" in their name + return true + let suiteAndTestFilters = filter.split("::", maxsplit=1) + + if suiteAndTestFilters.len == 1: + # no suite specified + let test_f = suiteAndTestFilters[0] + return glob(testName, test_f) + + return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1]) + +when defined(testing): export matchFilter + +proc shouldRun(currentSuiteName, testName: string): bool = + ## Check if a test should be run by matching suiteName and testName against + ## test filters. + if testsFilters.len == 0: + return true + + for f in testsFilters: + if matchFilter(currentSuiteName, testName, f): + return true + + return false proc ensureInitialized() = if formatters == nil: formatters = @[OutputFormatter(defaultConsoleFormatter())] - if not testsToRun.isValid: - testsToRun.init() - when declared(os): + if not testsFilters.isValid: + testsFilters.init() + when declared(paramCount): # Read tests to run from the command line. for i in 1 .. paramCount(): - testsToRun.incl(paramStr(i)) + testsFilters.incl(paramStr(i)) # These two procs are added as workarounds for # https://github.com/nim-lang/Nim/issues/5549 @@ -395,7 +464,7 @@ template test*(name, body) {.dirty.} = ensureInitialized() - if shouldRun(name): + if shouldRun(when declared(testSuiteName): testSuiteName else: "", name): checkpoints = @[] var testStatusIMPL {.inject.} = OK diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 4b2e4e052..a651530c3 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -47,6 +47,49 @@ proc add*(url: var Url, a: Url) {.deprecated.} = url = url / a {.pop.} +proc encodeUrl*(s: string): string = + ## Encodes a value to be HTTP safe: This means that characters in the set + ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result, + ## a space is converted to ``'+'`` and every other character is encoded as + ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. + result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars + for i in 0..s.len-1: + case s[i] + of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i]) + of ' ': add(result, '+') + else: + add(result, '%') + add(result, toHex(ord(s[i]), 2)) + +proc decodeUrl*(s: string): string = + ## Decodes a value from its HTTP representation: This means that a ``'+'`` + ## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal + ## value) is converted to the character with ordinal number ``xx``, and + ## and every other character is carried over. + proc handleHexChar(c: char, x: var int) {.inline.} = + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) + of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) + else: assert(false) + + result = newString(s.len) + var i = 0 + var j = 0 + while i < s.len: + case s[i] + of '%': + var x = 0 + handleHexChar(s[i+1], x) + handleHexChar(s[i+2], x) + inc(i, 2) + result[j] = chr(x) + of '+': result[j] = ' ' + else: result[j] = s[i] + inc(i) + inc(j) + setLen(result, j) + proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false @@ -250,7 +293,7 @@ proc combine*(base: Uri, reference: Uri): Uri = proc combine*(uris: varargs[Uri]): Uri = ## Combines multiple URIs together. result = uris[0] - for i in 1 .. <uris.len: + for i in 1 ..< uris.len: result = combine(result, uris[i]) proc isAbsolute*(uri: Uri): bool = @@ -308,11 +351,16 @@ proc `$`*(u: Uri): string = result.add(":") result.add(u.password) result.add("@") - result.add(u.hostname) + if u.hostname.endswith('/'): + result.add(u.hostname[0..^2]) + else: + result.add(u.hostname) if u.port.len > 0: result.add(":") result.add(u.port) if u.path.len > 0: + if u.hostname.len > 0 and u.path[0] != '/': + result.add('/') result.add(u.path) if u.query.len > 0: result.add("?") @@ -323,6 +371,11 @@ proc `$`*(u: Uri): string = when isMainModule: block: + const test1 = "abc\L+def xyz" + doAssert encodeUrl(test1) == "abc%0A%2Bdef+xyz" + doAssert decodeUrl(encodeUrl(test1)) == test1 + + block: let str = "http://localhost" let test = parseUri(str) doAssert test.path == "" @@ -483,6 +536,34 @@ when isMainModule: let foo = parseUri("http://localhost:9515") / "status" doAssert $foo == "http://localhost:9515/status" + # bug #6649 #6652 + block: + var foo = parseUri("http://example.com") + foo.hostname = "example.com" + foo.path = "baz" + doAssert $foo == "http://example.com/baz" + + foo.hostname = "example.com/" + foo.path = "baz" + doAssert $foo == "http://example.com/baz" + + foo.hostname = "example.com" + foo.path = "/baz" + doAssert $foo == "http://example.com/baz" + + foo.hostname = "example.com/" + foo.path = "/baz" + doAssert $foo == "http://example.com/baz" + + foo.hostname = "example.com/" + foo.port = "8000" + foo.path = "baz" + doAssert $foo == "http://example.com:8000/baz" + + foo = parseUri("file:/dir/file") + foo.path = "relative" + doAssert $foo == "file:relative" + # isAbsolute tests block: doAssert "www.google.com".parseUri().isAbsolute() == false @@ -524,4 +605,4 @@ when isMainModule: doAssert "https://example.com/about/staff.html?".parseUri().isAbsolute == true doAssert "https://example.com/about/staff.html?parameters".parseUri().isAbsolute == true - echo("All good!") \ No newline at end of file + echo("All good!") diff --git a/lib/system.nim b/lib/system.nim index d19a406cb..4d8610737 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -285,6 +285,13 @@ proc low*(x: string): int {.magic: "Low", noSideEffect.} ## low(2) #=> -9223372036854775808 ## low(int) #=> -9223372036854775808 +proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".} + ## use this instead of `=` for a `shallow copy`:idx:. The shallow copy + ## only changes the semantics for sequences and strings (and types which + ## contain those). Be careful with the changed semantics though! There + ## is a reason why the default assignment does a deep copy of sequences + ## and strings. + when defined(nimArrIdx): # :array|openarray|string|seq|cstring|tuple proc `[]`*[I: Ordinal;T](a: T; i: I): T {. @@ -292,15 +299,21 @@ when defined(nimArrIdx): proc `[]=`*[I: Ordinal;T,S](a: T; i: I; x: S) {.noSideEffect, magic: "ArrPut".} proc `=`*[T](dest: var T; src: T) {.noSideEffect, magic: "Asgn".} + when defined(nimNewRuntime): + proc `=destroy`*[T](x: var T) {.inline, magic: "Asgn".} = + ## generic `destructor`:idx: implementation that can be overriden. + discard + proc `=sink`*[T](x: var T; y: T) {.inline, magic: "Asgn".} = + ## generic `sink`:idx: implementation that can be overriden. + shallowCopy(x, y) type - Slice*[T] = object ## builtin slice type - a*, b*: T ## the bounds + HSlice*[T, U] = object ## "heterogenous" slice type + a*: T ## the lower bound (inclusive) + b*: U ## the upper bound (inclusive) + Slice*[T] = HSlice[T, T] ## an alias for ``HSlice[T, T]`` -when defined(nimalias): - {.deprecated: [TSlice: Slice].} - -proc `..`*[T](a, b: T): Slice[T] {.noSideEffect, inline, magic: "DotDot".} = +proc `..`*[T, U](a: T, b: U): HSlice[T, U] {.noSideEffect, inline, magic: "DotDot".} = ## `slice`:idx: operator that constructs an interval ``[a, b]``, both `a` ## and `b` are inclusive. Slices can also be used in the set constructor ## and in ordinal case statements, but then they are special-cased by the @@ -308,8 +321,8 @@ proc `..`*[T](a, b: T): Slice[T] {.noSideEffect, inline, magic: "DotDot".} = result.a = a result.b = b -proc `..`*[T](b: T): Slice[T] {.noSideEffect, inline, magic: "DotDot".} = - ## `slice`:idx: operator that constructs an interval ``[default(T), b]`` +proc `..`*[T](b: T): HSlice[int, T] {.noSideEffect, inline, magic: "DotDot".} = + ## `slice`:idx: operator that constructs an interval ``[default(int), b]`` result.b = b when not defined(niminheritable): @@ -443,7 +456,14 @@ type WriteIOEffect* = object of IOEffect ## Effect describing a write IO operation. ExecIOEffect* = object of IOEffect ## Effect describing an executing IO operation. - Exception* {.compilerproc.} = object of RootObj ## \ + StackTraceEntry* = object ## In debug mode exceptions store the stack trace that led + ## to them. A StackTraceEntry is a single entry of the + ## stack trace. + procname*: cstring ## name of the proc that is currently executing + line*: int ## line number of the proc that is currently executing + filename*: cstring ## filename of the proc that is currently executing + + Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \ ## Base exception class. ## ## Each exception has to inherit from `Exception`. See the full `exception @@ -455,7 +475,10 @@ type msg* {.exportc: "message".}: string ## the exception's message. Not ## providing an exception message ## is bad style. - trace: string + when defined(js): + trace: string + else: + trace: seq[StackTraceEntry] up: ref Exception # used for stacking exceptions. Not exported! SystemError* = object of Exception ## \ @@ -638,13 +661,17 @@ proc sizeof*[T](x: T): int {.magic: "SizeOf", noSideEffect.} when defined(nimtypedescfixed): proc sizeof*(x: typedesc): int {.magic: "SizeOf", noSideEffect.} -proc `<`*[T](x: Ordinal[T]): T {.magic: "UnaryLt", noSideEffect.} +proc `<`*[T](x: Ordinal[T]): T {.magic: "UnaryLt", noSideEffect, deprecated.} ## unary ``<`` that can be used for nice looking excluding ranges: ## ## .. code-block:: nim ## for i in 0 .. <10: echo i #=> 0 1 2 3 4 5 6 7 8 9 ## ## Semantically this is the same as ``pred``. + ## + ## **Deprecated since version 0.18.0**. For the common excluding range + ## write ``0 ..< 10`` instead of ``0 .. < 10`` (look at the spacing). + ## For ``<x`` write ``pred(x)``. proc succ*[T](x: Ordinal[T], y = 1): T {.magic: "Succ", noSideEffect.} ## returns the ``y``-th successor of the value ``x``. ``T`` has to be @@ -716,6 +743,18 @@ proc newSeqOfCap*[T](cap: Natural): seq[T] {. ## ``cap``. discard +when not defined(JS): + proc newSeqUninitialized*[T: SomeNumber](len: Natural): seq[T] = + ## creates a new sequence of type ``seq[T]`` with length ``len``. + ## + ## Only available for numbers types. Note that the sequence will be + ## uninitialized. After the creation of the sequence you should assign + ## entries to the sequence instead of adding them. + + result = newSeqOfCap[T](len) + var s = cast[PGenericSeq](result) + s.len = len + proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {. magic: "LengthOpenArray", noSideEffect.} proc len*(x: string): int {.magic: "LengthStr", noSideEffect.} @@ -878,7 +917,7 @@ proc `div` *(x, y: int8): int8 {.magic: "DivI", noSideEffect.} proc `div` *(x, y: int16): int16 {.magic: "DivI", noSideEffect.} proc `div` *(x, y: int32): int32 {.magic: "DivI", noSideEffect.} ## computes the integer division. This is roughly the same as - ## ``floor(x/y)``. + ## ``trunc(x/y)``. ## ## .. code-block:: Nim ## 1 div 2 == 0 @@ -1080,7 +1119,7 @@ proc `*`*[T: SomeUnsignedInt](x, y: T): T {.magic: "MulU", noSideEffect.} proc `div`*[T: SomeUnsignedInt](x, y: T): T {.magic: "DivU", noSideEffect.} ## computes the integer division. This is roughly the same as - ## ``floor(x/y)``. + ## ``trunc(x/y)``. ## ## .. code-block:: Nim ## (7 div 5) == 1 @@ -1150,7 +1189,7 @@ proc contains*[T](x: set[T], y: T): bool {.magic: "InSet", noSideEffect.} ## is achieved by reversing the parameters for ``contains``; ``in`` then ## passes its arguments in reverse order. -proc contains*[T](s: Slice[T], value: T): bool {.noSideEffect, inline.} = +proc contains*[U, V, W](s: HSlice[U, V], value: W): bool {.noSideEffect, inline.} = ## Checks if `value` is within the range of `s`; returns true iff ## `value >= s.a and value <= s.b` ## @@ -1298,6 +1337,7 @@ proc add*(x: var string, y: string) {.magic: "AppendStrStr", noSideEffect.} ## tmp.add("cd") ## assert(tmp == "abcd") + type Endianness* = enum ## is a type describing the endianness of a processor. littleEndian, bigEndian @@ -1412,7 +1452,11 @@ const ## is the value that should be passed to `quit <#quit>`_ to indicate ## failure. -var programResult* {.exportc: "nim_program_result".}: int +when defined(nodejs): + var programResult* {.importc: "process.exitCode".}: int + programResult = 0 +else: + var programResult* {.exportc: "nim_program_result".}: int ## modify this variable to specify the exit code of the program ## under normal circumstances. When the program is terminated ## prematurely using ``quit``, this value is ignored. @@ -1429,7 +1473,8 @@ when defined(nimdoc): ## <#GC_fullCollect>`_. ## ## The proc ``quit(QuitSuccess)`` is called implicitly when your nim - ## program finishes without incident. A raised unhandled exception is + ## program finishes without incident for platforms where this is the + ## expected behavior. A raised unhandled exception is ## equivalent to calling ``quit(QuitFailure)``. ## ## Note that this is a *runtime* call and using ``quit`` inside a macro won't @@ -1480,13 +1525,6 @@ proc add *[T](x: var seq[T], y: openArray[T]) {.noSideEffect.} = setLen(x, xl + y.len) for i in 0..high(y): x[xl+i] = y[i] -proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".} - ## use this instead of `=` for a `shallow copy`:idx:. The shallow copy - ## only changes the semantics for sequences and strings (and types which - ## contain those). Be careful with the changed semantics though! There - ## is a reason why the default assignment does a deep copy of sequences - ## and strings. - proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} = ## deletes the item at index `i` by putting ``x[high(x)]`` into position `i`. ## This is an O(1) operation. @@ -1507,7 +1545,7 @@ proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} = ## i.delete(2) #=> @[1, 2, 4, 5] template defaultImpl = let xl = x.len - for j in i..xl-2: shallowCopy(x[j], x[j+1]) + for j in i.int..xl-2: shallowCopy(x[j], x[j+1]) setLen(x, xl-1) when nimvm: @@ -1974,34 +2012,80 @@ iterator countdown*[T](a, b: T, step = 1): T {.inline.} = yield res dec(res, step) -iterator countup*[S, T](a: S, b: T, step = 1): T {.inline.} = - ## Counts from ordinal value `a` up to `b` (inclusive) with the given - ## step count. `S`, `T` may be any ordinal type, `step` may only - ## be positive. **Note**: This fails to count to ``high(int)`` if T = int for - ## efficiency reasons. - when T is IntLikeForCount: - var res = int(a) - while res <= int(b): - yield T(res) - inc(res, step) - else: - var res: T = T(a) - while res <= b: - yield res - inc(res, step) +when defined(nimNewRoof): + iterator countup*[T](a, b: T, step = 1): T {.inline.} = + ## Counts from ordinal value `a` up to `b` (inclusive) with the given + ## step count. `S`, `T` may be any ordinal type, `step` may only + ## be positive. **Note**: This fails to count to ``high(int)`` if T = int for + ## efficiency reasons. + when T is IntLikeForCount: + var res = int(a) + while res <= int(b): + yield T(res) + inc(res, step) + else: + var res: T = T(a) + while res <= b: + yield res + inc(res, step) + + iterator `..`*[T](a, b: T): T {.inline.} = + ## An alias for `countup`. + when T is IntLikeForCount: + var res = int(a) + while res <= int(b): + yield T(res) + inc(res) + else: + var res: T = T(a) + while res <= b: + yield res + inc(res) + + template dotdotImpl(t) {.dirty.} = + iterator `..`*(a, b: t): t {.inline.} = + ## A type specialized version of ``..`` for convenience so that + ## mixing integer types work better. + var res = a + while res <= b: + yield res + inc(res) + + dotdotImpl(int64) + dotdotImpl(int32) + dotdotImpl(uint64) + dotdotImpl(uint32) + +else: + iterator countup*[S, T](a: S, b: T, step = 1): T {.inline.} = + ## Counts from ordinal value `a` up to `b` (inclusive) with the given + ## step count. `S`, `T` may be any ordinal type, `step` may only + ## be positive. **Note**: This fails to count to ``high(int)`` if T = int for + ## efficiency reasons. + when T is IntLikeForCount: + var res = int(a) + while res <= int(b): + yield T(res) + inc(res, step) + else: + var res: T = T(a) + while res <= b: + yield res + inc(res, step) + + iterator `..`*[S, T](a: S, b: T): T {.inline.} = + ## An alias for `countup`. + when T is IntLikeForCount: + var res = int(a) + while res <= int(b): + yield T(res) + inc(res) + else: + var res: T = T(a) + while res <= b: + yield res + inc(res) -iterator `..`*[S, T](a: S, b: T): T {.inline.} = - ## An alias for `countup`. - when T is IntLikeForCount: - var res = int(a) - while res <= int(b): - yield T(res) - inc(res) - else: - var res: T = T(a) - while res <= b: - yield res - inc(res) iterator `||`*[S, T](a: S, b: T, annotation=""): T {. inline, magic: "OmpParFor", sideEffect.} = @@ -2065,6 +2149,9 @@ proc max*[T](x, y: T): T = if y <= x: x else: y {.pop.} +proc high*(T: typedesc[SomeReal]): T = Inf +proc low*(T: typedesc[SomeReal]): T = NegInf + proc clamp*[T](x, a, b: T): T = ## limits the value ``x`` within the interval [a, b] ## @@ -2075,7 +2162,7 @@ proc clamp*[T](x, a, b: T): T = if x > b: return b return x -proc len*[T: Ordinal](x: Slice[T]): int {.noSideEffect, inline.} = +proc len*[U: Ordinal; V: Ordinal](x: HSlice[U, V]): int {.noSideEffect, inline.} = ## length of ordinal slice, when x.b < x.a returns zero length ## ## .. code-block:: Nim @@ -2143,7 +2230,7 @@ iterator items*(E: typedesc[enum]): E = for v in low(E)..high(E): yield v -iterator items*[T](s: Slice[T]): T = +iterator items*[T](s: HSlice[T, T]): T = ## iterates over the slice `s`, yielding each value between `s.a` and `s.b` ## (inclusively). for x in s.a..s.b: @@ -2429,9 +2516,9 @@ proc `$`*[T: tuple|object](x: T): string = when compiles($value): when compiles(value.isNil): if value.isNil: result.add "nil" - else: result.add($value) + else: result.addQuoted(value) else: - result.add($value) + result.addQuoted(value) firstElement = false else: result.add("...") @@ -2451,12 +2538,9 @@ proc collectionToString[T](x: T, prefix, separator, suffix: string): string = if value.isNil: result.add "nil" else: - result.add($value) - # prevent temporary string allocation - elif compiles(result.add(value)): - result.add(value) + result.addQuoted(value) else: - result.add($value) + result.addQuoted(value) result.add(suffix) @@ -2703,9 +2787,9 @@ when defined(nimvarargstyped): ## pretends to be free of side effects, so that it can be used for debugging ## routines marked as `noSideEffect <manual.html#pragmas-nosideeffect-pragma>`_. else: - proc echo*(x: varargs[expr, `$`]) {.magic: "Echo", tags: [WriteIOEffect], + proc echo*(x: varargs[untyped, `$`]) {.magic: "Echo", tags: [WriteIOEffect], benign, sideEffect.} - proc debugEcho*(x: varargs[expr, `$`]) {.magic: "Echo", noSideEffect, + proc debugEcho*(x: varargs[untyped, `$`]) {.magic: "Echo", noSideEffect, tags: [], raises: [].} template newException*(exceptn: typedesc, message: string; @@ -2821,7 +2905,7 @@ when not defined(JS): #and not defined(nimscript): fmRead, ## Open the file for read access only. fmWrite, ## Open the file for write access only. ## If the file does not exist, it will be - ## created. + ## created. Existing files will be cleared! fmReadWrite, ## Open the file for read and write access. ## If the file does not exist, it will be ## created. Existing files will be cleared! @@ -2844,7 +2928,10 @@ when not defined(JS): #and not defined(nimscript): elif x > y: result = 1 else: result = 0 else: - result = int(c_strcmp(x, y)) + let minlen = min(x.len, y.len) + result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + if result == 0: + result = x.len - y.len when defined(nimscript): proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} @@ -3048,9 +3135,6 @@ when not defined(JS): #and not defined(nimscript): ## returns the OS file handle of the file ``f``. This is only useful for ## platform specific programming. - when not defined(nimfix): - {.deprecated: [fileHandle: getFileHandle].} - when declared(newSeq): proc cstringArrayToSeq*(a: cstringArray, len: Natural): seq[string] = ## converts a ``cstringArray`` to a ``seq[string]``. `a` is supposed to be @@ -3402,6 +3486,36 @@ proc `/`*(x, y: int): float {.inline, noSideEffect.} = ## integer division that results in a float. result = toFloat(x) / toFloat(y) +type + BackwardsIndex* = distinct int ## type that is constructed by ``^`` for + ## reversed array accesses. + +template `^`*(x: int): BackwardsIndex = BackwardsIndex(x) + ## builtin `roof`:idx: operator that can be used for convenient array access. + ## ``a[^x]`` is a shortcut for ``a[a.len-x]``. + +template `..^`*(a, b: untyped): untyped = + ## a shortcut for '.. ^' to avoid the common gotcha that a space between + ## '..' and '^' is required. + a .. ^b + +template `..<`*(a, b: untyped): untyped = + ## a shortcut for 'a..pred(b)'. + a .. pred(b) + +when defined(nimNewRoof): + iterator `..<`*[T](a, b: T): T = + var i = T(a) + while i < b: + yield i + inc i +else: + iterator `..<`*[S, T](a: S, b: T): T = + var i = T(a) + while i < b: + yield i + inc i + template spliceImpl(s, a, L, b: untyped): untyped = # make room for additional elements or cut: var shift = b.len - max(0,L) # ignore negative slice size @@ -3415,19 +3529,25 @@ template spliceImpl(s, a, L, b: untyped): untyped = # cut down: setLen(s, newLen) # fill the hole: - for i in 0 .. <b.len: s[a+i] = b[i] + for i in 0 ..< b.len: s[a+i] = b[i] + +template `^^`(s, i: untyped): untyped = + (when i is BackwardsIndex: s.len - int(i) else: int(i)) when hasAlloc or defined(nimscript): - proc `[]`*(s: string, x: Slice[int]): string {.inline.} = + proc `[]`*[T, U](s: string, x: HSlice[T, U]): string {.inline.} = ## slice operation for strings. ## returns the inclusive range [s[x.a], s[x.b]]: ## ## .. code-block:: nim ## var s = "abcdef" ## assert s[1..3] == "bcd" - result = s.substr(x.a, x.b) + let a = s ^^ x.a + let L = (s ^^ x.b) - a + 1 + result = newString(L) + for i in 0 ..< L: result[i] = s[i + a] - proc `[]=`*(s: var string, x: Slice[int], b: string) = + proc `[]=`*[T, U](s: var string, x: HSlice[T, U], b: string) = ## slice assignment for strings. If ## ``b.len`` is not exactly the number of elements that are referred to ## by `x`, a `splice`:idx: is performed: @@ -3436,75 +3556,76 @@ when hasAlloc or defined(nimscript): ## var s = "abcdef" ## s[1 .. ^2] = "xyz" ## assert s == "axyzf" - var a = x.a - var L = x.b - a + 1 + var a = s ^^ x.a + var L = (s ^^ x.b) - a + 1 if L == b.len: - for i in 0 .. <L: s[i+a] = b[i] + for i in 0..<L: s[i+a] = b[i] else: spliceImpl(s, a, L, b) -proc `[]`*[Idx, T](a: array[Idx, T], x: Slice[int]): seq[T] = +proc `[]`*[Idx, T, U, V](a: array[Idx, T], x: HSlice[U, V]): seq[T] = ## slice operation for arrays. ## returns the inclusive range [a[x.a], a[x.b]]: ## ## .. code-block:: nim ## var a = [1,2,3,4] ## assert a[0..2] == @[1,2,3] - when low(a) < 0: - {.error: "Slicing for arrays with negative indices is unsupported.".} - var L = x.b - x.a + 1 + let xa = a ^^ x.a + let L = (a ^^ x.b) - xa + 1 result = newSeq[T](L) - for i in 0.. <L: result[i] = a[i + x.a] + for i in 0..<L: result[i] = a[Idx(i + xa)] -proc `[]=`*[Idx, T](a: var array[Idx, T], x: Slice[int], b: openArray[T]) = +proc `[]=`*[Idx, T, U, V](a: var array[Idx, T], x: HSlice[U, V], b: openArray[T]) = ## slice assignment for arrays. - when low(a) < 0: - {.error: "Slicing for arrays with negative indices is unsupported.".} - var L = x.b - x.a + 1 + let xa = a ^^ x.a + let L = (a ^^ x.b) - xa + 1 if L == b.len: - for i in 0 .. <L: a[i+x.a] = b[i] + for i in 0..<L: a[Idx(i + xa)] = b[i] else: sysFatal(RangeError, "different lengths for slice assignment") -proc `[]`*[Idx, T](a: array[Idx, T], x: Slice[Idx]): seq[T] = - ## slice operation for arrays. - var L = ord(x.b) - ord(x.a) + 1 - newSeq(result, L) - for i in 0.. <L: - result[i] = a[Idx(ord(x.a) + i)] - -proc `[]=`*[Idx, T](a: var array[Idx, T], x: Slice[Idx], b: openArray[T]) = - ## slice assignment for arrays. - var L = ord(x.b) - ord(x.a) + 1 - if L == b.len: - for i in 0 .. <L: - a[Idx(ord(x.a) + i)] = b[i] - else: - sysFatal(RangeError, "different lengths for slice assignment") - -proc `[]`*[T](s: seq[T], x: Slice[int]): seq[T] = +proc `[]`*[T, U, V](s: openArray[T], x: HSlice[U, V]): seq[T] = ## slice operation for sequences. ## returns the inclusive range [s[x.a], s[x.b]]: ## ## .. code-block:: nim ## var s = @[1,2,3,4] ## assert s[0..2] == @[1,2,3] - var a = x.a - var L = x.b - a + 1 + let a = s ^^ x.a + let L = (s ^^ x.b) - a + 1 newSeq(result, L) - for i in 0.. <L: result[i] = s[i + a] + for i in 0 ..< L: result[i] = s[i + a] -proc `[]=`*[T](s: var seq[T], x: Slice[int], b: openArray[T]) = +proc `[]=`*[T, U, V](s: var seq[T], x: HSlice[U, V], b: openArray[T]) = ## slice assignment for sequences. If ## ``b.len`` is not exactly the number of elements that are referred to ## by `x`, a `splice`:idx: is performed. - var a = x.a - var L = x.b - a + 1 + let a = s ^^ x.a + let L = (s ^^ x.b) - a + 1 if L == b.len: - for i in 0 .. <L: s[i+a] = b[i] + for i in 0 ..< L: s[i+a] = b[i] else: spliceImpl(s, a, L, b) +proc `[]`*[T](s: openArray[T]; i: BackwardsIndex): T {.inline.} = + system.`[]`(s, s.len - int(i)) + +proc `[]`*[Idx, T](a: array[Idx, T]; i: BackwardsIndex): T {.inline.} = + a[Idx(a.len - int(i) + int low(a))] +proc `[]`*(s: string; i: BackwardsIndex): char {.inline.} = s[s.len - int(i)] + +proc `[]`*[T](s: var openArray[T]; i: BackwardsIndex): var T {.inline.} = + system.`[]`(s, s.len - int(i)) +proc `[]`*[Idx, T](a: var array[Idx, T]; i: BackwardsIndex): var T {.inline.} = + a[Idx(a.len - int(i) + int low(a))] + +proc `[]=`*[T](s: var openArray[T]; i: BackwardsIndex; x: T) {.inline.} = + system.`[]=`(s, s.len - int(i), x) +proc `[]=`*[Idx, T](a: var array[Idx, T]; i: BackwardsIndex; x: T) {.inline.} = + a[Idx(a.len - int(i) + int low(a))] = x +proc `[]=`*(s: var string; i: BackwardsIndex; x: char) {.inline.} = + s[s.len - int(i)] = x + proc slurp*(filename: string): string {.magic: "Slurp".} ## This is an alias for `staticRead <#staticRead>`_. @@ -3612,7 +3733,7 @@ proc instantiationInfo*(index = -1, fullPaths = false): tuple[ ## .. code-block:: nim ## import strutils ## - ## template testException(exception, code: expr): stmt = + ## template testException(exception, code: untyped): typed = ## try: ## let pos = instantiationInfo() ## discard(code) @@ -3651,6 +3772,7 @@ template assert*(cond: bool, msg = "") = ## that ``AssertionError`` is hidden from the effect system, so it doesn't ## produce ``{.raises: [AssertionError].}``. This exception is only supposed ## to be caught by unit testing frameworks. + ## ## The compiler may not generate any code at all for ``assert`` if it is ## advised to do so through the ``-d:release`` or ``--assertions:off`` ## `command line switches <nimc.html#command-line-switches>`_. @@ -3750,11 +3872,11 @@ type {.deprecated: [PNimrodNode: NimNode].} when false: - template eval*(blk: stmt): stmt = + template eval*(blk: typed): typed = ## executes a block of code at compile time just as if it was a macro ## optionally, the block can return an AST tree that will replace the ## eval expression - macro payload: stmt {.gensym.} = blk + macro payload: typed {.gensym.} = blk payload() when hasAlloc: @@ -3784,6 +3906,65 @@ proc compiles*(x: untyped): bool {.magic: "Compiles", noSideEffect, compileTime. when declared(initDebugger): initDebugger() +proc addEscapedChar*(s: var string, c: char) {.noSideEffect, inline.} = + ## Adds a char to string `s` and applies the following escaping: + ## + ## * replaces any ``\`` by ``\\`` + ## * replaces any ``'`` by ``\'`` + ## * replaces any ``"`` by ``\"`` + ## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}`` + ## by ``\xHH`` where ``HH`` is its hexadecimal value. + ## + ## The procedure has been designed so that its output is usable for many + ## different common syntaxes. + ## **Note**: This is not correct for producing Ansi C code! + case c + of '\0'..'\31', '\127'..'\255': + add(s, "\\x") + const HexChars = "0123456789ABCDEF" + let n = ord(c) + s.add(HexChars[int((n and 0xF0) shr 4)]) + s.add(HexChars[int(n and 0xF)]) + of '\\': add(s, "\\\\") + of '\'': add(s, "\\'") + of '\"': add(s, "\\\"") + else: add(s, c) + +proc addQuoted*[T](s: var string, x: T) = + ## Appends `x` to string `s` in place, applying quoting and escaping + ## if `x` is a string or char. See + ## `addEscapedChar <system.html#addEscapedChar>`_ + ## for the escaping scheme. + ## + ## The Nim standard library uses this function on the elements of + ## collections when producing a string representation of a collection. + ## It is recommended to use this function as well for user-side collections. + ## Users may overload `addQuoted` for custom (string-like) types if + ## they want to implement a customized element representation. + ## + ## .. code-block:: Nim + ## var tmp = "" + ## tmp.addQuoted(1) + ## tmp.add(", ") + ## tmp.addQuoted("string") + ## tmp.add(", ") + ## tmp.addQuoted('c') + ## assert(tmp == """1, "string", 'c'""") + when T is string: + s.add("\"") + for c in x: + s.addEscapedChar(c) + s.add("\"") + elif T is char: + s.add("'") + s.addEscapedChar(x) + s.add("'") + # prevent temporary string allocation + elif compiles(s.add(x)): + s.add(x) + else: + s.add($x) + when hasAlloc: # XXX: make these the default (or implement the NilObject optimization) proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect.} = @@ -3845,31 +4026,6 @@ proc procCall*(x: untyped) {.magic: "ProcCall", compileTime.} = ## procCall someMethod(a, b) discard -proc `^`*[T](x: int; y: openArray[T]): int {.noSideEffect, magic: "Roof".} -proc `^`*(x: int): int {.noSideEffect, magic: "Roof".} = - ## builtin `roof`:idx: operator that can be used for convenient array access. - ## ``a[^x]`` is rewritten to ``a[a.len-x]``. However currently the ``a`` - ## expression must not have side effects for this to compile. Note that since - ## this is a builtin, it automatically works for all kinds of - ## overloaded ``[]`` or ``[]=`` accessors. - discard - -template `..^`*(a, b: untyped): untyped = - ## a shortcut for '.. ^' to avoid the common gotcha that a space between - ## '..' and '^' is required. - a .. ^b - -template `..<`*(a, b: untyped): untyped {.dirty.} = - ## a shortcut for '.. <' to avoid the common gotcha that a space between - ## '..' and '<' is required. - a .. <b - -iterator `..<`*[S,T](a: S, b: T): T = - var i = T(a) - while i < b: - yield i - inc i - proc xlen*(x: string): int {.magic: "XLenStr", noSideEffect.} = discard proc xlen*[T](x: seq[T]): int {.magic: "XLenSeq", noSideEffect.} = ## returns the length of a sequence or a string without testing for 'nil'. @@ -3916,3 +4072,38 @@ when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage): proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32", importc: "SetConsoleOutputCP".} discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage + + +when defined(nimHasRunnableExamples): + proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} + ## A section you should use to mark `runnable example`:idx: code with. + ## + ## - In normal debug and release builds code within + ## a ``runnableExamples`` section is ignored. + ## - The documentation generator is aware of these examples and considers them + ## part of the ``##`` doc comment. As the last step of documentation + ## generation the examples are put into an ``$file_example.nim`` file, + ## compiled and tested. The collected examples are + ## put into their own module to ensure the examples do not refer to + ## non-exported symbols. +else: + template runnableExamples*(body: untyped) = + discard + +template doAssertRaises*(exception, code: untyped): typed = + ## Raises ``AssertionError`` if specified ``code`` does not raise the + ## specified exception. + runnableExamples: + doAssertRaises(ValueError): + raise newException(ValueError, "Hello World") + + try: + block: + code + raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) + except exception: + discard + except Exception as exc: + raiseAssert(astToStr(exception) & + " wasn't raised, another error was raised instead by:\n"& + astToStr(code)) diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 78db96e77..e274e8e0c 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -8,8 +8,6 @@ # # Low level allocator for Nim. Has been designed to support the GC. -# TODO: -# - make searching for block O(1) {.push profiler:off.} include osalloc @@ -19,14 +17,17 @@ template track(op, address, size) = memTrackerOp(op, address, size) # We manage *chunks* of memory. Each chunk is a multiple of the page size. -# Each chunk starts at an address that is divisible by the page size. Chunks -# that are bigger than ``ChunkOsReturn`` are returned back to the operating -# system immediately. +# Each chunk starts at an address that is divisible by the page size. const - ChunkOsReturn = 256 * PageSize # 1 MB - InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn! + InitialMemoryRequest = 128 * PageSize # 0.5 MB SmallChunkSize = PageSize + MaxFli = 30 + MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32' + # everywhere! + MaxSli = 1 shl MaxLog2Sli + FliOffset = 6 + RealFli = MaxFli - FliOffset type PTrunk = ptr Trunk @@ -99,10 +100,12 @@ type MemRegion = object minLargeObj, maxLargeObj: int freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk] + flBitmap: uint32 + slBitmap: array[RealFli, uint32] + matrix: array[RealFli, array[MaxSli, PBigChunk]] llmem: PLLChunk currMem, maxMem, freeMem: int # memory sizes (allocated from OS) lastSize: int # needed for the case that OS gives us pages linearly - freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access chunkStarts: IntSet root, deleted, last, freeAvlNodes: PAvlNode locked, blockChunkSizeIncrease: bool # if locked, we cannot free pages. @@ -110,7 +113,109 @@ type bottomData: AvlNode heapLinks: HeapLinks -{.deprecated: [TMemRegion: MemRegion].} +const + fsLookupTable: array[byte, int8] = [ + -1'i8, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + ] + +proc msbit(x: uint32): int {.inline.} = + let a = if x <= 0xff_ff: + (if x <= 0xff: 0 else: 8) + else: + (if x <= 0xff_ff_ff: 16 else: 24) + result = int(fsLookupTable[byte(x shr a)]) + a + +proc lsbit(x: uint32): int {.inline.} = + msbit(x and ((not x) + 1)) + +proc setBit(nr: int; dest: var uint32) {.inline.} = + dest = dest or (1u32 shl (nr and 0x1f)) + +proc clearBit(nr: int; dest: var uint32) {.inline.} = + dest = dest and not (1u32 shl (nr and 0x1f)) + +proc mappingSearch(r, fl, sl: var int) {.inline.} = + #let t = (1 shl (msbit(uint32 r) - MaxLog2Sli)) - 1 + # This diverges from the standard TLSF algorithm because we need to ensure + # PageSize alignment: + let t = roundup((1 shl (msbit(uint32 r) - MaxLog2Sli)), PageSize) - 1 + r = r + t + fl = msbit(uint32 r) + sl = (r shr (fl - MaxLog2Sli)) - MaxSli + dec fl, FliOffset + r = r and not t + sysAssert((r and PageMask) == 0, "mappingSearch: still not aligned") + +# See http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf for details of +# this algorithm. + +proc mappingInsert(r: int): tuple[fl, sl: int] {.inline.} = + sysAssert((r and PageMask) == 0, "mappingInsert: still not aligned") + result.fl = msbit(uint32 r) + result.sl = (r shr (result.fl - MaxLog2Sli)) - MaxSli + dec result.fl, FliOffset + +template mat(): untyped = a.matrix[fl][sl] + +proc findSuitableBlock(a: MemRegion; fl, sl: var int): PBigChunk {.inline.} = + let tmp = a.slBitmap[fl] and (not 0u32 shl sl) + result = nil + if tmp != 0: + sl = lsbit(tmp) + result = mat() + else: + fl = lsbit(a.flBitmap and (not 0u32 shl (fl + 1))) + if fl > 0: + sl = lsbit(a.slBitmap[fl]) + result = mat() + +template clearBits(sl, fl) = + clearBit(sl, a.slBitmap[fl]) + if a.slBitmap[fl] == 0u32: + # do not forget to cascade: + clearBit(fl, a.flBitmap) + +proc removeChunkFromMatrix(a: var MemRegion; b: PBigChunk) = + let (fl, sl) = mappingInsert(b.size) + if b.next != nil: b.next.prev = b.prev + if b.prev != nil: b.prev.next = b.next + if mat() == b: + mat() = b.next + if mat() == nil: + clearBits(sl, fl) + b.prev = nil + b.next = nil + +proc removeChunkFromMatrix2(a: var MemRegion; b: PBigChunk; fl, sl: int) = + mat() = b.next + if mat() != nil: + mat().prev = nil + else: + clearBits(sl, fl) + b.prev = nil + b.next = nil + +proc addChunkToMatrix(a: var MemRegion; b: PBigChunk) = + let (fl, sl) = mappingInsert(b.size) + b.prev = nil + b.next = mat() + if mat() != nil: + mat().prev = b + mat() = b + setBit(sl, a.slBitmap[fl]) + setBit(fl, a.flBitmap) {.push stack_trace: off.} proc initAllocator() = discard "nothing to do anymore" @@ -203,6 +308,7 @@ proc llDeallocAll(a: var MemRegion) = var next = it.next osDeallocPages(it, PageSize) it = next + a.llmem = nil proc intSetGet(t: IntSet, key: int): PTrunk = var it = t.data[key and high(t.data)] @@ -301,13 +407,14 @@ proc pageAddr(p: pointer): PChunk {.inline.} = result = cast[PChunk](cast[ByteAddress](p) and not PageMask) #sysAssert(Contains(allocator.chunkStarts, pageIndex(result))) -proc writeFreeList(a: MemRegion) = - var it = a.freeChunksList - c_fprintf(stdout, "freeChunksList: %p\n", it) - while it != nil: - c_fprintf(stdout, "it: %p, next: %p, prev: %p, size: %ld\n", - it, it.next, it.prev, it.size) - it = it.next +when false: + proc writeFreeList(a: MemRegion) = + var it = a.freeChunksList + c_fprintf(stdout, "freeChunksList: %p\n", it) + while it != nil: + c_fprintf(stdout, "it: %p, next: %p, prev: %p, size: %ld\n", + it, it.next, it.prev, it.size) + it = it.next const nimMaxHeap {.intdefine.} = 0 @@ -368,6 +475,7 @@ proc requestOsChunks(a: var MemRegion, size: int): PBigChunk = result.prevSize = 0 or (result.prevSize and 1) # unknown # but do not overwrite 'used' field a.lastSize = size # for next request + sysAssert((cast[int](result) and PageMask) == 0, "requestOschunks: unaligned chunk") proc isAccessible(a: MemRegion, p: pointer): bool {.inline.} = result = contains(a.chunkStarts, pageIndex(p)) @@ -418,7 +526,7 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = if isAccessible(a, ri) and chunkUnused(ri): sysAssert(not isSmallChunk(ri), "freeBigChunk 3") if not isSmallChunk(ri): - listRemove(a.freeChunksList, cast[PBigChunk](ri)) + removeChunkFromMatrix(a, cast[PBigChunk](ri)) inc(c.size, ri.size) excl(a.chunkStarts, pageIndex(ri)) when coalescLeft: @@ -429,49 +537,44 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = if isAccessible(a, le) and chunkUnused(le): sysAssert(not isSmallChunk(le), "freeBigChunk 5") if not isSmallChunk(le): - listRemove(a.freeChunksList, cast[PBigChunk](le)) + removeChunkFromMatrix(a, cast[PBigChunk](le)) inc(le.size, c.size) excl(a.chunkStarts, pageIndex(c)) c = cast[PBigChunk](le) incl(a, a.chunkStarts, pageIndex(c)) updatePrevSize(a, c, c.size) - listAdd(a.freeChunksList, c) + addChunkToMatrix(a, c) # set 'used' to false: c.prevSize = c.prevSize and not 1 proc splitChunk(a: var MemRegion, c: PBigChunk, size: int) = var rest = cast[PBigChunk](cast[ByteAddress](c) +% size) - sysAssert(rest notin a.freeChunksList, "splitChunk") rest.size = c.size - size track("rest.origSize", addr rest.origSize, sizeof(int)) + # XXX check if these two nil assignments are dead code given + # addChunkToMatrix's implementation: rest.next = nil rest.prev = nil - # size and not used + # size and not used: rest.prevSize = size sysAssert((size and 1) == 0, "splitChunk 2") + sysAssert((size and PageMask) == 0, + "splitChunk: size is not a multiple of the PageSize") updatePrevSize(a, c, rest.size) c.size = size incl(a, a.chunkStarts, pageIndex(rest)) - listAdd(a.freeChunksList, rest) + addChunkToMatrix(a, rest) proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # use first fit for now: - sysAssert((size and PageMask) == 0, "getBigChunk 1") sysAssert(size > 0, "getBigChunk 2") - result = a.freeChunksList - block search: - while result != nil: - sysAssert chunkUnused(result), "getBigChunk 3" - if result.size == size: - listRemove(a.freeChunksList, result) - break search - elif result.size > size: - listRemove(a.freeChunksList, result) - splitChunk(a, result, size) - break search - result = result.next - sysAssert result != a.freeChunksList, "getBigChunk 4" + var size = size # roundup(size, PageSize) + var fl, sl: int + mappingSearch(size, fl, sl) + sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk") + result = findSuitableBlock(a, fl, sl) + if result == nil: if size < InitialMemoryRequest: result = requestOsChunks(a, InitialMemoryRequest) splitChunk(a, result, size) @@ -480,7 +583,10 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # if we over allocated split the chunk: if result.size > size: splitChunk(a, result, size) - + else: + removeChunkFromMatrix2(a, result, fl, sl) + if result.size >= size + PageSize: + splitChunk(a, result, size) # set 'used' to to true: result.prevSize = 1 track("setUsedToFalse", addr result.origSize, sizeof(int)) @@ -571,14 +677,14 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = size == 0, "rawAlloc 21") sysAssert(allocInv(a), "rawAlloc: end small size") else: - size = roundup(requestedSize+bigChunkOverhead(), PageSize) + size = requestedSize + bigChunkOverhead() # roundup(requestedSize+bigChunkOverhead(), PageSize) # allocate a large block var c = getBigChunk(a, size) sysAssert c.prev == nil, "rawAlloc 10" sysAssert c.next == nil, "rawAlloc 11" - sysAssert c.size == size, "rawAlloc 12" result = addr(c.data) - sysAssert((cast[ByteAddress](result) and (MemAlign-1)) == 0, "rawAlloc 13") + sysAssert((cast[ByteAddress](c) and (MemAlign-1)) == 0, "rawAlloc 13") + sysAssert((cast[ByteAddress](c) and PageMask) == 0, "rawAlloc: Not aligned on a page boundary") if a.root == nil: a.root = getBottom(a) add(a, a.root, cast[ByteAddress](result), cast[ByteAddress](result)+%size) sysAssert(isAccessible(a, result), "rawAlloc 14") diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim index 8c3801687..afc435638 100644 --- a/lib/system/atomics.nim +++ b/lib/system/atomics.nim @@ -213,12 +213,20 @@ proc atomicDec*(memLoc: var int, x: int = 1): int = result = memLoc when defined(vcc): - proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64 - {.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".} - proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32 - {.importc: "_InterlockedCompareExchange", header: "<intrin.h>".} - proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte - {.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".} + when defined(cpp): + proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64 + {.importcpp: "_InterlockedCompareExchange64(static_cast<NI64 volatile *>(#), #, #)", header: "<intrin.h>".} + proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32 + {.importcpp: "_InterlockedCompareExchange(static_cast<NI volatile *>(#), #, #)", header: "<intrin.h>".} + proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte + {.importcpp: "_InterlockedCompareExchange8(static_cast<char volatile *>(#), #, #)", header: "<intrin.h>".} + else: + proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64 + {.importc: "_InterlockedCompareExchange64", header: "<intrin.h>".} + proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32 + {.importc: "_InterlockedCompareExchange", header: "<intrin.h>".} + proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte + {.importc: "_InterlockedCompareExchange8", header: "<intrin.h>".} proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool = when sizeof(T) == 8: diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 1520f231e..69b680dbd 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -63,7 +63,6 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} = while x != subclass: if x == nil: sysFatal(ObjectConversionError, "invalid object conversion") - break x = x.base proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} = diff --git a/lib/system/debugger.nim b/lib/system/debugger.nim index cc6919d36..937c0d6f0 100644 --- a/lib/system/debugger.nim +++ b/lib/system/debugger.nim @@ -127,7 +127,7 @@ proc fileMatches(c, bp: cstring): bool = proc canonFilename*(filename: cstring): cstring = ## returns 'nil' if the filename cannot be found. - for i in 0 .. <dbgFilenameLen: + for i in 0 .. dbgFilenameLen-1: result = dbgFilenames[i] if fileMatches(result, filename): return result result = nil @@ -261,7 +261,7 @@ proc genericHash(dest: pointer, mt: PNimType): int = proc dbgRegisterWatchpoint(address: pointer, name: cstring, typ: PNimType) {.compilerproc.} = let L = watchPointsLen - for i in 0.. <L: + for i in 0 .. pred(L): if watchPoints[i].name == name: # address may have changed: watchPoints[i].address = address @@ -288,7 +288,7 @@ var proc checkWatchpoints = let L = watchPointsLen - for i in 0.. <L: + for i in 0 .. pred(L): let newHash = genericHash(watchPoints[i].address, watchPoints[i].typ) if newHash != watchPoints[i].oldValue: dbgWatchpointHook(watchPoints[i].name) diff --git a/lib/system/endb.nim b/lib/system/endb.nim index 35d8f25c4..d51ae29df 100644 --- a/lib/system/endb.nim +++ b/lib/system/endb.nim @@ -370,7 +370,7 @@ proc commandPrompt() = if dbgUser.len == 0: dbgUser.len = oldLen # now look what we have to do: var i = scanWord(addr dbgUser.data, dbgTemp, 0) - template `?`(x: expr): expr = dbgTemp == cstring(x) + template `?`(x: untyped): untyped = dbgTemp == cstring(x) if ?"s" or ?"step": dbgState = dbStepInto again = false diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 950981227..8e42ea468 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -38,20 +38,29 @@ proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.} proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.} proc chckNil(p: pointer) {.noinline, compilerproc, benign.} +type + GcFrame = ptr GcFrameHeader + GcFrameHeader {.compilerproc.} = object + len: int + prev: ptr GcFrameHeader + var framePtr {.threadvar.}: PFrame excHandler {.threadvar.}: PSafePoint # list of exception handlers # a global variable for the root of all try blocks currException {.threadvar.}: ref Exception + gcFramePtr {.threadvar.}: GcFrame type - FrameState = tuple[framePtr: PFrame, excHandler: PSafePoint, currException: ref Exception] + FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame, + excHandler: PSafePoint, currException: ref Exception] proc getFrameState*(): FrameState {.compilerRtl, inl.} = - return (framePtr, excHandler, currException) + return (gcFramePtr, framePtr, excHandler, currException) proc setFrameState*(state: FrameState) {.compilerRtl, inl.} = + gcFramePtr = state.gcFramePtr framePtr = state.framePtr excHandler = state.excHandler currException = state.currException @@ -61,9 +70,29 @@ proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev +when false: + proc popFrameOfAddr(s: PFrame) {.compilerRtl.} = + var it = framePtr + if it == s: + framePtr = framePtr.prev + else: + while it != nil: + if it == s: + framePtr = it.prev + break + it = it.prev + proc setFrame*(s: PFrame) {.compilerRtl, inl.} = framePtr = s +proc getGcFrame*(): GcFrame {.compilerRtl, inl.} = gcFramePtr +proc popGcFrame*() {.compilerRtl, inl.} = gcFramePtr = gcFramePtr.prev +proc setGcFrame*(s: GcFrame) {.compilerRtl, inl.} = gcFramePtr = s +proc pushGcFrame*(s: GcFrame) {.compilerRtl, inl.} = + s.prev = gcFramePtr + zeroMem(cast[pointer](cast[int](s)+%sizeof(GcFrameHeader)), s.len*sizeof(pointer)) + gcFramePtr = s + proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} = s.hasRaiseAction = false s.prev = excHandler @@ -138,6 +167,52 @@ when not hasThreadSupport: var tempFrames: array[0..127, PFrame] # should not be alloc'd on stack +const + reraisedFromBegin = -10 + reraisedFromEnd = -100 + +template reraisedFrom(z): untyped = + StackTraceEntry(procname: nil, line: z, filename: nil) + +proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) = + var + it = f + i = 0 + while it != nil: + inc(i) + it = it.prev + var last = i-1 + if s.isNil: + s = newSeq[StackTraceEntry](i) + else: + last = s.len + i - 1 + s.setLen(last+1) + it = f + while it != nil: + s[last] = StackTraceEntry(procname: it.procname, + line: it.line, + filename: it.filename) + it = it.prev + dec last + +template addFrameEntry(s, f: untyped) = + var oldLen = s.len + add(s, f.filename) + if f.line > 0: + add(s, '(') + add(s, $f.line) + add(s, ')') + for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ') + add(s, f.procname) + add(s, "\n") + +proc `$`(s: seq[StackTraceEntry]): string = + result = newStringOfCap(2000) + for i in 0 .. s.len-1: + if s[i].line == reraisedFromBegin: result.add "[[reraised from:\n" + elif s[i].line == reraisedFromEnd: result.add "]]\n" + else: addFrameEntry(result, s[i]) + proc auxWriteStackTrace(f: PFrame, s: var string) = when hasThreadSupport: var @@ -177,17 +252,9 @@ proc auxWriteStackTrace(f: PFrame, s: var string) = if tempFrames[j] == nil: add(s, "(") add(s, $skipped) - add(s, " calls omitted) ...") + add(s, " calls omitted) ...\n") else: - var oldLen = s.len - add(s, tempFrames[j].filename) - if tempFrames[j].line > 0: - add(s, '(') - add(s, $tempFrames[j].line) - add(s, ')') - for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ') - add(s, tempFrames[j].procname) - add(s, "\n") + addFrameEntry(s, tempFrames[j]) proc stackTraceAvailable*(): bool @@ -204,6 +271,13 @@ when hasSomeStackTrace: auxWriteStackTraceWithBacktrace(s) else: add(s, "No stack traceback available\n") + + proc rawWriteStackTrace(s: var seq[StackTraceEntry]) = + when NimStackTrace: + auxWriteStackTrace(framePtr, s) + else: + s = nil + proc stackTraceAvailable(): bool = when NimStackTrace: if framePtr == nil: @@ -223,12 +297,6 @@ proc quitOrDebug() {.inline.} = else: endbStep() # call the debugger -when false: - proc rawRaise*(e: ref Exception) = - ## undocumented. Do not use. - pushCurrentException(e) - c_longjmp(excHandler.context, 1) - var onUnhandledException*: (proc (errorMsg: string) {. nimcall.}) ## set this error \ ## handler to override the existing behaviour on an unhandled exception. @@ -265,7 +333,7 @@ proc raiseExceptionAux(e: ref Exception) = when hasSomeStackTrace: var buf = newStringOfCap(2000) if isNil(e.trace): rawWriteStackTrace(buf) - else: add(buf, e.trace) + else: add(buf, $e.trace) add(buf, "Error: unhandled exception: ") if not isNil(e.msg): add(buf, e.msg) add(buf, " [") @@ -301,12 +369,11 @@ proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} = if e.name.isNil: e.name = ename when hasSomeStackTrace: if e.trace.isNil: - e.trace = "" rawWriteStackTrace(e.trace) elif framePtr != nil: - e.trace.add "[[reraised from:\n" + e.trace.add reraisedFrom(reraisedFromBegin) auxWriteStackTrace(framePtr, e.trace) - e.trace.add "]]\n" + e.trace.add reraisedFrom(reraisedFromEnd) raiseExceptionAux(e) proc reraiseException() {.compilerRtl.} = @@ -332,10 +399,15 @@ proc getStackTrace(): string = proc getStackTrace(e: ref Exception): string = if not isNil(e) and not isNil(e.trace): - result = e.trace + result = $e.trace else: result = "" +proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = + ## Returns the attached stack trace to the exception ``e`` as + ## a ``seq``. This is not yet available for the JS backend. + shallowCopy(result, e.trace) + when defined(nimRequiresNimFrame): proc stackOverflow() {.noinline.} = writeStackTrace() @@ -357,7 +429,7 @@ when defined(endb): var dbgAborting: bool # whether the debugger wants to abort -when not defined(noSignalHandler): +when not defined(noSignalHandler) and not defined(useNimRtl): proc signalHandler(sign: cint) {.exportc: "signalHandler", noconv.} = template processSignal(s, action: untyped) {.dirty.} = if s == SIGINT: action("SIGINT: Interrupted by Ctrl-C.\n") diff --git a/lib/system/gc.nim b/lib/system/gc.nim index a2ff72a30..dac06119d 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -155,14 +155,15 @@ template setColor(c, col) = else: c.refcount = c.refcount and not colorMask or col -proc writeCell(msg: cstring, c: PCell) = - var kind = -1 - var typName: cstring = "nil" - if c.typ != nil: - kind = ord(c.typ.kind) - when defined(nimTypeNames): - if not c.typ.name.isNil: - typName = c.typ.name +when defined(logGC): + proc writeCell(msg: cstring, c: PCell) = + var kind = -1 + var typName: cstring = "nil" + if c.typ != nil: + kind = ord(c.typ.kind) + when defined(nimTypeNames): + if not c.typ.name.isNil: + typName = c.typ.name when leakDetector: c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld from %s(%ld)\n", @@ -317,7 +318,7 @@ proc initGC() = init(gch.marked) init(gch.additionalRoots) when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() + init(gch.toDispose) when useMarkForDebug or useBackupGc: type @@ -642,9 +643,9 @@ when useMarkForDebug or useBackupGc: forAllChildren(d, waMarkPrecise) proc markGlobals(gch: var GcHeap) = - for i in 0 .. < globalMarkersLen: globalMarkers[i]() + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() let d = gch.additionalRoots.d - for i in 0 .. < gch.additionalRoots.len: markS(gch, d[i]) + for i in 0 .. gch.additionalRoots.len-1: markS(gch, d[i]) when logGC: var @@ -652,7 +653,7 @@ when logGC: cycleCheckALen = 0 proc alreadySeen(c: PCell): bool = - for i in 0 .. <cycleCheckALen: + for i in 0 .. cycleCheckALen-1: if cycleCheckA[i] == c: return true if cycleCheckALen == len(cycleCheckA): gcAssert(false, "cycle detection overflow") diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 6dffc323e..d57a01dc7 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -133,7 +133,7 @@ proc initGC() = init(gch.additionalRoots) init(gch.greyStack) when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() + init(gch.toDispose) # Which color to use for new objects is tricky: When we're marking, # they have to be *white* so that everything is marked that is only @@ -173,7 +173,7 @@ proc internRefcount(p: pointer): int {.exportc: "getRefcount".} = when BitsPerPage mod (sizeof(int)*8) != 0: {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} -template color(c): expr = c.refCount and colorMask +template color(c): untyped = c.refCount and colorMask template setColor(c, col) = c.refcount = c.refcount and not colorMask or col @@ -487,12 +487,12 @@ proc GC_dumpHeap*(file: File) = var spaceIter: ObjectSpaceIter when false: var d = gch.decStack.d - for i in 0 .. < gch.decStack.len: + for i in 0 .. gch.decStack.len-1: if isAllocatedPtr(gch.region, d[i]): c_fprintf(file, "onstack %p\n", d[i]) else: c_fprintf(file, "onstack_invalid %p\n", d[i]) - for i in 0 .. < globalMarkersLen: globalMarkers[i]() + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() while true: let x = allObjectsAsProc(gch.region, addr spaceIter) if spaceIter.state < 0: break @@ -579,7 +579,7 @@ proc markIncremental(gch: var GcHeap): bool = result = true proc markGlobals(gch: var GcHeap) = - for i in 0 .. < globalMarkersLen: globalMarkers[i]() + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() proc doOperation(p: pointer, op: WalkOp) = if p == nil: return diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index cfc0dfa8a..5fc48d848 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -233,7 +233,7 @@ proc initGC() = init(gch.allocated) init(gch.marked) when hasThreadSupport: - gch.toDispose = initSharedList[pointer]() + init(gch.toDispose) proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = var d = cast[ByteAddress](dest) @@ -450,9 +450,9 @@ when false: quit 1 proc markGlobals(gch: var GcHeap) = - for i in 0 .. < globalMarkersLen: globalMarkers[i]() + for i in 0 .. globalMarkersLen-1: globalMarkers[i]() let d = gch.additionalRoots.d - for i in 0 .. < gch.additionalRoots.len: mark(gch, d[i]) + for i in 0 .. gch.additionalRoots.len-1: mark(gch, d[i]) proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = # the addresses are not as cells on the stack, so turn them to cells: diff --git a/lib/system/genodealloc.nim b/lib/system/genodealloc.nim new file mode 100644 index 000000000..3646a842d --- /dev/null +++ b/lib/system/genodealloc.nim @@ -0,0 +1,114 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Emery Hemingway +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Low level dataspace allocator for Genode. + +when not defined(genode): + {.error: "Genode only module".} + +type DataspaceCapability {. + importcpp: "Genode::Dataspace_capability", pure.} = object + +type + Map = object + attachment: pointer + size: int + ds: DataspaceCapability + + SlabMeta = object + next: ptr MapSlab + ds: DataspaceCapability + + MapSlab = object + meta: SlabMeta + maps: array[1,Map] + +const SlabBackendSize = 4096 + +proc ramAvail(): int {. + importcpp: "genodeEnv->pd().avail_ram().value".} + ## Return number of bytes available for allocation. + +proc capsAvail(): int {. + importcpp: "genodeEnv->pd().avail_caps().value".} + ## Return the number of available capabilities. + ## Each dataspace allocation consumes a capability. + +proc allocDataspace(size: int): DataspaceCapability {. + importcpp: "genodeEnv->pd().alloc(@)".} + ## Allocate a dataspace and its capability. + +proc attachDataspace(ds: DataspaceCapability): pointer {. + importcpp: "genodeEnv->rm().attach(@)".} + ## Attach a dataspace into the component address-space. + +proc detachAddress(p: pointer) {. + importcpp: "genodeEnv->rm().detach(@)".} + ## Detach a dataspace from the component address-space. + +proc freeDataspace(ds: DataspaceCapability) {. + importcpp: "genodeEnv->pd().free(@)".} + ## Free a dataspace. + +proc newMapSlab(): ptr MapSlab = + let + ds = allocDataspace SlabBackendSize + p = attachDataspace ds + result = cast[ptr MapSlab](p) + result.meta.ds = ds + +iterator items(s: ptr MapSlab): ptr Map = + let mapCount = (SlabBackendSize - sizeof(SlabMeta)) div sizeof(Map) + for i in 0 .. <mapCount: + yield s.maps[i].addr + +var slabs: ptr MapSlab + +proc osAllocPages(size: int): pointer = + if slabs.isNil: + slabs = newMapSlab() + var + slab = slabs + map: ptr Map + let mapCount = (SlabBackendSize - sizeof(SlabMeta)) div sizeof(Map) + block findFreeMap: + while true: + # lookup first free spot in slabs + for m in slab.items: + if m.attachment.isNil: + map = m + break findFreeMap + if slab.meta.next.isNil: + slab.meta.next = newMapSlab() + # tack a new slab on the tail + slab = slab.meta.next + # move to next slab in linked list + map.ds = allocDataspace size + map.size = size + map.attachment = attachDataspace map.ds + result = map.attachment + +proc osTryAllocPages(size: int): pointer = + if ramAvail() >= size and capsAvail() > 1: + result = osAllocPages size + +proc osDeallocPages(p: pointer; size: int) = + var slab = slabs + while not slab.isNil: + # lookup first free spot in slabs + for m in slab.items: + if m.attachment == p: + if m.size != size: + echo "cannot partially detach dataspace" + quit -1 + detachAddress m.attachment + freeDataspace m.ds + m[] = Map() + return + slab = slab.meta.next diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 24093a310..8065f2255 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -531,13 +531,13 @@ proc mulInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = proc divInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ - return floor(`a` / `b`); + return trunc(`a` / `b`); """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); - return Math.floor(`a` / `b`); + return Math.trunc(`a` / `b`); """ proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = @@ -549,7 +549,7 @@ proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); - return Math.floor(`a` % `b`); + return Math.trunc(`a` % `b`); """ proc addInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = @@ -594,13 +594,13 @@ proc mulInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = proc divInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ - return floor(`a` / `b`); + return trunc(`a` / `b`); """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); - return Math.floor(`a` / `b`); + return Math.trunc(`a` / `b`); """ proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = @@ -612,7 +612,7 @@ proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); - return Math.floor(`a` % `b`); + return Math.trunc(`a` % `b`); """ proc negInt(a: int): int {.compilerproc.} = diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 824934966..45e0c74c0 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -42,7 +42,8 @@ type # Page size of the system; in most cases 4096 bytes. For exotic OS or # CPU this needs to be changed: const - PageShift = when defined(cpu16): 8 else: 12 + PageShift = when defined(cpu16): 8 else: 12 # \ + # my tests showed no improvments for using larger page sizes. PageSize = 1 shl PageShift PageMask = PageSize-1 @@ -108,7 +109,6 @@ when defined(boehmgc): if result == nil: raiseOutOfMem() proc alloc0(size: Natural): pointer = result = alloc(size) - zeroMem(result, size) proc realloc(p: pointer, newsize: Natural): pointer = result = boehmRealloc(p, newsize) if result == nil: raiseOutOfMem() @@ -118,8 +118,7 @@ when defined(boehmgc): result = boehmAlloc(size) if result == nil: raiseOutOfMem() proc allocShared0(size: Natural): pointer = - result = alloc(size) - zeroMem(result, size) + result = allocShared(size) proc reallocShared(p: pointer, newsize: Natural): pointer = result = boehmRealloc(p, newsize) if result == nil: raiseOutOfMem() @@ -343,7 +342,6 @@ elif defined(gogc): const goFlagNoZero: uint32 = 1 shl 3 proc goRuntimeMallocGC(size: uint, typ: uint, flag: uint32): pointer {.importc: "runtime_mallocgc", dynlib: goLib.} - proc goFree(v: pointer) {.importc: "__go_free", dynlib: goLib.} proc goSetFinalizer(obj: pointer, f: pointer) {.importc: "set_finalizer", codegenDecl:"$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", dynlib: goLib.} @@ -376,7 +374,6 @@ elif defined(gogc): result = goRuntimeMallocGC(roundup(newsize, sizeof(pointer)).uint, 0.uint, goFlagNoZero) copyMem(result, old, oldsize) zeroMem(cast[pointer](cast[ByteAddress](result) +% oldsize), newsize - oldsize) - goFree(old) proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard @@ -573,3 +570,11 @@ when not declared(nimNewSeqOfCap): cast[PGenericSeq](result).reserved = cap {.pop.} + +when not declared(ForeignCell): + type ForeignCell* = object + data*: pointer + + proc protect*(x: pointer): ForeignCell = ForeignCell(data: x) + proc dispose*(x: ForeignCell) = discard + proc isNotForeign*(x: ForeignCell): bool = false diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index f5b9cf3ed..f91dae41e 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -106,7 +106,7 @@ proc cmpic*(a, b: string): int = ## Compares `a` and `b` ignoring case. cmpIgnoreCase(a, b) -proc getEnv*(key: string): string {.tags: [ReadIOEffect].} = +proc getEnv*(key: string; default = ""): string {.tags: [ReadIOEffect].} = ## Retrieves the environment variable of name `key`. builtin diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 65a057772..1ad4cf695 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -78,17 +78,7 @@ when defined(emscripten): munmap(mmapDescr.realPointer, mmapDescr.realSize) elif defined(genode): - - proc osAllocPages(size: int): pointer {. - importcpp: "genodeEnv->rm().attach(genodeEnv->ram().alloc(@))".} - - proc osTryAllocPages(size: int): pointer = - {.emit: """try {""".} - result = osAllocPages size - {.emit: """} catch (...) { }""".} - - proc osDeallocPages(p: pointer, size: int) {. - importcpp: "genodeEnv->rm().detach(#)".} + include genodealloc # osAllocPages, osTryAllocPages, osDeallocPages elif defined(posix): const @@ -166,7 +156,7 @@ elif defined(windows): # space heavily, so we now treat Windows as a strange unmap target. when reallyOsDealloc: if virtualFree(p, 0, MEM_RELEASE) == 0: - cprintf "yes, failing!" + cprintf "virtualFree failing!" quit 1 #VirtualFree(p, size, MEM_DECOMMIT) diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 19fa564fb..982b07467 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -41,14 +41,14 @@ proc `$`(x: uint64): string = let half = i div 2 # Reverse - for t in 0 .. < half: swap(result[t], result[i-t-1]) + for t in 0 .. half-1: swap(result[t], result[i-t-1]) proc reprStrAux(result: var string, s: cstring; len: int) = if cast[pointer](s) == nil: add result, "nil" return add result, reprPointer(cast[pointer](s)) & "\"" - for i in 0.. <len: + for i in 0 .. pred(len): let c = s[i] case c of '"': add result, "\\\"" diff --git a/lib/system/reprjs.nim b/lib/system/reprjs.nim index 5c265a891..658220c11 100644 --- a/lib/system/reprjs.nim +++ b/lib/system/reprjs.nim @@ -9,13 +9,13 @@ # The generic ``repr`` procedure for the javascript backend. proc reprInt(x: int64): string {.compilerproc.} = return $x -proc reprFloat(x: float): string {.compilerproc.} = +proc reprFloat(x: float): string {.compilerproc.} = # Js toString doesn't differentiate between 1.0 and 1, # but we do. if $x == $(x.int): $x & ".0" else: $x -proc reprPointer(p: pointer): string {.compilerproc.} = +proc reprPointer(p: pointer): string {.compilerproc.} = # Do we need to generate the full 8bytes ? In js a pointer is an int anyway var tmp: int {. emit: """ @@ -38,7 +38,7 @@ proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = result = $typ.node.sons[e].name else: result = $e & " (invalid data!)" - + proc reprChar(x: char): string {.compilerRtl.} = result = "\'" case x @@ -50,7 +50,7 @@ proc reprChar(x: char): string {.compilerRtl.} = proc reprStrAux(result: var string, s: cstring, len: int) = add(result, "\"") - for i in 0 .. <len: + for i in 0 .. len-1: let c = s[i] case c of '"': add(result, "\\\"") @@ -67,7 +67,7 @@ proc reprStr(s: string): string {.compilerRtl.} = if cast[pointer](s).isNil: # Handle nil strings here because they don't have a length field in js # TODO: check for null/undefined before generating call to length in js? - # Also: c backend repr of a nil string is <pointer>"", but repr of an + # Also: c backend repr of a nil string is <pointer>"", but repr of an # array of string that is not initialized is [nil, nil, ...] ?? add(result, "nil") else: @@ -86,7 +86,7 @@ proc addSetElem(result: var string, elem: int, typ: PNimType) = iterator setKeys(s: int): int {.inline.} = # The type of s is a lie, but it's expected to be a set. - # Iterate over the JS object representing a set + # Iterate over the JS object representing a set # and returns the keys as int. var len: int var yieldRes: int @@ -124,16 +124,16 @@ proc initReprClosure(cl: var ReprClosure) = cl.recDepth = -1 # default is to display everything! cl.indent = 0 -proc reprAux(result: var string, p: pointer, typ: PNimType, cl: var ReprClosure) +proc reprAux(result: var string, p: pointer, typ: PNimType, cl: var ReprClosure) -proc reprArray(a: pointer, typ: PNimType, +proc reprArray(a: pointer, typ: PNimType, cl: var ReprClosure): string {.compilerRtl.} = var isNilArrayOrSeq: bool # isnil is not enough here as it would try to deref `a` without knowing what's inside {. emit: """ - if (`a` == null) { + if (`a` == null) { `isNilArrayOrSeq` = true; - } else if (`a`[0] == null) { + } else if (`a`[0] == null) { `isNilArrayOrSeq` = true; } else { `isNilArrayOrSeq` = false; @@ -146,19 +146,19 @@ proc reprArray(a: pointer, typ: PNimType, result = if typ.kind == tySequence: "@[" else: "[" var len: int = 0 var i: int = 0 - + {. emit: "`len` = `a`.length;\n" .} var dereffed: pointer = a - for i in 0 .. < len: + for i in 0 .. len-1: if i > 0 : add(result, ", ") # advance pointer and point to element at index {. emit: """ - `dereffed`_Idx = `i`; + `dereffed`_Idx = `i`; `dereffed` = `a`[`dereffed`_Idx]; """ .} reprAux(result, dereffed, typ.base, cl) - + add(result, "]") proc isPointedToNil(p: pointer): bool {.inline.}= @@ -181,7 +181,7 @@ proc reprRef(result: var string, p: pointer, typ: PNimType, proc reprRecordAux(result: var string, o: pointer, typ: PNimType, cl: var ReprClosure) = add(result, "[") - + var first: bool = true var val: pointer = o if typ.node.len == 0: @@ -192,7 +192,7 @@ proc reprRecordAux(result: var string, o: pointer, typ: PNimType, cl: var ReprCl reprAux(result, val, typ.node.typ, cl) else: # if the object has more than one field, sons is not nil and contains the fields. - for i in 0 .. <typ.node.len: + for i in 0 .. typ.node.len-1: if first: first = false else: add(result, ",\n") @@ -214,11 +214,11 @@ proc reprJSONStringify(p: int): string {.compilerRtl.} = {. emit: "`tmp` = JSON.stringify(`p`);\n" .} result = $tmp -proc reprAux(result: var string, p: pointer, typ: PNimType, +proc reprAux(result: var string, p: pointer, typ: PNimType, cl: var ReprClosure) = if cl.recDepth == 0: add(result, "...") - return + return dec(cl.recDepth) case typ.kind of tyInt..tyInt64, tyUInt..tyUInt64: diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 4f266e0ae..4348ffbb5 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -192,7 +192,7 @@ proc write(f: File, r: float32) = proc write(f: File, r: BiggestFloat) = if c_fprintf(f, "%g", r) < 0: checkErr(f) -proc write(f: File, c: char) = discard c_putc(ord(c), f) +proc write(f: File, c: char) = discard c_putc(cint(c), f) proc write(f: File, a: varargs[string, `$`]) = for x in items(a): write(f, x) @@ -404,4 +404,18 @@ proc setStdIoUnbuffered() = when declared(stdin): discard c_setvbuf(stdin, nil, IONBF, 0) +when declared(stdout): + proc echoBinSafe(args: openArray[string]) {.compilerProc.} = + when not defined(windows): + proc flockfile(f: File) {.importc, noDecl.} + proc funlockfile(f: File) {.importc, noDecl.} + flockfile(stdout) + for s in args: + discard c_fwrite(s.cstring, s.len, 1, stdout) + const linefeed = "\n" # can be 1 or more chars + discard c_fwrite(linefeed.cstring, linefeed.len, 1, stdout) + discard c_fflush(stdout) + when not defined(windows): + funlockfile(stdout) + {.pop.} diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim index 7da45b4dd..dc2d13578 100644 --- a/lib/system/sysspawn.nim +++ b/lib/system/sysspawn.nim @@ -142,7 +142,7 @@ var workersData: array[NumThreads, Worker] proc setup() = - for i in 0.. <NumThreads: + for i in 0 ..< NumThreads: workersData[i].taskArrived = createCondVar() workersData[i].taskStarted = createFastCondVar() createThread(workers[i], slave, addr(workersData[i])) @@ -153,12 +153,12 @@ proc preferSpawn*(): bool = ## it is not necessary to call this directly; use 'spawnX' instead. result = gSomeReady.event -proc spawn*(call: stmt) {.magic: "Spawn".} +proc spawn*(call: typed) {.magic: "Spawn".} ## always spawns a new task, so that the 'call' is never executed on ## the calling thread. 'call' has to be proc call 'p(...)' where 'p' ## is gcsafe and has 'void' as the return type. -template spawnX*(call: stmt) = +template spawnX*(call: typed) = ## spawns a new task if a CPU core is ready, otherwise executes the ## call in the calling thread. Usually it is advised to ## use 'spawn' in order to not block the producer for an unknown diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 43b5a0292..4c5f3d9a1 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -24,10 +24,10 @@ proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = if a == b: return 0 if a == nil: return -1 if b == nil: return 1 - when defined(nimNoArrayToCstringConversion): - return c_strcmp(addr a.data, addr b.data) - else: - return c_strcmp(a.data, b.data) + let minlen = min(a.len, b.len) + result = c_memcmp(addr a.data, addr b.data, minlen.csize) + if result == 0: + result = a.len - b.len proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = if a == b: return true @@ -259,7 +259,7 @@ proc incrSeqV2(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = result.reserved = r proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. - compilerRtl.} = + compilerRtl, inl.} = result = seq if result.space < newLen: let r = max(resize(result.space), newLen) @@ -278,14 +278,15 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. GenericSeqSize +% (i*%elemSize)), extGetCellType(result).base, waPush) let len1 = gch.tempStack.len - for i in len0 .. <len1: + for i in len0 ..< len1: doDecRef(gch.tempStack.d[i], LocalHeap, MaybeCyclic) gch.tempStack.len = len0 else: - for i in newLen..result.len-1: - forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% - GenericSeqSize +% (i*%elemSize)), - extGetCellType(result).base, waZctDecRef) + if ntfNoRefs notin extGetCellType(result).base.flags: + for i in newLen..result.len-1: + forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% + GenericSeqSize +% (i*%elemSize)), + extGetCellType(result).base, waZctDecRef) # XXX: zeroing out the memory can still result in crashes if a wiped-out # cell is aliased by another pointer (ie proc parameter or a let variable). @@ -322,36 +323,40 @@ proc nimIntToStr(x: int): string {.compilerRtl.} = result.add x proc add*(result: var string; x: float) = - var buf: array[0..64, char] - when defined(nimNoArrayToCstringConversion): - var n: int = c_sprintf(addr buf, "%.16g", x) + when nimvm: + result.add $x else: - var n: int = c_sprintf(buf, "%.16g", x) - var hasDot = false - for i in 0..n-1: - if buf[i] == ',': - buf[i] = '.' - hasDot = true - elif buf[i] in {'a'..'z', 'A'..'Z', '.'}: - hasDot = true - if not hasDot: - buf[n] = '.' - buf[n+1] = '0' - buf[n+2] = '\0' - # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' are produced. - # We want to get rid of these here: - if buf[n-1] in {'n', 'N'}: - result.add "nan" - elif buf[n-1] == 'F': - if buf[0] == '-': - result.add "-inf" + var buf: array[0..64, char] + when defined(nimNoArrayToCstringConversion): + var n: int = c_sprintf(addr buf, "%.16g", x) else: - result.add "inf" - else: - var i = 0 - while buf[i] != '\0': - result.add buf[i] - inc i + var n: int = c_sprintf(buf, "%.16g", x) + var hasDot = false + for i in 0..n-1: + if buf[i] == ',': + buf[i] = '.' + hasDot = true + elif buf[i] in {'a'..'z', 'A'..'Z', '.'}: + hasDot = true + if not hasDot: + buf[n] = '.' + buf[n+1] = '0' + buf[n+2] = '\0' + # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' + # of '-1.#IND' are produced. + # We want to get rid of these here: + if buf[n-1] in {'n', 'N', 'D', 'd'}: + result.add "nan" + elif buf[n-1] == 'F': + if buf[0] == '-': + result.add "-inf" + else: + result.add "inf" + else: + var i = 0 + while buf[i] != '\0': + result.add buf[i] + inc i proc nimFloatToStr(f: float): string {.compilerproc.} = result = newStringOfCap(8) @@ -362,9 +367,9 @@ proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {. const IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} - powtens = [ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, - 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, - 1e20, 1e21, 1e22] + powtens = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22] proc nimParseBiggestFloat(s: string, number: var BiggestFloat, start = 0): int {.compilerProc.} = @@ -508,7 +513,7 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, # insert exponent t[ti] = 'E'; inc(ti) - t[ti] = if exp_negative: '-' else: '+'; inc(ti) + t[ti] = (if exp_negative: '-' else: '+'); inc(ti) inc(ti, 3) # insert adjusted exponent diff --git a/lib/system/threads.nim b/lib/system/threads.nim index 96c045e6b..f61cc4280 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -255,9 +255,9 @@ when emulatedThreadVars: proc nimThreadVarsSize(): int {.noconv, importc: "NimThreadVarsSize".} # we preallocate a fixed size for thread local storage, so that no heap -# allocations are needed. Currently less than 7K are used on a 64bit machine. +# allocations are needed. Currently less than 16K are used on a 64bit machine. # We use ``float`` for proper alignment: -const nimTlsSize {.intdefine.} = 8000 +const nimTlsSize {.intdefine.} = 16000 type ThreadLocalStorage = array[0..(nimTlsSize div sizeof(float)), float] @@ -398,7 +398,7 @@ template afterThreadRuns() = threadDestructionHandlers[i]() when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcRegions): - proc deallocOsPages() + proc deallocOsPages() {.rtl.} when defined(boehmgc): type GCStackBaseProc = proc(sb: pointer, t: pointer) {.noconv.} diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index c3229cc7b..a833377e5 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -111,6 +111,7 @@ const WAIT_TIMEOUT* = 0x00000102'i32 WAIT_FAILED* = 0xFFFFFFFF'i32 INFINITE* = -1'i32 + STILL_ACTIVE* = 0x00000103'i32 STD_INPUT_HANDLE* = -10'i32 STD_OUTPUT_HANDLE* = -11'i32 @@ -541,6 +542,7 @@ var SO_DONTLINGER* {.importc, header: "winsock2.h".}: cint SO_EXCLUSIVEADDRUSE* {.importc, header: "winsock2.h".}: cint # disallow local address reuse SO_ERROR* {.importc, header: "winsock2.h".}: cint + TCP_NODELAY* {.importc, header: "winsock2.h".}: cint proc `==`*(x, y: SocketHandle): bool {.borrow.} diff --git a/lib/wrappers/libuv.nim b/lib/wrappers/libuv.nim deleted file mode 100644 index 3e28815ad..000000000 --- a/lib/wrappers/libuv.nim +++ /dev/null @@ -1,732 +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. -# - -## libuv is still fast moving target -## This file was last updated against a development HEAD revision of https://github.com/joyent/libuv/ - -## Use the following link to see changes (in uv.h) since then and don't forget to update the information here. -## https://github.com/joyent/libuv/compare/9f6024a6fa9d254527b4b59af724257df870288b...master - -when defined(Windows): - import winlean -else: - import posix - -type - Port* = distinct int16 ## port type - - cssize = int - coff = int - csize = int - - AllocProc* = proc (handle: PHandle, suggested_size: csize): Buf {.cdecl.} - ReadProc* = proc (stream: PStream, nread: cssize, buf: Buf) {.cdecl.} - ReadProc2* = proc (stream: PPipe, nread: cssize, buf: Buf, pending: HandleType) {.cdecl.} - WriteProc* = proc (req: PWrite, status: cint) {.cdecl.} - ConnectProc* = proc (req: PConnect, status: cint) {.cdecl.} - ShutdownProc* = proc (req: PShutdown, status: cint) {.cdecl.} - ConnectionProc* = proc (server: PStream, status: cint) {.cdecl.} - CloseProc* = proc (handle: PHandle) {.cdecl.} - TimerProc* = proc (handle: PTimer, status: cint) {.cdecl.} - AsyncProc* = proc (handle: PAsync, status: cint) {.cdecl.} - PrepareProc* = proc (handle: PPrepare, status: cint) {.cdecl.} - CheckProc* = proc (handle: PCheck, status: cint) {.cdecl.} - IdleProc* = proc (handle: PIdle, status: cint) {.cdecl.} - - PSockAddr* = ptr SockAddr - - GetAddrInfoProc* = proc (handle: PGetAddrInfo, status: cint, res: ptr AddrInfo) - - ExitProc* = proc (a2: PProcess, exit_status: cint, term_signal: cint) - FsProc* = proc (req: PFS) - WorkProc* = proc (req: PWork) - AfterWorkProc* = proc (req: PWork) - - FsEventProc* = proc (handle: PFsEvent, filename: cstring, events: cint, status: cint) - - ErrorCode* {.size: sizeof(cint).} = enum - UNKNOWN = - 1, OK = 0, EOF, EACCESS, EAGAIN, EADDRINUSE, EADDRNOTAVAIL, - EAFNOSUPPORT, EALREADY, EBADF, EBUSY, ECONNABORTED, ECONNREFUSED, - ECONNRESET, EDESTADDRREQ, EFAULT, EHOSTUNREACH, EINTR, EINVAL, EISCONN, - EMFILE, EMSGSIZE, ENETDOWN, ENETUNREACH, ENFILE, ENOBUFS, ENOMEM, ENONET, - ENOPROTOOPT, ENOTCONN, ENOTSOCK, ENOTSUP, ENOENT, EPIPE, EPROTO, - EPROTONOSUPPORT, EPROTOTYPE, ETIMEDOUT, ECHARSET, EAIFAMNOSUPPORT, - EAINONAME, EAISERVICE, EAISOCKTYPE, ESHUTDOWN, EEXIST - - HandleType* {.size: sizeof(cint).} = enum - UNKNOWN_HANDLE = 0, TCP, UDP, NAMED_PIPE, TTY, FILE, TIMER, PREPARE, CHECK, - IDLE, ASYNC, ARES_TASK, ARES_EVENT, PROCESS, FS_EVENT - - ReqType* {.size: sizeof(cint).} = enum - rUNKNOWN_REQ = 0, - rCONNECT, - rACCEPT, - rREAD, - rWRITE, - rSHUTDOWN, - rWAKEUP, - rUDP_SEND, - rFS, - rWORK, - rGETADDRINFO, - rREQ_TYPE_PRIVATE - - Err* {.pure, final, importc: "uv_err_t", header: "uv.h".} = object - code* {.importc: "code".}: ErrorCode - sys_errno* {.importc: "sys_errno_".}: cint - - FsEventType* = enum - evRENAME = 1, - evCHANGE = 2 - - TFsEvent* {.pure, final, importc: "uv_fs_event_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - filename {.importc: "filename".}: cstring - - PFsEvent* = ptr TFsEvent - - FsEvents* {.pure, final, importc: "uv_fs_event_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - filename* {.importc: "filename".}: cstring - - Buf* {.pure, final, importc: "uv_buf_t", header: "uv.h"} = object - base* {.importc: "base".}: cstring - len* {.importc: "len".}: csize - - AnyHandle* {.pure, final, importc: "uv_any_handle", header: "uv.h".} = object - tcp* {.importc: "tcp".}: TTcp - pipe* {.importc: "pipe".}: Pipe - prepare* {.importc: "prepare".}: TPrepare - check* {.importc: "check".}: TCheck - idle* {.importc: "idle".}: TIdle - async* {.importc: "async".}: TAsync - timer* {.importc: "timer".}: TTimer - getaddrinfo* {.importc: "getaddrinfo".}: Getaddrinfo - fs_event* {.importc: "fs_event".}: FsEvents - - AnyReq* {.pure, final, importc: "uv_any_req", header: "uv.h".} = object - req* {.importc: "req".}: Req - write* {.importc: "write".}: Write - connect* {.importc: "connect".}: Connect - shutdown* {.importc: "shutdown".}: Shutdown - fs_req* {.importc: "fs_req".}: Fs - work_req* {.importc: "work_req".}: Work - - ## better import this - uint64 = int64 - - Counters* {.pure, final, importc: "uv_counters_t", header: "uv.h".} = object - eio_init* {.importc: "eio_init".}: uint64 - req_init* {.importc: "req_init".}: uint64 - handle_init* {.importc: "handle_init".}: uint64 - stream_init* {.importc: "stream_init".}: uint64 - tcp_init* {.importc: "tcp_init".}: uint64 - udp_init* {.importc: "udp_init".}: uint64 - pipe_init* {.importc: "pipe_init".}: uint64 - tty_init* {.importc: "tty_init".}: uint64 - prepare_init* {.importc: "prepare_init".}: uint64 - check_init* {.importc: "check_init".}: uint64 - idle_init* {.importc: "idle_init".}: uint64 - async_init* {.importc: "async_init".}: uint64 - timer_init* {.importc: "timer_init".}: uint64 - process_init* {.importc: "process_init".}: uint64 - fs_event_init* {.importc: "fs_event_init".}: uint64 - - Loop* {.pure, final, importc: "uv_loop_t", header: "uv.h".} = object - # ares_handles_* {.importc: "uv_ares_handles_".}: pointer # XXX: This seems to be a private field? - eio_want_poll_notifier* {.importc: "uv_eio_want_poll_notifier".}: TAsync - eio_done_poll_notifier* {.importc: "uv_eio_done_poll_notifier".}: TAsync - eio_poller* {.importc: "uv_eio_poller".}: TIdle - counters* {.importc: "counters".}: Counters - last_err* {.importc: "last_err".}: Err - data* {.importc: "data".}: pointer - - PLoop* = ptr Loop - - Shutdown* {.pure, final, importc: "uv_shutdown_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - handle* {.importc: "handle".}: PStream - cb* {.importc: "cb".}: ShutdownProc - - PShutdown* = ptr Shutdown - - Handle* {.pure, final, importc: "uv_handle_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PHandle* = ptr Handle - - Stream* {.pure, final, importc: "uv_stream_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - alloc_cb* {.importc: "alloc_cb".}: AllocProc - read_cb* {.importc: "read_cb".}: ReadProc - read2_cb* {.importc: "read2_cb".}: ReadProc2 - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - write_queue_size* {.importc: "write_queue_size".}: csize - - PStream* = ptr Stream - - Write* {.pure, final, importc: "uv_write_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - cb* {.importc: "cb".}: WriteProc - send_handle* {.importc: "send_handle".}: PStream - handle* {.importc: "handle".}: PStream - - PWrite* = ptr Write - - TTcp* {.pure, final, importc: "uv_tcp_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - alloc_cb* {.importc: "alloc_cb".}: AllocProc - read_cb* {.importc: "read_cb".}: ReadProc - read2_cb* {.importc: "read2_cb".}: ReadProc2 - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - write_queue_size* {.importc: "write_queue_size".}: csize - - PTcp* = ptr TTcp - - Connect* {.pure, final, importc: "uv_connect_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - cb* {.importc: "cb".}: ConnectProc - handle* {.importc: "handle".}: PStream - - PConnect* = ptr Connect - - UdpFlags* = enum - UDP_IPV6ONLY = 1, UDP_PARTIAL = 2 - - ## XXX: better import this - cunsigned = int - - UdpSendProc* = proc (req: PUdpSend, status: cint) - UdpRecvProc* = proc (handle: PUdp, nread: cssize, buf: Buf, adr: ptr SockAddr, flags: cunsigned) - - TUdp* {.pure, final, importc: "uv_udp_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PUdp* = ptr TUdp - - UdpSend* {.pure, final, importc: "uv_udp_send_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - handle* {.importc: "handle".}: PUdp - cb* {.importc: "cb".}: UdpSendProc - - PUdpSend* = ptr UdpSend - - tTTy* {.pure, final, importc: "uv_tty_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - alloc_cb* {.importc: "alloc_cb".}: AllocProc - read_cb* {.importc: "read_cb".}: ReadProc - read2_cb* {.importc: "read2_cb".}: ReadProc2 - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - write_queue_size* {.importc: "write_queue_size".}: csize - - pTTy* = ptr tTTy - - Pipe* {.pure, final, importc: "uv_pipe_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - alloc_cb* {.importc: "alloc_cb".}: AllocProc - read_cb* {.importc: "read_cb".}: ReadProc - read2_cb* {.importc: "read2_cb".}: ReadProc2 - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - write_queue_size* {.importc: "write_queue_size".}: csize - ipc {.importc: "ipc".}: int - - PPipe* = ptr Pipe - - TPrepare* {.pure, final, importc: "uv_prepare_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PPrepare* = ptr TPrepare - - TCheck* {.pure, final, importc: "uv_check_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PCheck* = ptr TCheck - - TIdle* {.pure, final, importc: "uv_idle_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PIdle* = ptr TIdle - - TAsync* {.pure, final, importc: "uv_async_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PAsync* = ptr TAsync - - TTimer* {.pure, final, importc: "uv_timer_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - - PTimer* = ptr TTimer - - GetAddrInfo* {.pure, final, importc: "uv_getaddrinfo_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - loop* {.importc: "loop".}: PLoop - - PGetAddrInfo* = ptr GetAddrInfo - - ProcessOptions* {.pure, final, importc: "uv_process_options_t", header: "uv.h".} = object - exit_cb* {.importc: "exit_cb".}: ExitProc - file* {.importc: "file".}: cstring - args* {.importc: "args".}: cstringArray - env* {.importc: "env".}: cstringArray - cwd* {.importc: "cwd".}: cstring - windows_verbatim_arguments* {.importc: "windows_verbatim_arguments".}: cint - stdin_stream* {.importc: "stdin_stream".}: PPipe - stdout_stream* {.importc: "stdout_stream".}: PPipe - stderr_stream* {.importc: "stderr_stream".}: PPipe - - PProcessOptions* = ptr ProcessOptions - - TProcess* {.pure, final, importc: "uv_process_t", header: "uv.h".} = object - loop* {.importc: "loop".}: PLoop - typ* {.importc: "type".}: HandleType - close_cb* {.importc: "close_cb".}: CloseProc - data* {.importc: "data".}: pointer - exit_cb* {.importc: "exit_cb".}: ExitProc - pid* {.importc: "pid".}: cint - - PProcess* = ptr TProcess - - Work* {.pure, final, importc: "uv_work_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - loop* {.importc: "loop".}: PLoop - work_cb* {.importc: "work_cb".}: WorkProc - after_work_cb* {.importc: "after_work_cb".}: AfterWorkProc - - PWork* = ptr Work - - FsType* {.size: sizeof(cint).} = enum - FS_UNKNOWN = - 1, FS_CUSTOM, FS_OPEN, FS_CLOSE, FS_READ, FS_WRITE, - FS_SENDFILE, FS_STAT, FS_LSTAT, FS_FSTAT, FS_FTRUNCATE, FS_UTIME, FS_FUTIME, - FS_CHMOD, FS_FCHMOD, FS_FSYNC, FS_FDATASYNC, FS_UNLINK, FS_RMDIR, FS_MKDIR, - FS_RENAME, FS_READDIR, FS_LINK, FS_SYMLINK, FS_READLINK, FS_CHOWN, FS_FCHOWN - - FS* {.pure, final, importc: "uv_fs_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - loop* {.importc: "loop".}: PLoop - fs_type* {.importc: "fs_type".}: FsType - cb* {.importc: "cb".}: FsProc - result* {.importc: "result".}: cssize - fsPtr* {.importc: "ptr".}: pointer - path* {.importc: "path".}: cstring - errorno* {.importc: "errorno".}: cint - - PFS* = ptr FS - - Req* {.pure, final, importc: "uv_req_t", header: "uv.h".} = object - typ* {.importc: "type".}: ReqType - data* {.importc: "data".}: pointer - - PReq* = ptr Req - - AresOptions* {.pure, final, importc: "ares_options", header: "uv.h".} = object - flags* {.importc: "flags".}: int - timeout* {.importc: "timeout".}: int - tries* {.importc: "tries".}: int - ndots* {.importc: "ndots".}: int - udp_port* {.importc: "udp_port".}: Port - tcp_port* {.importc: "tcp_port".}: Port - socket_send_buffer_size* {.importc: "socket_send_buffer_size".}: int - socket_recv_buffer_size* {.importc: "socket_receive_buffer_size".}: int - servers* {.importc: "servers".}: ptr InAddr - nservers* {.importc: "nservers".}: int - domains* {.importc: "domains".}: ptr cstring - ndomains* {.importc: "ndomains".}: int - lookups* {.importc: "lookups".}: cstring - - #XXX: not yet exported - #ares_sock_state_cb sock_state_cb; - #void *sock_state_cb_data; - #struct apattern *sortlist; - #int nsort; - - PAresOptions* = ptr AresOptions - PAresChannel* = pointer -{.deprecated: [THandle: Handle, THandleType: HandleType, TAnyHandle: AnyHandle, - TAnyReq: AnyReq, TPort: Port, TErrorCode: ErrorCode, TReqType: ReqType, - TErr: Err, TFsEventType: FsEventType, - # TFsEvent: FsEvent, # Name conflict if we drop `T` - TFsEvents: FsEvents, TBuf: Buf, TCounters: Counters, TLoop: Loop, - TShutdown: Shutdown, TStream: Stream, TWrite: Write, - # TTcp: Tcp, # Name conflict if we drop `T` - TConnect: Connect, - TUdpFlags: UdpFlags, - # TUdp: Udp, # Name conflict if we drop `T` - TUdpSend: UdpSend, - # tTTy: TTy, # Name conflict if we drop `T` - TPipe: Pipe, - # TPrepare: Prepare, # Name conflict if we drop `T` - # TCheck: Check, # Name conflict if we drop `T` - # TIdle: Idle, # Name conflict if we drop `T` - # TAsync: Async, # Name conflict if we drop `T` - # TTimer: Timer, # Name conflict if we drop `T` - TGetAddrInfo: GetAddrInfo, TProcessOptions: ProcessOptions, - # TProcess: Process, # Name conflict if we drop `T` - TWork: Work, - TFsType: FsType, TFS: FS, TReq: Req, TAresOptions: AresOptions].} - -proc loop_new*(): PLoop{. - importc: "uv_loop_new", header: "uv.h".} - -proc loop_delete*(a2: PLoop){. - importc: "uv_loop_delete", header: "uv.h".} - -proc default_loop*(): PLoop{. - importc: "uv_default_loop", header: "uv.h".} - -proc run*(a2: PLoop): cint{. - importc: "uv_run", header: "uv.h".} - -proc addref*(a2: PLoop){. - importc: "uv_ref", header: "uv.h".} - -proc unref*(a2: PLoop){. - importc: "uv_unref", header: "uv.h".} - -proc update_time*(a2: PLoop){. - importc: "uv_update_time", header: "uv.h".} - -proc now*(a2: PLoop): int64{. - importc: "uv_now", header: "uv.h".} - -proc last_error*(a2: PLoop): Err{. - importc: "uv_last_error", header: "uv.h".} - -proc strerror*(err: Err): cstring{. - importc: "uv_strerror", header: "uv.h".} - -proc err_name*(err: Err): cstring{. - importc: "uv_err_name", header: "uv.h".} - -proc shutdown*(req: PShutdown, handle: PStream, cb: ShutdownProc): cint{. - importc: "uv_shutdown", header: "uv.h".} - -proc is_active*(handle: PHandle): cint{. - importc: "uv_is_active", header: "uv.h".} - -proc close*(handle: PHandle, close_cb: CloseProc){. - importc: "uv_close", header: "uv.h".} - -proc buf_init*(base: cstring, len: csize): Buf{. - importc: "uv_buf_init", header: "uv.h".} - -proc listen*(stream: PStream, backlog: cint, cb: ConnectionProc): cint{. - importc: "uv_listen", header: "uv.h".} - -proc accept*(server: PStream, client: PStream): cint{. - importc: "uv_accept", header: "uv.h".} - -proc read_start*(a2: PStream, alloc_cb: AllocProc, read_cb: ReadProc): cint{. - importc: "uv_read_start", header: "uv.h".} - -proc read_start*(a2: PStream, alloc_cb: AllocProc, read_cb: ReadProc2): cint{. - importc: "uv_read2_start", header: "uv.h".} - -proc read_stop*(a2: PStream): cint{. - importc: "uv_read_stop", header: "uv.h".} - -proc write*(req: PWrite, handle: PStream, bufs: ptr Buf, bufcnt: cint, cb: WriteProc): cint{. - importc: "uv_write", header: "uv.h".} - -proc write*(req: PWrite, handle: PStream, bufs: ptr Buf, bufcnt: cint, send_handle: PStream, cb: WriteProc): cint{. - importc: "uv_write2", header: "uv.h".} - -proc tcp_init*(a2: PLoop, handle: PTcp): cint{. - importc: "uv_tcp_init", header: "uv.h".} - -proc tcp_bind*(handle: PTcp, a3: SockAddrIn): cint{. - importc: "uv_tcp_bind", header: "uv.h".} - -proc tcp_bind6*(handle: PTcp, a3: TSockAddrIn6): cint{. - importc: "uv_tcp_bind6", header: "uv.h".} - -proc tcp_getsockname*(handle: PTcp, name: ptr SockAddr, namelen: var cint): cint{. - importc: "uv_tcp_getsockname", header: "uv.h".} - -proc tcp_getpeername*(handle: PTcp, name: ptr SockAddr, namelen: var cint): cint{. - importc: "uv_tcp_getpeername", header: "uv.h".} - -proc tcp_connect*(req: PConnect, handle: PTcp, address: SockAddrIn, cb: ConnectProc): cint{. - importc: "uv_tcp_connect", header: "uv.h".} - -proc tcp_connect6*(req: PConnect, handle: PTcp, address: TSockAddrIn6, cb: ConnectProc): cint{. - importc: "uv_tcp_connect6", header: "uv.h".} - -proc udp_init*(a2: PLoop, handle: PUdp): cint{. - importc: "uv_udp_init", header: "uv.h".} - -proc udp_bind*(handle: PUdp, adr: SockAddrIn, flags: cunsigned): cint{. - importc: "uv_udp_bind", header: "uv.h".} - -proc udp_bind6*(handle: PUdp, adr: TSockAddrIn6, flags: cunsigned): cint{. - importc: "uv_udp_bind6", header: "uv.h".} - -proc udp_getsockname*(handle: PUdp, name: ptr SockAddr, namelen: var cint): cint{. - importc: "uv_udp_getsockname", header: "uv.h".} - -proc udp_send*(req: PUdpSend, handle: PUdp, bufs: ptr Buf, bufcnt: cint, adr: SockAddrIn, send_cb: UdpSendProc): cint{. - importc: "uv_udp_send", header: "uv.h".} - -proc udp_send6*(req: PUdpSend, handle: PUdp, bufs: ptr Buf, bufcnt: cint, adr: TSockAddrIn6, send_cb: UdpSendProc): cint{. - importc: "uv_udp_send6", header: "uv.h".} - -proc udp_recv_start*(handle: PUdp, alloc_cb: AllocProc, recv_cb: UdpRecvProc): cint{. - importc: "uv_udp_recv_start", header: "uv.h".} - -proc udp_recv_stop*(handle: PUdp): cint{. - importc: "uv_udp_recv_stop", header: "uv.h".} - -proc tty_init*(a2: PLoop, a3: pTTy, fd: File): cint{. - importc: "uv_tty_init", header: "uv.h".} - -proc tty_set_mode*(a2: pTTy, mode: cint): cint{. - importc: "uv_tty_set_mode", header: "uv.h".} - -proc tty_get_winsize*(a2: pTTy, width: var cint, height: var cint): cint{. - importc: "uv_tty_get_winsize", header: "uv.h".} - -proc tty_reset_mode*() {. - importc: "uv_tty_reset_mode", header: "uv.h".} - -proc guess_handle*(file: File): HandleType{. - importc: "uv_guess_handle", header: "uv.h".} - -proc pipe_init*(a2: PLoop, handle: PPipe, ipc: int): cint{. - importc: "uv_pipe_init", header: "uv.h".} - -proc pipe_open*(a2: PPipe, file: File){. - importc: "uv_pipe_open", header: "uv.h".} - -proc pipe_bind*(handle: PPipe, name: cstring): cint{. - importc: "uv_pipe_bind", header: "uv.h".} - -proc pipe_connect*(req: PConnect, handle: PPipe, name: cstring, cb: ConnectProc): cint{. - importc: "uv_pipe_connect", header: "uv.h".} - -proc prepare_init*(a2: PLoop, prepare: PPrepare): cint{. - importc: "uv_prepare_init", header: "uv.h".} - -proc prepare_start*(prepare: PPrepare, cb: PrepareProc): cint{. - importc: "uv_prepare_start", header: "uv.h".} - -proc prepare_stop*(prepare: PPrepare): cint{. - importc: "uv_prepare_stop", header: "uv.h".} - -proc check_init*(a2: PLoop, check: PCheck): cint{. - importc: "uv_check_init", header: "uv.h".} - -proc check_start*(check: PCheck, cb: CheckProc): cint{. - importc: "uv_check_start", header: "uv.h".} - -proc check_stop*(check: PCheck): cint{. - importc: "uv_check_stop", header: "uv.h".} - -proc idle_init*(a2: PLoop, idle: PIdle): cint{. - importc: "uv_idle_init", header: "uv.h".} - -proc idle_start*(idle: PIdle, cb: IdleProc): cint{. - importc: "uv_idle_start", header: "uv.h".} - -proc idle_stop*(idle: PIdle): cint{. - importc: "uv_idle_stop", header: "uv.h".} - -proc async_init*(a2: PLoop, async: PAsync, async_cb: AsyncProc): cint{. - importc: "uv_async_init", header: "uv.h".} - -proc async_send*(async: PAsync): cint{. - importc: "uv_async_send", header: "uv.h".} - -proc timer_init*(a2: PLoop, timer: PTimer): cint{. - importc: "uv_timer_init", header: "uv.h".} - -proc timer_start*(timer: PTimer, cb: TimerProc, timeout: int64, repeat: int64): cint{. - importc: "uv_timer_start", header: "uv.h".} - -proc timer_stop*(timer: PTimer): cint{. - importc: "uv_timer_stop", header: "uv.h".} - -proc timer_again*(timer: PTimer): cint{. - importc: "uv_timer_again", header: "uv.h".} - -proc timer_set_repeat*(timer: PTimer, repeat: int64){. - importc: "uv_timer_set_repeat", header: "uv.h".} - -proc timer_get_repeat*(timer: PTimer): int64{. - importc: "uv_timer_get_repeat", header: "uv.h".} - -proc ares_init_options*(a2: PLoop, channel: PAresChannel, options: PAresOptions, optmask: cint): cint{. - importc: "uv_ares_init_options", header: "uv.h".} - -proc ares_destroy*(a2: PLoop, channel: PAresChannel){. - importc: "uv_ares_destroy", header: "uv.h".} - -proc getaddrinfo*(a2: PLoop, handle: PGetAddrInfo,getaddrinfo_cb: GetAddrInfoProc, node: cstring, service: cstring, hints: ptr AddrInfo): cint{. - importc: "uv_getaddrinfo", header: "uv.h".} - -proc freeaddrinfo*(ai: ptr AddrInfo){. - importc: "uv_freeaddrinfo", header: "uv.h".} - -proc spawn*(a2: PLoop, a3: PProcess, options: ProcessOptions): cint{. - importc: "uv_spawn", header: "uv.h".} - -proc process_kill*(a2: PProcess, signum: cint): cint{. - importc: "uv_process_kill", header: "uv.h".} - -proc queue_work*(loop: PLoop, req: PWork, work_cb: WorkProc, after_work_cb: AfterWorkProc): cint{. - importc: "uv_queue_work", header: "uv.h".} - -proc req_cleanup*(req: PFS){. - importc: "uv_fs_req_cleanup", header: "uv.h".} - -proc close*(loop: PLoop, req: PFS, file: File, cb: FsProc): cint{. - importc: "uv_fs_close", header: "uv.h".} - -proc open*(loop: PLoop, req: PFS, path: cstring, flags: cint, mode: cint, cb: FsProc): cint{. - importc: "uv_fs_open", header: "uv.h".} - -proc read*(loop: PLoop, req: PFS, file: File, buf: pointer, length: csize, offset: coff, cb: FsProc): cint{. - importc: "uv_fs_read", header: "uv.h".} - -proc unlink*(loop: PLoop, req: PFS, path: cstring, cb: FsProc): cint{. - importc: "uv_fs_unlink", header: "uv.h".} - -proc write*(loop: PLoop, req: PFS, file: File, buf: pointer, length: csize, offset: coff, cb: FsProc): cint{. - importc: "uv_fs_write", header: "uv.h".} - -proc mkdir*(loop: PLoop, req: PFS, path: cstring, mode: cint, cb: FsProc): cint{. - importc: "uv_fs_mkdir", header: "uv.h".} - -proc rmdir*(loop: PLoop, req: PFS, path: cstring, cb: FsProc): cint{. - importc: "uv_fs_rmdir", header: "uv.h".} - -proc readdir*(loop: PLoop, req: PFS, path: cstring, flags: cint, cb: FsProc): cint{. - importc: "uv_fs_readdir", header: "uv.h".} - -proc stat*(loop: PLoop, req: PFS, path: cstring, cb: FsProc): cint{. - importc: "uv_fs_stat", header: "uv.h".} - -proc fstat*(loop: PLoop, req: PFS, file: File, cb: FsProc): cint{. - importc: "uv_fs_fstat", header: "uv.h".} - -proc rename*(loop: PLoop, req: PFS, path: cstring, new_path: cstring, cb: FsProc): cint{. - importc: "uv_fs_rename", header: "uv.h".} - -proc fsync*(loop: PLoop, req: PFS, file: File, cb: FsProc): cint{. - importc: "uv_fs_fsync", header: "uv.h".} - -proc fdatasync*(loop: PLoop, req: PFS, file: File, cb: FsProc): cint{. - importc: "uv_fs_fdatasync", header: "uv.h".} - -proc ftruncate*(loop: PLoop, req: PFS, file: File, offset: coff, cb: FsProc): cint{. - importc: "uv_fs_ftruncate", header: "uv.h".} - -proc sendfile*(loop: PLoop, req: PFS, out_fd: File, in_fd: File, in_offset: coff, length: csize, cb: FsProc): cint{. - importc: "uv_fs_sendfile", header: "uv.h".} - -proc chmod*(loop: PLoop, req: PFS, path: cstring, mode: cint, cb: FsProc): cint{. - importc: "uv_fs_chmod", header: "uv.h".} - -proc utime*(loop: PLoop, req: PFS, path: cstring, atime: cdouble, mtime: cdouble, cb: FsProc): cint{. - importc: "uv_fs_utime", header: "uv.h".} - -proc futime*(loop: PLoop, req: PFS, file: File, atime: cdouble, mtime: cdouble, cb: FsProc): cint{. - importc: "uv_fs_futime", header: "uv.h".} - -proc lstat*(loop: PLoop, req: PFS, path: cstring, cb: FsProc): cint{. - importc: "uv_fs_lstat", header: "uv.h".} - -proc link*(loop: PLoop, req: PFS, path: cstring, new_path: cstring, cb: FsProc): cint{. - importc: "uv_fs_link", header: "uv.h".} - -proc symlink*(loop: PLoop, req: PFS, path: cstring, new_path: cstring, flags: cint, cb: FsProc): cint{. - importc: "uv_fs_symlink", header: "uv.h".} - -proc readlink*(loop: PLoop, req: PFS, path: cstring, cb: FsProc): cint{. - importc: "uv_fs_readlink", header: "uv.h".} - -proc fchmod*(loop: PLoop, req: PFS, file: File, mode: cint, cb: FsProc): cint{. - importc: "uv_fs_fchmod", header: "uv.h".} - -proc chown*(loop: PLoop, req: PFS, path: cstring, uid: cint, gid: cint, cb: FsProc): cint{. - importc: "uv_fs_chown", header: "uv.h".} - -proc fchown*(loop: PLoop, req: PFS, file: File, uid: cint, gid: cint, cb: FsProc): cint{. - importc: "uv_fs_fchown", header: "uv.h".} - -proc event_init*(loop: PLoop, handle: PFSEvent, filename: cstring, cb: FsEventProc): cint{. - importc: "uv_fs_event_init", header: "uv.h".} - -proc ip4_addr*(ip: cstring, port: cint): SockAddrIn{. - importc: "uv_ip4_addr", header: "uv.h".} - -proc ip6_addr*(ip: cstring, port: cint): TSockAddrIn6{. - importc: "uv_ip6_addr", header: "uv.h".} - -proc ip4_name*(src: ptr SockAddrIn, dst: cstring, size: csize): cint{. - importc: "uv_ip4_name", header: "uv.h".} - -proc ip6_name*(src: ptr TSockAddrIn6, dst: cstring, size: csize): cint{. - importc: "uv_ip6_name", header: "uv.h".} - -proc exepath*(buffer: cstring, size: var csize): cint{. - importc: "uv_exepath", header: "uv.h".} - -proc hrtime*(): uint64{. - importc: "uv_hrtime", header: "uv.h".} - -proc loadavg*(load: var array[0..2, cdouble]) {. - importc: "uv_loadavg", header: "uv.h"} - -proc get_free_memory*(): cdouble {. - importc: "uv_get_free_memory", header: "uv.h".} - -proc get_total_memory*(): cdouble {. - importc: "uv_get_total_memory", header: "uv.h".} - diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 5cbe2887c..55b0bc3f8 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -64,6 +64,8 @@ type des_key_schedule* = array[1..16, des_ks_struct] + pem_password_cb* = proc(buf: cstring, size, rwflag: cint, userdata: pointer): cint {.cdecl.} + {.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].} const @@ -432,6 +434,12 @@ proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_clear_error".} proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".} proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc: "ERR_remove_state".} +proc PEM_read_bio_RSA_PUBKEY*(bp: BIO, x: ptr PRSA, pw: pem_password_cb, u: pointer): PRSA {.cdecl, + dynlib: DLLSSLName, importc.} + +proc RSA_verify*(kind: cint, origMsg: pointer, origMsgLen: cuint, signature: pointer, + signatureLen: cuint, rsa: PRSA): cint {.cdecl, dynlib: DLLSSLName, importc.} + when true: discard else: @@ -593,7 +601,7 @@ from strutils import toHex, toLowerAscii proc hexStr(buf: cstring): string = # turn md5s output into a nice hex str result = newStringOfCap(32) - for i in 0 .. <16: + for i in 0 ..< 16: result.add toHex(buf[i].ord, 2).toLowerAscii proc md5_File*(file: string): string {.raises: [IOError,Exception].} = diff --git a/lib/wrappers/pdcurses.nim b/lib/wrappers/pdcurses.nim deleted file mode 100644 index 2d64ac97a..000000000 --- a/lib/wrappers/pdcurses.nim +++ /dev/null @@ -1,1560 +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. -# - -{.deadCodeElim: on.} - -discard """ - -curses.h: -#ifdef C2NIM -#dynlib pdcursesdll -#skipinclude -#prefix PDC_ -#def FALSE -#def TRUE -#def NULL -#def bool unsigned char -#def chtype unsigned long -#def cchar_t unsigned long -#def attr_t unsigned long -#def mmask_t unsigned long -#def wchar_t char -#def PDCEX -#cdecl -#endif - -pdcwin.h: -#ifdef C2NIM -#dynlib pdcursesdll -#skipinclude -#prefix pdc_ -#prefix PDC_ -#stdcall -#endif -""" - -when defined(windows): - import windows - - when defined(nimOldDlls): - const pdcursesdll = "pdcurses.dll" - elif defined(cpu64): - const pdcursesdll = "pdcurses64.dll" - else: - const pdcursesdll = "pdcurses32.dll" - - const - unixOS = false - {.pragma: extdecl, stdcall.} - -when not defined(windows): - const - unixOS = true - {.pragma: extdecl, cdecl.} - -type - cunsignedchar = char - cunsignedlong = uint32 - -const - BUILD* = 3401 - PDCURSES* = 1 # PDCurses-only routines - XOPEN* = 1 # X/Open Curses routines - SYSVcurses* = 1 # System V Curses routines - BSDcurses* = 1 # BSD Curses routines - CHTYPE_LONG* = 1 # size of chtype; long - ERR* = (- 1) - OK* = 0 - BUTTON_RELEASED* = 0x00000000 - BUTTON_PRESSED* = 0x00000001 - BUTTON_CLICKED* = 0x00000002 - BUTTON_DOUBLE_CLICKED* = 0x00000003 - BUTTON_TRIPLE_CLICKED* = 0x00000004 - BUTTON_MOVED* = 0x00000005 # PDCurses - WHEEL_SCROLLED* = 0x00000006 # PDCurses - BUTTON_ACTION_MASK* = 0x00000007 # PDCurses - BUTTON_MODIFIER_MASK* = 0x00000038 # PDCurses - MOUSE_MOVED* = 0x00000008 - MOUSE_POSITION* = 0x00000010 - MOUSE_WHEEL_UP* = 0x00000020 - MOUSE_WHEEL_DOWN* = 0x00000040 - BUTTON1_RELEASED* = 0x00000001 - BUTTON1_PRESSED* = 0x00000002 - BUTTON1_CLICKED* = 0x00000004 - BUTTON1_DOUBLE_CLICKED* = 0x00000008 - BUTTON1_TRIPLE_CLICKED* = 0x00000010 - BUTTON1_MOVED* = 0x00000010 # PDCurses - BUTTON2_RELEASED* = 0x00000020 - BUTTON2_PRESSED* = 0x00000040 - BUTTON2_CLICKED* = 0x00000080 - BUTTON2_DOUBLE_CLICKED* = 0x00000100 - BUTTON2_TRIPLE_CLICKED* = 0x00000200 - BUTTON2_MOVED* = 0x00000200 # PDCurses - BUTTON3_RELEASED* = 0x00000400 - BUTTON3_PRESSED* = 0x00000800 - BUTTON3_CLICKED* = 0x00001000 - BUTTON3_DOUBLE_CLICKED* = 0x00002000 - BUTTON3_TRIPLE_CLICKED* = 0x00004000 - BUTTON3_MOVED* = 0x00004000 # PDCurses - BUTTON4_RELEASED* = 0x00008000 - BUTTON4_PRESSED* = 0x00010000 - BUTTON4_CLICKED* = 0x00020000 - BUTTON4_DOUBLE_CLICKED* = 0x00040000 - BUTTON4_TRIPLE_CLICKED* = 0x00080000 - BUTTON5_RELEASED* = 0x00100000 - BUTTON5_PRESSED* = 0x00200000 - BUTTON5_CLICKED* = 0x00400000 - BUTTON5_DOUBLE_CLICKED* = 0x00800000 - BUTTON5_TRIPLE_CLICKED* = 0x01000000 - MOUSE_WHEEL_SCROLL* = 0x02000000 # PDCurses - BUTTON_MODIFIER_SHIFT* = 0x04000000 # PDCurses - BUTTON_MODIFIER_CONTROL* = 0x08000000 # PDCurses - BUTTON_MODIFIER_ALT* = 0x10000000 # PDCurses - ALL_MOUSE_EVENTS* = 0x1FFFFFFF - REPORT_MOUSE_POSITION* = 0x20000000 - A_NORMAL* = 0 - A_ALTCHARSET* = 0x00010000 - A_RIGHTLINE* = 0x00020000 - A_LEFTLINE* = 0x00040000 - A_INVIS* = 0x00080000 - A_UNDERLINE* = 0x00100000 - A_REVERSE* = 0x00200000 - A_BLINK* = 0x00400000 - A_BOLD* = 0x00800000 - A_ATTRIBUTES* = 0xFFFF0000 - A_CHARTEXT* = 0x0000FFFF - A_COLOR* = 0xFF000000 - A_ITALIC* = A_INVIS - A_PROTECT* = (A_UNDERLINE or A_LEFTLINE or A_RIGHTLINE) - ATTR_SHIFT* = 19 - COLOR_SHIFT* = 24 - A_STANDOUT* = (A_REVERSE or A_BOLD) # X/Open - A_DIM* = A_NORMAL - CHR_MSK* = A_CHARTEXT # Obsolete - ATR_MSK* = A_ATTRIBUTES # Obsolete - ATR_NRM* = A_NORMAL # Obsolete - WA_ALTCHARSET* = A_ALTCHARSET - WA_BLINK* = A_BLINK - WA_BOLD* = A_BOLD - WA_DIM* = A_DIM - WA_INVIS* = A_INVIS - WA_LEFT* = A_LEFTLINE - WA_PROTECT* = A_PROTECT - WA_REVERSE* = A_REVERSE - WA_RIGHT* = A_RIGHTLINE - WA_STANDOUT* = A_STANDOUT - WA_UNDERLINE* = A_UNDERLINE - WA_HORIZONTAL* = A_NORMAL - WA_LOW* = A_NORMAL - WA_TOP* = A_NORMAL - WA_VERTICAL* = A_NORMAL - COLOR_BLACK* = 0 - COLOR_RED* = 1 - COLOR_GREEN* = 2 - COLOR_BLUE* = 4 - COLOR_CYAN* = (COLOR_BLUE or COLOR_GREEN) - COLOR_MAGENTA* = (COLOR_RED or COLOR_BLUE) - COLOR_YELLOW* = (COLOR_RED or COLOR_GREEN) - COLOR_WHITE* = 7 - KEY_CODE_YES* = 0x00000100 # If get_wch() gives a key code - KEY_BREAK* = 0x00000101 # Not on PC KBD - KEY_DOWN* = 0x00000102 # Down arrow key - KEY_UP* = 0x00000103 # Up arrow key - KEY_LEFT* = 0x00000104 # Left arrow key - KEY_RIGHT* = 0x00000105 # Right arrow key - KEY_HOME* = 0x00000106 # home key - KEY_BACKSPACE* = 0x00000107 # not on pc - KEY_F0* = 0x00000108 # function keys; 64 reserved - KEY_DL* = 0x00000148 # delete line - KEY_IL* = 0x00000149 # insert line - KEY_DC* = 0x0000014A # delete character - KEY_IC* = 0x0000014B # insert char or enter ins mode - KEY_EIC* = 0x0000014C # exit insert char mode - KEY_CLEAR* = 0x0000014D # clear screen - KEY_EOS* = 0x0000014E # clear to end of screen - KEY_EOL* = 0x0000014F # clear to end of line - KEY_SF* = 0x00000150 # scroll 1 line forward - KEY_SR* = 0x00000151 # scroll 1 line back (reverse) - KEY_NPAGE* = 0x00000152 # next page - KEY_PPAGE* = 0x00000153 # previous page - KEY_STAB* = 0x00000154 # set tab - KEY_CTAB* = 0x00000155 # clear tab - KEY_CATAB* = 0x00000156 # clear all tabs - KEY_ENTER* = 0x00000157 # enter or send (unreliable) - KEY_SRESET* = 0x00000158 # soft/reset (partial/unreliable) - KEY_RESET* = 0x00000159 # reset/hard reset (unreliable) - KEY_PRINT* = 0x0000015A # print/copy - KEY_LL* = 0x0000015B # home down/bottom (lower left) - KEY_ABORT* = 0x0000015C # abort/terminate key (any) - KEY_SHELP* = 0x0000015D # short help - KEY_LHELP* = 0x0000015E # long help - KEY_BTAB* = 0x0000015F # Back tab key - KEY_BEG* = 0x00000160 # beg(inning) key - KEY_CANCEL* = 0x00000161 # cancel key - KEY_CLOSE* = 0x00000162 # close key - KEY_COMMAND* = 0x00000163 # cmd (command) key - KEY_COPY* = 0x00000164 # copy key - KEY_CREATE* = 0x00000165 # create key - KEY_END* = 0x00000166 # end key - KEY_EXIT* = 0x00000167 # exit key - KEY_FIND* = 0x00000168 # find key - KEY_HELP* = 0x00000169 # help key - KEY_MARK* = 0x0000016A # mark key - KEY_MESSAGE* = 0x0000016B # message key - KEY_MOVE* = 0x0000016C # move key - KEY_NEXT* = 0x0000016D # next object key - KEY_OPEN* = 0x0000016E # open key - KEY_OPTIONS* = 0x0000016F # options key - KEY_PREVIOUS* = 0x00000170 # previous object key - KEY_REDO* = 0x00000171 # redo key - KEY_REFERENCE* = 0x00000172 # ref(erence) key - KEY_REFRESH* = 0x00000173 # refresh key - KEY_REPLACE* = 0x00000174 # replace key - KEY_RESTART* = 0x00000175 # restart key - KEY_RESUME* = 0x00000176 # resume key - KEY_SAVE* = 0x00000177 # save key - KEY_SBEG* = 0x00000178 # shifted beginning key - KEY_SCANCEL* = 0x00000179 # shifted cancel key - KEY_SCOMMAND* = 0x0000017A # shifted command key - KEY_SCOPY* = 0x0000017B # shifted copy key - KEY_SCREATE* = 0x0000017C # shifted create key - KEY_SDC* = 0x0000017D # shifted delete char key - KEY_SDL* = 0x0000017E # shifted delete line key - KEY_SELECT* = 0x0000017F # select key - KEY_SEND* = 0x00000180 # shifted end key - KEY_SEOL* = 0x00000181 # shifted clear line key - KEY_SEXIT* = 0x00000182 # shifted exit key - KEY_SFIND* = 0x00000183 # shifted find key - KEY_SHOME* = 0x00000184 # shifted home key - KEY_SIC* = 0x00000185 # shifted input key - KEY_SLEFT* = 0x00000187 # shifted left arrow key - KEY_SMESSAGE* = 0x00000188 # shifted message key - KEY_SMOVE* = 0x00000189 # shifted move key - KEY_SNEXT* = 0x0000018A # shifted next key - KEY_SOPTIONS* = 0x0000018B # shifted options key - KEY_SPREVIOUS* = 0x0000018C # shifted prev key - KEY_SPRINT* = 0x0000018D # shifted print key - KEY_SREDO* = 0x0000018E # shifted redo key - KEY_SREPLACE* = 0x0000018F # shifted replace key - KEY_SRIGHT* = 0x00000190 # shifted right arrow - KEY_SRSUME* = 0x00000191 # shifted resume key - KEY_SSAVE* = 0x00000192 # shifted save key - KEY_SSUSPEND* = 0x00000193 # shifted suspend key - KEY_SUNDO* = 0x00000194 # shifted undo key - KEY_SUSPEND* = 0x00000195 # suspend key - KEY_UNDO* = 0x00000196 # undo key - ALT_0* = 0x00000197 - ALT_1* = 0x00000198 - ALT_2* = 0x00000199 - ALT_3* = 0x0000019A - ALT_4* = 0x0000019B - ALT_5* = 0x0000019C - ALT_6* = 0x0000019D - ALT_7* = 0x0000019E - ALT_8* = 0x0000019F - ALT_9* = 0x000001A0 - ALT_A* = 0x000001A1 - ALT_B* = 0x000001A2 - ALT_C* = 0x000001A3 - ALT_D* = 0x000001A4 - ALT_E* = 0x000001A5 - ALT_F* = 0x000001A6 - ALT_G* = 0x000001A7 - ALT_H* = 0x000001A8 - ALT_I* = 0x000001A9 - ALT_J* = 0x000001AA - ALT_K* = 0x000001AB - ALT_L* = 0x000001AC - ALT_M* = 0x000001AD - ALT_N* = 0x000001AE - ALT_O* = 0x000001AF - ALT_P* = 0x000001B0 - ALT_Q* = 0x000001B1 - ALT_R* = 0x000001B2 - ALT_S* = 0x000001B3 - ALT_T* = 0x000001B4 - ALT_U* = 0x000001B5 - ALT_V* = 0x000001B6 - ALT_W* = 0x000001B7 - ALT_X* = 0x000001B8 - ALT_Y* = 0x000001B9 - ALT_Z* = 0x000001BA - CTL_LEFT* = 0x000001BB # Control-Left-Arrow - CTL_RIGHT* = 0x000001BC - CTL_PGUP* = 0x000001BD - CTL_PGDN* = 0x000001BE - CTL_HOME* = 0x000001BF - CTL_END* = 0x000001C0 - KEY_A1* = 0x000001C1 # upper left on Virtual keypad - KEY_A2* = 0x000001C2 # upper middle on Virt. keypad - KEY_A3* = 0x000001C3 # upper right on Vir. keypad - KEY_B1* = 0x000001C4 # middle left on Virt. keypad - KEY_B2* = 0x000001C5 # center on Virt. keypad - KEY_B3* = 0x000001C6 # middle right on Vir. keypad - KEY_C1* = 0x000001C7 # lower left on Virt. keypad - KEY_C2* = 0x000001C8 # lower middle on Virt. keypad - KEY_C3* = 0x000001C9 # lower right on Vir. keypad - PADSLASH* = 0x000001CA # slash on keypad - PADENTER* = 0x000001CB # enter on keypad - CTL_PADENTER* = 0x000001CC # ctl-enter on keypad - ALT_PADENTER* = 0x000001CD # alt-enter on keypad - PADSTOP* = 0x000001CE # stop on keypad - PADSTAR* = 0x000001CF # star on keypad - PADMINUS* = 0x000001D0 # minus on keypad - PADPLUS* = 0x000001D1 # plus on keypad - CTL_PADSTOP* = 0x000001D2 # ctl-stop on keypad - CTL_PADCENTER* = 0x000001D3 # ctl-enter on keypad - CTL_PADPLUS* = 0x000001D4 # ctl-plus on keypad - CTL_PADMINUS* = 0x000001D5 # ctl-minus on keypad - CTL_PADSLASH* = 0x000001D6 # ctl-slash on keypad - CTL_PADSTAR* = 0x000001D7 # ctl-star on keypad - ALT_PADPLUS* = 0x000001D8 # alt-plus on keypad - ALT_PADMINUS* = 0x000001D9 # alt-minus on keypad - ALT_PADSLASH* = 0x000001DA # alt-slash on keypad - ALT_PADSTAR* = 0x000001DB # alt-star on keypad - ALT_PADSTOP* = 0x000001DC # alt-stop on keypad - CTL_INS* = 0x000001DD # ctl-insert - ALT_DEL* = 0x000001DE # alt-delete - ALT_INS* = 0x000001DF # alt-insert - CTL_UP* = 0x000001E0 # ctl-up arrow - CTL_DOWN* = 0x000001E1 # ctl-down arrow - CTL_TAB* = 0x000001E2 # ctl-tab - ALT_TAB* = 0x000001E3 - ALT_MINUS* = 0x000001E4 - ALT_EQUAL* = 0x000001E5 - ALT_HOME* = 0x000001E6 - ALT_PGUP* = 0x000001E7 - ALT_PGDN* = 0x000001E8 - ALT_END* = 0x000001E9 - ALT_UP* = 0x000001EA # alt-up arrow - ALT_DOWN* = 0x000001EB # alt-down arrow - ALT_RIGHT* = 0x000001EC # alt-right arrow - ALT_LEFT* = 0x000001ED # alt-left arrow - ALT_ENTER* = 0x000001EE # alt-enter - ALT_ESC* = 0x000001EF # alt-escape - ALT_BQUOTE* = 0x000001F0 # alt-back quote - ALT_LBRACKET* = 0x000001F1 # alt-left bracket - ALT_RBRACKET* = 0x000001F2 # alt-right bracket - ALT_SEMICOLON* = 0x000001F3 # alt-semi-colon - ALT_FQUOTE* = 0x000001F4 # alt-forward quote - ALT_COMMA* = 0x000001F5 # alt-comma - ALT_STOP* = 0x000001F6 # alt-stop - ALT_FSLASH* = 0x000001F7 # alt-forward slash - ALT_BKSP* = 0x000001F8 # alt-backspace - CTL_BKSP* = 0x000001F9 # ctl-backspace - PAD0* = 0x000001FA # keypad 0 - CTL_PAD0* = 0x000001FB # ctl-keypad 0 - CTL_PAD1* = 0x000001FC - CTL_PAD2* = 0x000001FD - CTL_PAD3* = 0x000001FE - CTL_PAD4* = 0x000001FF - CTL_PAD5* = 0x00000200 - CTL_PAD6* = 0x00000201 - CTL_PAD7* = 0x00000202 - CTL_PAD8* = 0x00000203 - CTL_PAD9* = 0x00000204 - ALT_PAD0* = 0x00000205 # alt-keypad 0 - ALT_PAD1* = 0x00000206 - ALT_PAD2* = 0x00000207 - ALT_PAD3* = 0x00000208 - ALT_PAD4* = 0x00000209 - ALT_PAD5* = 0x0000020A - ALT_PAD6* = 0x0000020B - ALT_PAD7* = 0x0000020C - ALT_PAD8* = 0x0000020D - ALT_PAD9* = 0x0000020E - CTL_DEL* = 0x0000020F # clt-delete - ALT_BSLASH* = 0x00000210 # alt-back slash - CTL_ENTER* = 0x00000211 # ctl-enter - SHF_PADENTER* = 0x00000212 # shift-enter on keypad - SHF_PADSLASH* = 0x00000213 # shift-slash on keypad - SHF_PADSTAR* = 0x00000214 # shift-star on keypad - SHF_PADPLUS* = 0x00000215 # shift-plus on keypad - SHF_PADMINUS* = 0x00000216 # shift-minus on keypad - SHF_UP* = 0x00000217 # shift-up on keypad - SHF_DOWN* = 0x00000218 # shift-down on keypad - SHF_IC* = 0x00000219 # shift-insert on keypad - SHF_DC* = 0x0000021A # shift-delete on keypad - KEY_MOUSE* = 0x0000021B # "mouse" key - KEY_SHIFT_L* = 0x0000021C # Left-shift - KEY_SHIFT_R* = 0x0000021D # Right-shift - KEY_CONTROL_L* = 0x0000021E # Left-control - KEY_CONTROL_R* = 0x0000021F # Right-control - KEY_ALT_L* = 0x00000220 # Left-alt - KEY_ALT_R* = 0x00000221 # Right-alt - KEY_RESIZE* = 0x00000222 # Window resize - KEY_SUP* = 0x00000223 # Shifted up arrow - KEY_SDOWN* = 0x00000224 # Shifted down arrow - KEY_MIN* = KEY_BREAK # Minimum curses key value - KEY_MAX* = KEY_SDOWN # Maximum curses key - CLIP_SUCCESS* = 0 - CLIP_ACCESS_ERROR* = 1 - CLIP_EMPTY* = 2 - CLIP_MEMORY_ERROR* = 3 - KEY_MODIFIER_SHIFT* = 1 - KEY_MODIFIER_CONTROL* = 2 - KEY_MODIFIER_ALT* = 4 - KEY_MODIFIER_NUMLOCK* = 8 - -when appType == "gui": - const - BUTTON_SHIFT* = BUTTON_MODIFIER_SHIFT - BUTTON_CONTROL* = BUTTON_MODIFIER_CONTROL - BUTTON_CTRL* = BUTTON_MODIFIER_CONTROL - BUTTON_ALT* = BUTTON_MODIFIER_ALT -else: - const - BUTTON_SHIFT* = 0x00000008 - BUTTON_CONTROL* = 0x00000010 - BUTTON_ALT* = 0x00000020 - -type - TMOUSE_STATUS*{.pure, final.} = object - x*: cint # absolute column, 0 based, measured in characters - y*: cint # absolute row, 0 based, measured in characters - button*: array[0..3 - 1, cshort] # state of each button - changes*: cint # flags indicating what has changed with the mouse - - MEVENT*{.pure, final.} = object - id*: cshort # unused, always 0 - x*: cint - y*: cint - z*: cint # x, y same as TMOUSE_STATUS; z unused - bstate*: cunsignedlong # equivalent to changes + button[], but - # in the same format as used for mousemask() - - WINDOW*{.pure, final.} = object - cury*: cint # current pseudo-cursor - curx*: cint - maxy*: cint # max window coordinates - maxx*: cint - begy*: cint # origin on screen - begx*: cint - flags*: cint # window properties - attrs*: cunsignedlong # standard attributes and colors - bkgd*: cunsignedlong # background, normally blank - clear*: cunsignedchar # causes clear at next refresh - leaveit*: cunsignedchar # leaves cursor where it is - scroll*: cunsignedchar # allows window scrolling - nodelay*: cunsignedchar # input character wait flag - immed*: cunsignedchar # immediate update flag - sync*: cunsignedchar # synchronise window ancestors - use_keypad*: cunsignedchar # flags keypad key mode active - y*: ptr ptr cunsignedlong # pointer to line pointer array - firstch*: ptr cint # first changed character in line - lastch*: ptr cint # last changed character in line - tmarg*: cint # top of scrolling region - bmarg*: cint # bottom of scrolling region - delayms*: cint # milliseconds of delay for getch() - parx*: cint - pary*: cint # coords relative to parent (0,0) - parent*: ptr WINDOW # subwin's pointer to parent win - - PANELOBS*{.pure, final.} = object - above*: ptr PANELOBS - pan*: ptr PANEL - - PANEL*{.pure, final.} = object - win*: ptr WINDOW - wstarty*: cint - wendy*: cint - wstartx*: cint - wendx*: cint - below*: ptr PANEL - above*: ptr PANEL - user*: pointer - obscure*: ptr PANELOBS -{.deprecated: [ - #TMOUSE_STATUS: MOUSE_STATUS, # Name conflict when we drop the `T` - TMEVENT: MEVENT, TWINDOW: WINDOW, - TPANELOBS: PANELOBS, TPANEL:PANEL].} - -when unixOS: - type - SCREEN*{.pure, final.} = object - alive*: cunsignedchar # if initscr() called, and not endwin() - autocr*: cunsignedchar # if cr -> lf - cbreak*: cunsignedchar # if terminal unbuffered - echo*: cunsignedchar # if terminal echo - raw_inp*: cunsignedchar # raw input mode (v. cooked input) - raw_out*: cunsignedchar # raw output mode (7 v. 8 bits) - audible*: cunsignedchar # FALSE if the bell is visual - mono*: cunsignedchar # TRUE if current screen is mono - resized*: cunsignedchar # TRUE if TERM has been resized - orig_attr*: cunsignedchar # TRUE if we have the original colors - orig_fore*: cshort # original screen foreground color - orig_back*: cshort # original screen foreground color - cursrow*: cint # position of physical cursor - curscol*: cint # position of physical cursor - visibility*: cint # visibility of cursor - orig_cursor*: cint # original cursor size - lines*: cint # new value for LINES - cols*: cint # new value for COLS - trap_mbe*: cunsignedlong # trap these mouse button events - map_mbe_to_key*: cunsignedlong # map mouse buttons to slk - mouse_wait*: cint # time to wait (in ms) for a button release after a press - slklines*: cint # lines in use by slk_init() - slk_winptr*: ptr WINDOW # window for slk - linesrippedoff*: cint # lines ripped off via ripoffline() - linesrippedoffontop*: cint # lines ripped off on top via ripoffline() - delaytenths*: cint # 1/10ths second to wait block getch() for - preserve*: cunsignedchar # TRUE if screen background to be preserved - restore*: cint # specifies if screen background to be restored, and how - save_key_modifiers*: cunsignedchar # TRUE if each key modifiers saved with each key press - return_key_modifiers*: cunsignedchar # TRUE if modifier keys are returned as "real" keys - key_code*: cunsignedchar # TRUE if last key is a special key; - XcurscrSize*: cint # size of Xcurscr shared memory block - sb_on*: cunsignedchar - sb_viewport_y*: cint - sb_viewport_x*: cint - sb_total_y*: cint - sb_total_x*: cint - sb_cur_y*: cint - sb_cur_x*: cint - line_color*: cshort # color of line attributes - default -1 - {.deprecated: [TSCREEN: SCREEN].} -else: - type - SCREEN*{.pure, final.} = object - alive*: cunsignedchar # if initscr() called, and not endwin() - autocr*: cunsignedchar # if cr -> lf - cbreak*: cunsignedchar # if terminal unbuffered - echo*: cunsignedchar # if terminal echo - raw_inp*: cunsignedchar # raw input mode (v. cooked input) - raw_out*: cunsignedchar # raw output mode (7 v. 8 bits) - audible*: cunsignedchar # FALSE if the bell is visual - mono*: cunsignedchar # TRUE if current screen is mono - resized*: cunsignedchar # TRUE if TERM has been resized - orig_attr*: cunsignedchar # TRUE if we have the original colors - orig_fore*: cshort # original screen foreground color - orig_back*: cshort # original screen foreground color - cursrow*: cint # position of physical cursor - curscol*: cint # position of physical cursor - visibility*: cint # visibility of cursor - orig_cursor*: cint # original cursor size - lines*: cint # new value for LINES - cols*: cint # new value for COLS - trap_mbe*: cunsignedlong # trap these mouse button events - map_mbe_to_key*: cunsignedlong # map mouse buttons to slk - mouse_wait*: cint # time to wait (in ms) for a button release after a press - slklines*: cint # lines in use by slk_init() - slk_winptr*: ptr WINDOW # window for slk - linesrippedoff*: cint # lines ripped off via ripoffline() - linesrippedoffontop*: cint # lines ripped off on top via ripoffline() - delaytenths*: cint # 1/10ths second to wait block getch() for - preserve*: cunsignedchar # TRUE if screen background to be preserved - restore*: cint # specifies if screen background to be restored, and how - save_key_modifiers*: cunsignedchar # TRUE if each key modifiers saved with each key press - return_key_modifiers*: cunsignedchar # TRUE if modifier keys are returned as "real" keys - key_code*: cunsignedchar # TRUE if last key is a special key; - line_color*: cshort # color of line attributes - default -1 - {.deprecated: [TSCREEN: SCREEN].} - -var - LINES*{.importc: "LINES", dynlib: pdcursesdll.}: cint - COLS*{.importc: "COLS", dynlib: pdcursesdll.}: cint - stdscr*{.importc: "stdscr", dynlib: pdcursesdll.}: ptr WINDOW - curscr*{.importc: "curscr", dynlib: pdcursesdll.}: ptr WINDOW - SP*{.importc: "SP", dynlib: pdcursesdll.}: ptr SCREEN - Mouse_status*{.importc: "Mouse_status", dynlib: pdcursesdll.}: MOUSE_STATUS - COLORS*{.importc: "COLORS", dynlib: pdcursesdll.}: cint - COLOR_PAIRS*{.importc: "COLOR_PAIRS", dynlib: pdcursesdll.}: cint - TABSIZE*{.importc: "TABSIZE", dynlib: pdcursesdll.}: cint - acs_map*{.importc: "acs_map", dynlib: pdcursesdll.}: ptr cunsignedlong - ttytype*{.importc: "ttytype", dynlib: pdcursesdll.}: cstring - -template BUTTON_CHANGED*(x: expr): expr = - (Mouse_status.changes and (1 shl ((x) - 1))) - -template BUTTON_STATUS*(x: expr): expr = - (Mouse_status.button[(x) - 1]) - -template ACS_PICK*(w, n: expr): expr = int32(w) or A_ALTCHARSET - -template KEY_F*(n: expr): expr = KEY_F0 + n - -template COLOR_PAIR*(n: expr): expr = - ((cunsignedlong(n) shl COLOR_SHIFT) and A_COLOR) - -template PAIR_NUMBER*(n: expr): expr = - (((n) and A_COLOR) shr COLOR_SHIFT) - -const - #MOUSE_X_POS* = (Mouse_status.x) - #MOUSE_Y_POS* = (Mouse_status.y) - #A_BUTTON_CHANGED* = (Mouse_status.changes and 7) - #MOUSE_MOVED* = (Mouse_status.changes and MOUSE_MOVED) - #MOUSE_POS_REPORT* = (Mouse_status.changes and MOUSE_POSITION) - #MOUSE_WHEEL_UP* = (Mouse_status.changes and MOUSE_WHEEL_UP) - #MOUSE_WHEEL_DOWN* = (Mouse_status.changes and MOUSE_WHEEL_DOWN) - ACS_ULCORNER* = ACS_PICK('l', '+') - ACS_LLCORNER* = ACS_PICK('m', '+') - ACS_URCORNER* = ACS_PICK('k', '+') - ACS_LRCORNER* = ACS_PICK('j', '+') - ACS_RTEE* = ACS_PICK('u', '+') - ACS_LTEE* = ACS_PICK('t', '+') - ACS_BTEE* = ACS_PICK('v', '+') - ACS_TTEE* = ACS_PICK('w', '+') - ACS_HLINE* = ACS_PICK('q', '-') - ACS_VLINE* = ACS_PICK('x', '|') - ACS_PLUS* = ACS_PICK('n', '+') - ACS_S1* = ACS_PICK('o', '-') - ACS_S9* = ACS_PICK('s', '_') - ACS_DIAMOND* = ACS_PICK('`', '+') - ACS_CKBOARD* = ACS_PICK('a', ':') - ACS_DEGREE* = ACS_PICK('f', '\'') - ACS_PLMINUS* = ACS_PICK('g', '#') - ACS_BULLET* = ACS_PICK('~', 'o') - ACS_LARROW* = ACS_PICK(',', '<') - ACS_RARROW* = ACS_PICK('+', '>') - ACS_DARROW* = ACS_PICK('.', 'v') - ACS_UARROW* = ACS_PICK('-', '^') - ACS_BOARD* = ACS_PICK('h', '#') - ACS_LANTERN* = ACS_PICK('i', '*') - ACS_BLOCK* = ACS_PICK('0', '#') - ACS_S3* = ACS_PICK('p', '-') - ACS_S7* = ACS_PICK('r', '-') - ACS_LEQUAL* = ACS_PICK('y', '<') - ACS_GEQUAL* = ACS_PICK('z', '>') - ACS_PI* = ACS_PICK('{', 'n') - ACS_NEQUAL* = ACS_PICK('|', '+') - ACS_STERLING* = ACS_PICK('}', 'L') - ACS_BSSB* = ACS_ULCORNER - ACS_SSBB* = ACS_LLCORNER - ACS_BBSS* = ACS_URCORNER - ACS_SBBS* = ACS_LRCORNER - ACS_SBSS* = ACS_RTEE - ACS_SSSB* = ACS_LTEE - ACS_SSBS* = ACS_BTEE - ACS_BSSS* = ACS_TTEE - ACS_BSBS* = ACS_HLINE - ACS_SBSB* = ACS_VLINE - ACS_SSSS* = ACS_PLUS -discard """WACS_ULCORNER* = (addr((acs_map['l']))) - WACS_LLCORNER* = (addr((acs_map['m']))) - WACS_URCORNER* = (addr((acs_map['k']))) - WACS_LRCORNER* = (addr((acs_map['j']))) - WACS_RTEE* = (addr((acs_map['u']))) - WACS_LTEE* = (addr((acs_map['t']))) - WACS_BTEE* = (addr((acs_map['v']))) - WACS_TTEE* = (addr((acs_map['w']))) - WACS_HLINE* = (addr((acs_map['q']))) - WACS_VLINE* = (addr((acs_map['x']))) - WACS_PLUS* = (addr((acs_map['n']))) - WACS_S1* = (addr((acs_map['o']))) - WACS_S9* = (addr((acs_map['s']))) - WACS_DIAMOND* = (addr((acs_map['`']))) - WACS_CKBOARD* = (addr((acs_map['a']))) - WACS_DEGREE* = (addr((acs_map['f']))) - WACS_PLMINUS* = (addr((acs_map['g']))) - WACS_BULLET* = (addr((acs_map['~']))) - WACS_LARROW* = (addr((acs_map[',']))) - WACS_RARROW* = (addr((acs_map['+']))) - WACS_DARROW* = (addr((acs_map['.']))) - WACS_UARROW* = (addr((acs_map['-']))) - WACS_BOARD* = (addr((acs_map['h']))) - WACS_LANTERN* = (addr((acs_map['i']))) - WACS_BLOCK* = (addr((acs_map['0']))) - WACS_S3* = (addr((acs_map['p']))) - WACS_S7* = (addr((acs_map['r']))) - WACS_LEQUAL* = (addr((acs_map['y']))) - WACS_GEQUAL* = (addr((acs_map['z']))) - WACS_PI* = (addr((acs_map['{']))) - WACS_NEQUAL* = (addr((acs_map['|']))) - WACS_STERLING* = (addr((acs_map['}']))) - WACS_BSSB* = WACS_ULCORNER - WACS_SSBB* = WACS_LLCORNER - WACS_BBSS* = WACS_URCORNER - WACS_SBBS* = WACS_LRCORNER - WACS_SBSS* = WACS_RTEE - WACS_SSSB* = WACS_LTEE - WACS_SSBS* = WACS_BTEE - WACS_BSSS* = WACS_TTEE - WACS_BSBS* = WACS_HLINE - WACS_SBSB* = WACS_VLINE - WACS_SSSS* = WACS_PLUS""" - -proc addch*(a2: cunsignedlong): cint{.extdecl, importc: "addch", - dynlib: pdcursesdll.} -proc addchnstr*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "addchnstr", dynlib: pdcursesdll.} -proc addchstr*(a2: ptr cunsignedlong): cint{.extdecl, importc: "addchstr", - dynlib: pdcursesdll.} -proc addnstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "addnstr", - dynlib: pdcursesdll.} -proc addstr*(a2: cstring): cint{.extdecl, importc: "addstr", dynlib: pdcursesdll.} -proc attroff*(a2: cunsignedlong): cint{.extdecl, importc: "attroff", - dynlib: pdcursesdll.} -proc attron*(a2: cunsignedlong): cint{.extdecl, importc: "attron", - dynlib: pdcursesdll.} -proc attrset*(a2: cunsignedlong): cint{.extdecl, importc: "attrset", - dynlib: pdcursesdll.} -proc attr_get*(a2: ptr cunsignedlong; a3: ptr cshort; a4: pointer): cint{.extdecl, - importc: "attr_get", dynlib: pdcursesdll.} -proc attr_off*(a2: cunsignedlong; a3: pointer): cint{.extdecl, - importc: "attr_off", dynlib: pdcursesdll.} -proc attr_on*(a2: cunsignedlong; a3: pointer): cint{.extdecl, importc: "attr_on", - dynlib: pdcursesdll.} -proc attr_set*(a2: cunsignedlong; a3: cshort; a4: pointer): cint{.extdecl, - importc: "attr_set", dynlib: pdcursesdll.} -proc baudrate*(): cint{.extdecl, importc: "baudrate", dynlib: pdcursesdll.} -proc beep*(): cint{.extdecl, importc: "beep", dynlib: pdcursesdll.} -proc bkgd*(a2: cunsignedlong): cint{.extdecl, importc: "bkgd", dynlib: pdcursesdll.} -proc bkgdset*(a2: cunsignedlong){.extdecl, importc: "bkgdset", dynlib: pdcursesdll.} -proc border*(a2: cunsignedlong; a3: cunsignedlong; a4: cunsignedlong; - a5: cunsignedlong; a6: cunsignedlong; a7: cunsignedlong; - a8: cunsignedlong; a9: cunsignedlong): cint{.extdecl, - importc: "border", dynlib: pdcursesdll.} -proc box*(a2: ptr WINDOW; a3: cunsignedlong; a4: cunsignedlong): cint{.extdecl, - importc: "box", dynlib: pdcursesdll.} -proc can_change_color*(): cunsignedchar{.extdecl, importc: "can_change_color", - dynlib: pdcursesdll.} -proc cbreak*(): cint{.extdecl, importc: "cbreak", dynlib: pdcursesdll.} -proc chgat*(a2: cint; a3: cunsignedlong; a4: cshort; a5: pointer): cint{.extdecl, - importc: "chgat", dynlib: pdcursesdll.} -proc clearok*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "clearok", dynlib: pdcursesdll.} -proc clear*(): cint{.extdecl, importc: "clear", dynlib: pdcursesdll.} -proc clrtobot*(): cint{.extdecl, importc: "clrtobot", dynlib: pdcursesdll.} -proc clrtoeol*(): cint{.extdecl, importc: "clrtoeol", dynlib: pdcursesdll.} -proc color_content*(a2: cshort; a3: ptr cshort; a4: ptr cshort; a5: ptr cshort): cint{. - extdecl, importc: "color_content", dynlib: pdcursesdll.} -proc color_set*(a2: cshort; a3: pointer): cint{.extdecl, importc: "color_set", - dynlib: pdcursesdll.} -proc copywin*(a2: ptr WINDOW; a3: ptr WINDOW; a4: cint; a5: cint; a6: cint; - a7: cint; a8: cint; a9: cint; a10: cint): cint{.extdecl, - importc: "copywin", dynlib: pdcursesdll.} -proc curs_set*(a2: cint): cint{.extdecl, importc: "curs_set", dynlib: pdcursesdll.} -proc def_prog_mode*(): cint{.extdecl, importc: "def_prog_mode", - dynlib: pdcursesdll.} -proc def_shell_mode*(): cint{.extdecl, importc: "def_shell_mode", - dynlib: pdcursesdll.} -proc delay_output*(a2: cint): cint{.extdecl, importc: "delay_output", - dynlib: pdcursesdll.} -proc delch*(): cint{.extdecl, importc: "delch", dynlib: pdcursesdll.} -proc deleteln*(): cint{.extdecl, importc: "deleteln", dynlib: pdcursesdll.} -proc delscreen*(a2: ptr SCREEN){.extdecl, importc: "delscreen", - dynlib: pdcursesdll.} -proc delwin*(a2: ptr WINDOW): cint{.extdecl, importc: "delwin", - dynlib: pdcursesdll.} -proc derwin*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cint): ptr WINDOW{. - extdecl, importc: "derwin", dynlib: pdcursesdll.} -proc doupdate*(): cint{.extdecl, importc: "doupdate", dynlib: pdcursesdll.} -proc dupwin*(a2: ptr WINDOW): ptr WINDOW{.extdecl, importc: "dupwin", - dynlib: pdcursesdll.} -proc echochar*(a2: cunsignedlong): cint{.extdecl, importc: "echochar", - dynlib: pdcursesdll.} -proc echo*(): cint{.extdecl, importc: "echo", dynlib: pdcursesdll.} -proc endwin*(): cint{.extdecl, importc: "endwin", dynlib: pdcursesdll.} -proc erasechar*(): char{.extdecl, importc: "erasechar", dynlib: pdcursesdll.} -proc erase*(): cint{.extdecl, importc: "erase", dynlib: pdcursesdll.} -proc filter*(){.extdecl, importc: "filter", dynlib: pdcursesdll.} -proc flash*(): cint{.extdecl, importc: "flash", dynlib: pdcursesdll.} -proc flushinp*(): cint{.extdecl, importc: "flushinp", dynlib: pdcursesdll.} -proc getbkgd*(a2: ptr WINDOW): cunsignedlong{.extdecl, importc: "getbkgd", - dynlib: pdcursesdll.} -proc getnstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "getnstr", - dynlib: pdcursesdll.} -proc getstr*(a2: cstring): cint{.extdecl, importc: "getstr", dynlib: pdcursesdll.} -proc getwin*(a2: File): ptr WINDOW{.extdecl, importc: "getwin", - dynlib: pdcursesdll.} -proc halfdelay*(a2: cint): cint{.extdecl, importc: "halfdelay", - dynlib: pdcursesdll.} -proc has_colors*(): cunsignedchar{.extdecl, importc: "has_colors", - dynlib: pdcursesdll.} -proc has_ic*(): cunsignedchar{.extdecl, importc: "has_ic", dynlib: pdcursesdll.} -proc has_il*(): cunsignedchar{.extdecl, importc: "has_il", dynlib: pdcursesdll.} -proc hline*(a2: cunsignedlong; a3: cint): cint{.extdecl, importc: "hline", - dynlib: pdcursesdll.} -proc idcok*(a2: ptr WINDOW; a3: cunsignedchar){.extdecl, importc: "idcok", - dynlib: pdcursesdll.} -proc idlok*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, importc: "idlok", - dynlib: pdcursesdll.} -proc immedok*(a2: ptr WINDOW; a3: cunsignedchar){.extdecl, importc: "immedok", - dynlib: pdcursesdll.} -proc inchnstr*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "inchnstr", dynlib: pdcursesdll.} -proc inchstr*(a2: ptr cunsignedlong): cint{.extdecl, importc: "inchstr", - dynlib: pdcursesdll.} -proc inch*(): cunsignedlong{.extdecl, importc: "inch", dynlib: pdcursesdll.} -proc init_color*(a2: cshort; a3: cshort; a4: cshort; a5: cshort): cint{.extdecl, - importc: "init_color", dynlib: pdcursesdll.} -proc init_pair*(a2: cshort; a3: cshort; a4: cshort): cint{.extdecl, - importc: "init_pair", dynlib: pdcursesdll.} -proc initscr*(): ptr WINDOW{.extdecl, importc: "initscr", dynlib: pdcursesdll.} -proc innstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "innstr", - dynlib: pdcursesdll.} -proc insch*(a2: cunsignedlong): cint{.extdecl, importc: "insch", - dynlib: pdcursesdll.} -proc insdelln*(a2: cint): cint{.extdecl, importc: "insdelln", dynlib: pdcursesdll.} -proc insertln*(): cint{.extdecl, importc: "insertln", dynlib: pdcursesdll.} -proc insnstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "insnstr", - dynlib: pdcursesdll.} -proc insstr*(a2: cstring): cint{.extdecl, importc: "insstr", dynlib: pdcursesdll.} -proc instr*(a2: cstring): cint{.extdecl, importc: "instr", dynlib: pdcursesdll.} -proc intrflush*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "intrflush", dynlib: pdcursesdll.} -proc isendwin*(): cunsignedchar{.extdecl, importc: "isendwin", dynlib: pdcursesdll.} -proc is_linetouched*(a2: ptr WINDOW; a3: cint): cunsignedchar{.extdecl, - importc: "is_linetouched", dynlib: pdcursesdll.} -proc is_wintouched*(a2: ptr WINDOW): cunsignedchar{.extdecl, - importc: "is_wintouched", dynlib: pdcursesdll.} -proc keyname*(a2: cint): cstring{.extdecl, importc: "keyname", dynlib: pdcursesdll.} -proc keypad*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, importc: "keypad", - dynlib: pdcursesdll.} -proc killchar*(): char{.extdecl, importc: "killchar", dynlib: pdcursesdll.} -proc leaveok*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "leaveok", dynlib: pdcursesdll.} -proc longname*(): cstring{.extdecl, importc: "longname", dynlib: pdcursesdll.} -proc meta*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, importc: "meta", - dynlib: pdcursesdll.} -proc move*(a2: cint; a3: cint): cint{.extdecl, importc: "move", - dynlib: pdcursesdll.} -proc mvaddch*(a2: cint; a3: cint; a4: cunsignedlong): cint{.extdecl, - importc: "mvaddch", dynlib: pdcursesdll.} -proc mvaddchnstr*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvaddchnstr", dynlib: pdcursesdll.} -proc mvaddchstr*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvaddchstr", dynlib: pdcursesdll.} -proc mvaddnstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvaddnstr", dynlib: pdcursesdll.} -proc mvaddstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvaddstr", dynlib: pdcursesdll.} -proc mvchgat*(a2: cint; a3: cint; a4: cint; a5: cunsignedlong; a6: cshort; - a7: pointer): cint{.extdecl, importc: "mvchgat", dynlib: pdcursesdll.} -proc mvcur*(a2: cint; a3: cint; a4: cint; a5: cint): cint{.extdecl, - importc: "mvcur", dynlib: pdcursesdll.} -proc mvdelch*(a2: cint; a3: cint): cint{.extdecl, importc: "mvdelch", - dynlib: pdcursesdll.} -proc mvderwin*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "mvderwin", dynlib: pdcursesdll.} -proc mvgetch*(a2: cint; a3: cint): cint{.extdecl, importc: "mvgetch", - dynlib: pdcursesdll.} -proc mvgetnstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvgetnstr", dynlib: pdcursesdll.} -proc mvgetstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvgetstr", dynlib: pdcursesdll.} -proc mvhline*(a2: cint; a3: cint; a4: cunsignedlong; a5: cint): cint{.extdecl, - importc: "mvhline", dynlib: pdcursesdll.} -proc mvinch*(a2: cint; a3: cint): cunsignedlong{.extdecl, importc: "mvinch", - dynlib: pdcursesdll.} -proc mvinchnstr*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvinchnstr", dynlib: pdcursesdll.} -proc mvinchstr*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvinchstr", dynlib: pdcursesdll.} -proc mvinnstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvinnstr", dynlib: pdcursesdll.} -proc mvinsch*(a2: cint; a3: cint; a4: cunsignedlong): cint{.extdecl, - importc: "mvinsch", dynlib: pdcursesdll.} -proc mvinsnstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvinsnstr", dynlib: pdcursesdll.} -proc mvinsstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvinsstr", dynlib: pdcursesdll.} -proc mvinstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, importc: "mvinstr", - dynlib: pdcursesdll.} -proc mvprintw*(a2: cint; a3: cint; a4: cstring): cint{.varargs, extdecl, - importc: "mvprintw", dynlib: pdcursesdll.} -proc mvscanw*(a2: cint; a3: cint; a4: cstring): cint{.varargs, extdecl, - importc: "mvscanw", dynlib: pdcursesdll.} -proc mvvline*(a2: cint; a3: cint; a4: cunsignedlong; a5: cint): cint{.extdecl, - importc: "mvvline", dynlib: pdcursesdll.} -proc mvwaddchnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwaddchnstr", - dynlib: pdcursesdll.} -proc mvwaddchstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwaddchstr", dynlib: pdcursesdll.} -proc mvwaddch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong): cint{. - extdecl, importc: "mvwaddch", dynlib: pdcursesdll.} -proc mvwaddnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwaddnstr", dynlib: pdcursesdll.} -proc mvwaddstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.extdecl, - importc: "mvwaddstr", dynlib: pdcursesdll.} -proc mvwchgat*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cunsignedlong; - a7: cshort; a8: pointer): cint{.extdecl, importc: "mvwchgat", - dynlib: pdcursesdll.} -proc mvwdelch*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "mvwdelch", dynlib: pdcursesdll.} -proc mvwgetch*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "mvwgetch", dynlib: pdcursesdll.} -proc mvwgetnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwgetnstr", dynlib: pdcursesdll.} -proc mvwgetstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.extdecl, - importc: "mvwgetstr", dynlib: pdcursesdll.} -proc mvwhline*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong; a6: cint): cint{. - extdecl, importc: "mvwhline", dynlib: pdcursesdll.} -proc mvwinchnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwinchnstr", - dynlib: pdcursesdll.} -proc mvwinchstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwinchstr", dynlib: pdcursesdll.} -proc mvwinch*(a2: ptr WINDOW; a3: cint; a4: cint): cunsignedlong{.extdecl, - importc: "mvwinch", dynlib: pdcursesdll.} -proc mvwinnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwinnstr", dynlib: pdcursesdll.} -proc mvwinsch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong): cint{. - extdecl, importc: "mvwinsch", dynlib: pdcursesdll.} -proc mvwinsnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwinsnstr", dynlib: pdcursesdll.} -proc mvwinsstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.extdecl, - importc: "mvwinsstr", dynlib: pdcursesdll.} -proc mvwinstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.extdecl, - importc: "mvwinstr", dynlib: pdcursesdll.} -proc mvwin*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, importc: "mvwin", - dynlib: pdcursesdll.} -proc mvwprintw*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.varargs, - extdecl, importc: "mvwprintw", dynlib: pdcursesdll.} -proc mvwscanw*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{.varargs, - extdecl, importc: "mvwscanw", dynlib: pdcursesdll.} -proc mvwvline*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong; a6: cint): cint{. - extdecl, importc: "mvwvline", dynlib: pdcursesdll.} -proc napms*(a2: cint): cint{.extdecl, importc: "napms", dynlib: pdcursesdll.} -proc newpad*(a2: cint; a3: cint): ptr WINDOW{.extdecl, importc: "newpad", - dynlib: pdcursesdll.} -proc newterm*(a2: cstring; a3: File; a4: File): ptr SCREEN{.extdecl, - importc: "newterm", dynlib: pdcursesdll.} -proc newwin*(a2: cint; a3: cint; a4: cint; a5: cint): ptr WINDOW{.extdecl, - importc: "newwin", dynlib: pdcursesdll.} -proc nl*(): cint{.extdecl, importc: "nl", dynlib: pdcursesdll.} -proc nocbreak*(): cint{.extdecl, importc: "nocbreak", dynlib: pdcursesdll.} -proc nodelay*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "nodelay", dynlib: pdcursesdll.} -proc noecho*(): cint{.extdecl, importc: "noecho", dynlib: pdcursesdll.} -proc nonl*(): cint{.extdecl, importc: "nonl", dynlib: pdcursesdll.} -proc noqiflush*(){.extdecl, importc: "noqiflush", dynlib: pdcursesdll.} -proc noraw*(): cint{.extdecl, importc: "noraw", dynlib: pdcursesdll.} -proc notimeout*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "notimeout", dynlib: pdcursesdll.} -proc overlay*(a2: ptr WINDOW; a3: ptr WINDOW): cint{.extdecl, importc: "overlay", - dynlib: pdcursesdll.} -proc overwrite*(a2: ptr WINDOW; a3: ptr WINDOW): cint{.extdecl, - importc: "overwrite", dynlib: pdcursesdll.} -proc pair_content*(a2: cshort; a3: ptr cshort; a4: ptr cshort): cint{.extdecl, - importc: "pair_content", dynlib: pdcursesdll.} -proc pechochar*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "pechochar", dynlib: pdcursesdll.} -proc pnoutrefresh*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cint; - a7: cint; a8: cint): cint{.extdecl, importc: "pnoutrefresh", - dynlib: pdcursesdll.} -proc prefresh*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cint; a7: cint; - a8: cint): cint{.extdecl, importc: "prefresh", dynlib: pdcursesdll.} -proc printw*(a2: cstring): cint{.varargs, extdecl, importc: "printw", - dynlib: pdcursesdll.} -proc putwin*(a2: ptr WINDOW; a3: File): cint{.extdecl, importc: "putwin", - dynlib: pdcursesdll.} -proc qiflush*(){.extdecl, importc: "qiflush", dynlib: pdcursesdll.} -proc raw*(): cint{.extdecl, importc: "raw", dynlib: pdcursesdll.} -proc redrawwin*(a2: ptr WINDOW): cint{.extdecl, importc: "redrawwin", - dynlib: pdcursesdll.} -proc refresh*(): cint{.extdecl, importc: "refresh", dynlib: pdcursesdll.} -proc reset_prog_mode*(): cint{.extdecl, importc: "reset_prog_mode", - dynlib: pdcursesdll.} -proc reset_shell_mode*(): cint{.extdecl, importc: "reset_shell_mode", - dynlib: pdcursesdll.} -proc resetty*(): cint{.extdecl, importc: "resetty", dynlib: pdcursesdll.} -#int ripoffline(int, int (*)(WINDOW *, int)); -proc savetty*(): cint{.extdecl, importc: "savetty", dynlib: pdcursesdll.} -proc scanw*(a2: cstring): cint{.varargs, extdecl, importc: "scanw", - dynlib: pdcursesdll.} -proc scr_dump*(a2: cstring): cint{.extdecl, importc: "scr_dump", - dynlib: pdcursesdll.} -proc scr_init*(a2: cstring): cint{.extdecl, importc: "scr_init", - dynlib: pdcursesdll.} -proc scr_restore*(a2: cstring): cint{.extdecl, importc: "scr_restore", - dynlib: pdcursesdll.} -proc scr_set*(a2: cstring): cint{.extdecl, importc: "scr_set", dynlib: pdcursesdll.} -proc scrl*(a2: cint): cint{.extdecl, importc: "scrl", dynlib: pdcursesdll.} -proc scroll*(a2: ptr WINDOW): cint{.extdecl, importc: "scroll", - dynlib: pdcursesdll.} -proc scrollok*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, - importc: "scrollok", dynlib: pdcursesdll.} -proc set_term*(a2: ptr SCREEN): ptr SCREEN{.extdecl, importc: "set_term", - dynlib: pdcursesdll.} -proc setscrreg*(a2: cint; a3: cint): cint{.extdecl, importc: "setscrreg", - dynlib: pdcursesdll.} -proc slk_attroff*(a2: cunsignedlong): cint{.extdecl, importc: "slk_attroff", - dynlib: pdcursesdll.} -proc slk_attr_off*(a2: cunsignedlong; a3: pointer): cint{.extdecl, - importc: "slk_attr_off", dynlib: pdcursesdll.} -proc slk_attron*(a2: cunsignedlong): cint{.extdecl, importc: "slk_attron", - dynlib: pdcursesdll.} -proc slk_attr_on*(a2: cunsignedlong; a3: pointer): cint{.extdecl, - importc: "slk_attr_on", dynlib: pdcursesdll.} -proc slk_attrset*(a2: cunsignedlong): cint{.extdecl, importc: "slk_attrset", - dynlib: pdcursesdll.} -proc slk_attr_set*(a2: cunsignedlong; a3: cshort; a4: pointer): cint{.extdecl, - importc: "slk_attr_set", dynlib: pdcursesdll.} -proc slk_clear*(): cint{.extdecl, importc: "slk_clear", dynlib: pdcursesdll.} -proc slk_color*(a2: cshort): cint{.extdecl, importc: "slk_color", - dynlib: pdcursesdll.} -proc slk_init*(a2: cint): cint{.extdecl, importc: "slk_init", dynlib: pdcursesdll.} -proc slk_label*(a2: cint): cstring{.extdecl, importc: "slk_label", - dynlib: pdcursesdll.} -proc slk_noutrefresh*(): cint{.extdecl, importc: "slk_noutrefresh", - dynlib: pdcursesdll.} -proc slk_refresh*(): cint{.extdecl, importc: "slk_refresh", dynlib: pdcursesdll.} -proc slk_restore*(): cint{.extdecl, importc: "slk_restore", dynlib: pdcursesdll.} -proc slk_set*(a2: cint; a3: cstring; a4: cint): cint{.extdecl, importc: "slk_set", - dynlib: pdcursesdll.} -proc slk_touch*(): cint{.extdecl, importc: "slk_touch", dynlib: pdcursesdll.} -proc standend*(): cint{.extdecl, importc: "standend", dynlib: pdcursesdll.} -proc standout*(): cint{.extdecl, importc: "standout", dynlib: pdcursesdll.} -proc start_color*(): cint{.extdecl, importc: "start_color", dynlib: pdcursesdll.} -proc subpad*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cint): ptr WINDOW{. - extdecl, importc: "subpad", dynlib: pdcursesdll.} -proc subwin*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint; a6: cint): ptr WINDOW{. - extdecl, importc: "subwin", dynlib: pdcursesdll.} -proc syncok*(a2: ptr WINDOW; a3: cunsignedchar): cint{.extdecl, importc: "syncok", - dynlib: pdcursesdll.} -proc termattrs*(): cunsignedlong{.extdecl, importc: "termattrs", - dynlib: pdcursesdll.} -proc termattrs2*(): cunsignedlong{.extdecl, importc: "term_attrs", - dynlib: pdcursesdll.} -proc termname*(): cstring{.extdecl, importc: "termname", dynlib: pdcursesdll.} -proc timeout*(a2: cint){.extdecl, importc: "timeout", dynlib: pdcursesdll.} -proc touchline*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "touchline", dynlib: pdcursesdll.} -proc touchwin*(a2: ptr WINDOW): cint{.extdecl, importc: "touchwin", - dynlib: pdcursesdll.} -proc typeahead*(a2: cint): cint{.extdecl, importc: "typeahead", - dynlib: pdcursesdll.} -proc untouchwin*(a2: ptr WINDOW): cint{.extdecl, importc: "untouchwin", - dynlib: pdcursesdll.} -proc use_env*(a2: cunsignedchar){.extdecl, importc: "use_env", dynlib: pdcursesdll.} -proc vidattr*(a2: cunsignedlong): cint{.extdecl, importc: "vidattr", - dynlib: pdcursesdll.} -proc vid_attr*(a2: cunsignedlong; a3: cshort; a4: pointer): cint{.extdecl, - importc: "vid_attr", dynlib: pdcursesdll.} -#int vidputs(chtype, int (*)(int)); -#int vid_puts(attr_t, short, void *, int (*)(int)); -proc vline*(a2: cunsignedlong; a3: cint): cint{.extdecl, importc: "vline", - dynlib: pdcursesdll.} -proc vwprintw*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, varargs, - importc: "vw_printw", dynlib: pdcursesdll.} -proc vwprintw2*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, varargs, - importc: "vwprintw", dynlib: pdcursesdll.} -proc vwscanw*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, varargs, - importc: "vw_scanw", dynlib: pdcursesdll.} -proc vwscanw2*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, varargs, - importc: "vwscanw", dynlib: pdcursesdll.} -proc waddchnstr*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{.extdecl, - importc: "waddchnstr", dynlib: pdcursesdll.} -proc waddchstr*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "waddchstr", dynlib: pdcursesdll.} -proc waddch*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, importc: "waddch", - dynlib: pdcursesdll.} -proc waddnstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "waddnstr", dynlib: pdcursesdll.} -proc waddstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, importc: "waddstr", - dynlib: pdcursesdll.} -proc wattroff*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "wattroff", dynlib: pdcursesdll.} -proc wattron*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "wattron", dynlib: pdcursesdll.} -proc wattrset*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "wattrset", dynlib: pdcursesdll.} -proc wattr_get*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: ptr cshort; - a5: pointer): cint{.extdecl, importc: "wattr_get", - dynlib: pdcursesdll.} -proc wattr_off*(a2: ptr WINDOW; a3: cunsignedlong; a4: pointer): cint{.extdecl, - importc: "wattr_off", dynlib: pdcursesdll.} -proc wattr_on*(a2: ptr WINDOW; a3: cunsignedlong; a4: pointer): cint{.extdecl, - importc: "wattr_on", dynlib: pdcursesdll.} -proc wattr_set*(a2: ptr WINDOW; a3: cunsignedlong; a4: cshort; a5: pointer): cint{. - extdecl, importc: "wattr_set", dynlib: pdcursesdll.} -proc wbkgdset*(a2: ptr WINDOW; a3: cunsignedlong){.extdecl, importc: "wbkgdset", - dynlib: pdcursesdll.} -proc wbkgd*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, importc: "wbkgd", - dynlib: pdcursesdll.} -proc wborder*(a2: ptr WINDOW; a3: cunsignedlong; a4: cunsignedlong; - a5: cunsignedlong; a6: cunsignedlong; a7: cunsignedlong; - a8: cunsignedlong; a9: cunsignedlong; a10: cunsignedlong): cint{. - extdecl, importc: "wborder", dynlib: pdcursesdll.} -proc wchgat*(a2: ptr WINDOW; a3: cint; a4: cunsignedlong; a5: cshort; - a6: pointer): cint{.extdecl, importc: "wchgat", dynlib: pdcursesdll.} -proc wclear*(a2: ptr WINDOW): cint{.extdecl, importc: "wclear", - dynlib: pdcursesdll.} -proc wclrtobot*(a2: ptr WINDOW): cint{.extdecl, importc: "wclrtobot", - dynlib: pdcursesdll.} -proc wclrtoeol*(a2: ptr WINDOW): cint{.extdecl, importc: "wclrtoeol", - dynlib: pdcursesdll.} -proc wcolor_set*(a2: ptr WINDOW; a3: cshort; a4: pointer): cint{.extdecl, - importc: "wcolor_set", dynlib: pdcursesdll.} -proc wcursyncup*(a2: ptr WINDOW){.extdecl, importc: "wcursyncup", - dynlib: pdcursesdll.} -proc wdelch*(a2: ptr WINDOW): cint{.extdecl, importc: "wdelch", - dynlib: pdcursesdll.} -proc wdeleteln*(a2: ptr WINDOW): cint{.extdecl, importc: "wdeleteln", - dynlib: pdcursesdll.} -proc wechochar*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "wechochar", dynlib: pdcursesdll.} -proc werase*(a2: ptr WINDOW): cint{.extdecl, importc: "werase", - dynlib: pdcursesdll.} -proc wgetch*(a2: ptr WINDOW): cint{.extdecl, importc: "wgetch", - dynlib: pdcursesdll.} -proc wgetnstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "wgetnstr", dynlib: pdcursesdll.} -proc wgetstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, importc: "wgetstr", - dynlib: pdcursesdll.} -proc whline*(a2: ptr WINDOW; a3: cunsignedlong; a4: cint): cint{.extdecl, - importc: "whline", dynlib: pdcursesdll.} -proc winchnstr*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{.extdecl, - importc: "winchnstr", dynlib: pdcursesdll.} -proc winchstr*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "winchstr", dynlib: pdcursesdll.} -proc winch*(a2: ptr WINDOW): cunsignedlong{.extdecl, importc: "winch", - dynlib: pdcursesdll.} -proc winnstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "winnstr", dynlib: pdcursesdll.} -proc winsch*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, importc: "winsch", - dynlib: pdcursesdll.} -proc winsdelln*(a2: ptr WINDOW; a3: cint): cint{.extdecl, importc: "winsdelln", - dynlib: pdcursesdll.} -proc winsertln*(a2: ptr WINDOW): cint{.extdecl, importc: "winsertln", - dynlib: pdcursesdll.} -proc winsnstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "winsnstr", dynlib: pdcursesdll.} -proc winsstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, importc: "winsstr", - dynlib: pdcursesdll.} -proc winstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, importc: "winstr", - dynlib: pdcursesdll.} -proc wmove*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, importc: "wmove", - dynlib: pdcursesdll.} -proc wnoutrefresh*(a2: ptr WINDOW): cint{.extdecl, importc: "wnoutrefresh", - dynlib: pdcursesdll.} -proc wprintw*(a2: ptr WINDOW; a3: cstring): cint{.varargs, extdecl, - importc: "wprintw", dynlib: pdcursesdll.} -proc wredrawln*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "wredrawln", dynlib: pdcursesdll.} -proc wrefresh*(a2: ptr WINDOW): cint{.extdecl, importc: "wrefresh", - dynlib: pdcursesdll.} -proc wscanw*(a2: ptr WINDOW; a3: cstring): cint{.varargs, extdecl, - importc: "wscanw", dynlib: pdcursesdll.} -proc wscrl*(a2: ptr WINDOW; a3: cint): cint{.extdecl, importc: "wscrl", - dynlib: pdcursesdll.} -proc wsetscrreg*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "wsetscrreg", dynlib: pdcursesdll.} -proc wstandend*(a2: ptr WINDOW): cint{.extdecl, importc: "wstandend", - dynlib: pdcursesdll.} -proc wstandout*(a2: ptr WINDOW): cint{.extdecl, importc: "wstandout", - dynlib: pdcursesdll.} -proc wsyncdown*(a2: ptr WINDOW){.extdecl, importc: "wsyncdown", - dynlib: pdcursesdll.} -proc wsyncup*(a2: ptr WINDOW){.extdecl, importc: "wsyncup", dynlib: pdcursesdll.} -proc wtimeout*(a2: ptr WINDOW; a3: cint){.extdecl, importc: "wtimeout", - dynlib: pdcursesdll.} -proc wtouchln*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cint): cint{.extdecl, - importc: "wtouchln", dynlib: pdcursesdll.} -proc wvline*(a2: ptr WINDOW; a3: cunsignedlong; a4: cint): cint{.extdecl, - importc: "wvline", dynlib: pdcursesdll.} -proc addnwstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "addnwstr", - dynlib: pdcursesdll.} -proc addwstr*(a2: cstring): cint{.extdecl, importc: "addwstr", - dynlib: pdcursesdll.} -proc add_wch*(a2: ptr cunsignedlong): cint{.extdecl, importc: "add_wch", - dynlib: pdcursesdll.} -proc add_wchnstr*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "add_wchnstr", dynlib: pdcursesdll.} -proc add_wchstr*(a2: ptr cunsignedlong): cint{.extdecl, importc: "add_wchstr", - dynlib: pdcursesdll.} -proc border_set*(a2: ptr cunsignedlong; a3: ptr cunsignedlong; - a4: ptr cunsignedlong; a5: ptr cunsignedlong; - a6: ptr cunsignedlong; a7: ptr cunsignedlong; - a8: ptr cunsignedlong; a9: ptr cunsignedlong): cint{.extdecl, - importc: "border_set", dynlib: pdcursesdll.} -proc box_set*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: ptr cunsignedlong): cint{. - extdecl, importc: "box_set", dynlib: pdcursesdll.} -proc echo_wchar*(a2: ptr cunsignedlong): cint{.extdecl, importc: "echo_wchar", - dynlib: pdcursesdll.} -proc erasewchar*(a2: cstring): cint{.extdecl, importc: "erasewchar", - dynlib: pdcursesdll.} -proc getbkgrnd*(a2: ptr cunsignedlong): cint{.extdecl, importc: "getbkgrnd", - dynlib: pdcursesdll.} -proc getcchar*(a2: ptr cunsignedlong; a3: cstring; a4: ptr cunsignedlong; - a5: ptr cshort; a6: pointer): cint{.extdecl, importc: "getcchar", - dynlib: pdcursesdll.} -proc getn_wstr*(a2: ptr cint; a3: cint): cint{.extdecl, importc: "getn_wstr", - dynlib: pdcursesdll.} -proc get_wch*(a2: ptr cint): cint{.extdecl, importc: "get_wch", - dynlib: pdcursesdll.} -proc get_wstr*(a2: ptr cint): cint{.extdecl, importc: "get_wstr", - dynlib: pdcursesdll.} -proc hline_set*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "hline_set", dynlib: pdcursesdll.} -proc innwstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "innwstr", - dynlib: pdcursesdll.} -proc ins_nwstr*(a2: cstring; a3: cint): cint{.extdecl, importc: "ins_nwstr", - dynlib: pdcursesdll.} -proc ins_wch*(a2: ptr cunsignedlong): cint{.extdecl, importc: "ins_wch", - dynlib: pdcursesdll.} -proc ins_wstr*(a2: cstring): cint{.extdecl, importc: "ins_wstr", - dynlib: pdcursesdll.} -proc inwstr*(a2: cstring): cint{.extdecl, importc: "inwstr", - dynlib: pdcursesdll.} -proc in_wch*(a2: ptr cunsignedlong): cint{.extdecl, importc: "in_wch", - dynlib: pdcursesdll.} -proc in_wchnstr*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "in_wchnstr", dynlib: pdcursesdll.} -proc in_wchstr*(a2: ptr cunsignedlong): cint{.extdecl, importc: "in_wchstr", - dynlib: pdcursesdll.} -proc key_name*(a2: char): cstring{.extdecl, importc: "key_name", - dynlib: pdcursesdll.} -proc killwchar*(a2: cstring): cint{.extdecl, importc: "killwchar", - dynlib: pdcursesdll.} -proc mvaddnwstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvaddnwstr", dynlib: pdcursesdll.} -proc mvaddwstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvaddwstr", dynlib: pdcursesdll.} -proc mvadd_wch*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvadd_wch", dynlib: pdcursesdll.} -proc mvadd_wchnstr*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvadd_wchnstr", dynlib: pdcursesdll.} -proc mvadd_wchstr*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvadd_wchstr", dynlib: pdcursesdll.} -proc mvgetn_wstr*(a2: cint; a3: cint; a4: ptr cint; a5: cint): cint{.extdecl, - importc: "mvgetn_wstr", dynlib: pdcursesdll.} -proc mvget_wch*(a2: cint; a3: cint; a4: ptr cint): cint{.extdecl, - importc: "mvget_wch", dynlib: pdcursesdll.} -proc mvget_wstr*(a2: cint; a3: cint; a4: ptr cint): cint{.extdecl, - importc: "mvget_wstr", dynlib: pdcursesdll.} -proc mvhline_set*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvhline_set", dynlib: pdcursesdll.} -proc mvinnwstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvinnwstr", dynlib: pdcursesdll.} -proc mvins_nwstr*(a2: cint; a3: cint; a4: cstring; a5: cint): cint{.extdecl, - importc: "mvins_nwstr", dynlib: pdcursesdll.} -proc mvins_wch*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvins_wch", dynlib: pdcursesdll.} -proc mvins_wstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvins_wstr", dynlib: pdcursesdll.} -proc mvinwstr*(a2: cint; a3: cint; a4: cstring): cint{.extdecl, - importc: "mvinwstr", dynlib: pdcursesdll.} -proc mvin_wch*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvin_wch", dynlib: pdcursesdll.} -proc mvin_wchnstr*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvin_wchnstr", dynlib: pdcursesdll.} -proc mvin_wchstr*(a2: cint; a3: cint; a4: ptr cunsignedlong): cint{.extdecl, - importc: "mvin_wchstr", dynlib: pdcursesdll.} -proc mvvline_set*(a2: cint; a3: cint; a4: ptr cunsignedlong; a5: cint): cint{. - extdecl, importc: "mvvline_set", dynlib: pdcursesdll.} -proc mvwaddnwstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwaddnwstr", dynlib: pdcursesdll.} -proc mvwaddwstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{. - extdecl, importc: "mvwaddwstr", dynlib: pdcursesdll.} -proc mvwadd_wch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwadd_wch", dynlib: pdcursesdll.} -proc mvwadd_wchnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwadd_wchnstr", - dynlib: pdcursesdll.} -proc mvwadd_wchstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwadd_wchstr", dynlib: pdcursesdll.} -proc mvwgetn_wstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cint; a6: cint): cint{. - extdecl, importc: "mvwgetn_wstr", dynlib: pdcursesdll.} -proc mvwget_wch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cint): cint{. - extdecl, importc: "mvwget_wch", dynlib: pdcursesdll.} -proc mvwget_wstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cint): cint{. - extdecl, importc: "mvwget_wstr", dynlib: pdcursesdll.} -proc mvwhline_set*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwhline_set", - dynlib: pdcursesdll.} -proc mvwinnwstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwinnwstr", dynlib: pdcursesdll.} -proc mvwins_nwstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring; a6: cint): cint{. - extdecl, importc: "mvwins_nwstr", dynlib: pdcursesdll.} -proc mvwins_wch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwins_wch", dynlib: pdcursesdll.} -proc mvwins_wstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{. - extdecl, importc: "mvwins_wstr", dynlib: pdcursesdll.} -proc mvwin_wch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwin_wch", dynlib: pdcursesdll.} -proc mvwin_wchnstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwin_wchnstr", - dynlib: pdcursesdll.} -proc mvwin_wchstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong): cint{. - extdecl, importc: "mvwin_wchstr", dynlib: pdcursesdll.} -proc mvwinwstr*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cstring): cint{. - extdecl, importc: "mvwinwstr", dynlib: pdcursesdll.} -proc mvwvline_set*(a2: ptr WINDOW; a3: cint; a4: cint; a5: ptr cunsignedlong; - a6: cint): cint{.extdecl, importc: "mvwvline_set", - dynlib: pdcursesdll.} -proc pecho_wchar*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "pecho_wchar", dynlib: pdcursesdll.} -proc setcchar*(a2: ptr cunsignedlong; a3: cstring; a4: cunsignedlong; - a5: cshort; a6: pointer): cint{.extdecl, importc: "setcchar", - dynlib: pdcursesdll.} -proc slk_wset*(a2: cint; a3: cstring; a4: cint): cint{.extdecl, - importc: "slk_wset", dynlib: pdcursesdll.} -proc unget_wch*(a2: char): cint{.extdecl, importc: "unget_wch", - dynlib: pdcursesdll.} -proc vline_set*(a2: ptr cunsignedlong; a3: cint): cint{.extdecl, - importc: "vline_set", dynlib: pdcursesdll.} -proc waddnwstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "waddnwstr", dynlib: pdcursesdll.} -proc waddwstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, - importc: "waddwstr", dynlib: pdcursesdll.} -proc wadd_wch*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wadd_wch", dynlib: pdcursesdll.} -proc wadd_wchnstr*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{. - extdecl, importc: "wadd_wchnstr", dynlib: pdcursesdll.} -proc wadd_wchstr*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wadd_wchstr", dynlib: pdcursesdll.} -proc wbkgrnd*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wbkgrnd", dynlib: pdcursesdll.} -proc wbkgrndset*(a2: ptr WINDOW; a3: ptr cunsignedlong){.extdecl, - importc: "wbkgrndset", dynlib: pdcursesdll.} -proc wborder_set*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: ptr cunsignedlong; - a5: ptr cunsignedlong; a6: ptr cunsignedlong; - a7: ptr cunsignedlong; a8: ptr cunsignedlong; - a9: ptr cunsignedlong; a10: ptr cunsignedlong): cint{.extdecl, - importc: "wborder_set", dynlib: pdcursesdll.} -proc wecho_wchar*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wecho_wchar", dynlib: pdcursesdll.} -proc wgetbkgrnd*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wgetbkgrnd", dynlib: pdcursesdll.} -proc wgetn_wstr*(a2: ptr WINDOW; a3: ptr cint; a4: cint): cint{.extdecl, - importc: "wgetn_wstr", dynlib: pdcursesdll.} -proc wget_wch*(a2: ptr WINDOW; a3: ptr cint): cint{.extdecl, - importc: "wget_wch", dynlib: pdcursesdll.} -proc wget_wstr*(a2: ptr WINDOW; a3: ptr cint): cint{.extdecl, - importc: "wget_wstr", dynlib: pdcursesdll.} -proc whline_set*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{.extdecl, - importc: "whline_set", dynlib: pdcursesdll.} -proc winnwstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "winnwstr", dynlib: pdcursesdll.} -proc wins_nwstr*(a2: ptr WINDOW; a3: cstring; a4: cint): cint{.extdecl, - importc: "wins_nwstr", dynlib: pdcursesdll.} -proc wins_wch*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "wins_wch", dynlib: pdcursesdll.} -proc wins_wstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, - importc: "wins_wstr", dynlib: pdcursesdll.} -proc winwstr*(a2: ptr WINDOW; a3: cstring): cint{.extdecl, importc: "winwstr", - dynlib: pdcursesdll.} -proc win_wch*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "win_wch", dynlib: pdcursesdll.} -proc win_wchnstr*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{.extdecl, - importc: "win_wchnstr", dynlib: pdcursesdll.} -proc win_wchstr*(a2: ptr WINDOW; a3: ptr cunsignedlong): cint{.extdecl, - importc: "win_wchstr", dynlib: pdcursesdll.} -proc wunctrl*(a2: ptr cunsignedlong): cstring{.extdecl, importc: "wunctrl", - dynlib: pdcursesdll.} -proc wvline_set*(a2: ptr WINDOW; a3: ptr cunsignedlong; a4: cint): cint{.extdecl, - importc: "wvline_set", dynlib: pdcursesdll.} -proc getattrs*(a2: ptr WINDOW): cunsignedlong{.extdecl, importc: "getattrs", - dynlib: pdcursesdll.} -proc getbegx*(a2: ptr WINDOW): cint{.extdecl, importc: "getbegx", - dynlib: pdcursesdll.} -proc getbegy*(a2: ptr WINDOW): cint{.extdecl, importc: "getbegy", - dynlib: pdcursesdll.} -proc getmaxx*(a2: ptr WINDOW): cint{.extdecl, importc: "getmaxx", - dynlib: pdcursesdll.} -proc getmaxy*(a2: ptr WINDOW): cint{.extdecl, importc: "getmaxy", - dynlib: pdcursesdll.} -proc getparx*(a2: ptr WINDOW): cint{.extdecl, importc: "getparx", - dynlib: pdcursesdll.} -proc getpary*(a2: ptr WINDOW): cint{.extdecl, importc: "getpary", - dynlib: pdcursesdll.} -proc getcurx*(a2: ptr WINDOW): cint{.extdecl, importc: "getcurx", - dynlib: pdcursesdll.} -proc getcury*(a2: ptr WINDOW): cint{.extdecl, importc: "getcury", - dynlib: pdcursesdll.} -proc traceoff*(){.extdecl, importc: "traceoff", dynlib: pdcursesdll.} -proc traceon*(){.extdecl, importc: "traceon", dynlib: pdcursesdll.} -proc unctrl*(a2: cunsignedlong): cstring{.extdecl, importc: "unctrl", - dynlib: pdcursesdll.} -proc crmode*(): cint{.extdecl, importc: "crmode", dynlib: pdcursesdll.} -proc nocrmode*(): cint{.extdecl, importc: "nocrmode", dynlib: pdcursesdll.} -proc draino*(a2: cint): cint{.extdecl, importc: "draino", dynlib: pdcursesdll.} -proc resetterm*(): cint{.extdecl, importc: "resetterm", dynlib: pdcursesdll.} -proc fixterm*(): cint{.extdecl, importc: "fixterm", dynlib: pdcursesdll.} -proc saveterm*(): cint{.extdecl, importc: "saveterm", dynlib: pdcursesdll.} -proc setsyx*(a2: cint; a3: cint): cint{.extdecl, importc: "setsyx", - dynlib: pdcursesdll.} -proc mouse_set*(a2: cunsignedlong): cint{.extdecl, importc: "mouse_set", - dynlib: pdcursesdll.} -proc mouse_on*(a2: cunsignedlong): cint{.extdecl, importc: "mouse_on", - dynlib: pdcursesdll.} -proc mouse_off*(a2: cunsignedlong): cint{.extdecl, importc: "mouse_off", - dynlib: pdcursesdll.} -proc request_mouse_pos*(): cint{.extdecl, importc: "request_mouse_pos", - dynlib: pdcursesdll.} -proc map_button*(a2: cunsignedlong): cint{.extdecl, importc: "map_button", - dynlib: pdcursesdll.} -proc wmouse_position*(a2: ptr WINDOW; a3: ptr cint; a4: ptr cint){.extdecl, - importc: "wmouse_position", dynlib: pdcursesdll.} -proc getmouse*(): cunsignedlong{.extdecl, importc: "getmouse", dynlib: pdcursesdll.} -proc getbmap*(): cunsignedlong{.extdecl, importc: "getbmap", dynlib: pdcursesdll.} -proc assume_default_colors*(a2: cint; a3: cint): cint{.extdecl, - importc: "assume_default_colors", dynlib: pdcursesdll.} -proc curses_version*(): cstring{.extdecl, importc: "curses_version", - dynlib: pdcursesdll.} -proc has_key*(a2: cint): cunsignedchar{.extdecl, importc: "has_key", - dynlib: pdcursesdll.} -proc use_default_colors*(): cint{.extdecl, importc: "use_default_colors", - dynlib: pdcursesdll.} -proc wresize*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "wresize", dynlib: pdcursesdll.} -proc mouseinterval*(a2: cint): cint{.extdecl, importc: "mouseinterval", - dynlib: pdcursesdll.} -proc mousemask*(a2: cunsignedlong; a3: ptr cunsignedlong): cunsignedlong{.extdecl, - importc: "mousemask", dynlib: pdcursesdll.} -proc mouse_trafo*(a2: ptr cint; a3: ptr cint; a4: cunsignedchar): cunsignedchar{. - extdecl, importc: "mouse_trafo", dynlib: pdcursesdll.} -proc nc_getmouse*(a2: ptr MEVENT): cint{.extdecl, importc: "nc_getmouse", - dynlib: pdcursesdll.} -proc ungetmouse*(a2: ptr MEVENT): cint{.extdecl, importc: "ungetmouse", - dynlib: pdcursesdll.} -proc wenclose*(a2: ptr WINDOW; a3: cint; a4: cint): cunsignedchar{.extdecl, - importc: "wenclose", dynlib: pdcursesdll.} -proc wmouse_trafo*(a2: ptr WINDOW; a3: ptr cint; a4: ptr cint; a5: cunsignedchar): cunsignedchar{. - extdecl, importc: "wmouse_trafo", dynlib: pdcursesdll.} -proc addrawch*(a2: cunsignedlong): cint{.extdecl, importc: "addrawch", - dynlib: pdcursesdll.} -proc insrawch*(a2: cunsignedlong): cint{.extdecl, importc: "insrawch", - dynlib: pdcursesdll.} -proc is_termresized*(): cunsignedchar{.extdecl, importc: "is_termresized", - dynlib: pdcursesdll.} -proc mvaddrawch*(a2: cint; a3: cint; a4: cunsignedlong): cint{.extdecl, - importc: "mvaddrawch", dynlib: pdcursesdll.} -proc mvdeleteln*(a2: cint; a3: cint): cint{.extdecl, importc: "mvdeleteln", - dynlib: pdcursesdll.} -proc mvinsertln*(a2: cint; a3: cint): cint{.extdecl, importc: "mvinsertln", - dynlib: pdcursesdll.} -proc mvinsrawch*(a2: cint; a3: cint; a4: cunsignedlong): cint{.extdecl, - importc: "mvinsrawch", dynlib: pdcursesdll.} -proc mvwaddrawch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong): cint{. - extdecl, importc: "mvwaddrawch", dynlib: pdcursesdll.} -proc mvwdeleteln*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "mvwdeleteln", dynlib: pdcursesdll.} -proc mvwinsertln*(a2: ptr WINDOW; a3: cint; a4: cint): cint{.extdecl, - importc: "mvwinsertln", dynlib: pdcursesdll.} -proc mvwinsrawch*(a2: ptr WINDOW; a3: cint; a4: cint; a5: cunsignedlong): cint{. - extdecl, importc: "mvwinsrawch", dynlib: pdcursesdll.} -proc raw_output*(a2: cunsignedchar): cint{.extdecl, importc: "raw_output", - dynlib: pdcursesdll.} -proc resize_term*(a2: cint; a3: cint): cint{.extdecl, importc: "resize_term", - dynlib: pdcursesdll.} -proc resize_window*(a2: ptr WINDOW; a3: cint; a4: cint): ptr WINDOW{.extdecl, - importc: "resize_window", dynlib: pdcursesdll.} -proc waddrawch*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "waddrawch", dynlib: pdcursesdll.} -proc winsrawch*(a2: ptr WINDOW; a3: cunsignedlong): cint{.extdecl, - importc: "winsrawch", dynlib: pdcursesdll.} -proc wordchar*(): char{.extdecl, importc: "wordchar", dynlib: pdcursesdll.} -proc slk_wlabel*(a2: cint): cstring{.extdecl, importc: "slk_wlabel", - dynlib: pdcursesdll.} -proc debug*(a2: cstring){.varargs, extdecl, importc: "PDC_debug", - dynlib: pdcursesdll.} -proc ungetch*(a2: cint): cint{.extdecl, importc: "PDC_ungetch", - dynlib: pdcursesdll.} -proc set_blink*(a2: cunsignedchar): cint{.extdecl, importc: "PDC_set_blink", - dynlib: pdcursesdll.} -proc set_line_color*(a2: cshort): cint{.extdecl, importc: "PDC_set_line_color", - dynlib: pdcursesdll.} -proc set_title*(a2: cstring){.extdecl, importc: "PDC_set_title", - dynlib: pdcursesdll.} -proc clearclipboard*(): cint{.extdecl, importc: "PDC_clearclipboard", - dynlib: pdcursesdll.} -proc freeclipboard*(a2: cstring): cint{.extdecl, importc: "PDC_freeclipboard", - dynlib: pdcursesdll.} -proc getclipboard*(a2: cstringArray; a3: ptr clong): cint{.extdecl, - importc: "PDC_getclipboard", dynlib: pdcursesdll.} -proc setclipboard*(a2: cstring; a3: clong): cint{.extdecl, - importc: "PDC_setclipboard", dynlib: pdcursesdll.} -proc get_input_fd*(): cunsignedlong{.extdecl, importc: "PDC_get_input_fd", - dynlib: pdcursesdll.} -proc get_key_modifiers*(): cunsignedlong{.extdecl, - importc: "PDC_get_key_modifiers", dynlib: pdcursesdll.} -proc return_key_modifiers*(a2: cunsignedchar): cint{.extdecl, - importc: "PDC_return_key_modifiers", dynlib: pdcursesdll.} -proc save_key_modifiers*(a2: cunsignedchar): cint{.extdecl, - importc: "PDC_save_key_modifiers", dynlib: pdcursesdll.} -proc bottom_panel*(pan: ptr PANEL): cint{.extdecl, importc: "bottom_panel", - dynlib: pdcursesdll.} -proc del_panel*(pan: ptr PANEL): cint{.extdecl, importc: "del_panel", - dynlib: pdcursesdll.} -proc hide_panel*(pan: ptr PANEL): cint{.extdecl, importc: "hide_panel", - dynlib: pdcursesdll.} -proc move_panel*(pan: ptr PANEL; starty: cint; startx: cint): cint{.extdecl, - importc: "move_panel", dynlib: pdcursesdll.} -proc new_panel*(win: ptr WINDOW): ptr PANEL{.extdecl, importc: "new_panel", - dynlib: pdcursesdll.} -proc panel_above*(pan: ptr PANEL): ptr PANEL{.extdecl, importc: "panel_above", - dynlib: pdcursesdll.} -proc panel_below*(pan: ptr PANEL): ptr PANEL{.extdecl, importc: "panel_below", - dynlib: pdcursesdll.} -proc panel_hidden*(pan: ptr PANEL): cint{.extdecl, importc: "panel_hidden", - dynlib: pdcursesdll.} -proc panel_userptr*(pan: ptr PANEL): pointer{.extdecl, importc: "panel_userptr", - dynlib: pdcursesdll.} -proc panel_window*(pan: ptr PANEL): ptr WINDOW{.extdecl, importc: "panel_window", - dynlib: pdcursesdll.} -proc replace_panel*(pan: ptr PANEL; win: ptr WINDOW): cint{.extdecl, - importc: "replace_panel", dynlib: pdcursesdll.} -proc set_panel_userptr*(pan: ptr PANEL; uptr: pointer): cint{.extdecl, - importc: "set_panel_userptr", dynlib: pdcursesdll.} -proc show_panel*(pan: ptr PANEL): cint{.extdecl, importc: "show_panel", - dynlib: pdcursesdll.} -proc top_panel*(pan: ptr PANEL): cint{.extdecl, importc: "top_panel", - dynlib: pdcursesdll.} -proc update_panels*(){.extdecl, importc: "update_panels", dynlib: pdcursesdll.} - -when unixOS: - proc Xinitscr*(a2: cint; a3: cstringArray): ptr WINDOW{.extdecl, - importc: "Xinitscr", dynlib: pdcursesdll.} - proc XCursesExit*(){.extdecl, importc: "XCursesExit", dynlib: pdcursesdll.} - proc sb_init*(): cint{.extdecl, importc: "sb_init", dynlib: pdcursesdll.} - proc sb_set_horz*(a2: cint; a3: cint; a4: cint): cint{.extdecl, - importc: "sb_set_horz", dynlib: pdcursesdll.} - proc sb_set_vert*(a2: cint; a3: cint; a4: cint): cint{.extdecl, - importc: "sb_set_vert", dynlib: pdcursesdll.} - proc sb_get_horz*(a2: ptr cint; a3: ptr cint; a4: ptr cint): cint{.extdecl, - importc: "sb_get_horz", dynlib: pdcursesdll.} - proc sb_get_vert*(a2: ptr cint; a3: ptr cint; a4: ptr cint): cint{.extdecl, - importc: "sb_get_vert", dynlib: pdcursesdll.} - proc sb_refresh*(): cint{.extdecl, importc: "sb_refresh", dynlib: pdcursesdll.} - -template getch*(): expr = - wgetch(stdscr) - -template ungetch*(ch: expr): expr = - ungetch(ch) - -template getbegyx*(w, y, x: expr): expr = - y = getbegy(w) - x = getbegx(w) - -template getmaxyx*(w, y, x: expr): expr = - y = getmaxy(w) - x = getmaxx(w) - -template getparyx*(w, y, x: expr): expr = - y = getpary(w) - x = getparx(w) - -template getyx*(w, y, x: expr): expr = - y = getcury(w) - x = getcurx(w) - -template getsyx*(y, x: expr): stmt = - if curscr.leaveit: - (x) = - 1 - (y) = (x) - else: getyx(curscr, (y), (x)) - -template getmouse*(x: expr): expr = - nc_getmouse(x) - -when defined(windows): - var - atrtab*{.importc: "pdc_atrtab", dynlib: pdcursesdll.}: cstring - con_out*{.importc: "pdc_con_out", dynlib: pdcursesdll.}: HANDLE - con_in*{.importc: "pdc_con_in", dynlib: pdcursesdll.}: HANDLE - quick_edit*{.importc: "pdc_quick_edit", dynlib: pdcursesdll.}: DWORD - - proc get_buffer_rows*(): cint{.extdecl, importc: "PDC_get_buffer_rows", - dynlib: pdcursesdll.} diff --git a/lib/wrappers/tinyc.nim b/lib/wrappers/tinyc.nim index 47b505abc..f2ce92d36 100644 --- a/lib/wrappers/tinyc.nim +++ b/lib/wrappers/tinyc.nim @@ -20,16 +20,12 @@ proc openCCState*(): PccState {.importc: "tcc_new", cdecl.} proc closeCCState*(s: PccState) {.importc: "tcc_delete", cdecl.} ## free a TCC compilation context -proc enableDebug*(s: PccState) {.importc: "tcc_enable_debug", cdecl.} - ## add debug information in the generated code - proc setErrorFunc*(s: PccState, errorOpaque: pointer, errorFun: ErrorFunc) {. cdecl, importc: "tcc_set_error_func".} ## set error/warning display callback -proc setWarning*(s: PccState, warningName: cstring, value: int) {.cdecl, - importc: "tcc_set_warning".} - ## set/reset a warning +proc setOptions*(s: PccState, options: cstring) {.cdecl, importc: "tcc_set_options".} + ## set a options # preprocessor @@ -41,7 +37,6 @@ proc addSysincludePath*(s: PccState, pathname: cstring) {.cdecl, importc: "tcc_add_sysinclude_path".} ## add in system include path - proc defineSymbol*(s: PccState, sym, value: cstring) {.cdecl, importc: "tcc_define_symbol".} ## define preprocessor symbol 'sym'. Can put optional value @@ -65,16 +60,12 @@ proc compileString*(s: PccState, buf: cstring): cint {.cdecl, const - OutputMemory*: cint = 0 ## output will be ran in memory (no + OutputMemory*: cint = 1 ## output will be ran in memory (no ## output file) (default) - OutputExe*: cint = 1 ## executable file - OutputDll*: cint = 2 ## dynamic library - OutputObj*: cint = 3 ## object file - OutputPreprocess*: cint = 4 ## preprocessed file (used internally) - - OutputFormatElf*: cint = 0 ## default output format: ELF - OutputFormatBinary*: cint = 1 ## binary image output - OutputFormatCoff*: cint = 2 ## COFF + OutputExe*: cint = 2 ## executable file + OutputDll*: cint = 3 ## dynamic library + OutputObj*: cint = 4 ## object file + OutputPreprocess*: cint = 5 ## preprocessed file (used internally) proc setOutputType*(s: PCCState, outputType: cint): cint {.cdecl, importc: "tcc_set_output_type".} |