Merge branch 'devel'

This commit is contained in:
Dominik Picheta 2015-10-27 23:36:00 +01:00
commit 3892969af4
1019 changed files with 35409 additions and 74200 deletions

59
lib/arch/arch.nim Normal file
View file

@ -0,0 +1,59 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Rokas Kupstys
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
when defined(windows):
const
ABI* = "ms"
elif defined(unix):
const
ABI* = "unix"
else:
{.error: "Unsupported ABI".}
when defined(amd64):
when defined(unix):
# unix (sysv) ABI
type
JmpBufReg* {.pure.} = enum
BX, BP, R12, R13, R14, R15, SP, IP, TOTAL
elif defined(windows):
# ms ABI
type
JmpBufReg* {.pure.} = enum
BX, BP, R12, R13, R14, R15, SP, IP, SI, DI, TOTAL
type
Reg* {.pure.} = enum
AX, BX, CX, DX, SI, DI, BP, SP, IP, R8, R9, R10, R11, R12, R13, R14, R15, TOTAL
elif defined(i386):
# identical fastcall calling convention on all x86 OS
type
JmpBufReg* {.pure.} = enum
BX, SI, DI, BP, SP, IP, TOTAL
Reg* {.pure.} = enum
AX, BX, CX, BP, SP, DI, SI, TOTAL
else:
{.error: "Unsupported architecture".}
{.compile: "./" & ABI & "_" & hostCPU & ".asm"}
type
JmpBuf* = array[JmpBufReg.TOTAL, pointer]
Registers* = array[Reg.TOTAL, pointer]
proc getRegisters*(ctx: var Registers) {.importc: "narch_$1", fastcall.}
proc setjmp*(ctx: var JmpBuf): int {.importc: "narch_$1", fastcall.}
proc longjmp*(ctx: JmpBuf, ret=1) {.importc: "narch_$1", fastcall.}
proc coroSwitchStack*(sp: pointer) {.importc: "narch_$1", fastcall.}
proc coroRestoreStack*() {.importc: "narch_$1", fastcall.}

79
lib/arch/i386.asm Normal file
View file

@ -0,0 +1,79 @@
;
;
; Nim's Runtime Library
; (c) Copyright 2015 Rokas Kupstys
;
; See the file "copying.txt", included in this
; distribution, for details about the copyright.
;
section ".text" executable
public narch_getRegisters
public @narch_getRegisters@4
public narch_setjmp
public @narch_setjmp@4
public narch_longjmp
public @narch_longjmp@8
public narch_coroSwitchStack
public @narch_coroSwitchStack@4
public narch_coroRestoreStack
public @narch_coroRestoreStack@0
@narch_getRegisters@4:
narch_getRegisters:
mov [ecx], eax
mov [ecx+4], ebx
mov [ecx+8], ecx
mov [ecx+0Ch], ebp
mov [ecx+10h], esp
mov [ecx+14h], edi
mov [ecx+18h], esi
ret
@narch_setjmp@4:
narch_setjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov [ecx], ebx
mov [ecx+4], esi
mov [ecx+8], edi
mov [ecx+0Ch], ebp
lea eax, [esp+4]
mov [ecx+10h], eax
mov eax, [esp]
mov [ecx+14h], eax
xor eax, eax
ret
@narch_longjmp@8:
narch_longjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov eax, edx
test eax, eax
jnz @F
inc eax
@@:
mov ebx, [ecx]
mov esi, [ecx+4]
mov edi, [ecx+8]
mov ebp, [ecx+0Ch]
mov esp, [ecx+10h]
mov edx, [ecx+14h]
jmp edx
@narch_coroSwitchStack@4:
narch_coroSwitchStack:
pop eax ; return address
mov edx, esp ; old esp for saving
mov esp, ecx ; swap stack with one passed to func
push edx ; store old stack pointer on newly switched stack
jmp eax ; return
@narch_coroRestoreStack@0:
narch_coroRestoreStack:
pop eax ; return address
pop esp ; resture old stack pointer
jmp eax ; return

90
lib/arch/ms_amd64.asm Normal file
View file

@ -0,0 +1,90 @@
;
;
; Nim's Runtime Library
; (c) Copyright 2015 Rokas Kupstys
;
; See the file "copying.txt", included in this
; distribution, for details about the copyright.
;
format MS64 COFF
section ".text" executable align 16
public narch_getRegisters
public narch_setjmp
public narch_longjmp
public narch_coroSwitchStack
public narch_coroRestoreStack
narch_getRegisters:
mov [rcx], rax
mov [rcx+8], rbx
mov [rcx+10h], rcx
mov [rcx+18h], rdx
mov [rcx+20h], rsi
mov [rcx+28h], rdi
mov [rcx+30h], rbp
mov [rcx+38h], rsp
mov rax, [rsp]
mov [rcx+40h], rax ; rip
mov [rcx+48h], r8
mov [rcx+50h], r9
mov [rcx+58h], r10
mov [rcx+60h], r11
mov [rcx+68h], r12
mov [rcx+70h], r13
mov [rcx+78h], r14
mov [rcx+80h], r15
ret
narch_setjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov [rcx], rbx ; rcx is jmp_buf, move registers onto it
mov [rcx+8], rbp
mov [rcx+10h], r12
mov [rcx+18h], r13
mov [rcx+20h], r14
mov [rcx+28h], r15
lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr
mov [rcx+30h], rdx
mov rdx, [rsp] ; save return addr ptr for new rip
mov [rcx+38h], rdx
mov [rcx+40h], rsi
mov [rcx+48h], rdi
xor rax, rax ; always return 0
ret
narch_longjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov rax, rdx ; val will be longjmp return
test rax, rax
jnz @F
inc rax ; if val==0, val=1 per longjmp semantics
@@:
mov rbx, [rcx] ; rax is the jmp_buf, restore regs from it
mov rbp, [rcx+8]
mov r12, [rcx+10h]
mov r13, [rcx+18h]
mov r14, [rcx+20h]
mov r15, [rcx+28h]
mov rsp, [rcx+30h] ; this ends up being the stack pointer
mov rdx, [rcx+38h] ; this is the instruction pointer
jmp rdx ; goto saved address without altering rsp
narch_coroSwitchStack:
pop rax ; return address
mov rdx, rsp ; old rsp for saving
mov rsp, rcx ; swap stack with one passed to func
push rdx ; store old stack pointer on newly switched stack
sub rsp, 28h ; stack alignment + shadow space
jmp rax ; return
narch_coroRestoreStack:
pop rax ; return address
add rsp, 28h ; stack alignment + shadow space
pop rsp ; resture old stack pointer
jmp rax ; return

12
lib/arch/ms_i386.asm Normal file
View file

@ -0,0 +1,12 @@
;
;
; Nim's Runtime Library
; (c) Copyright 2015 Rokas Kupstys
;
; See the file "copying.txt", included in this
; distribution, for details about the copyright.
;
format MS COFF
include 'i386.asm'

89
lib/arch/unix_amd64.asm Normal file
View file

@ -0,0 +1,89 @@
;
;
; Nim's Runtime Library
; (c) Copyright 2015 Rokas Kupstys
;
; See the file "copying.txt", included in this
; distribution, for details about the copyright.
;
format ELF64
section ".text" executable align 16
public narch_getRegisters
public narch_setjmp
public narch_longjmp
public narch_coroSwitchStack
public narch_coroRestoreStack
narch_getRegisters:
mov [rdi], rax
mov [rdi+8], rbx
mov [rdi+10h], rcx
mov [rdi+18h], rdx
mov [rdi+20h], rsi
mov [rdi+28h], rdi
mov [rdi+30h], rbp
mov [rdi+38h], rsp
mov rax, [rsp]
mov [rdi+40h], rax ; rip
mov [rdi+48h], r8
mov [rdi+50h], r9
mov [rdi+58h], r10
mov [rdi+60h], r11
mov [rdi+68h], r12
mov [rdi+70h], r13
mov [rdi+78h], r14
mov [rdi+80h], r15
ret
narch_setjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov [rdi], rbx ; rdi is jmp_buf, move registers onto it
mov [rdi+8], rbp
mov [rdi+10h], r12
mov [rdi+18h], r13
mov [rdi+20h], r14
mov [rdi+28h], r15
lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr
mov [rdi+30h], rdx
mov rdx, [rsp] ; save return addr ptr for new rip
mov [rdi+38h], rdx
xor rax, rax ; always return 0
ret
narch_longjmp:
; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al.
mov rax, rsi ; val will be longjmp return
test rax, rax
jnz @F
inc rax ; if val==0, val=1 per longjmp semantics
@@:
mov rbx, [rdi] ; rdi is the jmp_buf, restore regs from it
mov rbp, [rdi+8]
mov r12, [rdi+10h]
mov r13, [rdi+18h]
mov r14, [rdi+20h]
mov r15, [rdi+28h]
mov rsp, [rdi+30h] ; this ends up being the stack pointer
mov rdx, [rdi+38h] ; this is the instruction pointer
jmp rdx ; goto saved address without altering rsp
narch_coroSwitchStack:
pop rsi ; return address
mov rdx, rsp ; old rsp for saving
mov rsp, rdi ; swap stack with one passed to func
push rdx ; store old stack pointer on newly switched stack
sub rsp, 8h ; stack alignment
jmp rsi ; return
narch_coroRestoreStack:
pop rsi ; return address
add rsp, 8h ; stack alignment
pop rsp ; resture old stack pointer
jmp rsi ; return

12
lib/arch/unix_i386.asm Normal file
View file

@ -0,0 +1,12 @@
;
;
; Nim's Runtime Library
; (c) Copyright 2015 Rokas Kupstys
;
; See the file "copying.txt", included in this
; distribution, for details about the copyright.
;
format ELF
include 'i386.asm'

View file

@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -12,56 +12,45 @@
include "system/syslocks"
type
TLock* = TSysLock ## Nim lock; whether this is re-entrant
## or not is unspecified!
TCond* = TSysCond ## Nim condition variable
LockEffect* {.deprecated.} = object of RootEffect ## \
## effect that denotes that some lock operation
## is performed. Deprecated, do not use anymore!
AquireEffect* {.deprecated.} = object of LockEffect ## \
## effect that denotes that some lock is
## acquired. Deprecated, do not use anymore!
ReleaseEffect* {.deprecated.} = object of LockEffect ## \
## effect that denotes that some lock is
## released. Deprecated, do not use anymore!
{.deprecated: [FLock: LockEffect, FAquireLock: AquireEffect,
FReleaseLock: ReleaseEffect].}
Lock* = SysLock ## Nim lock; whether this is re-entrant
## or not is unspecified!
Cond* = SysCond ## Nim condition variable
proc initLock*(lock: var TLock) {.inline.} =
{.deprecated: [TLock: Lock, TCond: Cond].}
proc initLock*(lock: var Lock) {.inline.} =
## Initializes the given lock.
initSysLock(lock)
proc deinitLock*(lock: var TLock) {.inline.} =
proc deinitLock*(lock: var Lock) {.inline.} =
## Frees the resources associated with the lock.
deinitSys(lock)
proc tryAcquire*(lock: var TLock): bool =
proc tryAcquire*(lock: var Lock): bool =
## Tries to acquire the given lock. Returns `true` on success.
result = tryAcquireSys(lock)
proc acquire*(lock: var TLock) =
proc acquire*(lock: var Lock) =
## Acquires the given lock.
acquireSys(lock)
proc release*(lock: var TLock) =
proc release*(lock: var Lock) =
## Releases the given lock.
releaseSys(lock)
proc initCond*(cond: var TCond) {.inline.} =
proc initCond*(cond: var Cond) {.inline.} =
## Initializes the given condition variable.
initSysCond(cond)
proc deinitCond*(cond: var TCond) {.inline.} =
proc deinitCond*(cond: var Cond) {.inline.} =
## Frees the resources associated with the lock.
deinitSysCond(cond)
proc wait*(cond: var TCond, lock: var TLock) {.inline.} =
## waits on the condition variable `cond`.
proc wait*(cond: var Cond, lock: var Lock) {.inline.} =
## waits on the condition variable `cond`.
waitSysCond(cond, lock)
proc signal*(cond: var TCond) {.inline.} =
## sends a signal to the condition variable `cond`.
signalSysCond(cond)
proc signal*(cond: var Cond) {.inline.} =
## sends a signal to the condition variable `cond`.
signalSysCond(cond)

View file

@ -144,6 +144,11 @@ proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect.}
proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.}
## compares two Nim nodes
proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} =
## compares two Nim nodes' types. Return true if the types are the same,
## eg. true when comparing alias with original type.
discard
proc len*(n: NimNode): int {.magic: "NLen", noSideEffect.}
## returns the number of children of `n`.
@ -164,7 +169,7 @@ proc kind*(n: NimNode): NimNodeKind {.magic: "NKind", noSideEffect.}
## returns the `kind` of the node `n`.
proc intVal*(n: NimNode): BiggestInt {.magic: "NIntVal", noSideEffect.}
proc boolVal*(n: NimNode): bool {.compileTime, noSideEffect.} = n.intVal != 0
proc floatVal*(n: NimNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.}
proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect.}
proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect.}
@ -177,6 +182,12 @@ proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
## resolve recursive types, you have to call 'getType' again. To see what
## kind of type it is, call `typeKind` on getType's result.
proc getType*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.}
## Returns the Nim type node for given type. This can be used to turn macro
## typedesc parameter into proper NimNode representing type, since typedesc
## are an exception in macro calls - they are not mapped implicitly to
## NimNode like any other arguments.
proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.}
## Returns the type kind of the node 'n' that should represent a type, that
## means the node should have been obtained via `getType`.
@ -202,6 +213,11 @@ proc newNimNode*(kind: NimNodeKind,
proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.}
proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.}
proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect.} =
## retrieve the implementation of a symbol `s`. `s` can be a routine or a
## const.
discard
proc error*(msg: string) {.magic: "NError", benign.}
## writes an error message at compile time
@ -480,7 +496,7 @@ macro dumpTree*(s: stmt): stmt {.immediate.} = echo s.treeRepr
## Accepts a block of nim code and prints the parsed abstract syntax
## tree using the `toTree` function. Printing is done *at compile time*.
##
## You can use this as a tool to explore the Nimrod's abstract syntax
## You can use this as a tool to explore the Nim's abstract syntax
## tree and to discover what kind of nodes must be created to represent
## a certain expression/statement.
@ -581,10 +597,8 @@ proc newNilLit*(): NimNode {.compileTime.} =
## New nil literal shortcut
result = newNimNode(nnkNilLit)
proc high*(node: NimNode): int {.compileTime.} = len(node) - 1
## Return the highest index available for a node
proc last*(node: NimNode): NimNode {.compileTime.} = node[node.high]
## Return the last item in nodes children. Same as `node[node.high()]`
proc last*(node: NimNode): NimNode {.compileTime.} = node[<node.len]
## Return the last item in nodes children. Same as `node[^1]`
const
@ -684,7 +698,7 @@ proc `body=`*(someProc: NimNode, val: NimNode) {.compileTime.} =
of nnkBlockStmt, nnkWhileStmt:
someProc[1] = val
of nnkForStmt:
someProc[high(someProc)] = val
someProc[len(someProc)-1] = val
else:
badNodeKind someProc.kind, "body="
@ -702,14 +716,22 @@ proc `$`*(node: NimNode): string {.compileTime.} =
result = node.strVal
of nnkSym:
result = $node.symbol
of nnkOpenSymChoice, nnkClosedSymChoice:
result = $node[0]
else:
badNodeKind node.kind, "$"
proc ident*(name: string): NimNode {.compileTime,inline.} = newIdentNode(name)
## Create a new ident node from a string
iterator children*(n: NimNode): NimNode {.inline.}=
for i in 0 .. high(n):
iterator items*(n: NimNode): NimNode {.inline.} =
## Iterates over the children of the NimNode ``n``.
for i in 0 ..< n.len:
yield n[i]
iterator children*(n: NimNode): NimNode {.inline.} =
## Iterates over the children of the NimNode ``n``.
for i in 0 ..< n.len:
yield n[i]
template findChild*(n: NimNode; cond: expr): NimNode {.
@ -729,16 +751,16 @@ template findChild*(n: NimNode; cond: expr): NimNode {.
proc insert*(a: NimNode; pos: int; b: NimNode) {.compileTime.} =
## Insert node B into A at pos
if high(a) < pos:
if len(a)-1 < pos:
## add some empty nodes first
for i in high(a)..pos-2:
for i in len(a)-1..pos-2:
a.add newEmptyNode()
a.add b
else:
## push the last item onto the list again
## and shift each item down to pos up one
a.add(a[a.high])
for i in countdown(high(a) - 2, pos):
a.add(a[a.len-1])
for i in countdown(len(a) - 3, pos):
a[i + 1] = a[i]
a[pos] = b
@ -825,6 +847,10 @@ proc addIdentIfAbsent*(dest: NimNode, ident: string) {.compiletime.} =
else: discard
dest.add(ident(ident))
proc boolVal*(n: NimNode): bool {.compileTime, noSideEffect.} =
if n.kind == nnkIntLit: n.intVal != 0
else: n == bindSym"true" # hacky solution for now
when not defined(booting):
template emit*(e: static[string]): stmt =
## accepts a single string argument and treats it as nim code

View file

@ -9,11 +9,11 @@
## This module implements an interface to Nim's `runtime type information`:idx:
## (`RTTI`:idx:).
## Note that even though ``TAny`` and its operations hide the nasty low level
## Note that even though ``Any`` and its operations hide the nasty low level
## details from its clients, it remains inherently unsafe!
##
## See the `marshal <marshal.html>`_ module for what this module allows you
## to do.
## to do.
{.push hints: off.}
@ -23,7 +23,7 @@ include "system/hti.nim"
{.pop.}
type
TAnyKind* = enum ## what kind of ``any`` it is
AnyKind* = enum ## what kind of ``any`` it is
akNone = 0, ## invalid any
akBool = 1, ## any represents a ``bool``
akChar = 2, ## any represents a ``char``
@ -54,10 +54,10 @@ type
akUInt16 = 42, ## any represents an unsigned in16
akUInt32 = 43, ## any represents an unsigned int32
akUInt64 = 44, ## any represents an unsigned int64
TAny* = object ## can represent any nim value; NOTE: the wrapped
Any* = object ## can represent any nim value; NOTE: the wrapped
## value can be modified with its wrapper! This means
## that ``TAny`` keeps a non-traced pointer to its
## that ``Any`` keeps a non-traced pointer to its
## wrapped value and **must not** live longer than
## its wrapped value.
value: pointer
@ -68,10 +68,15 @@ type
TGenericSeq {.importc.} = object
len, space: int
when defined(gogc):
elemSize: int
PGenSeq = ptr TGenericSeq
{.deprecated: [TAny: Any, TAnyKind: AnyKind].}
const
GenericSeqSize = (2 * sizeof(int))
when defined(gogc):
const GenericSeqSize = (3 * sizeof(int))
else:
const GenericSeqSize = (2 * sizeof(int))
proc genericAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc.}
proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.
@ -103,58 +108,58 @@ proc selectBranch(aa: pointer, n: ptr TNimNode): ptr TNimNode =
else:
result = n.sons[n.len]
proc newAny(value: pointer, rawType: PNimType): TAny =
proc newAny(value: pointer, rawType: PNimType): Any =
result.value = value
result.rawType = rawType
when declared(system.TVarSlot):
proc toAny*(x: TVarSlot): TAny {.inline.} =
## constructs a ``TAny`` object from a variable slot ``x``.
when declared(system.VarSlot):
proc toAny*(x: VarSlot): Any {.inline.} =
## constructs a ``Any`` object from a variable slot ``x``.
## This captures `x`'s address, so `x` can be modified with its
## ``TAny`` wrapper! The client needs to ensure that the wrapper
## ``Any`` wrapper! The client needs to ensure that the wrapper
## **does not** live longer than `x`!
## This is provided for easier reflection capabilities of a debugger.
result.value = x.address
result.rawType = x.typ
proc toAny*[T](x: var T): TAny {.inline.} =
## constructs a ``TAny`` object from `x`. This captures `x`'s address, so
## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure
proc toAny*[T](x: var T): Any {.inline.} =
## constructs a ``Any`` object from `x`. This captures `x`'s address, so
## `x` can be modified with its ``Any`` wrapper! The client needs to ensure
## that the wrapper **does not** live longer than `x`!
result.value = addr(x)
result.rawType = cast[PNimType](getTypeInfo(x))
proc kind*(x: TAny): TAnyKind {.inline.} =
## get the type kind
result = TAnyKind(ord(x.rawType.kind))
proc size*(x: TAny): int {.inline.} =
proc kind*(x: Any): AnyKind {.inline.} =
## get the type kind
result = AnyKind(ord(x.rawType.kind))
proc size*(x: Any): int {.inline.} =
## returns the size of `x`'s type.
result = x.rawType.size
proc baseTypeKind*(x: TAny): TAnyKind {.inline.} =
proc baseTypeKind*(x: Any): AnyKind {.inline.} =
## get the base type's kind; ``akNone`` is returned if `x` has no base type.
if x.rawType.base != nil:
result = TAnyKind(ord(x.rawType.base.kind))
result = AnyKind(ord(x.rawType.base.kind))
proc baseTypeSize*(x: TAny): int {.inline.} =
proc baseTypeSize*(x: Any): int {.inline.} =
## returns the size of `x`'s basetype.
if x.rawType.base != nil:
result = x.rawType.base.size
proc invokeNew*(x: TAny) =
proc invokeNew*(x: Any) =
## performs ``new(x)``. `x` needs to represent a ``ref``.
assert x.rawType.kind == tyRef
var z = newObj(x.rawType, x.rawType.base.size)
genericAssign(x.value, addr(z), x.rawType)
proc invokeNewSeq*(x: TAny, len: int) =
proc invokeNewSeq*(x: Any, len: int) =
## performs ``newSeq(x, len)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence
var z = newSeq(x.rawType, len)
genericShallowAssign(x.value, addr(z), x.rawType)
proc extendSeq*(x: TAny) =
proc extendSeq*(x: Any) =
## performs ``setLen(x, x.len+1)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence
var y = cast[ptr PGenSeq](x.value)[]
@ -164,7 +169,7 @@ proc extendSeq*(x: TAny) =
cast[ppointer](x.value)[] = z
#genericShallowAssign(x.value, addr(z), x.rawType)
proc setObjectRuntimeType*(x: TAny) =
proc setObjectRuntimeType*(x: Any) =
## this needs to be called to set `x`'s runtime object type field.
assert x.rawType.kind == tyObject
objectInit(x.value, x.rawType)
@ -173,12 +178,12 @@ proc skipRange(x: PNimType): PNimType {.inline.} =
result = x
if result.kind == tyRange: result = result.base
proc `[]`*(x: TAny, i: int): TAny =
proc `[]`*(x: Any, i: int): Any =
## accessor for an any `x` that represents an array or a sequence.
case x.rawType.kind
of tyArray:
var bs = x.rawType.base.size
if i >=% x.rawType.size div bs:
if i >=% x.rawType.size div bs:
raise newException(IndexError, "index out of bounds")
return newAny(x.value +!! i*bs, x.rawType.base)
of tySequence:
@ -190,12 +195,12 @@ proc `[]`*(x: TAny, i: int): TAny =
return newAny(s +!! (GenericSeqSize+i*bs), x.rawType.base)
else: assert false
proc `[]=`*(x: TAny, i: int, y: TAny) =
proc `[]=`*(x: Any, i: int, y: Any) =
## accessor for an any `x` that represents an array or a sequence.
case x.rawType.kind
of tyArray:
var bs = x.rawType.base.size
if i >=% x.rawType.size div bs:
if i >=% x.rawType.size div bs:
raise newException(IndexError, "index out of bounds")
assert y.rawType == x.rawType.base
genericAssign(x.value +!! i*bs, y.value, y.rawType)
@ -209,7 +214,7 @@ proc `[]=`*(x: TAny, i: int, y: TAny) =
genericAssign(s +!! (GenericSeqSize+i*bs), y.value, y.rawType)
else: assert false
proc len*(x: TAny): int =
proc len*(x: Any): int =
## len for an any `x` that represents an array or a sequence.
case x.rawType.kind
of tyArray: result = x.rawType.size div x.rawType.base.size
@ -217,37 +222,37 @@ proc len*(x: TAny): int =
else: assert false
proc base*(x: TAny): TAny =
## returns base TAny (useful for inherited object types).
proc base*(x: Any): Any =
## returns base Any (useful for inherited object types).
result.rawType = x.rawType.base
result.value = x.value
proc isNil*(x: TAny): bool =
proc isNil*(x: Any): bool =
## `isNil` for an any `x` that represents a sequence, string, cstring,
## proc or some pointer type.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
result = isNil(cast[ppointer](x.value)[])
proc getPointer*(x: TAny): pointer =
proc getPointer*(x: Any): pointer =
## retrieve the pointer value out of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
result = cast[ppointer](x.value)[]
proc setPointer*(x: TAny, y: pointer) =
proc setPointer*(x: Any, y: pointer) =
## sets the pointer value of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
cast[ppointer](x.value)[] = y
proc fieldsAux(p: pointer, n: ptr TNimNode,
ret: var seq[tuple[name: cstring, any: TAny]]) =
ret: var seq[tuple[name: cstring, any: Any]]) =
case n.kind
of nkNone: assert(false)
of nkSlot:
@ -260,7 +265,7 @@ proc fieldsAux(p: pointer, n: ptr TNimNode,
ret.add((n.name, newAny(p +!! n.offset, n.typ)))
if m != nil: fieldsAux(p, m, ret)
iterator fields*(x: TAny): tuple[name: string, any: TAny] =
iterator fields*(x: Any): tuple[name: string, any: Any] =
## iterates over every active field of the any `x` that represents an object
## or a tuple.
assert x.rawType.kind in {tyTuple, tyObject}
@ -269,7 +274,7 @@ iterator fields*(x: TAny): tuple[name: string, any: TAny] =
# XXX BUG: does not work yet, however is questionable anyway
when false:
if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[]
var ret: seq[tuple[name: cstring, any: TAny]] = @[]
var ret: seq[tuple[name: cstring, any: Any]] = @[]
if t.kind == tyObject:
while true:
fieldsAux(p, t.node, ret)
@ -304,7 +309,7 @@ proc getFieldNode(p: pointer, n: ptr TNimNode,
if cmpIgnoreStyle(n.name, name) == 0:
result = n
of nkList:
for i in 0..n.len-1:
for i in 0..n.len-1:
result = getFieldNode(p, n.sons[i], name)
if result != nil: break
of nkCase:
@ -314,7 +319,7 @@ proc getFieldNode(p: pointer, n: ptr TNimNode,
var m = selectBranch(p, n)
if m != nil: result = getFieldNode(p, m, name)
proc `[]=`*(x: TAny, fieldName: string, value: TAny) =
proc `[]=`*(x: Any, fieldName: string, value: Any) =
## sets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType
# XXX BUG: does not work yet, however is questionable anyway
@ -328,7 +333,7 @@ proc `[]=`*(x: TAny, fieldName: string, value: TAny) =
else:
raise newException(ValueError, "invalid field name: " & fieldName)
proc `[]`*(x: TAny, fieldName: string): TAny =
proc `[]`*(x: Any, fieldName: string): Any =
## gets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType
# XXX BUG: does not work yet, however is questionable anyway
@ -339,47 +344,49 @@ proc `[]`*(x: TAny, fieldName: string): TAny =
if n != nil:
result.value = x.value +!! n.offset
result.rawType = n.typ
elif x.rawType.kind == tyObject and x.rawType.base != nil:
return `[]`(Any(value: x.value, rawType: x.rawType.base), fieldName)
else:
raise newException(ValueError, "invalid field name: " & fieldName)
proc `[]`*(x: TAny): TAny =
proc `[]`*(x: Any): Any =
## dereference operation for the any `x` that represents a ptr or a ref.
assert x.rawType.kind in {tyRef, tyPtr}
result.value = cast[ppointer](x.value)[]
result.rawType = x.rawType.base
proc `[]=`*(x, y: TAny) =
proc `[]=`*(x, y: Any) =
## dereference operation for the any `x` that represents a ptr or a ref.
assert x.rawType.kind in {tyRef, tyPtr}
assert y.rawType == x.rawType.base
genericAssign(cast[ppointer](x.value)[], y.value, y.rawType)
proc getInt*(x: TAny): int =
proc getInt*(x: Any): int =
## retrieve the int value out of `x`. `x` needs to represent an int.
assert skipRange(x.rawType).kind == tyInt
result = cast[ptr int](x.value)[]
proc getInt8*(x: TAny): int8 =
proc getInt8*(x: Any): int8 =
## retrieve the int8 value out of `x`. `x` needs to represent an int8.
assert skipRange(x.rawType).kind == tyInt8
result = cast[ptr int8](x.value)[]
proc getInt16*(x: TAny): int16 =
proc getInt16*(x: Any): int16 =
## retrieve the int16 value out of `x`. `x` needs to represent an int16.
assert skipRange(x.rawType).kind == tyInt16
result = cast[ptr int16](x.value)[]
proc getInt32*(x: TAny): int32 =
proc getInt32*(x: Any): int32 =
## retrieve the int32 value out of `x`. `x` needs to represent an int32.
assert skipRange(x.rawType).kind == tyInt32
result = cast[ptr int32](x.value)[]
proc getInt64*(x: TAny): int64 =
proc getInt64*(x: Any): int64 =
## retrieve the int64 value out of `x`. `x` needs to represent an int64.
assert skipRange(x.rawType).kind == tyInt64
result = cast[ptr int64](x.value)[]
proc getBiggestInt*(x: TAny): BiggestInt =
proc getBiggestInt*(x: Any): BiggestInt =
## retrieve the integer value out of `x`. `x` needs to represent
## some integer, a bool, a char, an enum or a small enough bit set.
## The value might be sign-extended to ``BiggestInt``.
@ -405,7 +412,7 @@ proc getBiggestInt*(x: TAny): BiggestInt =
of tyUInt32: result = BiggestInt(cast[ptr uint32](x.value)[])
else: assert false
proc setBiggestInt*(x: TAny, y: BiggestInt) =
proc setBiggestInt*(x: Any, y: BiggestInt) =
## sets the integer value of `x`. `x` needs to represent
## some integer, a bool, a char, an enum or a small enough bit set.
var t = skipRange(x.rawType)
@ -430,36 +437,36 @@ proc setBiggestInt*(x: TAny, y: BiggestInt) =
of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y)
else: assert false
proc getUInt*(x: TAny): uint =
proc getUInt*(x: Any): uint =
## retrieve the uint value out of `x`, `x` needs to represent an uint.
assert skipRange(x.rawType).kind == tyUInt
result = cast[ptr uint](x.value)[]
proc getUInt8*(x: TAny): uint8 =
proc getUInt8*(x: Any): uint8 =
## retrieve the uint8 value out of `x`, `x` needs to represent an
## uint8.
assert skipRange(x.rawType).kind == tyUInt8
result = cast[ptr uint8](x.value)[]
proc getUInt16*(x: TAny): uint16 =
proc getUInt16*(x: Any): uint16 =
## retrieve the uint16 value out of `x`, `x` needs to represent an
## uint16.
assert skipRange(x.rawType).kind == tyUInt16
result = cast[ptr uint16](x.value)[]
proc getUInt32*(x: TAny): uint32 =
proc getUInt32*(x: Any): uint32 =
## retrieve the uint32 value out of `x`, `x` needs to represent an
## uint32.
assert skipRange(x.rawType).kind == tyUInt32
result = cast[ptr uint32](x.value)[]
proc getUInt64*(x: TAny): uint64 =
proc getUInt64*(x: Any): uint64 =
## retrieve the uint64 value out of `x`, `x` needs to represent an
## uint64.
assert skipRange(x.rawType).kind == tyUInt64
result = cast[ptr uint64](x.value)[]
proc getBiggestUint*(x: TAny): uint64 =
proc getBiggestUint*(x: Any): uint64 =
## retrieve the unsigned integer value out of `x`. `x` needs to
## represent an unsigned integer.
var t = skipRange(x.rawType)
@ -471,7 +478,7 @@ proc getBiggestUint*(x: TAny): uint64 =
of tyUInt64: result = uint64(cast[ptr uint64](x.value)[])
else: assert false
proc setBiggestUint*(x: TAny; y: uint64) =
proc setBiggestUint*(x: Any; y: uint64) =
## sets the unsigned integer value of `c`. `c` needs to represent an
## unsigned integer.
var t = skipRange(x.rawType)
@ -483,25 +490,25 @@ proc setBiggestUint*(x: TAny; y: uint64) =
of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y)
else: assert false
proc getChar*(x: TAny): char =
proc getChar*(x: Any): char =
## retrieve the char value out of `x`. `x` needs to represent a char.
var t = skipRange(x.rawType)
assert t.kind == tyChar
result = cast[ptr char](x.value)[]
proc getBool*(x: TAny): bool =
proc getBool*(x: Any): bool =
## retrieve the bool value out of `x`. `x` needs to represent a bool.
var t = skipRange(x.rawType)
assert t.kind == tyBool
result = cast[ptr bool](x.value)[]
proc skipRange*(x: TAny): TAny =
proc skipRange*(x: Any): Any =
## skips the range information of `x`.
assert x.rawType.kind == tyRange
result.rawType = x.rawType.base
result.value = x.value
proc getEnumOrdinal*(x: TAny, name: string): int =
proc getEnumOrdinal*(x: Any, name: string): int =
## gets the enum field ordinal from `name`. `x` needs to represent an enum
## but is only used to access the type information. In case of an error
## ``low(int)`` is returned.
@ -510,17 +517,17 @@ proc getEnumOrdinal*(x: TAny, name: string): int =
var n = typ.node
var s = n.sons
for i in 0 .. n.len-1:
if cmpIgnoreStyle($s[i].name, name) == 0:
if cmpIgnoreStyle($s[i].name, name) == 0:
if ntfEnumHole notin typ.flags:
return i
else:
return s[i].offset
result = low(int)
proc getEnumField*(x: TAny, ordinalValue: int): string =
proc getEnumField*(x: Any, ordinalValue: int): string =
## gets the enum field name as a string. `x` needs to represent an enum
## but is only used to access the type information. The field name of
## `ordinalValue` is returned.
## `ordinalValue` is returned.
var typ = skipRange(x.rawType)
assert typ.kind == tyEnum
var e = ordinalValue
@ -535,26 +542,26 @@ proc getEnumField*(x: TAny, ordinalValue: int): string =
if s[i].offset == e: return $s[i].name
result = $e
proc getEnumField*(x: TAny): string =
proc getEnumField*(x: Any): string =
## gets the enum field name as a string. `x` needs to represent an enum.
result = getEnumField(x, getBiggestInt(x).int)
proc getFloat*(x: TAny): float =
## retrieve the float value out of `x`. `x` needs to represent an float.
proc getFloat*(x: Any): float =
## retrieve the float value out of `x`. `x` needs to represent an float.
assert skipRange(x.rawType).kind == tyFloat
result = cast[ptr float](x.value)[]
proc getFloat32*(x: TAny): float32 =
proc getFloat32*(x: Any): float32 =
## retrieve the float32 value out of `x`. `x` needs to represent an float32.
assert skipRange(x.rawType).kind == tyFloat32
result = cast[ptr float32](x.value)[]
proc getFloat64*(x: TAny): float64 =
proc getFloat64*(x: Any): float64 =
## retrieve the float64 value out of `x`. `x` needs to represent an float64.
assert skipRange(x.rawType).kind == tyFloat64
result = cast[ptr float64](x.value)[]
proc getBiggestFloat*(x: TAny): BiggestFloat =
proc getBiggestFloat*(x: Any): BiggestFloat =
## retrieve the float value out of `x`. `x` needs to represent
## some float. The value is extended to ``BiggestFloat``.
case skipRange(x.rawType).kind
@ -563,7 +570,7 @@ proc getBiggestFloat*(x: TAny): BiggestFloat =
of tyFloat64: result = BiggestFloat(cast[ptr float64](x.value)[])
else: assert false
proc setBiggestFloat*(x: TAny, y: BiggestFloat) =
proc setBiggestFloat*(x: Any, y: BiggestFloat) =
## sets the float value of `x`. `x` needs to represent
## some float.
case skipRange(x.rawType).kind
@ -572,29 +579,29 @@ proc setBiggestFloat*(x: TAny, y: BiggestFloat) =
of tyFloat64: cast[ptr float64](x.value)[] = y
else: assert false
proc getString*(x: TAny): string =
proc getString*(x: Any): string =
## retrieve the string value out of `x`. `x` needs to represent a string.
assert x.rawType.kind == tyString
if not isNil(cast[ptr pointer](x.value)[]):
result = cast[ptr string](x.value)[]
proc setString*(x: TAny, y: string) =
proc setString*(x: Any, y: string) =
## sets the string value of `x`. `x` needs to represent a string.
assert x.rawType.kind == tyString
cast[ptr string](x.value)[] = y
proc getCString*(x: TAny): cstring =
proc getCString*(x: Any): cstring =
## retrieve the cstring value out of `x`. `x` needs to represent a cstring.
assert x.rawType.kind == tyCString
result = cast[ptr cstring](x.value)[]
proc assign*(x, y: TAny) =
## copies the value of `y` to `x`. The assignment operator for ``TAny``
proc assign*(x, y: Any) =
## copies the value of `y` to `x`. The assignment operator for ``Any``
## does NOT do this; it performs a shallow copy instead!
assert y.rawType == x.rawType
genericAssign(x.value, y.value, y.rawType)
iterator elements*(x: TAny): int =
iterator elements*(x: Any): int =
## iterates over every element of `x` that represents a Nim bitset.
assert x.rawType.kind == tySet
var typ = x.rawType
@ -616,7 +623,7 @@ iterator elements*(x: TAny): int =
if (u and (1'i64 shl int64(i))) != 0'i64:
yield i+typ.node.len
proc inclSetElement*(x: TAny, elem: int) =
proc inclSetElement*(x: Any, elem: int) =
## includes an element `elem` in `x`. `x` needs to represent a Nim bitset.
assert x.rawType.kind == tySet
var typ = x.rawType
@ -630,7 +637,7 @@ proc inclSetElement*(x: TAny, elem: int) =
of 2:
var a = cast[ptr int16](p)
a[] = a[] or (1'i16 shl int16(e))
of 4:
of 4:
var a = cast[ptr int32](p)
a[] = a[] or (1'i32 shl int32(e))
of 8:
@ -644,7 +651,7 @@ when isMainModule:
type
TE = enum
blah, blah2
TestObj = object
test, asd: int
case test2: TE
@ -658,7 +665,7 @@ when isMainModule:
var y = 78
x[4] = toAny(y)
assert cast[ptr int](x[2].value)[] == 2
var test2: tuple[name: string, s: int] = ("test", 56)
var x2 = toAny(test2)
var i = 0
@ -668,7 +675,7 @@ when isMainModule:
of 1: assert n == "s" and $a.kind == "akInt"
else: assert false
inc i
var test3: TestObj
test3.test = 42
test3.test2 = blah2
@ -676,27 +683,27 @@ when isMainModule:
i = 0
for n, a in fields(x3):
case i
of 0: assert n == "test" and $a.kind == "akInt"
of 0: assert n == "test" and $a.kind == "akInt"
of 1: assert n == "asd" and $a.kind == "akInt"
of 2: assert n == "test2" and $a.kind == "akEnum"
else: assert false
inc i
var test4: ref string
new(test4)
test4[] = "test"
var x4 = toAny(test4)
assert($x4[].kind() == "akString")
block:
# gimme a new scope dammit
var myarr: array[0..4, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"]]
var m = toAny(myArr)
for i in 0 .. m.len-1:
for j in 0 .. m[i].len-1:
echo getString(m[i][j])

View file

@ -1,57 +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.
#
## This module implements basic arithmetic operators for unsigned integers.
## To discourage users from using ``unsigned``, it's not part of ``system``,
## but an extra import.
proc `not`*[T: SomeUnsignedInt](x: T): T {.magic: "BitnotI", noSideEffect.}
## computes the `bitwise complement` of the integer `x`.
proc `shr`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ShrI", noSideEffect.}
## computes the `shift right` operation of `x` and `y`.
proc `shl`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ShlI", noSideEffect.}
## computes the `shift left` operation of `x` and `y`.
proc `and`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitandI", noSideEffect.}
## computes the `bitwise and` of numbers `x` and `y`.
proc `or`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitorI", noSideEffect.}
## computes the `bitwise or` of numbers `x` and `y`.
proc `xor`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitxorI", noSideEffect.}
## computes the `bitwise xor` of numbers `x` and `y`.
proc `==`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "EqI", noSideEffect.}
## Compares two unsigned integers for equality.
proc `+`*[T: SomeUnsignedInt](x, y: T): T {.magic: "AddU", noSideEffect.}
## Binary `+` operator for unsigned integers.
proc `-`*[T: SomeUnsignedInt](x, y: T): T {.magic: "SubU", noSideEffect.}
## Binary `-` operator for unsigned integers.
proc `*`*[T: SomeUnsignedInt](x, y: T): T {.magic: "MulU", noSideEffect.}
## Binary `*` operator for unsigned integers.
proc `div`*[T: SomeUnsignedInt](x, y: T): T {.magic: "DivU", noSideEffect.}
## computes the integer division. This is roughly the same as
## ``floor(x/y)``.
proc `mod`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ModU", noSideEffect.}
## computes the integer modulo operation. This is the same as
## ``x - (x div y) * y``.
proc `<=`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "LeU", noSideEffect.}
## Returns true iff ``x <= y``.
proc `<`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "LtU", noSideEffect.}
## Returns true iff ``unsigned(x) < unsigned(y)``.

View file

@ -0,0 +1,18 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## **Warning:** Since version 0.11.4 this module is deprecated.
##
## This module implemented basic arithmetic operators for unsigned integers.
## These operators are now available in the ``system`` module directly.
{.deprecated.}
export `shr`, `shl`, `and`, `or`, `xor`, `==`, `+`, `-`, `*`, `div`, `mod`,
`<=`, `<`

View file

@ -16,7 +16,7 @@
## .. code-block:: nim
##
## var
## a: TActorPool[int, void]
## a: ActorPool[int, void]
## createActorPool(a)
## for i in 0 .. < 300:
## a.spawn(i, proc (x: int) {.thread.} = echo x)
@ -30,75 +30,76 @@
from os import sleep
type
TTask*[TIn, TOut] = object{.pure, final.} ## a task
when TOut isnot void:
receiver*: ptr TChannel[TOut] ## the receiver channel of the response
action*: proc (x: TIn): TOut {.thread.} ## action to execute;
Task*[In, Out] = object{.pure, final.} ## a task
when Out isnot void:
receiver*: ptr Channel[Out] ## the receiver channel of the response
action*: proc (x: In): Out {.thread.} ## action to execute;
## sometimes useful
shutDown*: bool ## set to tell an actor to shut-down
data*: TIn ## the data to process
data*: In ## the data to process
TActor[TIn, TOut] = object{.pure, final.}
i: TChannel[TTask[TIn, TOut]]
t: TThread[ptr TActor[TIn, TOut]]
PActor*[TIn, TOut] = ptr TActor[TIn, TOut] ## an actor
proc spawn*[TIn, TOut](action: proc(
self: PActor[TIn, TOut]){.thread.}): PActor[TIn, TOut] =
Actor[In, Out] = object{.pure, final.}
i: Channel[Task[In, Out]]
t: TThread[ptr Actor[In, Out]]
PActor*[In, Out] = ptr Actor[In, Out] ## an actor
{.deprecated: [TTask: Task, TActor: Actor].}
proc spawn*[In, Out](action: proc(
self: PActor[In, Out]){.thread.}): PActor[In, Out] =
## creates an actor; that is a thread with an inbox. The caller MUST call
## ``join`` because that also frees the actor's associated resources.
result = cast[PActor[TIn, TOut]](allocShared0(sizeof(result[])))
result = cast[PActor[In, Out]](allocShared0(sizeof(result[])))
open(result.i)
createThread(result.t, action, result)
proc inbox*[TIn, TOut](self: PActor[TIn, TOut]): ptr TChannel[TIn] =
proc inbox*[In, Out](self: PActor[In, Out]): ptr Channel[In] =
## gets a pointer to the associated inbox of the actor `self`.
result = addr(self.i)
proc running*[TIn, TOut](a: PActor[TIn, TOut]): bool =
proc running*[In, Out](a: PActor[In, Out]): bool =
## returns true if the actor `a` is running.
result = running(a.t)
proc ready*[TIn, TOut](a: PActor[TIn, TOut]): bool =
proc ready*[In, Out](a: PActor[In, Out]): bool =
## returns true if the actor `a` is ready to process new messages.
result = ready(a.i)
proc join*[TIn, TOut](a: PActor[TIn, TOut]) =
proc join*[In, Out](a: PActor[In, Out]) =
## joins an actor.
joinThread(a.t)
close(a.i)
deallocShared(a)
proc recv*[TIn, TOut](a: PActor[TIn, TOut]): TTask[TIn, TOut] =
proc recv*[In, Out](a: PActor[In, Out]): Task[In, Out] =
## receives a task from `a`'s inbox.
result = recv(a.i)
proc send*[TIn, TOut, X, Y](receiver: PActor[TIn, TOut], msg: TIn,
proc send*[In, Out, X, Y](receiver: PActor[In, Out], msg: In,
sender: PActor[X, Y]) =
## sends a message to `a`'s inbox.
var t: TTask[TIn, TOut]
var t: Task[In, Out]
t.receiver = addr(sender.i)
shallowCopy(t.data, msg)
send(receiver.i, t)
proc send*[TIn, TOut](receiver: PActor[TIn, TOut], msg: TIn,
sender: ptr TChannel[TOut] = nil) =
proc send*[In, Out](receiver: PActor[In, Out], msg: In,
sender: ptr Channel[Out] = nil) =
## sends a message to `receiver`'s inbox.
var t: TTask[TIn, TOut]
var t: Task[In, Out]
t.receiver = sender
shallowCopy(t.data, msg)
send(receiver.i, t)
proc sendShutdown*[TIn, TOut](receiver: PActor[TIn, TOut]) =
proc sendShutdown*[In, Out](receiver: PActor[In, Out]) =
## send a shutdown message to `receiver`.
var t: TTask[TIn, TOut]
var t: Task[In, Out]
t.shutdown = true
send(receiver.i, t)
proc reply*[TIn, TOut](t: TTask[TIn, TOut], m: TOut) =
proc reply*[In, Out](t: Task[In, Out], m: Out) =
## sends a message to io's output message box.
when TOut is void:
when Out is void:
{.error: "you cannot reply to a void outbox".}
assert t.receiver != nil
send(t.receiver[], m)
@ -107,36 +108,37 @@ proc reply*[TIn, TOut](t: TTask[TIn, TOut], m: TOut) =
# ----------------- actor pools ----------------------------------------------
type
TActorPool*[TIn, TOut] = object{.pure, final.} ## an actor pool
actors: seq[PActor[TIn, TOut]]
when TOut isnot void:
outputs: TChannel[TOut]
ActorPool*[In, Out] = object{.pure, final.} ## an actor pool
actors: seq[PActor[In, Out]]
when Out isnot void:
outputs: Channel[Out]
{.deprecated: [TActorPool: ActorPool].}
proc `^`*[T](f: ptr TChannel[T]): T =
proc `^`*[T](f: ptr Channel[T]): T =
## alias for 'recv'.
result = recv(f[])
proc poolWorker[TIn, TOut](self: PActor[TIn, TOut]) {.thread.} =
proc poolWorker[In, Out](self: PActor[In, Out]) {.thread.} =
while true:
var m = self.recv
if m.shutDown: break
when TOut is void:
when Out is void:
m.action(m.data)
else:
send(m.receiver[], m.action(m.data))
#self.reply()
proc createActorPool*[TIn, TOut](a: var TActorPool[TIn, TOut], poolSize = 4) =
proc createActorPool*[In, Out](a: var ActorPool[In, Out], poolSize = 4) =
## creates an actor pool.
newSeq(a.actors, poolSize)
when TOut isnot void:
when Out isnot void:
open(a.outputs)
for i in 0 .. < a.actors.len:
a.actors[i] = spawn(poolWorker[TIn, TOut])
a.actors[i] = spawn(poolWorker[In, Out])
proc sync*[TIn, TOut](a: var TActorPool[TIn, TOut], polling=50) =
proc sync*[In, Out](a: var ActorPool[In, Out], polling=50) =
## waits for every actor of `a` to finish with its work. Currently this is
## implemented as polling every `polling` ms and has a slight chance
## implemented as polling every `polling` ms and has a slight chance
## of failing since we check for every actor to be in `ready` state and not
## for messages still in ether. This will change in a later
## version, however.
@ -144,7 +146,7 @@ proc sync*[TIn, TOut](a: var TActorPool[TIn, TOut], polling=50) =
while true:
var wait = false
for i in 0..high(a.actors):
if not a.actors[i].i.ready:
if not a.actors[i].i.ready:
wait = true
allReadyCount = 0
break
@ -157,18 +159,18 @@ proc sync*[TIn, TOut](a: var TActorPool[TIn, TOut], polling=50) =
if allReadyCount > 1: break
sleep(polling)
proc terminate*[TIn, TOut](a: var TActorPool[TIn, TOut]) =
proc terminate*[In, Out](a: var ActorPool[In, Out]) =
## terminates each actor in the actor pool `a` and frees the
## resources attached to `a`.
var t: TTask[TIn, TOut]
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])
when TOut isnot void:
when Out isnot void:
close(a.outputs)
a.actors = nil
proc join*[TIn, TOut](a: var TActorPool[TIn, TOut]) =
proc join*[In, Out](a: var ActorPool[In, Out]) =
## short-cut for `sync` and then `terminate`.
sync(a)
terminate(a)
@ -202,28 +204,28 @@ template schedule =
else:
raise newException(DeadThreadError, "cannot send message; thread died")
proc spawn*[TIn, TOut](p: var TActorPool[TIn, TOut], input: TIn,
action: proc (input: TIn): TOut {.thread.}
): ptr TChannel[TOut] =
proc spawn*[In, Out](p: var ActorPool[In, Out], input: In,
action: proc (input: In): Out {.thread.}
): ptr Channel[Out] =
## uses the actor pool to run ``action(input)`` concurrently.
## `spawn` is guaranteed to not block.
var t: TTask[TIn, TOut]
var t: Task[In, Out]
setupTask()
result = addr(p.outputs)
t.receiver = result
schedule()
proc spawn*[TIn](p: var TActorPool[TIn, void], input: TIn,
action: proc (input: TIn) {.thread.}) =
proc spawn*[In](p: var ActorPool[In, void], input: In,
action: proc (input: In) {.thread.}) =
## uses the actor pool to run ``action(input)`` concurrently.
## `spawn` is guaranteed to not block.
var t: TTask[TIn, void]
var t: Task[In, void]
setupTask()
schedule()
when not defined(testing) and isMainModule:
var
a: TActorPool[int, void]
a: ActorPool[int, void]
createActorPool(a)
for i in 0 .. < 300:
a.spawn(i, proc (x: int) {.thread.} = echo x)

View file

@ -188,8 +188,8 @@ proc asyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
result.socket.setBlocking(false)
proc toAsyncSocket*(sock: Socket, state: SocketStatus = SockConnected): AsyncSocket =
## Wraps an already initialized ``TSocket`` into a AsyncSocket.
## This is useful if you want to use an already connected TSocket as an
## Wraps an already initialized ``Socket`` into a AsyncSocket.
## This is useful if you want to use an already connected Socket as an
## asynchronous AsyncSocket in asyncio's event loop.
##
## ``state`` may be overriden, i.e. if ``sock`` is not connected it should be

View file

@ -11,9 +11,14 @@ include "system/inclrtl"
import sockets, strutils, parseutils, times, os, asyncio
from asyncnet import nil
from rawsockets import nil
from nativesockets import nil
from asyncdispatch import PFuture
## **Note**: This module is deprecated since version 0.11.3.
## You should use the async version of this module
## `asyncftpclient <asyncftpclient.html>`_.
##
## ----
##
## This module **partially** implements an FTP client as specified
## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_.
##
@ -36,6 +41,8 @@ from asyncdispatch import PFuture
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
{.deprecated.}
type
FtpBase*[SockType] = ref FtpBaseObj[SockType]
FtpBaseObj*[SockType] = object
@ -48,7 +55,7 @@ type
user*, pass*: string
address*: string
when SockType is asyncnet.AsyncSocket:
port*: rawsockets.Port
port*: nativesockets.Port
else:
port*: Port
@ -107,6 +114,8 @@ type
EInvalidReply: ReplyError, EFTP: FTPError
].}
const multiLineLimit = 10000
proc ftpClient*(address: string, port = Port(21),
user, pass = ""): FtpClient =
## Create a ``FtpClient`` object.
@ -135,10 +144,24 @@ proc expectReply[T](ftp: FtpBase[T]): TaintedString =
ftp.csock.readLine(result)
else:
discard ftp.csock.readLine(result)
var count = 0
while result[3] == '-':
## Multi-line reply.
var line = TaintedString""
when T is Socket:
ftp.csock.readLine(line)
else:
discard ftp.csock.readLine(line)
result.add("\n" & line)
count.inc()
if count >= multiLineLimit:
raise newException(ReplyError, "Reached maximum multi-line reply count.")
proc send*[T](ftp: FtpBase[T], m: string): TaintedString =
## Send a message to the server, and wait for a primary reply.
## ``\c\L`` is added for you.
##
## **Note:** The server may return multiple lines of coded replies.
blockingOperation(ftp.csock):
ftp.csock.send(m & "\c\L")
return ftp.expectReply()
@ -263,7 +286,13 @@ proc connect*[T](ftp: FtpBase[T]) =
else:
{.fatal: "Incorrect socket instantiation".}
# TODO: Handle 120? or let user handle it.
var reply = ftp.expectReply()
if reply.startsWith("120"):
# 120 Service ready in nnn minutes.
# We wait until we receive 220.
reply = ftp.expectReply()
# Handle 220 messages from the server
assertReply ftp.expectReply(), "220"
if ftp.user != "":

View file

@ -19,7 +19,7 @@ import strutils
type
Url* = tuple[ ## represents a *Uniform Resource Locator* (URL)
## any optional component is "" if it does not exist
scheme, username, password,
scheme, username, password,
hostname, port, path, query, anchor: string]
{.deprecated: [TUrl: Url].}
@ -31,7 +31,7 @@ proc parseUrl*(url: string): Url {.deprecated.} =
var hostname, port, path, query, anchor: string = ""
var temp = ""
if url[i] != '/': # url isn't a relative path
while true:
# Scheme
@ -48,7 +48,7 @@ proc parseUrl*(url: string): Url {.deprecated.} =
password = username.substr(colon+1)
username = username.substr(0, colon-1)
temp.setLen(0)
inc(i) #Skip the @
inc(i) #Skip the @
# hostname(subdomain, domain, port)
if url[i] == '/' or url[i] == '\0':
hostname = temp
@ -56,10 +56,10 @@ proc parseUrl*(url: string): Url {.deprecated.} =
if colon >= 0:
port = hostname.substr(colon+1)
hostname = hostname.substr(0, colon-1)
temp.setLen(0)
break
temp.add(url[i])
inc(i)
@ -75,7 +75,7 @@ proc parseUrl*(url: string): Url {.deprecated.} =
else:
path = temp
temp.setLen(0)
if url[i] == '\0':
if temp[0] == '?':
query = temp
@ -84,10 +84,10 @@ proc parseUrl*(url: string): Url {.deprecated.} =
else:
path = temp
break
temp.add(url[i])
inc(i)
return (scheme, username, password, hostname, port, path, query, anchor)
proc `$`*(u: Url): string {.deprecated.} =
@ -103,12 +103,12 @@ proc `$`*(u: Url): string {.deprecated.} =
result.add(u.password)
result.add("@")
result.add(u.hostname)
if u.port.len > 0:
if u.port.len > 0:
result.add(":")
result.add(u.port)
if u.path.len > 0:
if u.path.len > 0:
result.add("/")
result.add(u.path)
result.add(u.query)
result.add(u.anchor)

View file

@ -0,0 +1,14 @@
import nativesockets
export nativesockets
{.warning: "rawsockets module is deprecated, use nativesockets instead".}
template newRawSocket*(domain, sockType, protocol: cint): expr =
{.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 =
{.warning: "newRawSocket is deprecated, use newNativeSocket instead".}
newNativeSocket(domain, sockType, protocol)

View file

@ -9,7 +9,7 @@
## **Warning:** Since version 0.10.2 this module is deprecated.
## Use the `net <net.html>`_ or the
## `rawsockets <rawsockets.html>`_ module instead.
## `nativesockets <nativesockets.html>`_ module instead.
##
## This module implements portable sockets, it supports a mix of different types
## of sockets. Sockets are buffered by default meaning that data will be
@ -51,17 +51,17 @@ else:
# Note: The enumerations are mapped to Window's constants.
when defined(ssl):
when defined(ssl):
type
SSLError* = object of Exception
SSLCVerifyMode* = enum
CVerifyNone, CVerifyPeer
SSLProtVersion* = enum
protSSLv2, protSSLv3, protTLSv1, protSSLv23
SSLContext* = distinct SSLCTX
SSLAcceptResult* = enum
@ -75,7 +75,7 @@ const
BufferSize*: int = 4000 ## size of a buffered socket's buffer
type
TSocketImpl = object ## socket type
SocketImpl = object ## socket type
fd: SocketHandle
case isBuffered: bool # determines whether this socket is buffered.
of true:
@ -93,11 +93,11 @@ type
sslPeekChar: char
of false: nil
nonblocking: bool
Socket* = ref TSocketImpl
Socket* = ref SocketImpl
Port* = distinct uint16 ## port type
Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed
## here are unsupported.
@ -112,7 +112,7 @@ type
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
Protocol* = enum ## third argument to `socket` proc
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_UDP = 17, ## User datagram protocol.
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
@ -146,8 +146,9 @@ type
{.deprecated: [TSocket: Socket, TType: SockType, TPort: Port, TDomain: Domain,
TProtocol: Protocol, TServent: Servent, THostent: Hostent,
TSOBool: SOBool, TRecvLineResult: RecvLineResult,
TReadLineResult: ReadLineResult, ETimeout: TimeoutError].}
TSOBool: SOBool, TRecvLineResult: RecvLineResult,
TReadLineResult: ReadLineResult, ETimeout: TimeoutError,
TSocketImpl: SocketImpl].}
when defined(booting):
let invalidSocket*: Socket = nil ## invalid socket
@ -177,7 +178,7 @@ proc `==`*(a, b: Port): bool {.borrow.}
proc `$`*(p: Port): string {.borrow.}
## returns the port number as a string
proc ntohl*(x: int32): int32 =
proc ntohl*(x: int32): int32 =
## Converts 32-bit integers from network to host byte order.
## On machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 4-byte swap operation.
@ -205,7 +206,7 @@ proc htons*(x: int16): int16 =
## On machines where the host byte order is the same as network byte
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
result = sockets.ntohs(x)
when defined(Posix):
proc toInt(domain: Domain): cint =
case domain
@ -233,19 +234,19 @@ when defined(Posix):
else: discard
else:
proc toInt(domain: Domain): cint =
proc toInt(domain: Domain): cint =
result = toU16(ord(domain))
proc toInt(typ: SockType): cint =
result = cint(ord(typ))
proc toInt(p: Protocol): cint =
result = cint(ord(p))
proc socket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
# 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)
@ -276,27 +277,27 @@ when defined(ssl):
raise newException(system.IOError, "Certificate file could not be found: " & certFile)
if keyFile != "" and not existsFile(keyFile):
raise newException(system.IOError, "Key file could not be found: " & keyFile)
if certFile != "":
var ret = SSLCTXUseCertificateChainFile(ctx, certFile)
if ret != 1:
raiseSslError()
# TODO: Password? www.rtfm.com/openssl-examples/part1.pdf
if keyFile != "":
if SSL_CTX_use_PrivateKey_file(ctx, keyFile,
SSL_FILETYPE_PEM) != 1:
raiseSslError()
if SSL_CTX_check_private_key(ctx) != 1:
raiseSslError("Verification of private key file failed.")
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
certFile = "", keyFile = ""): SSLContext =
## Creates an SSL context.
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 are
## are available with the addition of ``ProtSSLv23`` which allows for
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 are
## are available with the addition of ``ProtSSLv23`` which allows for
## compatibility with all of them.
##
## There are currently only two options for verify mode;
@ -313,15 +314,12 @@ when defined(ssl):
of protSSLv23:
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
of protSSLv2:
when not defined(linux) and not defined(OpenBSD):
newCTX = SSL_CTX_new(SSLv2_method())
else:
raiseSslError()
raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3")
of protSSLv3:
newCTX = SSL_CTX_new(SSLv3_method())
of protTLSv1:
newCTX = SSL_CTX_new(TLSv1_method())
if newCTX.SSLCTXSetCipherList("ALL") != 1:
raiseSslError()
case verifyMode
@ -342,7 +340,7 @@ when defined(ssl):
##
## **Disclaimer**: This code is not well tested, may be very unsafe and
## prone to security vulnerabilities.
socket.isSSL = true
socket.sslContext = ctx
socket.sslHandle = SSLNew(SSLCTX(socket.sslContext))
@ -350,7 +348,7 @@ when defined(ssl):
socket.sslHasPeekChar = false
if socket.sslHandle == nil:
raiseSslError()
if SSLSetFd(socket.sslHandle, socket.fd) != 1:
raiseSslError()
@ -381,7 +379,7 @@ proc raiseSocketError*(socket: Socket, err: int = -1, async = false) =
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
raiseSslError()
else: raiseSslError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
let lastError = osLastError()
if async:
@ -396,15 +394,15 @@ proc raiseSocketError*(socket: Socket, err: int = -1, async = false) =
else: raiseOSError(lastError)
proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## queue of pending connections.
if listen(socket.fd, cint(backlog)) < 0'i32: raiseOSError(osLastError())
proc invalidIp4(s: string) {.noreturn, noinline.} =
raise newException(ValueError, "invalid ip4 address: " & s)
proc parseIp4*(s: string): BiggestInt =
proc parseIp4*(s: string): BiggestInt =
## parses an IP version 4 in dotted decimal form like "a.b.c.d".
##
## This is equivalent to `inet_ntoa`:idx:.
@ -440,7 +438,7 @@ template gaiNim(a, p, h, list: expr): stmt =
when defined(windows):
raiseOSError(osLastError())
else:
raise newException(OSError, $gai_strerror(gaiResult))
raiseOSError(osLastError(), $gai_strerror(gaiResult))
proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
tags: [ReadIOEffect].} =
@ -468,8 +466,8 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
gaiNim(address, port, hints, aiList)
if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32:
raiseOSError(osLastError())
proc getSockName*(socket: Socket): Port =
proc getSockName*(socket: Socket): Port =
## returns the socket's associated port number.
var name: Sockaddr_in
when defined(Windows):
@ -484,14 +482,14 @@ proc getSockName*(socket: Socket): Port =
raiseOSError(osLastError())
result = Port(sockets.ntohs(name.sin_port))
template acceptAddrPlain(noClientRet, successRet: expr,
template acceptAddrPlain(noClientRet, successRet: expr,
sslImplementation: stmt): stmt {.immediate.} =
assert(client != nil)
var sockAddress: Sockaddr_in
var addrLen = sizeof(sockAddress).SockLen
var sock = accept(server.fd, cast[ptr SockAddr](addr(sockAddress)),
addr(addrLen))
if sock == osInvalidSocket:
let err = osLastError()
when defined(windows):
@ -536,7 +534,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string) {.
## The resulting client will inherit any properties of the server socket. For
## example: whether the socket is buffered or not.
##
## **Note**: ``client`` must be initialised (with ``new``), this function
## **Note**: ``client`` must be initialised (with ``new``), this function
## makes no effort to initialise the ``client`` variable.
##
## **Warning:** When using SSL with non-blocking sockets, it is best to use
@ -545,7 +543,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string) {.
when defined(ssl):
if server.isSSL:
# We must wrap the client sock in a ssl context.
server.sslContext.wrapSocket(client)
let ret = SSLAccept(client.sslHandle)
while ret <= 0:
@ -571,9 +569,9 @@ when defined(ssl):
proc acceptAddrSSL*(server: Socket, client: var Socket,
address: var string): SSLAcceptResult {.
tags: [ReadIOEffect].} =
## This procedure should only be used for non-blocking **SSL** sockets.
## This procedure should only be used for non-blocking **SSL** sockets.
## It will immediately return with one of the following values:
##
##
## ``AcceptSuccess`` will be returned when a client has been successfully
## accepted and the handshake has been successfully performed between
## ``server`` and the newly connected client.
@ -590,7 +588,7 @@ when defined(ssl):
if server.isSSL:
client.setBlocking(false)
# We must wrap the client sock in a ssl context.
if not client.isSSL or client.sslHandle == nil:
server.sslContext.wrapSocket(client)
let ret = SSLAccept(client.sslHandle)
@ -622,10 +620,10 @@ when defined(ssl):
proc accept*(server: Socket, client: var Socket) {.tags: [ReadIOEffect].} =
## Equivalent to ``acceptAddr`` but doesn't return the address, only the
## socket.
##
##
## **Note**: ``client`` must be initialised (with ``new``), this function
## makes no effort to initialise the ``client`` variable.
var addrDummy = ""
acceptAddr(server, client, addrDummy)
@ -661,7 +659,7 @@ proc close*(socket: Socket) =
socket.sslHandle = nil
proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## Searches the database from the beginning and finds the first entry for
## which the service name specified by ``name`` matches the s_name member
## and the protocol name specified by ``proto`` matches the s_proto member.
##
@ -670,15 +668,15 @@ proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
var s = winlean.getservbyname(name, proto)
else:
var s = posix.getservbyname(name, proto)
if s == nil: raise newException(OSError, "Service not found.")
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
## protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
@ -686,7 +684,7 @@ proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].}
var s = winlean.getservbyport(ze(int16(port)).cint, proto)
else:
var s = posix.getservbyport(ze(int16(port)).cint, proto)
if s == nil: raise newException(OSError, "Service not found.")
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
@ -696,20 +694,20 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the hostname of an IP Address.
var myaddr: InAddr
myaddr.s_addr = inet_addr(ip)
when defined(windows):
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
cint(sockets.AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
if s == nil:
raise newException(OSError, $hstrerror(h_errno))
raiseOSError(osLastError(), $hstrerror(h_errno))
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when defined(windows):
when defined(windows):
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
@ -717,11 +715,11 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
raiseOSError(osLastError(), "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the IP address of a hostname.
when defined(Windows):
var s = winlean.gethostbyname(name)
@ -730,7 +728,7 @@ proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
if s == nil: raiseOSError(osLastError())
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when defined(windows):
when defined(windows):
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
@ -738,16 +736,16 @@ proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
raiseOSError(osLastError(), "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getSockOptInt*(socket: Socket, level, optname: int): int {.
tags: [ReadIOEffect].} =
tags: [ReadIOEffect].} =
## getsockopt for integer options.
var res: cint
var size = sizeof(res).SockLen
if getsockopt(socket.fd, cint(level), cint(optname),
if getsockopt(socket.fd, cint(level), cint(optname),
addr(res), addr(size)) < 0'i32:
raiseOSError(osLastError())
result = int(res)
@ -756,7 +754,7 @@ proc setSockOptInt*(socket: Socket, level, optname, optval: int) {.
tags: [WriteIOEffect].} =
## setsockopt for integer options.
var value = cint(optval)
if setsockopt(socket.fd, cint(level), cint(optname), addr(value),
if setsockopt(socket.fd, cint(level), cint(optname), addr(value),
sizeof(value).SockLen) < 0'i32:
raiseOSError(osLastError())
@ -775,7 +773,7 @@ proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {.
## Retrieves option ``opt`` as a boolean value.
var res: cint
var size = sizeof(res).SockLen
if getsockopt(socket.fd, cint(level), toCInt(opt),
if getsockopt(socket.fd, cint(level), toCInt(opt),
addr(res), addr(size)) < 0'i32:
raiseOSError(osLastError())
result = res != 0
@ -784,11 +782,11 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {
tags: [WriteIOEffect].} =
## Sets option ``opt`` to a boolean value specified by ``value``.
var valuei = cint(if value: 1 else: 0)
if setsockopt(socket.fd, cint(level), toCInt(opt), addr(valuei),
if setsockopt(socket.fd, cint(level), toCInt(opt), addr(valuei),
sizeof(valuei).SockLen) < 0'i32:
raiseOSError(osLastError())
proc connect*(socket: Socket, address: string, port = Port(0),
proc connect*(socket: Socket, address: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a
## host name. If ``address`` is a host name, this function will try each IP
@ -815,7 +813,7 @@ proc connect*(socket: Socket, address: string, port = Port(0),
freeaddrinfo(aiList)
if not success: raiseOSError(lastError)
when defined(ssl):
if socket.isSSL:
let ret = SSLConnect(socket.sslHandle)
@ -824,7 +822,7 @@ proc connect*(socket: Socket, address: string, port = Port(0),
case err
of SSL_ERROR_ZERO_RETURN:
raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT,
of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT,
SSL_ERROR_WANT_ACCEPT:
raiseSslError("The operation did not complete. Perhaps you should use connectAsync?")
of SSL_ERROR_WANT_X509_LOOKUP:
@ -833,7 +831,7 @@ proc connect*(socket: Socket, address: string, port = Port(0),
raiseSslError()
else:
raiseSslError("Unknown error")
when false:
var s: TSockAddrIn
s.sin_addr.s_addr = inet_addr(address)
@ -841,7 +839,7 @@ proc connect*(socket: Socket, address: string, port = Port(0),
when defined(windows):
s.sin_family = toU16(ord(af))
else:
case af
case af
of AF_UNIX: s.sin_family = posix.AF_UNIX
of AF_INET: s.sin_family = posix.AF_INET
of AF_INET6: s.sin_family = posix.AF_INET6
@ -885,7 +883,7 @@ proc connectAsync*(socket: Socket, name: string, port = Port(0),
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
success = true
break
it = it.ai_next
freeaddrinfo(aiList)
@ -941,12 +939,12 @@ proc timeValFromMilliseconds(timeout = 500): Timeval =
result.tv_sec = seconds.int32
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
proc createFdSet(fd: var TFdSet, s: seq[Socket], m: var int) =
proc createFdSet(fd: var TFdSet, s: seq[Socket], m: var int) =
FD_ZERO(fd)
for i in items(s):
for i in items(s):
m = max(m, int(i.fd))
FD_SET(i.fd, fd)
proc pruneSocketSet(s: var seq[Socket], fd: var TFdSet) =
var i = 0
var L = s.len
@ -981,13 +979,13 @@ proc checkBuffer(readfds: var seq[Socket]): int =
if result > 0:
readfds = res
proc select*(readfds, writefds, exceptfds: var seq[Socket],
timeout = 500): int {.tags: [ReadIOEffect].} =
proc select*(readfds, writefds, exceptfds: var seq[Socket],
timeout = 500): int {.tags: [ReadIOEffect].} =
## Traditional select function. This function will return the number of
## sockets that are ready to be read from, written to, or which have errors.
## If there are none; 0 is returned.
## ``Timeout`` is in miliseconds and -1 can be specified for no timeout.
##
## If there are none; 0 is returned.
## ``Timeout`` is in milliseconds and -1 can be specified for no timeout.
##
## Sockets which are **not** ready for reading, writing or which don't have
## errors waiting on them are removed from the ``readfds``, ``writefds``,
## ``exceptfds`` sequences respectively.
@ -996,63 +994,63 @@ proc select*(readfds, writefds, exceptfds: var seq[Socket],
return buffersFilled
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd, wr, ex: TFdSet
var m = 0
createFdSet((rd), readfds, m)
createFdSet((wr), writefds, m)
createFdSet((ex), exceptfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), nil))
pruneSocketSet(readfds, (rd))
pruneSocketSet(writefds, (wr))
pruneSocketSet(exceptfds, (ex))
proc select*(readfds, writefds: var seq[Socket],
proc select*(readfds, writefds: var seq[Socket],
timeout = 500): int {.tags: [ReadIOEffect].} =
## Variant of select with only a read and write list.
let buffersFilled = checkBuffer(readfds)
if buffersFilled > 0:
return buffersFilled
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd, wr: TFdSet
var m = 0
createFdSet((rd), readfds, m)
createFdSet((wr), writefds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), addr(wr), nil, nil))
pruneSocketSet(readfds, (rd))
pruneSocketSet(writefds, (wr))
proc selectWrite*(writefds: var seq[Socket],
proc selectWrite*(writefds: var seq[Socket],
timeout = 500): int {.tags: [ReadIOEffect].} =
## When a socket in ``writefds`` is ready to be written to then a non-zero
## value will be returned specifying the count of the sockets which can be
## written to. The sockets which **cannot** be written to will also be removed
## from ``writefds``.
##
## ``timeout`` is specified in miliseconds and ``-1`` can be specified for
## ``timeout`` is specified in milliseconds and ``-1`` can be specified for
## an unlimited time.
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var wr: TFdSet
var m = 0
createFdSet((wr), writefds, m)
if timeout != -1:
result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv)))
else:
result = int(select(cint(m+1), nil, addr(wr), nil, nil))
pruneSocketSet(writefds, (wr))
proc select*(readfds: var seq[Socket], timeout = 500): int =
@ -1061,16 +1059,16 @@ proc select*(readfds: var seq[Socket], timeout = 500): int =
if buffersFilled > 0:
return buffersFilled
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd: TFdSet
var m = 0
createFdSet((rd), readfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), nil, nil, nil))
pruneSocketSet(readfds, (rd))
proc readIntoBuf(socket: Socket, flags: int32): int =
@ -1106,12 +1104,12 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
if socket.isBuffered:
if socket.bufLen == 0:
retRead(0'i32, 0)
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
let chunk = min(socket.bufLen-socket.currPos, size-read)
var d = cast[cstring](data)
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
@ -1154,7 +1152,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
else:
if timeout - int(waited * 1000.0) < 1:
raise newException(TimeoutError, "Call to '" & funcName & "' timed out.")
when defined(ssl):
if socket.isSSL:
if socket.hasDataBuffered:
@ -1163,7 +1161,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
let sslPending = SSLPending(socket.sslHandle)
if sslPending != 0:
return sslPending
var s = @[socket]
var startTime = epochTime()
let selRet = select(s, timeout - int(waited * 1000.0))
@ -1174,9 +1172,9 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
proc recv*(socket: Socket, data: pointer, size: int, timeout: int): int {.
tags: [ReadIOEffect, TimeEffect].} =
## overload with a ``timeout`` parameter in miliseconds.
var waited = 0.0 # number of seconds already waited
## overload with a ``timeout`` parameter in milliseconds.
var waited = 0.0 # number of seconds already waited
var read = 0
while read < size:
let avail = waitFor(socket, waited, timeout, size-read, "recv")
@ -1186,7 +1184,7 @@ proc recv*(socket: Socket, data: pointer, size: int, timeout: int): int {.
if result < 0:
return result
inc(read, result)
result = read
proc recv*(socket: Socket, data: var string, size: int, timeout = -1): int =
@ -1197,7 +1195,7 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1): int =
## This function will throw an EOS exception when an error occurs. A value
## lower than 0 is never returned.
##
## A timeout may be specified in miliseconds, if enough data is not received
## A timeout may be specified in milliseconds, if enough data is not received
## within the time specified an ETimeout exception will be raised.
##
## **Note**: ``data`` must be initialised.
@ -1230,7 +1228,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
var res = socket.readIntoBuf(0'i32)
if res <= 0:
result = res
c = socket.buffer[socket.currPos]
else:
when defined(ssl):
@ -1238,7 +1236,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = true
c = socket.sslPeekChar
return
result = recv(socket.fd, addr(c), 1, MSG_PEEK)
@ -1250,20 +1248,20 @@ proc recvLine*(socket: Socket, line: var TaintedString, timeout = -1): bool {.
## If a full line is received ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is received then ``line``
## will be set to it.
##
##
## ``True`` is returned if data is available. ``False`` suggests an
## error, EOS exceptions are not raised and ``False`` is simply returned
## instead.
##
##
## If the socket is disconnected, ``line`` will be set to ``""`` and ``True``
## will be returned.
##
## A timeout can be specified in miliseconds, if data is not received within
## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised.
##
## **Deprecated since version 0.9.2**: This function has been deprecated in
## favour of readLine.
template addNLIfEmpty(): stmt =
if line.len == 0:
line.add("\c\L")
@ -1285,7 +1283,7 @@ proc recvLine*(socket: Socket, line: var TaintedString, timeout = -1): bool {.
elif n <= 0: return false
addNLIfEmpty()
return true
elif c == '\L':
elif c == '\L':
addNLIfEmpty()
return true
add(line.string, c)
@ -1297,14 +1295,14 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1) {.
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## An EOS exception will be raised in the case of a socket error.
##
## A timeout can be specified in miliseconds, if data is not received within
## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised.
template addNLIfEmpty(): stmt =
if line.len == 0:
line.add("\c\L")
@ -1326,12 +1324,12 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1) {.
elif n <= 0: socket.raiseSocketError()
addNLIfEmpty()
return
elif c == '\L':
elif c == '\L':
addNLIfEmpty()
return
add(line.string, c)
proc recvLineAsync*(socket: Socket,
proc recvLineAsync*(socket: Socket,
line: var TaintedString): RecvLineResult {.tags: [ReadIOEffect], deprecated.} =
## Similar to ``recvLine`` but designed for non-blocking sockets.
##
@ -1349,21 +1347,21 @@ proc recvLineAsync*(socket: Socket,
while true:
var c: char
var n = recv(socket, addr(c), 1)
if n < 0:
if n < 0:
return (if line.len == 0: RecvFail else: RecvPartialLine)
elif n == 0:
elif n == 0:
return (if line.len == 0: RecvDisconnected else: RecvPartialLine)
if c == '\r':
n = peekChar(socket, c)
if n > 0 and c == '\L':
discard recv(socket, addr(c), 1)
elif n <= 0:
elif n <= 0:
return (if line.len == 0: RecvFail else: RecvPartialLine)
return RecvFullLine
elif c == '\L': return RecvFullLine
add(line.string, c)
proc readLineAsync*(socket: Socket,
proc readLineAsync*(socket: Socket,
line: var TaintedString): ReadLineResult {.tags: [ReadIOEffect].} =
## Similar to ``recvLine`` but designed for non-blocking sockets.
##
@ -1375,24 +1373,24 @@ proc readLineAsync*(socket: Socket,
## * If no data could be retrieved; ``ReadNone`` is returned.
## * If call to ``recv`` failed; **an EOS exception is raised.**
setLen(line.string, 0)
template errorOrNone =
socket.raiseSocketError(async = true)
return ReadNone
while true:
var c: char
var n = recv(socket, addr(c), 1)
#echo(n)
if n < 0:
if line.len == 0: errorOrNone else: return ReadPartialLine
elif n == 0:
elif n == 0:
return (if line.len == 0: ReadDisconnected else: ReadPartialLine)
if c == '\r':
n = peekChar(socket, c)
if n > 0 and c == '\L':
discard recv(socket, addr(c), 1)
elif n <= 0:
elif n <= 0:
if line.len == 0: errorOrNone else: return ReadPartialLine
return ReadFullLine
elif c == '\L': return ReadFullLine
@ -1423,7 +1421,7 @@ proc recv*(socket: Socket): TaintedString {.tags: [ReadIOEffect], deprecated.} =
var bytesRead = recv(socket, cstring(buf), bufSize-1)
# Error
if bytesRead == -1: OSError(osLastError())
buf[bytesRead] = '\0' # might not be necessary
setLen(buf, bytesRead)
add(result.string, buf)
@ -1433,7 +1431,7 @@ proc recv*(socket: Socket): TaintedString {.tags: [ReadIOEffect], deprecated.} =
proc recvTimeout*(socket: Socket, timeout: int): TaintedString {.
tags: [ReadIOEffect], deprecated.} =
## overloaded variant to support a ``timeout`` parameter, the ``timeout``
## parameter specifies the amount of miliseconds to wait for data on the
## parameter specifies the amount of milliseconds to wait for data on the
## socket.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
@ -1441,13 +1439,13 @@ proc recvTimeout*(socket: Socket, timeout: int): TaintedString {.
var s = @[socket]
if s.select(timeout) != 1:
raise newException(TimeoutError, "Call to recv() timed out.")
return socket.recv
{.pop.}
proc recvAsync*(socket: Socket, s: var TaintedString): bool {.
tags: [ReadIOEffect], deprecated.} =
## receives all the data from a non-blocking socket. If socket is non-blocking
## receives all the data from a non-blocking socket. If socket is non-blocking
## and there are no messages available, `False` will be returned.
## Other socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected
@ -1477,7 +1475,7 @@ proc recvAsync*(socket: Socket, s: var TaintedString): bool {.
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
raiseSslError()
else: raiseSslError("Unknown Error")
if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false):
let err = osLastError()
when defined(windows):
@ -1509,7 +1507,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int,
## so when ``socket`` is buffered the non-buffered implementation will be
## used. Therefore if ``socket`` contains something in its buffer this
## function will make no effort to return it.
# TODO: Buffered sockets
data.setLen(length)
var sockAddress: Sockaddr_in
@ -1523,7 +1521,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int,
port = ntohs(sockAddress.sin_port).Port
proc recvFromAsync*(socket: Socket, data: var string, length: int,
address: var string, port: var Port,
address: var string, port: var Port,
flags = 0'i32): bool {.tags: [ReadIOEffect].} =
## Variant of ``recvFrom`` for non-blocking sockets. Unlike ``recvFrom``,
## this function will raise an EOS error whenever a socket error occurs.
@ -1554,7 +1552,7 @@ proc skip*(socket: Socket) {.tags: [ReadIOEffect], deprecated.} =
proc skip*(socket: Socket, size: int, timeout = -1) =
## Skips ``size`` amount of bytes.
##
## An optional timeout can be specified in miliseconds, if skipping the
## An optional timeout can be specified in milliseconds, if skipping the
## bytes takes longer than specified an ETimeout exception will be raised.
##
## Returns the number of skipped bytes.
@ -1572,11 +1570,11 @@ proc send*(socket: Socket, data: pointer, size: int): int {.
when defined(ssl):
if socket.isSSL:
return SSLWrite(socket.sslHandle, cast[cstring](data), size)
when defined(windows) or defined(macosx):
result = send(socket.fd, data, size.cint, 0'i32)
else:
when defined(solaris):
when defined(solaris):
const MSG_NOSIGNAL = 0
result = send(socket.fd, data, size, int32(MSG_NOSIGNAL))
@ -1589,11 +1587,11 @@ proc send*(socket: Socket, data: string) {.tags: [WriteIOEffect].} =
when defined(ssl):
if socket.isSSL:
raiseSslError()
raiseOSError(osLastError())
if sent != data.len:
raise newException(OSError, "Could not send all data.")
raiseOSError(osLastError(), "Could not send all data.")
proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} =
## sends data to a non-blocking socket.
@ -1632,7 +1630,7 @@ proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} =
if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK:
return 0
else: raiseOSError(err)
proc trySend*(socket: Socket, data: string): bool {.tags: [WriteIOEffect].} =
## safe alternative to ``send``. Does not raise an EOS when an error occurs,
@ -1643,7 +1641,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
size: int, af: Domain = AF_INET, flags = 0'i32): int {.
tags: [WriteIOEffect].} =
## low-level sendTo proc. This proc sends ``data`` to the specified ``address``,
## which may be an IP address or a hostname, if a hostname is specified
## which may be an IP address or a hostname, if a hostname is specified
## this function will try each IP of that hostname.
##
## **Note:** This proc is not available for SSL sockets.
@ -1653,7 +1651,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
hints.ai_socktype = toInt(SOCK_STREAM)
hints.ai_protocol = toInt(IPPROTO_TCP)
gaiNim(address, port, hints, aiList)
# try all possibilities:
var success = false
var it = aiList
@ -1667,7 +1665,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
freeaddrinfo(aiList)
proc sendTo*(socket: Socket, address: string, port: Port,
proc sendTo*(socket: Socket, address: string, port: Port,
data: string): int {.tags: [WriteIOEffect].} =
## Friendlier version of the low-level ``sendTo``.
result = socket.sendTo(address, port, cstring(data), data.len)
@ -1676,10 +1674,10 @@ when defined(Windows):
const
IOCPARM_MASK = 127
IOC_IN = int(-2147483648)
FIONBIO = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
FIONBIO = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
(102 shl 8) or 126
proc ioctlsocket(s: SocketHandle, cmd: clong,
proc ioctlsocket(s: SocketHandle, cmd: clong,
argptr: ptr clong): cint {.
stdcall, importc:"ioctlsocket", dynlib: "ws2_32.dll".}
@ -1708,11 +1706,11 @@ proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} =
## Connects to server as specified by ``address`` on port specified by ``port``.
##
## The ``timeout`` paremeter specifies the time in miliseconds to allow for
## The ``timeout`` paremeter specifies the time in milliseconds to allow for
## the connection to the server to be made.
let originalStatus = not socket.nonblocking
socket.setBlocking(false)
socket.connectAsync(address, port, af)
var s: seq[Socket] = @[socket]
if selectWrite(s, timeout) != 1:

View file

@ -7,40 +7,75 @@
# distribution, for details about the copyright.
#
## A higher level `mySQL`:idx: database wrapper. The same interface is
## A higher level `mySQL`:idx: database wrapper. The same interface is
## implemented for other databases too.
##
## Example:
##
## .. code-block:: Nim
##
## import db_mysql, math
##
## let theDb = open("localhost", "nim", "nim", "test")
##
## theDb.exec(sql"Drop table if exists myTestTbl")
## theDb.exec(sql("create table myTestTbl (" &
## " Id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, " &
## " Name VARCHAR(50) NOT NULL, " &
## " i INT(11), " &
## " f DECIMAL(18,10))"))
##
## theDb.exec(sql"START TRANSACTION")
## for i in 1..1000:
## theDb.exec(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
## "Item#" & $i, i, sqrt(i.float))
## theDb.exec(sql"COMMIT")
##
## for x in theDb.fastRows(sql"select * from myTestTbl"):
## echo x
##
## let id = theDb.tryInsertId(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
## "Item#1001", 1001, sqrt(1001.0))
## echo "Inserted item: ", theDb.getValue(sql"SELECT name FROM myTestTbl WHERE id=?", id)
##
## theDb.close()
import strutils, mysql
type
TDbConn* = PMySQL ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be
DbConn* = PMySQL ## encapsulates a database connection
Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string.
InstantRow* = tuple[row: cstringArray, len: int] ## a handle that can be
## used to get a row's
## column text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string
SqlQuery* = distinct string ## an SQL query string
FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn].}
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be
proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"``
##
## If assertions are turned off, it does nothing. If assertions are turned
## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax.
result = TSqlQuery(query)
result = SqlQuery(query)
proc dbError(db: TDbConn) {.noreturn.} =
proc dbError(db: DbConn) {.noreturn.} =
## raises an EDb exception.
var e: ref EDb
new(e)
e.msg = $mysql.error(db)
raise e
proc dbError*(msg: string) {.noreturn.} =
proc dbError*(msg: string) {.noreturn.} =
## raises an EDb exception with message `msg`.
var e: ref EDb
new(e)
@ -48,12 +83,12 @@ proc dbError*(msg: string) {.noreturn.} =
raise e
when false:
proc dbQueryOpt*(db: TDbConn, query: string, args: varargs[string, `$`]) =
proc dbQueryOpt*(db: DbConn, query: string, args: varargs[string, `$`]) =
var stmt = mysql_stmt_init(db)
if stmt == nil: dbError(db)
if mysql_stmt_prepare(stmt, query, len(query)) != 0:
if mysql_stmt_prepare(stmt, query, len(query)) != 0:
dbError(db)
var
var
binding: seq[MYSQL_BIND]
discard mysql_stmt_close(stmt)
@ -65,7 +100,7 @@ proc dbQuote*(s: string): string =
else: add(result, c)
add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = ""
var a = 0
for c in items(string(formatstr)):
@ -75,39 +110,43 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
else:
add(result, dbQuote(args[a]))
inc(a)
else:
else:
add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): bool {.
proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.
tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args)
return mysql.realQuery(db, q, q.len) == 0'i32
proc rawExec(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) =
proc rawExec(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) =
var q = dbFormat(query, args)
if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful.
var q = dbFormat(query, args)
if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
proc newRow(L: int): TRow =
proc newRow(L: int): Row =
newSeq(result, L)
for i in 0..L-1: result[i] = ""
proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
if row != nil:
while mysql.fetchRow(sqlres) != nil: discard
mysql.freeResult(sqlres)
iterator fastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For MySQL this is the case!.
iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset.
##
## This is very fast, but potentially dangerous. Use this iterator only
## if you require **ALL** the rows.
##
## Breaking the fastRows() iterator during a loop will cause the next
## database query to raise an [EDb] exception ``Commands out of sync``.
rawExec(db, query, args)
var sqlres = mysql.useResult(db)
if sqlres != nil:
@ -117,7 +156,7 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
while true:
row = mysql.fetchRow(sqlres)
if row == nil: break
for i in 0..L-1:
for i in 0..L-1:
setLen(result[i], 0)
if row[i] == nil:
result[i] = nil
@ -126,18 +165,42 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
yield result
properFreeResult(sqlres, row)
proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within the interator body.
rawExec(db, query, args)
var sqlres = mysql.useResult(db)
if sqlres != nil:
let L = int(mysql.numFields(sqlres))
var row: cstringArray
while true:
row = mysql.fetchRow(sqlres)
if row == nil: break
yield (row: row, len: L)
properFreeResult(sqlres, row)
proc `[]`*(row: InstantRow, col: int): string {.inline.} =
## returns text for given column of the row
$row.row[col]
proc len*(row: InstantRow): int {.inline.} =
## returns number of columns in the row
row.len
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column.
## will return a Row with empty strings for each column.
rawExec(db, query, args)
var sqlres = mysql.useResult(db)
if sqlres != nil:
var L = int(mysql.numFields(sqlres))
result = newRow(L)
var row = mysql.fetchRow(sqlres)
if row != nil:
for i in 0..L-1:
if row != nil:
for i in 0..L-1:
setLen(result[i], 0)
if row[i] == nil:
result[i] = nil
@ -145,8 +208,8 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
add(result[i], row[i])
properFreeResult(sqlres, row)
proc getAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset.
result = @[]
rawExec(db, query, args)
@ -168,70 +231,67 @@ proc getAllRows*(db: TDbConn, query: TSqlQuery,
inc(j)
mysql.freeResult(sqlres)
iterator rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## same as `fastRows`, but slower and safe.
for r in items(getAllRows(db, query, args)): yield r
proc getValue*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDB].} =
proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database
## value is NULL.
result = ""
for row in fastRows(db, query, args):
result = row[0]
break
result = getRow(db, query, args)[0]
proc tryInsertId*(db: TDbConn, query: TSqlQuery,
proc tryInsertId*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error.
var q = dbFormat(query, args)
if mysql.realQuery(db, q, q.len) != 0'i32:
if mysql.realQuery(db, q, q.len) != 0'i32:
result = -1'i64
else:
result = mysql.insertId(db)
proc insertId*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
proc insertId*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
## generated ID for the row.
result = tryInsertID(db, query, args)
if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.
tags: [FReadDB, FWriteDb].} =
tags: [FReadDB, FWriteDb].} =
## runs the query (typically "UPDATE") and returns the
## number of affected rows
rawExec(db, query, args)
result = mysql.affectedRows(db)
proc close*(db: TDbConn) {.tags: [FDb].} =
proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection.
if db != nil: mysql.close(db)
proc open*(connection, user, password, database: string): TDbConn {.
proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not
## be established.
result = mysql.init(nil)
if result == nil: dbError("could not open database connection")
if result == nil: dbError("could not open database connection")
let
colonPos = connection.find(':')
host = if colonPos < 0: connection
else: substr(connection, 0, colonPos-1)
port: int32 = if colonPos < 0: 0'i32
else: substr(connection, colonPos+1).parseInt.int32
if mysql.realConnect(result, host, user, password, database,
if mysql.realConnect(result, host, user, password, database,
port, nil, 0) == nil:
var errmsg = $mysql.error(result)
db_mysql.close(result)
dbError(errmsg)
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## sets the encoding of a database connection, returns true for
## success, false for failure.
result = mysql.set_character_set(connection, encoding) == 0
result = mysql.set_character_set(connection, encoding) == 0

View file

@ -7,34 +7,89 @@
# distribution, for details about the copyright.
#
## A higher level `PostgreSQL`:idx: database wrapper. This interface
## is implemented for other databases too.
## A higher level `PostgreSQL`:idx: database wrapper. This interface
## is implemented for other databases also.
##
## Parameter substitution
## ----------------------
##
## All ``db_*`` modules support the same form of parameter substitution.
## That is, using the ``?`` (question mark) to signify the place where a
## value should be placed. For example:
##
## .. code-block:: Nim
## sql"INSERT INTO myTable (colA, colB, colC) VALUES (?, ?, ?)"
##
## **Note**: There are two approaches to parameter substitution support by
## this module.
##
## 1. ``SqlQuery`` using ``?, ?, ?, ...`` (same as all the ``db_*`` modules)
##
## 2. ``SqlPrepared`` using ``$1, $2, $3, ...``
##
## .. code-block:: Nim
## prepare(db, "myExampleInsert",
## sql"""INSERT INTO myTable
## (colA, colB, colC)
## VALUES ($1, $2, $3)""",
## 3)
##
## Examples
## --------
##
## Opening a connection to a database
## ==================================
##
## .. code-block:: Nim
## import db_postgres
## let db = open("localhost", "user", "password", "dbname")
## db.close()
##
## Creating a table
## ================
##
## .. code-block:: Nim
## db.exec(sql"DROP TABLE IF EXISTS myTable")
## db.exec(sql("""CREATE TABLE myTable (
## id integer,
## name varchar(50) not null)"""))
##
## Inserting data
## ==============
##
## .. code-block:: Nim
## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)",
## "Dominik")
import strutils, postgres
type
TDbConn* = PPGconn ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be
DbConn* = PPGconn ## encapsulates a database connection
Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string.
InstantRow* = tuple[res: PPGresult, line: int32] ## a handle that can be
## used to get a row's
## column text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string
TSqlPrepared* = distinct string ## a identifier for the prepared queries
SqlQuery* = distinct string ## an SQL query string
SqlPrepared* = distinct string ## a identifier for the prepared queries
FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn,
TSqlPrepared: SqlPrepared].}
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be
proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"``
##
## If assertions are turned off, it does nothing. If assertions are turned
## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax.
result = TSqlQuery(query)
proc dbError*(db: TDbConn) {.noreturn.} =
result = SqlQuery(query)
proc dbError*(db: DbConn) {.noreturn.} =
## raises an EDb exception.
var e: ref EDb
new(e)
@ -56,9 +111,11 @@ proc dbQuote*(s: string): string =
else: add(result, c)
add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = ""
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:
@ -68,28 +125,34 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
inc(a)
else:
add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery,
proc tryExec*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise.
var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil,
nil, nil, 0)
result = pqresultStatus(res) == PGRES_COMMAND_OK
pqclear(res)
proc tryExec*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise.
var arr = allocCStringArray(args)
var res = pqexecParams(db, query.string, int32(args.len), nil, arr,
nil, nil, 0)
var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
nil, nil, 0)
deallocCStringArray(arr)
result = pqresultStatus(res) == PGRES_COMMAND_OK
pqclear(res)
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful.
var arr = allocCStringArray(args)
var res = pqexecParams(db, query.string, int32(args.len), nil, arr,
var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil,
nil, nil, 0)
deallocCStringArray(arr)
if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
pqclear(res)
proc exec*(db: TDbConn, stmtName: TSqlPrepared,
proc exec*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string]) {.tags: [FReadDB, FWriteDb].} =
var arr = allocCStringArray(args)
var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
@ -98,19 +161,20 @@ proc exec*(db: TDbConn, stmtName: TSqlPrepared,
if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
pqclear(res)
proc newRow(L: int): TRow =
proc newRow(L: int): Row =
newSeq(result, L)
for i in 0..L-1: result[i] = ""
proc setupQuery(db: TDbConn, query: TSqlQuery,
proc setupQuery(db: DbConn, query: SqlQuery,
args: varargs[string]): PPGresult =
var arr = allocCStringArray(args)
result = pqexecParams(db, query.string, int32(args.len), nil, arr,
# s is a dummy unique id str for each setupQuery query
let s = "setupQuery_Query_" & string(query)
var res = pqprepare(db, s, dbFormat(query, args), 0, nil)
result = pqexecPrepared(db, s, 0, nil,
nil, nil, 0)
deallocCStringArray(arr)
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
proc setupQuery(db: TDbConn, stmtName: TSqlPrepared,
proc setupQuery(db: DbConn, stmtName: SqlPrepared,
args: varargs[string]): PPGresult =
var arr = allocCStringArray(args)
result = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
@ -118,13 +182,15 @@ proc setupQuery(db: TDbConn, stmtName: TSqlPrepared,
deallocCStringArray(arr)
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
proc prepare*(db: TDbConn; stmtName: string, query: TSqlQuery;
nParams: int): TSqlPrepared =
proc prepare*(db: DbConn; stmtName: string, query: SqlQuery;
nParams: int): SqlPrepared =
if nParams > 0 and not string(query).contains("$1"):
dbError("""parameter substitution expects "$1" """)
var res = pqprepare(db, stmtName, query.string, int32(nParams), nil)
if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
return TSqlPrepared(stmtName)
proc setRow(res: PPGresult, r: var TRow, line, cols: int32) =
return SqlPrepared(stmtName)
proc setRow(res: PPGresult, r: var Row, line, cols: int32) =
for col in 0..cols-1:
setLen(r[col], 0)
let x = pqgetvalue(res, line, col)
@ -133,9 +199,9 @@ proc setRow(res: PPGresult, r: var TRow, line, cols: int32) =
else:
add(r[col], x)
iterator fastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very
iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Postgres it is safe though.
var res = setupQuery(db, query, args)
@ -146,8 +212,8 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
yield result
pqclear(res)
iterator fastRows*(db: TDbConn, stmtName: TSqlPrepared,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
iterator fastRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the prepared query and iterates over the result dataset.
var res = setupQuery(db, stmtName, args)
var L = pqNfields(res)
@ -157,74 +223,107 @@ iterator fastRows*(db: TDbConn, stmtName: TSqlPrepared,
yield result
pqClear(res)
proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within interator body.
var res = setupQuery(db, query, args)
for i in 0..pqNtuples(res)-1:
yield (res: res, line: i)
pqClear(res)
iterator instantRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within interator body.
var res = setupQuery(db, stmtName, args)
for i in 0..pqNtuples(res)-1:
yield (res: res, line: i)
pqClear(res)
proc `[]`*(row: InstantRow, col: int32): string {.inline.} =
## returns text for given column of the row
$pqgetvalue(row.res, row.line, col)
proc len*(row: InstantRow): int32 {.inline.} =
## returns number of columns in the row
pqNfields(row.res)
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column.
## will return a Row with empty strings for each column.
var res = setupQuery(db, query, args)
var L = pqnfields(res)
result = newRow(L)
setRow(res, result, 0, L)
pqclear(res)
proc getRow*(db: TDbConn, stmtName: TSqlPrepared,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
proc getRow*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
var res = setupQuery(db, stmtName, args)
var L = pqNfields(res)
result = newRow(L)
setRow(res, result, 0, L)
pqClear(res)
proc getAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset.
result = @[]
for r in fastRows(db, query, args):
result.add(r)
proc getAllRows*(db: TDbConn, stmtName: TSqlPrepared,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
proc getAllRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the prepared query and returns the whole result dataset.
result = @[]
for r in fastRows(db, stmtName, args):
result.add(r)
iterator rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## same as `fastRows`, but slower and safe.
for r in items(getAllRows(db, query, args)): yield r
proc getValue*(db: TDbConn, query: TSqlQuery,
iterator rows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## same as `fastRows`, but slower and safe.
for r in items(getAllRows(db, stmtName, args)): yield r
proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database
## value is NULL.
var x = pqgetvalue(setupQuery(db, query, args), 0, 0)
result = if isNil(x): "" else: $x
proc tryInsertID*(db: TDbConn, query: TSqlQuery,
proc tryInsertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].}=
## executes the query (typically "INSERT") and returns the
## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is
## named ``id``.
var x = pqgetvalue(setupQuery(db, TSqlQuery(string(query) & " RETURNING id"),
## named ``id``.
var x = pqgetvalue(setupQuery(db, SqlQuery(string(query) & " RETURNING id"),
args), 0, 0)
if not isNil(x):
result = parseBiggestInt($x)
else:
result = -1
proc insertID*(db: TDbConn, query: TSqlQuery,
proc insertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is
## named ``id``.
## named ``id``.
result = tryInsertID(db, query, args)
if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [
FReadDB, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the
@ -235,11 +334,24 @@ proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
result = parseBiggestInt($pqcmdTuples(res))
pqclear(res)
proc close*(db: TDbConn) {.tags: [FDb].} =
proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): int64 {.tags: [
FReadDB, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the
## number of affected rows.
var arr = allocCStringArray(args)
var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
nil, nil, 0)
deallocCStringArray(arr)
if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
result = parseBiggestInt($pqcmdTuples(res))
pqclear(res)
proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection.
if db != nil: pqfinish(db)
proc open*(connection, user, password, database: string): TDbConn {.
proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not
## be established.
@ -261,8 +373,11 @@ proc open*(connection, user, password, database: string): TDbConn {.
result = pqsetdbLogin(nil, nil, nil, nil, database, user, password)
if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## sets the encoding of a database connection, returns true for
## success, false for failure.
return pqsetClientEncoding(connection, encoding) == 0
return pqsetClientEncoding(connection, encoding) == 0
# Tests are in ../../tests/untestable/tpostgres.

View file

@ -7,40 +7,73 @@
# distribution, for details about the copyright.
#
## A higher level `SQLite`:idx: database wrapper. This interface
## A higher level `SQLite`:idx: database wrapper. This interface
## is implemented for other databases too.
##
## Example:
##
## .. code-block:: nim
##
## import db_sqlite, math
##
## let theDb = open("mytest.db", nil, nil, nil)
##
## theDb.exec(sql"Drop table if exists myTestTbl")
## theDb.exec(sql("""create table myTestTbl (
## Id INTEGER PRIMARY KEY,
## Name VARCHAR(50) NOT NULL,
## i INT(11),
## f DECIMAL(18,10))"""))
##
## theDb.exec(sql"BEGIN")
## for i in 1..1000:
## theDb.exec(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
## "Item#" & $i, i, sqrt(i.float))
## theDb.exec(sql"COMMIT")
##
## for x in theDb.fastRows(sql"select * from myTestTbl"):
## echo x
##
## let id = theDb.tryInsertId(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
## "Item#1001", 1001, sqrt(1001.0))
## echo "Inserted item: ", theDb.getValue(sql"SELECT name FROM myTestTbl WHERE id=?", id)
##
## theDb.close()
import strutils, sqlite3
type
TDbConn* = PSqlite3 ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be
DbConn* = PSqlite3 ## encapsulates a database connection
Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string.
InstantRow* = Pstmt ## a handle that can be used to get a row's column
## text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string
SqlQuery* = distinct string ## an SQL query string
FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn].}
proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"``
##
## If assertions are turned off, it does nothing. If assertions are turned
## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax.
result = TSqlQuery(query)
proc dbError(db: TDbConn) {.noreturn.} =
result = SqlQuery(query)
proc dbError(db: DbConn) {.noreturn.} =
## raises an EDb exception.
var e: ref EDb
new(e)
e.msg = $sqlite3.errmsg(db)
raise e
proc dbError*(msg: string) {.noreturn.} =
proc dbError*(msg: string) {.noreturn.} =
## raises an EDb exception with message `msg`.
var e: ref EDb
new(e)
@ -55,7 +88,7 @@ proc dbQuote(s: string): string =
else: add(result, c)
add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = ""
var a = 0
for c in items(string(formatstr)):
@ -64,8 +97,8 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
inc(a)
else:
add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery,
proc tryExec*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): bool {.tags: [FReadDb, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args)
@ -74,72 +107,94 @@ proc tryExec*(db: TDbConn, query: TSqlQuery,
if step(stmt) == SQLITE_DONE:
result = finalize(stmt) == SQLITE_OK
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDb, FWriteDb].} =
## executes the query and raises EDB if not successful.
if not tryExec(db, query, args): dbError(db)
proc newRow(L: int): TRow =
proc newRow(L: int): Row =
newSeq(result, L)
for i in 0..L-1: result[i] = ""
proc setupQuery(db: TDbConn, query: TSqlQuery,
args: varargs[string]): Pstmt =
proc setupQuery(db: DbConn, query: SqlQuery,
args: varargs[string]): Pstmt =
var q = dbFormat(query, args)
if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db)
proc setRow(stmt: Pstmt, r: var TRow, cols: cint) =
proc setRow(stmt: Pstmt, r: var Row, cols: cint) =
for col in 0..cols-1:
setLen(r[col], column_bytes(stmt, col)) # set capacity
setLen(r[col], 0)
let x = column_text(stmt, col)
if not isNil(x): add(r[col], x)
iterator fastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} =
## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Sqlite it is safe though.
iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## Executes the query and iterates over the result dataset.
##
## This is very fast, but potentially dangerous. Use this iterator only
## if you require **ALL** the rows.
##
## Breaking the fastRows() iterator during a loop will cause the next
## database query to raise an [EDb] exception ``unable to close due to ...``.
var stmt = setupQuery(db, query, args)
var L = (column_count(stmt))
var result = newRow(L)
while step(stmt) == SQLITE_ROW:
while step(stmt) == SQLITE_ROW:
setRow(stmt, result, L)
yield result
if finalize(stmt) != SQLITE_OK: dbError(db)
proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} =
iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within the interator body.
var stmt = setupQuery(db, query, args)
while step(stmt) == SQLITE_ROW:
yield stmt
if finalize(stmt) != SQLITE_OK: dbError(db)
proc `[]`*(row: InstantRow, col: int32): string {.inline.} =
## returns text for given column of the row
$column_text(row, col)
proc len*(row: InstantRow): int32 {.inline.} =
## returns number of columns in the row
column_count(row)
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column.
## will return a Row with empty strings for each column.
var stmt = setupQuery(db, query, args)
var L = (column_count(stmt))
result = newRow(L)
if step(stmt) == SQLITE_ROW:
if step(stmt) == SQLITE_ROW:
setRow(stmt, result, L)
if finalize(stmt) != SQLITE_OK: dbError(db)
proc getAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDb].} =
proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[Row] {.tags: [FReadDb].} =
## executes the query and returns the whole result dataset.
result = @[]
for r in fastRows(db, query, args):
result.add(r)
iterator rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} =
iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## same as `FastRows`, but slower and safe.
for r in fastRows(db, query, args): yield r
proc getValue*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDb].} =
proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDb].} =
## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database
## value is NULL.
var stmt = setupQuery(db, query, args)
if step(stmt) == SQLITE_ROW:
let cb = column_bytes(stmt, 0)
if cb == 0:
if cb == 0:
result = ""
else:
result = newStringOfCap(cb)
@ -147,12 +202,12 @@ proc getValue*(db: TDbConn, query: TSqlQuery,
else:
result = ""
if finalize(stmt) != SQLITE_OK: dbError(db)
proc tryInsertID*(db: TDbConn, query: TSqlQuery,
proc tryInsertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64
{.tags: [FWriteDb], raises: [].} =
## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error.
## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error.
var q = dbFormat(query, args)
var stmt: sqlite3.Pstmt
result = -1
@ -162,45 +217,45 @@ proc tryInsertID*(db: TDbConn, query: TSqlQuery,
if finalize(stmt) != SQLITE_OK:
result = -1
proc insertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
proc insertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is
## named ``id``.
## named ``id``.
result = tryInsertID(db, query, args)
if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.
tags: [FReadDb, FWriteDb].} =
tags: [FReadDb, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the
## number of affected rows.
exec(db, query, args)
result = changes(db)
proc close*(db: TDbConn) {.tags: [FDb].} =
proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection.
if sqlite3.close(db) != SQLITE_OK: dbError(db)
proc open*(connection, user, password, database: string): TDbConn {.
proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not
## be established. Only the ``connection`` parameter is used for ``sqlite``.
var db: TDbConn
var db: DbConn
if sqlite3.open(connection, db) == SQLITE_OK:
result = db
else:
dbError(db)
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## sets the encoding of a database connection, returns true for
## success, false for failure.
##
## Note that the encoding cannot be changed once it's been set.
## According to SQLite3 documentation, any attempt to change
## the encoding after the database is created will be silently
## According to SQLite3 documentation, any attempt to change
## the encoding after the database is created will be silently
## ignored.
exec(connection, sql"PRAGMA encoding = ?", [encoding])
result = connection.getValue(sql"PRAGMA encoding") == encoding
@ -215,5 +270,7 @@ when not defined(testing) and isMainModule:
#db.query("insert into tbl1 values('goodbye', 20)")
for r in db.rows(sql"select * from tbl1", []):
echo(r[0], r[1])
for r in db.instantRows(sql"select * from tbl1", []):
echo(r[0], r[1])
db_sqlite.close(db)

View file

@ -1,226 +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.
#
## This module implements portable dialogs for Nim; the implementation
## builds on the GTK interface. On Windows, native dialogs are shown instead.
import
glib2, gtk2
when defined(Windows):
import windows, ShellAPI, os
proc info*(window: PWindow, msg: string) =
## Shows an information message to the user. The process waits until the
## user presses the OK button.
when defined(Windows):
discard MessageBoxA(0, msg, "Information", MB_OK or MB_ICONINFORMATION)
else:
var dialog = message_dialog_new(window,
DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
MESSAGE_INFO, BUTTONS_OK, "%s", cstring(msg))
setTitle(dialog, "Information")
discard run(dialog)
destroy(PWidget(dialog))
proc warning*(window: PWindow, msg: string) =
## Shows a warning message to the user. The process waits until the user
## presses the OK button.
when defined(Windows):
discard MessageBoxA(0, msg, "Warning", MB_OK or MB_ICONWARNING)
else:
var dialog = DIALOG(message_dialog_new(window,
DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
MESSAGE_WARNING, BUTTONS_OK, "%s", cstring(msg)))
setTitle(dialog, "Warning")
discard run(dialog)
destroy(PWidget(dialog))
proc error*(window: PWindow, msg: string) =
## Shows an error message to the user. The process waits until the user
## presses the OK button.
when defined(Windows):
discard MessageBoxA(0, msg, "Error", MB_OK or MB_ICONERROR)
else:
var dialog = DIALOG(message_dialog_new(window,
DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
MESSAGE_ERROR, BUTTONS_OK, "%s", cstring(msg)))
setTitle(dialog, "Error")
discard run(dialog)
destroy(PWidget(dialog))
proc chooseFileToOpen*(window: PWindow, root: string = ""): string =
## Opens a dialog that requests a filename from the user. Returns ""
## if the user closed the dialog without selecting a file. On Windows,
## the native dialog is used, else the GTK dialog is used.
when defined(Windows):
var
opf: TOPENFILENAME
buf: array [0..2047, char]
opf.lStructSize = sizeof(opf).int32
if root.len > 0:
opf.lpstrInitialDir = root
opf.lpstrFilter = "All Files\0*.*\0\0"
opf.flags = OFN_FILEMUSTEXIST
opf.lpstrFile = buf
opf.nMaxFile = sizeof(buf).int32
var res = GetOpenFileName(addr(opf))
if res != 0:
result = $buf
else:
result = ""
else:
var chooser = file_chooser_dialog_new("Open File", window,
FILE_CHOOSER_ACTION_OPEN,
STOCK_CANCEL, RESPONSE_CANCEL,
STOCK_OPEN, RESPONSE_OK, nil)
if root.len > 0:
discard set_current_folder(chooser, root)
if run(chooser) == cint(RESPONSE_OK):
var x = get_filename(chooser)
result = $x
g_free(x)
else:
result = ""
destroy(PWidget(chooser))
proc chooseFilesToOpen*(window: PWindow, root: string = ""): seq[string] =
## Opens a dialog that requests filenames from the user. Returns ``@[]``
## if the user closed the dialog without selecting a file. On Windows,
## the native dialog is used, else the GTK dialog is used.
when defined(Windows):
var
opf: TOPENFILENAME
buf: array [0..2047*4, char]
opf.lStructSize = sizeof(opf).int32
if root.len > 0:
opf.lpstrInitialDir = root
opf.lpstrFilter = "All Files\0*.*\0\0"
opf.flags = OFN_FILEMUSTEXIST or OFN_ALLOWMULTISELECT or OFN_EXPLORER
opf.lpstrFile = buf
opf.nMaxFile = sizeof(buf).int32
var res = GetOpenFileName(addr(opf))
result = @[]
if res != 0:
# parsing the result is horrible:
var
i = 0
s: string
path = ""
while buf[i] != '\0':
add(path, buf[i])
inc(i)
inc(i)
if buf[i] != '\0':
while true:
s = ""
while buf[i] != '\0':
add(s, buf[i])
inc(i)
add(result, s)
inc(i)
if buf[i] == '\0': break
for i in 0..result.len-1: result[i] = os.joinPath(path, result[i])
else:
# only one file selected --> gosh, what an ungly thing
# the windows API is
add(result, path)
else:
var chooser = file_chooser_dialog_new("Open Files", window,
FILE_CHOOSER_ACTION_OPEN,
STOCK_CANCEL, RESPONSE_CANCEL,
STOCK_OPEN, RESPONSE_OK, nil)
if root.len > 0:
discard set_current_folder(chooser, root)
set_select_multiple(chooser, true)
result = @[]
if run(chooser) == cint(RESPONSE_OK):
var L = get_filenames(chooser)
var it = L
while it != nil:
add(result, $cast[cstring](it.data))
g_free(it.data)
it = it.next
free(L)
destroy(PWidget(chooser))
proc chooseFileToSave*(window: PWindow, root: string = ""): string =
## Opens a dialog that requests a filename to save to from the user.
## Returns "" if the user closed the dialog without selecting a file.
## On Windows, the native dialog is used, else the GTK dialog is used.
when defined(Windows):
var
opf: TOPENFILENAME
buf: array [0..2047, char]
opf.lStructSize = sizeof(opf).int32
if root.len > 0:
opf.lpstrInitialDir = root
opf.lpstrFilter = "All Files\0*.*\0\0"
opf.flags = OFN_OVERWRITEPROMPT
opf.lpstrFile = buf
opf.nMaxFile = sizeof(buf).int32
var res = GetSaveFileName(addr(opf))
if res != 0:
result = $buf
else:
result = ""
else:
var chooser = file_chooser_dialog_new("Save File", window,
FILE_CHOOSER_ACTION_SAVE,
STOCK_CANCEL, RESPONSE_CANCEL,
STOCK_SAVE, RESPONSE_OK, nil)
if root.len > 0:
discard set_current_folder(chooser, root)
set_do_overwrite_confirmation(chooser, true)
if run(chooser) == cint(RESPONSE_OK):
var x = get_filename(chooser)
result = $x
g_free(x)
else:
result = ""
destroy(PWidget(chooser))
proc chooseDir*(window: PWindow, root: string = ""): string =
## Opens a dialog that requests a directory from the user.
## Returns "" if the user closed the dialog without selecting a directory.
## On Windows, the native dialog is used, else the GTK dialog is used.
when defined(Windows):
var
lpItemID: PItemIDList
BrowseInfo: TBrowseInfo
DisplayName: array [0..MAX_PATH, char]
TempPath: array [0..MAX_PATH, char]
result = ""
#BrowseInfo.hwndOwner = Application.Handle
BrowseInfo.pszDisplayName = DisplayName
BrowseInfo.ulFlags = 1 #BIF_RETURNONLYFSDIRS
lpItemID = SHBrowseForFolder(cast[LPBrowseInfo](addr(BrowseInfo)))
if lpItemId != nil:
discard SHGetPathFromIDList(lpItemID, TempPath)
result = $TempPath
discard GlobalFreePtr(lpItemID)
else:
var chooser = file_chooser_dialog_new("Select Directory", window,
FILE_CHOOSER_ACTION_SELECT_FOLDER,
STOCK_CANCEL, RESPONSE_CANCEL,
STOCK_OPEN, RESPONSE_OK, nil)
if root.len > 0:
discard set_current_folder(chooser, root)
if run(chooser) == cint(RESPONSE_OK):
var x = get_filename(chooser)
result = $x
g_free(x)
else:
result = ""
destroy(PWidget(chooser))

View file

@ -1,575 +0,0 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements graphical output for Nim; the current
## implementation uses SDL but the interface is meant to support multiple
## backends some day. There is no need to init SDL as this module does that
## implicitly.
import colors, math
from sdl import PSurface # Bug
from sdl_ttf import openFont, closeFont
type
TRect* = tuple[x, y, width, height: int]
TPoint* = tuple[x, y: int]
PSurface* = ref TSurface ## a surface to draw onto
TSurface* {.pure, final.} = object
w*, h*: Natural
s*: sdl.PSurface
EGraphics* = object of IOError
TFont {.pure, final.} = object
f: sdl_ttf.PFont
color: sdl.TColor
PFont* = ref TFont ## represents a font
proc toSdlColor*(c: Color): sdl.TColor =
## Convert colors.TColor to sdl.TColor
var x = c.extractRGB
result.r = x.r and 0xff
result.g = x.g and 0xff
result.b = x.b and 0xff
proc createSdlColor*(sur: PSurface, c: Color, alpha: int = 0): int32 =
## Creates a color using ``sdl.MapRGBA``.
var x = c.extractRGB
return sdl.mapRGBA(sur.s.format, x.r and 0xff, x.g and 0xff,
x.b and 0xff, alpha and 0xff)
proc toSdlRect*(r: TRect): sdl.TRect =
## Convert ``graphics.TRect`` to ``sdl.TRect``.
result.x = int16(r.x)
result.y = int16(r.y)
result.w = uint16(r.width)
result.h = uint16(r.height)
proc raiseEGraphics =
raise newException(EGraphics, $sdl.getError())
proc surfaceFinalizer(s: PSurface) = sdl.freeSurface(s.s)
proc newSurface*(width, height: int): PSurface =
## creates a new surface.
new(result, surfaceFinalizer)
result.w = width
result.h = height
result.s = sdl.createRGBSurface(sdl.SWSURFACE, width, height,
32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0)
if result.s == nil:
raiseEGraphics()
assert(not sdl.mustLock(result.s))
proc fontFinalizer(f: PFont) = closeFont(f.f)
proc newFont*(name = "VeraMono.ttf", size = 9, color = colBlack): PFont =
## Creates a new font object. Raises ``EIO`` if the font cannot be loaded.
new(result, fontFinalizer)
result.f = openFont(name, size.cint)
if result.f == nil:
raise newException(IOError, "Could not open font file: " & name)
result.color = toSdlColor(color)
var
defaultFont*: PFont ## default font that is used; this needs to initialized
## by the client!
proc initDefaultFont*(name = "VeraMono.ttf", size = 9, color = colBlack) =
## initializes the `defaultFont` var.
defaultFont = newFont(name, size, color)
proc newScreenSurface*(width, height: int): PSurface =
## Creates a new screen surface
new(result, surfaceFinalizer)
result.w = width
result.h = height
result.s = sdl.setVideoMode(width, height, 0, 0)
if result.s == nil:
raiseEGraphics()
proc writeToBMP*(sur: PSurface, filename: string) =
## Saves the contents of the surface `sur` to the file `filename` as a
## BMP file.
if sdl.saveBMP(sur.s, filename) != 0:
raise newException(IOError, "cannot write: " & filename)
type
TPixels = array[0..1000_000-1, int32]
PPixels = ptr TPixels
template setPix(video, pitch, x, y, col: expr): stmt =
video[y * pitch + x] = int32(col)
template getPix(video, pitch, x, y: expr): expr =
colors.Color(video[y * pitch + x])
const
ColSize = 4
proc getPixel(sur: PSurface, x, y: Natural): colors.Color {.inline.} =
assert x <% sur.w
assert y <% sur.h
result = getPix(cast[PPixels](sur.s.pixels), sur.s.pitch.int div ColSize,
x, y)
proc setPixel(sur: PSurface, x, y: Natural, col: colors.Color) {.inline.} =
assert x <% sur.w
assert y <% sur.h
var pixs = cast[PPixels](sur.s.pixels)
#pixs[y * (sur.s.pitch div colSize) + x] = int(col)
setPix(pixs, sur.s.pitch.int div ColSize, x, y, col)
proc `[]`*(sur: PSurface, p: TPoint): Color =
## get pixel at position `p`. No range checking is done!
result = getPixel(sur, p.x, p.y)
proc `[]`*(sur: PSurface, x, y: int): Color =
## get pixel at position ``(x, y)``. No range checking is done!
result = getPixel(sur, x, y)
proc `[]=`*(sur: PSurface, p: TPoint, col: Color) =
## set the pixel at position `p`. No range checking is done!
setPixel(sur, p.x, p.y, col)
proc `[]=`*(sur: PSurface, x, y: int, col: Color) =
## set the pixel at position ``(x, y)``. No range checking is done!
setPixel(sur, x, y, col)
proc blit*(destSurf: PSurface, destRect: TRect, srcSurf: PSurface,
srcRect: TRect) =
## Copies ``srcSurf`` into ``destSurf``
var destTRect, srcTRect: sdl.TRect
destTRect.x = int16(destRect.x)
destTRect.y = int16(destRect.y)
destTRect.w = uint16(destRect.width)
destTRect.h = uint16(destRect.height)
srcTRect.x = int16(srcRect.x)
srcTRect.y = int16(srcRect.y)
srcTRect.w = uint16(srcRect.width)
srcTRect.h = uint16(srcRect.height)
if sdl.blitSurface(srcSurf.s, addr(srcTRect), destSurf.s, addr(destTRect)) != 0:
raiseEGraphics()
proc textBounds*(text: string, font = defaultFont): tuple[width, height: int] =
var w, h: cint
if sdl_ttf.sizeUTF8(font.f, text, w, h) < 0: raiseEGraphics()
result.width = int(w)
result.height = int(h)
proc drawText*(sur: PSurface, p: TPoint, text: string, font = defaultFont) =
## Draws text with a transparent background, at location ``p`` with the given
## font.
var textSur: PSurface # This surface will have the text drawn on it
new(textSur, surfaceFinalizer)
# Render the text
textSur.s = sdl_ttf.renderTextBlended(font.f, text, font.color)
# Merge the text surface with sur
sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h))
proc drawText*(sur: PSurface, p: TPoint, text: string,
bg: Color, font = defaultFont) =
## Draws text, at location ``p`` with font ``font``. ``bg``
## is the background color.
var textSur: PSurface # This surface will have the text drawn on it
new(textSur, surfaceFinalizer)
textSur.s = sdl_ttf.renderTextShaded(font.f, text, font.color, toSdlColor(bg))
# Merge the text surface with sur
sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h))
proc drawCircle*(sur: PSurface, p: TPoint, r: Natural, color: Color) =
## draws a circle with center `p` and radius `r` with the given color
## onto the surface `sur`.
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
var a = 1 - r
var py = r
var px = 0
var x = p.x
var y = p.y
while px <= py + 1:
if x+px <% sur.w:
if y+py <% sur.h: setPix(video, pitch, x+px, y+py, color)
if y-py <% sur.h: setPix(video, pitch, x+px, y-py, color)
if x-px <% sur.w:
if y+py <% sur.h: setPix(video, pitch, x-px, y+py, color)
if y-py <% sur.h: setPix(video, pitch, x-px, y-py, color)
if x+py <% sur.w:
if y+px <% sur.h: setPix(video, pitch, x+py, y+px, color)
if y-px <% sur.h: setPix(video, pitch, x+py, y-px, color)
if x-py <% sur.w:
if y+px <% sur.h: setPix(video, pitch, x-py, y+px, color)
if y-px <% sur.h: setPix(video, pitch, x-py, y-px, color)
if a < 0:
a = a + (2 * px + 3)
else:
a = a + (2 * (px - py) + 5)
py = py - 1
px = px + 1
proc `>-<`(val: int, s: PSurface): int {.inline.} =
return if val < 0: 0 elif val >= s.w: s.w-1 else: val
proc `>|<`(val: int, s: PSurface): int {.inline.} =
return if val < 0: 0 elif val >= s.h: s.h-1 else: val
proc drawLine*(sur: PSurface, p1, p2: TPoint, color: Color) =
## draws a line between the two points `p1` and `p2` with the given color
## onto the surface `sur`.
var stepx, stepy: int = 0
var x0 = p1.x >-< sur
var x1 = p2.x >-< sur
var y0 = p1.y >|< sur
var y1 = p2.y >|< sur
var dy = y1 - y0
var dx = x1 - x0
if dy < 0:
dy = -dy
stepy = -1
else:
stepy = 1
if dx < 0:
dx = -dx
stepx = -1
else:
stepx = 1
dy = dy * 2
dx = dx * 2
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
setPix(video, pitch, x0, y0, color)
if dx > dy:
var fraction = dy - (dx div 2)
while x0 != x1:
if fraction >= 0:
y0 = y0 + stepy
fraction = fraction - dx
x0 = x0 + stepx
fraction = fraction + dy
setPix(video, pitch, x0, y0, color)
else:
var fraction = dx - (dy div 2)
while y0 != y1:
if fraction >= 0:
x0 = x0 + stepx
fraction = fraction - dy
y0 = y0 + stepy
fraction = fraction + dx
setPix(video, pitch, x0, y0, color)
proc drawHorLine*(sur: PSurface, x, y, w: Natural, color: Color) =
## draws a horizontal line from (x,y) to (x+w-1, y).
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
if y >= 0 and y <= sur.s.h:
for i in 0 .. min(sur.s.w-x, w)-1:
setPix(video, pitch, x + i, y, color)
proc drawVerLine*(sur: PSurface, x, y, h: Natural, color: Color) =
## draws a vertical line from (x,y) to (x, y+h-1).
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
if x >= 0 and x <= sur.s.w:
for i in 0 .. min(sur.s.h-y, h)-1:
setPix(video, pitch, x, y + i, color)
proc fillCircle*(s: PSurface, p: TPoint, r: Natural, color: Color) =
## draws a circle with center `p` and radius `r` with the given color
## onto the surface `sur` and fills it.
var a = 1 - r
var py: int = r
var px = 0
var x = p.x
var y = p.y
while px <= py:
# Fill up the middle half of the circle
drawVerLine(s, x + px, y, py + 1, color)
drawVerLine(s, x + px, y - py, py, color)
if px != 0:
drawVerLine(s, x - px, y, py + 1, color)
drawVerLine(s, x - px, y - py, py, color)
if a < 0:
a = a + (2 * px + 3)
else:
a = a + (2 * (px - py) + 5)
py = py - 1
# Fill up the left/right half of the circle
if py >= px:
drawVerLine(s, x + py + 1, y, px + 1, color)
drawVerLine(s, x + py + 1, y - px, px, color)
drawVerLine(s, x - py - 1, y, px + 1, color)
drawVerLine(s, x - py - 1, y - px, px, color)
px = px + 1
proc drawRect*(sur: PSurface, r: TRect, color: Color) =
## draws a rectangle.
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
if (r.x >= 0 and r.x <= sur.s.w) and (r.y >= 0 and r.y <= sur.s.h):
var minW = min(sur.s.w - r.x, r.width)
var minH = min(sur.s.h - r.y, r.height)
# Draw Top
for i in 0 .. minW - 1:
setPix(video, pitch, r.x + i, r.y, color)
setPix(video, pitch, r.x + i, r.y + minH - 1, color) # Draw bottom
# Draw left side
for i in 0 .. minH - 1:
setPix(video, pitch, r.x, r.y + i, color)
setPix(video, pitch, r.x + minW - 1, r.y + i, color) # Draw right side
proc fillRect*(sur: PSurface, r: TRect, col: Color) =
## Fills a rectangle using sdl's ``FillRect`` function.
var rect = toSdlRect(r)
if sdl.fillRect(sur.s, addr(rect), sur.createSdlColor(col)) == -1:
raiseEGraphics()
proc plot4EllipsePoints(sur: PSurface, cx, cy, x, y: Natural, col: Color) =
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
if cx+x <= sur.s.w-1:
if cy+y <= sur.s.h-1: setPix(video, pitch, cx+x, cy+y, col)
if cy-y <= sur.s.h-1: setPix(video, pitch, cx+x, cy-y, col)
if cx-x <= sur.s.w-1:
if cy+y <= sur.s.h-1: setPix(video, pitch, cx-x, cy+y, col)
if cy-y <= sur.s.h-1: setPix(video, pitch, cx-x, cy-y, col)
proc drawEllipse*(sur: PSurface, cx, cy, xRadius, yRadius: Natural,
col: Color) =
## Draws an ellipse, ``CX`` and ``CY`` specify the center X and Y of the
## ellipse, ``XRadius`` and ``YRadius`` specify half the width and height
## of the ellipse.
var
x, y: Natural
xChange, yChange: int
ellipseError: Natural
twoASquare, twoBSquare: Natural
stoppingX, stoppingY: Natural
twoASquare = 2 * xRadius * xRadius
twoBSquare = 2 * yRadius * yRadius
x = xRadius
y = 0
xChange = yRadius * yRadius * (1 - 2 * xRadius)
yChange = xRadius * xRadius
ellipseError = 0
stoppingX = twoBSquare * xRadius
stoppingY = 0
while stoppingX >= stoppingY: # 1st set of points, y` > - 1
sur.plot4EllipsePoints(cx, cy, x, y, col)
inc(y)
inc(stoppingY, twoASquare)
inc(ellipseError, yChange)
inc(yChange, twoASquare)
if (2 * ellipseError + xChange) > 0 :
dec(x)
dec(stoppingX, twoBSquare)
inc(ellipseError, xChange)
inc(xChange, twoBSquare)
# 1st point set is done; start the 2nd set of points
x = 0
y = yRadius
xChange = yRadius * yRadius
yChange = xRadius * xRadius * (1 - 2 * yRadius)
ellipseError = 0
stoppingX = 0
stoppingY = twoASquare * yRadius
while stoppingX <= stoppingY:
sur.plot4EllipsePoints(cx, cy, x, y, col)
inc(x)
inc(stoppingX, twoBSquare)
inc(ellipseError, xChange)
inc(xChange,twoBSquare)
if (2 * ellipseError + yChange) > 0:
dec(y)
dec(stoppingY, twoASquare)
inc(ellipseError, yChange)
inc(yChange,twoASquare)
proc plotAA(sur: PSurface, x, y: int, c: float, color: Color) =
if (x > 0 and x < sur.s.w) and (y > 0 and y < sur.s.h):
var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize
var pixColor = getPix(video, pitch, x, y)
setPix(video, pitch, x, y,
pixColor.intensity(1.0 - c) + color.intensity(c))
template ipart(x: expr): expr = floor(x)
template cround(x: expr): expr = ipart(x + 0.5)
template fpart(x: expr): expr = x - ipart(x)
template rfpart(x: expr): expr = 1.0 - fpart(x)
proc drawLineAA*(sur: PSurface, p1, p2: TPoint, color: Color) =
## Draws a anti-aliased line from ``p1`` to ``p2``, using Xiaolin Wu's
## line algorithm
var (x1, x2, y1, y2) = (p1.x.toFloat(), p2.x.toFloat(),
p1.y.toFloat(), p2.y.toFloat())
var dx = x2 - x1
var dy = y2 - y1
var ax = dx
if ax < 0'f64:
ax = 0'f64 - ax
var ay = dy
if ay < 0'f64:
ay = 0'f64 - ay
if ax < ay:
swap(x1, y1)
swap(x2, y2)
swap(dx, dy)
template doPlot(x, y: int, c: float, color: Color): stmt =
if ax < ay:
sur.plotAA(y, x, c, color)
else:
sur.plotAA(x, y, c, color)
if x2 < x1:
swap(x1, x2)
swap(y1, y2)
var gradient = dy / dx
# handle first endpoint
var xend = cround(x1)
var yend = y1 + gradient * (xend - x1)
var xgap = rfpart(x1 + 0.5)
var xpxl1 = int(xend) # this will be used in the main loop
var ypxl1 = int(ipart(yend))
doPlot(xpxl1, ypxl1, rfpart(yend)*xgap, color)
doPlot(xpxl1, ypxl1 + 1, fpart(yend)*xgap, color)
var intery = yend + gradient # first y-intersection for the main loop
# handle second endpoint
xend = cround(x2)
yend = y2 + gradient * (xend - x2)
xgap = fpart(x2 + 0.5)
var xpxl2 = int(xend) # this will be used in the main loop
var ypxl2 = int(ipart(yend))
doPlot(xpxl2, ypxl2, rfpart(yend) * xgap, color)
doPlot(xpxl2, ypxl2 + 1, fpart(yend) * xgap, color)
# main loop
var x = xpxl1 + 1
while x <= xpxl2-1:
doPlot(x, int(ipart(intery)), rfpart(intery), color)
doPlot(x, int(ipart(intery)) + 1, fpart(intery), color)
intery = intery + gradient
inc(x)
proc fillSurface*(sur: PSurface, color: Color) =
## Fills the entire surface with ``color``.
if sdl.fillRect(sur.s, nil, sur.createSdlColor(color)) == -1:
raiseEGraphics()
template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {.
immediate.} =
## Simple template which creates an event loop. ``Event`` is the name of the
## variable containing the TEvent object.
while true:
var event: sdl.TEvent
if sdl.waitEvent(addr(event)) == 1:
actions
if sdl.init(sdl.INIT_VIDEO) < 0: raiseEGraphics()
if sdl_ttf.init() < 0: raiseEGraphics()
when not defined(testing) and isMainModule:
var surf = newScreenSurface(800, 600)
surf.fillSurface(colWhite)
# Draw the shapes
surf.drawLineAA((150, 170), (400, 471), colTan)
surf.drawLine((100, 170), (400, 471), colRed)
surf.drawEllipse(200, 300, 200, 30, colSeaGreen)
surf.drawHorLine(1, 300, 400, colViolet)
# Check if the ellipse is the size it's suppose to be.
surf.drawVerLine(200, 300 - 30 + 1, 60, colViolet) # ^^ | i suppose it is
surf.drawEllipse(400, 300, 300, 300, colOrange)
surf.drawEllipse(5, 5, 5, 5, colGreen)
surf.drawHorLine(5, 5, 900, colRed)
surf.drawVerLine(5, 60, 800, colRed)
surf.drawCircle((600, 500), 60, colRed)
surf.fillRect((50, 50, 100, 100), colFuchsia)
surf.fillRect((150, 50, 100, 100), colGreen)
surf.drawRect((50, 150, 100, 100), colGreen)
surf.drawRect((150, 150, 100, 100), colAqua)
surf.drawRect((250, 150, 100, 100), colBlue)
surf.drawHorLine(250, 150, 100, colRed)
surf.drawLineAA((592, 160), (592, 280), colPurple)
#surf.drawText((300, 300), "TEST", colMidnightBlue)
#var textSize = textBounds("TEST")
#surf.drawText((300, 300 + textSize.height), $textSize.width & ", " &
# $textSize.height, colDarkGreen)
var mouseStartX = -1
var mouseStartY = -1
withEvents(surf, event):
var eventp = addr(event)
case event.kind:
of sdl.QUITEV:
break
of sdl.KEYDOWN:
var evk = sdl.evKeyboard(eventp)
if evk.keysym.sym == sdl.K_LEFT:
surf.drawHorLine(395, 300, 50, colBlack)
echo("Drawing")
elif evk.keysym.sym == sdl.K_ESCAPE:
break
else:
echo(evk.keysym.sym)
of sdl.MOUSEBUTTONDOWN:
var mbd = sdl.evMouseButton(eventp)
if mouseStartX == -1 or mouseStartY == -1:
mouseStartX = int(mbd.x)
mouseStartY = int(mbd.y)
else:
surf.drawLineAA((mouseStartX, mouseStartY), (int(mbd.x), int(mbd.y)), colPurple)
mouseStartX = -1
mouseStartY = -1
of sdl.MOUSEMOTION:
var mm = sdl.evMouseMotion(eventp)
if mouseStartX != -1 and mouseStartY != -1:
surf.drawLineAA((mouseStartX, mouseStartY), (int(mm.x), int(mm.y)), colPurple)
#echo(mm.x, " ", mm.y, " ", mm.yrel)
else:
discard "echo(event.kind)"
sdl.updateRect(surf.s, 0, 0, 800, 600)
surf.writeToBMP("test.bmp")
sdl.quit()

686
lib/impure/nre.nim Normal file
View file

@ -0,0 +1,686 @@
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Contributers
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
from pcre import nil
import nre.private.util
import tables
import unsigned
from strutils import toLower, `%`
from math import ceil
import options
from unicode import runeLenAt
## What is NRE?
## ============
##
## A regular expression library for Nim using PCRE to do the hard work.
##
## Licencing
## ---------
##
## PCRE has some additional terms that you must comply with if you use this module.::
##
## > Copyright (c) 1997-2001 University of Cambridge
## >
## > Permission is granted to anyone to use this software for any purpose on any
## > computer system, and to redistribute it freely, subject to the following
## > restrictions:
## >
## > 1. This software is distributed in the hope that it will be useful,
## > but WITHOUT ANY WARRANTY; without even the implied warranty of
## > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## >
## > 2. The origin of this software must not be misrepresented, either by
## > explicit claim or by omission. In practice, this means that if you use
## > PCRE in software that you distribute to others, commercially or
## > otherwise, you must put a sentence like this
## >
## > Regular expression support is provided by the PCRE library package,
## > which is open source software, written by Philip Hazel, and copyright
## > by the University of Cambridge, England.
## >
## > somewhere reasonably visible in your documentation and in any relevant
## > files or online help data or similar. A reference to the ftp site for
## > the source, that is, to
## >
## > ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
## >
## > should also be given in the documentation. However, this condition is not
## > intended to apply to whole chains of software. If package A includes PCRE,
## > it must acknowledge it, but if package B is software that includes package
## > A, the condition is not imposed on package B (unless it uses PCRE
## > independently).
## >
## > 3. Altered versions must be plainly marked as such, and must not be
## > misrepresented as being the original software.
## >
## > 4. If PCRE is embedded in any software that is released under the GNU
## > General Purpose Licence (GPL), or Lesser General Purpose Licence (LGPL),
## > then the terms of that licence shall supersede any condition above with
## > which it is incompatible.
# Type definitions {{{
type
Regex* = ref object
## Represents the pattern that things are matched against, constructed with
## ``re(string)``. Examples: ``re"foo"``, ``re(r"(*ANYCRLF)(?x)foo #
## comment".``
##
## ``pattern: string``
## the string that was used to create the pattern.
##
## ``captureCount: int``
## the number of captures that the pattern has.
##
## ``captureNameId: Table[string, int]``
## a table from the capture names to their numeric id.
##
##
## Options
## .......
##
## The following options may appear anywhere in the pattern, and they affect
## the rest of it.
##
## - ``(?i)`` - case insensitive
## - ``(?m)`` - multi-line: ``^`` and ``$`` match the beginning and end of
## lines, not of the subject string
## - ``(?s)`` - ``.`` also matches newline (*dotall*)
## - ``(?U)`` - expressions are not greedy by default. ``?`` can be added
## to a qualifier to make it greedy
## - ``(?x)`` - whitespace and comments (``#``) are ignored (*extended*)
## - ``(?X)`` - character escapes without special meaning (``\w`` vs.
## ``\a``) are errors (*extra*)
##
## One or a combination of these options may appear only at the beginning
## of the pattern:
##
## - ``(*UTF8)`` - treat both the pattern and subject as UTF-8
## - ``(*UCP)`` - Unicode character properties; ``\w`` matches ``я``
## - ``(*U)`` - a combination of the two options above
## - ``(*FIRSTLINE*)`` - fails if there is not a match on the first line
## - ``(*NO_AUTO_CAPTURE)`` - turn off auto-capture for groups;
## ``(?<name>...)`` can be used to capture
## - ``(*CR)`` - newlines are separated by ``\r``
## - ``(*LF)`` - newlines are separated by ``\n`` (UNIX default)
## - ``(*CRLF)`` - newlines are separated by ``\r\n`` (Windows default)
## - ``(*ANYCRLF)`` - newlines are separated by any of the above
## - ``(*ANY)`` - newlines are separated by any of the above and Unicode
## newlines:
##
## single characters VT (vertical tab, U+000B), FF (form feed, U+000C),
## NEL (next line, U+0085), LS (line separator, U+2028), and PS
## (paragraph separator, U+2029). For the 8-bit library, the last two
## are recognized only in UTF-8 mode.
## — man pcre
##
## - ``(*JAVASCRIPT_COMPAT)`` - JavaScript compatibility
## - ``(*NO_STUDY)`` - turn off studying; study is enabled by default
##
## For more details on the leading option groups, see the `Option
## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`__
## and the `Newline
## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`__
## sections of the `PCRE syntax
## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`__.
pattern*: string ## not nil
pcreObj: ptr pcre.Pcre ## not nil
pcreExtra: ptr pcre.ExtraData ## nil
captureNameToId: Table[string, int]
RegexMatch* = object
## Usually seen as Option[RegexMatch], it represents the result of an
## execution. On failure, it is none, on success, it is some.
##
## ``pattern: Regex``
## the pattern that is being matched
##
## ``str: string``
## the string that was matched against
##
## ``captures[]: string``
## the string value of whatever was captured at that id. If the value
## is invalid, then behavior is undefined. If the id is ``-1``, then
## the whole match is returned. If the given capture was not matched,
## ``nil`` is returned.
##
## - ``"abc".match(re"(\w)").captures[0] == "a"``
## - ``"abc".match(re"(?<letter>\w)").captures["letter"] == "a"``
## - ``"abc".match(re"(\w)\w").captures[-1] == "ab"``
##
## ``captureBounds[]: Option[Slice[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.
##
## - ``"abc".match(re"(\w)").captureBounds[0] == 0 .. 0``
## - ``"abc".match(re"").captureBounds[-1] == 0 .. -1``
## - ``"abc".match(re"abc").captureBounds[-1] == 0 .. 2``
##
## ``match: string``
## the full text of the match.
##
## ``matchBounds: Slice[int]``
## the bounds of the match, as in ``captureBounds[]``
##
## ``(captureBounds|captures).toTable``
## returns a table with each named capture as a key.
##
## ``(captureBounds|captures).toSeq``
## returns all the captures by their number.
##
## ``$: string``
## same as ``match``
pattern*: Regex ## The regex doing the matching.
## 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
Captures* = distinct RegexMatch
CaptureBounds* = distinct RegexMatch
RegexError* = ref object of Exception
RegexInternalError* = ref object of RegexError
## Internal error in the module, this probably means that there is a bug
InvalidUnicodeError* = ref object of RegexError
## Thrown when matching fails due to invalid unicode in strings
pos*: int ## the location of the invalid unicode in bytes
SyntaxError* = ref object of RegexError
## Thrown when there is a syntax error in the
## regular expression string passed in
pos*: int ## the location of the syntax error in bytes
pattern*: string ## the pattern that caused the problem
StudyError* = ref object of RegexError
## Thrown when studying the regular expression failes
## for whatever reason. The message contains the error
## code.
# }}}
proc getinfo[T](pattern: Regex, opt: cint): T =
let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result)
if retcode < 0:
# XXX Error message that doesn't expose implementation details
raise newException(FieldError, "Invalid getinfo for $1, errno $2" % [$opt, $retcode])
# Regex accessors {{{
proc captureCount*(pattern: Regex): int =
return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT)
proc captureNameId*(pattern: Regex): Table[string, int] =
return pattern.captureNameToId
proc matchesCrLf(pattern: Regex): bool =
let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS))
let newlineFlags = flags and (pcre.NEWLINE_CRLF or
pcre.NEWLINE_ANY or
pcre.NEWLINE_ANYCRLF)
if newLineFlags > 0u32:
return true
# get flags from build config
var confFlags: cint
if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0:
assert(false, "CONFIG_NEWLINE apparently got screwed up")
case confFlags
of 13: return false
of 10: return false
of (13 shl 8) or 10: return true
of -2: return true
of -1: return true
else: return false
# }}}
# Capture accessors {{{
proc captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern)
proc captures*(pattern: RegexMatch): Captures = return Captures(pattern)
proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[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])
proc `[]`*(pattern: Captures, i: int): string =
let pattern = RegexMatch(pattern)
let bounds = pattern.captureBounds[i]
if bounds.isSome:
let bounds = bounds.get
return pattern.str.substr(bounds.a, bounds.b)
else:
return nil
proc match*(pattern: RegexMatch): string =
return pattern.captures[-1]
proc matchBounds*(pattern: RegexMatch): Slice[int] =
return pattern.captureBounds[-1].get
proc `[]`*(pattern: CaptureBounds, name: string): Option[Slice[int]] =
let pattern = RegexMatch(pattern)
return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)]
proc `[]`*(pattern: Captures, name: string): string =
let pattern = RegexMatch(pattern)
return pattern.captures[pattern.pattern.captureNameToId.fget(name)]
template toTableImpl(cond: bool): stmt {.immediate, dirty.} =
for key in RegexMatch(pattern).pattern.captureNameId.keys:
let nextVal = pattern[key]
if cond:
result[key] = default
else:
result[key] = nextVal
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]]]()
toTableImpl(nextVal.isNone)
template itemsImpl(cond: bool): stmt {.immediate, dirty.} =
for i in 0 .. <RegexMatch(pattern).pattern.captureCount:
let nextVal = pattern[i]
# done in this roundabout way to avoid multiple yields (potential code
# bloat)
let nextYieldVal = if cond: default else: nextVal
yield nextYieldVal
iterator items*(pattern: CaptureBounds, default = none(Slice[int])): Option[Slice[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]]] =
accumulateResult(pattern.items(default))
proc toSeq*(pattern: Captures, default: string = nil): seq[string] =
accumulateResult(pattern.items(default))
proc `$`*(pattern: RegexMatch): string =
return pattern.captures[-1]
proc `==`*(a, b: Regex): bool =
if not a.isNil and not b.isNil:
return a.pattern == b.pattern and
a.pcreObj == b.pcreObj and
a.pcreExtra == b.pcreExtra
else:
return system.`==`(a, b)
proc `==`*(a, b: RegexMatch): bool =
return a.pattern == b.pattern and
a.str == b.str
# }}}
# Creation & Destruction {{{
# PCRE Options {{{
const PcreOptions = {
"NEVER_UTF": pcre.NEVER_UTF,
"ANCHORED": pcre.ANCHORED,
"DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY,
"FIRSTLINE": pcre.FIRSTLINE,
"NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
"JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
"U": pcre.UTF8 or pcre.UCP
}.toTable
# Options that are supported inside regular expressions themselves
const SkipOptions = [
"LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT",
"UTF8", "UTF16", "UTF32", "UTF", "UCP",
"CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE"
]
proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] =
result = ("", 0, true)
var optionStart = 0
var equals = false
for i, c in pattern:
if optionStart == i:
if c != '(':
break
optionStart = i
elif optionStart == i-1:
if c != '*':
break
elif c == ')':
let name = pattern[optionStart+2 .. i-1]
if equals or name in SkipOptions:
result.pattern.add pattern[optionStart .. i]
elif PcreOptions.hasKey name:
result.flags = result.flags or PcreOptions[name]
elif name == "NO_STUDY":
result.study = false
else:
break
optionStart = i+1
equals = false
elif not equals:
if c == '=':
equals = true
if pattern[optionStart+2 .. i] notin SkipOptions:
break
elif c notin {'A'..'Z', '0'..'9', '_'}:
break
result.pattern.add pattern[optionStart .. pattern.high]
# }}}
type UncheckedArray {.unchecked.}[T] = array[0 .. 0, T]
proc destroyRegex(pattern: Regex) =
pcre.free_substring(cast[cstring](pattern.pcreObj))
pattern.pcreObj = nil
if pattern.pcreExtra != nil:
pcre.free_study(pattern.pcreExtra)
proc getNameToNumberTable(pattern: Regex): Table[string, int] =
let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT)
let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE)
let table = cast[ptr UncheckedArray[uint8]](
getinfo[int](pattern, pcre.INFO_NAMETABLE))
result = initTable[string, int]()
for i in 0 .. <entryCount:
let pos = i * entrySize
let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1
var name = ""
var idx = 2
while table[pos + idx] != 0:
name.add(char(table[pos + idx]))
idx += 1
result[name] = num
proc initRegex(pattern: string, flags: int, study = true): Regex =
new(result, destroyRegex)
result.pattern = pattern
var errorMsg: cstring
var errOffset: cint
result.pcreObj = pcre.compile(cstring(pattern),
# better hope int is at least 4 bytes..
cint(flags), addr errorMsg,
addr errOffset, nil)
if result.pcreObj == nil:
# failed to compile
raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
if study:
# XXX investigate JIT
result.pcreExtra = pcre.study(result.pcreObj, 0x0, addr errorMsg)
if errorMsg != nil:
raise StudyError(msg: $errorMsg)
result.captureNameToId = result.getNameToNumberTable()
proc re*(pattern: string): Regex =
let (pattern, flags, study) = extractOptions(pattern)
initRegex(pattern, flags, study)
# }}}
# Operations {{{
proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] =
var myResult = RegexMatch(pattern : pattern, str : str)
# See PCRE man pages.
# 2x capture count to make room for start-end pairs
# 1x capture count as slack space for PCRE
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.setLen(vecsize div 3)
let strlen = if endpos == int.high: str.len else: endpos+1
doAssert(strlen <= str.len) # don't want buffer overflows
let execRet = pcre.exec(pattern.pcreObj,
pattern.pcreExtra,
cstring(str),
cint(strlen),
cint(start),
cint(flags),
cast[ptr cint](addr myResult.pcreMatchBounds[0]),
cint(vecsize))
if execRet >= 0:
return some(myResult)
case execRet:
of pcre.ERROR_NOMATCH:
return none(RegexMatch)
of pcre.ERROR_NULL:
raise newException(AccessViolationError, "Expected non-null parameters")
of pcre.ERROR_BADOPTION:
raise RegexInternalError(msg : "Unknown pattern flag. Either a bug or " &
"outdated PCRE.")
of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET:
raise InvalidUnicodeError(msg : "Invalid unicode byte sequence",
pos : myResult.pcreMatchBounds[0].a)
else:
raise RegexInternalError(msg : "Unknown internal error: " & $execRet)
proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Like ```find(...)`` <#proc-find>`__, but anchored to the start of the
## string. This means that ``"foo".match(re"f") == true``, but
## ``"foo".match(re"o") == false``.
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
## Works the same as ```find(...)`` <#proc-find>`__, but finds every
## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not
## ``"22", "22", "22"``.
##
## Arguments are the same as ```find(...)`` <#proc-find>`__
##
## Variants:
##
## - ``proc findAll(...)`` returns a ``seq[string]``
# see pcredemo for explaination
let matchesCrLf = pattern.matchesCrLf()
let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and
pcre.UTF8) > 0u32
let strlen = if endpos == int.high: str.len else: endpos+1
var offset = start
var match: Option[RegexMatch]
while true:
var flags = 0
if match.isSome and
match.get.matchBounds.a > match.get.matchBounds.b:
# 0-len match
flags = pcre.NOTEMPTY_ATSTART
match = str.matchImpl(pattern, offset, endpos, flags)
if match.isNone:
# either the end of the input or the string
# cannot be split here
if offset >= strlen:
break
if matchesCrLf and offset < (str.len - 1) and
str[offset] == '\r' and str[offset + 1] == '\L':
# if PCRE treats CrLf as newline, skip both at the same time
offset += 2
elif unicode:
# XXX what about invalid unicode?
offset += str.runeLenAt(offset)
assert(offset <= strlen)
else:
offset += 1
else:
offset = match.get.matchBounds.b + 1
yield match.get
proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Finds the given pattern in the string between the end and start
## positions.
##
## ``start``
## The start point at which to start matching. ``|abc`` is ``0``;
## ``a|bc`` is ``1``
##
## ``endpos``
## The maximum index for a match; ``int.high`` means the end of the
## string, otherwise it’s an inclusive upper bound.
return str.matchImpl(pattern, start, endpos, 0)
proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] =
result = @[]
for match in str.findIter(pattern, start, endpos):
result.add(match.match)
proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] =
## Splits the string with the given regex. This works according to the
## rules that Perl and Javascript use:
##
## - If the match is zero-width, then the string is still split:
## ``"123".split(r"") == @["1", "2", "3"]``.
##
## - If the pattern has a capture in it, it is added after the string
## split: ``"12".split(re"(\d)") == @["", "1", "", "2", ""]``.
##
## - If ``maxsplit != -1``, then the string will only be split
## ``maxsplit - 1`` times. This means that there will be ``maxsplit``
## strings in the output seq.
## ``"1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]``
##
## ``start`` behaves the same as in ```find(...)`` <#proc-find>`__.
result = @[]
var lastIdx = start
var splits = 0
var bounds = 0 .. -1
var never_ran = true
for match in str.findIter(pattern, start = start):
never_ran = false
# bounds are inclusive:
#
# 0123456
# ^^^
# (1, 3)
bounds = match.matchBounds
# "12".split("") would be @["", "1", "2"], but
# if we skip an empty first match, it's the correct
# @["1", "2"]
if bounds.a <= bounds.b or bounds.a > start:
result.add(str.substr(lastIdx, bounds.a - 1))
splits += 1
lastIdx = bounds.b + 1
for cap in match.captures:
# if there are captures, include them in the result
result.add(cap)
if splits == maxSplit - 1:
break
# "12".split("\b") would be @["1", "2", ""], but
# if we skip an empty last match, it's the correct
# @["1", "2"]
# If matches were never found, then the input string is the result
if bounds.a <= bounds.b or bounds.b < str.high or never_ran:
# last match: Each match takes the previous substring,
# but "1 2".split(/ /) needs to return @["1", "2"].
# This handles "2"
result.add(str.substr(bounds.b + 1, str.high))
template replaceImpl(str: string, pattern: Regex,
replacement: expr): stmt {.immediate, dirty.} =
# XXX seems very similar to split, maybe I can reduce code duplication
# somehow?
result = ""
var lastIdx = 0
for match {.inject.} in str.findIter(pattern):
let bounds = match.matchBounds
result.add(str.substr(lastIdx, bounds.a - 1))
let nextVal = replacement
assert(nextVal != nil)
result.add(nextVal)
lastIdx = bounds.b + 1
result.add(str.substr(lastIdx, str.len - 1))
return result
proc replace*(str: string, pattern: Regex,
subproc: proc (match: RegexMatch): string): string =
## Replaces each match of Regex in the string with ``sub``, which should
## never be or return ``nil``.
##
## If ``sub`` is a ``proc (RegexMatch): string``, then it is executed with
## each match and the return value is the replacement value.
##
## If ``sub`` is a ``proc (string): string``, then it is executed with the
## full text of the match and and the return value is the replacement
## value.
##
## If ``sub`` is a string, the syntax is as follows:
##
## - ``$$`` - literal ``$``
## - ``$123`` - capture number ``123``
## - ``$foo`` - named capture ``foo``
## - ``${foo}`` - same as above
## - ``$1$#`` - first and second captures
## - ``$#`` - first capture
## - ``$0`` - full match
##
## If a given capture is missing, a ``ValueError`` exception is thrown.
replaceImpl(str, pattern, subproc(match))
proc replace*(str: string, pattern: Regex,
subproc: proc (match: string): string): string =
replaceImpl(str, pattern, subproc(match.match))
proc replace*(str: string, pattern: Regex, sub: string): string =
# - 1 because the string numbers are 0-indexed
replaceImpl(str, pattern,
formatStr(sub, match.captures[name], match.captures[id - 1]))
# }}}
let SpecialCharMatcher = re"([\\+*?[^\]$(){}=!<>|:-])"
proc escapeRe*(str: string): string =
## Escapes the string so it doesn’t match any special characters.
## Incompatible with the Extra flag (``X``).
str.replace(SpecialCharMatcher, "\\$1")

9
lib/impure/nre/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# all executables
*
!*/
!*.*
*.exe
# Wildcard patterns.
*.swp
nimcache

View file

@ -0,0 +1,63 @@
## INTERNAL FILE FOR USE ONLY BY nre.nim.
import tables
proc fget*[K, V](self: Table[K, V], key: K): V =
if self.hasKey(key):
return self[key]
else:
raise newException(KeyError, "Key does not exist in table: " & $key)
const Ident = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
const StartIdent = Ident - {'0'..'9'}
proc checkNil(arg: string): string =
if arg == nil:
raise newException(ValueError, "Cannot use nil capture")
else:
return arg
template formatStr*(howExpr, namegetter, idgetter: expr): expr =
let how = howExpr
var val = newStringOfCap(how.len)
var i = 0
var lastNum = 1
while i < how.len:
if how[i] != '$':
val.add(how[i])
i += 1
else:
if how[i + 1] == '$':
val.add('$')
i += 2
elif how[i + 1] == '#':
var id {.inject.} = lastNum
val.add(checkNil(idgetter))
lastNum += 1
i += 2
elif how[i + 1] in {'0'..'9'}:
i += 1
var id {.inject.} = 0
while i < how.len and how[i] in {'0'..'9'}:
id += (id * 10) + (ord(how[i]) - ord('0'))
i += 1
val.add(checkNil(idgetter))
lastNum = id + 1
elif how[i + 1] in StartIdent:
i += 1
var name {.inject.} = ""
while i < how.len and how[i] in Ident:
name.add(how[i])
i += 1
val.add(checkNil(namegetter))
elif how[i + 1] == '{':
i += 2
var name {.inject.} = ""
while i < how.len and how[i] != '}':
name.add(how[i])
i += 1
i += 1
val.add(checkNil(namegetter))
else:
raise newException(Exception, "Syntax error in format string at " & $i)
val

View file

@ -7,8 +7,8 @@
# distribution, for details about the copyright.
#
## This module contains code for reading from `stdin`:idx:. On UNIX the GNU
## readline library is wrapped and set up to provide default key bindings
## This module contains code for reading from `stdin`:idx:. On UNIX the
## linenoise library is wrapped and set up to provide default key bindings
## (e.g. you can navigate with the arrow keys). On Windows ``system.readLine``
## is used. This suffices because Windows' console already provides the
## wanted functionality.
@ -94,40 +94,34 @@ when defined(Windows):
while i < password.len:
x = runeLenAt(password, i)
inc i, x
password.setLen(password.len - x)
password.setLen(max(password.len - x, 0))
else:
password.add(toUTF8(c.Rune))
stdout.write "\n"
else:
import readline, history, termios, unsigned
import linenoise, termios, unsigned
proc readLineFromStdin*(prompt: string): TaintedString {.
tags: [ReadIOEffect, WriteIOEffect].} =
var buffer = readline.readLine(prompt)
var buffer = linenoise.readLine(prompt)
if isNil(buffer): quit(0)
result = TaintedString($buffer)
if result.string.len > 0:
add_history(buffer)
readline.free(buffer)
historyAdd(buffer)
linenoise.free(buffer)
proc readLineFromStdin*(prompt: string, line: var TaintedString): bool {.
tags: [ReadIOEffect, WriteIOEffect].} =
var buffer = readline.readLine(prompt)
var buffer = linenoise.readLine(prompt)
if isNil(buffer): quit(0)
line = TaintedString($buffer)
if line.string.len > 0:
add_history(buffer)
readline.free(buffer)
historyAdd(buffer)
linenoise.free(buffer)
# XXX how to determine CTRL+D?
result = true
# initialization:
# disable auto-complete:
proc doNothing(a, b: cint): cint {.cdecl, procvar.} = discard
discard readline.bind_key('\t'.ord, doNothing)
proc readPasswordFromStdin*(prompt: string, password: var TaintedString):
bool {.tags: [ReadIOEffect, WriteIOEffect].} =
password.setLen(0)
@ -135,7 +129,7 @@ else:
var cur, old: Termios
discard fd.tcgetattr(cur.addr)
old = cur
cur.c_lflag = cur.c_lflag and not Tcflag(ECHO)
cur.c_lflag = cur.c_lflag and not Cflag(ECHO)
discard fd.tcsetattr(TCSADRAIN, cur.addr)
stdout.write prompt
result = stdin.readLine(password)

View file

@ -7,11 +7,8 @@
# distribution, for details about the copyright.
#
## Regular expression support for Nim. Consider using the pegs module instead.
##
## There is an alternative regular expressions library with a more unified API:
## `nre <https://github.com/flaviut/nre>`_. It may be added to the standard
## library in the future, instead of `re`.
## Regular expression support for Nim. Deprecated. Consider using the ``nre``
## or ``pegs`` modules instead.
##
## **Note:** The 're' proc defaults to the **extended regular expression
## syntax** which lets you use whitespace freely to make your regexes readable.
@ -31,6 +28,8 @@
import
pcre, strutils, rtarrays
{.deprecated.}
const
MaxSubpatterns* = 20
## defines the maximum number of subpatterns that can be captured.
@ -44,11 +43,11 @@ type
reExtended = 3, ## ignore whitespace and ``#`` comments
reStudy = 4 ## study the expression (may be omitted if the
## expression will be used only once)
RegexDesc = object
RegexDesc = object
h: ptr Pcre
e: ptr ExtraData
Regex* = ref RegexDesc ## a compiled regular expression
RegexError* = object of ValueError
@ -79,7 +78,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 = {reExtended, reStudy}): Regex {.deprecated.} =
## Constructor of regular expressions. Note that Nim's
## extended raw string literals support this syntax ``re"[abc]"`` as
## a short form for ``re(r"[abc]")``.
@ -146,8 +145,8 @@ proc findBounds*(s: string, pattern: Regex,
proc findBounds*(s: string, pattern: Regex,
start = 0): tuple[first, last: int] =
## returns the starting position of `pattern` in `s`. If it does not
## match, ``(-1,0)`` is returned.
## returns the starting position and end position of ``pattern`` in ``s``.
## If it does not match, ``(-1,0)`` is returned.
var
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
@ -372,7 +371,7 @@ iterator split*(s: string, sep: Regex): string =
##
## .. code-block:: nim
## for word in split("00232this02939is39an22example111", re"\d+"):
## writeln(stdout, word)
## writeLine(stdout, word)
##
## Results in:
##

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright.
#
## This module provides an easy to use sockets-style
## This module provides an easy to use sockets-style
## nim interface to the OpenSSL library.
{.deprecated.}
@ -15,44 +15,45 @@
import openssl, strutils, os
type
TSecureSocket* = object
SecureSocket* = object
ssl: SslPtr
bio: BIO
{.deprecated: [TSecureSocket: SecureSocket].}
proc connect*(sock: var TSecureSocket, address: string,
proc connect*(sock: var SecureSocket, address: string,
port: int): int =
## Connects to the specified `address` on the specified `port`.
## Returns the result of the certificate validation.
SslLoadErrorStrings()
ERR_load_BIO_strings()
if SSL_library_init() != 1:
raiseOSError(osLastError())
var ctx = SSL_CTX_new(SSLv23_client_method())
if ctx == nil:
ERR_print_errors_fp(stderr)
raiseOSError(osLastError())
#if SSL_CTX_load_verify_locations(ctx,
#if SSL_CTX_load_verify_locations(ctx,
# "/tmp/openssl-0.9.8e/certs/vsign1.pem", NIL) == 0:
# echo("Failed load verify locations")
# ERR_print_errors_fp(stderr)
sock.bio = BIO_new_ssl_connect(ctx)
if BIO_get_ssl(sock.bio, addr(sock.ssl)) == 0:
raiseOSError(osLastError())
if BIO_set_conn_hostname(sock.bio, address & ":" & $port) != 1:
raiseOSError(osLastError())
if BIO_do_connect(sock.bio) <= 0:
ERR_print_errors_fp(stderr)
raiseOSError(osLastError())
result = SSL_get_verify_result(sock.ssl)
proc recvLine*(sock: TSecureSocket, line: var TaintedString): bool =
proc recvLine*(sock: SecureSocket, line: var TaintedString): bool =
## Acts in a similar fashion to the `recvLine` in the sockets module.
## Returns false when no data is available to be read.
## `Line` must be initialized and not nil!
@ -71,26 +72,26 @@ proc recvLine*(sock: TSecureSocket, line: var TaintedString): bool =
add(line.string, c)
proc send*(sock: TSecureSocket, data: string) =
proc send*(sock: SecureSocket, data: string) =
## Writes `data` to the socket.
if BIO_write(sock.bio, data, data.len.cint) <= 0:
raiseOSError(osLastError())
proc close*(sock: TSecureSocket) =
proc close*(sock: SecureSocket) =
## Closes the socket
if BIO_free(sock.bio) <= 0:
ERR_print_errors_fp(stderr)
raiseOSError(osLastError())
when not defined(testing) and isMainModule:
var s: TSecureSocket
var s: SecureSocket
echo connect(s, "smtp.gmail.com", 465)
#var buffer: array[0..255, char]
#echo BIO_read(bio, buffer, buffer.len)
var buffer: string = ""
echo s.recvLine(buffer)
echo buffer
echo buffer
echo buffer.len

View file

@ -1,183 +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.
#
## This module implements a zip archive creator/reader/modifier.
import
streams, libzip, times, os, strutils
type
TZipArchive* = object of RootObj ## represents a zip archive
mode: FileMode
w: PZip
proc zipError(z: var TZipArchive) =
var e: ref IOError
new(e)
e.msg = $zip_strerror(z.w)
raise e
proc open*(z: var TZipArchive, filename: string, mode: FileMode = fmRead): bool =
## Opens a zip file for reading, writing or appending. All file modes are
## supported. Returns true iff successful, false otherwise.
var err, flags: int32
case mode
of fmRead, fmReadWriteExisting, fmAppend: flags = 0
of fmWrite:
if existsFile(filename): removeFile(filename)
flags = ZIP_CREATE or ZIP_EXCL
of fmReadWrite: flags = ZIP_CREATE
z.w = zip_open(filename, flags, addr(err))
z.mode = mode
result = z.w != nil
proc close*(z: var TZipArchive) =
## Closes a zip file.
zip_close(z.w)
proc createDir*(z: var TZipArchive, dir: string) =
## Creates a directory within the `z` archive. This does not fail if the
## directory already exists. Note that for adding a file like
## ``"path1/path2/filename"`` it is not necessary
## to create the ``"path/path2"`` subdirectories - it will be done
## automatically by ``addFile``.
assert(z.mode != fmRead)
discard zip_add_dir(z.w, dir)
zip_error_clear(z.w)
proc addFile*(z: var TZipArchive, dest, src: string) =
## Adds the file `src` to the archive `z` with the name `dest`. `dest`
## may contain a path that will be created.
assert(z.mode != fmRead)
if not fileExists(src):
raise newException(IOError, "File '" & src & "' does not exist")
var zipsrc = zip_source_file(z.w, src, 0, -1)
if zipsrc == nil:
#echo("Dest: " & dest)
#echo("Src: " & src)
zipError(z)
if zip_add(z.w, dest, zipsrc) < 0'i32:
zip_source_free(zipsrc)
zipError(z)
proc addFile*(z: var TZipArchive, file: string) =
## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is
## the name of the destination.
addFile(z, file, file)
proc mySourceCallback(state, data: pointer, len: int,
cmd: TZipSourceCmd): int {.cdecl.} =
var src = cast[Stream](state)
case cmd
of ZIP_SOURCE_OPEN:
if src.setPositionImpl != nil: setPosition(src, 0) # reset
of ZIP_SOURCE_READ:
result = readData(src, data, len)
of ZIP_SOURCE_CLOSE: close(src)
of ZIP_SOURCE_STAT:
var stat = cast[PZipStat](data)
zip_stat_init(stat)
stat.size = high(int32)-1 # we don't know the size
stat.mtime = getTime()
result = sizeof(TZipStat)
of ZIP_SOURCE_ERROR:
var err = cast[ptr array[0..1, cint]](data)
err[0] = ZIP_ER_INTERNAL
err[1] = 0
result = 2*sizeof(cint)
of constZIP_SOURCE_FREE: GC_unref(src)
else: assert(false)
proc addFile*(z: var TZipArchive, dest: string, src: Stream) =
## Adds a file named with `dest` to the archive `z`. `dest`
## may contain a path. The file's content is read from the `src` stream.
assert(z.mode != fmRead)
GC_ref(src)
var zipsrc = zip_source_function(z.w, mySourceCallback, cast[pointer](src))
if zipsrc == nil: zipError(z)
if zip_add(z.w, dest, zipsrc) < 0'i32:
zip_source_free(zipsrc)
zipError(z)
# -------------- zip file stream ---------------------------------------------
type
TZipFileStream = object of StreamObj
f: PZipFile
atEnd: bool
PZipFileStream* =
ref TZipFileStream ## a reader stream of a file within a zip archive
proc fsClose(s: Stream) = zip_fclose(PZipFileStream(s).f)
proc fsAtEnd(s: Stream): bool = PZipFileStream(s).atEnd
proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
result = zip_fread(PZipFileStream(s).f, buffer, bufLen)
if result == 0:
PZipFileStream(s).atEnd = true
proc newZipFileStream(f: PZipFile): PZipFileStream =
new(result)
result.f = f
result.atEnd = false
result.closeImpl = fsClose
result.readDataImpl = fsReadData
result.atEndImpl = fsAtEnd
# other methods are nil!
# ----------------------------------------------------------------------------
proc getStream*(z: var TZipArchive, filename: string): PZipFileStream =
## returns a stream that can be used to read the file named `filename`
## from the archive `z`. Returns nil in case of an error.
## The returned stream does not support the `setPosition`, `getPosition`,
## `writeData` or `atEnd` methods.
var x = zip_fopen(z.w, filename, 0'i32)
if x != nil: result = newZipFileStream(x)
iterator walkFiles*(z: var TZipArchive): string =
## walks over all files in the archive `z` and returns the filename
## (including the path).
var i = 0'i32
var num = zip_get_num_files(z.w)
while i < num:
yield $zip_get_name(z.w, i, 0'i32)
inc(i)
proc extractFile*(z: var TZipArchive, srcFile: string, dest: Stream) =
## extracts a file from the zip archive `z` to the destination stream.
var strm = getStream(z, srcFile)
while true:
if not strm.atEnd:
dest.write(strm.readStr(1))
else: break
dest.flush()
strm.close()
proc extractFile*(z: var TZipArchive, srcFile: string, dest: string) =
## extracts a file from the zip archive `z` to the destination filename.
var file = newFileStream(dest, fmWrite)
extractFile(z, srcFile, file)
file.close()
proc extractAll*(z: var TZipArchive, dest: string) =
## extracts all files from archive `z` to the destination directory.
for file in walkFiles(z):
if file.endsWith("/"):
createDir(dest / file)
else:
extractFile(z, file, dest / file)
when not defined(testing) and isMainModule:
var zip: TZipArchive
if not zip.open("nim-0.11.0.zip"):
raise newException(IOError, "opening zip failed")
zip.extractAll("test")

View file

@ -110,18 +110,31 @@ __clang__
# endif
# define N_LIB_IMPORT extern __declspec(dllimport)
#else
# define N_CDECL(rettype, name) rettype name
# define N_STDCALL(rettype, name) rettype name
# define N_SYSCALL(rettype, name) rettype name
# define N_FASTCALL(rettype, name) rettype name
# define N_SAFECALL(rettype, name) rettype name
/* function pointers with calling convention: */
# define N_CDECL_PTR(rettype, name) rettype (*name)
# define N_STDCALL_PTR(rettype, name) rettype (*name)
# define N_SYSCALL_PTR(rettype, name) rettype (*name)
# define N_FASTCALL_PTR(rettype, name) rettype (*name)
# define N_SAFECALL_PTR(rettype, name) rettype (*name)
# if defined(__GNUC__)
# define N_CDECL(rettype, name) rettype name
# define N_STDCALL(rettype, name) rettype name
# define N_SYSCALL(rettype, name) rettype name
# define N_FASTCALL(rettype, name) __attribute__((fastcall)) rettype name
# define N_SAFECALL(rettype, name) rettype name
/* function pointers with calling convention: */
# define N_CDECL_PTR(rettype, name) rettype (*name)
# define N_STDCALL_PTR(rettype, name) rettype (*name)
# define N_SYSCALL_PTR(rettype, name) rettype (*name)
# define N_FASTCALL_PTR(rettype, name) __attribute__((fastcall)) rettype (*name)
# define N_SAFECALL_PTR(rettype, name) rettype (*name)
# else
# define N_CDECL(rettype, name) rettype name
# define N_STDCALL(rettype, name) rettype name
# define N_SYSCALL(rettype, name) rettype name
# define N_FASTCALL(rettype, name) rettype name
# define N_SAFECALL(rettype, name) rettype name
/* function pointers with calling convention: */
# define N_CDECL_PTR(rettype, name) rettype (*name)
# define N_STDCALL_PTR(rettype, name) rettype (*name)
# define N_SYSCALL_PTR(rettype, name) rettype (*name)
# define N_FASTCALL_PTR(rettype, name) rettype (*name)
# define N_SAFECALL_PTR(rettype, name) rettype (*name)
# endif
# ifdef __cplusplus
# define N_LIB_EXPORT extern "C"
# else
@ -147,9 +160,15 @@ __clang__
#if defined(__BORLANDC__) || defined(__WATCOMC__) || \
defined(__POCC__) || defined(_MSC_VER) || defined(WIN32) || defined(_WIN32)
/* these compilers have a fastcall so use it: */
# define N_NIMCALL(rettype, name) rettype __fastcall name
# define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name)
# define N_RAW_NIMCALL __fastcall
# ifdef __TINYC__
# define N_NIMCALL(rettype, name) rettype __attribute((__fastcall)) name
# define N_NIMCALL_PTR(rettype, name) rettype (__attribute((__fastcall)) *name)
# define N_RAW_NIMCALL __attribute((__fastcall))
# else
# define N_NIMCALL(rettype, name) rettype __fastcall name
# define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name)
# define N_RAW_NIMCALL __fastcall
# endif
#else
# define N_NIMCALL(rettype, name) rettype name /* no modifier */
# define N_NIMCALL_PTR(rettype, name) rettype (*name)
@ -347,7 +366,7 @@ struct TFrame {
FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = 0; nimFrame(&FR);
#define nimfrs(proc, file, slots, length) \
struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} FR; \
struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; VarSlot s[slots];} FR; \
FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = length; nimFrame((TFrame*)&FR);
#define nimln(n, file) \
@ -399,6 +418,10 @@ typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(
# define NIM_EXTERNC
#endif
/* we have to tinker with TNimType as it's both part of system.nim and
typeinfo.nim but system.nim doesn't export it cleanly... */
typedef struct TNimType TNimType;
/* ---------------- platform specific includes ----------------------- */
/* VxWorks related includes */

View file

@ -7,8 +7,8 @@
# distribution, for details about the copyright.
#
## Main file to generate a DLL from the standard library.
## The default Nimrtl does not only contain the ``system`` module, but these
## Main file to generate a DLL from the standard library.
## The default Nimrtl does not only contain the ``system`` module, but these
## too:
##
## * parseutils
@ -22,12 +22,12 @@
## * unicode
## * pegs
## * ropes
##
##
when system.appType != "lib":
{.error: "This file has to be compiled as a library!".}
when not defined(createNimRtl):
when not defined(createNimRtl):
{.error: "This file has to be compiled with '-d:createNimRtl'".}
import

View file

@ -15,7 +15,7 @@ import
strutils
type
TTokenClass* = enum
TokenClass* = enum
gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber,
gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit,
gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff
@ -23,20 +23,22 @@ type
gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler,
gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel,
gtReference, gtOther
TGeneralTokenizer* = object of RootObj
kind*: TTokenClass
GeneralTokenizer* = object of RootObj
kind*: TokenClass
start*, length*: int
buf: cstring
pos: int
state: TTokenClass
state: TokenClass
TSourceLanguage* = enum
SourceLanguage* = enum
langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava
{.deprecated: [TSourceLanguage: SourceLanguage, TTokenClass: TokenClass,
TGeneralTokenizer: GeneralTokenizer].}
const
sourceLanguageToStr*: array[TSourceLanguage, string] = ["none",
sourceLanguageToStr*: array[SourceLanguage, string] = ["none",
"Nim", "Nimrod", "C++", "C#", "C", "Java"]
tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace",
tokenClassToStr*: array[TokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
"EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment",
@ -58,29 +60,29 @@ const
"template", "try", "tuple", "type", "using", "var", "when", "while", "with",
"without", "xor", "yield"]
proc getSourceLanguage*(name: string): TSourceLanguage =
for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)):
proc getSourceLanguage*(name: string): SourceLanguage =
for i in countup(succ(low(SourceLanguage)), high(SourceLanguage)):
if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0:
return i
result = langNone
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) =
proc initGeneralTokenizer*(g: var GeneralTokenizer, buf: cstring) =
g.buf = buf
g.kind = low(TTokenClass)
g.kind = low(TokenClass)
g.start = 0
g.length = 0
g.state = low(TTokenClass)
g.state = low(TokenClass)
var pos = 0 # skip initial whitespace:
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
g.pos = pos
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
proc initGeneralTokenizer*(g: var GeneralTokenizer, buf: string) =
initGeneralTokenizer(g, cstring(buf))
proc deinitGeneralTokenizer*(g: var TGeneralTokenizer) =
proc deinitGeneralTokenizer*(g: var GeneralTokenizer) =
discard
proc nimGetKeyword(id: string): TTokenClass =
proc nimGetKeyword(id: string): TokenClass =
for k in nimKeywords:
if cmpIgnoreStyle(id, k) == 0: return gtKeyword
result = gtIdentifier
@ -92,7 +94,7 @@ proc nimGetKeyword(id: string): TTokenClass =
else:
result = gtIdentifier
proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int =
proc nimNumberPostfix(g: var GeneralTokenizer, position: int): int =
var pos = position
if g.buf[pos] == '\'':
inc(pos)
@ -110,7 +112,7 @@ proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int =
discard
result = pos
proc nimNumber(g: var TGeneralTokenizer, position: int): int =
proc nimNumber(g: var GeneralTokenizer, position: int): int =
const decChars = {'0'..'9', '_'}
var pos = position
g.kind = gtDecNumber
@ -130,7 +132,7 @@ const
OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
'|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'}
proc nimNextToken(g: var TGeneralTokenizer) =
proc nimNextToken(g: var GeneralTokenizer) =
const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f', '_'}
octChars = {'0'..'7', '_'}
@ -278,7 +280,7 @@ proc nimNextToken(g: var TGeneralTokenizer) =
assert false, "nimNextToken: produced an empty token"
g.pos = pos
proc generalNumber(g: var TGeneralTokenizer, position: int): int =
proc generalNumber(g: var GeneralTokenizer, position: int): int =
const decChars = {'0'..'9'}
var pos = position
g.kind = gtDecNumber
@ -294,7 +296,7 @@ proc generalNumber(g: var TGeneralTokenizer, position: int): int =
while g.buf[pos] in decChars: inc(pos)
result = pos
proc generalStrLit(g: var TGeneralTokenizer, position: int): int =
proc generalStrLit(g: var GeneralTokenizer, position: int): int =
const
decChars = {'0'..'9'}
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
@ -355,12 +357,13 @@ proc isKeywordIgnoreCase(x: openArray[string], y: string): int =
result = - 1
type
TTokenizerFlag = enum
TokenizerFlag = enum
hasPreprocessor, hasNestedComments
TTokenizerFlags = set[TTokenizerFlag]
TokenizerFlags = set[TokenizerFlag]
{.deprecated: [TTokenizerFlag: TokenizerFlag, TTokenizerFlags: TokenizerFlags].}
proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
flags: TTokenizerFlags) =
proc clikeNextToken(g: var GeneralTokenizer, keywords: openArray[string],
flags: TokenizerFlags) =
const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
octChars = {'0'..'7'}
@ -493,7 +496,7 @@ proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
assert false, "clikeNextToken: produced an empty token"
g.pos = pos
proc cNextToken(g: var TGeneralTokenizer) =
proc cNextToken(g: var GeneralTokenizer) =
const
keywords: array[0..36, string] = ["_Bool", "_Complex", "_Imaginary", "auto",
"break", "case", "char", "const", "continue", "default", "do", "double",
@ -503,7 +506,7 @@ proc cNextToken(g: var TGeneralTokenizer) =
"volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc cppNextToken(g: var TGeneralTokenizer) =
proc cppNextToken(g: var GeneralTokenizer) =
const
keywords: array[0..47, string] = ["asm", "auto", "break", "case", "catch",
"char", "class", "const", "continue", "default", "delete", "do", "double",
@ -514,7 +517,7 @@ proc cppNextToken(g: var TGeneralTokenizer) =
"union", "unsigned", "virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc csharpNextToken(g: var TGeneralTokenizer) =
proc csharpNextToken(g: var GeneralTokenizer) =
const
keywords: array[0..76, string] = ["abstract", "as", "base", "bool", "break",
"byte", "case", "catch", "char", "checked", "class", "const", "continue",
@ -529,7 +532,7 @@ proc csharpNextToken(g: var TGeneralTokenizer) =
"virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc javaNextToken(g: var TGeneralTokenizer) =
proc javaNextToken(g: var GeneralTokenizer) =
const
keywords: array[0..52, string] = ["abstract", "assert", "boolean", "break",
"byte", "case", "catch", "char", "class", "const", "continue", "default",
@ -541,7 +544,7 @@ proc javaNextToken(g: var TGeneralTokenizer) =
"try", "void", "volatile", "while"]
clikeNextToken(g, keywords, {})
proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) =
proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) =
case lang
of langNone: assert false
of langNim, langNimrod: nimNextToken(g)

View file

@ -15,7 +15,7 @@ import
os, strutils, rstast
type
TRstParseOption* = enum ## options for the RST parser
RstParseOption* = enum ## options for the RST parser
roSkipPounds, ## skip ``#`` at line beginning (documentation
## embedded in Nim comments)
roSupportSmilies, ## make the RST parser support smilies like ``:)``
@ -23,14 +23,14 @@ type
## it for sandboxing)
roSupportMarkdown ## support additional features of markdown
TRstParseOptions* = set[TRstParseOption]
RstParseOptions* = set[RstParseOption]
TMsgClass* = enum
MsgClass* = enum
mcHint = "Hint",
mcWarning = "Warning",
mcError = "Error"
TMsgKind* = enum ## the possible messages
MsgKind* = enum ## the possible messages
meCannotOpenFile,
meExpected,
meGridTableNotImplemented,
@ -42,12 +42,14 @@ type
mwUnsupportedLanguage,
mwUnsupportedField
TMsgHandler* = proc (filename: string, line, col: int, msgKind: TMsgKind,
MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind,
arg: string) {.nimcall.} ## what to do in case of an error
TFindFileHandler* = proc (filename: string): string {.nimcall.}
FindFileHandler* = proc (filename: string): string {.nimcall.}
{.deprecated: [TRstParseOptions: RstParseOptions, TRstParseOption: RstParseOption,
TMsgKind: MsgKind].}
const
messages: array [TMsgKind, string] = [
messages: array [MsgKind, string] = [
meCannotOpenFile: "cannot open '$1'",
meExpected: "'$1' expected",
meGridTableNotImplemented: "grid table is not implemented",
@ -111,23 +113,24 @@ const
}
type
TTokType = enum
TokType = enum
tkEof, tkIndent, tkWhite, tkWord, tkAdornment, tkPunct, tkOther
TToken = object # a RST token
kind*: TTokType # the type of the token
Token = object # a RST token
kind*: TokType # the type of the token
ival*: int # the indentation or parsed integer value
symbol*: string # the parsed symbol as string
line*, col*: int # line and column of the token
TTokenSeq = seq[TToken]
TLexer = object of RootObj
TokenSeq = seq[Token]
Lexer = object of RootObj
buf*: cstring
bufpos*: int
line*, col*, baseIndent*: int
skipPounds*: bool
{.deprecated: [TTokType: TokType, TToken: Token, TTokenSeq: TokenSeq,
TLexer: Lexer].}
proc getThing(L: var TLexer, tok: var TToken, s: set[char]) =
proc getThing(L: var Lexer, tok: var Token, s: set[char]) =
tok.kind = tkWord
tok.line = L.line
tok.col = L.col
@ -139,7 +142,7 @@ proc getThing(L: var TLexer, tok: var TToken, s: set[char]) =
inc(L.col, pos - L.bufpos)
L.bufpos = pos
proc getAdornment(L: var TLexer, tok: var TToken) =
proc getAdornment(L: var Lexer, tok: var Token) =
tok.kind = tkAdornment
tok.line = L.line
tok.col = L.col
@ -152,7 +155,7 @@ proc getAdornment(L: var TLexer, tok: var TToken) =
inc(L.col, pos - L.bufpos)
L.bufpos = pos
proc getIndentAux(L: var TLexer, start: int): int =
proc getIndentAux(L: var Lexer, start: int): int =
var pos = start
var buf = L.buf
# skip the newline (but include it in the token!)
@ -181,7 +184,7 @@ proc getIndentAux(L: var TLexer, start: int): int =
result = getIndentAux(L, pos)
L.bufpos = pos # no need to set back buf
proc getIndent(L: var TLexer, tok: var TToken) =
proc getIndent(L: var Lexer, tok: var Token) =
tok.col = 0
tok.kind = tkIndent # skip the newline (but include it in the token!)
tok.ival = getIndentAux(L, L.bufpos)
@ -191,7 +194,7 @@ proc getIndent(L: var TLexer, tok: var TToken) =
tok.ival = max(tok.ival - L.baseIndent, 0)
tok.symbol = "\n" & spaces(tok.ival)
proc rawGetTok(L: var TLexer, tok: var TToken) =
proc rawGetTok(L: var Lexer, tok: var Token) =
tok.symbol = ""
tok.ival = 0
var c = L.buf[L.bufpos]
@ -222,8 +225,8 @@ proc rawGetTok(L: var TLexer, tok: var TToken) =
inc(L.col)
tok.col = max(tok.col - L.baseIndent, 0)
proc getTokens(buffer: string, skipPounds: bool, tokens: var TTokenSeq): int =
var L: TLexer
proc getTokens(buffer: string, skipPounds: bool, tokens: var TokenSeq): int =
var L: Lexer
var length = len(tokens)
L.buf = cstring(buffer)
L.line = 0 # skip UTF-8 BOM
@ -253,31 +256,31 @@ proc getTokens(buffer: string, skipPounds: bool, tokens: var TTokenSeq): int =
tokens[0].kind = tkIndent
type
TLevelMap = array[char, int]
TSubstitution = object
LevelMap = array[char, int]
Substitution = object
key*: string
value*: PRstNode
TSharedState = object
options: TRstParseOptions # parsing options
SharedState = object
options: RstParseOptions # parsing options
uLevel, oLevel: int # counters for the section levels
subs: seq[TSubstitution] # substitutions
refs: seq[TSubstitution] # references
underlineToLevel: TLevelMap # Saves for each possible title adornment
subs: seq[Substitution] # substitutions
refs: seq[Substitution] # references
underlineToLevel: LevelMap # Saves for each possible title adornment
# character its level in the
# current document.
# This is for single underline adornments.
overlineToLevel: TLevelMap # Saves for each possible title adornment
overlineToLevel: LevelMap # Saves for each possible title adornment
# character its level in the current
# document.
# This is for over-underline adornments.
msgHandler: TMsgHandler # How to handle errors.
findFile: TFindFileHandler # How to find files.
msgHandler: MsgHandler # How to handle errors.
findFile: FindFileHandler # How to find files.
PSharedState = ref TSharedState
TRstParser = object of RootObj
PSharedState = ref SharedState
RstParser = object of RootObj
idx*: int
tok*: TTokenSeq
tok*: TokenSeq
s*: PSharedState
indentStack*: seq[int]
filename*: string
@ -285,8 +288,12 @@ 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: TMsgKind): TMsgClass =
proc whichMsgClass*(k: MsgKind): MsgClass =
## returns which message class `k` belongs to.
case ($k)[1]
of 'e', 'E': result = mcError
@ -294,21 +301,21 @@ proc whichMsgClass*(k: TMsgKind): TMsgClass =
of 'h', 'H': result = mcHint
else: assert false, "msgkind does not fit naming scheme"
proc defaultMsgHandler*(filename: string, line, col: int, msgkind: TMsgKind,
proc defaultMsgHandler*(filename: string, line, col: int, msgkind: MsgKind,
arg: string) {.procvar.} =
let mc = msgkind.whichMsgClass
let a = messages[msgkind] % arg
let message = "$1($2, $3) $4: $5" % [filename, $line, $col, $mc, a]
if mc == mcError: raise newException(EParseError, message)
else: writeln(stdout, message)
else: writeLine(stdout, message)
proc defaultFindFile*(filename: string): string {.procvar.} =
if existsFile(filename): result = filename
else: result = ""
proc newSharedState(options: TRstParseOptions,
findFile: TFindFileHandler,
msgHandler: TMsgHandler): PSharedState =
proc newSharedState(options: RstParseOptions,
findFile: FindFileHandler,
msgHandler: MsgHandler): PSharedState =
new(result)
result.subs = @[]
result.refs = @[]
@ -316,34 +323,34 @@ proc newSharedState(options: TRstParseOptions,
result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler
result.findFile = if not isNil(findFile): findFile else: defaultFindFile
proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) =
proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,
p.col + p.tok[p.idx].col, msgKind, arg)
proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string, line, col: int) =
proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string, line, col: int) =
p.s.msgHandler(p.filename, p.line + line,
p.col + col, msgKind, arg)
proc rstMessage(p: TRstParser, msgKind: TMsgKind) =
proc rstMessage(p: RstParser, msgKind: MsgKind) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,
p.col + p.tok[p.idx].col, msgKind,
p.tok[p.idx].symbol)
when false:
proc corrupt(p: TRstParser) =
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: TRstParser): int =
proc currInd(p: RstParser): int =
result = p.indentStack[high(p.indentStack)]
proc pushInd(p: var TRstParser, ind: int) =
proc pushInd(p: var RstParser, ind: int) =
add(p.indentStack, ind)
proc popInd(p: var TRstParser) =
proc popInd(p: var RstParser) =
if len(p.indentStack) > 1: setLen(p.indentStack, len(p.indentStack) - 1)
proc initParser(p: var TRstParser, sharedState: PSharedState) =
proc initParser(p: var RstParser, sharedState: PSharedState) =
p.indentStack = @[0]
p.tok = @[]
p.idx = 0
@ -393,7 +400,7 @@ proc rstnodeToRefname(n: PRstNode): string =
var b = false
rstnodeToRefnameAux(n, result, b)
proc findSub(p: var TRstParser, n: PRstNode): int =
proc findSub(p: var RstParser, n: PRstNode): int =
var key = addNodes(n)
# the spec says: if no exact match, try one without case distinction:
for i in countup(0, high(p.s.subs)):
@ -404,7 +411,7 @@ proc findSub(p: var TRstParser, n: PRstNode): int =
return i
result = -1
proc setSub(p: var TRstParser, key: string, value: PRstNode) =
proc setSub(p: var RstParser, key: string, value: PRstNode) =
var length = len(p.s.subs)
for i in countup(0, length - 1):
if key == p.s.subs[i].key:
@ -414,7 +421,7 @@ proc setSub(p: var TRstParser, key: string, value: PRstNode) =
p.s.subs[length].key = key
p.s.subs[length].value = value
proc setRef(p: var TRstParser, key: string, value: PRstNode) =
proc setRef(p: var RstParser, key: string, value: PRstNode) =
var length = len(p.s.refs)
for i in countup(0, length - 1):
if key == p.s.refs[i].key:
@ -427,15 +434,15 @@ proc setRef(p: var TRstParser, key: string, value: PRstNode) =
p.s.refs[length].key = key
p.s.refs[length].value = value
proc findRef(p: var TRstParser, key: string): PRstNode =
proc findRef(p: var RstParser, key: string): PRstNode =
for i in countup(0, high(p.s.refs)):
if key == p.s.refs[i].key:
return p.s.refs[i].value
proc newLeaf(p: var TRstParser): PRstNode =
proc newLeaf(p: var RstParser): PRstNode =
result = newRstNode(rnLeaf, p.tok[p.idx].symbol)
proc getReferenceName(p: var TRstParser, endStr: string): PRstNode =
proc getReferenceName(p: var RstParser, endStr: string): PRstNode =
var res = newRstNode(rnInner)
while true:
case p.tok[p.idx].kind
@ -453,17 +460,17 @@ proc getReferenceName(p: var TRstParser, endStr: string): PRstNode =
inc(p.idx)
result = res
proc untilEol(p: var TRstParser): PRstNode =
proc untilEol(p: var RstParser): PRstNode =
result = newRstNode(rnInner)
while not (p.tok[p.idx].kind in {tkIndent, tkEof}):
add(result, newLeaf(p))
inc(p.idx)
proc expect(p: var TRstParser, tok: string) =
proc expect(p: var RstParser, tok: string) =
if p.tok[p.idx].symbol == tok: inc(p.idx)
else: rstMessage(p, meExpected, tok)
proc isInlineMarkupEnd(p: TRstParser, markup: string): bool =
proc isInlineMarkupEnd(p: RstParser, markup: string): bool =
result = p.tok[p.idx].symbol == markup
if not result:
return # Rule 3:
@ -480,7 +487,7 @@ proc isInlineMarkupEnd(p: TRstParser, markup: string): bool =
if (markup != "``") and (p.tok[p.idx - 1].symbol == "\\"):
result = false
proc isInlineMarkupStart(p: TRstParser, markup: string): bool =
proc isInlineMarkupStart(p: RstParser, markup: string): bool =
var d: char
result = p.tok[p.idx].symbol == markup
if not result:
@ -507,7 +514,7 @@ proc isInlineMarkupStart(p: TRstParser, markup: string): bool =
else: d = '\0'
if d != '\0': result = p.tok[p.idx + 1].symbol[0] != d
proc match(p: TRstParser, start: int, expr: string): bool =
proc match(p: RstParser, start: int, expr: string): bool =
# regular expressions are:
# special char exact match
# 'w' tkWord
@ -562,7 +569,7 @@ proc fixupEmbeddedRef(n, a, b: PRstNode) =
for i in countup(0, sep - incr): add(a, n.sons[i])
for i in countup(sep + 1, len(n) - 2): add(b, n.sons[i])
proc parsePostfix(p: var TRstParser, n: PRstNode): PRstNode =
proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode =
result = n
if isInlineMarkupEnd(p, "_") or isInlineMarkupEnd(p, "__"):
inc(p.idx)
@ -606,7 +613,7 @@ proc parsePostfix(p: var TRstParser, n: PRstNode): PRstNode =
add(result, newRstNode(rnLeaf, p.tok[p.idx + 1].symbol))
inc(p.idx, 3)
proc matchVerbatim(p: TRstParser, start: int, expr: string): int =
proc matchVerbatim(p: RstParser, start: int, expr: string): int =
result = start
var j = 0
while j < expr.len and result < p.tok.len and
@ -615,7 +622,7 @@ proc matchVerbatim(p: TRstParser, start: int, expr: string): int =
inc result
if j < expr.len: result = 0
proc parseSmiley(p: var TRstParser): PRstNode =
proc parseSmiley(p: var RstParser): PRstNode =
if p.tok[p.idx].symbol[0] notin SmileyStartChars: return
for key, val in items(Smilies):
let m = matchVerbatim(p, p.idx, key)
@ -631,12 +638,12 @@ when false:
'$', '(', ')', '~', '_', '?', '+', '-', '=', '\\', '.', '&',
'\128'..'\255'}
proc isUrl(p: TRstParser, i: int): bool =
proc isUrl(p: RstParser, i: int): bool =
result = (p.tok[i+1].symbol == ":") and (p.tok[i+2].symbol == "//") and
(p.tok[i+3].kind == tkWord) and
(p.tok[i].symbol in ["http", "https", "ftp", "telnet", "file"])
proc parseUrl(p: var TRstParser, father: PRstNode) =
proc parseUrl(p: var RstParser, father: PRstNode) =
#if p.tok[p.idx].symbol[strStart] == '<':
if isUrl(p, p.idx):
var n = newRstNode(rnStandaloneHyperlink)
@ -656,7 +663,7 @@ proc parseUrl(p: var TRstParser, father: PRstNode) =
if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n)
add(father, n)
proc parseBackslash(p: var TRstParser, father: PRstNode) =
proc parseBackslash(p: var RstParser, father: PRstNode) =
assert(p.tok[p.idx].kind == tkPunct)
if p.tok[p.idx].symbol == "\\\\":
add(father, newRstNode(rnLeaf, "\\"))
@ -671,7 +678,7 @@ proc parseBackslash(p: var TRstParser, father: PRstNode) =
inc(p.idx)
when false:
proc parseAdhoc(p: var TRstParser, father: PRstNode, verbatim: bool) =
proc parseAdhoc(p: var RstParser, father: PRstNode, verbatim: bool) =
if not verbatim and isURL(p, p.idx):
var n = newRstNode(rnStandaloneHyperlink)
while true:
@ -694,7 +701,7 @@ when false:
if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n)
add(father, n)
proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string,
proc parseUntil(p: var RstParser, father: PRstNode, postfix: string,
interpretBackslash: bool) =
let
line = p.tok[p.idx].line
@ -725,7 +732,7 @@ proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string,
inc(p.idx)
else: rstMessage(p, meExpected, postfix, line, col)
proc parseMarkdownCodeblock(p: var TRstParser): PRstNode =
proc parseMarkdownCodeblock(p: var RstParser): PRstNode =
var args = newRstNode(rnDirArg)
if p.tok[p.idx].kind == tkWord:
add(args, newLeaf(p))
@ -755,7 +762,7 @@ proc parseMarkdownCodeblock(p: var TRstParser): PRstNode =
add(result, nil)
add(result, lb)
proc parseInline(p: var TRstParser, father: PRstNode) =
proc parseInline(p: var RstParser, father: PRstNode) =
case p.tok[p.idx].kind
of tkPunct:
if isInlineMarkupStart(p, "***"):
@ -810,7 +817,7 @@ proc parseInline(p: var TRstParser, father: PRstNode) =
inc(p.idx)
else: discard
proc getDirective(p: var TRstParser): string =
proc getDirective(p: var RstParser): string =
if p.tok[p.idx].kind == tkWhite and p.tok[p.idx+1].kind == tkWord:
var j = p.idx
inc(p.idx)
@ -830,7 +837,7 @@ proc getDirective(p: var TRstParser): string =
else:
result = ""
proc parseComment(p: var TRstParser): PRstNode =
proc parseComment(p: var RstParser): PRstNode =
case p.tok[p.idx].kind
of tkIndent, tkEof:
if p.tok[p.idx].kind != tkEof and p.tok[p.idx + 1].kind == tkIndent:
@ -851,34 +858,35 @@ proc parseComment(p: var TRstParser): PRstNode =
result = nil
type
TDirKind = enum # must be ordered alphabetically!
DirKind = enum # must be ordered alphabetically!
dkNone, dkAuthor, dkAuthors, dkCode, dkCodeBlock, dkContainer, dkContents,
dkFigure, dkImage, dkInclude, dkIndex, dkRaw, dkTitle
{.deprecated: [TDirKind: DirKind].}
const
DirIds: array[0..12, string] = ["", "author", "authors", "code",
"code-block", "container", "contents", "figure", "image", "include",
"index", "raw", "title"]
proc getDirKind(s: string): TDirKind =
proc getDirKind(s: string): DirKind =
let i = find(DirIds, s)
if i >= 0: result = TDirKind(i)
if i >= 0: result = DirKind(i)
else: result = dkNone
proc parseLine(p: var TRstParser, father: PRstNode) =
proc parseLine(p: var RstParser, father: PRstNode) =
while true:
case p.tok[p.idx].kind
of tkWhite, tkWord, tkOther, tkPunct: parseInline(p, father)
else: break
proc parseUntilNewline(p: var TRstParser, father: PRstNode) =
proc parseUntilNewline(p: var RstParser, father: PRstNode) =
while true:
case p.tok[p.idx].kind
of tkWhite, tkWord, tkAdornment, tkOther, tkPunct: parseInline(p, father)
of tkEof, tkIndent: break
proc parseSection(p: var TRstParser, result: PRstNode) {.gcsafe.}
proc parseField(p: var TRstParser): PRstNode =
proc parseSection(p: var RstParser, result: PRstNode) {.gcsafe.}
proc parseField(p: var RstParser): PRstNode =
## Returns a parsed rnField node.
##
## rnField nodes have two children nodes, a rnFieldName and a rnFieldBody.
@ -897,7 +905,7 @@ proc parseField(p: var TRstParser): PRstNode =
add(result, fieldname)
add(result, fieldbody)
proc parseFields(p: var TRstParser): PRstNode =
proc parseFields(p: var RstParser): PRstNode =
## Parses fields for a section or directive block.
##
## This proc may return nil if the parsing doesn't find anything of value,
@ -947,8 +955,8 @@ proc getArgument(n: PRstNode): string =
if n.sons[0] == nil: result = ""
else: result = addNodes(n.sons[0])
proc parseDotDot(p: var TRstParser): PRstNode {.gcsafe.}
proc parseLiteralBlock(p: var TRstParser): PRstNode =
proc parseDotDot(p: var RstParser): PRstNode {.gcsafe.}
proc parseLiteralBlock(p: var RstParser): PRstNode =
result = newRstNode(rnLiteralBlock)
var n = newRstNode(rnLeaf, "")
if p.tok[p.idx].kind == tkIndent:
@ -974,13 +982,13 @@ proc parseLiteralBlock(p: var TRstParser): PRstNode =
inc(p.idx)
add(result, n)
proc getLevel(map: var TLevelMap, lvl: var int, c: char): int =
proc getLevel(map: var LevelMap, lvl: var int, c: char): int =
if map[c] == 0:
inc(lvl)
map[c] = lvl
result = map[c]
proc tokenAfterNewline(p: TRstParser): int =
proc tokenAfterNewline(p: RstParser): int =
result = p.idx
while true:
case p.tok[result].kind
@ -991,28 +999,28 @@ proc tokenAfterNewline(p: TRstParser): int =
break
else: inc(result)
proc isLineBlock(p: TRstParser): bool =
proc isLineBlock(p: RstParser): bool =
var j = tokenAfterNewline(p)
result = (p.tok[p.idx].col == p.tok[j].col) and (p.tok[j].symbol == "|") or
(p.tok[j].col > p.tok[p.idx].col)
proc predNL(p: TRstParser): bool =
proc predNL(p: RstParser): bool =
result = true
if p.idx > 0:
result = p.tok[p.idx-1].kind == tkIndent and
p.tok[p.idx-1].ival == currInd(p)
proc isDefList(p: TRstParser): bool =
proc isDefList(p: RstParser): bool =
var j = tokenAfterNewline(p)
result = (p.tok[p.idx].col < p.tok[j].col) and
(p.tok[j].kind in {tkWord, tkOther, tkPunct}) and
(p.tok[j - 2].symbol != "::")
proc isOptionList(p: TRstParser): bool =
proc isOptionList(p: RstParser): bool =
result = match(p, p.idx, "-w") or match(p, p.idx, "--w") or
match(p, p.idx, "/w") or match(p, p.idx, "//w")
proc whichSection(p: TRstParser): TRstNodeKind =
proc whichSection(p: RstParser): RstNodeKind =
case p.tok[p.idx].kind
of tkAdornment:
if match(p, p.idx + 1, "ii"): result = rnTransition
@ -1053,7 +1061,7 @@ proc whichSection(p: TRstParser): TRstNodeKind =
else: result = rnParagraph
else: result = rnLeaf
proc parseLineBlock(p: var TRstParser): PRstNode =
proc parseLineBlock(p: var RstParser): PRstNode =
result = nil
if p.tok[p.idx + 1].kind == tkWhite:
var col = p.tok[p.idx].col
@ -1072,7 +1080,7 @@ proc parseLineBlock(p: var TRstParser): PRstNode =
break
popInd(p)
proc parseParagraph(p: var TRstParser, result: PRstNode) =
proc parseParagraph(p: var RstParser, result: PRstNode) =
while true:
case p.tok[p.idx].kind
of tkIndent:
@ -1103,7 +1111,7 @@ proc parseParagraph(p: var TRstParser, result: PRstNode) =
parseInline(p, result)
else: break
proc parseHeadline(p: var TRstParser): PRstNode =
proc parseHeadline(p: var RstParser): PRstNode =
result = newRstNode(rnHeadline)
parseUntilNewline(p, result)
assert(p.tok[p.idx].kind == tkIndent)
@ -1113,12 +1121,13 @@ proc parseHeadline(p: var TRstParser): PRstNode =
result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c)
type
TIntSeq = seq[int]
IntSeq = seq[int]
{.deprecated: [TIntSeq: IntSeq].}
proc tokEnd(p: TRstParser): int =
proc tokEnd(p: RstParser): int =
result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1
proc getColumns(p: var TRstParser, cols: var TIntSeq) =
proc getColumns(p: var RstParser, cols: var IntSeq) =
var L = 0
while true:
inc(L)
@ -1133,15 +1142,15 @@ proc getColumns(p: var TRstParser, cols: var TIntSeq) =
# last column has no limit:
cols[L - 1] = 32000
proc parseDoc(p: var TRstParser): PRstNode {.gcsafe.}
proc parseDoc(p: var RstParser): PRstNode {.gcsafe.}
proc parseSimpleTable(p: var TRstParser): PRstNode =
proc parseSimpleTable(p: var RstParser): PRstNode =
var
cols: TIntSeq
cols: IntSeq
row: seq[string]
i, last, line: int
c: char
q: TRstParser
q: RstParser
a, b: PRstNode
result = newRstNode(rnTable)
cols = @[]
@ -1188,13 +1197,13 @@ proc parseSimpleTable(p: var TRstParser): PRstNode =
add(a, b)
add(result, a)
proc parseTransition(p: var TRstParser): PRstNode =
proc parseTransition(p: var RstParser): PRstNode =
result = newRstNode(rnTransition)
inc(p.idx)
if p.tok[p.idx].kind == tkIndent: inc(p.idx)
if p.tok[p.idx].kind == tkIndent: inc(p.idx)
proc parseOverline(p: var TRstParser): PRstNode =
proc parseOverline(p: var RstParser): PRstNode =
var c = p.tok[p.idx].symbol[0]
inc(p.idx, 2)
result = newRstNode(rnOverline)
@ -1213,7 +1222,7 @@ proc parseOverline(p: var TRstParser): PRstNode =
inc(p.idx) # XXX: check?
if p.tok[p.idx].kind == tkIndent: inc(p.idx)
proc parseBulletList(p: var TRstParser): PRstNode =
proc parseBulletList(p: var RstParser): PRstNode =
result = nil
if p.tok[p.idx + 1].kind == tkWhite:
var bullet = p.tok[p.idx].symbol
@ -1233,7 +1242,7 @@ proc parseBulletList(p: var TRstParser): PRstNode =
break
popInd(p)
proc parseOptionList(p: var TRstParser): PRstNode =
proc parseOptionList(p: var RstParser): PRstNode =
result = newRstNode(rnOptionList)
while true:
if isOptionList(p):
@ -1262,7 +1271,7 @@ proc parseOptionList(p: var TRstParser): PRstNode =
else:
break
proc parseDefinitionList(p: var TRstParser): PRstNode =
proc parseDefinitionList(p: var RstParser): PRstNode =
result = nil
var j = tokenAfterNewline(p) - 1
if (j >= 1) and (p.tok[j].kind == tkIndent) and
@ -1298,7 +1307,7 @@ proc parseDefinitionList(p: var TRstParser): PRstNode =
break
if len(result) == 0: result = nil
proc parseEnumList(p: var TRstParser): PRstNode =
proc parseEnumList(p: var RstParser): PRstNode =
const
wildcards: array[0..2, string] = ["(e) ", "e) ", "e. "]
wildpos: array[0..2, int] = [1, 0, 0]
@ -1328,11 +1337,11 @@ proc parseEnumList(p: var TRstParser): PRstNode =
dec(p.idx, wildpos[w] + 3)
result = nil
proc sonKind(father: PRstNode, i: int): TRstNodeKind =
proc sonKind(father: PRstNode, i: int): RstNodeKind =
result = rnLeaf
if i < len(father): result = father.sons[i].kind
proc parseSection(p: var TRstParser, result: PRstNode) =
proc parseSection(p: var RstParser, result: PRstNode) =
while true:
var leave = false
assert(p.idx >= 0)
@ -1380,16 +1389,16 @@ proc parseSection(p: var TRstParser, result: PRstNode) =
if sonKind(result, 0) == rnParagraph and sonKind(result, 1) != rnParagraph:
result.sons[0].kind = rnInner
proc parseSectionWrapper(p: var TRstParser): PRstNode =
proc parseSectionWrapper(p: var RstParser): PRstNode =
result = newRstNode(rnInner)
parseSection(p, result)
while (result.kind == rnInner) and (len(result) == 1):
result = result.sons[0]
proc `$`(t: TToken): string =
proc `$`(t: Token): string =
result = $t.kind & ' ' & (if isNil(t.symbol): "NIL" else: t.symbol)
proc parseDoc(p: var TRstParser): PRstNode =
proc parseDoc(p: var RstParser): PRstNode =
result = parseSectionWrapper(p)
if p.tok[p.idx].kind != tkEof:
when false:
@ -1403,12 +1412,14 @@ proc parseDoc(p: var TRstParser): PRstNode =
rstMessage(p, meGeneralParseError)
type
TDirFlag = enum
DirFlag = enum
hasArg, hasOptions, argIsFile, argIsWord
TDirFlags = set[TDirFlag]
TSectionParser = proc (p: var TRstParser): PRstNode {.nimcall.}
DirFlags = set[DirFlag]
SectionParser = proc (p: var RstParser): PRstNode {.nimcall.}
{.deprecated: [TDirFlag: DirFlag, TDirFlags: DirFlags,
TSectionParser: SectionParser].}
proc parseDirective(p: var TRstParser, flags: TDirFlags): PRstNode =
proc parseDirective(p: var RstParser, flags: DirFlags): PRstNode =
## Parses arguments and options for a directive block.
##
## A directive block will always have three sons: the arguments for the
@ -1446,11 +1457,11 @@ proc parseDirective(p: var TRstParser, flags: TDirFlags): PRstNode =
options = parseFields(p)
add(result, options)
proc indFollows(p: TRstParser): bool =
proc indFollows(p: RstParser): bool =
result = p.tok[p.idx].kind == tkIndent and p.tok[p.idx].ival > currInd(p)
proc parseDirective(p: var TRstParser, flags: TDirFlags,
contentParser: TSectionParser): PRstNode =
proc parseDirective(p: var RstParser, flags: DirFlags,
contentParser: SectionParser): PRstNode =
## Returns a generic rnDirective tree.
##
## The children are rnDirArg, rnFieldList and rnLineBlock. Any might be nil.
@ -1463,13 +1474,13 @@ proc parseDirective(p: var TRstParser, flags: TDirFlags,
else:
add(result, nil)
proc parseDirBody(p: var TRstParser, contentParser: TSectionParser): PRstNode =
proc parseDirBody(p: var RstParser, contentParser: SectionParser): PRstNode =
if indFollows(p):
pushInd(p, p.tok[p.idx].ival)
result = contentParser(p)
popInd(p)
proc dirInclude(p: var TRstParser): PRstNode =
proc dirInclude(p: var RstParser): PRstNode =
#
#The following options are recognized:
#
@ -1498,7 +1509,7 @@ proc dirInclude(p: var TRstParser): PRstNode =
result = newRstNode(rnLiteralBlock)
add(result, newRstNode(rnLeaf, readFile(path)))
else:
var q: TRstParser
var q: RstParser
initParser(q, p.s)
q.filename = filename
q.col += getTokens(readFile(path), false, q.tok)
@ -1507,7 +1518,7 @@ proc dirInclude(p: var TRstParser): PRstNode =
# InternalError("Too many binary zeros in include file")
result = parseDoc(q)
proc dirCodeBlock(p: var TRstParser, nimrodExtension = false): PRstNode =
proc dirCodeBlock(p: var RstParser, nimrodExtension = false): PRstNode =
## Parses a code block.
##
## Code blocks are rnDirective trees with a `kind` of rnCodeBlock. See the
@ -1548,35 +1559,35 @@ proc dirCodeBlock(p: var TRstParser, nimrodExtension = false): PRstNode =
result.kind = rnCodeBlock
proc dirContainer(p: var TRstParser): PRstNode =
proc dirContainer(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, parseSectionWrapper)
assert(result.kind == rnDirective)
assert(len(result) == 3)
result.kind = rnContainer
proc dirImage(p: var TRstParser): PRstNode =
proc dirImage(p: var RstParser): PRstNode =
result = parseDirective(p, {hasOptions, hasArg, argIsFile}, nil)
result.kind = rnImage
proc dirFigure(p: var TRstParser): PRstNode =
proc dirFigure(p: var RstParser): PRstNode =
result = parseDirective(p, {hasOptions, hasArg, argIsFile},
parseSectionWrapper)
result.kind = rnFigure
proc dirTitle(p: var TRstParser): PRstNode =
proc dirTitle(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, nil)
result.kind = rnTitle
proc dirContents(p: var TRstParser): PRstNode =
proc dirContents(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, nil)
result.kind = rnContents
proc dirIndex(p: var TRstParser): PRstNode =
proc dirIndex(p: var RstParser): PRstNode =
result = parseDirective(p, {}, parseSectionWrapper)
result.kind = rnIndex
proc dirRawAux(p: var TRstParser, result: var PRstNode, kind: TRstNodeKind,
contentParser: TSectionParser) =
proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind,
contentParser: SectionParser) =
var filename = getFieldValue(result, "file")
if filename.len > 0:
var path = p.s.findFile(filename)
@ -1590,7 +1601,7 @@ proc dirRawAux(p: var TRstParser, result: var PRstNode, kind: TRstNodeKind,
result.kind = kind
add(result, parseDirBody(p, contentParser))
proc dirRaw(p: var TRstParser): PRstNode =
proc dirRaw(p: var RstParser): PRstNode =
#
#The following options are recognized:
#
@ -1610,7 +1621,7 @@ proc dirRaw(p: var TRstParser): PRstNode =
else:
dirRawAux(p, result, rnRaw, parseSectionWrapper)
proc parseDotDot(p: var TRstParser): PRstNode =
proc parseDotDot(p: var RstParser): PRstNode =
result = nil
var col = p.tok[p.idx].col
inc(p.idx)
@ -1667,7 +1678,7 @@ proc parseDotDot(p: var TRstParser): PRstNode =
else:
result = parseComment(p)
proc resolveSubs(p: var TRstParser, n: PRstNode): PRstNode =
proc resolveSubs(p: var RstParser, n: PRstNode): PRstNode =
result = n
if n == nil: return
case n.kind
@ -1696,10 +1707,10 @@ proc resolveSubs(p: var TRstParser, n: PRstNode): PRstNode =
proc rstParse*(text, filename: string,
line, column: int, hasToc: var bool,
options: TRstParseOptions,
findFile: TFindFileHandler = nil,
msgHandler: TMsgHandler = nil): PRstNode =
var p: TRstParser
options: RstParseOptions,
findFile: FindFileHandler = nil,
msgHandler: MsgHandler = nil): PRstNode =
var p: RstParser
initParser(p, newSharedState(options, findFile, msgHandler))
p.filename = filename
p.line = line

View file

@ -12,7 +12,7 @@
import strutils, json
type
TRstNodeKind* = enum ## the possible node kinds of an PRstNode
RstNodeKind* = enum ## the possible node kinds of an PRstNode
rnInner, # an inner node or a root
rnHeadline, # a headline
rnOverline, # an over- and underlined headline
@ -62,24 +62,26 @@ type
# leaf val
PRstNode* = ref TRstNode ## an RST node
TRstNodeSeq* = seq[PRstNode]
TRstNode* {.acyclic, final.} = object ## an RST node's description
kind*: TRstNodeKind ## the node's kind
PRstNode* = ref RstNode ## an RST node
RstNodeSeq* = seq[PRstNode]
RstNode* {.acyclic, final.} = object ## an RST node's description
kind*: RstNodeKind ## the node's kind
text*: string ## valid for leafs in the AST; and the title of
## the document or the section
level*: int ## valid for some node kinds
sons*: TRstNodeSeq ## the node's sons
sons*: RstNodeSeq ## the node's sons
{.deprecated: [TRstNodeKind: RstNodeKind, TRstNodeSeq: RstNodeSeq,
TRstNode: RstNode].}
proc len*(n: PRstNode): int =
result = len(n.sons)
proc newRstNode*(kind: TRstNodeKind): PRstNode =
proc newRstNode*(kind: RstNodeKind): PRstNode =
new(result)
result.sons = @[]
result.kind = kind
proc newRstNode*(kind: TRstNodeKind, s: string): PRstNode =
proc newRstNode*(kind: RstNodeKind, s: string): PRstNode =
result = newRstNode(kind)
result.text = s
@ -94,18 +96,19 @@ proc addIfNotNil*(father, son: PRstNode) =
type
TRenderContext {.pure.} = object
RenderContext {.pure.} = object
indent: int
verbatim: int
{.deprecated: [TRenderContext: RenderContext].}
proc renderRstToRst(d: var TRenderContext, n: PRstNode,
proc renderRstToRst(d: var RenderContext, n: PRstNode,
result: var string) {.gcsafe.}
proc renderRstSons(d: var TRenderContext, n: PRstNode, result: var string) =
proc renderRstSons(d: var RenderContext, n: PRstNode, result: var string) =
for i in countup(0, len(n) - 1):
renderRstToRst(d, n.sons[i], result)
proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
proc renderRstToRst(d: var RenderContext, n: PRstNode, result: var string) =
# this is needed for the index generation; it may also be useful for
# debugging, but most code is already debugged...
const
@ -284,7 +287,7 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
proc renderRstToRst*(n: PRstNode, result: var string) =
## renders `n` into its string representation and appends to `result`.
var d: TRenderContext
var d: RenderContext
renderRstToRst(d, n, result)
proc renderRstToJsonNode(node: PRstNode): JsonNode =

View file

@ -18,7 +18,7 @@
## document you provide yourself, so it won't contain the usual ``<header>`` or
## ``<body>`` parts.
##
## You can also create a ``TRstGenerator`` structure and populate it with the
## You can also create a ``RstGenerator`` structure and populate it with the
## other lower level methods to finally build complete documents. This requires
## many options and tweaking, but you are not limited to snippets and can
## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.
@ -31,29 +31,30 @@ const
IndexExt* = ".idx"
type
TOutputTarget* = enum ## which document type to generate
OutputTarget* = enum ## which document type to generate
outHtml, # output is HTML
outLatex # output is Latex
TTocEntry = object
TocEntry = object
n*: PRstNode
refname*, header*: string
TMetaEnum* = enum
MetaEnum* = enum
metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
TRstGenerator* = object of RootObj
target*: TOutputTarget
RstGenerator* = object of RootObj
target*: OutputTarget
config*: StringTableRef
splitAfter*: int # split too long entries in the TOC
tocPart*: seq[TTocEntry]
listingCounter: int
tocPart*: seq[TocEntry]
hasToc*: bool
theIndex: string # Contents of the index file to be dumped at the end.
options*: TRstParseOptions
findFile*: TFindFileHandler
msgHandler*: TMsgHandler
options*: RstParseOptions
findFile*: FindFileHandler
msgHandler*: MsgHandler
filename*: string
meta*: array[TMetaEnum, string]
meta*: array[MetaEnum, string]
currentSection: string ## \
## Stores the empty string or the last headline/overline found in the rst
## document, so it can be used as a prettier name for term index generation.
@ -61,14 +62,15 @@ type
## Keeps count of same text index terms to generate different identifiers
## for hyperlinks. See renderIndexTerm proc for details.
PDoc = var TRstGenerator ## Alias to type less.
PDoc = var RstGenerator ## Alias to type less.
CodeBlockParams = object ## Stores code block params.
numberLines: bool ## True if the renderer has to show line numbers.
startLine: int ## The starting line of the code block, by default 1.
langStr: string ## Input string used to specify the language.
lang: TSourceLanguage ## Type of highlighting, by default none.
lang: SourceLanguage ## Type of highlighting, by default none.
{.deprecated: [TRstGenerator: RstGenerator, TTocEntry: TocEntry,
TOutputTarget: OutputTarget, TMetaEnum: MetaEnum].}
proc init(p: var CodeBlockParams) =
## Default initialisation of CodeBlockParams to sane values.
@ -76,14 +78,14 @@ proc init(p: var CodeBlockParams) =
p.lang = langNone
p.langStr = ""
proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
config: StringTableRef, filename: string,
options: TRstParseOptions,
findFile: TFindFileHandler=nil,
msgHandler: TMsgHandler=nil) =
## Initializes a ``TRstGenerator``.
options: RstParseOptions,
findFile: FindFileHandler=nil,
msgHandler: MsgHandler=nil) =
## Initializes a ``RstGenerator``.
##
## You need to call this before using a ``TRstGenerator`` with any other
## You need to call this before using a ``RstGenerator`` with any other
## procs in this module. Pass a non ``nil`` ``StringTableRef`` value as
## `config` with parameters used by the HTML output generator. If you don't
## know what to use, pass the results of the `defaultConfig()
@ -96,7 +98,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
## filename``. This default title can be overriden by the embedded rst, but
## it helps to prettify the generated index if no title is found.
##
## The ``TRstParseOptions``, ``TFindFileHandler`` and ``TMsgHandler`` types
## The ``RstParseOptions``, ``FindFileHandler`` and ``MsgHandler`` types
## are defined in the the `packages/docutils/rst module <rst.html>`_.
## ``options`` selects the behaviour of the rst parser.
##
@ -120,7 +122,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
##
## import packages/docutils/rstgen
##
## var gen: TRstGenerator
## var gen: RstGenerator
## gen.initRstGenerator(outHtml, defaultConfig(), "filename", {})
g.config = config
g.target = target
@ -137,11 +139,11 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
g.seenIndexTerms = initTable[string, int]()
g.msgHandler = msgHandler
let s = config["split.item.toc"]
let s = config.getOrDefault"split.item.toc"
if s != "": g.splitAfter = parseInt(s)
for i in low(g.meta)..high(g.meta): g.meta[i] = ""
proc writeIndexFile*(g: var TRstGenerator, outfile: string) =
proc writeIndexFile*(g: var RstGenerator, outfile: string) =
## Writes the current index buffer to the specified output file.
##
## You previously need to add entries to the index with the `setIndexTerm()
@ -183,7 +185,7 @@ proc addTexChar(dest: var string, c: char) =
var splitter*: string = "<wbr />"
proc escChar*(target: TOutputTarget, dest: var string, c: char) {.inline.} =
proc escChar*(target: OutputTarget, dest: var string, c: char) {.inline.} =
case target
of outHtml: addXmlChar(dest, c)
of outLatex: addTexChar(dest, c)
@ -200,7 +202,7 @@ proc nextSplitPoint*(s: string, start: int): int =
inc(result)
dec(result) # last valid index
proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
proc esc*(target: OutputTarget, s: string, splitAfter = -1): string =
result = ""
if splitAfter >= 0:
var partLen = 0
@ -217,16 +219,16 @@ proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
for i in countup(0, len(s) - 1): escChar(target, result, s[i])
proc disp(target: TOutputTarget, xml, tex: string): string =
proc disp(target: OutputTarget, xml, tex: string): string =
if target != outLatex: result = xml
else: result = tex
proc dispF(target: TOutputTarget, xml, tex: string,
proc dispF(target: OutputTarget, xml, tex: string,
args: varargs[string]): string =
if target != outLatex: result = xml % args
else: result = tex % args
proc dispA(target: TOutputTarget, dest: var string,
proc dispA(target: OutputTarget, dest: var string,
xml, tex: string, args: varargs[string]) =
if target != outLatex: addf(dest, xml, args)
else: addf(dest, tex, args)
@ -234,10 +236,10 @@ proc dispA(target: TOutputTarget, dest: var string,
proc `or`(x, y: string): string {.inline.} =
result = if x.isNil: y else: x
proc renderRstToOut*(d: var TRstGenerator, n: PRstNode, result: var string)
proc renderRstToOut*(d: var RstGenerator, n: PRstNode, result: var string)
## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration.
##
## Before using this proc you need to initialise a ``TRstGenerator`` with
## Before using this proc you need to initialise a ``RstGenerator`` with
## ``initRstGenerator`` and parse a rst file with ``rstParse`` from the
## `packages/docutils/rst module <rst.html>`_. Example:
##
@ -277,7 +279,7 @@ proc unquoteIndexColumn(text: string): string =
## Returns the unquoted version generated by ``quoteIndexColumn``.
result = text.replace("\\t", "\t").replace("\\n", "\n").replace("\\\\", "\\")
proc setIndexTerm*(d: var TRstGenerator, id, term: string,
proc setIndexTerm*(d: var RstGenerator, id, term: string,
linkTitle, linkDesc = "") =
## Adds a `term` to the index using the specified hyperlink identifier.
##
@ -339,10 +341,10 @@ proc renderIndexTerm*(d: PDoc, n: PRstNode, result: var string) =
## previously appeared to give a different identifier value for each.
let refname = n.rstnodeToRefname
if d.seenIndexTerms.hasKey(refname):
d.seenIndexTerms[refname] = d.seenIndexTerms[refname] + 1
d.seenIndexTerms[refname] = d.seenIndexTerms.getOrDefault(refname) + 1
else:
d.seenIndexTerms[refname] = 1
let id = refname & '_' & $d.seenIndexTerms[refname]
let id = refname & '_' & $d.seenIndexTerms.getOrDefault(refname)
var term = ""
renderAux(d, n, term)
@ -351,30 +353,30 @@ proc renderIndexTerm*(d: PDoc, n: PRstNode, result: var string) =
[id, term])
type
TIndexEntry = object
IndexEntry = object
keyword: string
link: string
linkTitle: string ## If not nil, contains a prettier text for the href
linkDesc: string ## If not nil, the title attribute of the final href
TIndexedDocs = Table[TIndexEntry, seq[TIndexEntry]] ## \
IndexedDocs = Table[IndexEntry, seq[IndexEntry]] ## \
## Contains the index sequences for doc types.
##
## The key is a *fake* TIndexEntry which will contain the title of the
## The key is a *fake* IndexEntry which will contain the title of the
## document in the `keyword` field and `link` will contain the html
## filename for the document. `linkTitle` and `linkDesc` will be nil.
##
## The value indexed by this TIndexEntry is a sequence with the real index
## 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: TIndexEntry): int =
## Sorts two ``TIndexEntry`` first by `keyword` field, then by `link`.
proc cmp(a, b: IndexEntry): int =
## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`.
result = cmpIgnoreStyle(a.keyword, b.keyword)
if result == 0:
result = cmpIgnoreStyle(a.link, b.link)
proc hash(x: TIndexEntry): THash =
proc hash(x: IndexEntry): Hash =
## Returns the hash for the combined fields of the type.
##
## The hash is computed as the chained hash of the individual string hashes.
@ -385,7 +387,7 @@ proc hash(x: TIndexEntry): THash =
result = result !& (x.linkDesc or "").hash
result = !$result
proc `<-`(a: var TIndexEntry, b: TIndexEntry) =
proc `<-`(a: var IndexEntry, b: IndexEntry) =
shallowCopy a.keyword, b.keyword
shallowCopy a.link, b.link
if b.linkTitle.isNil: a.linkTitle = nil
@ -393,7 +395,7 @@ proc `<-`(a: var TIndexEntry, b: TIndexEntry) =
if b.linkDesc.isNil: a.linkDesc = nil
else: shallowCopy a.linkDesc, b.linkDesc
proc sortIndex(a: var openArray[TIndexEntry]) =
proc sortIndex(a: var openArray[IndexEntry]) =
# we use shellsort here; fast and simple
let n = len(a)
var h = 1
@ -403,7 +405,7 @@ proc sortIndex(a: var openArray[TIndexEntry]) =
while true:
h = h div 3
for i in countup(h, n - 1):
var v: TIndexEntry
var v: IndexEntry
v <- a[i]
var j = i
while cmp(a[j-h], v) >= 0:
@ -413,7 +415,7 @@ proc sortIndex(a: var openArray[TIndexEntry]) =
a[j] <- v
if h == 1: break
proc generateSymbolIndex(symbols: seq[TIndexEntry]): string =
proc generateSymbolIndex(symbols: seq[IndexEntry]): string =
result = ""
var i = 0
while i < symbols.len:
@ -466,7 +468,7 @@ proc indentToLevel(level: var int, newLevel: int): string =
result = repeat("</ul>", level - newLevel)
level = newLevel
proc generateDocumentationTOC(entries: seq[TIndexEntry]): string =
proc generateDocumentationTOC(entries: seq[IndexEntry]): string =
## Returns the sequence of index entries in an HTML hierarchical list.
result = ""
# Build a list of levels and extracted titles to make processing easier.
@ -507,25 +509,25 @@ proc generateDocumentationTOC(entries: seq[TIndexEntry]): string =
assert(not titleRef.isNil,
"Can't use this proc on an API index, docs always have a title entry")
proc generateDocumentationIndex(docs: TIndexedDocs): string =
proc generateDocumentationIndex(docs: IndexedDocs): string =
## Returns all the documentation TOCs in an HTML hierarchical list.
result = ""
# Sort the titles to generate their toc in alphabetical order.
var titles = toSeq(keys[TIndexEntry, seq[TIndexEntry]](docs))
var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
sort(titles, cmp)
for title in titles:
let tocList = generateDocumentationTOC(docs[title])
let tocList = generateDocumentationTOC(docs.getOrDefault(title))
result.add("<ul><li><a href=\"" &
title.link & "\">" & title.keyword & "</a>\n" & tocList & "</ul>\n")
proc generateDocumentationJumps(docs: TIndexedDocs): string =
proc generateDocumentationJumps(docs: IndexedDocs): string =
## Returns a plain list of hyperlinks to documentation TOCs in HTML.
result = "Documents: "
# Sort the titles to generate their toc in alphabetical order.
var titles = toSeq(keys[TIndexEntry, seq[TIndexEntry]](docs))
var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
sort(titles, cmp)
var chunks: seq[string] = @[]
@ -545,14 +547,14 @@ proc generateModuleJumps(modules: seq[string]): string =
result.add(chunks.join(", ") & ".<br>")
proc readIndexDir(dir: string):
tuple[modules: seq[string], symbols: seq[TIndexEntry], docs: TIndexedDocs] =
## Walks `dir` reading ``.idx`` files converting them in TIndexEntry items.
tuple[modules: seq[string], symbols: seq[IndexEntry], docs: IndexedDocs] =
## Walks `dir` reading ``.idx`` files converting them in IndexEntry items.
##
## Returns the list of found module names, the list of free symbol entries
## and the different documentation indexes. The list of modules is sorted.
## See the documentation of ``mergeIndexes`` for details.
result.modules = @[]
result.docs = initTable[TIndexEntry, seq[TIndexEntry]](32)
result.docs = initTable[IndexEntry, seq[IndexEntry]](32)
newSeq(result.symbols, 15_000)
setLen(result.symbols, 0)
var L = 0
@ -560,8 +562,8 @@ proc readIndexDir(dir: string):
for kind, path in walkDir(dir):
if kind == pcFile and path.endsWith(IndexExt):
var
fileEntries: seq[TIndexEntry]
title: TIndexEntry
fileEntries: seq[IndexEntry]
title: IndexEntry
F = 0
newSeq(fileEntries, 500)
setLen(fileEntries, 0)
@ -662,7 +664,7 @@ proc mergeIndexes*(dir: string): string =
proc stripTOCHTML(s: string): string =
## Ugly quick hack to remove HTML tags from TOC titles.
##
## A TTocEntry.header field already contains rendered HTML tags. Instead of
## A TocEntry.header field already contains rendered HTML tags. Instead of
## implementing a proper version of renderRstToOut() which recursively
## renders an rst tree to plain text, we simply remove text found between
## angled brackets. Given the limited possibilities of rst inside TOC titles
@ -728,12 +730,12 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
rstnodeToRefname(n), tmp, $chr(n.level - 1 + ord('A'))])
proc renderTocEntry(d: PDoc, e: TTocEntry, result: var string) =
proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) =
dispA(d.target, result,
"<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n",
"\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header])
proc renderTocEntries*(d: var TRstGenerator, j: var int, lvl: int,
proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int,
result: var string) =
var tmp = ""
while j <= high(d.tocPart):
@ -753,7 +755,7 @@ proc renderTocEntries*(d: var TRstGenerator, j: var int, lvl: int,
proc renderImage(d: PDoc, n: PRstNode, result: var string) =
template valid(s): expr =
s.len > 0 and allCharsInSet(s, {'/',':','%','_','\\','\128'..'\xFF'} +
s.len > 0 and allCharsInSet(s, {'.','/',':','%','_','\\','\128'..'\xFF'} +
Digits + Letters + WhiteSpace)
var options = ""
@ -784,7 +786,8 @@ proc renderSmiley(d: PDoc, n: PRstNode, result: var string) =
dispA(d.target, result,
"""<img src="$1" width="15"
height="17" hspace="2" vspace="2" class="smiley" />""",
"\\includegraphics{$1}", [d.config["doc.smiley_format"] % n.text])
"\\includegraphics{$1}",
[d.config.getOrDefault"doc.smiley_format" % n.text])
proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) =
## Parses useful fields which can appear before a code block.
@ -831,7 +834,7 @@ proc parseCodeBlockParams(d: PDoc, n: PRstNode): CodeBlockParams =
if result.langStr != "":
result.lang = getSourceLanguage(result.langStr)
proc buildLinesHTMLTable(params: CodeBlockParams, code: string):
proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string):
tuple[beginTable, endTable: string] =
## Returns the necessary tags to start/end a code block in HTML.
##
@ -839,20 +842,26 @@ proc buildLinesHTMLTable(params: CodeBlockParams, code: string):
## <pre> pair. Otherwise it will build a table and insert an initial column
## with all the line numbers, which requires you to pass the `code` to detect
## how many lines have to be generated (and starting at which point!).
inc d.listingCounter
let id = $d.listingCounter
if not params.numberLines:
result = ("<pre>", "</pre>")
result = (d.config.getOrDefault"doc.listing_start" % id,
d.config.getOrDefault"doc.listing_end" % id)
return
var codeLines = 1 + code.strip.countLines
assert codeLines > 0
result.beginTable = """<table class="line-nums-table"><tbody><tr><td class="blob-line-nums"><pre>"""
result.beginTable = """<table class="line-nums-table"><tbody><tr><td class="blob-line-nums"><pre class="line-nums">"""
var line = params.startLine
while codeLines > 0:
result.beginTable.add($line & "\n")
line.inc
codeLines.dec
result.beginTable.add("</pre></td><td><pre>")
result.endTable = "</pre></td></tr></tbody></table>"
result.beginTable.add("</pre></td><td>" & (
d.config.getOrDefault"doc.listing_start" % id))
result.endTable = (d.config.getOrDefault"doc.listing_end" % id) &
"</td></tr></tbody></table>" & (
d.config.getOrDefault"doc.listing_button" % id)
proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
## Renders a code block, appending it to `result`.
@ -870,7 +879,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
var m = n.sons[2].sons[0]
assert m.kind == rnLeaf
let (blockStart, blockEnd) = params.buildLinesHTMLTable(m.text)
let (blockStart, blockEnd) = buildLinesHTMLTable(d, params, m.text)
dispA(d.target, result, blockStart, "\\begin{rstpre}\n", [])
if params.lang == langNone:
@ -878,7 +887,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr)
for letter in m.text: escChar(d.target, result, letter)
else:
var g: TGeneralTokenizer
var g: GeneralTokenizer
initGeneralTokenizer(g, m.text)
while true:
getNextToken(g, params.lang)
@ -1208,13 +1217,16 @@ $moduledesc
$content
</div>
""")
setConfigVar("doc.listing_start", "<pre class = \"listing\">")
setConfigVar("doc.listing_end", "</pre>")
setConfigVar("doc.listing_button", "</pre>")
setConfigVar("doc.body_no_toc", "$moduledesc $content")
setConfigVar("doc.file", "$content")
setConfigVar("doc.smiley_format", "/images/smilies/$1.gif")
# ---------- forum ---------------------------------------------------------
proc rstToHtml*(s: string, options: TRstParseOptions,
proc rstToHtml*(s: string, options: RstParseOptions,
config: StringTableRef): string =
## Converts an input rst string into embeddable HTML.
##
@ -1233,7 +1245,7 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
## # --> <em>Hello</em> <strong>world</strong>!
##
## If you need to allow the rst ``include`` directive or tweak the generated
## output you have to create your own ``TRstGenerator`` with
## output you have to create your own ``RstGenerator`` with
## ``initRstGenerator`` and related procs.
proc myFindFile(filename: string): string =
@ -1241,7 +1253,7 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
result = ""
const filen = "input"
var d: TRstGenerator
var d: RstGenerator
initRstGenerator(d, outHtml, config, filen, options, myFindFile,
rst.defaultMsgHandler)
var dummyHasToc = false

View file

@ -29,34 +29,34 @@ const
# Valid opcodes ( "op" parameter ) to issue to epoll_ctl().
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.
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.
type
epoll_data* {.importc: "union epoll_data",
type
epoll_data* {.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 # \
#u32*: uint32
#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
epoll_event* {.importc: "struct epoll_event", header: "<sys/epoll.h>", pure, final.} = object
events*: uint32 # Epoll events
data*: epoll_data # User data variable
proc epoll_create*(size: cint): cint {.importc: "epoll_create",
proc epoll_create*(size: cint): cint {.importc: "epoll_create",
header: "<sys/epoll.h>".}
## Creates an epoll instance. Returns an fd for the new instance.
## The "size" parameter is a hint specifying the number of file
## descriptors to be associated with the new instance. The fd
## returned by epoll_create() should be closed with close().
## returned by epoll_create() should be closed with close().
proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1",
proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1",
header: "<sys/epoll.h>".}
## Same as epoll_create but with an FLAGS parameter. The unused SIZE
## parameter has been dropped.
## parameter has been dropped.
proc epoll_ctl*(epfd: cint; op: cint; fd: cint | SocketHandle; event: ptr epoll_event): cint {.
importc: "epoll_ctl", header: "<sys/epoll.h>".}
@ -65,10 +65,10 @@ proc epoll_ctl*(epfd: cint; op: cint; fd: cint | SocketHandle; event: ptr epoll_
## specific error code ) The "op" parameter is one of the EPOLL_CTL_*
## constants defined above. The "fd" parameter is the target of the
## operation. The "event" parameter describes which events the caller
## is interested in and any associated user data.
## is interested in and any associated user data.
proc epoll_wait*(epfd: cint; events: ptr epoll_event; maxevents: cint;
timeout: cint): cint {.importc: "epoll_wait",
proc epoll_wait*(epfd: cint; events: ptr epoll_event; maxevents: cint;
timeout: cint): cint {.importc: "epoll_wait",
header: "<sys/epoll.h>".}
## Wait for events on an epoll instance "epfd". Returns the number of
## triggered events returned in "events" buffer. Or -1 in case of
@ -82,11 +82,11 @@ 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 epoll_event; 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
# and atomically replaced with the one provided as parameter.
#
# This function is a cancellation point and therefore not marked with
# __THROW.
# __THROW.

View file

@ -9,64 +9,65 @@
{.deadCodeElim:on.}
# Get the platform-dependent flags.
# Structure describing an inotify event.
type
Tinotify_event*{.pure, final, importc: "struct inotify_event",
header: "<sys/inotify.h>".} = object
wd*{.importc: "wd".}: cint # Watch descriptor.
mask*{.importc: "mask".}: uint32 # Watch mask.
cookie*{.importc: "cookie".}: uint32 # Cookie to synchronize two events.
len*{.importc: "len".}: uint32 # Length (including NULs) of name.
name*{.importc: "name".}: char # Name.
# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
const
IN_ACCESS* = 0x00000001 # File was accessed.
IN_MODIFY* = 0x00000002 # File was modified.
IN_ATTRIB* = 0x00000004 # Metadata changed.
IN_CLOSE_WRITE* = 0x00000008 # Writtable file was closed.
IN_CLOSE_NOWRITE* = 0x00000010 # Unwrittable file closed.
IN_CLOSE* = (IN_CLOSE_WRITE or IN_CLOSE_NOWRITE) # Close.
IN_OPEN* = 0x00000020 # File was opened.
IN_MOVED_FROM* = 0x00000040 # File was moved from X.
IN_MOVED_TO* = 0x00000080 # File was moved to Y.
IN_MOVE* = (IN_MOVED_FROM or IN_MOVED_TO) # Moves.
IN_CREATE* = 0x00000100 # Subfile was created.
IN_DELETE* = 0x00000200 # Subfile was deleted.
IN_DELETE_SELF* = 0x00000400 # Self was deleted.
IN_MOVE_SELF* = 0x00000800 # Self was moved.
# Events sent by the kernel.
const
IN_UNMOUNT* = 0x00002000 # Backing fs was unmounted.
IN_Q_OVERFLOW* = 0x00004000 # Event queued overflowed.
IN_IGNORED* = 0x00008000 # File was ignored.
# Special flags.
const
# Get the platform-dependent flags.
# Structure describing an inotify event.
type
InotifyEvent*{.pure, final, importc: "struct inotify_event",
header: "<sys/inotify.h>".} = object
wd*{.importc: "wd".}: cint # Watch descriptor.
mask*{.importc: "mask".}: uint32 # Watch mask.
cookie*{.importc: "cookie".}: uint32 # Cookie to synchronize two events.
len*{.importc: "len".}: uint32 # Length (including NULs) of name.
name*{.importc: "name".}: char # Name.
{.deprecated: [Tinotify_event: InotifyEvent].}
# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
const
IN_ACCESS* = 0x00000001 # File was accessed.
IN_MODIFY* = 0x00000002 # File was modified.
IN_ATTRIB* = 0x00000004 # Metadata changed.
IN_CLOSE_WRITE* = 0x00000008 # Writtable file was closed.
IN_CLOSE_NOWRITE* = 0x00000010 # Unwrittable file closed.
IN_CLOSE* = (IN_CLOSE_WRITE or IN_CLOSE_NOWRITE) # Close.
IN_OPEN* = 0x00000020 # File was opened.
IN_MOVED_FROM* = 0x00000040 # File was moved from X.
IN_MOVED_TO* = 0x00000080 # File was moved to Y.
IN_MOVE* = (IN_MOVED_FROM or IN_MOVED_TO) # Moves.
IN_CREATE* = 0x00000100 # Subfile was created.
IN_DELETE* = 0x00000200 # Subfile was deleted.
IN_DELETE_SELF* = 0x00000400 # Self was deleted.
IN_MOVE_SELF* = 0x00000800 # Self was moved.
# Events sent by the kernel.
const
IN_UNMOUNT* = 0x00002000 # Backing fs was unmounted.
IN_Q_OVERFLOW* = 0x00004000 # Event queued overflowed.
IN_IGNORED* = 0x00008000 # File was ignored.
# Special flags.
const
IN_ONLYDIR* = 0x01000000 # Only watch the path if it is a
# directory.
IN_DONT_FOLLOW* = 0x02000000 # Do not follow a sym link.
# directory.
IN_DONT_FOLLOW* = 0x02000000 # Do not follow a sym link.
IN_EXCL_UNLINK* = 0x04000000 # Exclude events on unlinked
# objects.
# objects.
IN_MASK_ADD* = 0x20000000 # Add to the mask of an already
# existing watch.
IN_ISDIR* = 0x40000000 # Event occurred against dir.
IN_ONESHOT* = 0x80000000 # Only send event once.
# All events which a program can wait on.
const
# existing watch.
IN_ISDIR* = 0x40000000 # Event occurred against dir.
IN_ONESHOT* = 0x80000000 # Only send event once.
# All events which a program can wait on.
const
IN_ALL_EVENTS* = (IN_ACCESS or IN_MODIFY or IN_ATTRIB or IN_CLOSE_WRITE or
IN_CLOSE_NOWRITE or IN_OPEN or IN_MOVED_FROM or IN_MOVED_TO or
IN_CREATE or IN_DELETE or IN_DELETE_SELF or IN_MOVE_SELF)
# Create and initialize inotify instance.
proc inotify_init*(): cint{.cdecl, importc: "inotify_init",
proc inotify_init*(): cint{.cdecl, importc: "inotify_init",
header: "<sys/inotify.h>".}
# Create and initialize inotify instance.
proc inotify_init1*(flags: cint): cint{.cdecl, importc: "inotify_init1",
# Create and initialize inotify instance.
proc inotify_init1*(flags: cint): cint{.cdecl, importc: "inotify_init1",
header: "<sys/inotify.h>".}
# Add watch of object NAME to inotify instance FD. Notify about
# events specified by MASK.
# events specified by MASK.
proc inotify_add_watch*(fd: cint; name: cstring; mask: uint32): cint{.
cdecl, importc: "inotify_add_watch", header: "<sys/inotify.h>".}
# Remove the watch specified by WD from the inotify instance FD.
proc inotify_rm_watch*(fd: cint; wd: cint): cint{.cdecl,
importc: "inotify_rm_watch", header: "<sys/inotify.h>".}
# Remove the watch specified by WD from the inotify instance FD.
proc inotify_rm_watch*(fd: cint; wd: cint): cint{.cdecl,
importc: "inotify_rm_watch", header: "<sys/inotify.h>".}

71
lib/posix/kqueue.nim Normal file
View file

@ -0,0 +1,71 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Adam Strzelecki
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
{.deadCodeElim:on.}
from posix import Timespec
# Filters:
const
EVFILT_READ* = -1
EVFILT_WRITE* = -2
EVFILT_AIO* = -3
EVFILT_VNODE* = -4
EVFILT_PROC* = -5
EVFILT_SIGNAL* = -6
EVFILT_TIMER* = -7
EVFILT_MACHPORT* = -8
EVFILT_FS* = -9
EVFILT_USER* = -10
# -11 is unused
EVFILT_VM* = -12
# Actions:
const
EV_ADD* = 0x0001 ## Add event to queue (implies enable).
## Re-adding an existing element modifies it.
EV_DELETE* = 0x0002 ## Delete event from queue.
EV_ENABLE* = 0x0004 ## Enable event.
EV_DISABLE* = 0x0008 ## Disable event (not reported).
# Flags:
const
EV_ONESHOT* = 0x0010 ## Only report one occurrence.
EV_CLEAR* = 0x0020 ## Clear event state after reporting.
EV_RECEIPT* = 0x0040 ## Force EV_ERROR on success, data == 0
EV_DISPATCH* = 0x0080 ## Disable event after reporting.
# Return values:
const
EV_EOF* = 0x8000 ## EOF detected
EV_ERROR* = 0x4000 ## Error, data contains errno
type
KEvent* {.importc: "struct kevent",
header: "<sys/event.h>", pure, final.} = object
ident*: cuint ## identifier for this event (uintptr_t)
filter*: cshort ## filter for event
flags*: cushort ## general flags
fflags*: cuint ## filter-specific flags
data*: cuint ## filter-specific data (intptr_t)
#udata*: ptr void ## opaque user data identifier
proc kqueue*(): cint {.importc: "kqueue", header: "<sys/event.h>".}
## Creates new queue and returns its descriptor.
proc kevent*(kqFD: cint,
changelist: ptr KEvent, nchanges: cint,
eventlist: ptr KEvent, nevents: cint, timeout: ptr Timespec): cint
{.importc: "kevent", header: "<sys/event.h>".}
## Manipulates queue for given ``kqFD`` descriptor.
proc EV_SET*(event: ptr KEvent, ident: cuint, filter: cshort, flags: cushort,
fflags: cuint, data: cuint, udata: ptr void)
{.importc: "EV_SET", header: "<sys/event.h>".}
## Fills event with given data.

View file

@ -24,5 +24,5 @@ const
# fn should be of type proc (a2: pointer): void {.cdecl.}
proc clone*(fn: pointer; child_stack: pointer; flags: cint;
arg: pointer; ptid: ptr TPid; tls: pointer;
ctid: ptr TPid): cint {.importc, header: "<sched.h>".}
arg: pointer; ptid: ptr Pid; tls: pointer;
ctid: ptr Pid): cint {.importc, header: "<sched.h>".}

File diff suppressed because it is too large Load diff

View file

@ -12,18 +12,18 @@ import posix
type
Speed* = cuint
Tcflag* = cuint
Cflag* = cuint
{.deprecated: [Tcflag: Cflag].}
const
NCCS* = 32
type
Termios* {.importc: "struct termios", header: "<termios.h>".} = object
c_iflag*: Tcflag # input mode flags
c_oflag*: Tcflag # output mode flags
c_cflag*: Tcflag # control mode flags
c_lflag*: Tcflag # local mode flags
c_line*: cuchar # line discipline
c_iflag*: Cflag # input mode flags
c_oflag*: Cflag # output mode flags
c_cflag*: Cflag # control mode flags
c_lflag*: Cflag # local mode flags
c_cc*: array[NCCS, cuchar] # control characters
# cc characters
@ -258,4 +258,4 @@ proc tcFlow*(fd: cint; action: cint): cint {.importc: "tcflow",
header: "<termios.h>".}
# Get process group ID for session leader for controlling terminal FD.
proc tcGetSid*(fd: cint): TPid {.importc: "tcgetsid", header: "<termios.h>".}
proc tcGetSid*(fd: cint): Pid {.importc: "tcgetsid", header: "<termios.h>".}

View file

@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -24,6 +24,17 @@ proc `*`*(x: int, order: SortOrder): int {.inline.} =
var y = order.ord - 1
result = (x xor y) - y
proc fill*[T](a: var openArray[T], first, last: Natural, value: T) =
## fills the array ``a[first..last]`` with `value`.
var x = first
while x <= last:
a[x] = value
inc(x)
proc fill*[T](a: var openArray[T], value: T) =
## fills the array `a` with `value`.
fill(a, 0, a.high, value)
proc reverse*[T](a: var openArray[T], first, last: Natural) =
## reverses the array ``a[first..last]``.
var x = first
@ -37,14 +48,15 @@ proc reverse*[T](a: var openArray[T]) =
## reverses the array `a`.
reverse(a, 0, a.high)
proc reversed*[T](a: openArray[T], first, last: Natural): seq[T] =
proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] =
## returns the reverse of the array `a[first..last]`.
result = newSeq[T](last - first + 1)
assert last >= first-1
var i = last - first
var x = first.int
var y = last.int
while x <= last:
result[x] = a[y]
dec(y)
result = newSeq[T](i + 1)
while i >= 0:
result[i] = a[x]
dec(i)
inc(x)
proc reversed*[T](a: openArray[T]): seq[T] =
@ -86,18 +98,15 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.})
##
## var arr = @[1,2,3,5,6,7,8,9]
## arr.insert(4, arr.lowerBound(4))
## `after running the above arr is `[1,2,3,4,5,6,7,8,9]`
## # after running the above arr is `[1,2,3,4,5,6,7,8,9]`
result = a.low
var pos = result
var count, step: int
count = a.high - a.low + 1
var count = a.high - a.low + 1
var step, pos: int
while count != 0:
pos = result
step = count div 2
pos += step
pos = result + step
if cmp(a[pos], key) < 0:
pos.inc
result = pos
result = pos + 1
count -= step + 1
else:
count = step
@ -152,8 +161,9 @@ proc merge[T](a, b: var openArray[T], lo, m, hi: int,
proc sort*[T](a: var openArray[T],
cmp: proc (x, y: T): int {.closure.},
order = SortOrder.Ascending) =
## Default Nim sort. The sorting is guaranteed to be stable and
## the worst case is guaranteed to be O(n log n).
## Default Nim sort (an implementation of merge sort). The sorting
## is guaranteed to be stable and the worst case is guaranteed to
## be O(n log n).
## The current implementation uses an iterative
## mergesort to achieve this. It uses a temporary sequence of
## length ``a.len div 2``. Currently Nim does not support a
@ -210,8 +220,7 @@ template sortedByIt*(seq1, op: expr): expr =
## p2: Person = (name: "p2", age: 20)
## p3: Person = (name: "p3", age: 30)
## p4: Person = (name: "p4", age: 30)
##
## people = @[p1,p2,p4,p3]
## people = @[p1,p2,p4,p3]
##
## echo people.sortedByIt(it.name)
##
@ -230,10 +239,21 @@ template sortedByIt*(seq1, op: expr): expr =
result = cmp(a, b))
result
proc isSorted*[T](a: openarray[T],
cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): bool =
## Checks to see whether `a` is already sorted in `order`
## using `cmp` for the comparison. Parameters identical
## to `sort`
result = true
for i in 0..<len(a)-1:
if cmp(a[i],a[i+1]) * order > 0:
return false
proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
## produces the Cartesian product of the array. Warning: complexity
## may explode.
result = @[]
result = newSeq[seq[T]]()
if x.len == 0:
return
if x.len == 1:
@ -243,8 +263,7 @@ proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
indexes = newSeq[int](x.len)
initial = newSeq[int](x.len)
index = 0
# replace with newSeq as soon as #853 is fixed
var next: seq[T] = @[]
var next = newSeq[T]()
next.setLen(x.len)
for i in 0..(x.len-1):
if len(x[i]) == 0: return
@ -273,7 +292,7 @@ proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} =
##
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
## v.nextPermutation()
## echo v
## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
if x.len < 2:
return false
@ -302,7 +321,7 @@ proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
##
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
## v.prevPermutation()
## echo v
## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
if x.len < 2:
return false
@ -322,3 +341,31 @@ proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
swap x[i-1], x[j]
result = true
when isMainModule:
# Tests for lowerBound
var arr = @[1,2,3,5,6,7,8,9]
assert arr.lowerBound(0) == 0
assert arr.lowerBound(4) == 3
assert arr.lowerBound(5) == 3
assert arr.lowerBound(10) == 8
arr = @[1,5,10]
assert arr.lowerBound(4) == 1
assert arr.lowerBound(5) == 1
assert arr.lowerBound(6) == 2
# Tests for isSorted
var srt1 = [1,2,3,4,4,4,4,5]
var srt2 = ["iello","hello"]
var srt3 = [1.0,1.0,1.0]
var srt4: seq[int] = @[]
assert srt1.isSorted(cmp) == true
assert srt2.isSorted(cmp) == false
assert srt3.isSorted(cmp) == true
var srtseq = newSeq[int]()
assert srtseq.isSorted(cmp) == true
# Tests for reversed
var arr1 = @[0,1,2,3,4]
assert arr1.reversed() == @[4,3,2,1,0]
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]

View file

@ -11,7 +11,7 @@ include "system/inclrtl"
import os, oids, tables, strutils, macros, times
import rawsockets, net
import nativesockets, net
export Port, SocketFlag
@ -122,10 +122,11 @@ export Port, SocketFlag
## Limitations/Bugs
## ----------------
##
## * ``except`` statement (without `try`) does not work inside async procedures.
## * The effect system (``raises: []``) does not work with async procedures.
## * Can't await in a ``except`` body
## * Forward declarations for async procs are broken,
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
## * FutureVar[T] needs to be completed manually.
# TODO: Check if yielded future is nil and throw a more meaningful exception
@ -145,10 +146,15 @@ type
Future*[T] = ref object of FutureBase ## Typed future.
value: T ## Stored value
FutureVar*[T] = distinct Future[T]
FutureError* = object of Exception
cause*: FutureBase
{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
var currentID = 0
when not defined(release):
var currentID = 0
proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
## Creates a new future.
##
@ -162,18 +168,39 @@ proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
result.fromProc = fromProc
currentID.inc()
proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
## Create a new ``FutureVar``. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
result = FutureVar[T](newFuture[T](fromProc))
proc clean*[T](future: FutureVar[T]) =
## Resets the ``finished`` status of ``future``.
Future[T](future).finished = false
Future[T](future).error = nil
proc checkFinished[T](future: Future[T]) =
## Checks whether `future` is finished. If it is then raises a
## ``FutureError``.
when not defined(release):
if future.finished:
echo("<-----> ", future.id, " ", future.fromProc)
echo(future.stackTrace)
echo("-----")
var msg = ""
msg.add("An attempt was made to complete a Future more than once. ")
msg.add("Details:")
msg.add("\n Future ID: " & $future.id)
msg.add("\n Created in proc: " & future.fromProc)
msg.add("\n Stack trace to moment of creation:")
msg.add("\n" & indent(future.stackTrace.strip(), 4))
when T is string:
echo("Contents: ", future.value.repr)
echo("<----->")
echo("Future already finished, cannot finish twice.")
echo getStackTrace()
assert false
msg.add("\n Contents (string): ")
msg.add("\n" & indent(future.value.repr, 4))
msg.add("\n Stack trace to moment of secondary completion:")
msg.add("\n" & indent(getStackTrace().strip(), 4))
var err = newException(FutureError, msg)
err.cause = future
raise err
proc complete*[T](future: Future[T], val: T) =
## Completes ``future`` with value ``val``.
@ -194,6 +221,15 @@ proc complete*(future: Future[void]) =
if future.cb != nil:
future.cb()
proc complete*[T](future: FutureVar[T]) =
## Completes a ``FutureVar``.
template fut: expr = Future[T](future)
checkFinished(fut)
assert(fut.error == nil)
fut.finished = true
if fut.cb != nil:
fut.cb()
proc fail*[T](future: Future[T], error: ref Exception) =
## Completes ``future`` with ``error``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
@ -230,15 +266,17 @@ proc `callback=`*[T](future: Future[T],
## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future)
proc echoOriginalStackTrace[T](future: Future[T]) =
proc injectStacktrace[T](future: Future[T]) =
# TODO: Come up with something better.
when not defined(release):
echo("Original stack trace in ", future.fromProc, ":")
var msg = ""
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
echo(future.errorStackTrace)
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
else:
echo("Empty or nil stack trace.")
echo("Continuing...")
msg.add("\n Empty or nil stack trace.")
future.error.msg.add(msg)
proc read*[T](future: Future[T]): T =
## Retrieves the value of ``future``. Future must be finished otherwise
@ -247,7 +285,7 @@ proc read*[T](future: Future[T]): T =
## If the result of the future is an error then that error will be raised.
if future.finished:
if future.error != nil:
echoOriginalStackTrace(future)
injectStacktrace(future)
raise future.error
when T isnot void:
return future.value
@ -264,6 +302,13 @@ proc readError*[T](future: Future[T]): ref Exception =
else:
raise newException(ValueError, "No error in future.")
proc mget*[T](future: FutureVar[T]): var T =
## Returns a mutable value stored in ``future``.
##
## Unlike ``read``, this function will not raise an exception if the
## Future has not been finished.
result = Future[T](future).value
proc finished*[T](future: Future[T]): bool =
## Determines whether ``future`` has completed.
##
@ -282,7 +327,7 @@ proc asyncCheck*[T](future: Future[T]) =
future.callback =
proc () =
if future.failed:
echoOriginalStackTrace(future)
injectStacktrace(future)
raise future.error
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
@ -323,32 +368,34 @@ proc processTimers(p: PDispatcherBase) =
when defined(windows) or defined(nimdoc):
import winlean, sets, hashes
type
TCompletionKey = Dword
CompletionKey = Dword
TCompletionData* = object
fd*: TAsyncFD # TODO: Rename this.
cb*: proc (fd: TAsyncFD, bytesTransferred: Dword,
CompletionData* = object
fd*: AsyncFD # TODO: Rename this.
cb*: proc (fd: AsyncFD, bytesTransferred: Dword,
errcode: OSErrorCode) {.closure,gcsafe.}
PDispatcher* = ref object of PDispatcherBase
ioPort: THandle
handles: HashSet[TAsyncFD]
ioPort: Handle
handles: HashSet[AsyncFD]
TCustomOverlapped = object of TOVERLAPPED
data*: TCompletionData
CustomOverlapped = object of OVERLAPPED
data*: CompletionData
PCustomOverlapped* = ref TCustomOverlapped
PCustomOverlapped* = ref CustomOverlapped
TAsyncFD* = distinct int
AsyncFD* = distinct int
{.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD,
TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].}
proc hash(x: TAsyncFD): THash {.borrow.}
proc `==`*(x: TAsyncFD, y: TAsyncFD): bool {.borrow.}
proc hash(x: AsyncFD): Hash {.borrow.}
proc `==`*(x: AsyncFD, y: AsyncFD): bool {.borrow.}
proc newDispatcher*(): PDispatcher =
## Creates a new Dispatcher instance.
new result
result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
result.handles = initSet[TAsyncFD]()
result.handles = initSet[AsyncFD]()
result.timers = @[]
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
@ -357,15 +404,15 @@ when defined(windows) or defined(nimdoc):
if gDisp.isNil: gDisp = newDispatcher()
result = gDisp
proc register*(fd: TAsyncFD) =
proc register*(fd: AsyncFD) =
## Registers ``fd`` with the dispatcher.
let p = getGlobalDispatcher()
if createIoCompletionPort(fd.THandle, p.ioPort,
cast[TCompletionKey](fd), 1) == 0:
if createIoCompletionPort(fd.Handle, p.ioPort,
cast[CompletionKey](fd), 1) == 0:
raiseOSError(osLastError())
p.handles.incl(fd)
proc verifyPresence(fd: TAsyncFD) =
proc verifyPresence(fd: AsyncFD) =
## Ensures that file descriptor has been registered with the dispatcher.
let p = getGlobalDispatcher()
if fd notin p.handles:
@ -394,7 +441,7 @@ when defined(windows) or defined(nimdoc):
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
if res:
# This is useful for ensuring the reliability of the overlapped struct.
assert customOverlapped.data.fd == lpCompletionKey.TAsyncFD
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, OSErrorCode(-1))
@ -402,7 +449,7 @@ when defined(windows) or defined(nimdoc):
else:
let errCode = osLastError()
if customOverlapped != nil:
assert customOverlapped.data.fd == lpCompletionKey.TAsyncFD
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, errCode)
GC_unref(customOverlapped)
@ -419,16 +466,16 @@ when defined(windows) or defined(nimdoc):
var acceptExPtr: pointer = nil
var getAcceptExSockAddrsPtr: pointer = nil
proc initPointer(s: SocketHandle, fun: var pointer, guid: var TGUID): bool =
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
var bytesRet: Dword
fun = nil
result = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, addr guid,
sizeof(TGUID).Dword, addr fun, sizeof(pointer).Dword,
sizeof(GUID).Dword, addr fun, sizeof(pointer).Dword,
addr bytesRet, nil, nil) == 0
proc initAll() =
let dummySock = newRawSocket()
let dummySock = newNativeSocket()
if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX):
raiseOSError(osLastError())
if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX):
@ -480,8 +527,8 @@ when defined(windows) or defined(nimdoc):
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
RemoteSockaddr, RemoteSockaddrLength)
proc connect*(socket: TAsyncFD, address: string, port: Port,
af = AF_INET): Future[void] =
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = nativesockets.AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
@ -490,14 +537,14 @@ when defined(windows) or defined(nimdoc):
var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(af))
saddr.sin_family = int16(toInt(domain))
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, af)
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
@ -506,8 +553,8 @@ when defined(windows) or defined(nimdoc):
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
@ -542,7 +589,7 @@ when defined(windows) or defined(nimdoc):
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: TAsyncFD, size: int,
proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will
## complete once all the data requested is read, a part of the data has been
@ -564,14 +611,14 @@ when defined(windows) or defined(nimdoc):
var retFuture = newFuture[string]("recv")
var dataBuf: TWSABuf
dataBuf.buf = cast[cstring](alloc0(size))
dataBuf.len = size
dataBuf.len = size.ULONG
var bytesReceived: Dword
var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0':
@ -634,7 +681,7 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the
@ -659,14 +706,14 @@ when defined(windows) or defined(nimdoc):
#buf[] = '\0'
var dataBuf: TWSABuf
dataBuf.buf = buf
dataBuf.len = size
dataBuf.len = size.ULONG
var bytesReceived: Dword
var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0':
@ -721,7 +768,7 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc send*(socket: TAsyncFD, data: string,
proc send*(socket: AsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` to ``socket``. The returned future will complete once all
## data has been sent.
@ -730,13 +777,13 @@ when defined(windows) or defined(nimdoc):
var dataBuf: TWSABuf
dataBuf.buf = data # since this is not used in a callback, this is fine
dataBuf.len = data.len
dataBuf.len = data.len.ULONG
var bytesReceived, lowFlags: Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
@ -764,8 +811,8 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc acceptAddr*(socket: TAsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: TAsyncFD]] =
proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncFD]] =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection and the remote address of the client.
## The future will complete when the connection is successfully accepted.
@ -778,9 +825,9 @@ when defined(windows) or defined(nimdoc):
## flag is specified then this error will not be raised and instead
## accept will be called again.
verifyPresence(socket)
var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]("acceptAddr")
var retFuture = newFuture[tuple[address: string, client: AsyncFD]]("acceptAddr")
var clientSock = newRawSocket()
var clientSock = newNativeSocket()
if clientSock == osInvalidSocket: raiseOSError(osLastError())
const lpOutputLen = 1024
@ -803,11 +850,11 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen)
register(clientSock.TAsyncFD)
register(clientSock.AsyncFD)
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
retFuture.complete(
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr),
client: clientSock.TAsyncFD)
client: clientSock.AsyncFD)
)
template failAccept(errcode): stmt =
@ -824,8 +871,8 @@ when defined(windows) or defined(nimdoc):
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
completeAccept()
@ -853,26 +900,26 @@ when defined(windows) or defined(nimdoc):
return retFuture
proc newAsyncRawSocket*(domain, typ, protocol: cint): TAsyncFD =
proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).TAsyncFD
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): TAsyncFD =
proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).TAsyncFD
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc closeSocket*(socket: TAsyncFD) =
proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close()
getGlobalDispatcher().handles.excl(socket)
proc unregister*(fd: TAsyncFD) =
proc unregister*(fd: AsyncFD) =
## Unregisters ``fd``.
getGlobalDispatcher().handles.excl(fd)
@ -892,18 +939,19 @@ else:
MSG_NOSIGNAL
type
TAsyncFD* = distinct cint
TCallback = proc (fd: TAsyncFD): bool {.closure,gcsafe.}
AsyncFD* = distinct cint
Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
PData* = ref object of RootRef
fd: TAsyncFD
readCBs: seq[TCallback]
writeCBs: seq[TCallback]
fd: AsyncFD
readCBs: seq[Callback]
writeCBs: seq[Callback]
PDispatcher* = ref object of PDispatcherBase
selector: Selector
{.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].}
proc `==`*(x, y: TAsyncFD): bool {.borrow.}
proc `==`*(x, y: AsyncFD): bool {.borrow.}
proc newDispatcher*(): PDispatcher =
new result
@ -915,48 +963,49 @@ else:
if gDisp.isNil: gDisp = newDispatcher()
result = gDisp
proc update(fd: TAsyncFD, events: set[Event]) =
proc update(fd: AsyncFD, events: set[Event]) =
let p = getGlobalDispatcher()
assert fd.SocketHandle in p.selector
discard p.selector.update(fd.SocketHandle, events)
p.selector.update(fd.SocketHandle, events)
proc register*(fd: TAsyncFD) =
proc register*(fd: AsyncFD) =
let p = getGlobalDispatcher()
var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef)
proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): TAsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
protocol: cint): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): TAsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD
proc newAsyncNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc closeSocket*(sock: TAsyncFD) =
proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher()
sock.SocketHandle.close()
disp.selector.unregister(sock.SocketHandle)
sock.SocketHandle.close()
proc unregister*(fd: TAsyncFD) =
proc unregister*(fd: AsyncFD) =
getGlobalDispatcher().selector.unregister(fd.SocketHandle)
proc addRead*(fd: TAsyncFD, cb: TCallback) =
proc addRead*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher()
if fd.SocketHandle notin p.selector:
raise newException(ValueError, "File descriptor not registered.")
p.selector[fd.SocketHandle].data.PData.readCBs.add(cb)
update(fd, p.selector[fd.SocketHandle].events + {EvRead})
proc addWrite*(fd: TAsyncFD, cb: TCallback) =
proc addWrite*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher()
if fd.SocketHandle notin p.selector:
raise newException(ValueError, "File descriptor not registered.")
@ -967,11 +1016,11 @@ else:
let p = getGlobalDispatcher()
for info in p.selector.select(timeout):
let data = PData(info.key.data)
assert data.fd == info.key.fd.TAsyncFD
assert data.fd == info.key.fd.AsyncFD
#echo("In poll ", data.fd.cint)
if EvError in info.events:
closeSocket(data.fd)
continue
# 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:
# Callback may add items to ``data.readCBs`` which causes issues if
@ -1005,16 +1054,25 @@ else:
processTimers(p)
proc connect*(socket: TAsyncFD, address: string, port: Port,
af = AF_INET): Future[void] =
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect")
proc cb(fd: TAsyncFD): bool =
# We have connected.
retFuture.complete()
return true
proc cb(fd: AsyncFD): bool =
var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR))
if ret == 0:
# We have connected.
retFuture.complete()
return true
elif ret == EINTR:
# interrupted, keep waiting
return false
else:
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
return true
var aiList = getAddrInfo(address, port, af)
assert getSockDomain(socket.SocketHandle) == domain
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
@ -1040,13 +1098,13 @@ else:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: TAsyncFD, size: int,
proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv")
var readBuffer = newString(size)
proc cb(sock: TAsyncFD): bool =
proc cb(sock: AsyncFD): bool =
result = true
let res = recv(sock.SocketHandle, addr readBuffer[0], size.cint,
flags.toOSFlags())
@ -1070,11 +1128,11 @@ else:
addRead(socket, cb)
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto")
proc cb(sock: TAsyncFD): bool =
proc cb(sock: AsyncFD): bool =
result = true
let res = recv(sock.SocketHandle, buf, size.cint,
flags.toOSFlags())
@ -1094,13 +1152,13 @@ else:
addRead(socket, cb)
return retFuture
proc send*(socket: TAsyncFD, data: string,
proc send*(socket: AsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send")
var written = 0
proc cb(sock: TAsyncFD): bool =
proc cb(sock: AsyncFD): bool =
result = true
let netSize = data.len-written
var d = data.cstring
@ -1126,13 +1184,13 @@ else:
addWrite(socket, cb)
return retFuture
proc acceptAddr*(socket: TAsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: TAsyncFD]] =
proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncFD]] =
var retFuture = newFuture[tuple[address: string,
client: TAsyncFD]]("acceptAddr")
proc cb(sock: TAsyncFD): bool =
client: AsyncFD]]("acceptAddr")
proc cb(sock: AsyncFD): bool =
result = true
var sockAddress: SockAddr_in
var sockAddress: Sockaddr_storage
var addrLen = sizeof(sockAddress).Socklen
var client = accept(sock.SocketHandle,
cast[ptr SockAddr](addr(sockAddress)), addr(addrLen))
@ -1147,28 +1205,28 @@ else:
else:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
register(client.TAsyncFD)
retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client.TAsyncFD))
register(client.AsyncFD)
retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), client.AsyncFD))
addRead(socket, cb)
return retFuture
proc sleepAsync*(ms: int): Future[void] =
## Suspends the execution of the current async procedure for the next
## ``ms`` miliseconds.
## ``ms`` milliseconds.
var retFuture = newFuture[void]("sleepAsync")
let p = getGlobalDispatcher()
p.timers.add((epochTime() + (ms / 1000), retFuture))
return retFuture
proc accept*(socket: TAsyncFD,
flags = {SocketFlag.SafeDisconn}): Future[TAsyncFD] =
proc accept*(socket: AsyncFD,
flags = {SocketFlag.SafeDisconn}): Future[AsyncFD] =
## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection.
## The future will complete when the connection is successfully accepted.
var retFut = newFuture[TAsyncFD]("accept")
var retFut = newFuture[AsyncFD]("accept")
var fut = acceptAddr(socket, flags)
fut.callback =
proc (future: Future[tuple[address: string, client: TAsyncFD]]) =
proc (future: Future[tuple[address: string, client: AsyncFD]]) =
assert future.finished
if future.failed:
retFut.fail(future.error)
@ -1328,7 +1386,7 @@ proc processBody(node, retFutureSym: NimNode,
else: discard
of nnkDiscardStmt:
# discard await x
if node[0].kind != nnkEmpty and node[0][0].kind == nnkIdent and
if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
node[0][0].ident == !"await":
var newDiscard = node
result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
@ -1400,26 +1458,35 @@ proc getName(node: NimNode): string {.compileTime.} =
else:
error("Unknown name.")
macro async*(prc: stmt): stmt {.immediate.} =
## Macro which processes async procedures into the appropriate
## iterators and yield statements.
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
## This macro transforms a single procedure into a closure iterator.
## The ``async`` macro supports a stmtList holding multiple async procedures.
if prc.kind notin {nnkProcDef, nnkLambda}:
error("Cannot transform this node kind into an async proc." &
" Proc definition or lambda node expected.")
error("Cannot transform this node kind into an async proc." &
" Proc definition or lambda node expected.")
hint("Processing " & prc[0].getName & " as an async proc.")
let returnType = prc[3][0]
var baseType: NimNode
# Verify that the return type is a Future[T]
if returnType.kind == nnkIdent:
error("Expected return type of 'Future' got '" & $returnType & "'")
elif returnType.kind == nnkBracketExpr:
if $returnType[0] != "Future":
error("Expected return type of 'Future' got '" & $returnType[0] & "'")
if returnType.kind == nnkBracketExpr:
let fut = repr(returnType[0])
if fut != "Future":
error("Expected return type of 'Future' got '" & fut & "'")
baseType = returnType[1]
elif returnType.kind in nnkCallKinds and $returnType[0] == "[]":
let fut = repr(returnType[1])
if fut != "Future":
error("Expected return type of 'Future' got '" & fut & "'")
baseType = returnType[2]
elif returnType.kind == nnkEmpty:
baseType = returnType
else:
error("Expected return type of 'Future' got '" & repr(returnType) & "'")
let subtypeIsVoid = returnType.kind == nnkEmpty or
(returnType.kind == nnkBracketExpr and
returnType[1].kind == nnkIdent and returnType[1].ident == !"void")
(baseType.kind == nnkIdent and returnType[1].ident == !"void")
var outerProcBody = newNimNode(nnkStmtList, prc[6])
@ -1427,7 +1494,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
var retFutureSym = genSym(nskVar, "retFuture")
var subRetType =
if returnType.kind == nnkEmpty: newIdentNode("void")
else: returnType[1]
else: baseType
outerProcBody.add(
newVarStmt(retFutureSym,
newCall(
@ -1451,7 +1518,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T
newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
procBody.insert(2, newNimNode(nnkPragma).add(
newIdentNode("pop"))) # -> {.pop.})
@ -1492,10 +1559,20 @@ macro async*(prc: stmt): stmt {.immediate.} =
result[6] = outerProcBody
#echo(treeRepr(result))
#if prc[0].getName == "test":
#if prc[0].getName == "hubConnectionLoop":
# echo(toStrLit(result))
proc recvLine*(socket: TAsyncFD): Future[string] {.async.} =
macro async*(prc: stmt): stmt {.immediate.} =
## Macro which processes async procedures into the appropriate
## iterators and yield statements.
if prc.kind == nnkStmtList:
for oneProc in prc:
result = newStmtList()
result.add asyncSingleProc(oneProc)
else:
result = asyncSingleProc(prc)
proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
##

View file

@ -31,7 +31,7 @@ else:
type
AsyncFile* = ref object
fd: TAsyncFd
fd: AsyncFd
offset: int64
when defined(windows) or defined(nimdoc):
@ -72,7 +72,7 @@ proc getFileSize(f: AsyncFile): int64 =
## Retrieves the specified file's size.
when defined(windows) or defined(nimdoc):
var high: DWord
let low = getFileSize(f.fd.THandle, addr high)
let low = getFileSize(f.fd.Handle, addr high)
if low == INVALID_FILE_SIZE:
raiseOSError(osLastError())
return (high shl 32) or low
@ -88,13 +88,13 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
when useWinUnicode:
result.fd = createFileW(newWideCString(filename), desiredAccess,
FILE_SHARE_READ,
nil, creationDisposition, flags, 0).TAsyncFd
nil, creationDisposition, flags, 0).AsyncFd
else:
result.fd = createFileA(filename, desiredAccess,
FILE_SHARE_READ,
nil, creationDisposition, flags, 0).TAsyncFd
nil, creationDisposition, flags, 0).AsyncFd
if result.fd.THandle == INVALID_HANDLE_VALUE:
if result.fd.Handle == INVALID_HANDLE_VALUE:
raiseOSError(osLastError())
register(result.fd)
@ -106,7 +106,7 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
let flags = getPosixFlags(mode)
# RW (Owner), RW (Group), R (Other)
let perm = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH
result.fd = open(filename, flags, perm).TAsyncFD
result.fd = open(filename, flags, perm).AsyncFD
if result.fd.cint == -1:
raiseOSError(osLastError())
@ -125,8 +125,8 @@ proc read*(f: AsyncFile, size: int): Future[string] =
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: f.fd, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
ol.data = CompletionData(fd: f.fd, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
assert bytesCount > 0
@ -148,7 +148,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
ol.offsetHigh = DWord(f.offset shr 32)
# According to MSDN we're supposed to pass nil to lpNumberOfBytesRead.
let ret = readFile(f.fd.THandle, buffer, size.int32, nil,
let ret = readFile(f.fd.Handle, buffer, size.int32, nil,
cast[POVERLAPPED](ol))
if not ret.bool:
let err = osLastError()
@ -161,7 +161,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
else:
# Request completed immediately.
var bytesRead: DWord
let overlappedRes = getOverlappedResult(f.fd.THandle,
let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesRead, false.WinBool)
if not overlappedRes.bool:
let err = osLastError()
@ -179,7 +179,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
else:
var readBuffer = newString(size)
proc cb(fd: TAsyncFD): bool =
proc cb(fd: AsyncFD): bool =
result = true
let res = read(fd.cint, addr readBuffer[0], size.cint)
if res < 0:
@ -195,10 +195,10 @@ proc read*(f: AsyncFile, size: int): Future[string] =
readBuffer.setLen(res)
f.offset.inc(res)
retFuture.complete(readBuffer)
if not cb(f.fd):
addRead(f.fd, cb)
return retFuture
proc readLine*(f: AsyncFile): Future[string] {.async.} =
@ -222,7 +222,7 @@ proc getFilePos*(f: AsyncFile): int64 =
proc setFilePos*(f: AsyncFile, pos: int64) =
## Sets the position of the file pointer that is used for read/write
## operations. The file's first byte has the index zero.
## operations. The file's first byte has the index zero.
f.offset = pos
when not defined(windows) and not defined(nimdoc):
let ret = lseek(f.fd.cint, pos, SEEK_SET)
@ -251,8 +251,8 @@ proc write*(f: AsyncFile, data: string): Future[void] =
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: f.fd, cb:
proc (fd: TAsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
ol.data = CompletionData(fd: f.fd, cb:
proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
assert bytesCount == data.len.int32
@ -268,7 +268,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
ol.offsetHigh = DWord(f.offset shr 32)
# According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten.
let ret = writeFile(f.fd.THandle, buffer, data.len.int32, nil,
let ret = writeFile(f.fd.Handle, buffer, data.len.int32, nil,
cast[POVERLAPPED](ol))
if not ret.bool:
let err = osLastError()
@ -281,7 +281,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
else:
# Request completed immediately.
var bytesWritten: DWord
let overlappedRes = getOverlappedResult(f.fd.THandle,
let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesWritten, false.WinBool)
if not overlappedRes.bool:
retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
@ -291,8 +291,8 @@ proc write*(f: AsyncFile, data: string): Future[void] =
retFuture.complete()
else:
var written = 0
proc cb(fd: TAsyncFD): bool =
proc cb(fd: AsyncFD): bool =
result = true
let remainderSize = data.len-written
let res = write(fd.cint, addr copy[written], remainderSize.cint)
@ -309,7 +309,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
result = false # We still have data to write.
else:
retFuture.complete()
if not cb(f.fd):
addWrite(f.fd, cb)
return retFuture
@ -317,7 +317,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
proc close*(f: AsyncFile) =
## Closes the file specified.
when defined(windows) or defined(nimdoc):
if not closeHandle(f.fd.THandle).bool:
if not closeHandle(f.fd.Handle).bool:
raiseOSError(osLastError())
else:
if close(f.fd.cint) == -1:

View file

@ -6,23 +6,74 @@
# distribution, for details about the copyright.
#
## This module implement an asynchronous FTP client.
## This module implements an asynchronous FTP client. It allows you to connect
## to an FTP server and perform operations on it such as for example:
##
## Examples
## --------
## * The upload of new files.
## * The removal of existing files.
## * Download of files.
## * Changing of files' permissions.
## * Navigation through the FTP server's directories.
##
## .. code-block::nim
## Connecting to an FTP server
## ------------------------
##
## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
## proc main(ftp: AsyncFtpClient) {.async.} =
## In order to begin any sort of transfer of files you must first
## connect to an FTP server. You can do so with the ``connect`` procedure.
##
## .. code-block::nim
## import asyncdispatch, asyncftpclient
## proc main() {.async.} =
## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
## await ftp.connect()
## echo await ftp.pwd()
## echo await ftp.listDirs()
## await ftp.store("payload.jpg", "payload.jpg")
## await ftp.retrFile("payload.jpg", "payload2.jpg")
## echo("Finished")
## echo("Connected")
## waitFor(main())
##
## waitFor main(ftp)
## A new ``main`` async procedure must be declared to allow the use of the
## ``await`` keyword. The connection will complete asynchronously and the
## client will be connected after the ``await ftp.connect()`` call.
##
## Uploading a new file
## --------------------
##
## After a connection is made you can use the ``store`` procedure to upload
## a new file to the FTP server. Make sure to check you are in the correct
## working directory before you do so with the ``pwd`` procedure, you can also
## instead specify an absolute path.
##
## .. code-block::nim
## import asyncdispatch, asyncftpclient
## proc main() {.async.} =
## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
## await ftp.connect()
## let currentDir = await ftp.pwd()
## assert currentDir == "/home/user/"
## await ftp.store("file.txt", "file.txt")
## echo("File finished uploading")
## waitFor(main())
##
## Checking the progress of a file transfer
## ----------------------------------------
##
## The progress of either a file upload or a file download can be checked
## by specifying a ``onProgressChanged`` procedure to the ``store`` or
## ``retrFile`` procedures.
##
## .. code-block::nim
## import asyncdispatch, asyncftpclient
##
## proc onProgressChanged(total, progress: BiggestInt,
## speed: float): Future[void] =
## echo("Uploaded ", progress, " of ", total, " bytes")
## echo("Current speed: ", speed, " kb/s")
##
## proc main() {.async.} =
## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
## await ftp.connect()
## await ftp.store("file.txt", "/home/user/file.txt", onProgressChanged)
## echo("File finished uploading")
## waitFor(main())
import asyncdispatch, asyncnet, strutils, parseutils, os, times
@ -37,12 +88,24 @@ type
proc (total, progress: BiggestInt, speed: float):
Future[void] {.closure, gcsafe.}
proc expectReply(ftp: AsyncFtpClient): Future[TaintedString] =
result = ftp.csock.recvLine()
const multiLineLimit = 10000
proc expectReply(ftp: AsyncFtpClient): Future[TaintedString] {.async.} =
result = await ftp.csock.recvLine()
var count = 0
while result[3] == '-':
## Multi-line reply.
let line = await ftp.csock.recvLine()
result.add("\n" & line)
count.inc()
if count >= multiLineLimit:
raise newException(ReplyError, "Reached maximum multi-line reply count.")
proc send*(ftp: AsyncFtpClient, m: string): Future[TaintedString] {.async.} =
## Send a message to the server, and wait for a primary reply.
## ``\c\L`` is added for you.
##
## **Note:** The server may return multiple lines of coded replies.
await ftp.csock.send(m & "\c\L")
return await ftp.expectReply()
@ -79,6 +142,8 @@ proc connect*(ftp: AsyncFtpClient) {.async.} =
# 120 Service ready in nnn minutes.
# We wait until we receive 220.
reply = await ftp.expectReply()
# Handle 220 messages from the server
assertReply(reply, "220")
if ftp.user != "":

View file

@ -67,6 +67,12 @@ type
Http409 = "409 Conflict",
Http410 = "410 Gone",
Http411 = "411 Length Required",
Http412 = "412 Precondition Failed",
Http413 = "413 Request Entity Too Large",
Http414 = "414 Request-URI Too Long",
Http415 = "415 Unsupported Media Type",
Http416 = "416 Requested Range Not Satisfiable",
Http417 = "417 Expectation Failed",
Http418 = "418 I'm a teapot",
Http500 = "500 Internal Server Error",
Http501 = "501 Not Implemented",
@ -126,8 +132,11 @@ proc parseHeader(line: string): tuple[key, value: string] =
var i = 0
i = line.parseUntil(result.key, ':')
inc(i) # skip :
i += line.skipWhiteSpace(i)
i += line.parseUntil(result.value, {'\c', '\L'}, i)
if i < len(line):
i += line.skipWhiteSpace(i)
i += line.parseUntil(result.value, {'\c', '\L'}, i)
else:
result.value = ""
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
var i = protocol.skipIgnoreCase("HTTP/")
@ -148,7 +157,8 @@ proc processClient(client: AsyncSocket, address: string,
var request: Request
request.url = initUri()
request.headers = newStringTable(modeCaseInsensitive)
var line = newStringOfCap(80)
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
lineFut.mget() = newStringOfCap(80)
var key, value = ""
while not client.isClosed:
@ -156,19 +166,21 @@ proc processClient(client: AsyncSocket, address: string,
# Header: val
# \n
request.headers.clear(modeCaseInsensitive)
request.body = ""
request.hostname.shallowCopy(address)
assert client != nil
request.client = client
# First line - GET /path HTTP/1.1
line.setLen(0)
await client.recvLineInto(addr line) # TODO: Timeouts.
if line == "":
lineFut.mget().setLen(0)
lineFut.clean()
await client.recvLineInto(lineFut) # TODO: Timeouts.
if lineFut.mget == "":
client.close()
return
var i = 0
for linePart in line.split(' '):
for linePart in lineFut.mget.split(' '):
case i
of 0: request.reqMethod.shallowCopy(linePart.normalize)
of 1: parseUri(linePart, request.url)
@ -180,26 +192,27 @@ proc processClient(client: AsyncSocket, address: string,
"Invalid request protocol. Got: " & linePart)
continue
else:
await request.respond(Http400, "Invalid request. Got: " & line)
await request.respond(Http400, "Invalid request. Got: " & lineFut.mget)
continue
inc i
# Headers
while true:
i = 0
line.setLen(0)
await client.recvLineInto(addr line)
lineFut.mget.setLen(0)
lineFut.clean()
await client.recvLineInto(lineFut)
if line == "":
if lineFut.mget == "":
client.close(); return
if line == "\c\L": break
let (key, value) = parseHeader(line)
if lineFut.mget == "\c\L": break
let (key, value) = parseHeader(lineFut.mget)
request.headers[key] = value
if request.reqMethod == "post":
# Check for Expect header
if request.headers.hasKey("Expect"):
if request.headers["Expect"].toLower == "100-continue":
if request.headers.getOrDefault("Expect").toLower == "100-continue":
await client.sendStatus("100 Continue")
else:
await client.sendStatus("417 Expectation Failed")
@ -208,8 +221,10 @@ proc processClient(client: AsyncSocket, address: string,
# - Check for Content-length header
if request.headers.hasKey("Content-Length"):
var contentLength = 0
if parseInt(request.headers["Content-Length"], contentLength) == 0:
if parseInt(request.headers.getOrDefault("Content-Length"),
contentLength) == 0:
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
continue
else:
request.body = await client.recv(contentLength)
assert request.body.len == contentLength
@ -218,16 +233,18 @@ proc processClient(client: AsyncSocket, address: string,
continue
case request.reqMethod
of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch":
of "get", "post", "head", "put", "delete", "trace", "options",
"connect", "patch":
await callback(request)
else:
await request.respond(Http400, "Invalid request method. Got: " & request.reqMethod)
await request.respond(Http400, "Invalid request method. Got: " &
request.reqMethod)
# Persistent connections
if (request.protocol == HttpVer11 and
request.headers["connection"].normalize != "close") or
request.headers.getOrDefault("connection").normalize != "close") or
(request.protocol == HttpVer10 and
request.headers["connection"].normalize == "keep-alive"):
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.

View file

@ -56,7 +56,7 @@
##
import asyncdispatch
import rawsockets
import nativesockets
import net
import os
@ -85,35 +85,45 @@ type
bioIn: BIO
bioOut: BIO
of false: nil
domain: Domain
sockType: SockType
protocol: Protocol
AsyncSocket* = ref AsyncSocketDesc
{.deprecated: [PAsyncSocket: AsyncSocket].}
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect.
proc newAsyncSocket*(fd: TAsyncFD, isBuff: bool): AsyncSocket =
proc newAsyncSocket*(fd: AsyncFD, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new ``AsyncSocket`` based on the supplied params.
assert fd != osInvalidSocket.TAsyncFD
assert fd != osInvalidSocket.AsyncFD
new(result)
result.fd = fd.SocketHandle
result.isBuffered = isBuff
if isBuff:
result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered:
result.currPos = 0
proc newAsyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
proc newAsyncSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
result = newAsyncSocket(newAsyncNativeSocket(domain, sockType, protocol),
domain, sockType, protocol, buffered)
proc newAsyncSocket*(domain, typ, protocol: cint, buffered = true): AsyncSocket =
proc newAsyncSocket*(domain, sockType, protocol: cint,
buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
result = newAsyncSocket(newAsyncNativeSocket(domain, sockType, protocol),
Domain(domain), SockType(sockType),
Protocol(protocol), buffered)
when defined(ssl):
proc getSslError(handle: SslPtr, err: cint): cint =
@ -142,7 +152,7 @@ when defined(ssl):
if read < 0:
raiseSslError()
data.setLen(read)
await socket.fd.TAsyncFd.send(data, flags)
await socket.fd.AsyncFd.send(data, flags)
proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag],
sslError: cint) {.async.} =
@ -150,7 +160,7 @@ when defined(ssl):
of SSL_ERROR_WANT_WRITE:
await sendPendingSslData(socket, flags)
of SSL_ERROR_WANT_READ:
var data = await recv(socket.fd.TAsyncFD, BufferSize, flags)
var data = await recv(socket.fd.AsyncFD, BufferSize, flags)
let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint)
if ret < 0:
raiseSSLError()
@ -169,13 +179,12 @@ when defined(ssl):
let err = getSslError(socket.sslHandle, opResult.cint)
yield appeaseSsl(socket, flags, err.cint)
proc connect*(socket: AsyncSocket, address: string, port: Port,
af = AF_INET) {.async.} =
proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs.
await connect(socket.fd.TAsyncFD, address, port, af)
await connect(socket.fd.AsyncFD, address, port, socket.domain)
if socket.isSsl:
when defined(ssl):
let flags = {SocketFlag.SafeDisconn}
@ -194,7 +203,7 @@ template readInto(buf: cstring, size: int, socket: AsyncSocket,
sslRead(socket.sslHandle, buf, size.cint))
res = opResult
else:
var recvIntoFut = recvInto(socket.fd.TAsyncFD, buf, size, flags)
var recvIntoFut = recvInto(socket.fd.AsyncFD, buf, size, flags)
yield recvIntoFut
# Not in SSL mode.
res = recvIntoFut.read()
@ -271,7 +280,7 @@ proc send*(socket: AsyncSocket, data: string,
sslWrite(socket.sslHandle, addr copy[0], copy.len.cint))
await sendPendingSslData(socket, flags)
else:
await send(socket.fd.TAsyncFD, data, flags)
await send(socket.fd.AsyncFD, data, flags)
proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncSocket]] =
@ -279,15 +288,16 @@ proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
## corresponding to that connection and the remote address of the client.
## The future will complete when the connection is successfully accepted.
var retFuture = newFuture[tuple[address: string, client: AsyncSocket]]("asyncnet.acceptAddr")
var fut = acceptAddr(socket.fd.TAsyncFD, flags)
var fut = acceptAddr(socket.fd.AsyncFD, flags)
fut.callback =
proc (future: Future[tuple[address: string, client: TAsyncFD]]) =
proc (future: Future[tuple[address: string, client: AsyncFD]]) =
assert future.finished
if future.failed:
retFuture.fail(future.readError)
else:
let resultTup = (future.read.address,
newAsyncSocket(future.read.client, socket.isBuffered))
newAsyncSocket(future.read.client, socket.domain,
socket.sockType, socket.protocol, socket.isBuffered))
retFuture.complete(resultTup)
return retFuture
@ -307,7 +317,7 @@ proc accept*(socket: AsyncSocket,
retFut.complete(future.read.client)
return retFut
proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
flags = {SocketFlag.SafeDisconn}) {.async.} =
## Reads a line of data from ``socket`` into ``resString``.
##
@ -329,16 +339,23 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO:
assert(not resString.mget.isNil(),
"String inside resString future needs to be initialised")
result = newFuture[void]("asyncnet.recvLineInto")
# TODO: Make the async transformation check for FutureVar params and complete
# them when the result future is completed.
# Can we replace the result future with the FutureVar?
template addNLIfEmpty(): stmt =
if resString[].len == 0:
resString[].add("\c\L")
if resString.mget.len == 0:
resString.mget.add("\c\L")
if socket.isBuffered:
if socket.bufLen == 0:
let res = socket.readIntoBuf(flags)
if res == 0:
resString.complete()
return
var lastR = false
@ -346,7 +363,8 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
if socket.currPos >= socket.bufLen:
let res = socket.readIntoBuf(flags)
if res == 0:
resString[].setLen(0)
resString.mget.setLen(0)
resString.complete()
return
case socket.buffer[socket.currPos]
@ -356,13 +374,15 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
of '\L':
addNLIfEmpty()
socket.currPos.inc()
resString.complete()
return
else:
if lastR:
socket.currPos.inc()
resString.complete()
return
else:
resString[].add socket.buffer[socket.currPos]
resString.mget.add socket.buffer[socket.currPos]
socket.currPos.inc()
else:
var c = ""
@ -370,18 +390,22 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
let recvFut = recv(socket, 1, flags)
c = recvFut.read()
if c.len == 0:
resString[].setLen(0)
resString.mget.setLen(0)
resString.complete()
return
if c == "\r":
let recvFut = recv(socket, 1, flags) # Skip \L
c = recvFut.read()
assert c == "\L"
addNLIfEmpty()
resString.complete()
return
elif c == "\L":
addNLIfEmpty()
resString.complete()
return
resString[].add c
resString.mget.add c
resString.complete()
proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
@ -407,8 +431,11 @@ proc recvLine*(socket: AsyncSocket,
result.add("\c\L")
assert SocketFlag.Peek notin flags ## TODO:
result = ""
await socket.recvLineInto(addr result, flags)
# TODO: Optimise this
var resString = newFutureVar[string]("asyncnet.recvLine")
resString.mget() = ""
await socket.recvLineInto(resString, flags)
result = resString.mget()
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
@ -423,32 +450,28 @@ proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {.
## Binds ``address``:``port`` to the socket.
##
## If ``address`` is "" then ADDR_ANY will be bound.
if address == "":
var name: Sockaddr_in
when defined(Windows) or defined(nimdoc):
name.sin_family = toInt(AF_INET).int16
var realaddr = address
if realaddr == "":
case socket.domain
of AF_INET6: realaddr = "::"
of AF_INET: realaddr = "0.0.0.0"
else:
name.sin_family = toInt(AF_INET)
name.sin_port = htons(int16(port))
name.sin_addr.s_addr = htonl(INADDR_ANY)
if bindAddr(socket.fd, cast[ptr SockAddr](addr(name)),
sizeof(name).Socklen) < 0'i32:
raiseOSError(osLastError())
else:
var aiList = getAddrInfo(address, port, AF_INET)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
dealloc(aiList)
raiseOSError(osLastError())
raiseOSError("Unknown socket address family and no address specified to bindAddr")
var aiList = getAddrInfo(realaddr, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
dealloc(aiList)
raiseOSError(osLastError())
dealloc(aiList)
proc close*(socket: AsyncSocket) =
## Closes the socket.
defer:
socket.fd.TAsyncFD.closeSocket()
socket.fd.AsyncFD.closeSocket()
when defined(ssl):
if socket.isSSL:
let res = SslShutdown(socket.sslHandle)
SSLFree(socket.sslHandle)
if res == 0:
discard
elif res != 1:
@ -472,6 +495,24 @@ when defined(ssl):
socket.bioOut = bioNew(bio_s_mem())
sslSetBio(socket.sslHandle, socket.bioIn, socket.bioOut)
proc wrapConnectedSocket*(ctx: SslContext, socket: AsyncSocket,
handshake: SslHandshakeType) =
## Wraps a connected socket in an SSL context. This function effectively
## turns ``socket`` into an SSL socket.
##
## This should be called on a connected socket, and will perform
## an SSL handshake immediately.
##
## **Disclaimer**: This code is not well tested, may be very unsafe and
## prone to security vulnerabilities.
wrapSocket(ctx, socket)
case handshake
of handshakeAsClient:
sslSetConnectState(socket.sslHandle)
of handshakeAsServer:
sslSetAcceptState(socket.sslHandle)
proc getSockOpt*(socket: AsyncSocket, opt: SOBool, level = SOL_SOCKET): bool {.
tags: [ReadIOEffect].} =
## Retrieves option ``opt`` as a boolean value.
@ -545,4 +586,3 @@ when not defined(testing) and isMainModule:
var f = accept(sock)
f.callback = onAccept
runForever()

View file

@ -8,12 +8,44 @@
#
## This module implements a base64 encoder and decoder.
##
## Encoding data
## -------------
##
## In order to encode some text simply call the ``encode`` procedure:
##
## .. code-block::nim
## import base64
## let encoded = encode("Hello World")
## echo(encoded) # SGVsbG8gV29ybGQ=
##
## Apart from strings you can also encode lists of integers or characters:
##
## .. code-block::nim
## import base64
## let encodedInts = encode([1,2,3])
## echo(encodedInts) # AQID
## let encodedChars = encode(['h','e','y'])
## echo(encodedChars) # aGV5
##
## The ``encode`` procedure takes an ``openarray`` so both arrays and sequences
## can be passed as parameters.
##
## Decoding data
## -------------
##
## To decode a base64 encoded data string simply call the ``decode``
## procedure:
##
## .. code-block::nim
## import base64
## echo(decode("SGVsbG8gV29ybGQ=")) # Hello World
const
const
cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediate.} =
## encodes `s` into base64 representation. After `lineLen` characters, a
template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediate.} =
## encodes `s` into base64 representation. After `lineLen` characters, a
## `newline` is added.
var total = ((len(s) + 2) div 3) * 4
var numLines = (total + lineLen - 1) div lineLen
@ -29,13 +61,13 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat
var c = ord(s[i+2])
result[r] = cb64[a shr 2]
result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
result[r+2] = cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)]
result[r+3] = cb64[c and 0x3F]
result[r+2] = cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)]
result[r+3] = cb64[c and 0x3F]
inc(r, 4)
inc(i, 3)
inc(currLine, 4)
if currLine >= lineLen and i != s.len-2:
for x in items(newLine):
if currLine >= lineLen and i != s.len-2:
for x in items(newLine):
result[r] = x
inc(r)
currLine = 0
@ -45,7 +77,7 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat
var b = ord(s[i+1])
result[r] = cb64[a shr 2]
result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
result[r+2] = cb64[((b and 0x0F) shl 2)]
result[r+2] = cb64[((b and 0x0F) shl 2)]
result[r+3] = '='
if r+4 != result.len:
setLen(result, r+4)
@ -61,17 +93,22 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat
#assert(r == result.len)
discard
proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string =
## encodes `s` into base64 representation. After `lineLen` characters, a
proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string =
## encodes `s` into base64 representation. After `lineLen` characters, a
## `newline` is added.
##
## This procedure encodes an openarray (array or sequence) of either integers
## or characters.
encodeInternal(s, lineLen, newLine)
proc encode*(s: string, lineLen = 75, newLine="\13\10"): string =
## encodes `s` into base64 representation. After `lineLen` characters, a
proc encode*(s: string, lineLen = 75, newLine="\13\10"): string =
## encodes `s` into base64 representation. After `lineLen` characters, a
## `newline` is added.
##
## This procedure encodes a string.
encodeInternal(s, lineLen, newLine)
proc decodeByte(b: char): int {.inline.} =
proc decodeByte(b: char): int {.inline.} =
case b
of '+': result = ord('>')
of '0'..'9': result = ord(b) + 4
@ -79,7 +116,7 @@ proc decodeByte(b: char): int {.inline.} =
of 'a'..'z': result = ord(b) - 71
else: result = 63
proc decode*(s: string): string =
proc decode*(s: string): string =
## decodes a string in base64 representation back into its original form.
## Whitespace is skipped.
const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'}
@ -96,7 +133,7 @@ proc decode*(s: string): string =
var b = s[i+1].decodeByte
var c = s[i+2].decodeByte
var d = s[i+3].decodeByte
result[r] = chr((a shl 2) and 0xff or ((b shr 4) and 0x03))
result[r+1] = chr((b shl 4) and 0xff or ((c shr 2) and 0x0F))
result[r+2] = chr((c shl 6) and 0xff or (d and 0x3F))
@ -105,19 +142,19 @@ proc decode*(s: string): string =
else: break
assert i == s.len
# adjust the length:
if i > 0 and s[i-1] == '=':
if i > 0 and s[i-1] == '=':
dec(r)
if i > 1 and s[i-2] == '=': dec(r)
setLen(result, r)
when isMainModule:
assert encode("leasure.") == "bGVhc3VyZS4="
assert encode("easure.") == "ZWFzdXJlLg=="
assert encode("asure.") == "YXN1cmUu"
assert encode("sure.") == "c3VyZS4="
const longText = """Man is distinguished, not only by his reason, but by this
singular passion from other animals, which is a lust of the mind,
singular passion from other animals, which is a lust of the mind,
that by a perseverance of delight in the continued and indefatigable
generation of knowledge, exceeds the short vehemence of any carnal
pleasure."""

View file

@ -13,27 +13,29 @@ import strutils
## Basic 2d support with vectors, points, matrices and some basic utilities.
## Vectors are implemented as direction vectors, ie. when transformed with a matrix
## the translation part of matrix is ignored.
## the translation part of matrix is ignored.
## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented for vectors and scalars.
##
## Quick start example:
##
##
## .. code-block:: nim
##
## # Create a matrix which first rotates, then scales and at last translates
##
## var m:TMatrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0)
##
##
## var m:Matrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0)
##
## # Create a 2d point at (100,0) and a vector (5,2)
##
## var pt:TPoint2d=point2d(100.0,0.0)
##
## var vec:TVector2d=vector2d(5.0,2.0)
##
##
##
## var pt:Point2d=point2d(100.0,0.0)
##
## var vec:Vector2d=vector2d(5.0,2.0)
##
##
## pt &= m # transforms pt in place
##
## var pt2:TPoint2d=pt & m #concatenates pt with m and returns a new point
##
## var vec2:TVector2d=vec & m #concatenates vec with m and returns a new vector
##
## var pt2:Point2d=pt & m #concatenates pt with m and returns a new point
##
## var vec2:Vector2d=vec & m #concatenates vec with m and returns a new vector
const
@ -57,49 +59,49 @@ const
## used internally by DegToRad and RadToDeg
type
TMatrix2d* = object
Matrix2d* = object
## Implements a row major 2d matrix, which means
## transformations are applied the order they are concatenated.
## The rightmost column of the 3x3 matrix is left out since normally
## not used for geometric transformations in 2d.
ax*,ay*,bx*,by*,tx*,ty*:float
TPoint2d* = object
## Implements a non-homegeneous 2d point stored as
Point2d* = object
## Implements a non-homogeneous 2d point stored as
## an `x` coordinate and an `y` coordinate.
x*,y*:float
TVector2d* = object
## Implements a 2d **direction vector** stored as
## an `x` coordinate and an `y` coordinate. Direction vector means,
Vector2d* = object
## Implements a 2d **direction vector** stored as
## an `x` coordinate and an `y` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored.
x*,y*:float
{.deprecated: [TMatrix2d: Matrix2d, TPoint2d: Point2d, TVector2d: Vector2d].}
# Some forward declarations...
proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d {.noInit.}
## Creates a new matrix.
proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d {.noInit.}
## Creates a new matrix.
## `ax`,`ay` is the local x axis
## `bx`,`by` is the local y axis
## `tx`,`ty` is the translation
proc vector2d*(x,y:float):TVector2d {.noInit,inline.}
proc vector2d*(x,y:float):Vector2d {.noInit,inline.}
## Returns a new vector (`x`,`y`)
proc point2d*(x,y:float):TPoint2d {.noInit,inline.}
proc point2d*(x,y:float):Point2d {.noInit,inline.}
## Returns a new point (`x`,`y`)
let
IDMATRIX*:TMatrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0)
IDMATRIX*:Matrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0)
## Quick access to an identity matrix
ORIGO*:TPoint2d=point2d(0.0,0.0)
## Quick acces to point (0,0)
XAXIS*:TVector2d=vector2d(1.0,0.0)
## Quick acces to an 2d x-axis unit vector
YAXIS*:TVector2d=vector2d(0.0,1.0)
## Quick acces to an 2d y-axis unit vector
ORIGO*:Point2d=point2d(0.0,0.0)
## Quick access to point (0,0)
XAXIS*:Vector2d=vector2d(1.0,0.0)
## Quick access to an 2d x-axis unit vector
YAXIS*:Vector2d=vector2d(0.0,1.0)
## Quick access to an 2d y-axis unit vector
# ***************************************
# Private utils
# ***************************************
@ -114,23 +116,23 @@ proc safeArccos(v:float):float=
return arccos(clamp(v,-1.0,1.0))
template makeBinOpVector(s:expr)=
template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector2d):TVector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
proc s*(a:TVector2d,b:float):TVector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b))
proc s*(a:float,b:TVector2d):TVector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y))
template makeBinOpAssignVector(s:expr)=
proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b))
proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y))
template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var TVector2d,b:TVector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
proc s*(a:var TVector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)
proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)
# ***************************************
# TMatrix2d implementation
# Matrix2d implementation
# ***************************************
proc setElements*(t:var TMatrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
proc setElements*(t:var Matrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
## Sets arbitrary elements in an existing matrix.
t.ax=ax
t.ay=ay
@ -139,12 +141,12 @@ proc setElements*(t:var TMatrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
t.tx=tx
t.ty=ty
proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d =
proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d =
result.setElements(ax,ay,bx,by,tx,ty)
proc `&`*(a,b:TMatrix2d):TMatrix2d {.noInit.} = #concatenate matrices
proc `&`*(a,b:Matrix2d):Matrix2d {.noInit.} = #concatenate matrices
## Concatenates matrices returning a new matrix.
# | a.AX a.AY 0 | | b.AX b.AY 0 |
# | a.BX a.BY 0 | * | b.BX b.BY 0 |
# | a.TX a.TY 1 | | b.TX b.TY 1 |
@ -153,46 +155,46 @@ proc `&`*(a,b:TMatrix2d):TMatrix2d {.noInit.} = #concatenate matrices
a.ax * b.ay + a.ay * b.by,
a.bx * b.ax + a.by * b.bx,
a.bx * b.ay + a.by * b.by,
a.tx * b.ax + a.ty * b.bx + b.tx,
a.tx * b.ax + a.ty * b.bx + b.tx,
a.tx * b.ay + a.ty * b.by + b.ty)
proc scale*(s:float):TMatrix2d {.noInit.} =
proc scale*(s:float):Matrix2d {.noInit.} =
## Returns a new scale matrix.
result.setElements(s,0,0,s,0,0)
proc scale*(s:float,org:TPoint2d):TMatrix2d {.noInit.} =
proc scale*(s:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new scale matrix using, `org` as scale origin.
result.setElements(s,0,0,s,org.x-s*org.x,org.y-s*org.y)
proc stretch*(sx,sy:float):TMatrix2d {.noInit.} =
proc stretch*(sx,sy:float):Matrix2d {.noInit.} =
## Returns new a stretch matrix, which is a
## scale matrix with non uniform scale in x and y.
result.setElements(sx,0,0,sy,0,0)
proc stretch*(sx,sy:float,org:TPoint2d):TMatrix2d {.noInit.} =
proc stretch*(sx,sy:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x and y.
## `org` is used as stretch origin.
result.setElements(sx,0,0,sy,org.x-sx*org.x,org.y-sy*org.y)
proc move*(dx,dy:float):TMatrix2d {.noInit.} =
proc move*(dx,dy:float):Matrix2d {.noInit.} =
## Returns a new translation matrix.
result.setElements(1,0,0,1,dx,dy)
proc move*(v:TVector2d):TMatrix2d {.noInit.} =
proc move*(v:Vector2d):Matrix2d {.noInit.} =
## Returns a new translation matrix from a vector.
result.setElements(1,0,0,1,v.x,v.y)
proc rotate*(rad:float):TMatrix2d {.noInit.} =
proc rotate*(rad:float):Matrix2d {.noInit.} =
## Returns a new rotation matrix, which
## represents a rotation by `rad` radians
let
let
s=sin(rad)
c=cos(rad)
result.setElements(c,s,-s,c,0,0)
proc rotate*(rad:float,org:TPoint2d):TMatrix2d {.noInit.} =
proc rotate*(rad:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new rotation matrix, which
## represents a rotation by `rad` radians around
## the origin `org`
@ -200,8 +202,8 @@ proc rotate*(rad:float,org:TPoint2d):TMatrix2d {.noInit.} =
s=sin(rad)
c=cos(rad)
result.setElements(c,s,-s,c,org.x+s*org.y-c*org.x,org.y-c*org.y-s*org.x)
proc mirror*(v:TVector2d):TMatrix2d {.noInit.} =
proc mirror*(v:Vector2d):Matrix2d {.noInit.} =
## Returns a new mirror matrix, mirroring
## around the line that passes through origo and
## has the direction of `v`
@ -211,7 +213,7 @@ proc mirror*(v:TVector2d):TMatrix2d {.noInit.} =
nd=1.0/(sqx+sqy) #used to normalize invector
xy2=v.x*v.y*2.0*nd
sqd=nd*(sqx-sqy)
if nd==Inf or nd==NegInf:
return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity
@ -220,7 +222,7 @@ proc mirror*(v:TVector2d):TMatrix2d {.noInit.} =
xy2,-sqd,
0.0,0.0)
proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} =
proc mirror*(org:Point2d,v:Vector2d):Matrix2d {.noInit.} =
## Returns a new mirror matrix, mirroring
## around the line that passes through `org` and
## has the direction of `v`
@ -230,7 +232,7 @@ proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} =
nd=1.0/(sqx+sqy) #used to normalize invector
xy2=v.x*v.y*2.0*nd
sqd=nd*(sqx-sqy)
if nd==Inf or nd==NegInf:
return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity
@ -238,83 +240,83 @@ proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} =
sqd,xy2,
xy2,-sqd,
org.x-org.y*xy2-org.x*sqd,org.y-org.x*xy2+org.y*sqd)
proc skew*(xskew,yskew:float):TMatrix2d {.noInit.} =
## Returns a new skew matrix, which has its
proc skew*(xskew,yskew:float):Matrix2d {.noInit.} =
## Returns a new skew matrix, which has its
## x axis rotated `xskew` radians from the local x axis, and
## y axis rotated `yskew` radians from the local y axis
result.setElements(cos(yskew),sin(yskew),-sin(xskew),cos(xskew),0,0)
proc `$`* (t:TMatrix2d):string {.noInit.} =
proc `$`* (t:Matrix2d):string {.noInit.} =
## Returns a string representation of the matrix
return rtos(t.ax) & "," & rtos(t.ay) &
"," & rtos(t.bx) & "," & rtos(t.by) &
"," & rtos(t.bx) & "," & rtos(t.by) &
"," & rtos(t.tx) & "," & rtos(t.ty)
proc isUniform*(t:TMatrix2d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
proc isUniform*(t:Matrix2d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example)
## it cannot transform a circle into an ellipse.
## `tol` is used as tolerance for both equal length comparison
## `tol` is used as tolerance for both equal length comparison
## and perp. comparison.
#dot product=0 means perpendicular coord. system:
if abs(t.ax*t.bx+t.ay*t.by)<=tol:
if abs(t.ax*t.bx+t.ay*t.by)<=tol:
#subtract squared lengths of axes to check if uniform scaling:
if abs((t.ax*t.ax+t.ay*t.ay)-(t.bx*t.bx+t.by*t.by))<=tol:
return true
return false
proc determinant*(t:TMatrix2d):float=
## Computes the determinant of the matrix.
#NOTE: equivalent with perp.dot product for two 2d vectors
return t.ax*t.by-t.bx*t.ay
proc isMirroring* (m:TMatrix2d):bool=
proc determinant*(t:Matrix2d):float=
## Computes the determinant of the matrix.
#NOTE: equivalent with perp.dot product for two 2d vectors
return t.ax*t.by-t.bx*t.ay
proc isMirroring* (m:Matrix2d):bool=
## Checks if the `m` is a mirroring matrix,
## which means it will reverse direction of a curve transformed with it
return m.determinant<0.0
proc inverse*(m:TMatrix2d):TMatrix2d {.noInit.} =
proc inverse*(m:Matrix2d):Matrix2d {.noInit.} =
## Returns a new matrix, which is the inverse of the matrix
## If the matrix is not invertible (determinant=0), an EDivByZero
## will be raised.
let d=m.determinant
if d==0.0:
raise newException(DivByZeroError,"Cannot invert a zero determinant matrix")
result.setElements(
m.by/d,-m.ay/d,
-m.bx/d,m.ax/d,
(m.bx*m.ty-m.by*m.tx)/d,
(m.ay*m.tx-m.ax*m.ty)/d)
proc equals*(m1:TMatrix2d,m2:TMatrix2d,tol=1.0e-6):bool=
proc equals*(m1:Matrix2d,m2:Matrix2d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`.
return
return
abs(m1.ax-m2.ax)<=tol and
abs(m1.ay-m2.ay)<=tol and
abs(m1.bx-m2.bx)<=tol and
abs(m1.by-m2.by)<=tol and
abs(m1.tx-m2.tx)<=tol and
abs(m1.ty-m2.ty)<=tol
proc `=~`*(m1,m2:TMatrix2d):bool=
proc `=~`*(m1,m2:Matrix2d):bool=
## Checks if `m1`and `m2` is approximately equal, using a
## tolerance of 1e-6.
equals(m1,m2)
proc isIdentity*(m:TMatrix2d,tol=1.0e-6):bool=
proc isIdentity*(m:Matrix2d,tol=1.0e-6):bool=
## Checks is a matrix is approximately an identity matrix,
## using `tol` as tolerance for each element.
return equals(m,IDMATRIX,tol)
proc apply*(m:TMatrix2d,x,y:var float,translate=false)=
proc apply*(m:Matrix2d,x,y:var float,translate=false)=
## Applies transformation `m` onto `x`,`y`, optionally
## using the translation part of the matrix.
if translate: # positional style transform
@ -329,37 +331,37 @@ proc apply*(m:TMatrix2d,x,y:var float,translate=false)=
# ***************************************
# TVector2d implementation
# Vector2d implementation
# ***************************************
proc vector2d*(x,y:float):TVector2d = #forward decl.
proc vector2d*(x,y:float):Vector2d = #forward decl.
result.x=x
result.y=y
proc polarVector2d*(ang:float,len:float):TVector2d {.noInit.} =
proc polarVector2d*(ang:float,len:float):Vector2d {.noInit.} =
## Returns a new vector with angle `ang` and magnitude `len`
result.x=cos(ang)*len
result.y=sin(ang)*len
proc slopeVector2d*(slope:float,len:float):TVector2d {.noInit.} =
proc slopeVector2d*(slope:float,len:float):Vector2d {.noInit.} =
## Returns a new vector having slope (dy/dx) given by
## `slope`, and a magnitude of `len`
let ang=arctan(slope)
result.x=cos(ang)*len
result.y=sin(ang)*len
proc len*(v:TVector2d):float {.inline.}=
proc len*(v:Vector2d):float {.inline.}=
## Returns the length of the vector.
sqrt(v.x*v.x+v.y*v.y)
proc `len=`*(v:var TVector2d,newlen:float) {.noInit.} =
proc `len=`*(v:var Vector2d,newlen:float) {.noInit.} =
## Sets the length of the vector, keeping its angle.
let fac=newlen/v.len
if newlen==0.0:
v.x=0.0
v.y=0.0
return
if fac==Inf or fac==NegInf:
#to short for float accuracy
#do as good as possible:
@ -368,30 +370,30 @@ proc `len=`*(v:var TVector2d,newlen:float) {.noInit.} =
else:
v.x*=fac
v.y*=fac
proc sqrLen*(v:TVector2d):float {.inline.}=
proc sqrLen*(v:Vector2d):float {.inline.}=
## Computes the squared length of the vector, which is
## faster than computing the absolute length.
v.x*v.x+v.y*v.y
proc angle*(v:TVector2d):float=
## Returns the angle of the vector.
proc angle*(v:Vector2d):float=
## Returns the angle of the vector.
## (The counter clockwise plane angle between posetive x axis and `v`)
result=arctan2(v.y,v.x)
if result<0.0: result+=DEG360
proc `$` *(v:TVector2d):string=
proc `$` *(v:Vector2d):string=
## String representation of `v`
result=rtos(v.x)
result.add(",")
result.add(rtos(v.y))
proc `&` *(v:TVector2d,m:TMatrix2d):TVector2d {.noInit.} =
proc `&` *(v:Vector2d,m:Matrix2d):Vector2d {.noInit.} =
## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY 0 |
# | X Y 1 | * | BX BY 0 |
# | 0 0 1 |
@ -399,11 +401,11 @@ proc `&` *(v:TVector2d,m:TMatrix2d):TVector2d {.noInit.} =
result.y=v.x*m.ay+v.y*m.by
proc `&=`*(v:var TVector2d,m:TMatrix2d) {.inline.}=
proc `&=`*(v:var Vector2d,m:Matrix2d) {.inline.}=
## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY 0 |
# | X Y 1 | * | BX BY 0 |
# | 0 0 1 |
@ -412,31 +414,31 @@ proc `&=`*(v:var TVector2d,m:TMatrix2d) {.inline.}=
v.x=newx
proc tryNormalize*(v:var TVector2d):bool=
proc tryNormalize*(v:var Vector2d):bool=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and
## If `v` has zero length (and thus no angle), it is left unmodified and
## false is returned, otherwise true is returned.
let mag=v.len
if mag==0.0:
return false
v.x/=mag
v.y/=mag
return true
proc normalize*(v:var TVector2d) {.inline.}=
proc normalize*(v:var Vector2d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc transformNorm*(v:var TVector2d,t:TMatrix2d)=
proc transformNorm*(v:var Vector2d,t:Matrix2d)=
## Applies a normal direction transformation `t` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## (determinant=0), an EDivByZero will be raised.
# transforming a normal is done by transforming
@ -452,7 +454,7 @@ proc transformNorm*(v:var TVector2d,t:TMatrix2d)=
v.y = (t.ax*v.y-t.bx*v.x)/d
v.x = newx
proc transformInv*(v:var TVector2d,t:TMatrix2d)=
proc transformInv*(v:var Vector2d,t:Matrix2d)=
## Applies inverse of a transformation `t` to `v` in place.
## This is faster than creating an inverse matrix and apply() it.
## Transforming a vector ignores the translational part
@ -467,42 +469,42 @@ proc transformInv*(v:var TVector2d,t:TMatrix2d)=
v.y = (t.ax*v.y-t.ay*v.x)/d
v.x = newx
proc transformNormInv*(v:var TVector2d,t:TMatrix2d)=
proc transformNormInv*(v:var Vector2d,t:Matrix2d)=
## Applies an inverse normal direction transformation `t` onto `v` in place.
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## translational part of the matrix.
# normal inverse transform is done by transforming
# by the inverse of the transpose of the inverse of the org. matrix
# which is equivalent with transforming with the transpose.
# | | | AX AY 0 |^-1|^T|^-1 | AX BX 0 |
# | X Y 1 | * | | | BX BY 0 | | | = | X Y 1 | * | AY BY 0 |
# | | | 0 0 1 | | | | 0 0 1 |
# | X Y 1 | * | | | BX BY 0 | | | = | X Y 1 | * | AY BY 0 |
# | | | 0 0 1 | | | | 0 0 1 |
# This can be heavily reduced to:
let newx=t.ay*v.y+t.ax*v.x
v.y=t.by*v.y+t.bx*v.x
v.x=newx
proc rotate90*(v:var TVector2d) {.inline.}=
proc rotate90*(v:var Vector2d) {.inline.}=
## Quickly rotates vector `v` 90 degrees counter clockwise,
## without using any trigonometrics.
swap(v.x,v.y)
v.x= -v.x
proc rotate180*(v:var TVector2d){.inline.}=
proc rotate180*(v:var Vector2d){.inline.}=
## Quickly rotates vector `v` 180 degrees counter clockwise,
## without using any trigonometrics.
v.x= -v.x
v.y= -v.y
proc rotate270*(v:var TVector2d) {.inline.}=
proc rotate270*(v:var Vector2d) {.inline.}=
## Quickly rotates vector `v` 270 degrees counter clockwise,
## without using any trigonometrics.
swap(v.x,v.y)
v.y= -v.y
proc rotate*(v:var TVector2d,rad:float) =
proc rotate*(v:var Vector2d,rad:float) =
## Rotates vector `v` `rad` radians in place.
let
s=sin(rad)
@ -510,19 +512,19 @@ proc rotate*(v:var TVector2d,rad:float) =
newx=c*v.x-s*v.y
v.y=c*v.y+s*v.x
v.x=newx
proc scale*(v:var TVector2d,fac:float){.inline.}=
proc scale*(v:var Vector2d,fac:float){.inline.}=
## Scales vector `v` `rad` radians in place.
v.x*=fac
v.y*=fac
proc stretch*(v:var TVector2d,facx,facy:float){.inline.}=
proc stretch*(v:var Vector2d,facx,facy:float){.inline.}=
## Stretches vector `v` `facx` times horizontally,
## and `facy` times vertically.
v.x*=facx
v.y*=facy
proc mirror*(v:var TVector2d,mirrvec:TVector2d)=
proc mirror*(v:var Vector2d,mirrvec:Vector2d)=
## Mirrors vector `v` using `mirrvec` as mirror direction.
let
sqx=mirrvec.x*mirrvec.x
@ -530,20 +532,20 @@ proc mirror*(v:var TVector2d,mirrvec:TVector2d)=
nd=1.0/(sqx+sqy) #used to normalize invector
xy2=mirrvec.x*mirrvec.y*2.0*nd
sqd=nd*(sqx-sqy)
if nd==Inf or nd==NegInf:
return #mirroring around a zero vector is arbitrary=>keep as is is fastest
let newx=xy2*v.y+sqd*v.x
v.y=v.x*xy2-sqd*v.y
v.x=newx
proc `-` *(v:TVector2d):TVector2d=
proc `-` *(v:Vector2d):Vector2d=
## Negates a vector
result.x= -v.x
result.y= -v.y
# declare templated binary operators
makeBinOpVector(`+`)
makeBinOpVector(`-`)
@ -555,28 +557,28 @@ makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector2d):float=
## Computes the dot product of two vectors.
proc dot*(v1,v2:Vector2d):float=
## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y
proc cross*(v1,v2:TVector2d):float=
proc cross*(v1,v2:Vector2d):float=
## Computes the cross product of two vectors, also called
## the 'perpendicular dot product' in 2d. Returns 0.0 if the vectors
## are parallel.
return v1.x*v2.y-v1.y*v2.x
proc equals*(v1,v2:TVector2d,tol=1.0e-6):bool=
proc equals*(v1,v2:Vector2d,tol=1.0e-6):bool=
## Checks if two vectors approximately equals with a tolerance.
return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol
proc `=~` *(v1,v2:TVector2d):bool=
## Checks if two vectors approximately equals with a
proc `=~` *(v1,v2:Vector2d):bool=
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(v1,v2)
proc angleTo*(v1,v2:TVector2d):float=
## Returns the smallest of the two possible angles
proc angleTo*(v1,v2:Vector2d):float=
## Returns the smallest of the two possible angles
## between `v1` and `v2` in radians.
var
nv1=v1
@ -584,16 +586,16 @@ proc angleTo*(v1,v2:TVector2d):float=
if not nv1.tryNormalize or not nv2.tryNormalize:
return 0.0 # zero length vector has zero angle to any other vector
return safeArccos(dot(nv1,nv2))
proc angleCCW*(v1,v2:TVector2d):float=
proc angleCCW*(v1,v2:Vector2d):float=
## Returns the counter clockwise plane angle from `v1` to `v2`,
## in range 0 - 2*PI
let a=v1.angleTo(v2)
if v1.cross(v2)>=0.0:
return a
return DEG360-a
proc angleCW*(v1,v2:TVector2d):float=
proc angleCW*(v1,v2:Vector2d):float=
## Returns the clockwise plane angle from `v1` to `v2`,
## in range 0 - 2*PI
let a=v1.angleTo(v2)
@ -601,7 +603,7 @@ proc angleCW*(v1,v2:TVector2d):float=
return a
return DEG360-a
proc turnAngle*(v1,v2:TVector2d):float=
proc turnAngle*(v1,v2:Vector2d):float=
## Returns the amount v1 should be rotated (in radians) to equal v2,
## in range -PI to PI
let a=v1.angleTo(v2)
@ -609,35 +611,35 @@ proc turnAngle*(v1,v2:TVector2d):float=
return -a
return a
proc bisect*(v1,v2:TVector2d):TVector2d {.noInit.}=
proc bisect*(v1,v2:Vector2d):Vector2d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length,
## of the other is returned. If both input vectors has zero length,
## an arbitrary normalized vector is returned.
var
vmag1=v1.len
vmag2=v2.len
# zero length vector equals arbitrary vector, just change to magnitude to one to
# avoid zero division
if vmag1==0.0:
if vmag1==0.0:
if vmag2==0: #both are zero length return any normalized vector
return XAXIS
vmag1=1.0
if vmag2==0.0: vmag2=1.0
if vmag2==0.0: vmag2=1.0
let
x1=v1.x/vmag1
y1=v1.y/vmag1
x2=v2.x/vmag2
y2=v2.y/vmag2
result.x=(x1 + x2) * 0.5
result.y=(y1 + y2) * 0.5
if not result.tryNormalize():
# This can happen if vectors are colinear. In this special case
# there are actually two bisectors, we select just
# there are actually two bisectors, we select just
# one of them (x1,y1 rotated 90 degrees ccw).
result.x = -y1
result.y = x1
@ -645,24 +647,24 @@ proc bisect*(v1,v2:TVector2d):TVector2d {.noInit.}=
# ***************************************
# TPoint2d implementation
# Point2d implementation
# ***************************************
proc point2d*(x,y:float):TPoint2d =
proc point2d*(x,y:float):Point2d =
result.x=x
result.y=y
proc sqrDist*(a,b:TPoint2d):float=
proc sqrDist*(a,b:Point2d):float=
## Computes the squared distance between `a` and `b`
let dx=b.x-a.x
let dy=b.y-a.y
result=dx*dx+dy*dy
proc dist*(a,b:TPoint2d):float {.inline.}=
proc dist*(a,b:Point2d):float {.inline.}=
## Computes the absolute distance between `a` and `b`
result=sqrt(sqrDist(a,b))
proc angle*(a,b:TPoint2d):float=
proc angle*(a,b:Point2d):float=
## Computes the angle of the vector `b`-`a`
let dx=b.x-a.x
let dy=b.y-a.y
@ -670,88 +672,88 @@ proc angle*(a,b:TPoint2d):float=
if result<0:
result += DEG360
proc `$` *(p:TPoint2d):string=
proc `$` *(p:Point2d):string=
## String representation of `p`
result=rtos(p.x)
result.add(",")
result.add(rtos(p.y))
proc `&`*(p:TPoint2d,t:TMatrix2d):TPoint2d {.noInit,inline.} =
proc `&`*(p:Point2d,t:Matrix2d):Point2d {.noInit,inline.} =
## Concatenates a point `p` with a transform `t`,
## resulting in a new, transformed point.
# | AX AY 0 |
# | X Y 1 | * | BX BY 0 |
# | TX TY 1 |
result.x=p.x*t.ax+p.y*t.bx+t.tx
result.y=p.x*t.ay+p.y*t.by+t.ty
proc `&=` *(p:var TPoint2d,t:TMatrix2d) {.inline.}=
proc `&=` *(p:var Point2d,t:Matrix2d) {.inline.}=
## Applies transformation `t` onto `p` in place.
let newx=p.x*t.ax+p.y*t.bx+t.tx
p.y=p.x*t.ay+p.y*t.by+t.ty
p.x=newx
proc transformInv*(p:var TPoint2d,t:TMatrix2d){.inline.}=
proc transformInv*(p:var Point2d,t:Matrix2d){.inline.}=
## Applies the inverse of transformation `t` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised.
# | AX AY 0 | ^-1
# | X Y 1 | * | BX BY 0 |
# | TX TY 1 |
let d=t.determinant
if d==0.0:
raise newException(DivByZeroError,"Cannot invert a zero determinant matrix")
let
let
newx= (t.bx*t.ty-t.by*t.tx+p.x*t.by-p.y*t.bx)/d
p.y = -(t.ax*t.ty-t.ay*t.tx+p.x*t.ay-p.y*t.ax)/d
p.x=newx
proc `+`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting
proc `+`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting
## in a new point.
result.x=p.x+v.x
result.y=p.y+v.y
proc `+=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} =
proc `+=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} =
## Adds a vector `v` to a point `p` in place.
p.x+=v.x
p.y+=v.y
proc `-`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting
proc `-`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting
## in a new point.
result.x=p.x-v.x
result.y=p.y-v.y
proc `-`*(p1,p2:TPoint2d):TVector2d {.noInit,inline.} =
proc `-`*(p1,p2:Point2d):Vector2d {.noInit,inline.} =
## Subtracts `p2`from `p1` resulting in a difference vector.
result.x=p1.x-p2.x
result.y=p1.y-p2.y
proc `-=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} =
proc `-=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x
p.y-=v.y
proc equals(p1,p2:TPoint2d,tol=1.0e-6):bool {.inline.}=
proc equals(p1,p2:Point2d,tol=1.0e-6):bool {.inline.}=
## Checks if two points approximately equals with a tolerance.
return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol
proc `=~`*(p1,p2:TPoint2d):bool {.inline.}=
## Checks if two vectors approximately equals with a
proc `=~`*(p1,p2:Point2d):bool {.inline.}=
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(p1,p2)
proc polar*(p:TPoint2d,ang,dist:float):TPoint2d {.noInit.} =
proc polar*(p:Point2d,ang,dist:float):Point2d {.noInit.} =
## Returns a point with a given angle and distance away from `p`
result.x=p.x+cos(ang)*dist
result.y=p.y+sin(ang)*dist
proc rotate*(p:var TPoint2d,rad:float)=
proc rotate*(p:var Point2d,rad:float)=
## Rotates a point in place `rad` radians around origo.
let
c=cos(rad)
@ -759,8 +761,8 @@ proc rotate*(p:var TPoint2d,rad:float)=
newx=p.x*c-p.y*s
p.y=p.y*c+p.x*s
p.x=newx
proc rotate*(p:var TPoint2d,rad:float,org:TPoint2d)=
proc rotate*(p:var Point2d,rad:float,org:Point2d)=
## Rotates a point in place `rad` radians using `org` as
## center of rotation.
let
@ -769,66 +771,66 @@ proc rotate*(p:var TPoint2d,rad:float,org:TPoint2d)=
newx=(p.x - org.x) * c - (p.y - org.y) * s + org.x
p.y=(p.y - org.y) * c + (p.x - org.x) * s + org.y
p.x=newx
proc scale*(p:var TPoint2d,fac:float) {.inline.}=
proc scale*(p:var Point2d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin.
p.x*=fac
p.y*=fac
proc scale*(p:var TPoint2d,fac:float,org:TPoint2d){.inline.}=
proc scale*(p:var Point2d,fac:float,org:Point2d){.inline.}=
## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x
p.y=(p.y - org.y) * fac + org.y
proc stretch*(p:var TPoint2d,facx,facy:float){.inline.}=
## Scales a point in place non uniformly `facx` and `facy` times with
proc stretch*(p:var Point2d,facx,facy:float){.inline.}=
## Scales a point in place non uniformly `facx` and `facy` times with
## world origo as origin.
p.x*=facx
p.y*=facy
proc stretch*(p:var TPoint2d,facx,facy:float,org:TPoint2d){.inline.}=
## Scales the point in place non uniformly `facx` and `facy` times with
proc stretch*(p:var Point2d,facx,facy:float,org:Point2d){.inline.}=
## Scales the point in place non uniformly `facx` and `facy` times with
## `org` as origin.
p.x=(p.x - org.x) * facx + org.x
p.y=(p.y - org.y) * facy + org.y
proc move*(p:var TPoint2d,dx,dy:float){.inline.}=
proc move*(p:var Point2d,dx,dy:float){.inline.}=
## Translates a point `dx`, `dy` in place.
p.x+=dx
p.y+=dy
proc move*(p:var TPoint2d,v:TVector2d){.inline.}=
proc move*(p:var Point2d,v:Vector2d){.inline.}=
## Translates a point with vector `v` in place.
p.x+=v.x
p.y+=v.y
proc sgnArea*(a,b,c:TPoint2d):float=
proc sgnArea*(a,b,c:Point2d):float=
## Computes the signed area of the triangle thru points `a`,`b` and `c`
## result>0.0 for counter clockwise triangle
## result<0.0 for clockwise triangle
## This is commonly used to determinate side of a point with respect to a line.
return ((b.x - c.x) * (b.y - a.y)-(b.y - c.y) * (b.x - a.x))*0.5
proc area*(a,b,c:TPoint2d):float=
proc area*(a,b,c:Point2d):float=
## Computes the area of the triangle thru points `a`,`b` and `c`
return abs(sgnArea(a,b,c))
proc closestPoint*(p:TPoint2d,pts:varargs[TPoint2d]):TPoint2d=
## Returns a point selected from `pts`, that has the closest
proc closestPoint*(p:Point2d,pts:varargs[Point2d]):Point2d=
## Returns a point selected from `pts`, that has the closest
## euclidean distance to `p`
assert(pts.len>0) # must have at least one point
var
var
bestidx=0
bestdist=p.sqrDist(pts[0])
curdist:float
for idx in 1..high(pts):
curdist=p.sqrDist(pts[idx])
if curdist<bestdist:
bestidx=idx
bestdist=curdist
result=pts[bestidx]
@ -843,7 +845,7 @@ proc normAngle*(ang:float):float=
return ang
return ang mod DEG360
proc degToRad*(deg:float):float {.inline.}=
## converts `deg` degrees to radians
deg / RAD2DEGCONST
@ -852,4 +854,4 @@ proc radToDeg*(rad:float):float {.inline.}=
## converts `rad` radians to degrees
rad * RAD2DEGCONST

View file

@ -23,27 +23,29 @@ import times
##
## Quick start example:
##
## .. code-block:: nim
##
## # Create a matrix which first rotates, then scales and at last translates
##
## var m:TMatrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0)
## var m:Matrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0)
##
## # Create a 3d point at (100,150,200) and a vector (5,2,3)
##
## var pt:TPoint3d=point3d(100.0,150.0,200.0)
## var pt:Point3d=point3d(100.0,150.0,200.0)
##
## var vec:TVector3d=vector3d(5.0,2.0,3.0)
## var vec:Vector3d=vector3d(5.0,2.0,3.0)
##
##
## pt &= m # transforms pt in place
##
## var pt2:TPoint3d=pt & m #concatenates pt with m and returns a new point
## var pt2:Point3d=pt & m #concatenates pt with m and returns a new point
##
## var vec2:TVector3d=vec & m #concatenates vec with m and returns a new vector
## var vec2:Vector3d=vec & m #concatenates vec with m and returns a new vector
type
TMatrix3d* =object
Matrix3d* =object
## Implements a row major 3d matrix, which means
## transformations are applied the order they are concatenated.
## This matrix is stored as an 4x4 matrix:
@ -52,31 +54,31 @@ type
## [ cx cy cz cw ]
## [ tx ty tz tw ]
ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float
TPoint3d* = object
## Implements a non-homegeneous 2d point stored as
Point3d* = object
## Implements a non-homogeneous 3d point stored as
## an `x` , `y` and `z` coordinate.
x*,y*,z*:float
TVector3d* = object
Vector3d* = object
## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored.
x*,y*,z*:float
{.deprecated: [TMatrix3d: Matrix3d, TPoint3d: Point3d, TVector3d: Vector3d].}
# Some forward declarations
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d {.noInit.}
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d {.noInit.}
## Creates a new 4x4 3d transformation matrix.
## `ax` , `ay` , `az` is the local x axis.
## `bx` , `by` , `bz` is the local y axis.
## `cx` , `cy` , `cz` is the local z axis.
## `tx` , `ty` , `tz` is the translation.
proc vector3d*(x,y,z:float):TVector3d {.noInit,inline.}
proc vector3d*(x,y,z:float):Vector3d {.noInit,inline.}
## Returns a new 3d vector (`x`,`y`,`z`)
proc point3d*(x,y,z:float):TPoint3d {.noInit,inline.}
proc point3d*(x,y,z:float):Point3d {.noInit,inline.}
## Returns a new 4d point (`x`,`y`,`z`)
proc tryNormalize*(v:var TVector3d):bool
proc tryNormalize*(v:var Vector3d):bool
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned.
@ -84,19 +86,19 @@ proc tryNormalize*(v:var TVector3d):bool
let
IDMATRIX*:TMatrix3d=matrix3d(
IDMATRIX*:Matrix3d=matrix3d(
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0)
## Quick access to a 3d identity matrix
ORIGO*:TPoint3d=point3d(0.0,0.0,0.0)
ORIGO*:Point3d=point3d(0.0,0.0,0.0)
## Quick access to point (0,0)
XAXIS*:TVector3d=vector3d(1.0,0.0,0.0)
XAXIS*:Vector3d=vector3d(1.0,0.0,0.0)
## Quick access to an 3d x-axis unit vector
YAXIS*:TVector3d=vector3d(0.0,1.0,0.0)
YAXIS*:Vector3d=vector3d(0.0,1.0,0.0)
## Quick access to an 3d y-axis unit vector
ZAXIS*:TVector3d=vector3d(0.0,0.0,1.0)
ZAXIS*:Vector3d=vector3d(0.0,0.0,1.0)
## Quick access to an 3d z-axis unit vector
@ -116,27 +118,27 @@ proc safeArccos(v:float):float=
template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} =
proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z))
proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} =
proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b),s(a.y,b),s(a.z,b))
proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} =
proc s*(a:float,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a,b.x),s(a,b.y),s(a,b.z))
template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var TVector3d,b:TVector3d) {.inline.} =
proc s*(a:var Vector3d,b:Vector3d) {.inline.} =
s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z)
proc s*(a:var TVector3d,b:float) {.inline.} =
proc s*(a:var Vector3d,b:float) {.inline.} =
s(a.x,b) ; s(a.y,b) ; s(a.z,b)
# ***************************************
# TMatrix3d implementation
# Matrix3d implementation
# ***************************************
proc setElements*(t:var TMatrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float) {.inline.}=
proc setElements*(t:var Matrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float) {.inline.}=
## Sets arbitrary elements in an exisitng matrix.
t.ax=ax
t.ay=ay
@ -155,10 +157,10 @@ proc setElements*(t:var TMatrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,t
t.tz=tz
t.tw=tw
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d =
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d =
result.setElements(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw)
proc `&`*(a,b:TMatrix3d):TMatrix3d {.noinit.} =
proc `&`*(a,b:Matrix3d):Matrix3d {.noinit.} =
## Concatenates matrices returning a new matrix.
result.setElements(
a.aw*b.tx+a.az*b.cx+a.ay*b.bx+a.ax*b.ax,
@ -182,36 +184,36 @@ proc `&`*(a,b:TMatrix3d):TMatrix3d {.noinit.} =
a.tw*b.tw+a.tz*b.cw+a.ty*b.bw+a.tx*b.aw)
proc scale*(s:float):TMatrix3d {.noInit.} =
proc scale*(s:float):Matrix3d {.noInit.} =
## Returns a new scaling matrix.
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1)
proc scale*(s:float,org:TPoint3d):TMatrix3d {.noInit.} =
proc scale*(s:float,org:Point3d):Matrix3d {.noInit.} =
## Returns a new scaling matrix using, `org` as scale origin.
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0,
org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0)
proc stretch*(sx,sy,sz:float):TMatrix3d {.noInit.} =
proc stretch*(sx,sy,sz:float):Matrix3d {.noInit.} =
## Returns new a stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1)
proc stretch*(sx,sy,sz:float,org:TPoint3d):TMatrix3d {.noInit.} =
proc stretch*(sx,sy,sz:float,org:Point3d):Matrix3d {.noInit.} =
## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z.
## `org` is used as stretch origin.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1)
proc move*(dx,dy,dz:float):TMatrix3d {.noInit.} =
proc move*(dx,dy,dz:float):Matrix3d {.noInit.} =
## Returns a new translation matrix.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1)
proc move*(v:TVector3d):TMatrix3d {.noInit.} =
proc move*(v:Vector3d):Matrix3d {.noInit.} =
## Returns a new translation matrix from a vector.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, v.x,v.y,v.z,1)
proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}=
proc rotate*(angle:float,axis:Vector3d):Matrix3d {.noInit.}=
## Creates a rotation matrix that rotates `angle` radians over
## `axis`, which passes through origo.
@ -242,7 +244,7 @@ proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}=
uwomc+vsi, vwomc-usi, w2+(1.0-w2)*cs, 0.0,
0.0,0.0,0.0,1.0)
proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
proc rotate*(angle:float,org:Point3d,axis:Vector3d):Matrix3d {.noInit.}=
## Creates a rotation matrix that rotates `angle` radians over
## `axis`, which passes through `org`.
@ -282,7 +284,7 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
(c*(u2+v2)-w*(a*u+b*v))*omc+(a*v-b*u)*si,1.0)
proc rotateX*(angle:float):TMatrix3d {.noInit.}=
proc rotateX*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the x-axis with `angle` radians,
## which is also called a 'roll' matrix.
let
@ -294,7 +296,7 @@ proc rotateX*(angle:float):TMatrix3d {.noInit.}=
0,-s,c,0,
0,0,0,1)
proc rotateY*(angle:float):TMatrix3d {.noInit.}=
proc rotateY*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the y-axis with `angle` radians,
## which is also called a 'pitch' matrix.
let
@ -306,7 +308,7 @@ proc rotateY*(angle:float):TMatrix3d {.noInit.}=
s,0,c,0,
0,0,0,1)
proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
proc rotateZ*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the z-axis with `angle` radians,
## which is also called a 'yaw' matrix.
let
@ -318,7 +320,7 @@ proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
0,0,1,0,
0,0,0,1)
proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
proc isUniform*(m:Matrix3d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example)
## it cannot transform a sphere into an ellipsoid.
@ -341,7 +343,7 @@ proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
proc mirror*(planeperp:Vector3d):Matrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through origo. `planeperp` does not need to be normalized.
@ -365,7 +367,7 @@ proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
0,0,0,1)
proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
proc mirror*(org:Point3d,planeperp:Vector3d):Matrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through `org`. `planeperp` does not need to be normalized.
@ -400,7 +402,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
2*(cc*tz+bc*ty+ac*tx) ,1)
proc determinant*(m:TMatrix3d):float=
proc determinant*(m:Matrix3d):float=
## Computes the determinant of matrix `m`.
# This computation is gotten from ratsimp(optimize(determinant(m)))
@ -419,7 +421,7 @@ proc determinant*(m:TMatrix3d):float=
(O3*m.az-O5*m.ay+O6*m.ax)*m.bw
proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
proc inverse*(m:Matrix3d):Matrix3d {.noInit.}=
## Computes the inverse of matrix `m`. If the matrix
## determinant is zero, thus not invertible, a EDivByZero
## will be raised.
@ -461,7 +463,7 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
(-m.ax*O7+m.ay*O14-m.az*O18)/det , (m.ax*O10-m.ay*O16+m.az*O19)/det)
proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
proc equals*(m1:Matrix3d,m2:Matrix3d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`.
return
@ -482,42 +484,42 @@ proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
abs(m1.tz-m2.tz)<=tol and
abs(m1.tw-m2.tw)<=tol
proc `=~`*(m1,m2:TMatrix3d):bool=
proc `=~`*(m1,m2:Matrix3d):bool=
## Checks if `m1` and `m2` is approximately equal, using a
## tolerance of 1e-6.
equals(m1,m2)
proc transpose*(m:TMatrix3d):TMatrix3d {.noInit.}=
proc transpose*(m:Matrix3d):Matrix3d {.noInit.}=
## Returns the transpose of `m`
result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw)
proc getXAxis*(m:TMatrix3d):TVector3d {.noInit.}=
proc getXAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local x axis of `m`
result.x=m.ax
result.y=m.ay
result.z=m.az
proc getYAxis*(m:TMatrix3d):TVector3d {.noInit.}=
proc getYAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local y axis of `m`
result.x=m.bx
result.y=m.by
result.z=m.bz
proc getZAxis*(m:TMatrix3d):TVector3d {.noInit.}=
proc getZAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local y axis of `m`
result.x=m.cx
result.y=m.cy
result.z=m.cz
proc `$`*(m:TMatrix3d):string=
proc `$`*(m:Matrix3d):string=
## String representation of `m`
return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) &
"\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) &
"\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw)
proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)=
proc apply*(m:Matrix3d, x,y,z:var float, translate=false)=
## Applies transformation `m` onto `x` , `y` , `z` , optionally
## using the translation part of the matrix.
let
@ -535,18 +537,18 @@ proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)=
z+=m.tz
# ***************************************
# TVector3d implementation
# Vector3d implementation
# ***************************************
proc vector3d*(x,y,z:float):TVector3d=
proc vector3d*(x,y,z:float):Vector3d=
result.x=x
result.y=y
result.z=z
proc len*(v:TVector3d):float=
proc len*(v:Vector3d):float=
## Returns the length of the vector `v`.
sqrt(v.x*v.x+v.y*v.y+v.z*v.z)
proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} =
proc `len=`*(v:var Vector3d,newlen:float) {.noInit.} =
## Sets the length of the vector, keeping its direction.
## If the vector has zero length before changing it's length,
## an arbitrary vector of the requested length is returned.
@ -571,12 +573,12 @@ proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} =
v.z*=fac
proc sqrLen*(v:TVector3d):float {.inline.}=
proc sqrLen*(v:Vector3d):float {.inline.}=
## Computes the squared length of the vector, which is
## faster than computing the absolute length.
return v.x*v.x+v.y*v.y+v.z*v.z
proc `$` *(v:TVector3d):string=
proc `$` *(v:Vector3d):string=
## String representation of `v`
result=rtos(v.x)
result.add(",")
@ -584,7 +586,7 @@ proc `$` *(v:TVector3d):string=
result.add(",")
result.add(rtos(v.z))
proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} =
proc `&` *(v:Vector3d,m:Matrix3d):Vector3d {.noInit.} =
## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part
## of the matrix.
@ -601,7 +603,7 @@ proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} =
result.x=newx
proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
proc `&=` *(v:var Vector3d,m:Matrix3d) {.noInit.} =
## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part
## of the matrix.
@ -618,7 +620,7 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
v.y=newy
v.x=newx
proc transformNorm*(v:var TVector3d,m:TMatrix3d)=
proc transformNorm*(v:var Vector3d,m:Matrix3d)=
## Applies a normal direction transformation `m` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
@ -631,7 +633,7 @@ proc transformNorm*(v:var TVector3d,m:TMatrix3d)=
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=transpose(inverse(m))
proc transformInv*(v:var TVector3d,m:TMatrix3d)=
proc transformInv*(v:var Vector3d,m:Matrix3d)=
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the
## translational part of the matrix.
@ -642,7 +644,7 @@ proc transformInv*(v:var TVector3d,m:TMatrix3d)=
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=m.inverse
proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)=
proc transformNormInv*(vec:var Vector3d,m:Matrix3d)=
## Applies an inverse normal direction transformation `m` onto `v` in place.
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
@ -651,7 +653,7 @@ proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)=
# see vector2d:s equivalent for a deeper look how/why this works
vec&=m.transpose
proc tryNormalize*(v:var TVector3d):bool=
proc tryNormalize*(v:var Vector3d):bool=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned.
@ -666,13 +668,13 @@ proc tryNormalize*(v:var TVector3d):bool=
return true
proc normalize*(v:var TVector3d) {.inline.}=
proc normalize*(v:var Vector3d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
proc rotate*(vec:var Vector3d,angle:float,axis:Vector3d)=
## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## through origo.
@ -699,19 +701,19 @@ proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si
vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si
proc scale*(v:var TVector3d,s:float)=
proc scale*(v:var Vector3d,s:float)=
## Scales the vector in place with factor `s`
v.x*=s
v.y*=s
v.z*=s
proc stretch*(v:var TVector3d,sx,sy,sz:float)=
proc stretch*(v:var Vector3d,sx,sy,sz:float)=
## Scales the vector non uniformly with factors `sx` , `sy` , `sz`
v.x*=sx
v.y*=sy
v.z*=sz
proc mirror*(v:var TVector3d,planeperp:TVector3d)=
proc mirror*(v:var Vector3d,planeperp:Vector3d)=
## Computes the mirrored vector of `v` over the plane
## that has `planeperp` as normal direction.
## `planeperp` does not need to be normalized.
@ -735,7 +737,7 @@ proc mirror*(v:var TVector3d,planeperp:TVector3d)=
v.z= -2*(c*c*z+bc*y+ac*x)+z
proc `-` *(v:TVector3d):TVector3d=
proc `-` *(v:Vector3d):Vector3d=
## Negates a vector
result.x= -v.x
result.y= -v.y
@ -751,12 +753,12 @@ makeBinOpAssignVector(`-=`)
makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector3d):float {.inline.}=
proc dot*(v1,v2:Vector3d):float {.inline.}=
## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
proc cross*(v1,v2:TVector3d):TVector3d {.inline.}=
proc cross*(v1,v2:Vector3d):Vector3d {.inline.}=
## Computes the cross product of two vectors.
## The result is a vector which is perpendicular
## to the plane of `v1` and `v2`, which means
@ -766,16 +768,16 @@ proc cross*(v1,v2:TVector3d):TVector3d {.inline.}=
result.y = (v1.z * v2.x) - (v2.z * v1.x)
result.z = (v1.x * v2.y) - (v2.x * v1.y)
proc equals*(v1,v2:TVector3d,tol=1.0e-6):bool=
proc equals*(v1,v2:Vector3d,tol=1.0e-6):bool=
## Checks if two vectors approximately equals with a tolerance.
return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol
proc `=~` *(v1,v2:TVector3d):bool=
proc `=~` *(v1,v2:Vector3d):bool=
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(v1,v2)
proc angleTo*(v1,v2:TVector3d):float=
proc angleTo*(v1,v2:Vector3d):float=
## Returns the smallest angle between v1 and v2,
## which is in range 0-PI
var
@ -785,13 +787,13 @@ proc angleTo*(v1,v2:TVector3d):float=
return 0.0 # zero length vector has zero angle to any other vector
return safeArccos(dot(nv1,nv2))
proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
proc arbitraryAxis*(norm:Vector3d):Matrix3d {.noInit.}=
## Computes the rotation matrix that would transform
## world z vector into `norm`. The inverse of this matrix
## is useful to transform a planar 3d object to 2d space.
## This is the same algorithm used to interpret DXF and DWG files.
const lim=1.0/64.0
var ax,ay,az:TVector3d
var ax,ay,az:Vector3d
if abs(norm.x)<lim and abs(norm.y)<lim:
ax=cross(YAXIS,norm)
else:
@ -808,7 +810,7 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
az.x,az.y,az.z,0.0,
0.0,0.0,0.0,1.0)
proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
proc bisect*(v1,v2:Vector3d):Vector3d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length,
@ -851,25 +853,25 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
# ***************************************
# TPoint3d implementation
# Point3d implementation
# ***************************************
proc point3d*(x,y,z:float):TPoint3d=
proc point3d*(x,y,z:float):Point3d=
result.x=x
result.y=y
result.z=z
proc sqrDist*(a,b:TPoint3d):float=
proc sqrDist*(a,b:Point3d):float=
## Computes the squared distance between `a`and `b`
let dx=b.x-a.x
let dy=b.y-a.y
let dz=b.z-a.z
result=dx*dx+dy*dy+dz*dz
proc dist*(a,b:TPoint3d):float {.inline.}=
proc dist*(a,b:Point3d):float {.inline.}=
## Computes the absolute distance between `a`and `b`
result=sqrt(sqrDist(a,b))
proc `$` *(p:TPoint3d):string=
proc `$` *(p:Point3d):string=
## String representation of `p`
result=rtos(p.x)
result.add(",")
@ -877,14 +879,14 @@ proc `$` *(p:TPoint3d):string=
result.add(",")
result.add(rtos(p.z))
proc `&`*(p:TPoint3d,m:TMatrix3d):TPoint3d=
proc `&`*(p:Point3d,m:Matrix3d):Point3d=
## Concatenates a point `p` with a transform `m`,
## resulting in a new, transformed point.
result.z=m.cz*p.z+m.bz*p.y+m.az*p.x+m.tz
result.y=m.cy*p.z+m.by*p.y+m.ay*p.x+m.ty
result.x=m.cx*p.z+m.bx*p.y+m.ax*p.x+m.tx
proc `&=` *(p:var TPoint3d,m:TMatrix3d)=
proc `&=` *(p:var Point3d,m:Matrix3d)=
## Applies transformation `m` onto `p` in place.
let
x=p.x
@ -894,7 +896,7 @@ proc `&=` *(p:var TPoint3d,m:TMatrix3d)=
p.y=m.cy*z+m.by*y+m.ay*x+m.ty
p.z=m.cz*z+m.bz*y+m.az*x+m.tz
proc transformInv*(p:var TPoint3d,m:TMatrix3d)=
proc transformInv*(p:var Point3d,m:Matrix3d)=
## Applies the inverse of transformation `m` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised.
@ -903,48 +905,48 @@ proc transformInv*(p:var TPoint3d,m:TMatrix3d)=
p&=inverse(m)
proc `+`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
proc `+`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting
## in a new point.
result.x=p.x+v.x
result.y=p.y+v.y
result.z=p.z+v.z
proc `+=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
proc `+=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} =
## Adds a vector `v` to a point `p` in place.
p.x+=v.x
p.y+=v.y
p.z+=v.z
proc `-`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
proc `-`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting
## in a new point.
result.x=p.x-v.x
result.y=p.y-v.y
result.z=p.z-v.z
proc `-`*(p1,p2:TPoint3d):TVector3d {.noInit,inline.} =
proc `-`*(p1,p2:Point3d):Vector3d {.noInit,inline.} =
## Subtracts `p2`from `p1` resulting in a difference vector.
result.x=p1.x-p2.x
result.y=p1.y-p2.y
result.z=p1.z-p2.z
proc `-=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
proc `-=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x
p.y-=v.y
p.z-=v.z
proc equals(p1,p2:TPoint3d,tol=1.0e-6):bool {.inline.}=
proc equals(p1,p2:Point3d,tol=1.0e-6):bool {.inline.}=
## Checks if two points approximately equals with a tolerance.
return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol
proc `=~`*(p1,p2:TPoint3d):bool {.inline.}=
proc `=~`*(p1,p2:Point3d):bool {.inline.}=
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(p1,p2)
proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)=
proc rotate*(p:var Point3d,rad:float,axis:Vector3d)=
## Rotates point `p` in place `rad` radians about an axis
## passing through origo.
@ -954,7 +956,7 @@ proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)=
p.y=v.y
p.z=v.z
proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
proc rotate*(p:var Point3d,angle:float,org:Point3d,axis:Vector3d)=
## Rotates point `p` in place `rad` radians about an axis
## passing through `org`
@ -992,26 +994,26 @@ proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si
p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si
proc scale*(p:var TPoint3d,fac:float) {.inline.}=
proc scale*(p:var Point3d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin.
p.x*=fac
p.y*=fac
p.z*=fac
proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}=
proc scale*(p:var Point3d,fac:float,org:Point3d){.inline.}=
## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x
p.y=(p.y - org.y) * fac + org.y
p.z=(p.z - org.z) * fac + org.z
proc stretch*(p:var TPoint3d,facx,facy,facz:float){.inline.}=
proc stretch*(p:var Point3d,facx,facy,facz:float){.inline.}=
## Scales a point in place non uniformly `facx` , `facy` , `facz` times
## with world origo as origin.
p.x*=facx
p.y*=facy
p.z*=facz
proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}=
proc stretch*(p:var Point3d,facx,facy,facz:float,org:Point3d){.inline.}=
## Scales the point in place non uniformly `facx` , `facy` , `facz` times
## with `org` as origin.
p.x=(p.x - org.x) * facx + org.x
@ -1019,19 +1021,19 @@ proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}=
p.z=(p.z - org.z) * facz + org.z
proc move*(p:var TPoint3d,dx,dy,dz:float){.inline.}=
proc move*(p:var Point3d,dx,dy,dz:float){.inline.}=
## Translates a point `dx` , `dy` , `dz` in place.
p.x+=dx
p.y+=dy
p.z+=dz
proc move*(p:var TPoint3d,v:TVector3d){.inline.}=
proc move*(p:var Point3d,v:Vector3d){.inline.}=
## Translates a point with vector `v` in place.
p.x+=v.x
p.y+=v.y
p.z+=v.z
proc area*(a,b,c:TPoint3d):float {.inline.}=
proc area*(a,b,c:Point3d):float {.inline.}=
## Computes the area of the triangle thru points `a` , `b` and `c`
# The area of a planar 3d quadliteral is the magnitude of the cross

View file

@ -25,9 +25,9 @@
## # generate content:
## write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n")
## write(stdout, "<html><head><title>Test</title></head><body>\n")
## writeln(stdout, "your name: " & myData["name"])
## writeln(stdout, "your password: " & myData["password"])
## writeln(stdout, "</body></html>")
## writeLine(stdout, "your name: " & myData["name"])
## writeLine(stdout, "your password: " & myData["password"])
## writeLine(stdout, "</body></html>")
import strutils, os, strtabs, cookies
@ -387,7 +387,7 @@ var
proc getCookie*(name: string): TaintedString =
## Gets a cookie. If no cookie of `name` exists, "" is returned.
if gcookies == nil: gcookies = parseCookies(getHttpCookie())
result = TaintedString(gcookies[name])
result = TaintedString(gcookies.getOrDefault(name))
proc existsCookie*(name: string): bool =
## Checks if a cookie of `name` exists.

View file

@ -6,32 +6,32 @@ import unsigned, math, hashes
## Memory Utility Functions
proc newHeap*[T](): ptr T =
result = cast[ptr T](alloc0(sizeof(T)))
result = cast[ptr T](alloc0(sizeof(T)))
proc copyNew*[T](x: var T): ptr T =
var
size = sizeof(T)
mem = alloc(size)
copyMem(mem, x.addr, size)
var
size = sizeof(T)
mem = alloc(size)
copyMem(mem, x.addr, size)
return cast[ptr T](mem)
proc copyTo*[T](val: var T, dest: int) =
copyMem(pointer(dest), val.addr, sizeof(T))
proc copyTo*[T](val: var T, dest: int) =
copyMem(pointer(dest), val.addr, sizeof(T))
proc allocType*[T](): pointer = alloc(sizeof(T))
proc allocType*[T](): pointer = alloc(sizeof(T))
proc newShared*[T](): ptr T =
result = cast[ptr T](allocShared0(sizeof(T)))
result = cast[ptr T](allocShared0(sizeof(T)))
proc copyShared*[T](x: var T): ptr T =
var
size = sizeof(T)
mem = allocShared(size)
copyMem(mem, x.addr, size)
var
size = sizeof(T)
mem = allocShared(size)
copyMem(mem, x.addr, size)
return cast[ptr T](mem)
#------------------------------------------------------------------------------
## Pointer arithmetic
## Pointer arithmetic
proc `+`*(p: pointer, i: int): pointer {.inline.} =
cast[pointer](cast[int](p) + i)
@ -41,73 +41,74 @@ const
reProbeLimit = 12
minCopyWork = 4096
intSize = sizeof(int)
when sizeof(int) == 4: # 32bit
type
TRaw = range[0..1073741823]
type
Raw = range[0..1073741823]
## The range of uint values that can be stored directly in a value slot
## when on a 32 bit platform
{.deprecated: [TRaw: Raw].}
elif sizeof(int) == 8: # 64bit
type
TRaw = range[0..4611686018427387903]
Raw = range[0..4611686018427387903]
## The range of uint values that can be stored directly in a value slot
## when on a 64 bit platform
else:
{.deprecated: [TRaw: Raw].}
else:
{.error: "unsupported platform".}
type
TEntry = tuple
type
Entry = tuple
key: int
value: int
TEntryArr = ptr array[0..10_000_000, TEntry]
EntryArr = ptr array[0..10_000_000, Entry]
PConcTable[K,V] = ptr object {.pure.}
len: int
used: int
active: int
copyIdx: int
copyDone: int
next: PConcTable[K,V]
data: TEntryArr
next: PConcTable[K,V]
data: EntryArr
{.deprecated: [TEntry: Entry, TEntryArr: EntryArr.}
proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
expVal: int, match: bool): int
expVal: int, match: bool): int
#------------------------------------------------------------------------------
# Create a new table
proc newLFTable*[K,V](size: int = minTableSize): PConcTable[K,V] =
let
dataLen = max(nextPowerOfTwo(size), minTableSize)
dataSize = dataLen*sizeof(TEntry)
dataMem = allocShared0(dataSize)
let
dataLen = max(nextPowerOfTwo(size), minTableSize)
dataSize = dataLen*sizeof(Entry)
dataMem = allocShared0(dataSize)
tableSize = 7 * intSize
tableMem = allocShared0(tableSize)
table = cast[PConcTable[K,V]](tableMem)
table = cast[PConcTable[K,V]](tableMem)
table.len = dataLen
table.used = 0
table.active = 0
table.copyIdx = 0
table.copyDone = 0
table.next = nil
table.data = cast[TEntryArr](dataMem)
table.data = cast[EntryArr](dataMem)
result = table
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Delete a table
proc deleteConcTable[K,V](tbl: PConcTable[K,V]) =
deallocShared(tbl.data)
deallocShared(tbl.data)
deallocShared(tbl)
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
proc `[]`[K,V](table: var PConcTable[K,V], i: int): var TEntry {.inline.} =
proc `[]`[K,V](table: var PConcTable[K,V], i: int): var Entry {.inline.} =
table.data[i]
#------------------------------------------------------------------------------
@ -118,15 +119,15 @@ proc pack[T](x: T): int {.inline.} =
result = (cast[int](x) shl 2)
#echo("packKey ",cast[int](x) , " -> ", result)
# Pop the flags off returning a 4 byte aligned ptr to our Key or Val
# Pop the flags off returning a 4 byte aligned ptr to our Key or Val
proc pop(x: int): int {.inline.} =
result = x and 0xFFFFFFFC'i32
# Pop the raw value off of our Key or Val
# Pop the raw value off of our Key or Val
proc popRaw(x: int): int {.inline.} =
result = x shr 2
result = x shr 2
# Pop the flags off returning a 4 byte aligned ptr to our Key or Val
# Pop the flags off returning a 4 byte aligned ptr to our Key or Val
proc popPtr[V](x: int): ptr V {.inline.} =
result = cast[ptr V](pop(x))
#echo("popPtr " & $x & " -> " & $cast[int](result))
@ -135,34 +136,34 @@ proc popPtr[V](x: int): ptr V {.inline.} =
# K or V is no longer valid use new table
const Ghost = 0xFFFFFFFC
proc isGhost(x: int): bool {.inline.} =
result = x == 0xFFFFFFFC
result = x == 0xFFFFFFFC
# Tombstone
# applied to V = K is dead
proc isTomb(x: int): bool {.inline.} =
# Tombstone
# applied to V = K is dead
proc isTomb(x: int): bool {.inline.} =
result = (x and 0x00000002) != 0
proc setTomb(x: int): int {.inline.} =
result = x or 0x00000002
# Prime
# K or V is in new table copied from old
proc isPrime(x: int): bool {.inline.} =
# K or V is in new table copied from old
proc isPrime(x: int): bool {.inline.} =
result = (x and 0x00000001) != 0
proc setPrime(x: int): int {.inline.} =
result = x or 0x00000001
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
##This is for i32 only need to override for i64
proc hashInt(x: int):int {.inline.} =
var h = uint32(x) #shr 2'u32
proc hashInt(x: int):int {.inline.} =
var h = uint32(x) #shr 2'u32
h = h xor (h shr 16'u32)
h *= 0x85ebca6b'u32
h = h xor (h shr 13'u32)
h *= 0xc2b2ae35'u32
h = h xor (h shr 16'u32)
h = h xor (h shr 16'u32)
result = int(h)
#------------------------------------------------------------------------------
@ -174,31 +175,31 @@ proc resize[K,V](self: PConcTable[K,V]): PConcTable[K,V] =
#echo("A new table already exists, copy in progress")
return next
var
oldLen = atomic_load_n(self.len.addr, ATOMIC_RELAXED)
oldLen = atomic_load_n(self.len.addr, ATOMIC_RELAXED)
newTable = newLFTable[K,V](oldLen*2)
success = atomic_compare_exchange_n(self.next.addr, next.addr, newTable,
false, ATOMIC_RELAXED, ATOMIC_RELAXED)
if not success:
echo("someone beat us to it! delete table we just created and return his " & $cast[int](next))
deleteConcTable(newTable)
return next
deleteConcTable(newTable)
return next
else:
echo("Created New Table! " & $cast[int](newTable) & " Size = " & $newTable.len)
return newTable
#------------------------------------------------------------------------------
#proc keyEQ[K](key1: ptr K, key2: ptr K): bool {.inline.} =
proc keyEQ[K](key1: int, key2: int): bool {.inline.} =
#proc keyEQ[K](key1: ptr K, key2: ptr K): bool {.inline.} =
proc keyEQ[K](key1: int, key2: int): bool {.inline.} =
result = false
when K is TRaw:
if key1 == key2:
when K is Raw:
if key1 == key2:
result = true
else:
var
var
p1 = popPtr[K](key1)
p2 = popPtr[K](key2)
if p1 != nil and p2 != nil:
if p1 != nil and p2 != nil:
if cast[int](p1) == cast[int](p2):
return true
if p1[] == p2[]:
@ -213,53 +214,53 @@ proc keyEQ[K](key1: int, key2: int): bool {.inline.} =
proc copySlot[K,V](idx: int, oldTbl: var PConcTable[K,V], newTbl: var PConcTable[K,V]): bool =
#echo("Copy idx " & $idx)
var
var
oldVal = 0
oldkey = 0
oldkey = 0
ok = false
result = false
#Block the key so no other threads waste time here
while not ok:
ok = atomic_compare_exchange_n(oldTbl[idx].key.addr, oldKey.addr,
ok = atomic_compare_exchange_n(oldTbl[idx].key.addr, oldKey.addr,
setTomb(oldKey), false, ATOMIC_RELAXED, ATOMIC_RELAXED)
#echo("oldKey was = " & $oldKey & " set it to tomb " & $setTomb(oldKey))
#Prevent new values from appearing in the old table by priming
#echo("oldKey was = " & $oldKey & " set it to tomb " & $setTomb(oldKey))
#Prevent new values from appearing in the old table by priming
oldVal = atomic_load_n(oldTbl[idx].value.addr, ATOMIC_RELAXED)
while not isPrime(oldVal):
var box = if oldVal == 0 or isTomb(oldVal) : oldVal.setTomb.setPrime
else: oldVal.setPrime
if atomic_compare_exchange_n(oldTbl[idx].value.addr, oldVal.addr,
var box = if oldVal == 0 or isTomb(oldVal) : oldVal.setTomb.setPrime
else: oldVal.setPrime
if atomic_compare_exchange_n(oldTbl[idx].value.addr, oldVal.addr,
box, false, ATOMIC_RELAXED, ATOMIC_RELAXED):
if isPrime(box) and isTomb(box):
if isPrime(box) and isTomb(box):
return true
oldVal = box
break
#echo("oldVal was = ", oldVal, " set it to prime ", box)
if isPrime(oldVal) and isTomb(oldVal):
#when not (K is TRaw):
# deallocShared(popPtr[K](oldKey))
if isPrime(oldVal) and isTomb(oldVal):
#when not (K is Raw):
# deallocShared(popPtr[K](oldKey))
return false
if isTomb(oldVal):
if isTomb(oldVal):
echo("oldVal is Tomb!!!, should not happen")
if pop(oldVal) != 0:
if pop(oldVal) != 0:
result = setVal(newTbl, pop(oldKey), pop(oldVal), 0, true) == 0
if result:
#echo("Copied a Slot! idx= " & $idx & " key= " & $oldKey & " val= " & $oldVal)
else:
#echo("copy slot failed")
if result:
#echo("Copied a Slot! idx= " & $idx & " key= " & $oldKey & " val= " & $oldVal)
else:
#echo("copy slot failed")
# Our copy is done so we disable the old slot
while not ok:
ok = atomic_compare_exchange_n(oldTbl[idx].value.addr, oldVal.addr,
ok = atomic_compare_exchange_n(oldTbl[idx].value.addr, oldVal.addr,
oldVal.setTomb.setPrime , false, ATOMIC_RELAXED, ATOMIC_RELAXED)
#echo("disabled old slot")
#echo"---------------------"
#echo("disabled old slot")
#echo"---------------------"
#------------------------------------------------------------------------------
proc promote[K,V](table: var PConcTable[K,V]) =
var
newData = atomic_load_n(table.next.data.addr, ATOMIC_RELAXED)
newLen = atomic_load_n(table.next.len.addr, ATOMIC_RELAXED)
newLen = atomic_load_n(table.next.len.addr, ATOMIC_RELAXED)
newUsed = atomic_load_n(table.next.used.addr, ATOMIC_RELAXED)
deallocShared(table.data)
@ -269,52 +270,52 @@ proc promote[K,V](table: var PConcTable[K,V]) =
atomic_store_n(table.copyIdx.addr, 0, ATOMIC_RELAXED)
atomic_store_n(table.copyDone.addr, 0, ATOMIC_RELAXED)
deallocShared(table.next)
atomic_store_n(table.next.addr, nil, ATOMIC_RELAXED)
atomic_store_n(table.next.addr, nil, ATOMIC_RELAXED)
echo("new table swapped!")
#------------------------------------------------------------------------------
proc checkAndPromote[K,V](table: var PConcTable[K,V], workDone: int): bool =
var
proc checkAndPromote[K,V](table: var PConcTable[K,V], workDone: int): bool =
var
oldLen = atomic_load_n(table.len.addr, ATOMIC_RELAXED)
copyDone = atomic_load_n(table.copyDone.addr, ATOMIC_RELAXED)
ok: bool
result = false
result = false
if workDone > 0:
#echo("len to copy =" & $oldLen)
#echo("copyDone + workDone = " & $copyDone & " + " & $workDone)
#echo("copyDone + workDone = " & $copyDone & " + " & $workDone)
while not ok:
ok = atomic_compare_exchange_n(table.copyDone.addr, copyDone.addr,
ok = atomic_compare_exchange_n(table.copyDone.addr, copyDone.addr,
copyDone + workDone, false, ATOMIC_RELAXED, ATOMIC_RELAXED)
#if ok: echo("set copyDone")
# If the copy is done we can promote this table
#if ok: echo("set copyDone")
# If the copy is done we can promote this table
if copyDone + workDone >= oldLen:
# Swap new data
#echo("work is done!")
#echo("work is done!")
table.promote
result = true
#------------------------------------------------------------------------------
proc copySlotAndCheck[K,V](table: var PConcTable[K,V], idx: int):
PConcTable[K,V] =
var
newTable = cast[PConcTable[K,V]](atomic_load_n(table.next.addr, ATOMIC_RELAXED))
result = newTable
if newTable != nil and copySlot(idx, table, newTable):
#echo("copied a single slot, idx = " & $idx)
result = newTable
if newTable != nil and copySlot(idx, table, newTable):
#echo("copied a single slot, idx = " & $idx)
if checkAndPromote(table, 1): return table
#------------------------------------------------------------------------------
proc helpCopy[K,V](table: var PConcTable[K,V]): PConcTable[K,V] =
var
newTable = cast[PConcTable[K,V]](atomic_load_n(table.next.addr, ATOMIC_RELAXED))
result = newTable
if newTable != nil:
var
oldLen = atomic_load_n(table.len.addr, ATOMIC_RELAXED)
newTable = cast[PConcTable[K,V]](atomic_load_n(table.next.addr, ATOMIC_RELAXED))
result = newTable
if newTable != nil:
var
oldLen = atomic_load_n(table.len.addr, ATOMIC_RELAXED)
copyDone = atomic_load_n(table.copyDone.addr, ATOMIC_RELAXED)
copyIdx = 0
work = min(oldLen, minCopyWork)
@ -323,54 +324,54 @@ proc helpCopy[K,V](table: var PConcTable[K,V]): PConcTable[K,V] =
if copyDone < oldLen:
var ok: bool
while not ok:
ok = atomic_compare_exchange_n(table.copyIdx.addr, copyIdx.addr,
ok = atomic_compare_exchange_n(table.copyIdx.addr, copyIdx.addr,
copyIdx + work, false, ATOMIC_RELAXED, ATOMIC_RELAXED)
#echo("copy idx = ", copyIdx)
#echo("copy idx = ", copyIdx)
for i in 0..work-1:
var idx = (copyIdx + i) and (oldLen - 1)
var idx = (copyIdx + i) and (oldLen - 1)
if copySlot(idx, table, newTable):
workDone += 1
if workDone > 0:
#echo("did work ", workDone, " on thread ", cast[int](myThreadID[pointer]()))
if checkAndPromote(table, workDone): return table
# In case a thread finished all the work then got stalled before promotion
# In case a thread finished all the work then got stalled before promotion
if checkAndPromote(table, 0): return table
#------------------------------------------------------------------------------
proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
expVal: int, match: bool): int =
#echo("-try set- in table ", " key = ", (popPtr[K](key)[]), " val = ", val)
when K is TRaw:
var idx = hashInt(key)
#echo("-try set- in table ", " key = ", (popPtr[K](key)[]), " val = ", val)
when K is Raw:
var idx = hashInt(key)
else:
var idx = popPtr[K](key)[].hash
var
nextTable: PConcTable[K,V]
var idx = popPtr[K](key)[].hash
var
nextTable: PConcTable[K,V]
probes = 1
# spin until we find a key slot or build and jump to next table
while true:
idx = idx and (table.len - 1)
# spin until we find a key slot or build and jump to next table
while true:
idx = idx and (table.len - 1)
#echo("try set idx = " & $idx & "for" & $key)
var
probedKey = 0
openKey = atomic_compare_exchange_n(table[idx].key.addr, probedKey.addr,
key, false, ATOMIC_RELAXED, ATOMIC_RELAXED)
probedKey = 0
openKey = atomic_compare_exchange_n(table[idx].key.addr, probedKey.addr,
key, false, ATOMIC_RELAXED, ATOMIC_RELAXED)
if openKey:
if val.isTomb:
#echo("val was tomb, bail, no reason to set an open slot to tomb")
return val
#increment used slots
#echo("found an open slot, total used = " &
#increment used slots
#echo("found an open slot, total used = " &
#$atomic_add_fetch(table.used.addr, 1, ATOMIC_RELAXED))
discard atomic_add_fetch(table.used.addr, 1, ATOMIC_RELAXED)
break # We found an open slot
#echo("set idx ", idx, " key = ", key, " probed = ", probedKey)
break # We found an open slot
#echo("set idx ", idx, " key = ", key, " probed = ", probedKey)
if keyEQ[K](probedKey, key):
#echo("we found the matching slot")
break # We found a matching slot
#echo("we found the matching slot")
break # We found a matching slot
if (not(expVal != 0 and match)) and (probes >= reProbeLimit or key.isTomb):
if key.isTomb: echo("Key is Tombstone")
#if probes >= reProbeLimit: echo("Too much probing " & $probes)
@ -378,22 +379,22 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
#create next bigger table
nextTable = resize(table)
#help do some copying
#echo("help copy old table to new")
nextTable = helpCopy(table)
#echo("help copy old table to new")
nextTable = helpCopy(table)
#now setVal in the new table instead
#echo("jumping to next table to set val")
return setVal(nextTable, key, val, expVal, match)
#echo("jumping to next table to set val")
return setVal(nextTable, key, val, expVal, match)
else:
idx += 1
probes += 1
# Done spinning for a new slot
var oldVal = atomic_load_n(table[idx].value.addr, ATOMIC_RELAXED)
var oldVal = atomic_load_n(table[idx].value.addr, ATOMIC_RELAXED)
if val == oldVal:
#echo("this val is alredy in the slot")
#echo("this val is alredy in the slot")
return oldVal
nextTable = atomic_load_n(table.next.addr, ATOMIC_SEQ_CST)
if nextTable == nil and
((oldVal == 0 and
nextTable = atomic_load_n(table.next.addr, ATOMIC_SEQ_CST)
if nextTable == nil and
((oldVal == 0 and
(probes >= reProbeLimit or table.used / table.len > 0.8)) or
(isPrime(oldVal))):
if table.used / table.len > 0.8: echo("resize because usage ratio = " &
@ -401,7 +402,7 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
if isPrime(oldVal): echo("old val isPrime, should be a rare mem ordering event")
nextTable = resize(table)
if nextTable != nil:
#echo("tomb old slot then set in new table")
#echo("tomb old slot then set in new table")
nextTable = copySlotAndCheck(table,idx)
return setVal(nextTable, key, val, expVal, match)
# Finally ready to add new val to table
@ -409,7 +410,7 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
if match and oldVal != expVal:
#echo("set failed, no match oldVal= " & $oldVal & " expVal= " & $expVal)
return oldVal
if atomic_compare_exchange_n(table[idx].value.addr, oldVal.addr,
if atomic_compare_exchange_n(table[idx].value.addr, oldVal.addr,
val, false, ATOMIC_RELEASE, ATOMIC_RELAXED):
#echo("val set at table " & $cast[int](table))
if expVal != 0:
@ -426,82 +427,83 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
#------------------------------------------------------------------------------
proc getVal[K,V](table: var PConcTable[K,V], key: int): int =
proc getVal[K,V](table: var PConcTable[K,V], key: int): int =
#echo("-try get- key = " & $key)
when K is TRaw:
when K is Raw:
var idx = hashInt(key)
else:
var idx = popPtr[K](key)[].hash
#echo("get idx ", idx)
var
var idx = popPtr[K](key)[].hash
#echo("get idx ", idx)
var
probes = 0
val: int
val: int
while true:
idx = idx and (table.len - 1)
var
idx = idx and (table.len - 1)
var
newTable: PConcTable[K,V] # = atomic_load_n(table.next.addr, ATOMIC_ACQUIRE)
probedKey = atomic_load_n(table[idx].key.addr, ATOMIC_SEQ_CST)
probedKey = atomic_load_n(table[idx].key.addr, ATOMIC_SEQ_CST)
if keyEQ[K](probedKey, key):
#echo("found key after ", probes+1)
val = atomic_load_n(table[idx].value.addr, ATOMIC_ACQUIRE)
if not isPrime(val):
if isTomb(val):
#echo("val was tomb but not prime")
#echo("val was tomb but not prime")
return 0
else:
#echo("-GotIt- idx = ", idx, " key = ", key, " val ", val )
#echo("-GotIt- idx = ", idx, " key = ", key, " val ", val )
return val
else:
newTable = copySlotAndCheck(table, idx)
return getVal(newTable, key)
return getVal(newTable, key)
else:
#echo("probe ", probes, " idx = ", idx, " key = ", key, " found ", probedKey )
#echo("probe ", probes, " idx = ", idx, " key = ", key, " found ", probedKey )
if probes >= reProbeLimit*4 or key.isTomb:
if newTable == nil:
#echo("too many probes and no new table ", key, " ", idx )
return 0
else:
else:
newTable = helpCopy(table)
return getVal(newTable, key)
idx += 1
probes += 1
#------------------------------------------------------------------------------
#proc set*(table: var PConcTable[TRaw,TRaw], key: TRaw, val: TRaw) =
#proc set*(table: var PConcTable[Raw,Raw], key: Raw, val: Raw) =
# discard setVal(table, pack(key), pack(key), 0, false)
#proc set*[V](table: var PConcTable[TRaw,V], key: TRaw, val: ptr V) =
#proc set*[V](table: var PConcTable[Raw,V], key: Raw, val: ptr V) =
# discard setVal(table, pack(key), cast[int](val), 0, false)
proc set*[K,V](table: var PConcTable[K,V], key: var K, val: var V) =
when not (K is TRaw):
when not (K is Raw):
var newKey = cast[int](copyShared(key))
else:
else:
var newKey = pack(key)
when not (V is TRaw):
when not (V is Raw):
var newVal = cast[int](copyShared(val))
else:
else:
var newVal = pack(val)
var oldPtr = pop(setVal(table, newKey, newVal, 0, false))
#echo("oldPtr = ", cast[int](oldPtr), " newPtr = ", cast[int](newPtr))
when not (V is TRaw):
if newVal != oldPtr and oldPtr != 0:
when not (V is Raw):
if newVal != oldPtr and oldPtr != 0:
deallocShared(cast[ptr V](oldPtr))
proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
when not (V is TRaw):
when not (K is TRaw):
when not (V is Raw):
when not (K is Raw):
return popPtr[V](getVal(table, cast[int](key.addr)))[]
else:
else:
return popPtr[V](getVal(table, pack(key)))[]
else:
when not (K is TRaw):
when not (K is Raw):
return popRaw(getVal(table, cast[int](key.addr)))
else:
return popRaw(getVal(table, pack(key)))
else:
return popRaw(getVal(table, pack(key)))
@ -511,7 +513,6 @@ proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
#proc `[]`[K,V](table: var PConcTable[K,V], key: K): PEntry[K,V] {.inline.} =
# getVal(table, key)
@ -527,79 +528,80 @@ proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
#Tests ----------------------------
when not defined(testing) and isMainModule:
import locks, times, mersenne
const
const
numTests = 100000
numThreads = 10
type
TTestObj = tuple
thr: int
TestObj = tuple
thr: int
f0: int
f1: int
TData = tuple[k: string,v: TTestObj]
PDataArr = array[0..numTests-1, TData]
Dict = PConcTable[string,TTestObj]
var
thr: array[0..numThreads-1, TThread[Dict]]
table = newLFTable[string,TTestObj](8)
Data = tuple[k: string,v: TestObj]
PDataArr = array[0..numTests-1, Data]
Dict = PConcTable[string,TestObj]
{.deprecated: [TTestObj: TestObj, TData: Data].}
var
thr: array[0..numThreads-1, Thread[Dict]]
table = newLFTable[string,TestObj](8)
rand = newMersenneTwister(2525)
proc createSampleData(len: int): PDataArr =
#result = cast[PDataArr](allocShared0(sizeof(TData)*numTests))
proc createSampleData(len: int): PDataArr =
#result = cast[PDataArr](allocShared0(sizeof(Data)*numTests))
for i in 0..len-1:
result[i].k = "mark" & $(i+1)
#echo("mark" & $(i+1), " ", hash("mark" & $(i+1)))
#echo("mark" & $(i+1), " ", hash("mark" & $(i+1)))
result[i].v.thr = 0
result[i].v.f0 = i+1
result[i].v.f1 = 0
result[i].v.f0 = i+1
result[i].v.f1 = 0
#echo("key = " & $(i+1) & " Val ptr = " & $cast[int](result[i].v.addr))
proc threadProc(tp: Dict) {.thread.} =
var t = cpuTime();
proc threadProc(tp: Dict) {.thread.} =
var t = cpuTime();
for i in 1..numTests:
var key = "mark" & $(i)
var got = table.get(key)
var got = table.get(key)
got.thr = cast[int](myThreadID[pointer]())
got.f1 = got.f1 + 1
got.f1 = got.f1 + 1
table.set(key, got)
t = cpuTime() - t
echo t
echo t
var testData = createSampleData(numTests)
for i in 0..numTests-1:
table.set(testData[i].k, testData[i].v)
var i = 0
while i < numThreads:
createThread(thr[i], threadProc, table)
i += 1
joinThreads(thr)
joinThreads(thr)
var fails = 0
for i in 0..numTests-1:
var got = table.get(testData[i].k)
for i in 0..numTests-1:
var got = table.get(testData[i].k)
if got.f0 != i+1 or got.f1 != numThreads:
fails += 1
echo(got)
echo("Failed read or write = ", fails)
#for i in 1..numTests:
# echo(i, " = ", hashInt(i) and 8191)

View file

@ -11,17 +11,19 @@
## container for a set or a mapping of strings. Based on the excellent paper
## by Adam Langley.
include "system/inclrtl"
type
NodeObj[T] = object {.acyclic.}
byte: int ## byte index of the difference
otherbits: char
case isLeaf: bool
of false: child: array[0..1, ref NodeObj[T]]
of true:
of true:
key: string
when T isnot void:
val: T
Node[T] = ref NodeObj[T]
CritBitTree*[T] = object ## The crit bit tree can either be used
## as a mapping from strings to
@ -66,7 +68,7 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] =
let ch = if it.byte < key.len: key[it.byte] else: '\0'
let dir = (1 + (ch.ord or it.otherBits.ord)) shr 8
it = it.child[dir]
var newOtherBits = 0
var newByte = 0
block blockX:
@ -84,7 +86,7 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] =
newOtherBits = newOtherBits xor 255
let ch = it.key[newByte]
let dir = (1 + (ord(ch) or newOtherBits)) shr 8
var inner: Node[T]
new inner
new result
@ -93,7 +95,7 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] =
inner.otherBits = chr(newOtherBits)
inner.byte = newByte
inner.child[1 - dir] = result
var wherep = addr(c.root)
while true:
var p = wherep[]
@ -123,6 +125,14 @@ proc containsOrIncl*(c: var CritBitTree[void], key: string): bool =
var n = rawInsert(c, key)
result = c.count == oldCount
proc inc*(c: var CritBitTree[int]; key: string) =
## counts the 'key'.
let oldCount = c.count
var n = rawInsert(c, key)
if c.count == oldCount:
# not a new key:
inc n.val
proc incl*(c: var CritBitTree[void], key: string) =
## includes `key` in `c`.
discard rawInsert(c, key)
@ -132,20 +142,32 @@ proc `[]=`*[T](c: var CritBitTree[T], key: string, val: T) =
var n = rawInsert(c, key)
n.val = val
proc `[]`*[T](c: CritBitTree[T], key: string): T {.inline.} =
## retrieves the value at ``c[key]``. If `key` is not in `t`,
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
template get[T](c: CritBitTree[T], key: string): T {.immediate.} =
let n = rawGet(c, key)
if n != nil: result = n.val
else:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline.} =
proc `[]`*[T](c: CritBitTree[T], key: string): T {.inline, deprecatedGet.} =
## retrieves the value at ``c[key]``. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
## the key exists.
get(c, key)
proc `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline,
deprecatedGet.} =
## retrieves the value at ``c[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
let n = rawGet(c, key)
if n != nil: result = n.val
else: raise newException(KeyError, "key not found: " & $key)
get(c, key)
proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline, deprecated.} =
## retrieves the value at ``c[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
get(c, key)
proc excl*[T](c: var CritBitTree[T], key: string) =
## removes `key` (and its associated value) from the set `c`.
@ -176,7 +198,7 @@ iterator leaves[T](n: Node[T]): Node[T] =
# XXX actually we could compute the necessary stack size in advance:
# it's roughly log2(c.count).
var stack = @[n]
while stack.len > 0:
while stack.len > 0:
var it = stack.pop
while not it.isLeaf:
stack.add(it.child[1])
@ -205,7 +227,7 @@ iterator items*[T](c: CritBitTree[T]): string =
iterator pairs*[T](c: CritBitTree[T]): tuple[key: string, val: T] =
## yields all (key, value)-pairs of `c`.
for x in leaves(c.root): yield (x.key, x.val)
iterator mpairs*[T](c: var CritBitTree[T]): tuple[key: string, val: var T] =
## yields all (key, value)-pairs of `c`. The yielded values can be modified.
for x in leaves(c.root): yield (x.key, x.val)
@ -251,7 +273,7 @@ iterator pairsWithPrefix*[T](c: CritBitTree[T],
## yields all (key, value)-pairs of `c` starting with `prefix`.
let top = allprefixedAux(c, prefix)
for x in leaves(top): yield (x.key, x.val)
iterator mpairsWithPrefix*[T](c: var CritBitTree[T],
prefix: string): tuple[key: string, val: var T] =
## yields all (key, value)-pairs of `c` starting with `prefix`.

View file

@ -30,25 +30,25 @@ const
IntMask = 1 shl IntShift - 1
type
PTrunk = ref TTrunk
TTrunk {.final.} = object
PTrunk = ref Trunk
Trunk {.final.} = object
next: PTrunk # all nodes are connected with this pointer
key: int # start address at bit 0
bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector
TTrunkSeq = seq[PTrunk]
TrunkSeq = seq[PTrunk]
IntSet* = object ## an efficient set of 'int' implemented as a sparse bit set
counter, max: int
head: PTrunk
data: TTrunkSeq
data: TrunkSeq
{.deprecated: [TIntSet: IntSet].}
{.deprecated: [TIntSet: IntSet, TTrunk: Trunk, TTrunkSeq: TrunkSeq].}
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc intSetGet(t: IntSet, key: int): PTrunk =
@ -59,7 +59,7 @@ proc intSetGet(t: IntSet, key: int): PTrunk =
h = nextTry(h, t.max)
result = nil
proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) =
proc intSetRawInsert(t: IntSet, data: var TrunkSeq, desc: PTrunk) =
var h = desc.key and t.max
while data[h] != nil:
assert(data[h] != desc)
@ -68,7 +68,7 @@ proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) =
data[h] = desc
proc intSetEnlarge(t: var IntSet) =
var n: TTrunkSeq
var n: TrunkSeq
var oldMax = t.max
t.max = ((t.max + 1) * 2) - 1
newSeq(n, t.max + 1)
@ -138,6 +138,8 @@ proc initIntSet*: IntSet =
result.counter = 0
result.head = nil
proc isNil*(x: IntSet): bool {.inline.} = x.head.isNil
proc assign*(dest: var IntSet, src: IntSet) =
## copies `src` to `dest`. `dest` does not need to be initialized by
## `initIntSet`.

View file

@ -27,18 +27,18 @@ type
SinglyLinkedList*[T] = object ## a singly linked list
head*, tail*: SinglyLinkedNode[T]
DoublyLinkedList*[T] = object ## a doubly linked list
head*, tail*: DoublyLinkedNode[T]
SinglyLinkedRing*[T] = object ## a singly linked ring
head*, tail*: SinglyLinkedNode[T]
DoublyLinkedRing*[T] = object ## a doubly linked ring
head*: DoublyLinkedNode[T]
{.deprecated: [TDoublyLinkedNode: DoublyLinkedNodeObj,
PDoublyLinkedNode: DoublyLinkedNode,
PDoublyLinkedNode: DoublyLinkedNode,
TSinglyLinkedNode: SinglyLinkedNodeObj,
PSinglyLinkedNode: SinglyLinkedNode,
TDoublyLinkedList: DoublyLinkedList,
@ -106,19 +106,19 @@ 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: DoublyLinkedList[T]): T =
## yields every value of `L`.
itemsListImpl()
iterator items*[T](L: SinglyLinkedList[T]): T =
iterator items*[T](L: SinglyLinkedList[T]): T =
## yields every value of `L`.
itemsListImpl()
iterator items*[T](L: SinglyLinkedRing[T]): T =
iterator items*[T](L: SinglyLinkedRing[T]): T =
## yields every value of `L`.
itemsRingImpl()
iterator items*[T](L: DoublyLinkedRing[T]): T =
iterator items*[T](L: DoublyLinkedRing[T]): T =
## yields every value of `L`.
itemsRingImpl()
@ -138,22 +138,22 @@ 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] =
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] =
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: SinglyLinkedRing[T]): SinglyLinkedNode[T] =
## iterates over every node of `x`. Removing the current node from the
## list during traversal is supported.
nodesRingImpl()
iterator nodes*[T](L: DoublyLinkedRing[T]): DoublyLinkedNode[T] =
iterator nodes*[T](L: DoublyLinkedRing[T]): DoublyLinkedNode[T] =
## iterates over every node of `x`. Removing the current node from the
## list during traversal is supported.
nodesRingImpl()
@ -165,87 +165,87 @@ template dollarImpl() {.dirty.} =
result.add($x.value)
result.add("]")
proc `$`*[T](L: SinglyLinkedList[T]): string =
proc `$`*[T](L: SinglyLinkedList[T]): string =
## turns a list into its string representation.
dollarImpl()
proc `$`*[T](L: DoublyLinkedList[T]): string =
proc `$`*[T](L: DoublyLinkedList[T]): string =
## turns a list into its string representation.
dollarImpl()
proc `$`*[T](L: SinglyLinkedRing[T]): string =
proc `$`*[T](L: SinglyLinkedRing[T]): string =
## turns a list into its string representation.
dollarImpl()
proc `$`*[T](L: DoublyLinkedRing[T]): string =
proc `$`*[T](L: DoublyLinkedRing[T]): string =
## turns a list into its string representation.
dollarImpl()
proc find*[T](L: SinglyLinkedList[T], value: T): SinglyLinkedNode[T] =
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: DoublyLinkedList[T], value: T): DoublyLinkedNode[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] =
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] =
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.} =
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.} =
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.} =
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
proc contains*[T](L: DoublyLinkedRing[T], value: T): bool {.inline.} =
proc contains*[T](L: DoublyLinkedRing[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 prepend*[T](L: var SinglyLinkedList[T],
n: SinglyLinkedNode[T]) {.inline.} =
proc prepend*[T](L: var SinglyLinkedList[T],
n: SinglyLinkedNode[T]) {.inline.} =
## prepends a node to `L`. Efficiency: O(1).
n.next = L.head
L.head = n
proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
## prepends a node to `L`. Efficiency: O(1).
prepend(L, newSinglyLinkedNode(value))
proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## appends a node `n` to `L`. Efficiency: O(1).
n.next = nil
n.prev = L.tail
if L.tail != nil:
if L.tail != nil:
assert(L.tail.next == nil)
L.tail.next = n
L.tail = n
if L.head == nil: L.head = n
proc append*[T](L: var DoublyLinkedList[T], value: T) =
proc append*[T](L: var DoublyLinkedList[T], value: T) =
## appends a value to `L`. Efficiency: O(1).
append(L, newDoublyLinkedNode(value))
proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## prepends a node `n` to `L`. Efficiency: O(1).
n.prev = nil
n.next = L.head
@ -255,11 +255,11 @@ proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
L.head = n
if L.tail == nil: L.tail = n
proc prepend*[T](L: var DoublyLinkedList[T], value: T) =
proc prepend*[T](L: var DoublyLinkedList[T], value: T) =
## prepends a value to `L`. Efficiency: O(1).
prepend(L, newDoublyLinkedNode(value))
proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## removes `n` from `L`. Efficiency: O(1).
if n == L.tail: L.tail = n.prev
if n == L.head: L.head = n.next
@ -267,7 +267,7 @@ proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
if n.prev != nil: n.prev.next = n.next
proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
## appends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
n.next = L.head
@ -279,11 +279,11 @@ proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
L.head = n
L.tail = n
proc append*[T](L: var SinglyLinkedRing[T], value: T) =
proc append*[T](L: var SinglyLinkedRing[T], value: T) =
## appends a value to `L`. Efficiency: O(1).
append(L, newSinglyLinkedNode(value))
proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
## prepends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
n.next = L.head
@ -294,11 +294,11 @@ proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
L.tail = n
L.head = n
proc prepend*[T](L: var SinglyLinkedRing[T], value: T) =
proc prepend*[T](L: var SinglyLinkedRing[T], value: T) =
## prepends a value to `L`. Efficiency: O(1).
prepend(L, newSinglyLinkedNode(value))
proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
## appends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
n.next = L.head
@ -310,13 +310,13 @@ proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
n.next = n
L.head = n
proc append*[T](L: var DoublyLinkedRing[T], value: T) =
proc append*[T](L: var DoublyLinkedRing[T], value: T) =
## appends a value to `L`. Efficiency: O(1).
append(L, newDoublyLinkedNode(value))
proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
## prepends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
if L.head != nil:
n.next = L.head
n.prev = L.head.prev
L.head.prev.next = n
@ -326,17 +326,17 @@ proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
n.next = n
L.head = n
proc prepend*[T](L: var DoublyLinkedRing[T], value: T) =
proc prepend*[T](L: var DoublyLinkedRing[T], value: T) =
## prepends a value to `L`. Efficiency: O(1).
prepend(L, newDoublyLinkedNode(value))
proc remove*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
proc remove*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) =
## removes `n` from `L`. Efficiency: O(1).
n.next.prev = n.prev
n.prev.next = n.next
if n == L.head:
if n == L.head:
var p = L.head.prev
if p == L.head:
if p == L.head:
# only one element left:
L.head = nil
else:

View file

@ -77,7 +77,7 @@ proc dequeue*[T](q: var Queue[T]): T =
result = q.data[q.rd]
q.rd = (q.rd + 1) and q.mask
proc `$`*[T](q: Queue[T]): string =
proc `$`*[T](q: Queue[T]): string =
## turns a queue into its string representation.
result = "["
for x in items(q):
@ -95,7 +95,7 @@ when isMainModule:
q.add(6)
var second = q.dequeue
q.add(789)
assert first == 123
assert second == 9
assert($q == "[4, 56, 6, 789]")

View file

@ -1,13 +1,13 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2011 Alex Mitchell
# (c) Copyright 2011 Alexander Mitchell-Robinson
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## :Author: Alex Mitchell
## :Author: Alexander Mitchell-Robinson (Amrykid)
##
## This module implements operations for the built-in `seq`:idx: type which
## were inspired by functional programming languages. If you are looking for
@ -47,7 +47,7 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
result[i] = itm
inc(i)
proc repeat*[T](s: seq[T], n: Natural): seq[T] =
proc cycle*[T](s: seq[T], n: Natural): seq[T] =
## Returns a new sequence with the items of `s` repeated `n` times.
##
## Example:
@ -56,15 +56,29 @@ proc repeat*[T](s: seq[T], n: Natural): seq[T] =
##
## let
## s = @[1, 2, 3]
## total = s.repeat(3)
## total = s.cycle(3)
## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3]
result = newSeq[T](n * s.len)
var o = 0
for x in 1..n:
for x in 0..<n:
for e in s:
result[o] = e
inc o
proc repeat*[T](x: T, n: Natural): seq[T] =
## Returns a new sequence with the item `x` repeated `n` times.
##
## Example:
##
## .. code-block:
##
## let
## total = repeat(5, 3)
## assert total == @[5, 5, 5]
result = newSeq[T](n)
for i in 0..<n:
result[i] = x
proc deduplicate*[T](seq1: seq[T]): seq[T] =
## Returns a new sequence without duplicates.
##
@ -169,6 +183,77 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] =
first = last
proc map*[T, S](data: 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`.
##
## 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:
##
## .. 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])
proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.})
{.deprecated.} =
## Applies `op` to every item in `data` 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:
##
## .. code-block:: nim
## var a = @["1", "2", "3", "4"]
## echo repr(a)
## # --> ["1", "2", "3", "4"]
## map(a, proc(x: var string) = x &= "42")
## 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])
proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.})
{.inline.} =
## Applies `op` to every item in `data` 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
## var a = @["1", "2", "3", "4"]
## echo repr(a)
## # --> ["1", "2", "3", "4"]
## map(a, proc(x: var string) = x &= "42")
## echo repr(a)
## # --> ["142", "242", "342", "442"]
##
for i in 0..data.len-1: op(data[i])
proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.})
{.inline.} =
## Applies `op` to every item in `data` 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
## var a = @["1", "2", "3", "4"]
## echo repr(a)
## # --> ["1", "2", "3", "4"]
## map(a, proc(x: string): string = x & "42")
## echo repr(a)
## # --> ["142", "242", "342", "442"]
##
for i in 0..data.len-1: data[i] = op(data[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
@ -181,11 +266,12 @@ 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 countup(0, len(seq1)-1):
var item = seq1[i]
if pred(item): yield seq1[i]
for i in 0..<seq1.len:
if pred(seq1[i]):
yield seq1[i]
proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] =
proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T]
{.inline.} =
## Returns a new sequence with all the items that fulfilled the predicate.
##
## Example:
@ -197,9 +283,13 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] =
## f2 = filter(colors) do (x: string) -> bool : x.len > 5
## assert f1 == @["red", "black"]
## assert f2 == @["yellow"]
accumulateResult(filter(seq1, pred))
result = newSeq[T]()
for i in 0..<seq1.len:
if pred(seq1[i]):
result.add(seq1[i])
proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) =
proc keepIf*[T](seq1: var seq[T], pred: proc(item: 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.
##
@ -213,11 +303,11 @@ proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) =
for i in 0 .. <len(seq1):
if pred(seq1[i]):
if pos != i:
seq1[pos] = seq1[i]
shallowCopy(seq1[pos], seq1[i])
inc(pos)
setLen(seq1, pos)
proc delete*[T](s: var seq[T], first=0, last=0) =
proc delete*[T](s: var seq[T]; first, last: Natural) =
## Deletes in `s` the items at position `first` .. `last`. This modifies
## `s` itself, it does not return a copy.
##
@ -268,7 +358,7 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) =
inc(j)
template filterIt*(seq1, pred: expr): expr {.immediate.} =
template filterIt*(seq1, pred: expr): expr =
## Returns a new sequence with all the items that fulfilled the predicate.
##
## Unlike the `proc` version, the predicate needs to be an expression using
@ -282,12 +372,12 @@ template filterIt*(seq1, pred: expr): expr {.immediate.} =
## 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 {.gensym.}: type(seq1) = @[]
var result {.gensym.} = newSeq[type(seq1[0])]()
for it {.inject.} in items(seq1):
if pred: result.add(it)
result
template keepItIf*(varSeq, pred: expr) =
template keepItIf*(varSeq: seq, pred: expr) =
## Convenience template around the ``keepIf`` proc to reduce typing.
##
## Unlike the `proc` version, the predicate needs to be an expression using
@ -303,10 +393,71 @@ template keepItIf*(varSeq, pred: expr) =
let it {.inject.} = varSeq[i]
if pred:
if pos != i:
varSeq[pos] = varSeq[i]
shallowCopy(varSeq[pos], varSeq[i])
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
## predicate.
##
## Example:
##
## .. code-block::
## 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:
if not pred(i):
return false
return true
template allIt*(seq1, pred: expr): bool {.immediate.} =
## Checks if every item fulfills the predicate.
##
## Example:
##
## .. code-block::
## let numbers = @[1, 4, 5, 8, 9, 7, 4]
## assert allIt(numbers, it < 10) == true
## assert allIt(numbers, it < 9) == false
var result {.gensym.} = true
for it {.inject.} in items(seq1):
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
## predicate.
##
## Example:
##
## .. code-block::
## 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:
if pred(i):
return true
return false
template anyIt*(seq1, pred: expr): bool {.immediate.} =
## Checks if some item fulfills the predicate.
##
## Example:
##
## .. code-block::
## let numbers = @[1, 4, 5, 8, 9, 7, 4]
## assert anyIt(numbers, it > 8) == true
## assert anyIt(numbers, it > 9) == false
var result {.gensym.} = false
for it {.inject.} in items(seq1):
if pred:
result = true
break
result
template toSeq*(iter: expr): expr {.immediate.} =
## Transforms any iterator into a sequence.
@ -320,14 +471,19 @@ template toSeq*(iter: expr): expr {.immediate.} =
## if x mod 2 == 1:
## result = true)
## assert odd_numbers == @[1, 3, 5, 7, 9]
##
## **Note**: Since this is an immediate macro, you cannot always invoke this
## as ``x.toSeq``, depending on the ``x``.
## See `this <manual.html#limitations-of-the-method-call-syntax>`_
## for an explanation.
var result {.gensym.}: seq[type(iter)] = @[]
for x in iter: add(result, x)
result
when compiles(iter.len):
var i = 0
var result = newSeq[type(iter)](iter.len)
for x in iter:
result[i] = x
inc i
result
else:
var result: seq[type(iter)] = @[]
for x in iter:
result.add(x)
result
template foldl*(sequence, operation: expr): expr =
## Template to fold a sequence from left to right, returning the accumulation.
@ -358,7 +514,7 @@ template foldl*(sequence, operation: expr): expr =
assert sequence.len > 0, "Can't fold empty sequences"
var result {.gensym.}: type(sequence[0])
result = sequence[0]
for i in countup(1, sequence.len - 1):
for i in 1..<sequence.len:
let
a {.inject.} = result
b {.inject.} = sequence[i]
@ -401,7 +557,7 @@ template foldr*(sequence, operation: expr): expr =
result = operation
result
template mapIt*(seq1, typ, op: expr): expr =
template mapIt*(seq1, typ, op: expr): expr {.deprecated.}=
## Convenience template around the ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@ -414,13 +570,45 @@ template mapIt*(seq1, typ, op: expr): expr =
## nums = @[1, 2, 3, 4]
## strings = nums.mapIt(string, $(4 * it))
## assert strings == @["4", "8", "12", "16"]
## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)``
## template instead.
var result {.gensym.}: seq[typ] = @[]
for it {.inject.} in items(seq1):
result.add(op)
result
template mapIt*(varSeq, op: expr) =
## Convenience template around the mutable ``map`` proc to reduce typing.
template mapIt*(seq1, op: expr): expr =
## Convenience template around the ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
## expression. Example:
##
## .. code-block::
## let
## nums = @[1, 2, 3, 4]
## strings = nums.mapIt($(4 * it))
## assert strings == @["4", "8", "12", "16"]
type outType = type((
block:
var it{.inject.}: type(items(seq1));
op))
var result: seq[outType]
when compiles(seq1.len):
let s = seq1
var i = 0
result = newSeq[outType](s.len)
for it {.inject.} in s:
result[i] = op
i += 1
else:
result = @[]
for it {.inject.} in seq1:
result.add(op)
result
template applyIt*(varSeq, op: expr) =
## Convenience template around the mutable ``apply`` proc to reduce typing.
##
## 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
@ -428,12 +616,14 @@ template mapIt*(varSeq, op: expr) =
##
## .. code-block::
## var nums = @[1, 2, 3, 4]
## nums.mapIt(it * 3)
## nums.applyIt(it * 3)
## assert nums[0] + nums[3] == 15
for i in 0 .. <len(varSeq):
for i in 0 .. <varSeq.len:
let it {.inject.} = varSeq[i]
varSeq[i] = op
template newSeqWith*(len: int, init: expr): expr =
## creates a new sequence, calling `init` to initialize each value. Example:
##
@ -513,6 +703,38 @@ 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]
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
block: # allIt
let
numbers = @[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
block: # toSeq test
let
numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
@ -568,8 +790,8 @@ when isMainModule:
block: # mapIt tests
var
nums = @[1, 2, 3, 4]
strings = nums.mapIt(string, $(4 * it))
nums.mapIt(it * 3)
strings = nums.mapIt($(4 * it))
nums.applyIt(it * 3)
assert nums[0] + nums[3] == 15
block: # distribute tests
@ -605,15 +827,19 @@ when isMainModule:
seq2D[0][1] = true
doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]]
block: # repeat tests
block: # cycle tests
let
a = @[1, 2, 3]
b: seq[int] = @[]
doAssert a.repeat(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3]
doAssert a.repeat(0) == @[]
#doAssert a.repeat(-1) == @[] # will not compile!
doAssert b.repeat(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) == @[]
block: # repeat tests
assert repeat(10, 5) == @[10, 10, 10, 10, 10]
assert repeat(@[1,2,3], 2) == @[@[1,2,3], @[1,2,3]]
when not defined(testing):
echo "Finished doc tests"

View file

@ -29,7 +29,7 @@ when not defined(nimhygiene):
# codes should never be needed, and this can pack more entries per cache-line.
# Losing hcode entirely is also possible - if some element value is forbidden.
type
KeyValuePair[A] = tuple[hcode: THash, key: A]
KeyValuePair[A] = tuple[hcode: Hash, key: A]
KeyValuePairSeq[A] = seq[KeyValuePair[A]]
HashSet* {.myShallow.}[A] = object ## \
## A generic hash set.
@ -43,10 +43,10 @@ type
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: THash): bool {.inline.} =
proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: THash): bool {.inline.} =
proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0
proc isValid*[A](s: HashSet[A]): bool =
@ -58,7 +58,7 @@ proc isValid*[A](s: HashSet[A]): bool =
## initialized. Example:
##
## .. code-block ::
## proc savePreferences(options: TSet[string]) =
## proc savePreferences(options: Set[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = not s.data.isNil
@ -72,7 +72,7 @@ proc len*[A](s: HashSet[A]): int =
##
## .. code-block::
##
## var values: TSet[int]
## var values: Set[int]
## assert(not values.isValid)
## assert values.len == 0
result = s.counter
@ -123,15 +123,15 @@ proc rightSize*(count: Natural): int {.inline.} =
## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(s.data) # start with real hash value
var h: Hash = hc and high(s.data) # start with real hash value
while isFilled(s.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra THash cmp&and..usually
# It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if s.data[h].hcode == hc and s.data[h].key == key: # compare hc THEN key
return h
@ -148,23 +148,30 @@ template rawInsertImpl() {.dirty.} =
data[h].key = key
data[h].hcode = hc
proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: THash): int {.inline.} =
proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: HashSet[A], key: A, hc: var THash): int {.inline.} =
proc rawGet[A](s: HashSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc mget*[A](s: var HashSet[A], key: A): var A =
proc `[]`*[A](s: var HashSet[A], key: A): var A =
## returns the element that is actually stored in 's' which has the same
## value as 'key' or raises the ``EInvalidKey`` exception. This is useful
## value as 'key' or raises the ``KeyError`` exception. This is useful
## when one overloaded 'hash' and '==' but still needs reference semantics
## for sharing.
assert s.isValid, "The set needs to be initialized."
var hc: THash
var hc: Hash
var index = rawGet(s, key, hc)
if index >= 0: result = s.data[index].key
else: raise newException(KeyError, "key not found: " & $key)
proc mget*[A](s: var HashSet[A], key: A): var A {.deprecated.} =
## returns the element that is actually stored in 's' which has the same
## value as 'key' or raises the ``KeyError`` exception. This is useful
## when one overloaded 'hash' and '==' but still needs reference semantics
## for sharing. Use ```[]``` instead.
s[key]
proc contains*[A](s: HashSet[A], key: A): bool =
## Returns true iff `key` is in `s`.
##
@ -178,12 +185,12 @@ proc contains*[A](s: HashSet[A], key: A): bool =
## values.excl(2)
## assert(not values.contains(2))
assert s.isValid, "The set needs to be initialized."
var hc: THash
var hc: Hash
var index = rawGet(s, key, hc)
result = index >= 0
proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A,
hc: THash, h: THash) =
hc: Hash, h: Hash) =
rawInsertImpl()
proc enlarge[A](s: var HashSet[A]) =
@ -196,7 +203,7 @@ proc enlarge[A](s: var HashSet[A]) =
rawInsert(s, s.data, n[i].key, n[i].hcode, j)
template inclImpl() {.dirty.} =
var hc: THash
var hc: Hash
var index = rawGet(s, key, hc)
if index < 0:
if mustRehash(len(s.data), s.counter):
@ -206,7 +213,7 @@ template inclImpl() {.dirty.} =
inc(s.counter)
template containsOrInclImpl() {.dirty.} =
var hc: THash
var hc: Hash
var index = rawGet(s, key, hc)
if index >= 0:
result = true
@ -261,7 +268,7 @@ proc excl*[A](s: var HashSet[A], key: A) =
## s.excl(2)
## assert s.len == 3
assert s.isValid, "The set needs to be initialized."
var hc: THash
var hc: Hash
var i = rawGet(s, key, hc)
var msk = high(s.data)
if i >= 0:
@ -323,7 +330,7 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
##
## .. code-block ::
## var a: TSet[int]
## var a: Set[int]
## a.init(4)
## a.incl(2)
## a.init
@ -552,7 +559,7 @@ proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] =
type
OrderedKeyValuePair[A] = tuple[
hcode: THash, next: int, key: A]
hcode: Hash, next: int, key: A]
OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]]
OrderedSet* {.myShallow.}[A] = object ## \
## A generic hash set that remembers insertion order.
@ -574,7 +581,7 @@ proc isValid*[A](s: OrderedSet[A]): bool =
## correctly initialized. Example:
##
## .. code-block::
## proc saveTarotCards(cards: TOrderedSet[int]) =
## proc saveTarotCards(cards: OrderedSet[int]) =
## assert cards.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = not s.data.isNil
@ -588,7 +595,7 @@ proc len*[A](s: OrderedSet[A]): int {.inline.} =
##
## .. code-block::
##
## var values: TOrderedSet[int]
## var values: OrderedSet[int]
## assert(not values.isValid)
## assert values.len == 0
result = s.counter
@ -629,10 +636,10 @@ iterator items*[A](s: OrderedSet[A]): A =
forAllOrderedPairs:
yield s.data[h].key
proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: THash): int {.inline.} =
proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: OrderedSet[A], key: A, hc: var THash): int {.inline.} =
proc rawGet[A](s: OrderedSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc contains*[A](s: OrderedSet[A], key: A): bool =
@ -646,12 +653,12 @@ proc contains*[A](s: OrderedSet[A], key: A): bool =
## values.incl(2)
## assert values.contains(2)
assert s.isValid, "The set needs to be initialized."
var hc: THash
var hc: Hash
var index = rawGet(s, key, hc)
result = index >= 0
proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A],
key: A, hc: THash, h: THash) =
key: A, hc: Hash, h: Hash) =
rawInsertImpl()
data[h].next = -1
if s.first < 0: s.first = h
@ -729,7 +736,7 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) =
## from an ordered hash set. Example:
##
## .. code-block ::
## var a: TOrderedSet[int]
## var a: OrderedSet[int]
## a.init(4)
## a.incl(2)
## a.init

View file

@ -0,0 +1,154 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Shared string support for Nim.
const ArrayDummySize = when defined(cpu16): 10_000 else: 100_000_000
type
UncheckedCharArray {.unchecked.} = array[0..ArrayDummySize, char]
type
Buffer = ptr object
refcount: int
capacity, realLen: int
data: UncheckedCharArray
SharedString* = object ## A string that can be shared. Slicing is O(1).
buffer: Buffer
first, len: int
proc decRef(b: Buffer) {.inline.} =
if atomicDec(b.refcount) <= 0:
deallocShared(b)
proc incRef(b: Buffer) {.inline.} =
atomicInc(b.refcount)
{.experimental.}
proc `=destroy`*(s: SharedString) =
#echo "destroyed"
if not s.buffer.isNil:
decRef(s.buffer)
when false:
proc `=`*(dest: var SharedString; src: SharedString) =
incRef(src.buffer)
if not dest.buffer.isNil:
decRef(dest.buffer)
dest.buffer = src.buffer
dest.first = src.first
dest.len = src.len
proc len*(s: SharedString): int = s.len
proc `[]`*(s: SharedString; i: Natural): char =
if i < s.len: result = s.buffer.data[i+s.first]
else: raise newException(IndexError, "index out of bounds")
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 =
#incRef(src.buffer)
if ab.a < s.len:
result.buffer = s.buffer
result.first = ab.a
result.len = min(s.len, ab.b - ab.a + 1)
# else: produce empty string ;-)
proc newBuffer(cap, len: int): Buffer =
assert cap >= len
result = cast[Buffer](allocShared0(sizeof(int)*3 + cap))
result.refcount = 0
result.capacity = cap
result.realLen = len
proc newSharedString*(len: Natural): SharedString =
if len != 0:
# optimization: Don't have an underlying buffer when 'len == 0'
result.buffer = newBuffer(len, len)
result.first = 0
result.len = len
proc newSharedString*(s: string): SharedString =
let len = s.len
if len != 0:
# optimization: Don't have an underlying buffer when 'len == 0'
result.buffer = newBuffer(len, len)
copyMem(addr result.buffer.data[0], cstring(s), s.len)
result.first = 0
result.len = len
when declared(atomicLoadN):
template load(x): expr = atomicLoadN(addr x, ATOMIC_SEQ_CST)
else:
# XXX Fixme
template load(x): expr = x
proc add*(s: var SharedString; t: cstring; len: Natural) =
if len == 0: return
let newLen = s.len + len
if s.buffer.isNil:
s.buffer = newBuffer(len, len)
copyMem(addr s.buffer.data[0], t, len)
s.len = len
elif newLen >= s.buffer.capacity or s.first != 0 or
s.len != s.buffer.realLen or load(s.buffer.refcount) > 1:
let oldBuf = s.buffer
s.buffer = newBuffer(max(s.buffer.capacity * 3 div 2, newLen), newLen)
copyMem(addr s.buffer.data[0], addr oldBuf.data[s.first], s.len)
copyMem(addr s.buffer.data[s.len], t, len)
decRef(oldBuf)
else:
copyMem(addr s.buffer.data[s.len], t, len)
s.buffer.realLen += len
s.len += len
proc add*(s: var SharedString; t: string) =
s.add(t.cstring, t.len)
proc rawData*(s: var SharedString): pointer =
if s.buffer.isNil: result = nil
else: result = addr s.buffer.data[s.first]
proc add*(s: var SharedString; t: SharedString) =
if t.buffer.isNil: return
s.add(cast[cstring](addr s.buffer.data[s.first]), t.len)
proc `$`*(s: SharedString): string =
result = newString(s.len)
if s.len > 0:
copyMem(addr result[0], addr s.buffer.data[s.first], s.len)
proc `==`*(s: SharedString; t: string): bool =
if s.buffer.isNil: result = t.len == 0
else: result = t.len == s.len and equalMem(addr s.buffer.data[s.first],
cstring(t), t.len)
proc `==`*(s, t: SharedString): bool =
if s.buffer.isNil: result = t.len == 0
else: result = t.len == s.len and equalMem(addr s.buffer.data[s.first],
addr t.buffer.data[t.first], t.len)
iterator items*(s: SharedString): char =
let buf = s.buffer.data
let x = s.first
if buf != nil:
for i in 0..<s.len:
yield buf[i+x]
import hashes
proc hash*(s: SharedString): THash =
var h: THash = 0
for x in s: h = h !& x.hash
result = !$h

View file

@ -0,0 +1,105 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Shared table support for Nim. Use plain old non GC'ed keys and values or
## you'll be in trouble. Uses a single lock to protect the table, lockfree
## implementations welcome but if lock contention is so high that you need a
## lockfree hash table, you're doing it wrong.
import
hashes, math, locks
type
KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B]
KeyValuePairSeq[A, B] = ptr array[10_000_000, KeyValuePair[A, B]]
SharedTable* [A, B] = object ## generic hash SharedTable
data: KeyValuePairSeq[A, B]
counter, dataLen: int
lock: Lock
template maxHash(t): expr = t.dataLen-1
include tableimpl
proc enlarge[A, B](t: var SharedTable[A, B]) =
let oldSize = t.dataLen
let size = oldSize * growthFactor
var n = cast[KeyValuePairSeq[A, B]](allocShared0(
sizeof(KeyValuePair[A, B]) * size))
t.dataLen = size
swap(t.data, n)
for i in 0..<oldSize:
if isFilled(n[i].hcode):
var j = -1 - rawGetKnownHC(t, n[i].key, n[i].hcode)
rawInsert(t, t.data, n[i].key, n[i].val, n[i].hcode, j)
deallocShared(n)
template withLock(t, x: untyped) =
acquire(t.lock)
x
release(t.lock)
proc mget*[A, B](t: var SharedTable[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
withLock t:
var hc: Hash
var index = rawGet(t, key, hc)
let hasKey = index >= 0
if hasKey: result = t.data[index].val
if not hasKey:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
proc mgetOrPut*[A, B](t: var SharedTable[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
## returning a value which can be modified. **Note**: This is inherently
## unsafe in the context of multi-threading since it returns a pointer
## to ``B``.
withLock t:
mgetOrPutImpl(enlarge)
proc hasKeyOrPut*[A, B](t: var SharedTable[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
withLock t:
hasKeyOrPutImpl(enlarge)
proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
withLock t:
putImpl(enlarge)
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.
withLock t:
addImpl(enlarge)
proc del*[A, B](t: var SharedTable[A, B], key: A) =
## deletes `key` from hash table `t`.
withLock t:
delImpl()
proc initSharedTable*[A, B](initialSize=64): SharedTable[A, B] =
## 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(
sizeof(KeyValuePair[A, B]) * initialSize))
initLock result.lock
proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) =
deallocShared(t.data)
deinitLock t.lock

View file

@ -0,0 +1,132 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## An ``include`` file for the different table implementations.
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0
const
growthFactor = 2
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: Hash = hc and maxHash(t) # start with real hash value
while isFilled(t.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if t.data[h].hcode == hc and t.data[h].key == key:
return h
h = nextTry(h, maxHash(t))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template rawGetImpl() {.dirty.} =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
rawGetKnownHCImpl()
template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add
hc = hash(key)
if hc == 0:
hc = 314159265
var h: Hash = hc and maxHash(t)
while isFilled(t.data[h].hcode):
h = nextTry(h, maxHash(t))
result = h
template rawInsertImpl() {.dirty.} =
data[h].key = key
data[h].val = val
data[h].hcode = hc
proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline.} =
rawGetDeepImpl()
proc rawGet[X, A](t: X, key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B],
key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl()
template addImpl(enlarge) {.dirty, immediate.} =
if mustRehash(t.dataLen, t.counter): enlarge(t)
var hc: Hash
var j = rawGetDeep(t, key, hc)
rawInsert(t, t.data, key, val, hc, j)
inc(t.counter)
template maybeRehashPutImpl(enlarge) {.dirty, immediate.} =
if mustRehash(t.dataLen, t.counter):
enlarge(t)
index = rawGetKnownHC(t, key, hc)
index = -1 - index # important to transform for mgetOrPutImpl
rawInsert(t, t.data, key, val, hc, index)
inc(t.counter)
template putImpl(enlarge) {.dirty, immediate.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl(enlarge)
template mgetOrPutImpl(enlarge) {.dirty, immediate.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index < 0:
# not present: insert (flipping index)
maybeRehashPutImpl(enlarge)
# either way return modifiable val
result = t.data[index].val
template hasKeyOrPutImpl(enlarge) {.dirty, immediate.} =
var hc: Hash
var index = rawGet(t, key, hc)
if index < 0:
result = false
maybeRehashPutImpl(enlarge)
else: result = true
template delImpl() {.dirty, immediate.} =
var hc: Hash
var i = rawGet(t, key, hc)
let msk = maxHash(t)
if i >= 0:
t.data[i].hcode = 0
dec(t.counter)
block outer:
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
t.data[i].hcode = 0 # mark current EMPTY
while true:
i = (i + 1) and msk # increment mod table size
if isEmpty(t.data[i].hcode): # end of collision cluster; So all done
break outer
r = t.data[i].hcode and msk # "home" location of key@i
if not ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
break
shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop

View file

@ -24,13 +24,13 @@
##
## Error: type mismatch: got (Person)
## but expected one of:
## hashes.hash(x: openarray[A]): THash
## hashes.hash(x: int): THash
## hashes.hash(x: float): THash
## hashes.hash(x: openarray[A]): Hash
## hashes.hash(x: int): Hash
## hashes.hash(x: float): Hash
## …
##
## What is happening here is that the types used for table keys require to have
## a ``hash()`` proc which will convert them to a `THash <hashes.html#THash>`_
## a ``hash()`` proc which will convert them to a `Hash <hashes.html#Hash>`_
## value, and the compiler is listing all the hash functions it knows.
## Additionally there has to be a ``==`` operator that provides the same
## semantics as its corresponding ``hash`` proc.
@ -46,7 +46,7 @@
## Person = object
## firstName, lastName: string
##
## proc hash(x: Person): THash =
## proc hash(x: Person): Hash =
## ## Piggyback on the already available string hash proc.
## ##
## ## Without this proc nothing works!
@ -68,33 +68,91 @@
import
hashes, math
{.pragma: myShallow.}
include "system/inclrtl"
type
KeyValuePair[A, B] = tuple[hcode: THash, key: A, val: B]
KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B]
KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]]
Table* {.myShallow.}[A, B] = object ## generic hash table
Table*[A, B] = object ## generic hash table
data: KeyValuePairSeq[A, B]
counter: int
TableRef*[A,B] = ref Table[A, B]
{.deprecated: [TTable: Table, PTable: TableRef].}
when not defined(nimhygiene):
{.pragma: dirty.}
template maxHash(t): expr {.immediate.} = high(t.data)
template dataLen(t): expr = len(t.data)
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: THash): bool {.inline.} =
result = hcode == 0
include tableimpl
proc isFilled(hcode: THash): bool {.inline.} =
result = hcode != 0
proc rightSize*(count: Natural): int {.inline.} =
## Return the value of `initialSize` to support `count` items.
##
## If more items are expected to be added, simply add that
## expected extra amount to the parameter before calling this.
##
## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4)
proc len*[A, B](t: Table[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
template get(t, key): untyped {.immediate.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
else:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
template getOrDefaultImpl(t, key): untyped {.immediate.} =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
proc `[]`*[A, B](t: Table[A, B], key: A): B {.deprecatedGet.} =
## retrieves the value at ``t[key]``. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
## the key exists.
get(t, key)
proc `[]`*[A, B](t: var Table[A, B], key: A): var B {.deprecatedGet.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
get(t, key)
proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised. Use ```[]```
## instead.
get(t, key)
proc getOrDefault*[A, B](t: Table[A, B], key: A): B = getOrDefaultImpl(t, key)
iterator allValues*[A, B](t: Table[A, B]; key: A): B =
## iterates over any value in the table `t` that belongs to the given `key`.
var h: Hash = hash(key) and high(t.data)
while isFilled(t.data[h].hcode):
if t.data[h].key == key:
yield t.data[h].val
h = nextTry(h, high(t.data))
proc hasKey*[A, B](t: Table[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
var hc: Hash
result = rawGet(t, key, hc) >= 0
proc contains*[A, B](t: Table[A, B], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A, B](t, key)
iterator pairs*[A, B](t: Table[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
@ -121,103 +179,9 @@ iterator mvalues*[A, B](t: var Table[A, B]): var B =
for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val
const
growthFactor = 2
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc rightSize*(count: Natural): int {.inline.} =
## Return the value of `initialSize` to support `count` items.
##
## If more items are expected to be added, simply add that
## expected extra amount to the parameter before calling this.
##
## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(t.data) # start with real hash value
while isFilled(t.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra THash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if t.data[h].hcode == hc and t.data[h].key == key:
return h
h = nextTry(h, high(t.data))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template rawGetImpl() {.dirty.} =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
rawGetKnownHCImpl()
template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add
hc = hash(key)
if hc == 0:
hc = 314159265
var h: THash = hc and high(t.data)
while isFilled(t.data[h].hcode):
h = nextTry(h, high(t.data))
result = h
template rawInsertImpl() {.dirty.} =
data[h].key = key
data[h].val = val
data[h].hcode = hc
proc rawGetKnownHC[A, B](t: Table[A, B], key: A, hc: THash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} =
rawGetDeepImpl()
proc rawGet[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} =
rawGetImpl()
proc `[]`*[A, B](t: Table[A, B], key: A): B =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
proc mget*[A, B](t: var Table[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
else:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
iterator allValues*[A, B](t: Table[A, B]; key: A): B =
## iterates over any value in the table `t` that belongs to the given `key`.
var h: THash = hash(key) and high(t.data)
while isFilled(t.data[h].hcode):
if t.data[h].key == key:
yield t.data[h].val
h = nextTry(h, high(t.data))
proc hasKey*[A, B](t: Table[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
var hc: THash
result = rawGet(t, key, hc) >= 0
proc rawInsert[A, B](t: var Table[A, B], data: var KeyValuePairSeq[A, B],
key: A, val: B, hc: THash, h: THash) =
rawInsertImpl()
proc del*[A, B](t: var Table[A, B], key: A) =
## deletes `key` from hash table `t`.
delImpl()
proc enlarge[A, B](t: var Table[A, B]) =
var n: KeyValuePairSeq[A, B]
@ -228,81 +192,26 @@ proc enlarge[A, B](t: var Table[A, B]) =
var j = -1 - rawGetKnownHC(t, n[i].key, n[i].hcode)
rawInsert(t, t.data, n[i].key, n[i].val, n[i].hcode, j)
template addImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter): enlarge(t)
var hc: THash
var j = rawGetDeep(t, key, hc)
rawInsert(t, t.data, key, val, hc, j)
inc(t.counter)
template maybeRehashPutImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter):
enlarge(t)
index = rawGetKnownHC(t, key, hc)
index = -1 - index # important to transform for mgetOrPutImpl
rawInsert(t, t.data, key, val, hc, index)
inc(t.counter)
template putImpl() {.dirty.} =
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl()
template mgetOrPutImpl() {.dirty.} =
var hc: THash
var index = rawGet(t, key, hc)
if index < 0: maybeRehashPutImpl() # not present: insert (flipping index)
result = t.data[index].val # either way return modifiable val
template hasKeyOrPutImpl() {.dirty.} =
var hc: THash
var index = rawGet(t, key, hc)
if index < 0:
result = false
maybeRehashPutImpl()
else: result = true
proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
## returning a value which can be modified.
mgetOrPutImpl()
mgetOrPutImpl(enlarge)
proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
hasKeyOrPutImpl()
hasKeyOrPutImpl(enlarge)
proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
putImpl()
putImpl(enlarge)
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.
addImpl()
addImpl(enlarge)
template doWhile(a: expr, b: stmt): stmt =
while true:
b
if not a: break
proc del*[A, B](t: var Table[A, B], key: A) =
## deletes `key` from hash table `t`.
var hc: THash
var i = rawGet(t, key, hc)
let msk = high(t.data)
if i >= 0:
t.data[i].hcode = 0
dec(t.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
t.data[i].hcode = 0 # mark current EMPTY
doWhile ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(t.data[i].hcode): # end of collision cluster; So all done
return
r = t.data[i].hcode and msk # "home" location of key@i
shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop
proc len*[A, B](t: TableRef[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
proc initTable*[A, B](initialSize=64): Table[A, B] =
## creates a new hash table that is empty.
@ -336,6 +245,10 @@ proc `$`*[A, B](t: Table[A, B]): string =
## The `$` operator for hash tables.
dollarImpl()
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
template equalsImpl() =
if s.counter == t.counter:
# different insertion orders mean different 'data' seqs, so we have
@ -356,10 +269,6 @@ proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] =
for item in collection:
result[index(item)] = item
proc len*[A, B](t: TableRef[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*[A, B](t: TableRef[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
@ -386,17 +295,19 @@ iterator mvalues*[A, B](t: TableRef[A, B]): var B =
for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val
proc `[]`*[A, B](t: TableRef[A, B], key: A): B =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
proc `[]`*[A, B](t: TableRef[A, B], key: A): var B {.deprecatedGet.} =
## retrieves the value at ``t[key]``. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
## the key exists.
result = t[][key]
proc mget*[A, B](t: TableRef[A, B], key: A): var B =
proc mget*[A, B](t: TableRef[A, B], key: A): var B {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
t[].mget(key)
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
t[][key]
proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = getOrDefault(t[], key)
proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
@ -407,9 +318,9 @@ proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
t[].hasKeyOrPut(key, val)
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
proc contains*[A, B](t: TableRef[A, B], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A, B](t, key)
proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
@ -452,10 +363,9 @@ proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B]
type
OrderedKeyValuePair[A, B] = tuple[
hcode: THash, next: int, key: A, val: B]
hcode: Hash, next: int, key: A, val: B]
OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]]
OrderedTable* {.
myShallow.}[A, B] = object ## table that remembers insertion order
OrderedTable* [A, B] = object ## table that remembers insertion order
data: OrderedKeyValuePairSeq[A, B]
counter, first, last: int
OrderedTableRef*[A, B] = ref OrderedTable[A, B]
@ -501,40 +411,48 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B =
forAllOrderedPairs:
yield t.data[h].val
proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: THash): int =
proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int =
rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int {.inline.} =
proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} =
rawGetDeepImpl()
proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int =
proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int =
rawGetImpl()
proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B {.deprecatedGet.} =
## retrieves the value at ``t[key]``. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
## the key exists.
get(t, key)
proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B =
proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B{.deprecatedGet.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key not found: " & $key)
## If `key` is not in `t`, the ``KeyError`` exception is raised.
get(t, key)
proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
get(t, key)
proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B =
getOrDefaultImpl(t, key)
proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
var hc: THash
var hc: Hash
result = rawGet(t, key, hc) >= 0
proc contains*[A, B](t: OrderedTable[A, B], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A, B](t, key)
proc rawInsert[A, B](t: var OrderedTable[A, B],
data: var OrderedKeyValuePairSeq[A, B],
key: A, val: B, hc: THash, h: THash) =
key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl()
data[h].next = -1
if t.first < 0: t.first = h
@ -557,20 +475,20 @@ proc enlarge[A, B](t: var OrderedTable[A, B]) =
proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
putImpl()
putImpl(enlarge)
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.
addImpl()
addImpl(enlarge)
proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``value`` if not present, either way
## returning a value which can be modified.
mgetOrPutImpl()
mgetOrPutImpl(enlarge)
proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
hasKeyOrPutImpl()
hasKeyOrPutImpl(enlarge)
proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] =
## creates a new ordered hash table that is empty.
@ -679,17 +597,20 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B =
forAllOrderedPairs:
yield t.data[h].val
proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): B =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B =
## retrieves the value at ``t[key]``. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether
## the key exists.
result = t[][key]
proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B =
proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
result = t[].mget(key)
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
result = t[][key]
proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B =
getOrDefault(t[], key)
proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
@ -704,6 +625,10 @@ proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A, B](t, key)
proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
t[][key] = val
@ -741,7 +666,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B],
# ------------------------------ count tables -------------------------------
type
CountTable* {.myShallow.}[
CountTable* [
A] = object ## table that counts the number of each key
data: seq[tuple[key: A, val: int]]
counter: int
@ -780,33 +705,53 @@ iterator mvalues*[A](t: CountTable[A]): var int =
if t.data[h].val != 0: yield t.data[h].val
proc rawGet[A](t: CountTable[A], key: A): int =
var h: THash = hash(key) and high(t.data) # start with real hash value
var h: Hash = hash(key) and high(t.data) # start with real hash value
while t.data[h].val != 0:
if t.data[h].key == key: return h
h = nextTry(h, high(t.data))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
proc `[]`*[A](t: CountTable[A], key: A): int =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## 0 is returned. One can check with ``hasKey`` whether the key
## exists.
template ctget(t, key: untyped): untyped {.immediate.} =
var index = rawGet(t, key)
if index >= 0: result = t.data[index].val
else:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
proc mget*[A](t: var CountTable[A], key: A): var int =
proc `[]`*[A](t: CountTable[A], key: A): int {.deprecatedGet.} =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## the ``KeyError`` exception is raised. One can check with ``hasKey``
## whether the key exists.
ctget(t, key)
proc `[]`*[A](t: var CountTable[A], key: A): var int {.deprecatedGet.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
ctget(t, key)
proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
ctget(t, key)
proc getOrDefault*[A](t: CountTable[A], key: A): int =
var index = rawGet(t, key)
if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key not found: " & $key)
proc hasKey*[A](t: CountTable[A], key: A): bool =
## returns true iff `key` is in the table `t`.
result = rawGet(t, key) >= 0
proc contains*[A](t: CountTable[A], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A](t, key)
proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]],
key: A, val: int) =
var h: THash = hash(key) and high(data)
var h: Hash = hash(key) and high(data)
while data[h].val != 0: h = nextTry(h, high(data))
data[h].key = key
data[h].val = val
@ -930,21 +875,28 @@ iterator mvalues*[A](t: CountTableRef[A]): var int =
for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].val
proc `[]`*[A](t: CountTableRef[A], key: A): int =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
## 0 is returned. One can check with ``hasKey`` whether the key
## exists.
proc `[]`*[A](t: CountTableRef[A], key: A): var int {.deprecatedGet.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised.
result = t[][key]
proc mget*[A](t: CountTableRef[A], key: A): var int =
proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
result = t[].mget(key)
## If `key` is not in `t`, the ``KeyError`` exception is raised.
## Use ```[]``` instead.
result = t[][key]
proc getOrDefault*[A](t: CountTableRef[A], key: A): int =
getOrDefaultImpl(t, key)
proc hasKey*[A](t: CountTableRef[A], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
proc contains*[A](t: CountTableRef[A], key: A): bool =
## alias of `hasKey` for use with the `in` operator.
return hasKey[A](t, key)
proc `[]=`*[A](t: CountTableRef[A], key: A, val: int) =
## puts a (key, value)-pair into `t`. `val` has to be positive.
assert val > 0
@ -1008,7 +960,7 @@ when isMainModule:
Person = object
firstName, lastName: string
proc hash(x: Person): THash =
proc hash(x: Person): Hash =
## Piggyback on the already available string hash proc.
##
## Without this proc nothing works!

View file

@ -6,7 +6,7 @@
# distribution, for details about the copyright.
#
## This module implements color handling for Nimrod. It is used by
## This module implements color handling for Nimrod. It is used by
## the ``graphics`` module.
import strutils
@ -18,15 +18,15 @@ type
proc `==` *(a, b: Color): bool {.borrow.}
## compares two colors.
template extract(a: Color, r, g, b: expr) {.immediate.}=
var r = a.int shr 16 and 0xff
var g = a.int shr 8 and 0xff
var b = a.int and 0xff
template rawRGB(r, g, b: int): expr =
Color(r shl 16 or g shl 8 or b)
template colorOp(op: expr) {.immediate.} =
extract(a, ar, ag, ab)
extract(b, br, bg, bb)
@ -39,24 +39,24 @@ proc satPlus(a, b: int): int {.inline.} =
proc satMinus(a, b: int): int {.inline.} =
result = a -% b
if result < 0: result = 0
proc `+`*(a, b: Color): Color =
## adds two colors: This uses saturated artithmetic, so that each color
## component cannot overflow (255 is used as a maximum).
colorOp(satPlus)
proc `-`*(a, b: Color): Color =
## subtracts two colors: This uses saturated artithmetic, so that each color
## component cannot overflow (255 is used as a maximum).
colorOp(satMinus)
proc extractRGB*(a: Color): tuple[r, g, b: range[0..255]] =
## extracts the red/green/blue components of the color `a`.
result.r = a.int shr 16 and 0xff
result.g = a.int shr 8 and 0xff
result.b = a.int and 0xff
proc intensity*(a: Color, f: float): Color =
proc intensity*(a: Color, f: float): Color =
## returns `a` with intensity `f`. `f` should be a float from 0.0 (completely
## dark) to 1.0 (full color intensity).
var r = toInt(toFloat(a.int shr 16 and 0xff) * f)
@ -66,7 +66,7 @@ proc intensity*(a: Color, f: float): Color =
if g >% 255: g = 255
if b >% 255: b = 255
result = rawRGB(r, g, b)
template mix*(a, b: Color, fn: expr): expr =
## uses `fn` to mix the colors `a` and `b`. `fn` is invoked for each component
## R, G, and B. This is a template because `fn` should be inlined and the
@ -79,7 +79,7 @@ template mix*(a, b: Color, fn: expr): expr =
if y >% 255:
y = if y < 0: 0 else: 255
y
(bind extract)(a, ar, ag, ab)
(bind extract)(b, br, bg, bb)
(bind rawRGB)(><fn(ar, br), ><fn(ag, bg), ><fn(ab, bb))
@ -226,7 +226,7 @@ const
colWhiteSmoke* = Color(0xF5F5F5)
colYellow* = Color(0xFFFF00)
colYellowGreen* = Color(0x9ACD32)
colorNames = [
("aliceblue", colAliceBlue),
("antiquewhite", colAntiqueWhite),
@ -369,24 +369,24 @@ const
("yellow", colYellow),
("yellowgreen", colYellowGreen)]
proc `$`*(c: Color): string =
proc `$`*(c: Color): string =
## converts a color into its textual representation. Example: ``#00FF00``.
result = '#' & toHex(int(c), 6)
proc binaryStrSearch(x: openArray[tuple[name: string, col: Color]],
y: string): int =
proc binaryStrSearch(x: openArray[tuple[name: string, col: Color]],
y: string): int =
var a = 0
var b = len(x) - 1
while a <= b:
while a <= b:
var mid = (a + b) div 2
var c = cmp(x[mid].name, y)
if c < 0: a = mid + 1
elif c > 0: b = mid - 1
else: return mid
result = - 1
proc parseColor*(name: string): Color =
## parses `name` to a color value. If no valid color could be
proc parseColor*(name: string): Color =
## parses `name` to a color value. If no valid color could be
## parsed ``EInvalidValue`` is raised.
if name[0] == '#':
result = Color(parseHexInt(name))
@ -396,10 +396,10 @@ proc parseColor*(name: string): Color =
result = colorNames[idx][1]
proc isColor*(name: string): bool =
## returns true if `name` is a known color name or a hexadecimal color
## returns true if `name` is a known color name or a hexadecimal color
## prefixed with ``#``.
if name[0] == '#':
for i in 1 .. name.len-1:
if name[0] == '#':
for i in 1 .. name.len-1:
if name[i] notin {'0'..'9', 'a'..'f', 'A'..'F'}: return false
result = true
else:

View file

@ -17,7 +17,7 @@
import
math
const
EPS = 1.0e-7 ## Epsilon used for float comparisons.
@ -248,7 +248,7 @@ proc pow*(x, y: Complex): Complex =
var r = y.re*theta + y.im*ln(rho)
result.re = s*cos(r)
result.im = s*sin(r)
proc sin*(z: Complex): Complex =
## Returns the sine of `z`.
@ -387,7 +387,7 @@ when isMainModule:
var one = (1.0,0.0)
var tt = (10.0, 20.0)
var ipi = (0.0, -PI)
assert( a == a )
assert( (a-a) == z )
assert( (a+b) == z )
@ -403,7 +403,7 @@ when isMainModule:
assert( conjugate(a) == (1.0, -2.0) )
assert( sqrt(m1) == i )
assert( exp(ipi) =~ m1 )
assert( pow(a,b) =~ (-3.72999124927876, -1.68815826725068) )
assert( pow(z,a) =~ (0.0, 0.0) )
assert( pow(z,z) =~ (1.0, 0.0) )

View file

@ -18,7 +18,7 @@ when not defined(windows):
when defined(linux):
import linux
when defined(freebsd) or defined(macosx):
{.emit:"#include <sys/types.h>".}

View file

@ -13,7 +13,7 @@
when defined(windows):
import winlean, os, strutils, math
proc `-`(a, b: TFILETIME): int64 = a.rdFileTime - b.rdFileTime
proc `-`(a, b: FILETIME): int64 = a.rdFileTime - b.rdFileTime
elif defined(linux):
from cpuinfo import countProcessors
@ -25,16 +25,16 @@ type
ThreadPoolState* = object
when defined(windows):
prevSysKernel, prevSysUser, prevProcKernel, prevProcUser: TFILETIME
prevSysKernel, prevSysUser, prevProcKernel, prevProcUser: FILETIME
calls*: int
proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
when defined(windows):
var
sysIdle, sysKernel, sysUser,
procCreation, procExit, procKernel, procUser: TFILETIME
procCreation, procExit, procKernel, procUser: FILETIME
if getSystemTimes(sysIdle, sysKernel, sysUser) == 0 or
getProcessTimes(THandle(-1), procCreation, procExit,
getProcessTimes(Handle(-1), procCreation, procExit,
procKernel, procUser) == 0:
return doNothing
if s.calls > 0:
@ -57,7 +57,7 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
s.prevProcKernel = procKernel
s.prevProcUser = procUser
elif defined(linux):
proc fscanf(c: File, frmt: cstring) {.varargs, importc,
proc fscanf(c: File, frmt: cstring) {.varargs, importc,
header: "<stdio.h>".}
var f = open("/proc/loadavg")

View file

@ -18,8 +18,8 @@ import cpuinfo, cpuload, locks
type
Semaphore = object
c: TCond
L: TLock
c: Cond
L: Lock
counter: int
proc createSemaphore(): Semaphore =
@ -113,7 +113,7 @@ type
ToFreeQueue = object
len: int
lock: TLock
lock: Lock
empty: Semaphore
data: array[128, pointer]
@ -128,6 +128,7 @@ type
initialized: bool # whether it has even been initialized
shutdown: bool # the pool requests to shut down this worker thread
q: ToFreeQueue
readyForTask: Semaphore
proc await*(fv: FlowVarBase) =
## waits until the value for the flowVar arrives. Usually it is not necessary
@ -221,11 +222,17 @@ proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) =
action(fv.blob)
finished(fv)
proc `^`*[T](fv: FlowVar[ref T]): foreign ptr T =
proc unsafeRead*[T](fv: FlowVar[ref T]): foreign ptr T =
## blocks until the value is available and then returns this value.
await(fv)
result = cast[foreign ptr T](fv.data)
proc `^`*[T](fv: FlowVar[ref T]): ref T =
## blocks until the value is available and then returns this value.
await(fv)
let src = cast[ref T](fv.data)
deepCopy result, src
proc `^`*[T](fv: FlowVar[T]): T =
## blocks until the value is available and then returns this value.
await(fv)
@ -260,6 +267,17 @@ proc awaitAny*(flowVars: openArray[FlowVarBase]): int =
result = -1
destroySemaphore(ai.cv)
proc isReady*(fv: FlowVarBase): bool =
## Determines whether the specified ``FlowVarBase``'s value is available.
##
## If ``true`` awaiting ``fv`` will not block.
if fv.usesSemaphore and not fv.awaited:
acquire(fv.cv.L)
result = fv.cv.counter > 0
release(fv.cv.L)
else:
result = true
proc nimArgsPassingDone(p: pointer) {.compilerProc.} =
let w = cast[ptr Worker](p)
signal(w.taskStarted)
@ -267,6 +285,10 @@ proc nimArgsPassingDone(p: pointer) {.compilerProc.} =
const
MaxThreadPoolSize* = 256 ## maximal size of the thread pool. 256 threads
## should be good enough for anybody ;-)
MaxDistinguishedThread* = 32 ## maximal number of "distinguished" threads.
type
ThreadId* = range[0..MaxDistinguishedThread-1]
var
currentPoolSize: int
@ -284,17 +306,37 @@ proc slave(w: ptr Worker) {.thread.} =
readyWorker = w
signal(gSomeReady)
await(w.taskArrived)
assert(not w.ready)
# XXX Somebody needs to look into this (why does this assertion fail
# in Visual Studio?)
when not defined(vcc): assert(not w.ready)
w.f(w, w.data)
if w.q.len != 0: w.cleanFlowVars
if w.shutdown:
w.shutdown = false
atomicDec currentPoolSize
proc distinguishedSlave(w: ptr Worker) {.thread.} =
while true:
when declared(atomicStoreN):
atomicStoreN(addr(w.ready), true, ATOMIC_SEQ_CST)
else:
w.ready = true
signal(w.readyForTask)
await(w.taskArrived)
assert(not w.ready)
w.f(w, w.data)
if w.q.len != 0: w.cleanFlowVars
var
workers: array[MaxThreadPoolSize, TThread[ptr Worker]]
workersData: array[MaxThreadPoolSize, Worker]
distinguished: array[MaxDistinguishedThread, TThread[ptr Worker]]
distinguishedData: array[MaxDistinguishedThread, Worker]
when defined(nimPinToCpu):
var gCpus: Natural
proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) =
## sets the minimal thread pool size. The default value of this is 4.
minPoolSize = size
@ -308,18 +350,37 @@ proc setMaxPoolSize*(size: range[1..MaxThreadPoolSize]) =
let w = addr(workersData[i])
w.shutdown = true
proc activateThread(i: int) {.noinline.} =
when defined(nimRecursiveSpawn):
var localThreadId {.threadvar.}: int
proc activateWorkerThread(i: int) {.noinline.} =
workersData[i].taskArrived = createSemaphore()
workersData[i].taskStarted = createSemaphore()
workersData[i].initialized = true
workersData[i].q.empty = createSemaphore()
initLock(workersData[i].q.lock)
createThread(workers[i], slave, addr(workersData[i]))
when defined(nimRecursiveSpawn):
localThreadId = i+1
when defined(nimPinToCpu):
if gCpus > 0: pinToCpu(workers[i], i mod gCpus)
proc activateDistinguishedThread(i: int) {.noinline.} =
distinguishedData[i].taskArrived = createSemaphore()
distinguishedData[i].taskStarted = createSemaphore()
distinguishedData[i].initialized = true
distinguishedData[i].q.empty = createSemaphore()
initLock(distinguishedData[i].q.lock)
distinguishedData[i].readyForTask = createSemaphore()
createThread(distinguished[i], distinguishedSlave, addr(distinguishedData[i]))
proc setup() =
currentPoolSize = min(countProcessors(), MaxThreadPoolSize)
let p = countProcessors()
when defined(nimPinToCpu):
gCpus = p
currentPoolSize = min(p, MaxThreadPoolSize)
readyWorker = addr(workersData[0])
for i in 0.. <currentPoolSize: activateThread(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
@ -333,6 +394,13 @@ proc spawn*(call: expr): expr {.magic: "Spawn".}
## is gcsafe and has a return type that is either 'void' or compatible
## with ``FlowVar[T]``.
proc pinnedSpawn*(id: ThreadId; call: expr): expr {.magic: "Spawn".}
## always spawns a new task on the worker thread with ``id``, so that
## the 'call' is **always** executed on
## the this thread. 'call' has to be proc call 'p(...)' where 'p'
## is gcsafe and has a return type that is either 'void' or compatible
## with ``FlowVar[T]``.
template spawnX*(call: expr): expr =
## spawns a new task if a CPU core is ready, otherwise executes the
## call in the calling thread. Usually it is advised to
@ -349,11 +417,11 @@ proc parallel*(body: stmt) {.magic: "Parallel".}
var
state: ThreadPoolState
stateLock: TLock
stateLock: Lock
initLock stateLock
proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} =
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
@ -370,7 +438,7 @@ proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} =
of doCreateThread:
if currentPoolSize < maxPoolSize:
if not workersData[currentPoolSize].initialized:
activateThread(currentPoolSize)
activateWorkerThread(currentPoolSize)
let w = addr(workersData[currentPoolSize])
atomicInc currentPoolSize
if selectWorker(w, fn, data):
@ -385,7 +453,34 @@ proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} =
release(stateLock)
# else the acquire failed, but this means some
# other thread succeeded, so we don't need to do anything here.
await(gSomeReady)
when defined(nimRecursiveSpawn):
if localThreadId > 0:
# we are a worker thread, so instead of waiting for something which
# might as well never happen (see tparallel_quicksort), we run the task
# on the current thread instead.
var self = addr(workersData[localThreadId-1])
fn(self, data)
await(self.taskStarted)
return
else:
await(gSomeReady)
else:
await(gSomeReady)
var
distinguishedLock: TLock
initLock distinguishedLock
proc nimSpawn4(fn: WorkerProc; data: pointer; id: ThreadId) {.compilerProc.} =
acquire(distinguishedLock)
if not distinguishedData[id].initialized:
activateDistinguishedThread(id)
release(distinguishedLock)
while true:
if selectWorker(addr(distinguishedData[id]), fn, data): break
await(distinguishedData[id].readyForTask)
proc sync*() =
## a simple barrier to wait for all spawn'ed tasks. If you need more elaborate

View file

@ -11,7 +11,7 @@
import strtabs, times
proc parseCookies*(s: string): StringTableRef =
proc parseCookies*(s: string): StringTableRef =
## parses cookies into a string table.
result = newStringTable(modeCaseInsensitive)
var i = 0
@ -31,7 +31,7 @@ proc parseCookies*(s: string): StringTableRef =
proc setCookie*(key, value: string, domain = "", path = "",
expires = "", noName = false,
secure = false, httpOnly = false): string =
## Creates a command in the format of
## Creates a command in the format of
## ``Set-Cookie: key=value; Domain=...; ...``
result = ""
if not noName: result.add("Set-Cookie: ")
@ -45,10 +45,10 @@ proc setCookie*(key, value: string, domain = "", path = "",
proc setCookie*(key, value: string, expires: TimeInfo,
domain = "", path = "", noName = false,
secure = false, httpOnly = false): string =
## Creates a command in the format of
## Creates a command in the format of
## ``Set-Cookie: key=value; Domain=...; ...``
##
## **Note:** UTC is assumed as the timezone for ``expires``.
## **Note:** UTC is assumed as the timezone for ``expires``.
return setCookie(key, value, domain, path,
format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"),
noname, secure, httpOnly)
@ -61,7 +61,7 @@ when isMainModule:
echo cookie
let start = "Set-Cookie: test=value; Expires="
assert cookie[0..start.high] == start
let table = parseCookies("uid=1; kp=2")
assert table["uid"] == "1"
assert table["kp"] == "2"

143
lib/pure/coro.nim Normal file
View file

@ -0,0 +1,143 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Rokas Kupstys
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
when not defined(nimCoroutines):
{.error: "Coroutines require -d:nimCoroutines".}
import os, times
import macros
import arch
import lists
const defaultStackSize = 512 * 1024
type Coroutine = ref object
# prev: ptr Coroutine
# next: ptr Coroutine
ctx: JmpBuf
fn: proc()
started: bool
lastRun: float
sleepTime: float
stack: pointer
stacksize: int
var coroutines = initDoublyLinkedList[Coroutine]()
var current: Coroutine
var mainCtx: JmpBuf
proc GC_addStack(starts: pointer) {.cdecl, importc.}
proc GC_removeStack(starts: pointer) {.cdecl, importc.}
proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
proc start*(c: proc(), stacksize: int=defaultStackSize) =
## Adds coroutine to event loop. It does not run immediately.
var coro = Coroutine()
coro.fn = c
while coro.stack == nil:
coro.stack = alloc0(stacksize)
coro.stacksize = stacksize
coroutines.append(coro)
{.push stackTrace: off.}
proc suspend*(sleepTime: float=0) =
## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
## Until then other coroutines are executed.
##
## This is similar to a `yield`:idx:, or a `yieldFrom`:idx in Python.
var oldFrame = getFrame()
var sp {.volatile.}: pointer
GC_setCurrentStack(current.stack, cast[pointer](addr sp))
current.sleepTime = sleepTime
current.lastRun = epochTime()
if setjmp(current.ctx) == 0:
longjmp(mainCtx, 1)
setFrame(oldFrame)
{.pop.}
proc run*() =
## Starts main event loop which exits when all coroutines exit. Calling this proc
## starts execution of first coroutine.
var node = coroutines.head
var minDelay: float = 0
var frame: PFrame
while node != nil:
var coro = node.value
current = coro
os.sleep(int(minDelay * 1000))
var remaining = coro.sleepTime - (epochTime() - coro.lastRun);
if remaining <= 0:
remaining = 0
let res = setjmp(mainCtx)
if res == 0:
frame = getFrame()
if coro.started: # coroutine resumes
longjmp(coro.ctx, 1)
else:
coro.started = true # coroutine starts
var stackEnd = cast[pointer](cast[ByteAddress](coro.stack) + coro.stacksize)
GC_addStack(coro.stack)
coroSwitchStack(stackEnd)
coro.fn()
coroRestoreStack()
GC_removeStack(coro.stack)
var next = node.prev
coroutines.remove(node)
dealloc(coro.stack)
node = next
setFrame(frame)
else:
setFrame(frame)
elif remaining > 0:
if minDelay > 0 and remaining > 0:
minDelay = min(remaining, minDelay)
else:
minDelay = remaining
if node == nil or node.next == nil:
node = coroutines.head
else:
node = node.next
proc alive*(c: proc()): bool =
## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
for coro in items(coroutines):
if coro.fn == c:
return true
proc wait*(c: proc(), interval=0.01) =
## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
while alive(c):
suspend interval
when defined(nimCoroutines) and isMainModule:
var stackCheckValue = 1100220033
proc c2()
proc c1() =
for i in 0 .. 3:
echo "c1"
suspend 0.05
echo "c1 exits"
proc c2() =
for i in 0 .. 3:
echo "c2"
suspend 0.025
wait(c1)
echo "c2 exits"
start(c1)
start(c2)
run()
echo "done ", stackCheckValue

1
lib/pure/coro.nimcfg Normal file
View file

@ -0,0 +1 @@
-d:nimCoroutines

View file

@ -8,8 +8,8 @@
#
## This module implements the ability to access symbols from shared
## libraries. On POSIX this uses the ``dlsym`` mechanism, on
## Windows ``LoadLibrary``.
## libraries. On POSIX this uses the ``dlsym`` mechanism, on
## Windows ``LoadLibrary``.
type
LibHandle* = pointer ## a handle to a dynamically loaded library
@ -17,11 +17,11 @@ type
{.deprecated: [TLibHandle: LibHandle].}
proc loadLib*(path: string, global_symbols=false): LibHandle
## loads a library from `path`. Returns nil if the library could not
## loads a library from `path`. Returns nil if the library could not
## be loaded.
proc loadLib*(): LibHandle
## gets the handle from the current executable. Returns nil if the
## gets the handle from the current executable. Returns nil if the
## library could not be loaded.
proc unloadLib*(lib: LibHandle)
@ -63,13 +63,13 @@ when defined(posix):
proc dlsym(lib: LibHandle, name: cstring): pointer {.
importc, header: "<dlfcn.h>".}
proc loadLib(path: string, global_symbols=false): LibHandle =
proc loadLib(path: string, global_symbols=false): LibHandle =
var flags = RTLD_NOW
if global_symbols: flags = flags or RTLD_GLOBAL
return dlopen(path, flags)
proc loadLib(): LibHandle = return dlopen(nil, RTLD_NOW)
proc unloadLib(lib: LibHandle) = dlclose(lib)
proc symAddr(lib: LibHandle, name: cstring): pointer =
proc symAddr(lib: LibHandle, name: cstring): pointer =
return dlsym(lib, name)
elif defined(windows) or defined(dos):

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright.
#
## Converts between different character encodings. On UNIX, this uses
## Converts between different character encodings. On UNIX, this uses
## the `iconv`:idx: library, on Windows the Windows API.
import os, parseutils, strutils
@ -41,199 +41,200 @@ when defined(windows):
inc j
result = i == a.len and j == b.len
const
const
winEncodings = [
(1, "OEMCP"), # current OEM codepage
(037, "IBM037"), # IBM EBCDIC US-Canada
(437, "IBM437"), # OEM United States
(500, "IBM500"), # IBM EBCDIC International
(708, "ASMO-708"), # Arabic (ASMO 708)
(709, "ASMO_449"), # Arabic (ASMO-449+, BCON V4)
(710, ""), # Arabic - Transparent Arabic
(720, "DOS-720"), # Arabic (Transparent ASMO); Arabic (DOS)
(737, "ibm737"), # OEM Greek (formerly 437G); Greek (DOS)
(775, "ibm775"), # OEM Baltic; Baltic (DOS)
(850, "ibm850"), # OEM Multilingual Latin 1; Western European (DOS)
(852, "ibm852"), # OEM Latin 2; Central European (DOS)
(855, "IBM855"), # OEM Cyrillic (primarily Russian)
(857, "ibm857"), # OEM Turkish; Turkish (DOS)
(858, "IBM00858"), # OEM Multilingual Latin 1 + Euro symbol
(860, "IBM860"), # OEM Portuguese; Portuguese (DOS)
(861, "ibm861"), # OEM Icelandic; Icelandic (DOS)
(862, "DOS-862"), # OEM Hebrew; Hebrew (DOS)
(863, "IBM863"), # OEM French Canadian; French Canadian (DOS)
(864, "IBM864"), # OEM Arabic; Arabic (864)
(865, "IBM865"), # OEM Nordic; Nordic (DOS)
(866, "cp866"), # OEM Russian; Cyrillic (DOS)
(869, "ibm869"), # OEM Modern Greek; Greek, Modern (DOS)
(870, "IBM870"), # IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
(874, "windows-874"), # ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows)
(875, "cp875"), # IBM EBCDIC Greek Modern
(932, "shift_jis"), # ANSI/OEM Japanese; Japanese (Shift-JIS)
(936, "gb2312"), # ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
(949, "ks_c_5601-1987"), # ANSI/OEM Korean (Unified Hangul Code)
(950, "big5"), # ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
(1026, "IBM1026"), # IBM EBCDIC Turkish (Latin 5)
(1047, "IBM01047"), # IBM EBCDIC Latin 1/Open System
(1140, "IBM01140"), # IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
(1141, "IBM01141"), # IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
(1142, "IBM01142"), # IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
(1143, "IBM01143"), # IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
(1144, "IBM01144"), # IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
(1145, "IBM01145"), # IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
(1146, "IBM01146"), # IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
(1147, "IBM01147"), # IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
(1148, "IBM01148"), # IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
(1149, "IBM01149"), # IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
(1200, "utf-16"), # Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
(1201, "unicodeFFFE"), # Unicode UTF-16, big endian byte order; available only to managed applications
(1250, "windows-1250"), # ANSI Central European; Central European (Windows)
(1251, "windows-1251"), # ANSI Cyrillic; Cyrillic (Windows)
(1252, "windows-1252"), # ANSI Latin 1; Western European (Windows)
(1253, "windows-1253"), # ANSI Greek; Greek (Windows)
(1254, "windows-1254"), # ANSI Turkish; Turkish (Windows)
(1255, "windows-1255"), # ANSI Hebrew; Hebrew (Windows)
(1256, "windows-1256"), # ANSI Arabic; Arabic (Windows)
(1257, "windows-1257"), # ANSI Baltic; Baltic (Windows)
(1258, "windows-1258"), # ANSI/OEM Vietnamese; Vietnamese (Windows)
(037, "IBM037"), # IBM EBCDIC US-Canada
(437, "IBM437"), # OEM United States
(500, "IBM500"), # IBM EBCDIC International
(708, "ASMO-708"), # Arabic (ASMO 708)
(709, "ASMO_449"), # Arabic (ASMO-449+, BCON V4)
(710, ""), # Arabic - Transparent Arabic
(720, "DOS-720"), # Arabic (Transparent ASMO); Arabic (DOS)
(737, "ibm737"), # OEM Greek (formerly 437G); Greek (DOS)
(775, "ibm775"), # OEM Baltic; Baltic (DOS)
(850, "ibm850"), # OEM Multilingual Latin 1; Western European (DOS)
(852, "ibm852"), # OEM Latin 2; Central European (DOS)
(855, "IBM855"), # OEM Cyrillic (primarily Russian)
(857, "ibm857"), # OEM Turkish; Turkish (DOS)
(858, "IBM00858"), # OEM Multilingual Latin 1 + Euro symbol
(860, "IBM860"), # OEM Portuguese; Portuguese (DOS)
(861, "ibm861"), # OEM Icelandic; Icelandic (DOS)
(862, "DOS-862"), # OEM Hebrew; Hebrew (DOS)
(863, "IBM863"), # OEM French Canadian; French Canadian (DOS)
(864, "IBM864"), # OEM Arabic; Arabic (864)
(865, "IBM865"), # OEM Nordic; Nordic (DOS)
(866, "cp866"), # OEM Russian; Cyrillic (DOS)
(869, "ibm869"), # OEM Modern Greek; Greek, Modern (DOS)
(870, "IBM870"), # IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
(874, "windows-874"), # ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows)
(875, "cp875"), # IBM EBCDIC Greek Modern
(932, "shift_jis"), # ANSI/OEM Japanese; Japanese (Shift-JIS)
(936, "gb2312"), # ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
(949, "ks_c_5601-1987"), # ANSI/OEM Korean (Unified Hangul Code)
(950, "big5"), # ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
(1026, "IBM1026"), # IBM EBCDIC Turkish (Latin 5)
(1047, "IBM01047"), # IBM EBCDIC Latin 1/Open System
(1140, "IBM01140"), # IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
(1141, "IBM01141"), # IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
(1142, "IBM01142"), # IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
(1143, "IBM01143"), # IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
(1144, "IBM01144"), # IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
(1145, "IBM01145"), # IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
(1146, "IBM01146"), # IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
(1147, "IBM01147"), # IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
(1148, "IBM01148"), # IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
(1149, "IBM01149"), # IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
(1200, "utf-16"), # Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
(1201, "unicodeFFFE"), # Unicode UTF-16, big endian byte order; available only to managed applications
(1250, "windows-1250"), # ANSI Central European; Central European (Windows)
(1251, "windows-1251"), # ANSI Cyrillic; Cyrillic (Windows)
(1252, "windows-1252"), # ANSI Latin 1; Western European (Windows)
(1253, "windows-1253"), # ANSI Greek; Greek (Windows)
(1254, "windows-1254"), # ANSI Turkish; Turkish (Windows)
(1255, "windows-1255"), # ANSI Hebrew; Hebrew (Windows)
(1256, "windows-1256"), # ANSI Arabic; Arabic (Windows)
(1257, "windows-1257"), # ANSI Baltic; Baltic (Windows)
(1258, "windows-1258"), # ANSI/OEM Vietnamese; Vietnamese (Windows)
(1250, "cp-1250"), # ANSI Central European; Central European (Windows)
(1251, "cp-1251"), # ANSI Cyrillic; Cyrillic (Windows)
(1252, "cp-1252"), # ANSI Latin 1; Western European (Windows)
(1253, "cp-1253"), # ANSI Greek; Greek (Windows)
(1254, "cp-1254"), # ANSI Turkish; Turkish (Windows)
(1255, "cp-1255"), # ANSI Hebrew; Hebrew (Windows)
(1256, "cp-1256"), # ANSI Arabic; Arabic (Windows)
(1257, "cp-1257"), # ANSI Baltic; Baltic (Windows)
(1258, "cp-1258"), # ANSI/OEM Vietnamese; Vietnamese (Windows)
(1250, "cp-1250"), # ANSI Central European; Central European (Windows)
(1251, "cp-1251"), # ANSI Cyrillic; Cyrillic (Windows)
(1252, "cp-1252"), # ANSI Latin 1; Western European (Windows)
(1253, "cp-1253"), # ANSI Greek; Greek (Windows)
(1254, "cp-1254"), # ANSI Turkish; Turkish (Windows)
(1255, "cp-1255"), # ANSI Hebrew; Hebrew (Windows)
(1256, "cp-1256"), # ANSI Arabic; Arabic (Windows)
(1257, "cp-1257"), # ANSI Baltic; Baltic (Windows)
(1258, "cp-1258"), # ANSI/OEM Vietnamese; Vietnamese (Windows)
(1361, "Johab"), # Korean (Johab)
(10000, "macintosh"), # MAC Roman; Western European (Mac)
(10001, "x-mac-japanese"), # Japanese (Mac)
(10002, "x-mac-chinesetrad"), # MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
(10003, "x-mac-korean"), # Korean (Mac)
(10004, "x-mac-arabic"), # Arabic (Mac)
(10005, "x-mac-hebrew"), # Hebrew (Mac)
(10006, "x-mac-greek"), # Greek (Mac)
(10007, "x-mac-cyrillic"), # Cyrillic (Mac)
(10008, "x-mac-chinesesimp"), # MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
(10010, "x-mac-romanian"), # Romanian (Mac)
(10017, "x-mac-ukrainian"), # Ukrainian (Mac)
(10021, "x-mac-thai"), # Thai (Mac)
(10029, "x-mac-ce"), # MAC Latin 2; Central European (Mac)
(10079, "x-mac-icelandic"), # Icelandic (Mac)
(10081, "x-mac-turkish"), # Turkish (Mac)
(10082, "x-mac-croatian"), # Croatian (Mac)
(12000, "utf-32"), # Unicode UTF-32, little endian byte order; available only to managed applications
(12001, "utf-32BE"), # Unicode UTF-32, big endian byte order; available only to managed applications
(20000, "x-Chinese_CNS"), # CNS Taiwan; Chinese Traditional (CNS)
(20001, "x-cp20001"), # TCA Taiwan
(20002, "x_Chinese-Eten"), # Eten Taiwan; Chinese Traditional (Eten)
(20003, "x-cp20003"), # IBM5550 Taiwan
(20004, "x-cp20004"), # TeleText Taiwan
(20005, "x-cp20005"), # Wang Taiwan
(20105, "x-IA5"), # IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
(20106, "x-IA5-German"), # IA5 German (7-bit)
(20107, "x-IA5-Swedish"), # IA5 Swedish (7-bit)
(20108, "x-IA5-Norwegian"), # IA5 Norwegian (7-bit)
(20127, "us-ascii"), # US-ASCII (7-bit)
(20261, "x-cp20261"), # T.61
(20269, "x-cp20269"), # ISO 6937 Non-Spacing Accent
(20273, "IBM273"), # IBM EBCDIC Germany
(20277, "IBM277"), # IBM EBCDIC Denmark-Norway
(20278, "IBM278"), # IBM EBCDIC Finland-Sweden
(20280, "IBM280"), # IBM EBCDIC Italy
(20284, "IBM284"), # IBM EBCDIC Latin America-Spain
(20285, "IBM285"), # IBM EBCDIC United Kingdom
(20290, "IBM290"), # IBM EBCDIC Japanese Katakana Extended
(20297, "IBM297"), # IBM EBCDIC France
(20420, "IBM420"), # IBM EBCDIC Arabic
(20423, "IBM423"), # IBM EBCDIC Greek
(20424, "IBM424"), # IBM EBCDIC Hebrew
(20833, "x-EBCDIC-KoreanExtended"), # IBM EBCDIC Korean Extended
(20838, "IBM-Thai"), # IBM EBCDIC Thai
(20866, "koi8-r"), # Russian (KOI8-R); Cyrillic (KOI8-R)
(20871, "IBM871"), # IBM EBCDIC Icelandic
(20880, "IBM880"), # IBM EBCDIC Cyrillic Russian
(20905, "IBM905"), # IBM EBCDIC Turkish
(20924, "IBM00924"), # IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
(20932, "EUC-JP"), # Japanese (JIS 0208-1990 and 0121-1990)
(20936, "x-cp20936"), # Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
(20949, "x-cp20949"), # Korean Wansung
(21025, "cp1025"), # IBM EBCDIC Cyrillic Serbian-Bulgarian
(21027, ""), # (deprecated)
(21866, "koi8-u"), # Ukrainian (KOI8-U); Cyrillic (KOI8-U)
(28591, "iso-8859-1"), # ISO 8859-1 Latin 1; Western European (ISO)
(28592, "iso-8859-2"), # ISO 8859-2 Central European; Central European (ISO)
(28593, "iso-8859-3"), # ISO 8859-3 Latin 3
(28594, "iso-8859-4"), # ISO 8859-4 Baltic
(28595, "iso-8859-5"), # ISO 8859-5 Cyrillic
(28596, "iso-8859-6"), # ISO 8859-6 Arabic
(28597, "iso-8859-7"), # ISO 8859-7 Greek
(28598, "iso-8859-8"), # ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
(28599, "iso-8859-9"), # ISO 8859-9 Turkish
(28603, "iso-8859-13"), # ISO 8859-13 Estonian
(28605, "iso-8859-15"), # ISO 8859-15 Latin 9
(29001, "x-Europa"), # Europa 3
(38598, "iso-8859-8-i"), # ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
(50220, "iso-2022-jp"), # ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
(50221, "csISO2022JP"), # ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
(50222, "iso-2022-jp"), # ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
(50225, "iso-2022-kr"), # ISO 2022 Korean
(50227, "x-cp50227"), # ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
(50229, ""), # ISO 2022 Traditional Chinese
(50930, ""), # EBCDIC Japanese (Katakana) Extended
(50931, ""), # EBCDIC US-Canada and Japanese
(50933, ""), # EBCDIC Korean Extended and Korean
(50935, ""), # EBCDIC Simplified Chinese Extended and Simplified Chinese
(50936, ""), # EBCDIC Simplified Chinese
(50937, ""), # EBCDIC US-Canada and Traditional Chinese
(50939, ""), # EBCDIC Japanese (Latin) Extended and Japanese
(51932, "euc-jp"), # EUC Japanese
(51936, "EUC-CN"), # EUC Simplified Chinese; Chinese Simplified (EUC)
(51949, "euc-kr"), # EUC Korean
(51950, ""), # EUC Traditional Chinese
(52936, "hz-gb-2312"), # HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
(54936, "GB18030"), # Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
(57002, "x-iscii-de"), # ISCII Devanagari
(57003, "x-iscii-be"), # ISCII Bengali
(57004, "x-iscii-ta"), # ISCII Tamil
(57005, "x-iscii-te"), # ISCII Telugu
(57006, "x-iscii-as"), # ISCII Assamese
(57007, "x-iscii-or"), # ISCII Oriya
(57008, "x-iscii-ka"), # ISCII Kannada
(57009, "x-iscii-ma"), # ISCII Malayalam
(57010, "x-iscii-gu"), # ISCII Gujarati
(57011, "x-iscii-pa"), # ISCII Punjabi
(65000, "utf-7"), # Unicode (UTF-7)
(65001, "utf-8")] # Unicode (UTF-8)
(1361, "Johab"), # Korean (Johab)
(10000, "macintosh"), # MAC Roman; Western European (Mac)
(10001, "x-mac-japanese"), # Japanese (Mac)
(10002, "x-mac-chinesetrad"), # MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
(10003, "x-mac-korean"), # Korean (Mac)
(10004, "x-mac-arabic"), # Arabic (Mac)
(10005, "x-mac-hebrew"), # Hebrew (Mac)
(10006, "x-mac-greek"), # Greek (Mac)
(10007, "x-mac-cyrillic"), # Cyrillic (Mac)
(10008, "x-mac-chinesesimp"), # MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
(10010, "x-mac-romanian"), # Romanian (Mac)
(10017, "x-mac-ukrainian"), # Ukrainian (Mac)
(10021, "x-mac-thai"), # Thai (Mac)
(10029, "x-mac-ce"), # MAC Latin 2; Central European (Mac)
(10079, "x-mac-icelandic"), # Icelandic (Mac)
(10081, "x-mac-turkish"), # Turkish (Mac)
(10082, "x-mac-croatian"), # Croatian (Mac)
(12000, "utf-32"), # Unicode UTF-32, little endian byte order; available only to managed applications
(12001, "utf-32BE"), # Unicode UTF-32, big endian byte order; available only to managed applications
(20000, "x-Chinese_CNS"), # CNS Taiwan; Chinese Traditional (CNS)
(20001, "x-cp20001"), # TCA Taiwan
(20002, "x_Chinese-Eten"), # Eten Taiwan; Chinese Traditional (Eten)
(20003, "x-cp20003"), # IBM5550 Taiwan
(20004, "x-cp20004"), # TeleText Taiwan
(20005, "x-cp20005"), # Wang Taiwan
(20105, "x-IA5"), # IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
(20106, "x-IA5-German"), # IA5 German (7-bit)
(20107, "x-IA5-Swedish"), # IA5 Swedish (7-bit)
(20108, "x-IA5-Norwegian"), # IA5 Norwegian (7-bit)
(20127, "us-ascii"), # US-ASCII (7-bit)
(20261, "x-cp20261"), # T.61
(20269, "x-cp20269"), # ISO 6937 Non-Spacing Accent
(20273, "IBM273"), # IBM EBCDIC Germany
(20277, "IBM277"), # IBM EBCDIC Denmark-Norway
(20278, "IBM278"), # IBM EBCDIC Finland-Sweden
(20280, "IBM280"), # IBM EBCDIC Italy
(20284, "IBM284"), # IBM EBCDIC Latin America-Spain
(20285, "IBM285"), # IBM EBCDIC United Kingdom
(20290, "IBM290"), # IBM EBCDIC Japanese Katakana Extended
(20297, "IBM297"), # IBM EBCDIC France
(20420, "IBM420"), # IBM EBCDIC Arabic
(20423, "IBM423"), # IBM EBCDIC Greek
(20424, "IBM424"), # IBM EBCDIC Hebrew
(20833, "x-EBCDIC-KoreanExtended"), # IBM EBCDIC Korean Extended
(20838, "IBM-Thai"), # IBM EBCDIC Thai
(20866, "koi8-r"), # Russian (KOI8-R); Cyrillic (KOI8-R)
(20871, "IBM871"), # IBM EBCDIC Icelandic
(20880, "IBM880"), # IBM EBCDIC Cyrillic Russian
(20905, "IBM905"), # IBM EBCDIC Turkish
(20924, "IBM00924"), # IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
(20932, "EUC-JP"), # Japanese (JIS 0208-1990 and 0121-1990)
(20936, "x-cp20936"), # Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
(20949, "x-cp20949"), # Korean Wansung
(21025, "cp1025"), # IBM EBCDIC Cyrillic Serbian-Bulgarian
(21027, ""), # (deprecated)
(21866, "koi8-u"), # Ukrainian (KOI8-U); Cyrillic (KOI8-U)
(28591, "iso-8859-1"), # ISO 8859-1 Latin 1; Western European (ISO)
(28592, "iso-8859-2"), # ISO 8859-2 Central European; Central European (ISO)
(28593, "iso-8859-3"), # ISO 8859-3 Latin 3
(28594, "iso-8859-4"), # ISO 8859-4 Baltic
(28595, "iso-8859-5"), # ISO 8859-5 Cyrillic
(28596, "iso-8859-6"), # ISO 8859-6 Arabic
(28597, "iso-8859-7"), # ISO 8859-7 Greek
(28598, "iso-8859-8"), # ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
(28599, "iso-8859-9"), # ISO 8859-9 Turkish
(28603, "iso-8859-13"), # ISO 8859-13 Estonian
(28605, "iso-8859-15"), # ISO 8859-15 Latin 9
(29001, "x-Europa"), # Europa 3
(38598, "iso-8859-8-i"), # ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
(50220, "iso-2022-jp"), # ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
(50221, "csISO2022JP"), # ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
(50222, "iso-2022-jp"), # ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
(50225, "iso-2022-kr"), # ISO 2022 Korean
(50227, "x-cp50227"), # ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
(50229, ""), # ISO 2022 Traditional Chinese
(50930, ""), # EBCDIC Japanese (Katakana) Extended
(50931, ""), # EBCDIC US-Canada and Japanese
(50933, ""), # EBCDIC Korean Extended and Korean
(50935, ""), # EBCDIC Simplified Chinese Extended and Simplified Chinese
(50936, ""), # EBCDIC Simplified Chinese
(50937, ""), # EBCDIC US-Canada and Traditional Chinese
(50939, ""), # EBCDIC Japanese (Latin) Extended and Japanese
(51932, "euc-jp"), # EUC Japanese
(51936, "EUC-CN"), # EUC Simplified Chinese; Chinese Simplified (EUC)
(51949, "euc-kr"), # EUC Korean
(51950, ""), # EUC Traditional Chinese
(52936, "hz-gb-2312"), # HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
(54936, "GB18030"), # Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
(57002, "x-iscii-de"), # ISCII Devanagari
(57003, "x-iscii-be"), # ISCII Bengali
(57004, "x-iscii-ta"), # ISCII Tamil
(57005, "x-iscii-te"), # ISCII Telugu
(57006, "x-iscii-as"), # ISCII Assamese
(57007, "x-iscii-or"), # ISCII Oriya
(57008, "x-iscii-ka"), # ISCII Kannada
(57009, "x-iscii-ma"), # ISCII Malayalam
(57010, "x-iscii-gu"), # ISCII Gujarati
(57011, "x-iscii-pa"), # ISCII Punjabi
(65000, "utf-7"), # Unicode (UTF-7)
(65001, "utf-8")] # Unicode (UTF-8)
when false:
# not needed yet:
type
TCpInfo = object
CpInfo = object
maxCharSize: int32
defaultChar: array[0..1, char]
leadByte: array[0..12-1, char]
{.deprecated: [TCpInfo: CpInfo].}
proc getCPInfo(codePage: CodePage, lpCPInfo: var TCpInfo): int32 {.
proc getCPInfo(codePage: CodePage, lpCPInfo: var CpInfo): int32 {.
stdcall, importc: "GetCPInfo", dynlib: "kernel32".}
proc nameToCodePage(name: string): CodePage =
var nameAsInt: int
if parseInt(name, nameAsInt) == 0: nameAsInt = -1
for no, na in items(winEncodings):
if no == nameAsInt or eqEncodingNames(na, name): return CodePage(no)
result = CodePage(-1)
proc codePageToName(c: CodePage): string =
for no, na in items(winEncodings):
if no == int(c):
return if na.len != 0: na else: $no
result = ""
proc getACP(): CodePage {.stdcall, importc: "GetACP", dynlib: "kernel32".}
proc multiByteToWideChar(
codePage: CodePage,
dwFlags: int32,
@ -253,7 +254,7 @@ when defined(windows):
lpDefaultChar: cstring=nil,
lpUsedDefaultChar: pointer=nil): cint {.
stdcall, importc: "WideCharToMultiByte", dynlib: "kernel32".}
else:
when defined(haiku):
const iconvDll = "(libc.so.6|libiconv.so|libtextencoding.so)"
@ -262,7 +263,7 @@ else:
else:
const iconvDll = "(libc.so.6|libiconv.so)"
when defined(macosx) and defined(powerpc):
when defined(macosx):
const prefix = "lib"
else:
const prefix = ""
@ -291,31 +292,31 @@ else:
proc iconv(c: EncodingConverter, inbuf: pointer, inbytesLeft: pointer,
outbuf: var cstring, outbytesLeft: var int): int {.
importc: prefix & "iconv", cdecl, dynlib: iconvDll.}
proc getCurrentEncoding*(): string =
## retrieves the current encoding. On Unix, always "UTF-8" is returned.
when defined(windows):
result = codePageToName(getACP())
else:
result = "UTF-8"
proc open*(destEncoding = "UTF-8", srcEncoding = "CP1252"): EncodingConverter =
## opens a converter that can convert from `srcEncoding` to `destEncoding`.
## Raises `EIO` if it cannot fulfill the request.
when not defined(windows):
result = iconvOpen(destEncoding, srcEncoding)
if result == nil:
raise newException(EncodingError,
"cannot create encoding converter from " &
raise newException(EncodingError,
"cannot create encoding converter from " &
srcEncoding & " to " & destEncoding)
else:
result.dest = nameToCodePage(destEncoding)
result.src = nameToCodePage(srcEncoding)
if int(result.dest) == -1:
raise newException(EncodingError,
raise newException(EncodingError,
"cannot find encoding " & destEncoding)
if int(result.src) == -1:
raise newException(EncodingError,
raise newException(EncodingError,
"cannot find encoding " & srcEncoding)
proc close*(c: EncodingConverter) =
@ -327,7 +328,7 @@ when defined(windows):
proc convert*(c: EncodingConverter, s: string): string =
## converts `s` to `destEncoding` that was given to the converter `c`. It
## assumed that `s` is in `srcEncoding`.
# special case: empty string: needed because MultiByteToWideChar
# return 0 in case of error:
if s.len == 0: return ""
@ -335,21 +336,21 @@ when defined(windows):
var cap = s.len + s.len shr 2
result = newStringOfCap(cap*2)
# convert to utf-16 LE
var m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
var m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
lpMultiByteStr = cstring(s),
cbMultiByte = cint(s.len),
lpWideCharStr = cstring(result),
cchWideChar = cint(cap))
if m == 0:
if m == 0:
# try again; ask for capacity:
cap = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
cap = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
lpMultiByteStr = cstring(s),
cbMultiByte = cint(s.len),
lpWideCharStr = nil,
cchWideChar = cint(0))
# and do the conversion properly:
result = newStringOfCap(cap*2)
m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32,
lpMultiByteStr = cstring(s),
cbMultiByte = cint(s.len),
lpWideCharStr = cstring(result),
@ -360,7 +361,7 @@ when defined(windows):
setLen(result, m*2)
else:
assert(false) # cannot happen
# if already utf-16 LE, no further need to do something:
if int(c.dest) == 1200: return
# otherwise the fun starts again:
@ -427,7 +428,7 @@ else:
outLen = len(result) - offset
else:
raiseOSError(lerr.OSErrorCode)
# iconv has a buffer that needs flushing, specially if the last char is
# iconv has a buffer that needs flushing, specially if the last char is
# not '\0'
discard iconv(c, nil, nil, dst, outLen)
if iconvres == cint(-1) and errno == E2BIG:
@ -440,7 +441,7 @@ else:
# trim output buffer
setLen(result, len(result) - outLen)
proc convert*(s: string, destEncoding = "UTF-8",
proc convert*(s: string, destEncoding = "UTF-8",
srcEncoding = "CP1252"): string =
## converts `s` to `destEncoding`. It assumed that `s` is in `srcEncoding`.
## This opens a converter, uses it and closes it again and is thus more

View file

@ -24,7 +24,7 @@ proc swapEndian64*(outp, inp: pointer) =
o[6] = i[1]
o[7] = i[0]
proc swapEndian32*(outp, inp: pointer) =
proc swapEndian32*(outp, inp: pointer) =
## copies `inp` to `outp` swapping bytes. Both buffers are supposed to
## contain at least 4 bytes.
var i = cast[cstring](inp)
@ -34,7 +34,7 @@ proc swapEndian32*(outp, inp: pointer) =
o[2] = i[1]
o[3] = i[0]
proc swapEndian16*(outp, inp: pointer) =
proc swapEndian16*(outp, inp: pointer) =
## copies `inp` to `outp` swapping bytes. Both buffers are supposed to
## contain at least 2 bytes.
var
@ -50,7 +50,7 @@ when system.cpuEndian == bigEndian:
proc bigEndian64*(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 8)
proc bigEndian32*(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 4)
proc bigEndian16*(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 2)
else:
else:
proc littleEndian64*(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 8)
proc littleEndian32*(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 4)
proc littleEndian16*(outp, inp: pointer){.inline.} = copyMem(outp, inp, 2)

23
lib/pure/etcpriv.nim Normal file
View file

@ -0,0 +1,23 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Authors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains utils that are less then easy to categorize and
## don't really warrant a specific module. They are private to compiler
## and stdlib usage, and should not be used outside of that - they may
## change or disappear at any time.
# Used by pure/hashes.nim, and the compiler parsing
const magicIdentSeparatorRuneByteWidth* = 3
# Used by pure/hashes.nim, and the compiler parsing
proc isMagicIdentSeparatorRune*(cs: cstring, i: int): bool {. inline } =
result = cs[i] == '\226' and
cs[i + 1] == '\128' and
cs[i + 2] == '\147' # en-dash # 145 = nb-hyphen

View file

@ -1,16 +1,16 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2011 Alex Mitchell
# (c) Copyright 2011 Alexander Mitchell-Robinson
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## :Author: Alex Mitchell
## :Author: Alexander Mitchell-Robinson (Amrykid)
##
## This module implements an event system that is not dependent on external
## graphical toolkits. It was originally called ``NimEE`` because
## graphical toolkits. It was originally called ``NimEE`` because
## it was inspired by Python's PyEE module. There are two ways you can use
## events: one is a python-inspired way; the other is more of a C-style way.
##
@ -23,7 +23,7 @@
## # Python way
## ee.on("EventName", handleevent)
## ee.emit("EventName", genericargs)
##
##
## # C/Java way
## # Declare a type
## type
@ -45,7 +45,7 @@ type
{.deprecated: [TEventArgs: EventArgs, TEventHandler: EventHandler,
TEventEmitter: EventEmitter, EInvalidEvent: EventError].}
proc initEventHandler*(name: string): EventHandler =
## Initializes an EventHandler with the specified name and returns it.
result.handlers = @[]
@ -61,7 +61,7 @@ proc removeHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.
if fn == handler.handlers[i]:
handler.handlers.del(i)
break
proc containsHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.}): bool =
## Checks if a callback is registered to this event handler.
return handler.handlers.contains(fn)
@ -86,8 +86,8 @@ proc on*(emitter: var EventEmitter, event: string, fn: proc(e: EventArgs) {.clos
emitter.s.add(eh)
else:
addHandler(emitter.s[i], fn)
proc emit*(emitter: var EventEmitter, eventhandler: var EventHandler,
proc emit*(emitter: var EventEmitter, eventhandler: var EventHandler,
args: EventArgs) =
## Fires an event handler with specified event arguments.
for fn in items(eventhandler.handlers): fn(args)

View file

@ -30,7 +30,7 @@ type
fd: cint
handleEvent: proc (m: FSMonitor, ev: MonitorEvent) {.closure.}
targets: Table[cint, string]
MonitorEventType* = enum ## Monitor event type
MonitorAccess, ## File was accessed.
MonitorAttrib, ## Metadata changed.
@ -44,7 +44,7 @@ type
MonitorMoved, ## File was moved.
MonitorOpen, ## File was opened.
MonitorAll ## Filter for all event types.
MonitorEvent* = object
case kind*: MonitorEventType ## Type of the event.
of MonitorMoveSelf, MonitorMoved:
@ -77,7 +77,7 @@ proc add*(monitor: FSMonitor, target: string,
## Adds ``target`` which may be a directory or a file to the list of
## watched paths of ``monitor``.
## You can specify the events to report using the ``filters`` parameter.
var INFilter = -1
for f in filters:
case f
@ -93,7 +93,7 @@ proc add*(monitor: FSMonitor, target: string,
of MonitorMoved: INFilter = INFilter and IN_MOVED_FROM and IN_MOVED_TO
of MonitorOpen: INFilter = INFilter and IN_OPEN
of MonitorAll: INFilter = INFilter and IN_ALL_EVENTS
result = inotifyAddWatch(monitor.fd, target, INFilter.uint32)
if result < 0:
raiseOSError(osLastError())
@ -108,7 +108,7 @@ proc del*(monitor: FSMonitor, wd: cint) =
proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
result = @[]
let size = (sizeof(TINotifyEvent)+2000)*MaxEvents
let size = (sizeof(INotifyEvent)+2000)*MaxEvents
var buffer = newString(size)
let le = read(fd, addr(buffer[0]), size)
@ -117,7 +117,7 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
var i = 0
while i < le:
var event = cast[ptr TINotifyEvent](addr(buffer[i]))
var event = cast[ptr INotifyEvent](addr(buffer[i]))
var mev: MonitorEvent
mev.wd = event.wd
if event.len.int != 0:
@ -125,13 +125,13 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
mev.name = $cstr
else:
mev.name = ""
if (event.mask.int and IN_MOVED_FROM) != 0:
if (event.mask.int and IN_MOVED_FROM) != 0:
# Moved from event, add to m's collection
movedFrom.add(event.cookie.cint, (mev.wd, mev.name))
inc(i, sizeof(TINotifyEvent) + event.len.int)
inc(i, sizeof(INotifyEvent) + event.len.int)
continue
elif (event.mask.int and IN_MOVED_TO) != 0:
elif (event.mask.int and IN_MOVED_TO) != 0:
mev.kind = MonitorMoved
assert movedFrom.hasKey(event.cookie.cint)
# Find the MovedFrom event.
@ -141,25 +141,25 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
movedFrom.del(event.cookie.cint)
elif (event.mask.int and IN_ACCESS) != 0: mev.kind = MonitorAccess
elif (event.mask.int and IN_ATTRIB) != 0: mev.kind = MonitorAttrib
elif (event.mask.int and IN_CLOSE_WRITE) != 0:
elif (event.mask.int and IN_CLOSE_WRITE) != 0:
mev.kind = MonitorCloseWrite
elif (event.mask.int and IN_CLOSE_NOWRITE) != 0:
elif (event.mask.int and IN_CLOSE_NOWRITE) != 0:
mev.kind = MonitorCloseNoWrite
elif (event.mask.int and IN_CREATE) != 0: mev.kind = MonitorCreate
elif (event.mask.int and IN_DELETE) != 0:
elif (event.mask.int and IN_DELETE) != 0:
mev.kind = MonitorDelete
elif (event.mask.int and IN_DELETE_SELF) != 0:
elif (event.mask.int and IN_DELETE_SELF) != 0:
mev.kind = MonitorDeleteSelf
elif (event.mask.int and IN_MODIFY) != 0: mev.kind = MonitorModify
elif (event.mask.int and IN_MOVE_SELF) != 0:
elif (event.mask.int and IN_MOVE_SELF) != 0:
mev.kind = MonitorMoveSelf
elif (event.mask.int and IN_OPEN) != 0: mev.kind = MonitorOpen
if mev.kind != MonitorMoved:
mev.fullname = ""
result.add(mev)
inc(i, sizeof(TINotifyEvent) + event.len.int)
inc(i, sizeof(INotifyEvent) + event.len.int)
# If movedFrom events have not been matched with a moveTo. File has
# been moved to an unwatched location, emit a MonitorDelete.
@ -211,7 +211,7 @@ when not defined(testing) and isMainModule:
echo("Name is ", ev.name)
else:
echo("Name ", ev.name, " fullname ", ev.fullName))
while true:
if not disp.poll(): break
main()

View file

@ -75,7 +75,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} =
identDefs.add(newEmptyNode())
of nnkIdent:
identDefs.add(c)
identDefs.add(newEmptyNode())
identDefs.add(newIdentNode("auto"))
identDefs.add(newEmptyNode())
of nnkInfix:
if c[0].kind == nnkIdent and c[0].ident == !"->":
@ -93,7 +93,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} =
of nnkIdent:
var identDefs = newNimNode(nnkIdentDefs)
identDefs.add(p)
identDefs.add(newEmptyNode())
identDefs.add(newIdentNode("auto"))
identDefs.add(newEmptyNode())
params.add(identDefs)
of nnkInfix:

View file

@ -18,20 +18,22 @@ import
os, hashes, strutils
type
TGenTableMode* = enum ## describes the table's key matching mode
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
TGenKeyValuePair[T] = tuple[key: string, val: T]
TGenKeyValuePairSeq[T] = seq[TGenKeyValuePair[T]]
TGenTable*[T] = object of RootObj
GenKeyValuePair[T] = tuple[key: string, val: T]
GenKeyValuePairSeq[T] = seq[GenKeyValuePair[T]]
GenTable*[T] = object of RootObj
counter: int
data: TGenKeyValuePairSeq[T]
mode: TGenTableMode
data: GenKeyValuePairSeq[T]
mode: GenTableMode
PGenTable*[T] = ref TGenTable[T] ## use this type to declare hash tables
PGenTable*[T] = ref GenTable[T] ## use this type to declare hash tables
{.deprecated: [TGenTableMode: GenTableMode, TGenKeyValuePair: GenKeyValuePair,
TGenKeyValuePairSeq: GenKeyValuePairSeq, TGenTable: GenTable].}
const
growthFactor = 2
@ -48,7 +50,7 @@ iterator pairs*[T](tbl: PGenTable[T]): tuple[key: string, value: T] =
if not isNil(tbl.data[h].key):
yield (tbl.data[h].key, tbl.data[h].val)
proc myhash[T](tbl: PGenTable[T], key: string): THash =
proc myhash[T](tbl: PGenTable[T], key: string): Hash =
case tbl.mode
of modeCaseSensitive: result = hashes.hash(key)
of modeCaseInsensitive: result = hashes.hashIgnoreCase(key)
@ -64,18 +66,18 @@ proc mustRehash(length, counter: int): bool =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc newGenTable*[T](mode: TGenTableMode): PGenTable[T] =
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: THash): THash {.inline.} =
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc rawGet[T](tbl: PGenTable[T], key: string): int =
var h: THash
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):
@ -83,9 +85,9 @@ proc rawGet[T](tbl: PGenTable[T], key: string): int =
h = nextTry(h, high(tbl.data))
result = - 1
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
proc rawInsert[T](tbl: PGenTable[T], data: var GenKeyValuePairSeq[T],
key: string, val: T) =
var h: THash
var h: Hash
h = myhash(tbl, key) and high(data)
while not isNil(data[h].key):
h = nextTry(h, high(data))
@ -93,7 +95,7 @@ proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
data[h].val = val
proc enlarge[T](tbl: PGenTable[T]) =
var n: TGenKeyValuePairSeq[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):
@ -146,19 +148,20 @@ when isMainModule:
# Verify a table of user-defined types
#
type
TMyType = tuple[first, second: string] # a pair of strings
MyType = tuple[first, second: string] # a pair of strings
{.deprecated: [TMyType: MyType].}
var y = newGenTable[TMyType](modeCaseInsensitive) # hash table where each
# value is TMyType tuple
var y = newGenTable[MyType](modeCaseInsensitive) # hash table where each
# value is MyType tuple
#var junk: TMyType = ("OK", "Here")
#var junk: MyType = ("OK", "Here")
#echo junk.first, " ", junk.second
y["Hello"] = ("Hello", "World")
y["Goodbye"] = ("Goodbye", "Everyone")
#y["Hello"] = TMyType( ("Hello", "World") )
#y["Goodbye"] = TMyType( ("Goodbye", "Everyone") )
#y["Hello"] = MyType( ("Hello", "World") )
#y["Goodbye"] = MyType( ("Goodbye", "Everyone") )
assert( not isNil(y["Hello"].first) )
assert( y["Hello"].first == "Hello" )

View file

@ -8,16 +8,17 @@
#
## This module implements efficient computations of hash values for diverse
## Nim types. All the procs are based on these two building blocks: the `!&
## proc <#!&>`_ used to start or mix a hash value, and the `!$ proc <#!$>`_
## used to *finish* the hash value. If you want to implement hash procs for
## Nim types. All the procs are based on these two building blocks:
## - `!& proc <#!&>`_ used to start or mix a hash value, and
## - `!$ proc <#!$>`_ used to *finish* the hash value.
## If you want to implement hash procs for
## your custom types you will end up writing the following kind of skeleton of
## code:
##
## .. code-block:: Nim
## proc hash(x: Something): THash =
## ## Computes a THash from `x`.
## var h: THash = 0
## proc hash(x: Something): Hash =
## ## Computes a Hash from `x`.
## var h: Hash = 0
## # Iterate over parts of `x`.
## for xAtom in x:
## # Mix the atom with the partial hash.
@ -30,38 +31,39 @@
## together the hash value of the individual fields:
##
## .. code-block:: Nim
## proc hash(x: Something): THash =
## ## Computes a THash from `x`.
## var h: THash = 0
## proc hash(x: Something): Hash =
## ## Computes a Hash from `x`.
## var h: Hash = 0
## h = h !& hash(x.foo)
## h = h !& hash(x.bar)
## result = !$h
import
strutils
import
strutils, etcpriv
type
THash* = int ## a hash value; hash tables using these values should
type
Hash* = int ## a hash value; hash tables using these values should
## always have a size of a power of two and can use the ``and``
## operator instead of ``mod`` for truncation of the hash value.
{.deprecated: [THash: Hash].}
proc `!&`*(h: THash, val: int): THash {.inline.} =
proc `!&`*(h: Hash, val: int): Hash {.inline.} =
## mixes a hash value `h` with `val` to produce a new hash value. This is
## only needed if you need to implement a hash proc for a new datatype.
result = h +% val
result = result +% result shl 10
result = result xor (result shr 6)
proc `!$`*(h: THash): THash {.inline.} =
proc `!$`*(h: Hash): Hash {.inline.} =
## finishes the computation of the hash value. This is
## only needed if you need to implement a hash proc for a new datatype.
result = h +% h shl 3
result = result xor (result shr 11)
result = result +% result shl 15
proc hashData*(data: pointer, size: int): THash =
proc hashData*(data: pointer, size: int): Hash =
## hashes an array of bytes of size `size`
var h: THash = 0
var h: Hash = 0
when defined(js):
var p: cstring
asm """`p` = `Data`;"""
@ -69,7 +71,7 @@ proc hashData*(data: pointer, size: int): THash =
var p = cast[cstring](data)
var i = 0
var s = size
while s > 0:
while s > 0:
h = h !& ord(p[i])
inc(i)
dec(s)
@ -78,7 +80,7 @@ proc hashData*(data: pointer, size: int): THash =
when defined(js):
var objectID = 0
proc hash*(x: pointer): THash {.inline.} =
proc hash*(x: pointer): Hash {.inline.} =
## efficient hashing of pointers
when defined(js):
asm """
@ -92,71 +94,155 @@ proc hash*(x: pointer): THash {.inline.} =
}
"""
else:
result = (cast[THash](x)) shr 3 # skip the alignment
result = (cast[Hash](x)) shr 3 # skip the alignment
when not defined(booting):
proc hash*[T: proc](x: T): THash {.inline.} =
proc hash*[T: proc](x: T): Hash {.inline.} =
## efficient hashing of proc vars; closures are supported too.
when T is "closure":
result = hash(rawProc(x)) !& hash(rawEnv(x))
else:
result = hash(pointer(x))
proc hash*(x: int): THash {.inline.} =
proc hash*(x: int): Hash {.inline.} =
## efficient hashing of integers
result = x
proc hash*(x: int64): THash {.inline.} =
## efficient hashing of integers
proc hash*(x: int64): Hash {.inline.} =
## efficient hashing of int64 integers
result = toU32(x)
proc hash*(x: char): THash {.inline.} =
proc hash*(x: char): Hash {.inline.} =
## efficient hashing of characters
result = ord(x)
proc hash*(x: string): THash =
proc hash*[T: Ordinal](x: T): Hash {.inline.} =
## efficient hashing of other ordinal types (e.g., enums)
result = ord(x)
proc hash*(x: string): Hash =
## efficient hashing of strings
var h: THash = 0
for i in 0..x.len-1:
var h: Hash = 0
for i in 0..x.len-1:
h = h !& ord(x[i])
result = !$h
proc hashIgnoreStyle*(x: string): THash =
proc hash*(sBuf: string, sPos, ePos: int): Hash =
## efficient hashing of a string buffer, from starting
## position `sPos` to ending position `ePos`
##
## ``hash(myStr, 0, myStr.high)`` is equivalent to ``hash(myStr)``
var h: Hash = 0
for i in sPos..ePos:
h = h !& ord(sBuf[i])
result = !$h
proc hashIgnoreStyle*(x: string): Hash =
## efficient hashing of strings; style is ignored
var h: THash = 0
for i in 0..x.len-1:
var h: Hash = 0
var i = 0
let xLen = x.len
while i < xLen:
var c = x[i]
if c == '_':
continue # skip _
if c in {'A'..'Z'}:
if c == '_':
inc(i)
elif isMagicIdentSeparatorRune(cstring(x), i):
inc(i, magicIdentSeparatorRuneByteWidth)
else:
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
inc(i)
result = !$h
proc hashIgnoreStyle*(sBuf: string, sPos, ePos: int): Hash =
## efficient hashing of a string buffer, from starting
## position `sPos` to ending position `ePos`; style is ignored
##
## ``hashIgnoreStyle(myBuf, 0, myBuf.high)`` is equivalent
## to ``hashIgnoreStyle(myBuf)``
var h: Hash = 0
var i = sPos
while i <= ePos:
var c = sBuf[i]
if c == '_':
inc(i)
elif isMagicIdentSeparatorRune(cstring(sBuf), i):
inc(i, magicIdentSeparatorRuneByteWidth)
else:
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
inc(i)
result = !$h
proc hashIgnoreCase*(x: string): Hash =
## efficient hashing of strings; case is ignored
var h: Hash = 0
for i in 0..x.len-1:
var c = x[i]
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
result = !$h
proc hashIgnoreCase*(x: string): THash =
## efficient hashing of strings; case is ignored
var h: THash = 0
for i in 0..x.len-1:
var c = x[i]
if c in {'A'..'Z'}:
proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash =
## efficient hashing of a string buffer, from starting
## position `sPos` to ending position `ePos`; case is ignored
##
## ``hashIgnoreCase(myBuf, 0, myBuf.high)`` is equivalent
## to ``hashIgnoreCase(myBuf)``
var h: Hash = 0
for i in sPos..ePos:
var c = sBuf[i]
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
result = !$h
proc hash*[T: tuple](x: T): THash =
proc hash*(x: float): Hash {.inline.} =
## efficient hashing of floats.
var y = x + 1.0
result = cast[ptr Hash](addr(y))[]
# Forward declarations before methods that hash containers. This allows
# containers to contain other containers
proc hash*[A](x: openArray[A]): Hash
proc hash*[A](x: set[A]): Hash
proc hash*[T: tuple](x: T): Hash =
## efficient hashing of tuples.
for f in fields(x):
result = result !& hash(f)
result = !$result
proc hash*(x: float): THash {.inline.} =
var y = x + 1.0
result = cast[ptr THash](addr(y))[]
proc hash*[A](x: openArray[A]): THash =
proc hash*[A](x: openArray[A]): Hash =
## efficient hashing of arrays and sequences.
for it in items(x): result = result !& hash(it)
result = !$result
proc hash*[A](x: set[A]): THash =
proc hash*[A](aBuf: openArray[A], sPos, ePos: int): Hash =
## efficient hashing of portions of arrays and sequences.
##
## ``hash(myBuf, 0, myBuf.high)`` is equivalent to ``hash(myBuf)``
for i in sPos..ePos:
result = result !& hash(aBuf[i])
result = !$result
proc hash*[A](x: set[A]): Hash =
## efficient hashing of sets.
for it in items(x): result = result !& hash(it)
result = !$result
when isMainModule:
doAssert( hash("aa bb aaaa1234") == hash("aa bb aaaa1234", 0, 13) )
doAssert( hashIgnoreCase("aa bb aaaa1234") == hash("aa bb aaaa1234") )
doAssert( hashIgnoreStyle("aa bb aaaa1234") == hashIgnoreCase("aa bb aaaa1234") )
let xx = @['H','e','l','l','o']
let ss = "Hello"
doAssert( hash(xx) == hash(ss) )
doAssert( hash(xx) == hash(xx, 0, xx.high) )
doAssert( hash(ss) == hash(ss, 0, ss.high) )

View file

@ -33,7 +33,7 @@ import
const
coreAttr* = " id class title style "
eventAttr* = " onclick ondblclick onmousedown onmouseup " &
"onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup "
"onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup onload "
commonAttr* = coreAttr & eventAttr
proc getIdent(e: NimNode): string {.compileTime.} =

View file

@ -9,7 +9,7 @@
## This module parses an HTML document and creates its XML tree representation.
## It is supposed to handle the *wild* HTML the real world uses.
##
##
## It can be used to parse a wild HTML document and output it as valid XHTML
## document (well, if you are lucky):
##
@ -19,7 +19,7 @@
##
## Every tag in the resulting tree is in lower case.
##
## **Note:** The resulting ``PXmlNode`` already uses the ``clientData`` field,
## **Note:** The resulting ``XmlNode`` already uses the ``clientData`` field,
## so it cannot be used by clients of this library.
##
## Example: Transforming hyperlinks
@ -32,8 +32,8 @@
## .. code-block:: Nim
##
## import htmlparser
## import xmltree # To use '$' for PXmlNode
## import strtabs # To access PXmlAttributes
## import xmltree # To use '$' for XmlNode
## import strtabs # To access XmlAttributes
## import os # To use splitFile
## import strutils # To use cmpIgnoreCase
##
@ -52,7 +52,7 @@
import strutils, streams, parsexml, xmltree, unicode, strtabs
type
THtmlTag* = enum ## list of all supported HTML tags; order will always be
HtmlTag* = enum ## list of all supported HTML tags; order will always be
## alphabetically
tagUnknown, ## unknown HTML element
tagA, ## the HTML ``a`` element
@ -178,27 +178,28 @@ type
tagVar, ## the HTML ``var`` element
tagVideo, ## the HTML ``video`` element
tagWbr ## the HTML ``wbr`` element
{.deprecated: [THtmlTag: HtmlTag].}
const
tagToStr* = [
"a", "abbr", "acronym", "address", "applet", "area", "article",
"a", "abbr", "acronym", "address", "applet", "area", "article",
"aside", "audio",
"b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body",
"br", "button", "canvas", "caption", "center", "cite", "code",
"b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body",
"br", "button", "canvas", "caption", "center", "cite", "code",
"col", "colgroup", "command",
"datalist", "dd", "del", "details", "dfn", "dialog", "div",
"dir", "dl", "dt", "em", "embed", "fieldset",
"datalist", "dd", "del", "details", "dfn", "dialog", "div",
"dir", "dl", "dt", "em", "embed", "fieldset",
"figcaption", "figure", "font", "footer",
"form", "frame", "frameset", "h1", "h2", "h3",
"h4", "h5", "h6", "head", "header", "hgroup", "html", "hr",
"i", "iframe", "img", "input", "ins", "isindex",
"form", "frame", "frameset", "h1", "h2", "h3",
"h4", "h5", "h6", "head", "header", "hgroup", "html", "hr",
"i", "iframe", "img", "input", "ins", "isindex",
"kbd", "keygen", "label", "legend", "li", "link", "map", "mark",
"menu", "meta", "meter", "nav", "nobr", "noframes", "noscript",
"object", "ol",
"optgroup", "option", "output", "p", "param", "pre", "progress", "q",
"rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small",
"source", "span", "strike", "strong", "style",
"sub", "summary", "sup", "table",
"menu", "meta", "meter", "nav", "nobr", "noframes", "noscript",
"object", "ol",
"optgroup", "option", "output", "p", "param", "pre", "progress", "q",
"rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small",
"source", "span", "strike", "strong", "style",
"sub", "summary", "sup", "table",
"tbody", "td", "textarea", "tfoot", "th", "thead", "time",
"title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]
InlineTags* = {tagA, tagAbbr, tagAcronym, tagApplet, tagB, tagBasefont,
@ -206,17 +207,17 @@ const
tagEm, tagFont, tagI, tagImg, tagIns, tagInput, tagIframe, tagKbd,
tagLabel, tagMap, tagObject, tagQ, tagSamp, tagScript, tagSelect,
tagSmall, tagSpan, tagStrong, tagSub, tagSup, tagTextarea, tagTt,
tagVar, tagApplet, tagBasefont, tagFont, tagIframe, tagU, tagS,
tagVar, tagApplet, tagBasefont, tagFont, tagIframe, tagU, tagS,
tagStrike, tagWbr}
BlockTags* = {tagAddress, tagBlockquote, tagCenter, tagDel, tagDir, tagDiv,
tagDl, tagFieldset, tagForm, tagH1, tagH2, tagH3, tagH4,
tagH5, tagH6, tagHr, tagIns, tagIsindex, tagMenu, tagNoframes, tagNoscript,
tagOl, tagP, tagPre, tagTable, tagUl, tagCenter, tagDir, tagIsindex,
BlockTags* = {tagAddress, tagBlockquote, tagCenter, tagDel, tagDir, tagDiv,
tagDl, tagFieldset, tagForm, tagH1, tagH2, tagH3, tagH4,
tagH5, tagH6, tagHr, tagIns, tagIsindex, tagMenu, tagNoframes, tagNoscript,
tagOl, tagP, tagPre, tagTable, tagUl, tagCenter, tagDir, tagIsindex,
tagMenu, tagNoframes}
SingleTags* = {tagArea, tagBase, tagBasefont,
SingleTags* = {tagArea, tagBase, tagBasefont,
tagBr, tagCol, tagFrame, tagHr, tagImg, tagIsindex,
tagLink, tagMeta, tagParam, tagWbr}
Entities = [
("nbsp", 0x00A0), ("iexcl", 0x00A1), ("cent", 0x00A2), ("pound", 0x00A3),
("curren", 0x00A4), ("yen", 0x00A5), ("brvbar", 0x00A6), ("sect", 0x00A7),
@ -225,13 +226,13 @@ const
("deg", 0x00B0), ("plusmn", 0x00B1), ("sup2", 0x00B2), ("sup3", 0x00B3),
("acute", 0x00B4), ("micro", 0x00B5), ("para", 0x00B6), ("middot", 0x00B7),
("cedil", 0x00B8), ("sup1", 0x00B9), ("ordm", 0x00BA), ("raquo", 0x00BB),
("frac14", 0x00BC), ("frac12", 0x00BD), ("frac34", 0x00BE),
("frac14", 0x00BC), ("frac12", 0x00BD), ("frac34", 0x00BE),
("iquest", 0x00BF), ("Agrave", 0x00C0), ("Aacute", 0x00C1),
("Acirc", 0x00C2), ("Atilde", 0x00C3), ("Auml", 0x00C4), ("Aring", 0x00C5),
("AElig", 0x00C6), ("Ccedil", 0x00C7), ("Egrave", 0x00C8),
("Eacute", 0x00C9), ("Ecirc", 0x00CA), ("Euml", 0x00CB), ("Igrave", 0x00CC),
("Iacute", 0x00CD), ("Icirc", 0x00CE), ("Iuml", 0x00CF), ("ETH", 0x00D0),
("Ntilde", 0x00D1), ("Ograve", 0x00D2), ("Oacute", 0x00D3),
("Ntilde", 0x00D1), ("Ograve", 0x00D2), ("Oacute", 0x00D3),
("Ocirc", 0x00D4), ("Otilde", 0x00D5), ("Ouml", 0x00D6), ("times", 0x00D7),
("Oslash", 0x00D8), ("Ugrave", 0x00D9), ("Uacute", 0x00DA),
("Ucirc", 0x00DB), ("Uuml", 0x00DC), ("Yacute", 0x00DD), ("THORN", 0x00DE),
@ -263,7 +264,7 @@ const
("zwnj", 0x200C), ("zwj", 0x200D), ("lrm", 0x200E), ("rlm", 0x200F),
("ndash", 0x2013), ("mdash", 0x2014), ("lsquo", 0x2018), ("rsquo", 0x2019),
("sbquo", 0x201A), ("ldquo", 0x201C), ("rdquo", 0x201D), ("bdquo", 0x201E),
("dagger", 0x2020), ("Dagger", 0x2021), ("bull", 0x2022),
("dagger", 0x2020), ("Dagger", 0x2021), ("bull", 0x2022),
("hellip", 0x2026), ("permil", 0x2030), ("prime", 0x2032),
("Prime", 0x2033), ("lsaquo", 0x2039), ("rsaquo", 0x203A),
("oline", 0x203E), ("frasl", 0x2044), ("euro", 0x20AC),
@ -295,7 +296,7 @@ proc allLower(s: string): bool =
if c < 'a' or c > 'z': return false
return true
proc toHtmlTag(s: string): THtmlTag =
proc toHtmlTag(s: string): HtmlTag =
case s
of "a": tagA
of "abbr": tagAbbr
@ -422,19 +423,20 @@ proc toHtmlTag(s: string): THtmlTag =
of "wbr": tagWbr
else: tagUnknown
proc htmlTag*(n: XmlNode): THtmlTag =
## gets `n`'s tag as a ``THtmlTag``.
proc htmlTag*(n: XmlNode): HtmlTag =
## gets `n`'s tag as a ``HtmlTag``.
if n.clientData == 0:
n.clientData = toHtmlTag(n.tag).ord
result = THtmlTag(n.clientData)
result = HtmlTag(n.clientData)
proc htmlTag*(s: string): THtmlTag =
## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
proc htmlTag*(s: string): HtmlTag =
## converts `s` to a ``HtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
## returned.
let s = if allLower(s): s else: s.toLower
result = toHtmlTag(s)
proc entityToUtf8*(entity: string): string =
proc entityToUtf8*(entity: string): string =
## converts an HTML entity name like ``&Uuml;`` to its UTF-8 equivalent.
## "" is returned if the entity name is unknown. The HTML parser
## already converts entities to UTF-8.
@ -442,7 +444,7 @@ proc entityToUtf8*(entity: string): string =
if name == entity: return toUTF8(Rune(val))
result = ""
proc addNode(father, son: XmlNode) =
proc addNode(father, son: XmlNode) =
if son != nil: add(father, son)
proc parse(x: var XmlParser, errors: var seq[string]): XmlNode
@ -452,9 +454,9 @@ proc expected(x: var XmlParser, n: XmlNode): string =
template elemName(x: expr): expr = rawData(x)
proc untilElementEnd(x: var XmlParser, result: XmlNode,
proc untilElementEnd(x: var XmlParser, result: XmlNode,
errors: var seq[string]) =
# we parsed e.g. ``<br>`` and don't really expect a ``</br>``:
# we parsed e.g. ``<br>`` and don't really expect a ``</br>``:
if result.htmlTag in SingleTags:
if x.kind != xmlElementEnd or cmpIgnoreCase(x.elemName, result.tag) != 0:
return
@ -468,7 +470,7 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
tagOption}:
errors.add(expected(x, result))
break
of tagTd, tagTh, tagTfoot, tagThead:
of tagTd, tagTh:
if htmlTag(x.elemName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}:
errors.add(expected(x, result))
break
@ -482,11 +484,11 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
break
else: discard
result.addNode(parse(x, errors))
of xmlElementEnd:
if cmpIgnoreCase(x.elemName, result.tag) == 0:
of xmlElementEnd:
if cmpIgnoreCase(x.elemName, result.tag) == 0:
next(x)
else:
#echo "5; expected: ", result.htmltag, " ", x.elemName
#echo "5; expected: ", result.htmltag, " ", x.elemName
errors.add(expected(x, result))
# do not skip it here!
break
@ -498,7 +500,7 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
case x.kind
of xmlComment:
of xmlComment:
result = newComment(x.rawData)
next(x)
of xmlCharData, xmlWhitespace:
@ -516,11 +518,11 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
untilElementEnd(x, result, errors)
of xmlElementEnd:
errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
of xmlElementOpen:
of xmlElementOpen:
result = newElement(x.elemName.toLower)
next(x)
result.attrs = newStringTable()
while true:
while true:
case x.kind
of xmlAttribute:
result.attrs[x.rawData] = x.rawData2
@ -540,7 +542,7 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
of xmlAttribute, xmlElementClose:
errors.add(errorMsg(x, "<some_tag> expected"))
next(x)
of xmlCData:
of xmlCData:
result = newCData(x.rawData)
next(x)
of xmlEntity:
@ -549,8 +551,8 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
next(x)
of xmlEof: discard
proc parseHtml*(s: Stream, filename: string,
errors: var seq[string]): XmlNode =
proc parseHtml*(s: Stream, filename: string,
errors: var seq[string]): XmlNode =
## parses the XML from stream `s` and returns a ``PXmlNode``. Every
## occurred parsing error is added to the `errors` sequence.
var x: XmlParser
@ -558,7 +560,7 @@ proc parseHtml*(s: Stream, filename: string,
next(x)
# skip the DOCTYPE:
if x.kind == xmlSpecial: next(x)
result = newElement("document")
result.addNode(parse(x, errors))
#if x.kind != xmlEof:
@ -573,22 +575,22 @@ proc parseHtml*(s: Stream, filename: string,
if result.len == 1:
result = result[0]
proc parseHtml*(s: Stream): XmlNode =
proc parseHtml*(s: Stream): XmlNode =
## parses the XTML from stream `s` and returns a ``PXmlNode``. All parsing
## errors are ignored.
var errors: seq[string] = @[]
result = parseHtml(s, "unknown_html_doc", errors)
proc loadHtml*(path: string, errors: var seq[string]): XmlNode =
## Loads and parses HTML from file specified by ``path``, and returns
proc loadHtml*(path: string, errors: var seq[string]): XmlNode =
## Loads and parses HTML from file specified by ``path``, and returns
## a ``PXmlNode``. Every occurred parsing error is added to
## the `errors` sequence.
var s = newFileStream(path, fmRead)
if s == nil: raise newException(IOError, "Unable to read file: " & path)
result = parseHtml(s, path, errors)
proc loadHtml*(path: string): XmlNode =
## Loads and parses HTML from file specified by ``path``, and returns
proc loadHtml*(path: string): XmlNode =
## Loads and parses HTML from file specified by ``path``, and returns
## a ``PXmlNode``. All parsing errors are ignored.
var errors: seq[string] = @[]
result = loadHtml(path, errors)
@ -596,10 +598,10 @@ proc loadHtml*(path: string): XmlNode =
when not defined(testing) and isMainModule:
import os
var errors: seq[string] = @[]
var errors: seq[string] = @[]
var x = loadHtml(paramStr(1), errors)
for e in items(errors): echo e
var f: File
if open(f, "test.txt", fmWrite):
f.write($x)

View file

@ -64,7 +64,7 @@
## ========
## Currently all functions support an optional timeout, by default the timeout is set to
## `-1` which means that the function will never time out. The timeout is
## measured in miliseconds, once it is set any call on a socket which may
## measured in milliseconds, once it is set any call on a socket which may
## block will be susceptible to this timeout, however please remember that the
## function as a whole can take longer than the specified timeout, only
## individual internal calls on the socket are affected. In practice this means
@ -81,7 +81,7 @@
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, math
import asyncnet, asyncdispatch
import rawsockets
import nativesockets
type
Response* = tuple[
@ -166,12 +166,12 @@ proc parseChunks(s: Socket, timeout: int): string =
proc parseBody(s: Socket, headers: StringTableRef, timeout: int): string =
result = ""
if headers["Transfer-Encoding"] == "chunked":
if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = parseChunks(s, timeout)
else:
# -REGION- Content-Length
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
var contentLengthHeader = headers["Content-Length"]
var contentLengthHeader = headers.getOrDefault"Content-Length"
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
if length > 0:
@ -190,7 +190,7 @@ proc parseBody(s: Socket, headers: StringTableRef, timeout: int): string =
# -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers["Connection"] == "close":
if headers.getOrDefault"Connection" == "close":
var buf = ""
while true:
buf = newString(4000)
@ -386,7 +386,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
## | Requests ``url`` with the custom method string specified by the
## | ``httpMethod`` parameter.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
var r = if proxy == nil: parseUri(url) else: proxy.url
var headers = substr(httpMethod, len("http"))
@ -402,7 +402,11 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
headers.add(" HTTP/1.1\c\L")
add(headers, "Host: " & r.hostname & "\c\L")
if r.port == "":
add(headers, "Host: " & r.hostname & "\c\L")
else:
add(headers, "Host: " & r.hostname & ":" & r.port & "\c\L")
if userAgent != "":
add(headers, "User-Agent: " & userAgent & "\c\L")
if proxy != nil and proxy.auth != "":
@ -440,7 +444,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
userAgent = defUserAgent, proxy: Proxy = nil): Response =
## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout,
userAgent, proxy)
@ -452,7 +456,7 @@ proc redirection(status: string): bool =
return true
proc getNewLocation(lastUrl: string, headers: StringTableRef): string =
result = headers["Location"]
result = headers.getOrDefault"Location"
if result == "": httpError("location header expected")
# Relative URLs. (Not part of the spec, but soon will be.)
let r = parseUri(result)
@ -467,7 +471,7 @@ proc get*(url: string, extraHeaders = "", maxRedirects = 5,
## | GETs the ``url`` and returns a ``Response`` object
## | This proc also handles redirection
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
result = request(url, httpGET, extraHeaders, "", sslContext, timeout,
userAgent, proxy)
@ -486,7 +490,7 @@ proc getContent*(url: string, extraHeaders = "", maxRedirects = 5,
## | GETs the body and returns it as a string.
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent,
proxy)
@ -505,7 +509,7 @@ proc post*(url: string, extraHeaders = "", body = "",
## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection.
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
@ -542,7 +546,7 @@ proc postContent*(url: string, extraHeaders = "", body = "",
## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
@ -558,7 +562,7 @@ proc downloadFile*(url: string, outputFilename: string,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil) =
## | Downloads ``url`` and saves it to ``outputFilename``
## | An optional timeout can be specified in miliseconds, if reading from the
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
var f: File
if open(f, outputFilename, fmWrite):
@ -569,7 +573,7 @@ proc downloadFile*(url: string, outputFilename: string,
fileError("Unable to open file")
proc generateHeaders(r: Uri, httpMethod: string,
headers: StringTableRef): string =
headers: StringTableRef, body: string): string =
# TODO: Use this in the blocking HttpClient once it supports proxies.
result = substr(httpMethod, len("http"))
# TODO: Proxies
@ -580,8 +584,14 @@ proc generateHeaders(r: Uri, httpMethod: string,
result.add("?" & r.query)
result.add(" HTTP/1.1\c\L")
add(result, "Host: " & r.hostname & "\c\L")
if r.port == "":
add(result, "Host: " & r.hostname & "\c\L")
else:
add(result, "Host: " & r.hostname & ":" & r.port & "\c\L")
add(result, "Connection: Keep-Alive\c\L")
if body.len > 0 and not headers.hasKey("Content-Length"):
add(result, "Content-Length: " & $body.len & "\c\L")
for key, val in headers:
add(result, key & ": " & val & "\c\L")
@ -669,12 +679,12 @@ proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
proc parseBody(client: AsyncHttpClient,
headers: StringTableRef): Future[string] {.async.} =
result = ""
if headers["Transfer-Encoding"] == "chunked":
if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = await parseChunks(client)
else:
# -REGION- Content-Length
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
var contentLengthHeader = headers["Content-Length"]
var contentLengthHeader = headers.getOrDefault"Content-Length"
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
if length > 0:
@ -689,7 +699,7 @@ proc parseBody(client: AsyncHttpClient,
# -REGION- Connection: Close
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers["Connection"] == "close":
if headers.getOrDefault"Connection" == "close":
var buf = ""
while true:
buf = await client.socket.recvFull(4000)
@ -754,10 +764,10 @@ proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
let port =
if url.port == "":
if url.scheme.toLower() == "https":
rawsockets.Port(443)
nativesockets.Port(443)
else:
rawsockets.Port(80)
else: rawsockets.Port(url.port.parseInt)
nativesockets.Port(80)
else: nativesockets.Port(url.port.parseInt)
if url.scheme.toLower() == "https":
when defined(ssl):
@ -786,7 +796,7 @@ proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
if not client.headers.hasKey("user-agent") and client.userAgent != "":
client.headers["User-Agent"] = client.userAgent
var headers = generateHeaders(r, $httpMethod, client.headers)
var headers = generateHeaders(r, $httpMethod, client.headers, body)
await client.socket.send(headers)
if body != "":
@ -819,6 +829,25 @@ proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
result = await client.request(redirectTo, httpGET)
lastUrl = redirectTo
proc post*(client: AsyncHttpClient, url: string, body = "", multipart: MultipartData = nil): Future[Response] {.async.} =
## Connects to the hostname specified by the URL and performs a POST request.
##
## This procedure will follow redirects up to a maximum number of redirects
## specified in ``newAsyncHttpClient``.
let (mpHeader, mpBody) = format(multipart)
template withNewLine(x): expr =
if x.len > 0 and not x.endsWith("\c\L"):
x & "\c\L"
else:
x
var xb = mpBody.withNewLine() & body
if multipart != nil:
client.headers["Content-Type"] = mpHeader.split(": ")[1]
client.headers["Content-Length"] = $len(xb)
result = await client.request(url, httpPOST, xb)
when not defined(testing) and isMainModule:
when true:
# Async

View file

@ -106,9 +106,10 @@ proc serveFile*(client: Socket, filename: string) =
when false:
# TODO: Fix this, or get rid of it.
type
TRequestMethod = enum reqGet, reqPost
RequestMethod = enum reqGet, reqPost
{.deprecated: [TRequestMethod: RequestMethod].}
proc executeCgi(client: Socket, path, query: string, meth: TRequestMethod) =
proc executeCgi(client: Socket, path, query: string, meth: RequestMethod) =
var env = newStringTable(modeCaseInsensitive)
var contentLength = -1
case meth
@ -141,7 +142,7 @@ when false:
if meth == reqPost:
# get from client and post to CGI program:
var buf = alloc(contentLength)
if recv(client, buf, contentLength) != contentLength:
if recv(client, buf, contentLength) != contentLength:
dealloc(buf)
raiseOSError()
var inp = process.inputStream
@ -176,7 +177,7 @@ when false:
else:
path = "." & data[1]
# path starts with "/", by adding "." in front of it we serve files from cwd
if cmpIgnoreCase(data[0], "GET") == 0:
if q >= 0:
cgi = true
@ -208,7 +209,7 @@ when false:
executeCgi(client, path, query, meth)
type
TServer* = object of RootObj ## contains the current server state
Server* = object of RootObj ## contains the current server state
socket: Socket
port: Port
client*: Socket ## the socket to write the file data to
@ -217,12 +218,13 @@ type
headers*: StringTableRef ## headers with which the client made the request
body*: string ## only set with POST requests
ip*: string ## ip address of the requesting client
PAsyncHTTPServer* = ref TAsyncHTTPServer
TAsyncHTTPServer = object of TServer
PAsyncHTTPServer* = ref AsyncHTTPServer
AsyncHTTPServer = object of Server
asyncSocket: AsyncSocket
proc open*(s: var TServer, port = Port(80), reuseAddr = false) =
{.deprecated: [TAsyncHTTPServer: AsyncHTTPServer, TServer: Server].}
proc open*(s: var Server, port = Port(80), reuseAddr = false) =
## creates a new server at port `port`. If ``port == 0`` a free port is
## acquired that can be accessed later by the ``port`` proc.
s.socket = socket(AF_INET)
@ -243,11 +245,11 @@ proc open*(s: var TServer, port = Port(80), reuseAddr = false) =
s.query = ""
s.headers = {:}.newStringTable()
proc port*(s: var TServer): Port =
proc port*(s: var Server): Port =
## get the port number the server has acquired.
result = s.port
proc next*(s: var TServer) =
proc next*(s: var Server) =
## proceed to the first/next request.
var client: Socket
new(client)
@ -260,7 +262,7 @@ proc next*(s: var TServer) =
var data = ""
s.client.readLine(data)
if data == "":
# Socket disconnected
# Socket disconnected
s.client.close()
next(s)
return
@ -281,9 +283,9 @@ proc next*(s: var TServer) =
s.client.close()
next(s)
return
var i = skipWhitespace(data)
if skipIgnoreCase(data, "GET") > 0:
if skipIgnoreCase(data, "GET") > 0:
s.reqMethod = "GET"
inc(i, 3)
elif skipIgnoreCase(data, "POST") > 0:
@ -294,7 +296,7 @@ proc next*(s: var TServer) =
s.client.close()
next(s)
return
if s.reqMethod == "POST":
# Check for Expect header
if s.headers.hasKey("Expect"):
@ -302,7 +304,7 @@ proc next*(s: var TServer) =
s.client.sendStatus("100 Continue")
else:
s.client.sendStatus("417 Expectation Failed")
# Read the body
# - Check for Content-length header
if s.headers.hasKey("Content-Length"):
@ -338,13 +340,13 @@ proc next*(s: var TServer) =
s.client.close()
next(s)
return
var L = skipWhitespace(data, i)
inc(i, L)
# XXX we ignore "HTTP/1.1" etc. for now here
var query = 0
var last = i
while last < data.len and data[last] notin Whitespace:
while last < data.len and data[last] notin Whitespace:
if data[last] == '?' and query == 0: query = last
inc(last)
if query > 0:
@ -354,15 +356,15 @@ proc next*(s: var TServer) =
s.query = ""
s.path = data.substr(i, last-1)
proc close*(s: TServer) =
proc close*(s: Server) =
## closes the server (and the socket the server uses).
close(s.socket)
proc run*(handleRequest: proc (client: Socket,
proc run*(handleRequest: proc (client: Socket,
path, query: string): bool {.closure.},
port = Port(80)) =
## encapsulates the server object and main loop
var s: TServer
var s: Server
open(s, port, reuseAddr = true)
#echo("httpserver running on port ", s.port)
while true:
@ -386,7 +388,7 @@ proc nextAsync(s: PAsyncHTTPServer) =
var data = ""
s.client.readLine(data)
if data == "":
# Socket disconnected
# Socket disconnected
s.client.close()
return
var header = ""
@ -406,9 +408,9 @@ proc nextAsync(s: PAsyncHTTPServer) =
else:
s.client.close()
return
var i = skipWhitespace(data)
if skipIgnoreCase(data, "GET") > 0:
if skipIgnoreCase(data, "GET") > 0:
s.reqMethod = "GET"
inc(i, 3)
elif skipIgnoreCase(data, "POST") > 0:
@ -418,7 +420,7 @@ proc nextAsync(s: PAsyncHTTPServer) =
unimplemented(s.client)
s.client.close()
return
if s.reqMethod == "POST":
# Check for Expect header
if s.headers.hasKey("Expect"):
@ -426,7 +428,7 @@ proc nextAsync(s: PAsyncHTTPServer) =
s.client.sendStatus("100 Continue")
else:
s.client.sendStatus("417 Expectation Failed")
# Read the body
# - Check for Content-length header
if s.headers.hasKey("Content-Length"):
@ -458,13 +460,13 @@ proc nextAsync(s: PAsyncHTTPServer) =
badRequest(s.client)
s.client.close()
return
var L = skipWhitespace(data, i)
inc(i, L)
# XXX we ignore "HTTP/1.1" etc. for now here
var query = 0
var last = i
while last < data.len and data[last] notin Whitespace:
while last < data.len and data[last] notin Whitespace:
if data[last] == '?' and query == 0: query = last
inc(last)
if query > 0:
@ -474,7 +476,7 @@ proc nextAsync(s: PAsyncHTTPServer) =
s.query = ""
s.path = data.substr(i, last-1)
proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: Socket,
proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: Socket,
path, query: string): bool {.closure, gcsafe.},
port = Port(80), address = "",
reuseAddr = false): PAsyncHTTPServer =
@ -490,14 +492,14 @@ proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: Soc
if quit: capturedRet.asyncSocket.close()
if reuseAddr:
capturedRet.asyncSocket.setSockOpt(OptReuseAddr, true)
capturedRet.asyncSocket.bindAddr(port, address)
capturedRet.asyncSocket.listen()
if port == Port(0):
capturedRet.port = getSockName(capturedRet.asyncSocket)
else:
capturedRet.port = port
capturedRet.client = invalidSocket
capturedRet.reqMethod = ""
capturedRet.body = ""
@ -517,16 +519,16 @@ proc close*(h: PAsyncHTTPServer) =
when not defined(testing) and isMainModule:
var counter = 0
var s: TServer
var s: Server
open(s, Port(0))
echo("httpserver running on port ", s.port)
while true:
next(s)
inc(counter)
s.client.send("Hello, Andreas, for the $#th time. $# ? $#" % [
$counter, s.path, s.query] & wwwNL)
close(s.client)
close(s)

View file

@ -68,7 +68,7 @@ type
jsonArrayStart, ## start of an array: the ``[`` token
jsonArrayEnd ## start of an array: the ``]`` token
TTokKind = enum # must be synchronized with TJsonEventKind!
TokKind = enum # must be synchronized with TJsonEventKind!
tkError,
tkEof,
tkString,
@ -103,14 +103,14 @@ type
JsonParser* = object of BaseLexer ## the parser object.
a: string
tok: TTokKind
tok: TokKind
kind: JsonEventKind
err: JsonError
state: seq[ParserState]
filename: string
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
TJsonParser: JsonParser].}
TJsonParser: JsonParser, TTokKind: TokKind].}
const
errorMessages: array [JsonError, string] = [
@ -126,7 +126,7 @@ const
"EOF expected",
"expression expected"
]
tokToStr: array [TTokKind, string] = [
tokToStr: array [TokKind, string] = [
"invalid token",
"EOF",
"string literal",
@ -203,7 +203,16 @@ proc handleHexChar(c: char, x: var int): bool =
of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
else: result = false # error
proc parseString(my: var JsonParser): TTokKind =
proc parseEscapedUTF16(buf: cstring, pos: var int): int =
result = 0
#UTF-16 escape is always 4 bytes.
for _ in 0..3:
if handleHexChar(buf[pos], result):
inc(pos)
else:
return -1
proc parseString(my: var JsonParser): TokKind =
result = tkString
var pos = my.bufpos + 1
var buf = my.buf
@ -238,11 +247,22 @@ proc parseString(my: var JsonParser): TTokKind =
inc(pos, 2)
of 'u':
inc(pos, 2)
var r: int
if handleHexChar(buf[pos], r): inc(pos)
if handleHexChar(buf[pos], r): inc(pos)
if handleHexChar(buf[pos], r): inc(pos)
if handleHexChar(buf[pos], r): inc(pos)
var r = parseEscapedUTF16(buf, pos)
if r < 0:
my.err = errInvalidToken
break
# Deal with surrogates
if (r and 0xfc00) == 0xd800:
if buf[pos] & buf[pos+1] != "\\u":
my.err = errInvalidToken
break
inc(pos, 2)
var s = parseEscapedUTF16(buf, pos)
if (s and 0xfc00) == 0xdc00 and s > 0:
r = 0x10000 + (((r - 0xd800) shl 10) or (s - 0xdc00))
else:
my.err = errInvalidToken
break
add(my.a, toUTF8(Rune(r)))
else:
# don't bother with the error
@ -359,7 +379,7 @@ proc parseName(my: var JsonParser) =
inc(pos)
my.bufpos = pos
proc getTok(my: var JsonParser): TTokKind =
proc getTok(my: var JsonParser): TokKind =
setLen(my.a, 0)
skip(my) # skip whitespace, comments
case my.buf[my.bufpos]
@ -608,29 +628,32 @@ proc newJArray*(): JsonNode =
proc getStr*(n: JsonNode, default: string = ""): string =
## Retrieves the string value of a `JString JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JString``.
if n.kind != JString: return default
## Returns ``default`` if ``n`` is not a ``JString``, or if ``n`` is nil.
if n.isNil or n.kind != JString: return default
else: return n.str
proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt =
## Retrieves the int value of a `JInt JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JInt``.
if n.kind != JInt: return default
## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil.
if n.isNil or n.kind != JInt: return default
else: return n.num
proc getFNum*(n: JsonNode, default: float = 0.0): float =
## Retrieves the float value of a `JFloat JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JFloat``.
if n.kind != JFloat: return default
else: return n.fnum
## Returns ``default`` if ``n`` is not a ``JFloat`` or ``JInt``, or if ``n`` is nil.
if n.isNil: return default
case n.kind
of JFloat: return n.fnum
of JInt: return float(n.num)
else: return default
proc getBVal*(n: JsonNode, default: bool = false): bool =
## Retrieves the bool value of a `JBool JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JBool``.
if n.kind != JBool: return default
## Returns ``default`` if ``n`` is not a ``JBool``, or if ``n`` is nil.
if n.isNil or n.kind != JBool: return default
else: return n.bval
proc getFields*(n: JsonNode,
@ -638,15 +661,15 @@ proc getFields*(n: JsonNode,
seq[tuple[key: string, val: JsonNode]] =
## Retrieves the key, value pairs of a `JObject JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JObject``.
if n.kind != JObject: return default
## Returns ``default`` if ``n`` is not a ``JObject``, or if ``n`` is nil.
if n.isNil or n.kind != JObject: return default
else: return n.fields
proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] =
## Retrieves the int value of a `JArray JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JArray``.
if n.kind != JArray: return default
## Returns ``default`` if ``n`` is not a ``JArray``, or if ``n`` is nil.
if n.isNil or n.kind != JArray: return default
else: return n.elems
proc `%`*(s: string): JsonNode =
@ -734,7 +757,7 @@ proc `==`* (a,b: JsonNode): bool =
of JObject:
a.fields == b.fields
proc hash* (n:JsonNode): THash =
proc hash* (n:JsonNode): Hash =
## Compute the hash for a JSON node
case n.kind
of JArray:
@ -761,7 +784,7 @@ proc len*(n: JsonNode): int =
of JObject: result = n.fields.len
else: discard
proc `[]`*(node: JsonNode, name: string): JsonNode =
proc `[]`*(node: JsonNode, name: string): JsonNode {.inline.} =
## Gets a field from a `JObject`, which must not be nil.
## If the value at `name` does not exist, returns nil
assert(not isNil(node))
@ -771,7 +794,7 @@ proc `[]`*(node: JsonNode, name: string): JsonNode =
return item
return nil
proc `[]`*(node: JsonNode, index: int): JsonNode =
proc `[]`*(node: JsonNode, index: int): JsonNode {.inline.} =
## Gets the node at `index` in an Array. Result is undefined if `index`
## is out of bounds
assert(not isNil(node))
@ -799,7 +822,7 @@ proc add*(obj: JsonNode, key: string, val: JsonNode) =
assert obj.kind == JObject
obj.fields.add((key, val))
proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) =
proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} =
## Sets a field from a `JObject`. Performs a check for duplicate keys.
assert(obj.kind == JObject)
for i in 0..obj.fields.len-1:
@ -815,7 +838,7 @@ proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode =
result = node
for key in keys:
if isNil(result) or result.kind!=JObject:
return nil
return nil
result=result[key]
proc `{}=`*(node: JsonNode, keys: varargs[string], value: JsonNode) =
@ -949,10 +972,46 @@ proc pretty*(node: JsonNode, indent = 2): string =
result = ""
toPretty(result, node, indent)
proc toUgly*(result: var string, node: JsonNode) =
## Converts `node` to its JSON Representation, without
## regard for human readability. Meant to improve ``$`` string
## conversion performance.
##
## This provides higher efficiency than the ``toPretty`` procedure as it
## does **not** attempt to format the resulting JSON to make it human readable.
var comma = false
case node.kind:
of JArray:
result.add "["
for child in node.elems:
if comma: result.add ","
else: comma = true
result.toUgly child
result.add "]"
of JObject:
result.add "{"
for key, value in items(node.fields):
if comma: result.add ","
else: comma = true
result.add key.escapeJson()
result.add ":"
result.toUgly value
result.add "}"
of JString:
result.add node.str.escapeJson()
of JInt:
result.add($node.num)
of JFloat:
result.add($node.fnum)
of JBool:
result.add(if node.bval: "true" else: "false")
of JNull:
result.add "null"
proc `$`*(node: JsonNode): string =
## Converts `node` to its JSON Representation on one line.
result = ""
toPretty(result, node, 0, false)
result = newStringOfCap(node.len shl 1)
toUgly(result, node)
iterator items*(node: JsonNode): JsonNode =
## Iterator for the items of `node`. `node` has to be a JArray.
@ -980,7 +1039,7 @@ iterator mpairs*(node: var JsonNode): var tuple[key: string, val: JsonNode] =
for keyVal in mitems(node.fields):
yield keyVal
proc eat(p: var JsonParser, tok: TTokKind) =
proc eat(p: var JsonParser, tok: TokKind) =
if p.tok == tok: discard getTok(p)
else: raiseParseErr(p, tokToStr[tok])
@ -1038,9 +1097,9 @@ when not defined(js):
## for nice error messages.
var p: JsonParser
p.open(s, filename)
defer: p.close()
discard getTok(p) # read first token
result = p.parseJson()
p.close()
proc parseJson*(buffer: string): JsonNode =
## Parses JSON from `buffer`.
@ -1055,8 +1114,10 @@ when not defined(js):
else:
from math import `mod`
type
TJSObject = object
proc parseNativeJson(x: cstring): TJSObject {.importc: "JSON.parse".}
JSObject = object
{.deprecated: [TJSObject: JSObject].}
proc parseNativeJson(x: cstring): JSObject {.importc: "JSON.parse".}
proc getVarType(x): JsonNodeKind =
result = JNull
@ -1075,25 +1136,25 @@ else:
of "[object String]": return JString
else: assert false
proc len(x: TJSObject): int =
proc len(x: JSObject): int =
assert x.getVarType == JArray
asm """
return `x`.length;
"""
proc `[]`(x: TJSObject, y: string): TJSObject =
proc `[]`(x: JSObject, y: string): JSObject =
assert x.getVarType == JObject
asm """
return `x`[`y`];
"""
proc `[]`(x: TJSObject, y: int): TJSObject =
proc `[]`(x: JSObject, y: int): JSObject =
assert x.getVarType == JArray
asm """
return `x`[`y`];
"""
proc convertObject(x: TJSObject): JsonNode =
proc convertObject(x: JSObject): JsonNode =
case getVarType(x)
of JArray:
result = newJArray()
@ -1105,7 +1166,7 @@ else:
if (`x`.hasOwnProperty(property)) {
"""
var nimProperty: cstring
var nimValue: TJSObject
var nimValue: JSObject
asm "`nimProperty` = property; `nimValue` = `x`[property];"
result[$nimProperty] = nimValue.convertObject()
asm "}}"
@ -1151,30 +1212,39 @@ when false:
# To get that we shall use, obj["json"]
when isMainModule:
#var node = parse("{ \"test\": null }")
#echo(node.existsKey("test56"))
var parsed = parseFile("tests/testdata/jsontest.json")
var parsed2 = parseFile("tests/testdata/jsontest2.json")
when not defined(testing):
echo(parsed)
echo()
echo(pretty(parsed, 2))
echo()
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(ValueError, "That line was expected to fail")
except IndexError: echo()
try:
discard parsed["key2"][12123]
assert(false)
except IndexError: assert(true)
let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }"""
let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd", "c": "\ud83c\udf83", "d": "\u00E6"}"""
# nil passthrough
assert(testJson{"doesnt_exist"}{"anything"}.isNil)
testJson{["c", "d"]} = %true
assert(testJson["c"]["d"].bval)
testJson{["e", "f"]} = %true
assert(testJson["e"]["f"].bval)
# make sure UTF-16 decoding works.
assert(testJson["c"].str == "🎃")
assert(testJson["d"].str == "æ")
# make sure no memory leek when parsing invalid string
let startMemory = getOccupiedMem()
for i in 0 .. 10000:
try:
discard parseJson"""{ invalid"""
except:
discard
# memory diff should less than 2M
assert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024)
# test `$`
let stringified = $testJson
let parsedAgain = parseJson(stringified)
assert(parsedAgain["b"].str == "asd")
# Bounds checking
try:
@ -1192,17 +1262,17 @@ when isMainModule:
except:
assert(false, "EInvalidIndex thrown for valid index")
assert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}")
assert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil")
assert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}")
assert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil")
assert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(testJson{"a"}==parseJson"[1, 2, 3, 4]", "Didn't return a non-JObject when there was one to be found")
assert(isNil(parseJson("[1, 2, 3]"){"foo"}), "Indexing directly into a list should return nil")
# Generator:
var j = %* [{"name": "John", "age": 30}, {"name": "Susan", "age": 31}]
assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
var j2 = %*
[
@ -1230,13 +1300,3 @@ when isMainModule:
}
]
assert j3 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
when not defined(testing):
discard """
while true:
var json = stdin.readLine()
var node = parse(json)
echo(node)
echo()
echo()
"""

View file

@ -34,37 +34,15 @@ type
lineNumber*: int ## the current line number
sentinel: int
lineStart: int # index of last line start in buffer
fileOpened: bool
refillChars: set[char]
{.deprecated: [TBaseLexer: BaseLexer].}
proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192)
## inits the TBaseLexer with a stream to read from
proc close*(L: var BaseLexer)
## closes the base lexer. This closes `L`'s associated stream too.
proc getCurrentLine*(L: BaseLexer, marker: bool = true): string
## retrieves the current line.
proc getColNumber*(L: BaseLexer, pos: int): int
## retrieves the current column.
proc handleCR*(L: var BaseLexer, pos: int): int
## Call this if you scanned over '\c' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\c'.
proc handleLF*(L: var BaseLexer, pos: int): int
## Call this if you scanned over '\L' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\L'.
# implementation
const
chrSize = sizeof(char)
proc close(L: var BaseLexer) =
proc close*(L: var BaseLexer) =
## closes the base lexer. This closes `L`'s associated stream too.
dealloc(L.buf)
close(L.input)
@ -80,7 +58,7 @@ proc fillBuffer(L: var BaseLexer) =
toCopy = L.bufLen - L.sentinel - 1
assert(toCopy >= 0)
if toCopy > 0:
moveMem(L.buf, addr(L.buf[L.sentinel + 1]), toCopy * chrSize)
moveMem(L.buf, addr(L.buf[L.sentinel + 1]), toCopy * chrSize)
# "moveMem" handles overlapping regions
charsRead = readData(L.input, addr(L.buf[toCopy]),
(L.sentinel + 1) * chrSize) div chrSize
@ -93,7 +71,7 @@ proc fillBuffer(L: var BaseLexer) =
dec(s) # BUGFIX (valgrind)
while true:
assert(s < L.bufLen)
while (s >= 0) and not (L.buf[s] in NewLines): dec(s)
while s >= 0 and L.buf[s] notin L.refillChars: dec(s)
if s >= 0:
# we found an appropriate character for a sentinel:
L.sentinel = s
@ -121,31 +99,46 @@ proc fillBaseLexer(L: var BaseLexer, pos: int): int =
fillBuffer(L)
L.bufpos = 0 # XXX: is this really correct?
result = 0
L.lineStart = result
proc handleCR(L: var BaseLexer, pos: int): int =
proc handleCR*(L: var BaseLexer, pos: int): int =
## Call this if you scanned over '\c' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\c'.
assert(L.buf[pos] == '\c')
inc(L.lineNumber)
result = fillBaseLexer(L, pos)
if L.buf[result] == '\L':
result = fillBaseLexer(L, result)
L.lineStart = result
proc handleLF(L: var BaseLexer, pos: int): int =
proc handleLF*(L: var BaseLexer, pos: int): int =
## Call this if you scanned over '\L' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\L'.
assert(L.buf[pos] == '\L')
inc(L.lineNumber)
result = fillBaseLexer(L, pos) #L.lastNL := result-1; // BUGFIX: was: result;
L.lineStart = result
proc handleRefillChar*(L: var BaseLexer, pos: int): int =
## To be documented.
assert(L.buf[pos] in L.refillChars)
result = fillBaseLexer(L, pos) #L.lastNL := result-1; // BUGFIX: was: result;
proc skipUtf8Bom(L: var BaseLexer) =
if (L.buf[0] == '\xEF') and (L.buf[1] == '\xBB') and (L.buf[2] == '\xBF'):
inc(L.bufpos, 3)
inc(L.lineStart, 3)
proc open(L: var BaseLexer, input: Stream, bufLen: int = 8192) =
proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192;
refillChars: set[char] = NewLines) =
## inits the BaseLexer with a stream to read from.
assert(bufLen > 0)
assert(input != nil)
L.input = input
L.bufpos = 0
L.bufLen = bufLen
L.refillChars = refillChars
L.buf = cast[cstring](alloc(bufLen * chrSize))
L.sentinel = bufLen - 1
L.lineStart = 0
@ -153,10 +146,12 @@ proc open(L: var BaseLexer, input: Stream, bufLen: int = 8192) =
fillBuffer(L)
skipUtf8Bom(L)
proc getColNumber(L: BaseLexer, pos: int): int =
proc getColNumber*(L: BaseLexer, pos: int): int =
## retrieves the current column.
result = abs(pos - L.lineStart)
proc getCurrentLine(L: BaseLexer, marker: bool = true): string =
proc getCurrentLine*(L: BaseLexer, marker: bool = true): string =
## retrieves the current line.
var i: int
result = ""
i = L.lineStart
@ -166,4 +161,3 @@ proc getCurrentLine(L: BaseLexer, marker: bool = true): string =
add(result, "\n")
if marker:
add(result, spaces(getColNumber(L, L.bufpos)) & "^\n")

View file

@ -19,7 +19,12 @@
## ============ =======================
## $date Current date
## $time Current time
## $datetime $dateT$time
## $app ``os.getAppFilename()``
## $appname base name of $app
## $appdir directory name of $app
## $levelid first letter of log level
## $levelname log level name
## ============ =======================
##
##
@ -59,20 +64,20 @@ const
"DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
]
defaultFmtStr* = "" ## default string between log level and message per logger
verboseFmtStr* = "$date $time "
defaultFmtStr* = "$levelname " ## default format string
verboseFmtStr* = "$levelid, [$datetime] -- $appname: "
type
Logger* = ref object of RootObj ## abstract logger; the base type of all loggers
levelThreshold*: Level ## only messages of level >= levelThreshold
## should be processed
fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc.
fmtStr*: string ## = defaultFmtStr by default, see substituteLog for $date etc.
ConsoleLogger* = ref object of Logger ## logger that writes the messages to the
## console
FileLogger* = ref object of Logger ## logger that writes the messages to a file
f: File
file*: File ## the wrapped file.
RollingFileLogger* = ref object of FileLogger ## logger that writes the
## messages to a file and
@ -82,16 +87,18 @@ type
baseName: string # initial filename
baseMode: FileMode # initial file mode
logFiles: int # how many log files already created, e.g. basename.1, basename.2...
bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger,
PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
proc substituteLog(frmt: string): string =
## converts $date to the current date
## converts $time to the current time
## converts $app to getAppFilename()
## converts
result = newStringOfCap(frmt.len + 20)
proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string =
## Format a log message using the ``frmt`` format string, ``level`` and varargs.
## See the module documentation for the format string syntax.
var msgLen = 0
for arg in args:
msgLen += arg.len
result = newStringOfCap(frmt.len + msgLen + 20)
var i = 0
while i < frmt.len:
if frmt[i] != '$':
@ -107,36 +114,38 @@ proc substituteLog(frmt: string): string =
case v
of "date": result.add(getDateStr())
of "time": result.add(getClockStr())
of "datetime": result.add(getDateStr() & "T" & getClockStr())
of "app": result.add(app)
of "appdir": result.add(app.splitFile.dir)
of "appname": result.add(app.splitFile.name)
of "levelid": result.add(LevelNames[level][0])
of "levelname": result.add(LevelNames[level])
else: discard
for arg in args:
result.add(arg)
method log*(logger: Logger, level: Level,
frmt: string, args: varargs[string, `$`]) {.
method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
raises: [Exception],
tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} =
tags: [TimeEffect, WriteIOEffect, ReadIOEffect], base.} =
## Override this method in custom loggers. Default implementation does
## nothing.
discard
method log*(logger: ConsoleLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
## Logs to the console using ``logger`` only.
if level >= logger.levelThreshold:
writeln(stdout, LevelNames[level], " ", substituteLog(logger.fmtStr),
frmt % args)
writeLine(stdout, substituteLog(logger.fmtStr, level, args))
if level in {lvlError, lvlFatal}: flushFile(stdout)
method log*(logger: FileLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
## Logs to a file using ``logger`` only.
if level >= logger.levelThreshold:
writeln(logger.f, LevelNames[level], " ",
substituteLog(logger.fmtStr), frmt % args)
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
if level in {lvlError, lvlFatal}: flushFile(logger.file)
proc defaultFilename*(): string =
## Returns the default filename for a logger.
var (path, name, ext) = splitFile(getAppFilename())
var (path, name, _) = splitFile(getAppFilename())
result = changeFileExt(path / name, "log")
proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger =
@ -148,18 +157,21 @@ proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): Console
proc newFileLogger*(filename = defaultFilename(),
mode: FileMode = fmAppend,
levelThreshold = lvlAll,
fmtStr = defaultFmtStr): FileLogger =
fmtStr = defaultFmtStr,
bufSize: int = -1): FileLogger =
## Creates a new file logger. This logger logs to a file.
## Use ``bufSize`` as size of the output buffer when writing the file
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
new(result)
result.levelThreshold = levelThreshold
result.f = open(filename, mode)
result.file = open(filename, mode, bufSize = bufSize)
result.fmtStr = fmtStr
# ------
proc countLogLines(logger: RollingFileLogger): int =
result = 0
for line in logger.f.lines():
for line in logger.file.lines():
result.inc()
proc countFiles(filename: string): int =
@ -181,14 +193,18 @@ proc newRollingFileLogger*(filename = defaultFilename(),
mode: FileMode = fmReadWrite,
levelThreshold = lvlAll,
fmtStr = defaultFmtStr,
maxLines = 1000): RollingFileLogger =
maxLines = 1000,
bufSize: int = -1): RollingFileLogger =
## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines
## a new log file will be started and the old will be renamed.
## Use ``bufSize`` as size of the output buffer when writing the file
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
new(result)
result.levelThreshold = levelThreshold
result.fmtStr = fmtStr
result.maxLines = maxLines
result.f = open(filename, mode)
result.bufSize = bufSize
result.file = open(filename, mode, bufSize=result.bufSize)
result.curLine = 0
result.baseName = filename
result.baseMode = mode
@ -206,18 +222,18 @@ proc rotate(logger: RollingFileLogger) =
moveFile(dir / (name & ext & srcSuff),
dir / (name & ext & ExtSep & $(i+1)))
method log*(logger: RollingFileLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
## Logs to a file using rolling ``logger`` only.
if level >= logger.levelThreshold:
if logger.curLine >= logger.maxLines:
logger.f.close()
logger.file.close()
rotate(logger)
logger.logFiles.inc
logger.curLine = 0
logger.f = open(logger.baseName, logger.baseMode)
logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
writeln(logger.f, LevelNames[level], " ",substituteLog(logger.fmtStr), frmt % args)
writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
if level in {lvlError, lvlFatal}: flushFile(logger.file)
logger.curLine.inc
# --------
@ -225,39 +241,39 @@ method log*(logger: RollingFileLogger, level: Level,
var level {.threadvar.}: Level ## global log filter
var handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels
proc logLoop(level: Level, frmt: string, args: varargs[string, `$`]) =
proc logLoop(level: Level, args: varargs[string, `$`]) =
for logger in items(handlers):
if level >= logger.levelThreshold:
log(logger, level, frmt, args)
log(logger, level, args)
template log*(level: Level, frmt: string, args: varargs[string, `$`]) =
template log*(level: Level, args: varargs[string, `$`]) =
## Logs a message to all registered handlers at the given level.
bind logLoop
bind `%`
bind logging.level
if level >= logging.level:
logLoop(level, frmt, args)
logLoop(level, args)
template debug*(frmt: string, args: varargs[string, `$`]) =
template debug*(args: varargs[string, `$`]) =
## Logs a debug message to all registered handlers.
log(lvlDebug, frmt, args)
log(lvlDebug, args)
template info*(frmt: string, args: varargs[string, `$`]) =
template info*(args: varargs[string, `$`]) =
## Logs an info message to all registered handlers.
log(lvlInfo, frmt, args)
log(lvlInfo, args)
template warn*(frmt: string, args: varargs[string, `$`]) =
template warn*(args: varargs[string, `$`]) =
## Logs a warning message to all registered handlers.
log(lvlWarn, frmt, args)
log(lvlWarn, args)
template error*(frmt: string, args: varargs[string, `$`]) =
template error*(args: varargs[string, `$`]) =
## Logs an error message to all registered handlers.
log(lvlError, frmt, args)
log(lvlError, args)
template fatal*(frmt: string, args: varargs[string, `$`]) =
template fatal*(args: varargs[string, `$`]) =
## Logs a fatal error message to all registered handlers.
log(lvlFatal, frmt, args)
log(lvlFatal, args)
proc addHandler*(handler: Logger) =
## Adds ``handler`` to the list of handlers.
@ -286,6 +302,4 @@ when not defined(testing) and isMainModule:
addHandler(fL)
addHandler(rL)
for i in 0 .. 25:
info("hello" & $i, [])
info("hello", i)

View file

@ -17,13 +17,13 @@
## .. code-block:: nim
##
## type
## TA = object
## TB = object of TA
## A = object of RootObj
## B = object of A
## f: int
##
## var
## a: ref TA
## b: ref TB
## a: ref A
## b: ref B
##
## new(b)
## a = b
@ -36,7 +36,7 @@ import streams, typeinfo, json, intsets, tables
proc ptrToInt(x: pointer): int {.inline.} =
result = cast[int](x) # don't skip alignment
proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
proc storeAny(s: Stream, a: Any, stored: var IntSet) =
case a.kind
of akNone: assert false
of akBool: s.write($getBool(a))
@ -96,7 +96,7 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
of akInt..akInt64, akUInt..akUInt64: s.write($getBiggestInt(a))
of akFloat..akFloat128: s.write($getBiggestFloat(a))
proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
proc loadAny(p: var JsonParser, a: Any, t: var Table[BiggestInt, pointer]) =
case a.kind
of akNone: assert false
of akBool:
@ -176,7 +176,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
setPointer(a, nil)
next(p)
of jsonInt:
setPointer(a, t[p.getInt])
setPointer(a, t.getOrDefault(p.getInt))
next(p)
of jsonArrayStart:
next(p)
@ -222,7 +222,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
raiseParseErr(p, "float expected")
of akRange: loadAny(p, a.skipRange, t)
proc loadAny(s: Stream, a: TAny, t: var Table[BiggestInt, pointer]) =
proc loadAny(s: Stream, a: Any, t: var Table[BiggestInt, pointer]) =
var p: JsonParser
open(p, s, "unknown file")
next(p)
@ -278,10 +278,11 @@ when not defined(testing) and isMainModule:
else:
nil
PNode = ref TNode
TNode = object
PNode = ref Node
Node = object
next, prev: PNode
data: string
{.deprecated: [TNode: Node].}
proc buildList(): PNode =
new(result)
@ -317,14 +318,15 @@ when not defined(testing) and isMainModule:
testit(test7)
type
TA {.inheritable.} = object
TB = object of TA
A {.inheritable.} = object
B = object of A
f: int
var
a: ref TA
b: ref TB
a: ref A
b: ref B
new(b)
a = b
echo($$a[]) # produces "{}", not "{f: 0}"

View file

@ -18,15 +18,15 @@ include "system/inclrtl"
import parseutils, strutils
proc validEmailAddress*(s: string): bool {.noSideEffect,
rtl, extern: "nsuValidEmailAddress".} =
## returns true if `s` seems to be a valid e-mail address.
rtl, extern: "nsuValidEmailAddress".} =
## returns true if `s` seems to be a valid e-mail address.
## The checking also uses a domain list.
const
chars = Letters + Digits + {'!','#','$','%','&',
'\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'}
var i = 0
if s[i] notin chars or s[i] == '.': return false
while s[i] in chars:
while s[i] in chars:
if s[i] == '.' and s[i+1] == '.': return false
inc(i)
if s[i] != '@': return false
@ -34,9 +34,9 @@ proc validEmailAddress*(s: string): bool {.noSideEffect,
if s[j] notin Letters: return false
while j >= i and s[j] in Letters: dec(j)
inc(i) # skip '@'
while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i)
while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i)
if s[i] != '\0': return false
var x = substr(s, j+1)
if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true
case toLower(x)
@ -59,6 +59,6 @@ proc parseInt*(s: string, value: var int, validRange: Slice[int]) {.
when isMainModule:
doAssert "wuseldusel@codehome.com".validEmailAddress
{.pop.}

View file

@ -8,20 +8,38 @@
#
## Constructive mathematics is naturally typed. -- Simon Thompson
##
##
## Basic math routines for Nim.
## This module is available for the `JavaScript target
## <backends.html#the-javascript-target>`_.
##
## Note that the trigonometric functions naturally operate on radians.
## The helper functions `degToRad` and `radToDeg` provide conversion
## between radians and degrees.
include "system/inclrtl"
{.push debugger:off .} # the user does not want to trace a part
# of the standard library!
proc binom*(n, k: int): int {.noSideEffect.} =
## Computes the binomial coefficient
if k <= 0: return 1
if 2*k > n: return binom(n, n-k)
result = n
for i in countup(2, k):
result = (result * (n + 1 - i)) div i
proc fac*(n: int): int {.noSideEffect.} =
## Computes the faculty/factorial function.
result = 1
for i in countup(2, n):
result = result * i
{.push checks:off, line_dir:off, stack_trace:off.}
when defined(Posix) and not defined(haiku):
{.passl: "-lm".}
when not defined(js):
when not defined(js) and not defined(nimscript):
import times
const
@ -34,10 +52,11 @@ const
MaxFloat32Precision* = 8 ## maximum number of meaningful digits
## after the decimal point for Nim's
## ``float32`` type.
MaxFloatPrecision* = MaxFloat64Precision ## maximum number of
MaxFloatPrecision* = MaxFloat64Precision ## maximum number of
## meaningful digits
## after the decimal point
## after the decimal point
## for Nim's ``float`` type.
RadPerDeg = PI / 180.0 ## number of radians per degree
type
FloatClass* = enum ## describes the class a floating point value belongs to.
@ -50,10 +69,10 @@ type
fcInf, ## value is positive infinity
fcNegInf ## value is negative infinity
proc classify*(x: float): FloatClass =
## classifies a floating point value. Returns `x`'s class as specified by
proc classify*(x: float): FloatClass =
## Classifies a floating point value. Returns `x`'s class as specified by
## `FloatClass`.
# JavaScript and most C compilers have no classify:
if x == 0.0:
if 1.0/x == Inf:
@ -67,30 +86,15 @@ proc classify*(x: float): FloatClass =
return fcNormal
# XXX: fcSubnormal is not detected!
proc binom*(n, k: int): int {.noSideEffect.} =
## computes the binomial coefficient
if k <= 0: return 1
if 2*k > n: return binom(n, n-k)
result = n
for i in countup(2, k):
result = (result * (n + 1 - i)) div i
proc fac*(n: int): int {.noSideEffect.} =
## computes the faculty/factorial function.
result = 1
for i in countup(2, n):
result = result * i
proc isPowerOfTwo*(x: int): bool {.noSideEffect.} =
## returns true, if `x` is a power of two, false otherwise.
## Returns true, if `x` is a power of two, false otherwise.
## Zero and negative numbers are not a power of two.
return (x != 0) and ((x and (x - 1)) == 0)
return (x > 0) and ((x and (x - 1)) == 0)
proc nextPowerOfTwo*(x: int): int {.noSideEffect.} =
## returns `x` rounded up to the nearest power of two.
## Returns `x` rounded up to the nearest power of two.
## Zero and negative numbers get rounded up to 1.
result = x - 1
result = x - 1
when defined(cpu64):
result = result or (result shr 32)
when sizeof(int) > 2:
@ -103,67 +107,77 @@ proc nextPowerOfTwo*(x: int): int {.noSideEffect.} =
result += 1 + ord(x<=0)
proc countBits32*(n: int32): int {.noSideEffect.} =
## counts the set bits in `n`.
## Counts the set bits in `n`.
var v = n
v = v -% ((v shr 1'i32) and 0x55555555'i32)
v = (v and 0x33333333'i32) +% ((v shr 2'i32) and 0x33333333'i32)
result = ((v +% (v shr 4'i32) and 0xF0F0F0F'i32) *% 0x1010101'i32) shr 24'i32
proc sum*[T](x: openArray[T]): T {.noSideEffect.} =
## computes the sum of the elements in `x`.
proc sum*[T](x: openArray[T]): T {.noSideEffect.} =
## Computes the sum of the elements in `x`.
## If `x` is empty, 0 is returned.
for i in items(x): result = result + i
proc mean*(x: openArray[float]): float {.noSideEffect.} =
## computes the mean of the elements in `x`.
## If `x` is empty, NaN is returned.
result = sum(x) / toFloat(len(x))
template toFloat(f: float): float = f
proc variance*(x: openArray[float]): float {.noSideEffect.} =
## computes the variance of the elements in `x`.
proc mean*[T](x: openArray[T]): float {.noSideEffect.} =
## Computes the mean of the elements in `x`, which are first converted to floats.
## If `x` is empty, NaN is returned.
## ``toFloat(x: T): float`` must be defined.
for i in items(x): result = result + toFloat(i)
result = result / toFloat(len(x))
proc variance*[T](x: openArray[T]): float {.noSideEffect.} =
## Computes the variance of the elements in `x`.
## If `x` is empty, NaN is returned.
## ``toFloat(x: T): float`` must be defined.
result = 0.0
var m = mean(x)
for i in 0 .. high(x):
var diff = x[i] - m
for i in items(x):
var diff = toFloat(i) - m
result = result + diff*diff
result = result / toFloat(len(x))
proc random*(max: int): int {.benign.}
## returns a random number in the range 0..max-1. The sequence of
## 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.
proc random*(max: float): float {.benign.}
## returns a random number in the range 0..<max. The sequence of
## 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. This has a 16-bit resolution on windows
## and a 48-bit resolution on other platforms.
proc randomize*() {.benign.}
## initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does nothing for the JavaScript target,
## as JavaScript does not support this.
when not defined(nimscript):
proc randomize*() {.benign.}
## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does nothing for the JavaScript target,
## as JavaScript does not support this. Nor does it work for NimScript.
proc randomize*(seed: int) {.benign.}
## initializes the random number generator with a specific seed.
## Initializes the random number generator with a specific seed.
## Note: Does nothing for the JavaScript target,
## as JavaScript does not support this.
{.push noSideEffect.}
when not defined(JS):
proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".}
## computes the square root of `x`.
## Computes the square root of `x`.
proc cbrt*(x: float): float {.importc: "cbrt", header: "<math.h>".}
## Computes the cubic root of `x`
proc ln*(x: float): float {.importc: "log", header: "<math.h>".}
## computes ln(x).
## Computes the natural log of `x`
proc log10*(x: float): float {.importc: "log10", header: "<math.h>".}
## Computes the common logarithm (base 10) of `x`
proc log2*(x: float): float = return ln(x) / ln(2.0)
## Computes the binary logarithm (base 2) of `x`
proc exp*(x: float): float {.importc: "exp", header: "<math.h>".}
## computes e**x.
## Computes the exponential function of `x` (pow(E, x))
proc frexp*(x: float, exponent: var int): float {.
importc: "frexp", header: "<math.h>".}
## Split a number into mantissa and exponent.
@ -171,62 +185,123 @@ when not defined(JS):
## and less than 1) and the integer value n such that `x` (the original
## float value) equals m * 2**n. frexp stores n in `exponent` and returns
## m.
proc round*(x: float): int {.importc: "lrint", header: "<math.h>".}
## converts a float to an int by rounding.
## Converts a float to an int by rounding.
proc arccos*(x: float): float {.importc: "acos", header: "<math.h>".}
## Computes the arc cosine of `x`
proc arcsin*(x: float): float {.importc: "asin", header: "<math.h>".}
## Computes the arc sine of `x`
proc arctan*(x: float): float {.importc: "atan", header: "<math.h>".}
## Calculate the arc tangent of `y` / `x`
proc arctan2*(y, x: float): float {.importc: "atan2", header: "<math.h>".}
## Calculate the arc tangent of `y` / `x`.
## `atan2` returns the arc tangent of `y` / `x`; it produces correct
## results even when the resulting angle is near pi/2 or -pi/2
## (`x` near 0).
proc cos*(x: float): float {.importc: "cos", header: "<math.h>".}
## Computes the cosine of `x`
proc cosh*(x: float): float {.importc: "cosh", header: "<math.h>".}
## Computes the hyperbolic cosine of `x`
proc hypot*(x, y: float): float {.importc: "hypot", header: "<math.h>".}
## same as ``sqrt(x*x + y*y)``.
## Computes the hypotenuse of a right-angle triangle with `x` and
## `y` as its base and height. Equivalent to ``sqrt(x*x + y*y)``.
proc sinh*(x: float): float {.importc: "sinh", header: "<math.h>".}
## Computes the hyperbolic sine of `x`
proc sin*(x: float): float {.importc: "sin", header: "<math.h>".}
## Computes the sine of `x`
proc tan*(x: float): float {.importc: "tan", header: "<math.h>".}
## Computes the tangent of `x`
proc tanh*(x: float): float {.importc: "tanh", header: "<math.h>".}
## Computes the hyperbolic tangent of `x`
proc pow*(x, y: float): float {.importc: "pow", header: "<math.h>".}
## computes x to power raised of y.
## Computes `x` to power of `y`.
proc erf*(x: float): float {.importc: "erf", header: "<math.h>".}
## The error function
proc erfc*(x: float): float {.importc: "erfc", header: "<math.h>".}
## The complementary error function
proc lgamma*(x: float): float {.importc: "lgamma", header: "<math.h>".}
## Natural log of the gamma function
proc tgamma*(x: float): float {.importc: "tgamma", header: "<math.h>".}
## The gamma function
# C procs:
proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>".}
proc rand(): cint {.importc: "rand", header: "<stdlib.h>".}
when defined(vcc) and false:
# The "secure" random, available from Windows XP
# https://msdn.microsoft.com/en-us/library/sxtz2fa8.aspx
# Present in some variants of MinGW but not enough to justify
# `when defined(windows)` yet
proc rand_s(val: var cuint) {.importc: "rand_s", header: "<stdlib.h>".}
# To behave like the normal version
proc rand(): cuint = rand_s(result)
else:
proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>".}
proc rand(): cint {.importc: "rand", header: "<stdlib.h>".}
when not defined(windows):
proc srand48(seed: clong) {.importc: "srand48", header: "<stdlib.h>".}
proc drand48(): float {.importc: "drand48", header: "<stdlib.h>".}
proc random(max: float): float =
result = drand48() * max
when defined(windows):
proc random(max: float): float =
# we are hardcodeing this because
# importcing macros is extremely problematic
# and because the value is publicly documented
# on MSDN and very unlikely to change
const rand_max = 32767
result = (float(rand()) / float(rand_max)) * max
proc randomize() =
randomize(cast[int](epochTime()))
else:
when defined(vcc): # Windows with Visual C
proc random(max: float): float =
# we are hardcoding this because
# importc-ing macros is extremely problematic
# and because the value is publicly documented
# on MSDN and very unlikely to change
# See https://msdn.microsoft.com/en-us/library/296az74e.aspx
const rand_max = 4294967295 # UINT_MAX
result = (float(rand()) / float(rand_max)) * max
proc randomize() = discard
proc randomize(seed: int) = discard
else: # Windows with another compiler
proc random(max: float): float =
# we are hardcoding this because
# importc-ing macros is extremely problematic
# and because the value is publicly documented
# on MSDN and very unlikely to change
const rand_max = 32767
result = (float(rand()) / float(rand_max)) * max
when not defined(vcc): # the above code for vcc uses `discard` instead
# this is either not Windows or is Windows without vcc
when not defined(nimscript):
proc randomize() =
randomize(cast[int](epochTime()))
proc randomize(seed: int) =
srand(cint(seed)) # rand_s doesn't use srand
when declared(srand48): srand48(seed)
proc randomize(seed: int) =
srand(cint(seed))
when declared(srand48): srand48(seed)
proc random(max: int): int =
result = int(rand()) mod max
proc trunc*(x: float): float {.importc: "trunc", header: "<math.h>".}
## Truncates `x` to the decimal point
##
## .. code-block:: nim
## echo trunc(PI) # 3.0
proc floor*(x: float): float {.importc: "floor", header: "<math.h>".}
## Computes the floor function (i.e., the largest integer not greater than `x`)
##
## .. code-block:: nim
## echo floor(-3.5) ## -4.0
proc ceil*(x: float): float {.importc: "ceil", header: "<math.h>".}
## Computes the ceiling function (i.e., the smallest integer not less than `x`)
##
## .. code-block:: nim
## echo ceil(-2.1) ## -2.0
proc fmod*(x, y: float): float {.importc: "fmod", header: "<math.h>".}
## Computes the remainder of `x` divided by `y`
##
## .. code-block:: nim
## echo fmod(-2.5, 0.3) ## -0.1
else:
proc mathrandom(): float {.importc: "Math.random", nodecl.}
@ -238,7 +313,7 @@ else:
result = float(mathrandom() * float(max))
proc randomize() = discard
proc randomize(seed: int) = discard
proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.}
proc ln*(x: float): float {.importc: "Math.log", nodecl.}
proc log10*(x: float): float = return ln(x) / ln(10.0)
@ -247,7 +322,7 @@ else:
proc exp*(x: float): float {.importc: "Math.exp", nodecl.}
proc round*(x: float): int {.importc: "Math.round", nodecl.}
proc pow*(x, y: float): float {.importc: "Math.pow", nodecl.}
proc frexp*(x: float, exponent: var int): float =
if x == 0.0:
exponent = 0
@ -263,7 +338,7 @@ else:
proc arcsin*(x: float): float {.importc: "Math.asin", nodecl.}
proc arctan*(x: float): float {.importc: "Math.atan", nodecl.}
proc arctan2*(y, x: float): float {.importc: "Math.atan2", nodecl.}
proc cos*(x: float): float {.importc: "Math.cos", nodecl.}
proc cosh*(x: float): float = return (exp(x)+exp(-x))*0.5
proc hypot*(x, y: float): float = return sqrt(x*x + y*y)
@ -276,7 +351,21 @@ else:
{.pop.}
proc degToRad*[T: float32|float64](d: T): T {.inline.} =
## Convert from degrees to radians
result = T(d) * RadPerDeg
proc radToDeg*[T: float32|float64](d: T): T {.inline.} =
## Convert from radians to degrees
result = T(d) / RadPerDeg
proc `mod`*(x, y: float): float =
## Computes the modulo operation for float operators. Equivalent
## to ``x - y * floor(x/y)``. Note that the remainder will always
## have the same sign as the divisor.
##
## .. code-block:: nim
## echo (4.0 mod -3.1) # -2.2
result = if y == 0.0: x else: x - y * (x/y).floor
proc random*[T](x: Slice[T]): T =
@ -295,7 +384,7 @@ type
{.deprecated: [TFloatClass: FloatClass, TRunningStat: RunningStat].}
proc push*(s: var RunningStat, x: float) =
proc push*(s: var RunningStat, x: float) =
## pushes a value `x` for processing
inc(s.n)
# See Knuth TAOCP vol 2, 3rd edition, page 232
@ -315,17 +404,17 @@ proc push*(s: var RunningStat, x: float) =
s.oldM = s.mean
s.oldS = s.newS
s.sum = s.sum + x
proc push*(s: var RunningStat, x: int) =
proc push*(s: var RunningStat, x: int) =
## pushes a value `x` for processing. `x` is simply converted to ``float``
## and the other push operation is called.
push(s, toFloat(x))
proc variance*(s: RunningStat): float =
proc variance*(s: RunningStat): float =
## computes the current variance of `s`
if s.n > 1: result = s.newS / (toFloat(s.n - 1))
proc standardDeviation*(s: RunningStat): float =
proc standardDeviation*(s: RunningStat): float =
## computes the current standard deviation of `s`
result = sqrt(variance(s))
@ -339,14 +428,19 @@ proc `^`*[T](x, y: T): T =
var (x, y) = (x, y)
result = 1
while y != 0:
while true:
if (y and 1) != 0:
result *= x
y = y shr 1
if y == 0:
break
x *= x
proc gcd*[T](x, y: T): T =
## Computes the greatest common divisor of ``x`` and ``y``.
## Note that for floats, the result cannot always be interpreted as
## "greatest decimal `z` such that ``z*N == x and z*M == y``
## where N and M are positive integers."
var (x,y) = (x,y)
while y != 0:
x = x mod y
@ -379,3 +473,9 @@ when isMainModule and not defined(JS):
# Check for no side effect annotation
proc mySqrt(num: float): float {.noSideEffect.} =
return sqrt(num)
# check gamma function
assert($tgamma(5.0) == $24.0) # 4!
assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0
assert(erf(6.0) > erf(5.0))
assert(erfc(6.0) < erfc(5.0))

View file

@ -9,8 +9,6 @@
## Module for computing MD5 checksums.
import unsigned
type
MD5State = array[0..3, uint32]
MD5Block = array[0..15, uint32]

View file

@ -11,6 +11,9 @@
##
## This module provides support for `memory mapped files`:idx:
## (Posix's `mmap`:idx:) on the different operating systems.
##
## It also provides some fast iterators over lines in text files (or
## other "line-like", variable length, delimited records).
when defined(windows):
import winlean
@ -29,8 +32,9 @@ type
size*: int ## size of the memory mapped file
when defined(windows):
fHandle: int
mapHandle: int
fHandle: Handle
mapHandle: Handle
wasOpened: bool ## only close if wasOpened
else:
handle: cint
@ -112,7 +116,8 @@ proc open*(filename: string, mode: FileMode = fmRead,
template callCreateFile(winApiProc, filename: expr): expr =
winApiProc(
filename,
if readonly: GENERIC_READ else: GENERIC_ALL,
# GENERIC_ALL != (GENERIC_READ or GENERIC_WRITE)
if readonly: GENERIC_READ else: GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ,
nil,
if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING,
@ -128,7 +133,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
fail(osLastError(), "error opening file")
if newFileSize != -1:
var
var
sizeHigh = int32(newFileSize shr 32)
sizeLow = int32(newFileSize and 0xffffffff)
@ -169,12 +174,14 @@ proc open*(filename: string, mode: FileMode = fmRead,
if mappedSize != -1: result.size = min(fileSize, mappedSize).int
else: result.size = fileSize.int
result.wasOpened = true
else:
template fail(errCode: OSErrorCode, msg: expr) =
rollback()
if result.handle != 0: discard close(result.handle)
raiseOSError(errCode)
var flags = if readonly: O_RDONLY else: O_RDWR
if newFileSize != -1:
@ -196,7 +203,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
if mappedSize != -1:
result.size = mappedSize
else:
var stat: TStat
var stat: Stat
if fstat(result.handle, stat) != -1:
# XXX: Hmm, this could be unsafe
# Why is mmap taking int anyway?
@ -218,12 +225,12 @@ proc open*(filename: string, mode: FileMode = fmRead,
proc close*(f: var MemFile) =
## closes the memory mapped file `f`. All changes are written back to the
## file system, if `f` was opened with write access.
var error = false
var lastErr: OSErrorCode
when defined(windows):
if f.fHandle != INVALID_HANDLE_VALUE:
if f.fHandle != INVALID_HANDLE_VALUE and f.wasOpened:
error = unmapViewOfFile(f.mem) == 0
lastErr = osLastError()
error = (closeHandle(f.mapHandle) == 0) or error
@ -240,8 +247,102 @@ proc close*(f: var MemFile) =
when defined(windows):
f.fHandle = 0
f.mapHandle = 0
f.wasOpened = false
else:
f.handle = 0
if error: raiseOSError(lastErr)
type MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records
data*: pointer
size*: int
proc c_memcpy(a, b: pointer, n: int) {.importc: "memcpy", header: "<string.h>".}
proc `$`*(ms: MemSlice): string {.inline.} =
## Return a Nim string built from a MemSlice.
var buf = newString(ms.size)
c_memcpy(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
result = buf
iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
## Iterates over [optional `eat`] `delim`-delimited slices in MemFile `mfile`.
##
## Default parameters parse lines ending in either Unix(\\l) or Windows(\\r\\l)
## style on on a line-by-line basis. I.e., not every line needs the same ending.
## Unlike readLine(File) & lines(File), archaic MacOS9 \\r-delimited lines
## are not supported as a third option for each line. Such archaic MacOS9
## files can be handled by passing delim='\\r', eat='\\0', though.
##
## Delimiters are not part of the returned slice. A final, unterminated line
## or record is returned just like any other.
##
## Non-default delimiters can be passed to allow iteration over other sorts
## of "line-like" variable length records. Pass eat='\\0' to be strictly
## `delim`-delimited. (Eating an optional prefix equal to '\\0' is not
## supported.)
##
## This zero copy, memchr-limited interface is probably the fastest way to
## iterate over line-like records in a file. However, returned (data,size)
## objects are not Nim strings, bounds checked Nim arrays, or even terminated
## C strings. So, care is required to access the data (e.g., think C mem*
## functions, not str* functions). Example:
##
## .. code-block:: nim
## var count = 0
## for slice in memSlices(memfiles.open("foo")):
## if slice.size > 0 and cast[cstring](slice.data)[0] != '#':
## inc(count)
## echo count
proc c_memchr(cstr: pointer, c: char, n: csize): pointer {.
importc: "memchr", header: "<string.h>" .}
proc `-!`(p, q: pointer): int {.inline.} = return cast[int](p) -% cast[int](q)
var ms: MemSlice
var ending: pointer
ms.data = mfile.mem
var remaining = mfile.size
while remaining > 0:
ending = c_memchr(ms.data, delim, remaining)
if ending == nil: # unterminated final slice
ms.size = remaining # Weird case..check eat?
yield ms
break
ms.size = ending -! ms.data # delim is NOT included
if eat != '\0' and ms.size > 0 and cast[cstring](ms.data)[ms.size - 1] == eat:
dec(ms.size) # trim pre-delim char
yield ms
ms.data = cast[pointer](cast[int](ending) +% 1) # skip delim
remaining = mfile.size - (ms.data -! mfile.mem)
iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): TaintedString {.inline.} =
## Replace contents of passed buffer with each new line, like
## `readLine(File) <system.html#readLine,File,TaintedString>`_.
## `delim`, `eat`, and delimiting logic is exactly as for
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
##
## .. code-block:: nim
## var buffer: TaintedString = ""
## for line in lines(memfiles.open("foo"), buffer):
## echo line
for ms in memSlices(mfile, delim, eat):
buf.setLen(ms.size)
c_memcpy(addr(buf[0]), ms.data, ms.size)
buf[ms.size] = '\0'
yield buf
iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.} =
## Return each line in a file as a Nim string, like
## `lines(File) <system.html#lines.i,File>`_.
## `delim`, `eat`, and delimiting logic is exactly as for
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
##
## .. code-block:: nim
## for line in lines(memfiles.open("foo")):
## echo line
var buf = TaintedString(newStringOfCap(80))
for line in lines(mfile, buf, delim, eat):
yield buf

View file

@ -7,7 +7,7 @@ type
{.deprecated: [TMersenneTwister: MersenneTwister].}
proc newMersenneTwister*(seed: int): MersenneTwister =
proc newMersenneTwister*(seed: int): MersenneTwister =
result.index = 0
result.mt[0]= uint32(seed)
for i in 1..623'u32:

View file

@ -499,7 +499,7 @@ 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[ext]
result = mimedb.mimes.getOrDefault(ext)
if result == "":
return default

View file

@ -27,8 +27,10 @@ else:
import posix
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET
export Sockaddr_storage
export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen,
Sockaddr_in6,
inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto
export
@ -39,12 +41,12 @@ export
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
MSG_PEEK
when defined(macosx):
when defined(macosx) and not defined(nimdoc):
export SO_NOSIGPIPE
type
Port* = distinct uint16 ## port type
Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed
## here are unsupported.
@ -59,7 +61,7 @@ type
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
Protocol* = enum ## third argument to `socket` proc
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_UDP = 17, ## User datagram protocol.
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
@ -89,15 +91,19 @@ when useWinVersion:
const
IOCPARM_MASK* = 127
IOC_IN* = int(-2147483648)
FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
(102 shl 8) or 126
nativeAfInet = winlean.AF_INET
nativeAfInet6 = winlean.AF_INET6
proc ioctlsocket*(s: SocketHandle, cmd: clong,
proc ioctlsocket*(s: SocketHandle, cmd: clong,
argptr: ptr clong): cint {.
stdcall, importc: "ioctlsocket", dynlib: "ws2_32.dll".}
else:
let
osInvalidSocket* = posix.INVALID_SOCKET
nativeAfInet = posix.AF_INET
nativeAfInet6 = posix.AF_INET6
proc `==`*(a, b: Port): bool {.borrow.}
## ``==`` for ports.
@ -106,13 +112,13 @@ proc `$`*(p: Port): string {.borrow.}
## returns the port number as a string
proc toInt*(domain: Domain): cint
## Converts the TDomain enum to a platform-dependent ``cint``.
## Converts the Domain enum to a platform-dependent ``cint``.
proc toInt*(typ: SockType): cint
## Converts the TType enum to a platform-dependent ``cint``.
## Converts the SockType enum to a platform-dependent ``cint``.
proc toInt*(p: Protocol): cint
## Converts the TProtocol enum to a platform-dependent ``cint``.
## Converts the Protocol enum to a platform-dependent ``cint``.
when not useWinVersion:
proc toInt(domain: Domain): cint =
@ -141,27 +147,29 @@ when not useWinVersion:
else: discard
else:
proc toInt(domain: Domain): cint =
proc toInt(domain: Domain): cint =
result = toU16(ord(domain))
proc toInt(typ: SockType): cint =
result = cint(ord(typ))
proc toInt(p: Protocol): cint =
result = cint(ord(p))
proc newRawSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle =
proc newNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
socket(toInt(domain), toInt(typ), toInt(protocol))
socket(toInt(domain), toInt(sockType), toInt(protocol))
proc newRawSocket*(domain: cint, typ: cint, protocol: cint): SocketHandle =
proc newNativeSocket*(domain: cint, sockType: cint,
protocol: cint): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
##
## Use this overload if one of the enums specified above does
## not contain what you need.
socket(domain, typ, protocol)
socket(domain, sockType, protocol)
proc close*(socket: SocketHandle) =
## closes a socket.
@ -176,35 +184,39 @@ proc bindAddr*(socket: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint
result = bindSocket(socket, name, namelen)
proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## queue of pending connections.
when useWinVersion:
result = winlean.listen(socket, cint(backlog))
else:
result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, af: Domain = AF_INET, typ: SockType = SOCK_STREAM,
prot: Protocol = IPPROTO_TCP): ptr AddrInfo =
proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): ptr AddrInfo =
##
##
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
var hints: AddrInfo
result = nil
hints.ai_family = toInt(af)
hints.ai_socktype = toInt(typ)
hints.ai_protocol = toInt(prot)
hints.ai_family = toInt(domain)
hints.ai_socktype = toInt(sockType)
hints.ai_protocol = toInt(protocol)
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
when not defined(freebsd):
hints.ai_flags = AI_V4MAPPED
var gaiResult = getaddrinfo(address, $port, addr(hints), result)
if gaiResult != 0'i32:
when useWinVersion:
raiseOSError(osLastError())
else:
raise newException(OSError, $gai_strerror(gaiResult))
raiseOSError(osLastError(), $gai_strerror(gaiResult))
proc dealloc*(ai: ptr AddrInfo) =
freeaddrinfo(ai)
proc ntohl*(x: int32): int32 =
proc ntohl*(x: int32): int32 =
## Converts 32-bit integers from network to host byte order.
## On machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 4-byte swap operation.
@ -221,20 +233,20 @@ proc ntohs*(x: int16): int16 =
when cpuEndian == bigEndian: result = x
else: result = (x shr 8'i16) or (x shl 8'i16)
proc htonl*(x: int32): int32 =
template htonl*(x: int32): expr =
## Converts 32-bit integers from host to network byte order. On machines
## where the host byte order is the same as network byte order, this is
## a no-op; otherwise, it performs a 4-byte swap operation.
result = rawsockets.ntohl(x)
nativesockets.ntohl(x)
proc htons*(x: int16): int16 =
template htons*(x: int16): expr =
## Converts 16-bit positive integers from host to network byte order.
## On machines where the host byte order is the same as network byte
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
result = rawsockets.ntohs(x)
nativesockets.ntohs(x)
proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## Searches the database from the beginning and finds the first entry for
## which the service name specified by ``name`` matches the s_name member
## and the protocol name specified by ``proto`` matches the s_proto member.
##
@ -243,15 +255,15 @@ proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
var s = winlean.getservbyname(name, proto)
else:
var s = posix.getservbyname(name, proto)
if s == nil: raise newException(OSError, "Service not found.")
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
## protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
@ -259,7 +271,7 @@ proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].}
var s = winlean.getservbyport(ze(int16(port)).cint, proto)
else:
var s = posix.getservbyport(ze(int16(port)).cint, proto)
if s == nil: raise newException(OSError, "Service not found.")
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
@ -269,17 +281,17 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the hostname of an IP Address.
var myaddr: InAddr
myaddr.s_addr = inet_addr(ip)
when useWinVersion:
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
cint(rawsockets.AF_INET))
cint(AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
if s == nil:
raise newException(OSError, $hstrerror(h_errno))
raiseOSError(osLastError(), $hstrerror(h_errno))
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when useWinVersion:
@ -290,11 +302,11 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
raiseOSError(osLastError(), "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the IP address of a hostname.
when useWinVersion:
var s = winlean.gethostbyname(name)
@ -311,11 +323,43 @@ proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
raiseOSError(osLastError(), "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getSockName*(socket: SocketHandle): Port =
proc getSockDomain*(socket: SocketHandle): Domain =
## returns the socket's domain (AF_INET or AF_INET6).
var name: SockAddr
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
if name.sa_family == nativeAfInet:
result = AF_INET
elif name.sa_family == nativeAfInet6:
result = AF_INET6
else:
raiseOSError(osLastError(), "unknown socket family in getSockFamily")
proc getAddrString*(sockAddr: ptr SockAddr): string =
## return the string representation of address within sockAddr
if sockAddr.sa_family == nativeAfInet:
result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr)
elif sockAddr.sa_family == nativeAfInet6:
when not useWinVersion:
# TODO: Windows
result = newString(posix.INET6_ADDRSTRLEN)
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr
discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring,
result.len.int32)
if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0:
result = result.substr("::ffff:".len)
else:
raiseOSError(osLastError(), "unknown socket family in getAddrString")
proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number.
var name: Sockaddr_in
when useWinVersion:
@ -328,14 +372,86 @@ proc getSockName*(socket: SocketHandle): Port =
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = Port(rawsockets.ntohs(name.sin_port))
result = Port(nativesockets.ntohs(name.sin_port))
proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) =
## returns the socket's local address and port number.
##
## Similar to POSIX's `getsockname`:idx:.
case domain
of AF_INET:
var name: Sockaddr_in
when useWinVersion:
name.sin_family = int16(ord(AF_INET))
else:
name.sin_family = posix.AF_INET
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = ($inet_ntoa(name.sin_addr),
Port(nativesockets.ntohs(name.sin_port)))
of AF_INET6:
var name: Sockaddr_in6
when useWinVersion:
name.sin6_family = int16(ord(AF_INET6))
else:
name.sin6_family = posix.AF_INET6
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
# Cannot use INET6_ADDRSTRLEN here, because it's a C define.
var buf: array[64, char]
if inet_ntop(name.sin6_family.cint,
addr name, buf.cstring, sizeof(buf).int32).isNil:
raiseOSError(osLastError())
result = ($buf, Port(nativesockets.ntohs(name.sin6_port)))
else:
raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr")
proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) =
## returns the socket's peer address and port number.
##
## Similar to POSIX's `getpeername`:idx:
case domain
of AF_INET:
var name: Sockaddr_in
when useWinVersion:
name.sin_family = int16(ord(AF_INET))
else:
name.sin_family = posix.AF_INET
var namelen = sizeof(name).SockLen
if getpeername(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = ($inet_ntoa(name.sin_addr),
Port(nativesockets.ntohs(name.sin_port)))
of AF_INET6:
var name: Sockaddr_in6
when useWinVersion:
name.sin6_family = int16(ord(AF_INET6))
else:
name.sin6_family = posix.AF_INET6
var namelen = sizeof(name).SockLen
if getpeername(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
# Cannot use INET6_ADDRSTRLEN here, because it's a C define.
var buf: array[64, char]
if inet_ntop(name.sin6_family.cint,
addr name, buf.cstring, sizeof(buf).int32).isNil:
raiseOSError(osLastError())
result = ($buf, Port(nativesockets.ntohs(name.sin6_port)))
else:
raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr")
proc getSockOptInt*(socket: SocketHandle, level, optname: int): int {.
tags: [ReadIOEffect].} =
tags: [ReadIOEffect].} =
## getsockopt for integer options.
var res: cint
var size = sizeof(res).SockLen
if getsockopt(socket, cint(level), cint(optname),
if getsockopt(socket, cint(level), cint(optname),
addr(res), addr(size)) < 0'i32:
raiseOSError(osLastError())
result = int(res)
@ -344,7 +460,7 @@ proc setSockOptInt*(socket: SocketHandle, level, optname, optval: int) {.
tags: [WriteIOEffect].} =
## setsockopt for integer options.
var value = cint(optval)
if setsockopt(socket, cint(level), cint(optname), addr(value),
if setsockopt(socket, cint(level), cint(optname), addr(value),
sizeof(value).SockLen) < 0'i32:
raiseOSError(osLastError())
@ -371,13 +487,13 @@ proc timeValFromMilliseconds(timeout = 500): Timeval =
result.tv_sec = seconds.int32
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
proc createFdSet(fd: var TFdSet, s: seq[SocketHandle], m: var int) =
proc createFdSet(fd: var TFdSet, s: seq[SocketHandle], m: var int) =
FD_ZERO(fd)
for i in items(s):
for i in items(s):
m = max(m, int(i))
FD_SET(i, fd)
proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) =
proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) =
var i = 0
var L = s.len
while i < L:
@ -391,22 +507,22 @@ proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) =
proc select*(readfds: var seq[SocketHandle], timeout = 500): int =
## Traditional select function. This function will return the number of
## sockets that are ready to be read from, written to, or which have errors.
## If there are none; 0 is returned.
## ``Timeout`` is in miliseconds and -1 can be specified for no timeout.
##
## If there are none; 0 is returned.
## ``Timeout`` is in milliseconds and -1 can be specified for no timeout.
##
## A socket is removed from the specific ``seq`` when it has data waiting to
## be read/written to or has errors (``exceptfds``).
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd: TFdSet
var m = 0
createFdSet((rd), readfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), nil, nil, nil))
pruneSocketSet(readfds, (rd))
proc selectWrite*(writefds: var seq[SocketHandle],
@ -416,19 +532,19 @@ proc selectWrite*(writefds: var seq[SocketHandle],
## written to. The sockets which can be written to will also be removed
## from ``writefds``.
##
## ``timeout`` is specified in miliseconds and ``-1`` can be specified for
## ``timeout`` is specified in milliseconds and ``-1`` can be specified for
## an unlimited time.
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var wr: TFdSet
var m = 0
createFdSet((wr), writefds, m)
if timeout != -1:
result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv)))
else:
result = int(select(cint(m+1), nil, addr(wr), nil, nil))
pruneSocketSet(writefds, (wr))
when defined(Windows):

View file

@ -10,7 +10,7 @@
## This module implements a high-level cross-platform sockets interface.
{.deadCodeElim: on.}
import rawsockets, os, strutils, unsigned, parseutils, times
import nativesockets, os, strutils, unsigned, parseutils, times
export Port, `$`, `==`
const useWinVersion = defined(Windows) or defined(nimdoc)
@ -26,15 +26,18 @@ when defined(ssl):
SslCVerifyMode* = enum
CVerifyNone, CVerifyPeer
SslProtVersion* = enum
protSSLv2, protSSLv3, protTLSv1, protSSLv23
SslContext* = distinct SslCtx
SslAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
SslHandshakeType* = enum
handshakeAsClient, handshakeAsServer
{.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext,
TSSLAcceptResult: SSLAcceptResult].}
@ -61,6 +64,9 @@ type
sslPeekChar: char
of false: nil
lastError: OSErrorCode ## stores the last error on this socket
domain: Domain
sockType: SockType
protocol: Protocol
Socket* = ref SocketImpl
@ -86,7 +92,7 @@ type
IPv6, ## IPv6 address
IPv4 ## IPv4 address
TIpAddress* = object ## stores an arbitrary IP address
IpAddress* = object ## stores an arbitrary IP address
case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6)
of IpAddressFamily.IPv6:
address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in
@ -94,9 +100,12 @@ type
of IpAddressFamily.IPv4:
address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in
## case of IPv4
{.deprecated: [TIpAddress: IpAddress].}
proc isIpAddress*(address_str: string): bool {.tags: [].}
proc parseIpAddress*(address_str: string): TIpAddress
proc parseIpAddress*(address_str: string): IpAddress
proc socketError*(socket: Socket, err: int = -1, async = false,
lastError = (-1).OSErrorCode): void
proc isDisconnectionError*(flags: set[SocketFlag],
lastError: OSErrorCode): bool =
@ -108,7 +117,7 @@ proc isDisconnectionError*(flags: set[SocketFlag],
WSAEDISCON, ERROR_NETNAME_DELETED}
else:
SocketFlag.SafeDisconn in flags and
lastError.int32 in {ECONNRESET, EPIPE, ENETRESET}
lastError.int32 in {ECONNRESET, EPIPE, ENETRESET}
proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
## Converts the flags into the underlying OS representation.
@ -118,33 +127,39 @@ proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
result = result or MSG_PEEK
of SocketFlag.SafeDisconn: continue
proc newSocket(fd: SocketHandle, isBuff: bool): Socket =
proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket as specified by the params.
assert fd != osInvalidSocket
new(result)
result.fd = fd
result.isBuffered = isBuff
if isBuff:
result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered:
result.currPos = 0
proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket =
proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
## Creates a new socket.
##
## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol)
let fd = newNativeSocket(domain, sockType, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = newSocket(fd, buffered)
result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol,
buffered)
proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket.
##
## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol)
let fd = newNativeSocket(domain, sockType, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = newSocket(fd, buffered)
result = newSocket(fd, domain, sockType, protocol, buffered)
when defined(ssl):
CRYPTO_malloc_init()
@ -171,27 +186,27 @@ when defined(ssl):
raise newException(system.IOError, "Certificate file could not be found: " & certFile)
if keyFile != "" and not existsFile(keyFile):
raise newException(system.IOError, "Key file could not be found: " & keyFile)
if certFile != "":
var ret = SSLCTXUseCertificateChainFile(ctx, certFile)
if ret != 1:
raiseSSLError()
# TODO: Password? www.rtfm.com/openssl-examples/part1.pdf
if keyFile != "":
if SSL_CTX_use_PrivateKey_file(ctx, keyFile,
SSL_FILETYPE_PEM) != 1:
raiseSSLError()
if SSL_CTX_check_private_key(ctx) != 1:
raiseSSLError("Verification of private key file failed.")
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
certFile = "", keyFile = ""): SSLContext =
## Creates an SSL context.
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1
## are available with the addition of ``protSSLv23`` which allows for
##
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1
## are available with the addition of ``protSSLv23`` which allows for
## compatibility with all of them.
##
## There are currently only two options for verify mode;
@ -208,15 +223,12 @@ when defined(ssl):
of protSSLv23:
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
of protSSLv2:
when not defined(linux):
newCTX = SSL_CTX_new(SSLv2_method())
else:
raiseSslError()
raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3")
of protSSLv3:
newCTX = SSL_CTX_new(SSLv3_method())
of protTLSv1:
newCTX = SSL_CTX_new(TLSv1_method())
if newCTX.SSLCTXSetCipherList("ALL") != 1:
raiseSSLError()
case verifyMode
@ -235,9 +247,13 @@ when defined(ssl):
## Wraps a socket in an SSL context. This function effectively turns
## ``socket`` into an SSL socket.
##
## This must be called on an unconnected socket; an SSL session will
## be started when the socket is connected.
##
## **Disclaimer**: This code is not well tested, may be very unsafe and
## prone to security vulnerabilities.
assert (not socket.isSSL)
socket.isSSL = true
socket.sslContext = ctx
socket.sslHandle = SSLNew(SSLCTX(socket.sslContext))
@ -245,10 +261,29 @@ when defined(ssl):
socket.sslHasPeekChar = false
if socket.sslHandle == nil:
raiseSSLError()
if SSLSetFd(socket.sslHandle, socket.fd) != 1:
raiseSSLError()
proc wrapConnectedSocket*(ctx: SSLContext, socket: Socket,
handshake: SslHandshakeType) =
## Wraps a connected socket in an SSL context. This function effectively
## turns ``socket`` into an SSL socket.
##
## This should be called on a connected socket, and will perform
## an SSL handshake immediately.
##
## **Disclaimer**: This code is not well tested, may be very unsafe and
## prone to security vulnerabilities.
wrapSocket(ctx, socket)
case handshake
of handshakeAsClient:
let ret = SSLConnect(socket.sslHandle)
socketError(socket, ret)
of handshakeAsServer:
let ret = SSLAccept(socket.sslHandle)
socketError(socket, ret)
proc getSocketError*(socket: Socket): OSErrorCode =
## Checks ``osLastError`` for a valid error. If it has been reset it uses
## the last error stored in the socket object.
@ -256,7 +291,7 @@ proc getSocketError*(socket: Socket): OSErrorCode =
if result == 0.OSErrorCode:
result = socket.lastError
if result == 0.OSErrorCode:
raise newException(OSError, "No valid socket error code available")
raiseOSError(result, "No valid socket error code available")
proc socketError*(socket: Socket, err: int = -1, async = false,
lastError = (-1).OSErrorCode) =
@ -294,14 +329,12 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
else:
let errStr = $ErrErrorString(sslErr, nil)
raiseSSLError(errStr & ": " & errStr)
let osMsg = osErrorMsg osLastError()
if osMsg != "":
errStr.add ". The OS reports: " & osMsg
raise newException(OSError, errStr)
let osErr = osLastError()
raiseOSError(osErr, errStr)
of SSL_ERROR_SSL:
raiseSSLError()
else: raiseSSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
var lastE = if lastError.int == -1: getSocketError(socket) else: lastError
if async:
@ -316,12 +349,12 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
else: raiseOSError(lastE)
proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## queue of pending connections.
##
## Raises an EOS error upon failure.
if rawsockets.listen(socket.fd, backlog) < 0'i32:
if nativesockets.listen(socket.fd, backlog) < 0'i32:
raiseOSError(osLastError())
proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
@ -342,7 +375,7 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
sizeof(name).SockLen) < 0'i32:
raiseOSError(osLastError())
else:
var aiList = getAddrInfo(address, port, AF_INET)
var aiList = getAddrInfo(address, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32:
dealloc(aiList)
raiseOSError(osLastError())
@ -359,7 +392,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
## The resulting client will inherit any properties of the server socket. For
## example: whether the socket is buffered or not.
##
## **Note**: ``client`` must be initialised (with ``new``), this function
## **Note**: ``client`` must be initialised (with ``new``), this function
## makes no effort to initialise the ``client`` variable.
##
## The ``accept`` call may result in an error if the connecting socket
@ -371,7 +404,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
var addrLen = sizeof(sockAddress).SockLen
var sock = accept(server.fd, cast[ptr SockAddr](addr(sockAddress)),
addr(addrLen))
if sock == osInvalidSocket:
let err = osLastError()
if flags.isDisconnectionError(err):
@ -385,21 +418,21 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
when defined(ssl):
if server.isSSL:
# We must wrap the client sock in a ssl context.
server.sslContext.wrapSocket(client)
let ret = SSLAccept(client.sslHandle)
socketError(client, ret, false)
# Client socket is set above.
address = $inet_ntoa(sockAddress.sin_addr)
when false: #defined(ssl):
proc acceptAddrSSL*(server: Socket, client: var Socket,
address: var string): TSSLAcceptResult {.
address: var string): SSLAcceptResult {.
tags: [ReadIOEffect].} =
## This procedure should only be used for non-blocking **SSL** sockets.
## This procedure should only be used for non-blocking **SSL** sockets.
## It will immediately return with one of the following values:
##
##
## ``AcceptSuccess`` will be returned when a client has been successfully
## accepted and the handshake has been successfully performed between
## ``server`` and the newly connected client.
@ -416,7 +449,7 @@ when false: #defined(ssl):
if server.isSSL:
client.setBlocking(false)
# We must wrap the client sock in a ssl context.
if not client.isSSL or client.sslHandle == nil:
server.sslContext.wrapSocket(client)
let ret = SSLAccept(client.sslHandle)
@ -449,7 +482,7 @@ proc accept*(server: Socket, client: var Socket,
flags = {SocketFlag.SafeDisconn}) {.tags: [ReadIOEffect].} =
## Equivalent to ``acceptAddr`` but doesn't return the address, only the
## socket.
##
##
## **Note**: ``client`` must be initialised (with ``new``), this function
## makes no effort to initialise the ``client`` variable.
##
@ -497,21 +530,33 @@ proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {.
var res = getSockOptInt(socket.fd, cint(level), toCInt(opt))
result = res != 0
proc getLocalAddr*(socket: Socket): (string, Port) =
## Get the socket's local address and port number.
##
## This is high-level interface for `getsockname`:idx:.
getLocalAddr(socket.fd, socket.domain)
proc getPeerAddr*(socket: Socket): (string, Port) =
## Get the socket's peer address and port number.
##
## This is high-level interface for `getpeername`:idx:.
getPeerAddr(socket.fd, socket.domain)
proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {.
tags: [WriteIOEffect].} =
## Sets option ``opt`` to a boolean value specified by ``value``.
var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
proc connect*(socket: Socket, address: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
proc connect*(socket: Socket, address: string,
port = Port(0)) {.tags: [ReadIOEffect].} =
## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a
## host name. If ``address`` is a host name, this function will try each IP
## of that host name. ``htons`` is already performed on ``port`` so you must
## not do it.
##
## If ``socket`` is an SSL socket a handshake will be automatically performed.
var aiList = getAddrInfo(address, port, af)
var aiList = getAddrInfo(address, port, socket.domain)
# try all possibilities:
var success = false
var lastError: OSErrorCode
@ -525,7 +570,7 @@ proc connect*(socket: Socket, address: string, port = Port(0),
dealloc(aiList)
if not success: raiseOSError(lastError)
when defined(ssl):
if socket.isSSL:
# RFC3546 for SNI specifies that IP addresses are not allowed.
@ -633,12 +678,12 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
if socket.isBuffered:
if socket.bufLen == 0:
retRead(0'i32, 0)
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
let chunk = min(socket.bufLen-socket.currPos, size-read)
var d = cast[cstring](data)
assert size-read >= chunk
@ -685,7 +730,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
else:
if timeout - int(waited * 1000.0) < 1:
raise newException(TimeoutError, "Call to '" & funcName & "' timed out.")
when defined(ssl):
if socket.isSSL:
if socket.hasDataBuffered:
@ -694,7 +739,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
let sslPending = SSLPending(socket.sslHandle)
if sslPending != 0:
return sslPending
var startTime = epochTime()
let selRet = select(socket, timeout - int(waited * 1000.0))
if selRet < 0: raiseOSError(osLastError())
@ -704,9 +749,9 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
proc recv*(socket: Socket, data: pointer, size: int, timeout: int): int {.
tags: [ReadIOEffect, TimeEffect].} =
## overload with a ``timeout`` parameter in miliseconds.
var waited = 0.0 # number of seconds already waited
## overload with a ``timeout`` parameter in milliseconds.
var waited = 0.0 # number of seconds already waited
var read = 0
while read < size:
let avail = waitFor(socket, waited, timeout, size-read, "recv")
@ -717,7 +762,7 @@ proc recv*(socket: Socket, data: pointer, size: int, timeout: int): int {.
if result < 0:
return result
inc(read, result)
result = read
proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
@ -729,7 +774,7 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
## This function will throw an EOS exception when an error occurs. A value
## lower than 0 is never returned.
##
## A timeout may be specified in miliseconds, if enough data is not received
## A timeout may be specified in milliseconds, if enough data is not received
## within the time specified an ETimeout exception will be raised.
##
## **Note**: ``data`` must be initialised.
@ -751,7 +796,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
var res = socket.readIntoBuf(0'i32)
if res <= 0:
result = res
c = socket.buffer[socket.currPos]
else:
when defined(ssl):
@ -759,7 +804,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = true
c = socket.sslPeekChar
return
result = recv(socket.fd, addr(c), 1, MSG_PEEK)
@ -772,16 +817,16 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## An EOS exception will be raised in the case of a socket error.
##
## A timeout can be specified in miliseconds, if data is not received within
## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
template addNLIfEmpty(): stmt =
if line.len == 0:
line.add("\c\L")
@ -808,7 +853,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
elif n <= 0: raiseSockError()
addNLIfEmpty()
return
elif c == '\L':
elif c == '\L':
addNLIfEmpty()
return
add(line.string, c)
@ -826,7 +871,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int,
## so when ``socket`` is buffered the non-buffered implementation will be
## used. Therefore if ``socket`` contains something in its buffer this
## function will make no effort to return it.
# TODO: Buffered sockets
data.setLen(length)
var sockAddress: Sockaddr_in
@ -844,7 +889,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int,
proc skip*(socket: Socket, size: int, timeout = -1) =
## Skips ``size`` amount of bytes.
##
## An optional timeout can be specified in miliseconds, if skipping the
## An optional timeout can be specified in milliseconds, if skipping the
## bytes takes longer than specified an ETimeout exception will be raised.
##
## Returns the number of skipped bytes.
@ -860,16 +905,16 @@ proc send*(socket: Socket, data: pointer, size: int): int {.
tags: [WriteIOEffect].} =
## Sends data to a socket.
##
## **Note**: This is a low-level version of ``send``. You likely should use
## **Note**: This is a low-level version of ``send``. You likely should use
## the version below.
when defined(ssl):
if socket.isSSL:
return SSLWrite(socket.sslHandle, cast[cstring](data), size)
when useWinVersion or defined(macosx):
result = send(socket.fd, data, size.cint, 0'i32)
else:
when defined(solaris):
when defined(solaris):
const MSG_NOSIGNAL = 0
result = send(socket.fd, data, size, int32(MSG_NOSIGNAL))
@ -883,7 +928,7 @@ proc send*(socket: Socket, data: string,
socketError(socket, lastError = lastError)
if sent != data.len:
raise newException(OSError, "Could not send all data.")
raiseOSError(osLastError(), "Could not send all data.")
proc trySend*(socket: Socket, data: string): bool {.tags: [WriteIOEffect].} =
## Safe alternative to ``send``. Does not raise an EOS when an error occurs,
@ -894,7 +939,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
size: int, af: Domain = AF_INET, flags = 0'i32): int {.
tags: [WriteIOEffect].} =
## This proc sends ``data`` to the specified ``address``,
## which may be an IP address or a hostname, if a hostname is specified
## which may be an IP address or a hostname, if a hostname is specified
## this function will try each IP of that hostname.
##
##
@ -903,7 +948,7 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
##
## **Note:** This proc is not available for SSL sockets.
var aiList = getAddrInfo(address, port, af)
# try all possibilities:
var success = false
var it = aiList
@ -917,10 +962,10 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer,
dealloc(aiList)
proc sendTo*(socket: Socket, address: string, port: Port,
proc sendTo*(socket: Socket, address: string, port: Port,
data: string): int {.tags: [WriteIOEffect].} =
## This proc sends ``data`` to the specified ``address``,
## which may be an IP address or a hostname, if a hostname is specified
## which may be an IP address or a hostname, if a hostname is specified
## this function will try each IP of that hostname.
##
## This is the high-level version of the above ``sendTo`` function.
@ -957,21 +1002,21 @@ proc connectAsync(socket: Socket, name: string, port = Port(0),
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
success = true
break
it = it.ai_next
dealloc(aiList)
if not success: raiseOSError(lastError)
proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} =
proc connect*(socket: Socket, address: string, port = Port(0),
timeout: int) {.tags: [ReadIOEffect, WriteIOEffect].} =
## Connects to server as specified by ``address`` on port specified by ``port``.
##
## The ``timeout`` paremeter specifies the time in miliseconds to allow for
## The ``timeout`` paremeter specifies the time in milliseconds to allow for
## the connection to the server to be made.
socket.fd.setBlocking(false)
socket.connectAsync(address, port, af)
socket.connectAsync(address, port, socket.domain)
var s = @[socket.fd]
if selectWrite(s, timeout) != 1:
raise newException(TimeoutError, "Call to 'connect' timed out.")
@ -982,7 +1027,7 @@ proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
doAssert socket.handshake()
socket.fd.setBlocking(true)
proc isSsl*(socket: Socket): bool =
proc isSsl*(socket: Socket): bool =
## Determines whether ``socket`` is a SSL socket.
when defined(ssl):
result = socket.isSSL
@ -992,39 +1037,39 @@ proc isSsl*(socket: Socket): bool =
proc getFd*(socket: Socket): SocketHandle = return socket.fd
## Returns the socket's file descriptor
proc IPv4_any*(): TIpAddress =
proc IPv4_any*(): IpAddress =
## Returns the IPv4 any address, which can be used to listen on all available
## network adapters
result = TIpAddress(
result = IpAddress(
family: IpAddressFamily.IPv4,
address_v4: [0'u8, 0, 0, 0])
proc IPv4_loopback*(): TIpAddress =
proc IPv4_loopback*(): IpAddress =
## Returns the IPv4 loopback address (127.0.0.1)
result = TIpAddress(
result = IpAddress(
family: IpAddressFamily.IPv4,
address_v4: [127'u8, 0, 0, 1])
proc IPv4_broadcast*(): TIpAddress =
proc IPv4_broadcast*(): IpAddress =
## Returns the IPv4 broadcast address (255.255.255.255)
result = TIpAddress(
result = IpAddress(
family: IpAddressFamily.IPv4,
address_v4: [255'u8, 255, 255, 255])
proc IPv6_any*(): TIpAddress =
proc IPv6_any*(): IpAddress =
## Returns the IPv6 any address (::0), which can be used
## to listen on all available network adapters
result = TIpAddress(
## to listen on all available network adapters
result = IpAddress(
family: IpAddressFamily.IPv6,
address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
proc IPv6_loopback*(): TIpAddress =
proc IPv6_loopback*(): IpAddress =
## Returns the IPv6 loopback address (::1)
result = TIpAddress(
result = IpAddress(
family: IpAddressFamily.IPv6,
address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
proc `==`*(lhs, rhs: TIpAddress): bool =
proc `==`*(lhs, rhs: IpAddress): bool =
## Compares two IpAddresses for Equality. Returns two if the addresses are equal
if lhs.family != rhs.family: return false
if lhs.family == IpAddressFamily.IPv4:
@ -1035,8 +1080,8 @@ proc `==`*(lhs, rhs: TIpAddress): bool =
if lhs.address_v6[i] != rhs.address_v6[i]: return false
return true
proc `$`*(address: TIpAddress): string =
## Converts an TIpAddress into the textual representation
proc `$`*(address: IpAddress): string =
## Converts an IpAddress into the textual representation
result = ""
case address.family
of IpAddressFamily.IPv4:
@ -1095,7 +1140,7 @@ proc `$`*(address: TIpAddress): string =
mask = mask shr 4
printedLastGroup = true
proc parseIPv4Address(address_str: string): TIpAddress =
proc parseIPv4Address(address_str: string): IpAddress =
## Parses IPv4 adresses
## Raises EInvalidValue on errors
var
@ -1129,7 +1174,7 @@ proc parseIPv4Address(address_str: string): TIpAddress =
raise newException(ValueError, "Invalid IP Address")
result.address_v4[byteCount] = cast[uint8](currentByte)
proc parseIPv6Address(address_str: string): TIpAddress =
proc parseIPv6Address(address_str: string): IpAddress =
## Parses IPv6 adresses
## Raises EInvalidValue on errors
result.family = IpAddressFamily.IPv6
@ -1151,7 +1196,7 @@ proc parseIPv6Address(address_str: string): TIpAddress =
if not seperatorValid:
raise newException(ValueError,
"Invalid IP Address. Address contains an invalid seperator")
if lastWasColon:
if lastWasColon:
if dualColonGroup != -1:
raise newException(ValueError,
"Invalid IP Address. Address contains more than one \"::\" seperator")
@ -1164,14 +1209,14 @@ proc parseIPv6Address(address_str: string): TIpAddress =
result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8)
result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF)
currentShort = 0
groupCount.inc()
groupCount.inc()
if dualColonGroup != -1: seperatorValid = false
elif i == 0: # only valid if address starts with ::
if address_str[1] != ':':
raise newException(ValueError,
"Invalid IP Address. Address may not start with \":\"")
else: # i == high(address_str) - only valid if address ends with ::
if address_str[high(address_str)-1] != ':':
if address_str[high(address_str)-1] != ':':
raise newException(ValueError,
"Invalid IP Address. Address may not end with \":\"")
lastWasColon = true
@ -1250,7 +1295,7 @@ proc parseIPv6Address(address_str: string): TIpAddress =
raise newException(ValueError,
"Invalid IP Address. The address consists of too many groups")
proc parseIpAddress(address_str: string): TIpAddress =
proc parseIpAddress(address_str: string): IpAddress =
## Parses an IP address
## Raises EInvalidValue on error
if address_str == nil:

View file

@ -26,17 +26,19 @@ const
withThreads = compileOption("threads")
tickCountCorrection = 50_000
when not declared(system.TStackTrace):
type TStackTrace = array [0..20, cstring]
when not declared(system.StackTrace):
type StackTrace = array [0..20, cstring]
{.deprecated: [TStackTrace: StackTrace].}
# We use a simple hash table of bounded size to keep track of the stack traces:
type
TProfileEntry = object
ProfileEntry = object
total: int
st: TStackTrace
TProfileData = array [0..64*1024-1, ptr TProfileEntry]
st: StackTrace
ProfileData = array [0..64*1024-1, ptr ProfileEntry]
{.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].}
proc `==`(a, b: TStackTrace): bool =
proc `==`(a, b: StackTrace): bool =
for i in 0 .. high(a):
if a[i] != b[i]: return false
result = true
@ -44,13 +46,13 @@ proc `==`(a, b: TStackTrace): bool =
# XXX extract this data structure; it is generally useful ;-)
# However a chain length of over 3000 is suspicious...
var
profileData: TProfileData
profileData: ProfileData
emptySlots = profileData.len * 3 div 2
maxChainLen = 0
totalCalls = 0
when not defined(memProfiler):
var interval: TNanos = 5_000_000 - tickCountCorrection # 5ms
var interval: Nanos = 5_000_000 - tickCountCorrection # 5ms
proc setSamplingFrequency*(intervalInUs: int) =
## set this to change the sampling frequency. Default value is 5ms.
@ -62,11 +64,11 @@ when not defined(memProfiler):
when withThreads:
import locks
var
profilingLock: TLock
profilingLock: Lock
initLock profilingLock
proc hookAux(st: TStackTrace, costs: int) =
proc hookAux(st: StackTrace, costs: int) =
# this is quite performance sensitive!
when withThreads: acquire profilingLock
inc totalCalls
@ -94,8 +96,8 @@ proc hookAux(st: TStackTrace, costs: int) =
var chain = 0
while true:
if profileData[h] == nil:
profileData[h] = cast[ptr TProfileEntry](
allocShared0(sizeof(TProfileEntry)))
profileData[h] = cast[ptr ProfileEntry](
allocShared0(sizeof(ProfileEntry)))
profileData[h].total = costs
profileData[h].st = st
dec emptySlots
@ -115,7 +117,7 @@ when defined(memProfiler):
var
gTicker {.threadvar.}: int
proc hook(st: TStackTrace, size: int) {.nimcall.} =
proc hook(st: StackTrace, size: int) {.nimcall.} =
if gTicker == 0:
gTicker = -1
when defined(ignoreAllocationSize):
@ -127,33 +129,33 @@ when defined(memProfiler):
else:
var
t0 {.threadvar.}: TTicks
t0 {.threadvar.}: Ticks
proc hook(st: TStackTrace) {.nimcall.} =
proc hook(st: StackTrace) {.nimcall.} =
if interval == 0:
hookAux(st, 1)
elif int64(t0) == 0 or getTicks() - t0 > interval:
hookAux(st, 1)
t0 = getTicks()
proc getTotal(x: ptr TProfileEntry): int =
proc getTotal(x: ptr ProfileEntry): int =
result = if isNil(x): 0 else: x.total
proc cmpEntries(a, b: ptr TProfileEntry): int =
proc cmpEntries(a, b: ptr ProfileEntry): int =
result = b.getTotal - a.getTotal
proc `//`(a, b: int): string =
result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2))
proc writeProfile() {.noconv.} =
when declared(system.TStackTrace):
when declared(system.StackTrace):
system.profilerHook = nil
const filename = "profile_results.txt"
echo "writing " & filename & "..."
var f: File
if open(f, filename, fmWrite):
sort(profileData, cmpEntries)
writeln(f, "total executions of each stack trace:")
writeLine(f, "total executions of each stack trace:")
var entries = 0
for i in 0..high(profileData):
if profileData[i] != nil: inc entries
@ -161,7 +163,7 @@ proc writeProfile() {.noconv.} =
var perProc = initCountTable[string]()
for i in 0..entries-1:
var dups = initSet[string]()
for ii in 0..high(TStackTrace):
for ii in 0..high(StackTrace):
let procname = profileData[i].st[ii]
if isNil(procname): break
let p = $procname
@ -173,13 +175,13 @@ proc writeProfile() {.noconv.} =
for i in 0..min(100, entries-1):
if profileData[i].total > 1:
inc sum, profileData[i].total
writeln(f, "Entry: ", i+1, "/", entries, " Calls: ",
writeLine(f, "Entry: ", i+1, "/", entries, " Calls: ",
profileData[i].total // totalCalls, " [sum: ", sum, "; ",
sum // totalCalls, "]")
for ii in 0..high(TStackTrace):
for ii in 0..high(StackTrace):
let procname = profileData[i].st[ii]
if isNil(procname): break
writeln(f, " ", procname, " ", perProc[$procname] // totalCalls)
writeLine(f, " ", procname, " ", perProc[$procname] // totalCalls)
close(f)
echo "... done"
else:
@ -189,16 +191,16 @@ var
disabled: int
proc disableProfiling*() =
when declared(system.TStackTrace):
when declared(system.StackTrace):
atomicDec disabled
system.profilerHook = nil
proc enableProfiling*() =
when declared(system.TStackTrace):
when declared(system.StackTrace):
if atomicInc(disabled) >= 0:
system.profilerHook = hook
when declared(system.TStackTrace):
when declared(system.StackTrace):
system.profilerHook = hook
addQuitProc(writeProfile)

View file

@ -11,23 +11,23 @@ type OneVarFunction* = proc (x: float): float
{.deprecated: [TOneVarFunction: OneVarFunction].}
proc brent*(xmin,xmax:float, function:OneVarFunction, tol:float,maxiter=1000):
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`
## 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
## 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
var
a=xmin
b=xmax
c=a
d=1.0e308
d=1.0e308
fa=function(a)
fb=function(b)
fc=fa
@ -42,19 +42,19 @@ proc brent*(xmin,xmax:float, function:OneVarFunction, tol:float,maxiter=1000):
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
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
@ -80,5 +80,5 @@ proc brent*(xmin,xmax:float, function:OneVarFunction, tol:float,maxiter=1000):
inc i
if i>maxiter:
break
return (b,fb,true)

View file

@ -8,7 +8,7 @@
#
## Nim OID support. An OID is a global ID that consists of a timestamp,
## a unique counter and a random value. This combination should suffice to
## a unique counter and a random value. This combination should suffice to
## produce a globally distributed unique ID. This implementation was extracted
## from the Mongodb interface and it thus binary compatible with a Mongo OID.
##
@ -19,13 +19,17 @@ import times, endians
type
Oid* = object ## an OID
time: int32 ##
fuzz: int32 ##
count: int32 ##
time: int32 ##
fuzz: int32 ##
count: int32 ##
{.deprecated: [Toid: Oid].}
proc hexbyte*(hex: char): int =
proc `==`*(oid1: Oid, oid2: Oid): bool =
## Compare two Mongo Object IDs for equality
return (oid1.time == oid2.time) and (oid1.fuzz == oid2.fuzz) and (oid1.count == oid2.count)
proc hexbyte*(hex: char): int =
case hex
of '0'..'9': result = (ord(hex) - ord('0'))
of 'a'..'f': result = (ord(hex) - ord('a') + 10)
@ -40,7 +44,7 @@ proc parseOid*(str: cstring): Oid =
bytes[i] = chr((hexbyte(str[2 * i]) shl 4) or hexbyte(str[2 * i + 1]))
inc(i)
proc oidToString*(oid: Oid, str: cstring) =
proc oidToString*(oid: Oid, str: cstring) =
const hex = "0123456789abcdef"
# work around a compiler bug:
var str = str
@ -59,7 +63,7 @@ proc `$`*(oid: Oid): string =
oidToString(oid, result)
var
incr: int
incr: int
fuzz: int32
proc genOid*(): Oid =
@ -69,10 +73,10 @@ proc genOid*(): Oid =
proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.}
var t = gettime(nil)
var i = int32(incr)
atomicInc(incr)
if fuzz == 0:
# racy, but fine semantically:
srand(t)

222
lib/pure/options.nim Normal file
View file

@ -0,0 +1,222 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Abstract
## ========
##
## This module implements types which encapsulate an optional value.
##
## A value of type ``Option[T]`` either contains a value `x` (represented as
## ``some(x)``) or is empty (``none(T)``).
##
## This can be useful when you have a value that can be present or not. The
## absence of a value is often represented by ``nil``, but it is not always
## available, nor is it always a good solution.
##
##
## Tutorial
## ========
##
## Let's start with an example: a procedure that finds the index of a character
## in a string.
##
## .. code-block:: nim
##
## import optionals
##
## proc find(haystack: string, needle: char): Option[int] =
## for i, c in haystack:
## if c == needle:
## return some(i)
## return none(int) # This line is actually optional,
## # because the default is empty
##
## .. code-block:: nim
##
## try:
## assert("abc".find('c').get() == 2) # Immediately extract the value
## except UnpackError: # If there is no value
## assert false # This will not be reached, because the value is present
##
## The ``get`` operation demonstrated above returns the underlying value, or
## raises ``UnpackError`` if there is no value. There is another option for
## obtaining the value: ``unsafeGet``, but you must only use it when you are
## absolutely sure the value is present (e.g. after checking ``isSome``). If
## you do not care about the tiny overhead that ``get`` causes, you should
## simply never use ``unsafeGet``.
##
## How to deal with an absence of a value:
##
## .. code-block:: nim
##
## let result = "team".find('i')
##
## # Nothing was found, so the result is `none`.
## assert(result == none(int))
## # It has no value:
## assert(result.isNone)
##
## try:
## echo result.get()
## assert(false) # This will not be reached
## except UnpackError: # Because an exception is raised
## discard
import typetraits
type
Option*[T] = object
## An optional type that stores its value and state separately in a boolean.
val: T
has: bool
UnpackError* = ref object of ValueError
proc some*[T](val: T): Option[T] =
## Returns a ``Option`` that has this value.
result.has = true
result.val = val
proc none*(T: typedesc): Option[T] =
## Returns a ``Option`` for this type that has no value.
result.has = false
proc isSome*[T](self: Option[T]): bool =
self.has
proc isNone*[T](self: Option[T]): bool =
not self.has
proc unsafeGet*[T](self: Option[T]): T =
## Returns the value of a ``some``. Behavior is undefined for ``none``.
assert self.isSome
self.val
proc get*[T](self: Option[T]): T =
## Returns contents of the Option. If it is none, then an exception is
## thrown.
if self.isNone:
raise UnpackError(msg : "Can't obtain a value from a `none`")
self.val
proc get*[T](self: Option[T], otherwise: T): T =
## Returns the contents of this option or `otherwise` if the option is none.
if self.isSome:
self.val
else:
otherwise
proc map*[T](self: Option[T], callback: proc (input: T)) =
## Applies a callback to the value in this Option
if self.has:
callback(self.val)
proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] =
## Applies a callback to the value in this Option and returns an option
## containing the new value. If this option is None, None will be returned
if self.has:
some[R]( callback(self.val) )
else:
none(R)
proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] =
## Applies a callback to the value in this Option. If the callback returns
## `true`, the option is returned as a Some. If it returns false, it is
## returned as a None.
if self.has and not callback(self.val):
none(T)
else:
self
proc `==`*(a, b: Option): bool =
## Returns ``true`` if both ``Option``s are ``none``,
## or if they have equal values
(a.has and b.has and a.val == b.val) or (not a.has and not b.has)
proc `$`*[T]( self: Option[T] ): string =
## Returns the contents of this option or `otherwise` if the option is none.
if self.has:
"Some(" & $self.val & ")"
else:
"None[" & T.name & "]"
when isMainModule:
import unittest, sequtils
suite "optionals":
# work around a bug in unittest
let intNone = none(int)
let stringNone = none(string)
test "example":
proc find(haystack: string, needle: char): Option[int] =
for i, c in haystack:
if c == needle:
return some i
check("abc".find('c').get() == 2)
let result = "team".find('i')
check result == intNone
check result.isNone
test "some":
check some(6).get() == 6
check some("a").unsafeGet() == "a"
check some(6).isSome
check some("a").isSome
test "none":
expect UnpackError:
discard none(int).get()
check(none(int).isNone)
check(not none(string).isSome)
test "equality":
check some("a") == some("a")
check some(7) != some(6)
check some("a") != stringNone
check intNone == intNone
when compiles(some("a") == some(5)):
check false
when compiles(none(string) == none(int)):
check false
test "get with a default value":
check( some("Correct").get("Wrong") == "Correct" )
check( stringNone.get("Correct") == "Correct" )
test "$":
check( $(some("Correct")) == "Some(Correct)" )
check( $(stringNone) == "None[string]" )
test "map with a void result":
var procRan = 0
some(123).map(proc (v: int) = procRan = v)
check procRan == 123
intNone.map(proc (v: int) = check false)
test "map":
check( some(123).map(proc (v: int): int = v * 2) == some(246) )
check( intNone.map(proc (v: int): int = v * 2).isNone )
test "filter":
check( some(123).filter(proc (v: int): bool = v == 123) == some(123) )
check( some(456).filter(proc (v: int): bool = v == 123).isNone )
check( intNone.filter(proc (v: int): bool = check false).isNone )

Some files were not shown because too many files have changed in this diff Show more