Merge branch 'devel'
This commit is contained in:
commit
3892969af4
1019 changed files with 35409 additions and 74200 deletions
59
lib/arch/arch.nim
Normal file
59
lib/arch/arch.nim
Normal 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
79
lib/arch/i386.asm
Normal 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
90
lib/arch/ms_amd64.asm
Normal 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
12
lib/arch/ms_i386.asm
Normal 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
89
lib/arch/unix_amd64.asm
Normal 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
12
lib/arch/unix_i386.asm
Normal 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'
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)``.
|
||||
|
||||
18
lib/deprecated/core/unsigned.nim
Normal file
18
lib/deprecated/core/unsigned.nim
Normal 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`,
|
||||
`<=`, `<`
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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 != "":
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
14
lib/deprecated/pure/rawsockets.nim
Normal file
14
lib/deprecated/pure/rawsockets.nim
Normal 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)
|
||||
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
@ -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
686
lib/impure/nre.nim
Normal 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
9
lib/impure/nre/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# all executables
|
||||
*
|
||||
!*/
|
||||
!*.*
|
||||
*.exe
|
||||
|
||||
# Wildcard patterns.
|
||||
*.swp
|
||||
nimcache
|
||||
63
lib/impure/nre/private/util.nim
Normal file
63
lib/impure/nre/private/util.nim
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
71
lib/posix/kqueue.nim
Normal 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.
|
||||
|
|
@ -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
|
|
@ -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>".}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
##
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 != "":
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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]")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
154
lib/pure/collections/sharedstrings.nim
Normal file
154
lib/pure/collections/sharedstrings.nim
Normal 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
|
||||
105
lib/pure/collections/sharedtables.nim
Normal file
105
lib/pure/collections/sharedtables.nim
Normal 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
|
||||
132
lib/pure/collections/tableimpl.nim
Normal file
132
lib/pure/collections/tableimpl.nim
Normal 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
|
||||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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) )
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ when not defined(windows):
|
|||
|
||||
when defined(linux):
|
||||
import linux
|
||||
|
||||
|
||||
when defined(freebsd) or defined(macosx):
|
||||
{.emit:"#include <sys/types.h>".}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
143
lib/pure/coro.nim
Normal 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
1
lib/pure/coro.nimcfg
Normal file
|
|
@ -0,0 +1 @@
|
|||
-d:nimCoroutines
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
23
lib/pure/etcpriv.nim
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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" )
|
||||
|
|
|
|||
|
|
@ -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) )
|
||||
|
|
|
|||
|
|
@ -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.} =
|
||||
|
|
|
|||
|
|
@ -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 ``Ü`` 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@
|
|||
|
||||
## Module for computing MD5 checksums.
|
||||
|
||||
import unsigned
|
||||
|
||||
type
|
||||
MD5State = array[0..3, uint32]
|
||||
MD5Block = array[0..15, uint32]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
251
lib/pure/net.nim
251
lib/pure/net.nim
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
222
lib/pure/options.nim
Normal 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
Loading…
Add table
Add a link
Reference in a new issue