Merge remote-tracking branch 'upstream/devel' into devel
This commit is contained in:
commit
d60f8ab991
106 changed files with 3991 additions and 4893 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -52,6 +52,7 @@ xcuserdata/
|
||||||
/run.json
|
/run.json
|
||||||
# for `nim doc foo.nim`
|
# for `nim doc foo.nim`
|
||||||
/*.html
|
/*.html
|
||||||
|
lib/**/*.html
|
||||||
#/testresults.html #covered by /*.html
|
#/testresults.html #covered by /*.html
|
||||||
|
|
||||||
/testresults.json
|
/testresults.json
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,8 @@ proc enumToString*(enums: openArray[enum]): string =
|
||||||
- Added `os.getCurrentCompilerExe` (implmented as `getAppFilename` at CT),
|
- Added `os.getCurrentCompilerExe` (implmented as `getAppFilename` at CT),
|
||||||
can be used to retrieve the currently executing compiler.
|
can be used to retrieve the currently executing compiler.
|
||||||
|
|
||||||
|
- Added `xmltree.toXmlAttributes`.
|
||||||
|
|
||||||
|
|
||||||
### Library changes
|
### Library changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ proc resetLoc(p: BProc, loc: var TLoc) =
|
||||||
else:
|
else:
|
||||||
if optNilCheck in p.options:
|
if optNilCheck in p.options:
|
||||||
linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(p.config, loc))
|
linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(p.config, loc))
|
||||||
if loc.storage != OnStack:
|
if loc.storage != OnStack and containsGcRef:
|
||||||
linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
|
linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
|
||||||
addrLoc(p.config, loc), genTypeInfo(p.module, loc.t, loc.lode.info))
|
addrLoc(p.config, loc), genTypeInfo(p.module, loc.t, loc.lode.info))
|
||||||
# XXX: generated reset procs should not touch the m_type
|
# XXX: generated reset procs should not touch the m_type
|
||||||
|
|
@ -1056,9 +1056,22 @@ proc genVarPrototype(m: BModule, n: PNode) =
|
||||||
if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile")
|
if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile")
|
||||||
addf(m.s[cfsVars], " $1;$n", [sym.loc.r])
|
addf(m.s[cfsVars], " $1;$n", [sym.loc.r])
|
||||||
|
|
||||||
|
const
|
||||||
|
frameDefines = """
|
||||||
|
$1 define nimfr_(proc, file) \
|
||||||
|
TFrame FR_; \
|
||||||
|
FR_.procname = proc; FR_.filename = file; FR_.line = 0; FR_.len = 0; #nimFrame(&FR_);
|
||||||
|
|
||||||
|
$1 define nimfrs_(proc, file, slots, length) \
|
||||||
|
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_);
|
||||||
|
|
||||||
|
$1 define nimln_(n, file) \
|
||||||
|
FR_.line = n; FR_.filename = file;
|
||||||
|
"""
|
||||||
|
|
||||||
proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} =
|
proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} =
|
||||||
addf(result, "#define NIM_NEW_MANGLING_RULES\L" &
|
addf(result, "#define NIM_INTBITS $1\L", [
|
||||||
"#define NIM_INTBITS $1\L", [
|
|
||||||
platform.CPU[conf.target.targetCPU].intSize.rope])
|
platform.CPU[conf.target.targetCPU].intSize.rope])
|
||||||
if conf.cppCustomNamespace.len > 0:
|
if conf.cppCustomNamespace.len > 0:
|
||||||
result.add("#define USE_NIM_NAMESPACE ")
|
result.add("#define USE_NIM_NAMESPACE ")
|
||||||
|
|
@ -1302,6 +1315,7 @@ proc genInitCode(m: BModule) =
|
||||||
## this function is called in cgenWriteModules after all modules are closed,
|
## this function is called in cgenWriteModules after all modules are closed,
|
||||||
## it means raising dependency on the symbols is too late as it will not propogate
|
## it means raising dependency on the symbols is too late as it will not propogate
|
||||||
## into other modules, only simple rope manipulations are allowed
|
## into other modules, only simple rope manipulations are allowed
|
||||||
|
appcg(m, m.s[cfsForwardTypes], frameDefines, [rope("#")])
|
||||||
|
|
||||||
var moduleInitRequired = false
|
var moduleInitRequired = false
|
||||||
let initname = getInitName(m.module)
|
let initname = getInitName(m.module)
|
||||||
|
|
@ -1313,9 +1327,9 @@ proc genInitCode(m: BModule) =
|
||||||
appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n",
|
appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n",
|
||||||
[m.nimTypesName, rope(m.nimTypes)])
|
[m.nimTypesName, rope(m.nimTypes)])
|
||||||
|
|
||||||
# Give this small function its own scope
|
if m.preInitProc.s(cpsInit).len > 0 or m.preInitProc.s(cpsStmts).len > 0:
|
||||||
addf(prc, "{$N", [])
|
# Give this small function its own scope
|
||||||
block:
|
addf(prc, "{$N", [])
|
||||||
# Keep a bogus frame in case the code needs one
|
# Keep a bogus frame in case the code needs one
|
||||||
add(prc, ~"\tTFrame FR_; FR_.len = 0;$N")
|
add(prc, ~"\tTFrame FR_; FR_.len = 0;$N")
|
||||||
|
|
||||||
|
|
@ -1336,8 +1350,11 @@ proc genInitCode(m: BModule) =
|
||||||
add(prc, genSectionStart(cpsStmts, m.config))
|
add(prc, genSectionStart(cpsStmts, m.config))
|
||||||
add(prc, m.preInitProc.s(cpsStmts))
|
add(prc, m.preInitProc.s(cpsStmts))
|
||||||
add(prc, genSectionEnd(cpsStmts, m.config))
|
add(prc, genSectionEnd(cpsStmts, m.config))
|
||||||
addf(prc, "}$N", [])
|
addf(prc, "}$N", [])
|
||||||
|
|
||||||
|
# add new scope for following code, because old vcc compiler need variable
|
||||||
|
# be defined at the top of the block
|
||||||
|
addf(prc, "{$N", [])
|
||||||
if m.initProc.gcFrameId > 0:
|
if m.initProc.gcFrameId > 0:
|
||||||
moduleInitRequired = true
|
moduleInitRequired = true
|
||||||
add(prc, initGCFrame(m.initProc))
|
add(prc, initGCFrame(m.initProc))
|
||||||
|
|
@ -1374,6 +1391,7 @@ proc genInitCode(m: BModule) =
|
||||||
if m.initProc.gcFrameId > 0:
|
if m.initProc.gcFrameId > 0:
|
||||||
moduleInitRequired = true
|
moduleInitRequired = true
|
||||||
add(prc, deinitGCFrame(m.initProc))
|
add(prc, deinitGCFrame(m.initProc))
|
||||||
|
addf(prc, "}$N", [])
|
||||||
|
|
||||||
addf(prc, "}$N$N", [])
|
addf(prc, "}$N$N", [])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -746,6 +746,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||||
else:
|
else:
|
||||||
conf.cppCustomNamespace = "Nim"
|
conf.cppCustomNamespace = "Nim"
|
||||||
defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace)
|
defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace)
|
||||||
|
of "docinternal":
|
||||||
|
processOnOffSwitchG(conf, {optDocInternal}, arg, pass, info)
|
||||||
else:
|
else:
|
||||||
if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
|
if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
|
||||||
else: invalidCmdLineOption(conf, pass, switch, info)
|
else: invalidCmdLineOption(conf, pass, switch, info)
|
||||||
|
|
|
||||||
|
|
@ -132,61 +132,36 @@ type
|
||||||
emptyNode: PNode
|
emptyNode: PNode
|
||||||
otherRead: PNode
|
otherRead: PNode
|
||||||
|
|
||||||
|
proc isLastRead(s: PSym; c: var Con; pc, comesFrom: int): int =
|
||||||
proc isHarmlessVar*(s: PSym; c: Con): bool =
|
var pc = pc
|
||||||
# 's' is harmless if it used only once and its
|
while pc < c.g.len:
|
||||||
# definition/usage are not split by any labels:
|
case c.g[pc].kind
|
||||||
#
|
|
||||||
# let s = foo()
|
|
||||||
# while true:
|
|
||||||
# a[i] = s
|
|
||||||
#
|
|
||||||
# produces:
|
|
||||||
#
|
|
||||||
# def s
|
|
||||||
# L1:
|
|
||||||
# use s
|
|
||||||
# goto L1
|
|
||||||
#
|
|
||||||
# let s = foo()
|
|
||||||
# if cond:
|
|
||||||
# a[i] = s
|
|
||||||
# else:
|
|
||||||
# a[j] = s
|
|
||||||
#
|
|
||||||
# produces:
|
|
||||||
#
|
|
||||||
# def s
|
|
||||||
# fork L2
|
|
||||||
# use s
|
|
||||||
# goto L3
|
|
||||||
# L2:
|
|
||||||
# use s
|
|
||||||
# L3
|
|
||||||
#
|
|
||||||
# So this analysis is for now overly conservative, but correct.
|
|
||||||
var defsite = -1
|
|
||||||
var usages = 0
|
|
||||||
for i in 0..<c.g.len:
|
|
||||||
case c.g[i].kind
|
|
||||||
of def:
|
of def:
|
||||||
if c.g[i].sym == s:
|
if c.g[pc].sym == s:
|
||||||
if defsite < 0: defsite = i
|
# the path lead to a redefinition of 's' --> abandon it.
|
||||||
else: return false
|
return high(int)
|
||||||
|
inc pc
|
||||||
of use:
|
of use:
|
||||||
if c.g[i].sym == s:
|
if c.g[pc].sym == s:
|
||||||
if defsite < 0: return false
|
c.otherRead = c.g[pc].n
|
||||||
for j in defsite .. i:
|
return -1
|
||||||
# not within the same basic block?
|
inc pc
|
||||||
if j in c.jumpTargets: return false
|
of goto:
|
||||||
# if we want to die after the first 'use':
|
pc = pc + c.g[pc].dest
|
||||||
if usages > 1: return false
|
of fork:
|
||||||
inc usages
|
# every branch must lead to the last read of the location:
|
||||||
#of useWithinCall:
|
var variantA = isLastRead(s, c, pc+1, pc)
|
||||||
# if c.g[i].sym == s: return false
|
if variantA < 0: return -1
|
||||||
of goto, fork:
|
let variantB = isLastRead(s, c, pc + c.g[pc].dest, pc)
|
||||||
discard "we do not perform an abstract interpretation yet"
|
if variantB < 0: return -1
|
||||||
result = usages <= 1
|
elif variantA == high(int):
|
||||||
|
variantA = variantB
|
||||||
|
pc = variantA
|
||||||
|
of InstrKind.join:
|
||||||
|
let dest = pc + c.g[pc].dest
|
||||||
|
if dest == comesFrom: return pc + 1
|
||||||
|
inc pc
|
||||||
|
return pc
|
||||||
|
|
||||||
proc isLastRead(n: PNode; c: var Con): bool =
|
proc isLastRead(n: PNode; c: var Con): bool =
|
||||||
# first we need to search for the instruction that belongs to 'n':
|
# first we need to search for the instruction that belongs to 'n':
|
||||||
|
|
@ -195,59 +170,52 @@ proc isLastRead(n: PNode; c: var Con): bool =
|
||||||
var instr = -1
|
var instr = -1
|
||||||
for i in 0..<c.g.len:
|
for i in 0..<c.g.len:
|
||||||
if c.g[i].n == n:
|
if c.g[i].n == n:
|
||||||
if instr < 0: instr = i
|
if instr < 0:
|
||||||
else:
|
instr = i
|
||||||
# eh, we found two positions that belong to 'n'?
|
break
|
||||||
# better return 'false' then:
|
|
||||||
return false
|
|
||||||
if instr < 0: return false
|
if instr < 0: return false
|
||||||
# we go through all paths beginning from 'instr+1' and need to
|
# we go through all paths beginning from 'instr+1' and need to
|
||||||
# ensure that we don't find another 'use X' instruction.
|
# ensure that we don't find another 'use X' instruction.
|
||||||
if instr+1 >= c.g.len: return true
|
if instr+1 >= c.g.len: return true
|
||||||
let s = n.sym
|
when true:
|
||||||
var pcs: seq[int] = @[instr+1]
|
result = isLastRead(n.sym, c, instr+1, -1) >= 0
|
||||||
var takenGotos: IntSet
|
else:
|
||||||
var takenForks = initIntSet()
|
let s = n.sym
|
||||||
while pcs.len > 0:
|
var pcs: seq[int] = @[instr+1]
|
||||||
var pc = pcs.pop
|
var takenGotos: IntSet
|
||||||
|
var takenForks = initIntSet()
|
||||||
|
while pcs.len > 0:
|
||||||
|
var pc = pcs.pop
|
||||||
|
|
||||||
takenGotos = initIntSet()
|
takenGotos = initIntSet()
|
||||||
while pc < c.g.len:
|
while pc < c.g.len:
|
||||||
case c.g[pc].kind
|
case c.g[pc].kind
|
||||||
of def:
|
of def:
|
||||||
if c.g[pc].sym == s:
|
if c.g[pc].sym == s:
|
||||||
# the path lead to a redefinition of 's' --> abandon it.
|
# the path lead to a redefinition of 's' --> abandon it.
|
||||||
when false:
|
break
|
||||||
# Too complex thinking ahead: In reality it is enough to find
|
|
||||||
# the 'def x' here on the current path to make the 'use x' valid.
|
|
||||||
# but for this the definition needs to dominate the usage:
|
|
||||||
var dominates = true
|
|
||||||
for j in pc+1 .. instr:
|
|
||||||
# not within the same basic block?
|
|
||||||
if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr):
|
|
||||||
#if j in c.jumpTargets:
|
|
||||||
dominates = false
|
|
||||||
if dominates: break
|
|
||||||
break
|
|
||||||
inc pc
|
|
||||||
of use:
|
|
||||||
if c.g[pc].sym == s:
|
|
||||||
c.otherRead = c.g[pc].n
|
|
||||||
return false
|
|
||||||
inc pc
|
|
||||||
of goto:
|
|
||||||
# we must leave endless loops eventually:
|
|
||||||
if not takenGotos.containsOrIncl(pc):
|
|
||||||
pc = pc + c.g[pc].dest
|
|
||||||
else:
|
|
||||||
inc pc
|
inc pc
|
||||||
of fork:
|
of use:
|
||||||
# we follow the next instruction but push the dest onto our "work" stack:
|
if c.g[pc].sym == s:
|
||||||
if not takenForks.containsOrIncl(pc):
|
c.otherRead = c.g[pc].n
|
||||||
pcs.add pc + c.g[pc].dest
|
return false
|
||||||
inc pc
|
inc pc
|
||||||
#echo c.graph.config $ n.info, " last read here!"
|
of goto:
|
||||||
return true
|
# we must leave endless loops eventually:
|
||||||
|
if not takenGotos.containsOrIncl(pc):
|
||||||
|
pc = pc + c.g[pc].dest
|
||||||
|
else:
|
||||||
|
inc pc
|
||||||
|
of fork:
|
||||||
|
# we follow the next instruction but push the dest onto our "work" stack:
|
||||||
|
if not takenForks.containsOrIncl(pc):
|
||||||
|
pcs.add pc + c.g[pc].dest
|
||||||
|
inc pc
|
||||||
|
of InstrKind.join:
|
||||||
|
inc pc
|
||||||
|
#echo c.graph.config $ n.info, " last read here!"
|
||||||
|
return true
|
||||||
|
|
||||||
template interestingSym(s: PSym): bool =
|
template interestingSym(s: PSym): bool =
|
||||||
s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
|
s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
|
||||||
|
|
|
||||||
558
compiler/dfa.nim
558
compiler/dfa.nim
|
|
@ -34,12 +34,12 @@ import ast, astalgo, types, intsets, tables, msgs, options, lineinfos
|
||||||
|
|
||||||
type
|
type
|
||||||
InstrKind* = enum
|
InstrKind* = enum
|
||||||
goto, fork, def, use
|
goto, fork, join, def, use
|
||||||
Instr* = object
|
Instr* = object
|
||||||
n*: PNode
|
n*: PNode
|
||||||
case kind*: InstrKind
|
case kind*: InstrKind
|
||||||
of def, use: sym*: PSym
|
of def, use: sym*: PSym
|
||||||
of goto, fork: dest*: int
|
of goto, fork, join: dest*: int
|
||||||
|
|
||||||
ControlFlowGraph* = seq[Instr]
|
ControlFlowGraph* = seq[Instr]
|
||||||
|
|
||||||
|
|
@ -56,6 +56,7 @@ type
|
||||||
inCall, inTryStmt: int
|
inCall, inTryStmt: int
|
||||||
blocks: seq[TBlock]
|
blocks: seq[TBlock]
|
||||||
tryStmtFixups: seq[TPosition]
|
tryStmtFixups: seq[TPosition]
|
||||||
|
forks: seq[TPosition]
|
||||||
owner: PSym
|
owner: PSym
|
||||||
|
|
||||||
proc debugInfo(info: TLineInfo): string =
|
proc debugInfo(info: TLineInfo): string =
|
||||||
|
|
@ -67,18 +68,18 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
|
||||||
var jumpTargets = initIntSet()
|
var jumpTargets = initIntSet()
|
||||||
let last = if last < 0: c.len-1 else: min(last, c.len-1)
|
let last = if last < 0: c.len-1 else: min(last, c.len-1)
|
||||||
for i in start..last:
|
for i in start..last:
|
||||||
if c[i].kind in {goto, fork}:
|
if c[i].kind in {goto, fork, join}:
|
||||||
jumpTargets.incl(i+c[i].dest)
|
jumpTargets.incl(i+c[i].dest)
|
||||||
var i = start
|
var i = start
|
||||||
while i <= last:
|
while i <= last:
|
||||||
if i in jumpTargets: result.add("L" & $i & ":\n")
|
if i in jumpTargets: result.add("L" & $i & ":\n")
|
||||||
result.add "\t"
|
result.add "\t"
|
||||||
result.add $c[i].kind
|
result.add ($i & " " & $c[i].kind)
|
||||||
result.add "\t"
|
result.add "\t"
|
||||||
case c[i].kind
|
case c[i].kind
|
||||||
of def, use:
|
of def, use:
|
||||||
result.add c[i].sym.name.s
|
result.add c[i].sym.name.s
|
||||||
of goto, fork:
|
of goto, fork, join:
|
||||||
result.add "L"
|
result.add "L"
|
||||||
result.add c[i].dest+i
|
result.add c[i].dest+i
|
||||||
result.add("\t#")
|
result.add("\t#")
|
||||||
|
|
@ -98,11 +99,166 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
|
||||||
proc forkI(c: var Con; n: PNode): TPosition =
|
proc forkI(c: var Con; n: PNode): TPosition =
|
||||||
result = TPosition(c.code.len)
|
result = TPosition(c.code.len)
|
||||||
c.code.add Instr(n: n, kind: fork, dest: 0)
|
c.code.add Instr(n: n, kind: fork, dest: 0)
|
||||||
|
c.forks.add result
|
||||||
|
|
||||||
proc gotoI(c: var Con; n: PNode): TPosition =
|
proc gotoI(c: var Con; n: PNode): TPosition =
|
||||||
result = TPosition(c.code.len)
|
result = TPosition(c.code.len)
|
||||||
c.code.add Instr(n: n, kind: goto, dest: 0)
|
c.code.add Instr(n: n, kind: goto, dest: 0)
|
||||||
|
|
||||||
|
#[
|
||||||
|
|
||||||
|
Design of join
|
||||||
|
==============
|
||||||
|
|
||||||
|
block:
|
||||||
|
if cond: break
|
||||||
|
def(x)
|
||||||
|
|
||||||
|
use(x)
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
|
||||||
|
L0: fork L1
|
||||||
|
join L0 # patched.
|
||||||
|
goto Louter
|
||||||
|
L1:
|
||||||
|
def x
|
||||||
|
join L0
|
||||||
|
Louter:
|
||||||
|
use x
|
||||||
|
|
||||||
|
|
||||||
|
block outer:
|
||||||
|
while a:
|
||||||
|
while b:
|
||||||
|
if foo:
|
||||||
|
if bar:
|
||||||
|
break outer # --> we need to 'join' every pushed 'fork' here
|
||||||
|
|
||||||
|
|
||||||
|
This works and then our abstract interpretation needs to deal with 'fork'
|
||||||
|
differently. It really causes a split in execution. Two threads are
|
||||||
|
"spawned" and both need to reach the 'join L' instruction. Afterwards
|
||||||
|
the abstract interpretations are joined and execution resumes single
|
||||||
|
threaded.
|
||||||
|
|
||||||
|
|
||||||
|
Abstract Interpretation
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
proc interpret(pc, state, comesFrom): state =
|
||||||
|
result = state
|
||||||
|
# we need an explicit 'create' instruction (an explicit heap), in order
|
||||||
|
# to deal with 'var x = create(); var y = x; var z = y; destroy(z)'
|
||||||
|
while true:
|
||||||
|
case pc
|
||||||
|
of fork:
|
||||||
|
let a = interpret(pc+1, result, pc)
|
||||||
|
let b = interpret(forkTarget, result, pc)
|
||||||
|
result = a ++ b # ++ is a union operation
|
||||||
|
inc pc
|
||||||
|
of join:
|
||||||
|
if joinTarget == comesFrom: return result
|
||||||
|
else: inc pc
|
||||||
|
of use X:
|
||||||
|
if not result.contains(x):
|
||||||
|
error "variable not initialized " & x
|
||||||
|
inc pc
|
||||||
|
of def X:
|
||||||
|
if not result.contains(x):
|
||||||
|
result.incl X
|
||||||
|
else:
|
||||||
|
error "overwrite of variable causes memory leak " & x
|
||||||
|
inc pc
|
||||||
|
of destroy X:
|
||||||
|
result.excl X
|
||||||
|
|
||||||
|
This is correct but still can lead to false positives:
|
||||||
|
|
||||||
|
proc p(cond: bool) =
|
||||||
|
if cond:
|
||||||
|
new(x)
|
||||||
|
otherThings()
|
||||||
|
if cond:
|
||||||
|
destroy x
|
||||||
|
|
||||||
|
Is not a leak. We should find a way to model *data* flow, not just
|
||||||
|
control flow. One solution is to rewrite the 'if' without a fork
|
||||||
|
instruction. The unstructured aspect can now be easily dealt with
|
||||||
|
the 'goto' and 'join' instructions.
|
||||||
|
|
||||||
|
proc p(cond: bool) =
|
||||||
|
L0: fork Lend
|
||||||
|
new(x)
|
||||||
|
# do not 'join' here!
|
||||||
|
|
||||||
|
Lend:
|
||||||
|
otherThings()
|
||||||
|
join L0 # SKIP THIS FOR new(x) SOMEHOW
|
||||||
|
destroy x
|
||||||
|
join L0 # but here.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
But if we follow 'goto Louter' we will never come to the join point.
|
||||||
|
We restore the bindings after popping pc from the stack then there
|
||||||
|
"no" problem?!
|
||||||
|
|
||||||
|
|
||||||
|
while cond:
|
||||||
|
prelude()
|
||||||
|
if not condB: break
|
||||||
|
postlude()
|
||||||
|
|
||||||
|
--->
|
||||||
|
var setFlag = true
|
||||||
|
while cond and not setFlag:
|
||||||
|
prelude()
|
||||||
|
if not condB:
|
||||||
|
setFlag = true # BUT: Dependency
|
||||||
|
if not setFlag: # HERE
|
||||||
|
postlude()
|
||||||
|
|
||||||
|
--->
|
||||||
|
var setFlag = true
|
||||||
|
while cond and not setFlag:
|
||||||
|
prelude()
|
||||||
|
if not condB:
|
||||||
|
postlude()
|
||||||
|
setFlag = true
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
while cond:
|
||||||
|
prelude()
|
||||||
|
if more:
|
||||||
|
if not condB: break
|
||||||
|
stuffHere()
|
||||||
|
postlude()
|
||||||
|
|
||||||
|
-->
|
||||||
|
var setFlag = true
|
||||||
|
while cond and not setFlag:
|
||||||
|
prelude()
|
||||||
|
if more:
|
||||||
|
if not condB:
|
||||||
|
setFlag = false
|
||||||
|
else:
|
||||||
|
stuffHere()
|
||||||
|
postlude()
|
||||||
|
else:
|
||||||
|
postlude()
|
||||||
|
|
||||||
|
This is getting complicated. Instead we keep the whole 'join' idea but
|
||||||
|
duplicate the 'join' instructions on breaks and return exits!
|
||||||
|
|
||||||
|
]#
|
||||||
|
|
||||||
|
proc joinI(c: var Con; fromFork: TPosition; n: PNode) =
|
||||||
|
let dist = fromFork.int - c.code.len
|
||||||
|
c.code.add Instr(n: n, kind: join, dest: dist)
|
||||||
|
|
||||||
proc genLabel(c: Con): TPosition =
|
proc genLabel(c: Con): TPosition =
|
||||||
result = TPosition(c.code.len)
|
result = TPosition(c.code.len)
|
||||||
|
|
||||||
|
|
@ -135,30 +291,97 @@ proc isTrue(n: PNode): bool =
|
||||||
|
|
||||||
proc gen(c: var Con; n: PNode) # {.noSideEffect.}
|
proc gen(c: var Con; n: PNode) # {.noSideEffect.}
|
||||||
|
|
||||||
proc genWhile(c: var Con; n: PNode) =
|
when true:
|
||||||
# L1:
|
proc genWhile(c: var Con; n: PNode) =
|
||||||
# cond, tmp
|
# We unroll every loop 3 times. We emulate 0, 1, 2 iterations
|
||||||
# fork tmp, L2
|
# through the loop. We need to prove this is correct for our
|
||||||
# body
|
# purposes. But Herb Sutter claims it is. (Proof by authority.)
|
||||||
# jmp L1
|
#[
|
||||||
# L2:
|
while cond:
|
||||||
let L1 = c.genLabel
|
body
|
||||||
withBlock(nil):
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
if cond:
|
||||||
|
body
|
||||||
|
if cond:
|
||||||
|
body
|
||||||
|
if cond:
|
||||||
|
body
|
||||||
|
|
||||||
|
We still need to ensure 'break' resolves properly, so an AST to AST
|
||||||
|
translation is impossible.
|
||||||
|
|
||||||
|
So the code to generate is:
|
||||||
|
|
||||||
|
cond
|
||||||
|
fork L4 # F1
|
||||||
|
body
|
||||||
|
cond
|
||||||
|
fork L5 # F2
|
||||||
|
body
|
||||||
|
cond
|
||||||
|
fork L6 # F3
|
||||||
|
body
|
||||||
|
L6:
|
||||||
|
join F3
|
||||||
|
L5:
|
||||||
|
join F2
|
||||||
|
L4:
|
||||||
|
join F1
|
||||||
|
]#
|
||||||
if isTrue(n.sons[0]):
|
if isTrue(n.sons[0]):
|
||||||
c.gen(n.sons[1])
|
# 'while true' is an idiom in Nim and so we produce
|
||||||
c.jmpBack(n, L1)
|
# better code for it:
|
||||||
|
for i in 0..2:
|
||||||
|
withBlock(nil):
|
||||||
|
c.gen(n.sons[1])
|
||||||
else:
|
else:
|
||||||
c.gen(n.sons[0])
|
let oldForksLen = c.forks.len
|
||||||
let L2 = c.forkI(n)
|
var endings: array[3, TPosition]
|
||||||
c.gen(n.sons[1])
|
for i in 0..2:
|
||||||
c.jmpBack(n, L1)
|
withBlock(nil):
|
||||||
c.patch(L2)
|
c.gen(n.sons[0])
|
||||||
|
endings[i] = c.forkI(n)
|
||||||
|
c.gen(n.sons[1])
|
||||||
|
for i in countdown(endings.high, 0):
|
||||||
|
let endPos = endings[i]
|
||||||
|
c.patch(endPos)
|
||||||
|
c.joinI(c.forks.pop(), n)
|
||||||
|
doAssert(c.forks.len == oldForksLen)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
proc genWhile(c: var Con; n: PNode) =
|
||||||
|
# L1:
|
||||||
|
# cond, tmp
|
||||||
|
# fork tmp, L2
|
||||||
|
# body
|
||||||
|
# jmp L1
|
||||||
|
# L2:
|
||||||
|
let oldForksLen = c.forks.len
|
||||||
|
let L1 = c.genLabel
|
||||||
|
withBlock(nil):
|
||||||
|
if isTrue(n.sons[0]):
|
||||||
|
c.gen(n.sons[1])
|
||||||
|
c.jmpBack(n, L1)
|
||||||
|
else:
|
||||||
|
c.gen(n.sons[0])
|
||||||
|
let L2 = c.forkI(n)
|
||||||
|
c.gen(n.sons[1])
|
||||||
|
c.jmpBack(n, L1)
|
||||||
|
c.patch(L2)
|
||||||
|
setLen(c.forks, oldForksLen)
|
||||||
|
|
||||||
proc genBlock(c: var Con; n: PNode) =
|
proc genBlock(c: var Con; n: PNode) =
|
||||||
withBlock(n.sons[0].sym):
|
withBlock(n.sons[0].sym):
|
||||||
c.gen(n.sons[1])
|
c.gen(n.sons[1])
|
||||||
|
|
||||||
|
proc genJoins(c: var Con; n: PNode) =
|
||||||
|
for i in countdown(c.forks.high, 0): joinI(c, c.forks[i], n)
|
||||||
|
|
||||||
proc genBreak(c: var Con; n: PNode) =
|
proc genBreak(c: var Con; n: PNode) =
|
||||||
|
genJoins(c, n)
|
||||||
let L1 = c.gotoI(n)
|
let L1 = c.gotoI(n)
|
||||||
if n.sons[0].kind == nkSym:
|
if n.sons[0].kind == nkSym:
|
||||||
#echo cast[int](n.sons[0].sym)
|
#echo cast[int](n.sons[0].sym)
|
||||||
|
|
@ -170,28 +393,76 @@ proc genBreak(c: var Con; n: PNode) =
|
||||||
else:
|
else:
|
||||||
c.blocks[c.blocks.high].fixups.add L1
|
c.blocks[c.blocks.high].fixups.add L1
|
||||||
|
|
||||||
|
template forkT(n, body) =
|
||||||
|
let oldLen = c.forks.len
|
||||||
|
let L1 = c.forkI(n)
|
||||||
|
body
|
||||||
|
c.patch(L1)
|
||||||
|
c.joinI(L1, n)
|
||||||
|
setLen(c.forks, oldLen)
|
||||||
|
|
||||||
proc genIf(c: var Con, n: PNode) =
|
proc genIf(c: var Con, n: PNode) =
|
||||||
|
#[
|
||||||
|
|
||||||
|
if cond:
|
||||||
|
A
|
||||||
|
elif condB:
|
||||||
|
B
|
||||||
|
elif condC:
|
||||||
|
C
|
||||||
|
else:
|
||||||
|
D
|
||||||
|
|
||||||
|
cond
|
||||||
|
fork L1
|
||||||
|
A
|
||||||
|
goto Lend
|
||||||
|
L1:
|
||||||
|
condB
|
||||||
|
fork L2
|
||||||
|
B
|
||||||
|
goto Lend2
|
||||||
|
L2:
|
||||||
|
condC
|
||||||
|
fork L3
|
||||||
|
C
|
||||||
|
goto Lend3
|
||||||
|
L3:
|
||||||
|
D
|
||||||
|
goto Lend3 # not eliminated to simplify the join generation
|
||||||
|
Lend3:
|
||||||
|
join F3
|
||||||
|
Lend2:
|
||||||
|
join F2
|
||||||
|
Lend:
|
||||||
|
join F1
|
||||||
|
|
||||||
|
]#
|
||||||
|
let oldLen = c.forks.len
|
||||||
var endings: seq[TPosition] = @[]
|
var endings: seq[TPosition] = @[]
|
||||||
for i in countup(0, len(n) - 1):
|
for i in countup(0, len(n) - 1):
|
||||||
var it = n.sons[i]
|
var it = n.sons[i]
|
||||||
c.gen(it.sons[0])
|
c.gen(it.sons[0])
|
||||||
if it.len == 2:
|
if it.len == 2:
|
||||||
let elsePos = c.forkI(it.sons[1])
|
let elsePos = forkI(c, it[1])
|
||||||
c.gen(it.sons[1])
|
c.gen(it.sons[1])
|
||||||
if i < sonsLen(n)-1:
|
endings.add(c.gotoI(it.sons[1]))
|
||||||
endings.add(c.gotoI(it.sons[1]))
|
|
||||||
c.patch(elsePos)
|
c.patch(elsePos)
|
||||||
for endPos in endings: c.patch(endPos)
|
for i in countdown(endings.high, 0):
|
||||||
|
let endPos = endings[i]
|
||||||
|
c.patch(endPos)
|
||||||
|
c.joinI(c.forks.pop(), n)
|
||||||
|
doAssert(c.forks.len == oldLen)
|
||||||
|
|
||||||
proc genAndOr(c: var Con; n: PNode) =
|
proc genAndOr(c: var Con; n: PNode) =
|
||||||
# asgn dest, a
|
# asgn dest, a
|
||||||
# fork L1
|
# fork L1
|
||||||
# asgn dest, b
|
# asgn dest, b
|
||||||
# L1:
|
# L1:
|
||||||
|
# join F1
|
||||||
c.gen(n.sons[1])
|
c.gen(n.sons[1])
|
||||||
let L1 = c.forkI(n)
|
forkT(n):
|
||||||
c.gen(n.sons[2])
|
c.gen(n.sons[2])
|
||||||
c.patch(L1)
|
|
||||||
|
|
||||||
proc genCase(c: var Con; n: PNode) =
|
proc genCase(c: var Con; n: PNode) =
|
||||||
# if (!expr1) goto L1;
|
# if (!expr1) goto L1;
|
||||||
|
|
@ -204,72 +475,94 @@ proc genCase(c: var Con; n: PNode) =
|
||||||
# L2:
|
# L2:
|
||||||
# elsePart
|
# elsePart
|
||||||
# Lend:
|
# Lend:
|
||||||
when false:
|
let isExhaustive = skipTypes(n.sons[0].typ,
|
||||||
# XXX Exhaustiveness is not yet mapped to the control flow graph as
|
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
|
||||||
# it seems to offer no benefits for the 'last read of' question.
|
|
||||||
let isExhaustive = skipTypes(n.sons[0].typ,
|
|
||||||
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or
|
|
||||||
lastSon(n).kind == nkElse
|
|
||||||
|
|
||||||
var endings: seq[TPosition] = @[]
|
var endings: seq[TPosition] = @[]
|
||||||
|
let oldLen = c.forks.len
|
||||||
c.gen(n.sons[0])
|
c.gen(n.sons[0])
|
||||||
for i in 1 ..< n.len:
|
for i in 1 ..< n.len:
|
||||||
let it = n.sons[i]
|
let it = n.sons[i]
|
||||||
if it.len == 1:
|
if it.len == 1:
|
||||||
c.gen(it.sons[0])
|
c.gen(it.sons[0])
|
||||||
|
elif i == n.len-1 and isExhaustive:
|
||||||
|
# treat the last branch as 'else' if this is an exhaustive case statement.
|
||||||
|
c.gen(it.lastSon)
|
||||||
else:
|
else:
|
||||||
let elsePos = c.forkI(it.lastSon)
|
let elsePos = c.forkI(it.lastSon)
|
||||||
c.gen(it.lastSon)
|
c.gen(it.lastSon)
|
||||||
if i < sonsLen(n)-1:
|
endings.add(c.gotoI(it.lastSon))
|
||||||
endings.add(c.gotoI(it.lastSon))
|
|
||||||
c.patch(elsePos)
|
c.patch(elsePos)
|
||||||
for endPos in endings: c.patch(endPos)
|
for i in countdown(endings.high, 0):
|
||||||
|
let endPos = endings[i]
|
||||||
|
c.patch(endPos)
|
||||||
|
c.joinI(c.forks.pop(), n)
|
||||||
|
doAssert(c.forks.len == oldLen)
|
||||||
|
|
||||||
proc genTry(c: var Con; n: PNode) =
|
proc genTry(c: var Con; n: PNode) =
|
||||||
|
let oldLen = c.forks.len
|
||||||
var endings: seq[TPosition] = @[]
|
var endings: seq[TPosition] = @[]
|
||||||
inc c.inTryStmt
|
inc c.inTryStmt
|
||||||
var newFixups: seq[TPosition]
|
let oldFixups = c.tryStmtFixups.len
|
||||||
swap(newFixups, c.tryStmtFixups)
|
|
||||||
|
|
||||||
let elsePos = c.forkI(n)
|
#let elsePos = c.forkI(n)
|
||||||
c.gen(n.sons[0])
|
c.gen(n.sons[0])
|
||||||
dec c.inTryStmt
|
dec c.inTryStmt
|
||||||
for f in newFixups:
|
for i in oldFixups..c.tryStmtFixups.high:
|
||||||
|
let f = c.tryStmtFixups[i]
|
||||||
c.patch(f)
|
c.patch(f)
|
||||||
swap(newFixups, c.tryStmtFixups)
|
# we also need to produce join instructions
|
||||||
|
# for the 'fork' that might preceed the goto instruction
|
||||||
|
if f.int-1 >= 0 and c.code[f.int-1].kind == fork:
|
||||||
|
c.joinI(TPosition(f.int-1), n)
|
||||||
|
|
||||||
c.patch(elsePos)
|
setLen(c.tryStmtFixups, oldFixups)
|
||||||
|
|
||||||
|
#c.patch(elsePos)
|
||||||
for i in 1 ..< n.len:
|
for i in 1 ..< n.len:
|
||||||
let it = n.sons[i]
|
let it = n.sons[i]
|
||||||
if it.kind != nkFinally:
|
if it.kind != nkFinally:
|
||||||
var blen = len(it)
|
var blen = len(it)
|
||||||
let endExcept = c.forkI(it)
|
let endExcept = c.forkI(it)
|
||||||
c.gen(it.lastSon)
|
c.gen(it.lastSon)
|
||||||
if i < sonsLen(n)-1:
|
endings.add(c.gotoI(it))
|
||||||
endings.add(c.gotoI(it))
|
|
||||||
c.patch(endExcept)
|
c.patch(endExcept)
|
||||||
for endPos in endings: c.patch(endPos)
|
for i in countdown(endings.high, 0):
|
||||||
|
let endPos = endings[i]
|
||||||
|
c.patch(endPos)
|
||||||
|
c.joinI(c.forks.pop(), n)
|
||||||
|
|
||||||
|
# join the 'elsePos' forkI instruction:
|
||||||
|
#c.joinI(c.forks.pop(), n)
|
||||||
|
|
||||||
let fin = lastSon(n)
|
let fin = lastSon(n)
|
||||||
if fin.kind == nkFinally:
|
if fin.kind == nkFinally:
|
||||||
c.gen(fin.sons[0])
|
c.gen(fin.sons[0])
|
||||||
|
doAssert(c.forks.len == oldLen)
|
||||||
|
|
||||||
|
template genNoReturn(c: var Con; n: PNode) =
|
||||||
|
# leave the graph
|
||||||
|
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
|
||||||
|
|
||||||
proc genRaise(c: var Con; n: PNode) =
|
proc genRaise(c: var Con; n: PNode) =
|
||||||
|
genJoins(c, n)
|
||||||
gen(c, n.sons[0])
|
gen(c, n.sons[0])
|
||||||
if c.inTryStmt > 0:
|
if c.inTryStmt > 0:
|
||||||
c.tryStmtFixups.add c.gotoI(n)
|
c.tryStmtFixups.add c.gotoI(n)
|
||||||
else:
|
else:
|
||||||
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
|
genNoReturn(c, n)
|
||||||
|
|
||||||
proc genImplicitReturn(c: var Con) =
|
proc genImplicitReturn(c: var Con) =
|
||||||
if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
|
if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
|
||||||
gen(c, c.owner.ast.sons[resultPos])
|
gen(c, c.owner.ast.sons[resultPos])
|
||||||
|
|
||||||
proc genReturn(c: var Con; n: PNode) =
|
proc genReturn(c: var Con; n: PNode) =
|
||||||
|
genJoins(c, n)
|
||||||
if n.sons[0].kind != nkEmpty:
|
if n.sons[0].kind != nkEmpty:
|
||||||
gen(c, n.sons[0])
|
gen(c, n.sons[0])
|
||||||
else:
|
else:
|
||||||
genImplicitReturn(c)
|
genImplicitReturn(c)
|
||||||
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
|
genNoReturn(c, n)
|
||||||
|
|
||||||
const
|
const
|
||||||
InterestingSyms = {skVar, skResult, skLet, skParam}
|
InterestingSyms = {skVar, skResult, skLet, skParam}
|
||||||
|
|
@ -287,6 +580,14 @@ proc genDef(c: var Con; n: PNode) =
|
||||||
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
||||||
c.code.add Instr(n: n, kind: def, sym: n.sym)
|
c.code.add Instr(n: n, kind: def, sym: n.sym)
|
||||||
|
|
||||||
|
proc canRaise(fn: PNode): bool =
|
||||||
|
const magicsThatCanRaise = {
|
||||||
|
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst}
|
||||||
|
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
|
||||||
|
result = false
|
||||||
|
else:
|
||||||
|
result = true
|
||||||
|
|
||||||
proc genCall(c: var Con; n: PNode) =
|
proc genCall(c: var Con; n: PNode) =
|
||||||
gen(c, n[0])
|
gen(c, n[0])
|
||||||
var t = n[0].typ
|
var t = n[0].typ
|
||||||
|
|
@ -297,8 +598,16 @@ proc genCall(c: var Con; n: PNode) =
|
||||||
if t != nil and i < t.len and t.sons[i].kind == tyVar:
|
if t != nil and i < t.len and t.sons[i].kind == tyVar:
|
||||||
genDef(c, n[i])
|
genDef(c, n[i])
|
||||||
# every call can potentially raise:
|
# every call can potentially raise:
|
||||||
if c.inTryStmt > 0:
|
if c.inTryStmt > 0 and canRaise(n[0]):
|
||||||
c.tryStmtFixups.add c.forkI(n)
|
# we generate the instruction sequence:
|
||||||
|
# fork L1
|
||||||
|
# goto exceptionHandler (except or finally)
|
||||||
|
# L1:
|
||||||
|
# join F1
|
||||||
|
let endGoto = c.forkI(n)
|
||||||
|
c.tryStmtFixups.add c.gotoI(n)
|
||||||
|
c.patch(endGoto)
|
||||||
|
c.joinI(c.forks.pop(), n)
|
||||||
dec c.inCall
|
dec c.inCall
|
||||||
|
|
||||||
proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
||||||
|
|
@ -307,9 +616,6 @@ proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
||||||
of mNew, mNewFinalize:
|
of mNew, mNewFinalize:
|
||||||
genDef(c, n[1])
|
genDef(c, n[1])
|
||||||
for i in 2..<n.len: gen(c, n[i])
|
for i in 2..<n.len: gen(c, n[i])
|
||||||
of mExit:
|
|
||||||
genCall(c, n)
|
|
||||||
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
|
|
||||||
else:
|
else:
|
||||||
genCall(c, n)
|
genCall(c, n)
|
||||||
|
|
||||||
|
|
@ -334,6 +640,8 @@ proc gen(c: var Con; n: PNode) =
|
||||||
genMagic(c, n, s.magic)
|
genMagic(c, n, s.magic)
|
||||||
else:
|
else:
|
||||||
genCall(c, n)
|
genCall(c, n)
|
||||||
|
if sfNoReturn in n.sons[0].sym.flags:
|
||||||
|
genNoReturn(c, n)
|
||||||
else:
|
else:
|
||||||
genCall(c, n)
|
genCall(c, n)
|
||||||
of nkCharLit..nkNilLit: discard
|
of nkCharLit..nkNilLit: discard
|
||||||
|
|
@ -368,114 +676,48 @@ proc gen(c: var Con; n: PNode) =
|
||||||
doAssert false, "dfa construction pass requires the elimination of 'defer'"
|
doAssert false, "dfa construction pass requires the elimination of 'defer'"
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
proc dfa(code: seq[Instr]; conf: ConfigRef) =
|
|
||||||
var u = newSeq[IntSet](code.len) # usages
|
|
||||||
var d = newSeq[IntSet](code.len) # defs
|
|
||||||
var c = newSeq[IntSet](code.len) # consumed
|
|
||||||
var backrefs = initTable[int, int]()
|
|
||||||
for i in 0..<code.len:
|
|
||||||
u[i] = initIntSet()
|
|
||||||
d[i] = initIntSet()
|
|
||||||
c[i] = initIntSet()
|
|
||||||
case code[i].kind
|
|
||||||
of use: u[i].incl(code[i].sym.id)
|
|
||||||
of def: d[i].incl(code[i].sym.id)
|
|
||||||
of fork, goto:
|
|
||||||
let d = i+code[i].dest
|
|
||||||
backrefs.add(d, i)
|
|
||||||
|
|
||||||
var w = @[0]
|
|
||||||
var maxIters = 50
|
|
||||||
var someChange = true
|
|
||||||
var takenGotos = initIntSet()
|
|
||||||
var consuming = -1
|
|
||||||
while w.len > 0 and maxIters > 0: # and someChange:
|
|
||||||
dec maxIters
|
|
||||||
var pc = w.pop() # w[^1]
|
|
||||||
var prevPc = -1
|
|
||||||
# this simulates a single linear control flow execution:
|
|
||||||
while pc < code.len:
|
|
||||||
if prevPc >= 0:
|
|
||||||
someChange = false
|
|
||||||
# merge step and test for changes (we compute the fixpoints here):
|
|
||||||
# 'u' needs to be the union of prevPc, pc
|
|
||||||
# 'd' needs to be the intersection of 'pc'
|
|
||||||
for id in u[prevPc]:
|
|
||||||
if not u[pc].containsOrIncl(id):
|
|
||||||
someChange = true
|
|
||||||
# in (a; b) if ``a`` sets ``v`` so does ``b``. The intersection
|
|
||||||
# is only interesting on merge points:
|
|
||||||
for id in d[prevPc]:
|
|
||||||
if not d[pc].containsOrIncl(id):
|
|
||||||
someChange = true
|
|
||||||
# if this is a merge point, we take the intersection of the 'd' sets:
|
|
||||||
if backrefs.hasKey(pc):
|
|
||||||
var intersect = initIntSet()
|
|
||||||
assign(intersect, d[pc])
|
|
||||||
var first = true
|
|
||||||
for prevPc in backrefs.allValues(pc):
|
|
||||||
for def in d[pc]:
|
|
||||||
if def notin d[prevPc]:
|
|
||||||
excl(intersect, def)
|
|
||||||
someChange = true
|
|
||||||
when defined(debugDfa):
|
|
||||||
echo "Excluding ", pc, " prev ", prevPc
|
|
||||||
assign d[pc], intersect
|
|
||||||
if consuming >= 0:
|
|
||||||
if not c[pc].containsOrIncl(consuming):
|
|
||||||
someChange = true
|
|
||||||
consuming = -1
|
|
||||||
|
|
||||||
# our interpretation ![I!]:
|
|
||||||
prevPc = pc
|
|
||||||
case code[pc].kind
|
|
||||||
of goto:
|
|
||||||
# we must leave endless loops eventually:
|
|
||||||
if not takenGotos.containsOrIncl(pc) or someChange:
|
|
||||||
pc = pc + code[pc].dest
|
|
||||||
else:
|
|
||||||
inc pc
|
|
||||||
of fork:
|
|
||||||
# we follow the next instruction but push the dest onto our "work" stack:
|
|
||||||
#if someChange:
|
|
||||||
w.add pc + code[pc].dest
|
|
||||||
inc pc
|
|
||||||
of use:
|
|
||||||
#if not d[prevPc].missingOrExcl():
|
|
||||||
# someChange = true
|
|
||||||
consuming = code[pc].sym.id
|
|
||||||
inc pc
|
|
||||||
of def:
|
|
||||||
if not d[pc].containsOrIncl(code[pc].sym.id):
|
|
||||||
someChange = true
|
|
||||||
inc pc
|
|
||||||
|
|
||||||
when defined(useDfa) and defined(debugDfa):
|
|
||||||
for i in 0..<code.len:
|
|
||||||
echo "PC ", i, ": defs: ", d[i], "; uses ", u[i], "; consumes ", c[i]
|
|
||||||
|
|
||||||
# now check the condition we're interested in:
|
|
||||||
for i in 0..<code.len:
|
|
||||||
case code[i].kind
|
|
||||||
of use:
|
|
||||||
let s = code[i].sym
|
|
||||||
if s.id notin d[i]:
|
|
||||||
localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s)
|
|
||||||
if s.id in c[i]:
|
|
||||||
localError(conf, code[i].n.info, "usage of an already consumed variable: " & s.name.s)
|
|
||||||
|
|
||||||
else: discard
|
|
||||||
|
|
||||||
proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) =
|
|
||||||
var c = Con(code: @[], blocks: @[])
|
|
||||||
gen(c, body)
|
|
||||||
genImplicitReturn(c)
|
|
||||||
when defined(useDfa) and defined(debugDfa): echoCfg(c.code)
|
|
||||||
dfa(c.code, conf)
|
|
||||||
|
|
||||||
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
|
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
|
||||||
## constructs a control flow graph for ``body``.
|
## constructs a control flow graph for ``body``.
|
||||||
var c = Con(code: @[], blocks: @[], owner: s)
|
var c = Con(code: @[], blocks: @[], owner: s)
|
||||||
gen(c, body)
|
gen(c, body)
|
||||||
genImplicitReturn(c)
|
genImplicitReturn(c)
|
||||||
shallowCopy(result, c.code)
|
shallowCopy(result, c.code)
|
||||||
|
|
||||||
|
proc interpret(code: ControlFlowGraph; pc: int, state: seq[PSym], comesFrom: int; threadId: int): (seq[PSym], int) =
|
||||||
|
var res = state
|
||||||
|
var pc = pc
|
||||||
|
while pc < code.len:
|
||||||
|
#echo threadId, " ", code[pc].kind
|
||||||
|
case code[pc].kind
|
||||||
|
of goto:
|
||||||
|
pc = pc + code[pc].dest
|
||||||
|
of fork:
|
||||||
|
let target = pc + code[pc].dest
|
||||||
|
let (branchA, pcA) = interpret(code, pc+1, res, pc, threadId+1)
|
||||||
|
let (branchB, _) = interpret(code, target, res, pc, threadId+2)
|
||||||
|
# we add vars if they are in both branches:
|
||||||
|
for v in branchB:
|
||||||
|
if v in branchA:
|
||||||
|
if v notin res:
|
||||||
|
res.add v
|
||||||
|
pc = pcA+1
|
||||||
|
of join:
|
||||||
|
let target = pc + code[pc].dest
|
||||||
|
if comesFrom == target: return (res, pc)
|
||||||
|
inc pc
|
||||||
|
of use:
|
||||||
|
let v = code[pc].sym
|
||||||
|
if v notin res and v.kind != skParam:
|
||||||
|
echo "attempt to read uninitialized variable ", v.name.s
|
||||||
|
inc pc
|
||||||
|
of def:
|
||||||
|
let v = code[pc].sym
|
||||||
|
if v notin res:
|
||||||
|
res.add v
|
||||||
|
inc pc
|
||||||
|
return (res, pc)
|
||||||
|
|
||||||
|
proc dataflowAnalysis*(s: PSym; body: PNode) =
|
||||||
|
let c = constructCfg(s, body)
|
||||||
|
#echoCfg c
|
||||||
|
discard interpret(c, 0, @[], -1, 1)
|
||||||
|
|
|
||||||
|
|
@ -144,28 +144,28 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
|
||||||
initStrTable result.types
|
initStrTable result.types
|
||||||
result.onTestSnippet =
|
result.onTestSnippet =
|
||||||
proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) =
|
proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) =
|
||||||
var d = TDocumentor(gen)
|
var d = TDocumentor(gen)
|
||||||
var outp: AbsoluteFile
|
var outp: AbsoluteFile
|
||||||
if filename.len == 0:
|
if filename.len == 0:
|
||||||
inc(d.id)
|
inc(d.id)
|
||||||
let nameOnly = splitFile(d.filename).name
|
let nameOnly = splitFile(d.filename).name
|
||||||
let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly)
|
let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly)
|
||||||
createDir(subdir)
|
createDir(subdir)
|
||||||
outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
|
outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
|
||||||
elif isAbsolute(filename):
|
elif isAbsolute(filename):
|
||||||
outp = AbsoluteFile filename
|
outp = AbsoluteFile filename
|
||||||
else:
|
else:
|
||||||
# Nim's convention: every path is relative to the file it was written in:
|
# Nim's convention: every path is relative to the file it was written in:
|
||||||
outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename)
|
outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename)
|
||||||
# Include the current file if we're parsing a nim file
|
# Include the current file if we're parsing a nim file
|
||||||
let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename]
|
let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename]
|
||||||
writeFile(outp, importStmt & content)
|
writeFile(outp, importStmt & content)
|
||||||
let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3)
|
let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3)
|
||||||
else: cmd
|
else: cmd
|
||||||
let c2 = c % quoteShell(outp)
|
let c2 = c % quoteShell(outp)
|
||||||
rawMessage(conf, hintExecuting, c2)
|
rawMessage(conf, hintExecuting, c2)
|
||||||
if execShellCmd(c2) != status:
|
if execShellCmd(c2) != status:
|
||||||
rawMessage(conf, errGenerated, "executing of external program failed: " & c2)
|
rawMessage(conf, errGenerated, "executing of external program failed: " & c2)
|
||||||
result.emitted = initIntSet()
|
result.emitted = initIntSet()
|
||||||
result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath),
|
result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath),
|
||||||
outExt, RelativeDir"htmldocs", false)
|
outExt, RelativeDir"htmldocs", false)
|
||||||
|
|
@ -300,13 +300,17 @@ proc externalDep(d: PDoc; module: PSym): string =
|
||||||
else:
|
else:
|
||||||
result = extractFilename toFullPath(d.conf, FileIndex module.position)
|
result = extractFilename toFullPath(d.conf, FileIndex module.position)
|
||||||
|
|
||||||
proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) =
|
proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {};
|
||||||
|
procLink: Rope) =
|
||||||
var r: TSrcGen
|
var r: TSrcGen
|
||||||
var literal = ""
|
var literal = ""
|
||||||
initTokRender(r, n, renderFlags)
|
initTokRender(r, n, renderFlags)
|
||||||
var kind = tkEof
|
var kind = tkEof
|
||||||
|
var tokenPos = 0
|
||||||
|
var procTokenPos = 0
|
||||||
while true:
|
while true:
|
||||||
getNextTok(r, kind, literal)
|
getNextTok(r, kind, literal)
|
||||||
|
inc tokenPos
|
||||||
case kind
|
case kind
|
||||||
of tkEof:
|
of tkEof:
|
||||||
break
|
break
|
||||||
|
|
@ -314,6 +318,8 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
|
||||||
dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
|
dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
|
||||||
[rope(esc(d.target, literal))])
|
[rope(esc(d.target, literal))])
|
||||||
of tokKeywordLow..tokKeywordHigh:
|
of tokKeywordLow..tokKeywordHigh:
|
||||||
|
if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}:
|
||||||
|
procTokenPos = tokenPos
|
||||||
dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
|
dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
|
||||||
[rope(literal)])
|
[rope(literal)])
|
||||||
of tkOpr:
|
of tkOpr:
|
||||||
|
|
@ -333,7 +339,11 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
|
||||||
"\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
|
"\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
|
||||||
of tkSymbol:
|
of tkSymbol:
|
||||||
let s = getTokSym(r)
|
let s = getTokSym(r)
|
||||||
if s != nil and s.kind == skType and sfExported in s.flags and
|
# -2 because of the whitespace in between:
|
||||||
|
if procTokenPos == tokenPos-2 and procLink != nil:
|
||||||
|
dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>",
|
||||||
|
"\\spanIdentifier{$1}", [rope(esc(d.target, literal)), procLink])
|
||||||
|
elif s != nil and s.kind == skType and sfExported in s.flags and
|
||||||
s.owner != nil and belongsToPackage(d.conf, s.owner) and
|
s.owner != nil and belongsToPackage(d.conf, s.owner) and
|
||||||
d.target == outHtml:
|
d.target == outHtml:
|
||||||
let external = externalDep(d, s.owner)
|
let external = externalDep(d, s.owner)
|
||||||
|
|
@ -445,7 +455,7 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) =
|
||||||
for b in body:
|
for b in body:
|
||||||
if i > 0: dest.add "\n"
|
if i > 0: dest.add "\n"
|
||||||
inc i
|
inc i
|
||||||
nodeToHighlightedHtml(d, b, dest, {})
|
nodeToHighlightedHtml(d, b, dest, {}, nil)
|
||||||
dest.add(d.config.getOrDefault"doc.listing_end" % id)
|
dest.add(d.config.getOrDefault"doc.listing_end" % id)
|
||||||
else: discard
|
else: discard
|
||||||
for i in 0 ..< n.safeLen:
|
for i in 0 ..< n.safeLen:
|
||||||
|
|
@ -464,7 +474,10 @@ proc isVisible(d: PDoc; n: PNode): bool =
|
||||||
# we cannot generate code for forwarded symbols here as we have no
|
# we cannot generate code for forwarded symbols here as we have no
|
||||||
# exception tracking information here. Instead we copy over the comment
|
# exception tracking information here. Instead we copy over the comment
|
||||||
# from the proc header.
|
# from the proc header.
|
||||||
result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
|
if optDocInternal in d.conf.globalOptions:
|
||||||
|
result = {sfFromGeneric, sfForward}*n.sym.flags == {}
|
||||||
|
else:
|
||||||
|
result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
|
||||||
if result and containsOrIncl(d.emitted, n.sym.id):
|
if result and containsOrIncl(d.emitted, n.sym.id):
|
||||||
result = false
|
result = false
|
||||||
elif n.kind == nkPragmaExpr:
|
elif n.kind == nkPragmaExpr:
|
||||||
|
|
@ -545,16 +558,19 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string =
|
||||||
## If you modify the output of this proc, please update the anchor generation
|
## If you modify the output of this proc, please update the anchor generation
|
||||||
## section of ``doc/docgen.txt``.
|
## section of ``doc/docgen.txt``.
|
||||||
result = baseName
|
result = baseName
|
||||||
case k:
|
case k
|
||||||
of skProc, skFunc: result.add(defaultParamSeparator)
|
of skProc, skFunc: discard
|
||||||
of skMacro: result.add(".m" & defaultParamSeparator)
|
of skMacro: result.add(".m")
|
||||||
of skMethod: result.add(".e" & defaultParamSeparator)
|
of skMethod: result.add(".e")
|
||||||
of skIterator: result.add(".i" & defaultParamSeparator)
|
of skIterator: result.add(".i")
|
||||||
of skTemplate: result.add(".t" & defaultParamSeparator)
|
of skTemplate: result.add(".t")
|
||||||
of skConverter: result.add(".c" & defaultParamSeparator)
|
of skConverter: result.add(".c")
|
||||||
else: discard
|
else: discard
|
||||||
if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams:
|
if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams:
|
||||||
result.add(renderParamTypes(n[paramsPos]))
|
let params = renderParamTypes(n[paramsPos])
|
||||||
|
if params.len > 0:
|
||||||
|
result.add(defaultParamSeparator)
|
||||||
|
result.add(params)
|
||||||
|
|
||||||
proc isCallable(n: PNode): bool =
|
proc isCallable(n: PNode): bool =
|
||||||
## Returns true if `n` contains a callable node.
|
## Returns true if `n` contains a callable node.
|
||||||
|
|
@ -612,9 +628,6 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
|
||||||
break
|
break
|
||||||
plainName.add(literal)
|
plainName.add(literal)
|
||||||
|
|
||||||
nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
|
|
||||||
renderDocComments, renderSyms})
|
|
||||||
|
|
||||||
inc(d.id)
|
inc(d.id)
|
||||||
let
|
let
|
||||||
plainNameRope = rope(xmltree.escape(plainName.strip))
|
plainNameRope = rope(xmltree.escape(plainName.strip))
|
||||||
|
|
@ -627,6 +640,9 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
|
||||||
symbolOrIdRope = symbolOrId.rope
|
symbolOrIdRope = symbolOrId.rope
|
||||||
symbolOrIdEncRope = encodeUrl(symbolOrId).rope
|
symbolOrIdEncRope = encodeUrl(symbolOrId).rope
|
||||||
|
|
||||||
|
nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
|
||||||
|
renderDocComments, renderSyms}, symbolOrIdEncRope)
|
||||||
|
|
||||||
var seeSrcRope: Rope = nil
|
var seeSrcRope: Rope = nil
|
||||||
let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
|
let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
|
||||||
if docItemSeeSrc.len > 0:
|
if docItemSeeSrc.len > 0:
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ proc readOutput(p: Process): (string, int) =
|
||||||
|
|
||||||
proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) =
|
proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) =
|
||||||
let workingDir = parentDir(toFullPath(conf, info))
|
let workingDir = parentDir(toFullPath(conf, info))
|
||||||
if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
|
if cache.len > 0:
|
||||||
let h = secureHash(cmd & "\t" & input & "\t" & cache)
|
let h = secureHash(cmd & "\t" & input & "\t" & cache)
|
||||||
let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string
|
let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string
|
||||||
var f: File
|
var f: File
|
||||||
if open(f, filename):
|
if optForceFullMake notin conf.globalOptions and open(f, filename):
|
||||||
result = (f.readAll, 0)
|
result = (f.readAll, 0)
|
||||||
f.close
|
f.close
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
|
||||||
result = absPath
|
result = absPath
|
||||||
else:
|
else:
|
||||||
let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string
|
let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string
|
||||||
result = if absPath.len < relPath.len: absPath else: relPath
|
result = if relPath.count("..") > 2: absPath else: relPath
|
||||||
|
|
||||||
proc toLinenumber*(info: TLineInfo): int {.inline.} =
|
proc toLinenumber*(info: TLineInfo): int {.inline.} =
|
||||||
result = int info.line
|
result = int info.line
|
||||||
|
|
@ -432,7 +432,7 @@ proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
||||||
proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
||||||
if i.fileIndex.int32 < 0: return ""
|
if i.fileIndex.int32 < 0: return ""
|
||||||
|
|
||||||
if not optPreserveOrigSource(conf) and conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
|
if conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
|
||||||
try:
|
try:
|
||||||
for line in lines(toFullPath(conf, i)):
|
for line in lines(toFullPath(conf, i)):
|
||||||
addSourceLine conf, i.fileIndex, line.string
|
addSourceLine conf, i.fileIndex, line.string
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ type # please make sure we have under 32 options
|
||||||
optExcessiveStackTrace # fully qualified module filenames
|
optExcessiveStackTrace # fully qualified module filenames
|
||||||
optShowAllMismatches # show all overloading resolution candidates
|
optShowAllMismatches # show all overloading resolution candidates
|
||||||
optWholeProject # for 'doc2': output any dependency
|
optWholeProject # for 'doc2': output any dependency
|
||||||
|
optDocInternal # generate documentation for non-exported symbols
|
||||||
optMixedMode # true if some module triggered C++ codegen
|
optMixedMode # true if some module triggered C++ codegen
|
||||||
optListFullPaths # use full paths in toMsgFilename, toFilename
|
optListFullPaths # use full paths in toMsgFilename, toFilename
|
||||||
optNoNimblePath
|
optNoNimblePath
|
||||||
|
|
|
||||||
|
|
@ -450,17 +450,18 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
||||||
flags: TExprFlags = {}): PNode =
|
flags: TExprFlags = {}): PNode =
|
||||||
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
|
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
|
||||||
|
|
||||||
markUsed(c.config, n.info, sym, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.info, sym)
|
markUsed(c.config, info, sym, c.graph.usageSym)
|
||||||
|
onUse(info, sym)
|
||||||
if sym == c.p.owner:
|
if sym == c.p.owner:
|
||||||
globalError(c.config, n.info, "recursive dependency: '$1'" % sym.name.s)
|
globalError(c.config, info, "recursive dependency: '$1'" % sym.name.s)
|
||||||
|
|
||||||
let genericParams = if sfImmediate in sym.flags: 0
|
let genericParams = if sfImmediate in sym.flags: 0
|
||||||
else: sym.ast[genericParamsPos].len
|
else: sym.ast[genericParamsPos].len
|
||||||
let suppliedParams = max(n.safeLen - 1, 0)
|
let suppliedParams = max(n.safeLen - 1, 0)
|
||||||
|
|
||||||
if suppliedParams < genericParams:
|
if suppliedParams < genericParams:
|
||||||
globalError(c.config, n.info, errMissingGenericParamsForTemplate % n.renderTree)
|
globalError(c.config, info, errMissingGenericParamsForTemplate % n.renderTree)
|
||||||
|
|
||||||
#if c.evalContext == nil:
|
#if c.evalContext == nil:
|
||||||
# c.evalContext = c.createEvalContext(emStatic)
|
# c.evalContext = c.createEvalContext(emStatic)
|
||||||
|
|
|
||||||
|
|
@ -462,16 +462,23 @@ proc updateDefaultParams(call: PNode) =
|
||||||
if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam
|
if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam
|
||||||
call[i] = def
|
call[i] = def
|
||||||
|
|
||||||
|
proc getCallLineInfo(n: PNode): TLineInfo =
|
||||||
|
case n.kind
|
||||||
|
of nkAccQuoted, nkBracketExpr, nkCall, nkCommand: getCallLineInfo(n.sons[0])
|
||||||
|
of nkDotExpr: getCallLineInfo(n.sons[1])
|
||||||
|
else: n.info
|
||||||
|
|
||||||
proc semResolvedCall(c: PContext, x: TCandidate,
|
proc semResolvedCall(c: PContext, x: TCandidate,
|
||||||
n: PNode, flags: TExprFlags): PNode =
|
n: PNode, flags: TExprFlags): PNode =
|
||||||
assert x.state == csMatch
|
assert x.state == csMatch
|
||||||
var finalCallee = x.calleeSym
|
var finalCallee = x.calleeSym
|
||||||
markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.sons[0].info, finalCallee)
|
markUsed(c.config, info, finalCallee, c.graph.usageSym)
|
||||||
|
onUse(info, finalCallee)
|
||||||
assert finalCallee.ast != nil
|
assert finalCallee.ast != nil
|
||||||
if x.hasFauxMatch:
|
if x.hasFauxMatch:
|
||||||
result = x.call
|
result = x.call
|
||||||
result.sons[0] = newSymNode(finalCallee, result.sons[0].info)
|
result.sons[0] = newSymNode(finalCallee, getCallLineInfo(result.sons[0]))
|
||||||
if containsGenericType(result.typ) or x.fauxMatch == tyUnknown:
|
if containsGenericType(result.typ) or x.fauxMatch == tyUnknown:
|
||||||
result.typ = newTypeS(x.fauxMatch, c)
|
result.typ = newTypeS(x.fauxMatch, c)
|
||||||
return
|
return
|
||||||
|
|
@ -496,7 +503,7 @@ proc semResolvedCall(c: PContext, x: TCandidate,
|
||||||
|
|
||||||
result = x.call
|
result = x.call
|
||||||
instGenericConvertersSons(c, result, x)
|
instGenericConvertersSons(c, result, x)
|
||||||
result[0] = newSymNode(finalCallee, result[0].info)
|
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
|
||||||
result.typ = finalCallee.typ.sons[0]
|
result.typ = finalCallee.typ.sons[0]
|
||||||
updateDefaultParams(result)
|
updateDefaultParams(result)
|
||||||
|
|
||||||
|
|
@ -551,7 +558,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||||
notFoundError(c, n, errors)
|
notFoundError(c, n, errors)
|
||||||
|
|
||||||
proc explicitGenericInstError(c: PContext; n: PNode): PNode =
|
proc explicitGenericInstError(c: PContext; n: PNode): PNode =
|
||||||
localError(c.config, n.info, errCannotInstantiateX % renderTree(n))
|
localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n))
|
||||||
result = n
|
result = n
|
||||||
|
|
||||||
proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
|
proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
|
|
@ -574,9 +581,10 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
if tm in {isNone, isConvertible}: return nil
|
if tm in {isNone, isConvertible}: return nil
|
||||||
var newInst = generateInstance(c, s, m.bindings, n.info)
|
var newInst = generateInstance(c, s, m.bindings, n.info)
|
||||||
newInst.typ.flags.excl tfUnresolved
|
newInst.typ.flags.excl tfUnresolved
|
||||||
markUsed(c.config, n.info, s, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.info, s)
|
markUsed(c.config, info, s, c.graph.usageSym)
|
||||||
result = newSymNode(newInst, n.info)
|
onUse(info, s)
|
||||||
|
result = newSymNode(newInst, info)
|
||||||
|
|
||||||
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
assert n.kind == nkBracketExpr
|
assert n.kind == nkBracketExpr
|
||||||
|
|
@ -593,7 +601,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
# number of generic type parameters:
|
# number of generic type parameters:
|
||||||
if safeLen(s.ast.sons[genericParamsPos]) != n.len-1:
|
if safeLen(s.ast.sons[genericParamsPos]) != n.len-1:
|
||||||
let expected = safeLen(s.ast.sons[genericParamsPos])
|
let expected = safeLen(s.ast.sons[genericParamsPos])
|
||||||
localError(c.config, n.info, errGenerated, "cannot instantiate: '" & renderTree(n) &
|
localError(c.config, getCallLineInfo(n), errGenerated, "cannot instantiate: '" & renderTree(n) &
|
||||||
"'; got " & $(n.len-1) & " type(s) but expected " & $expected)
|
"'; got " & $(n.len-1) & " type(s) but expected " & $expected)
|
||||||
return n
|
return n
|
||||||
result = explicitGenericSym(c, n, s)
|
result = explicitGenericSym(c, n, s)
|
||||||
|
|
@ -602,7 +610,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
# choose the generic proc with the proper number of type parameters.
|
# choose the generic proc with the proper number of type parameters.
|
||||||
# XXX I think this could be improved by reusing sigmatch.paramTypesMatch.
|
# XXX I think this could be improved by reusing sigmatch.paramTypesMatch.
|
||||||
# It's good enough for now.
|
# It's good enough for now.
|
||||||
result = newNodeI(a.kind, n.info)
|
result = newNodeI(a.kind, getCallLineInfo(n))
|
||||||
for i in countup(0, len(a)-1):
|
for i in countup(0, len(a)-1):
|
||||||
var candidate = a.sons[i].sym
|
var candidate = a.sons[i].sym
|
||||||
if candidate.kind in {skProc, skMethod, skConverter,
|
if candidate.kind in {skProc, skMethod, skConverter,
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,18 @@ const
|
||||||
|
|
||||||
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
||||||
flags: TExprFlags = {}): PNode =
|
flags: TExprFlags = {}): PNode =
|
||||||
markUsed(c.config, n.info, s, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.info, s)
|
markUsed(c.config, info, s, c.graph.usageSym)
|
||||||
|
onUse(info, s)
|
||||||
|
# Note: This is n.info on purpose. It prevents template from creating an info
|
||||||
|
# context when called from an another template
|
||||||
pushInfoContext(c.config, n.info, s.detailedInfo)
|
pushInfoContext(c.config, n.info, s.detailedInfo)
|
||||||
result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags)
|
result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags)
|
||||||
if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags)
|
if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags)
|
||||||
popInfoContext(c.config)
|
popInfoContext(c.config)
|
||||||
|
|
||||||
# XXX: A more elaborate line info rewrite might be needed
|
# XXX: A more elaborate line info rewrite might be needed
|
||||||
result.info = n.info
|
result.info = info
|
||||||
|
|
||||||
proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
|
proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
|
||||||
|
|
||||||
|
|
@ -1084,8 +1087,9 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
|
||||||
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
|
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
|
||||||
(n.kind notin nkCallKinds and s.requiredParams > 0) or
|
(n.kind notin nkCallKinds and s.requiredParams > 0) or
|
||||||
sfCustomPragma in sym.flags:
|
sfCustomPragma in sym.flags:
|
||||||
markUsed(c.config, n.info, s, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.info, s)
|
markUsed(c.config, info, s, c.graph.usageSym)
|
||||||
|
onUse(info, s)
|
||||||
result = symChoice(c, n, s, scClosed)
|
result = symChoice(c, n, s, scClosed)
|
||||||
else:
|
else:
|
||||||
result = semTemplateExpr(c, n, s, flags)
|
result = semTemplateExpr(c, n, s, flags)
|
||||||
|
|
@ -1171,9 +1175,10 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
|
||||||
onUse(n.info, s)
|
onUse(n.info, s)
|
||||||
result = newSymNode(s, n.info)
|
result = newSymNode(s, n.info)
|
||||||
else:
|
else:
|
||||||
markUsed(c.config, n.info, s, c.graph.usageSym)
|
let info = getCallLineInfo(n)
|
||||||
onUse(n.info, s)
|
markUsed(c.config, info, s, c.graph.usageSym)
|
||||||
result = newSymNode(s, n.info)
|
onUse(info, s)
|
||||||
|
result = newSymNode(s, info)
|
||||||
|
|
||||||
proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
## returns nil if it's not a built-in field access
|
## returns nil if it's not a built-in field access
|
||||||
|
|
@ -1286,7 +1291,11 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
if ty.sons[0] == nil: break
|
if ty.sons[0] == nil: break
|
||||||
ty = skipTypes(ty.sons[0], skipPtrs)
|
ty = skipTypes(ty.sons[0], skipPtrs)
|
||||||
if f != nil:
|
if f != nil:
|
||||||
if fieldVisible(c, f):
|
let visibilityCheckNeeded =
|
||||||
|
if n[1].kind == nkSym and n[1].sym == f:
|
||||||
|
false # field lookup was done already, likely by hygienic template or bindSym
|
||||||
|
else: true
|
||||||
|
if not visibilityCheckNeeded or fieldVisible(c, f):
|
||||||
# is the access to a public field or in the same module or in a friend?
|
# is the access to a public field or in the same module or in a friend?
|
||||||
markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
|
markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
|
||||||
onUse(n.sons[1].info, f)
|
onUse(n.sons[1].info, f)
|
||||||
|
|
|
||||||
|
|
@ -410,4 +410,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
|
||||||
result = n
|
result = n
|
||||||
else:
|
else:
|
||||||
result = plugin(c, n)
|
result = plugin(c, n)
|
||||||
|
of mNewFinalize:
|
||||||
|
# Make sure the finalizer procedure refers to a procedure
|
||||||
|
if n[^1].kind == nkSym and n[^1].sym.kind notin {skProc, skFunc}:
|
||||||
|
localError(c.config, n.info, "finalizer must be a direct reference to a procedure")
|
||||||
|
result = n
|
||||||
else: result = n
|
else: result = n
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,8 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
|
|
||||||
var branches = 1
|
var branches = 1
|
||||||
var hasFinally = false
|
var hasFinally = false
|
||||||
|
|
||||||
|
# Collect the exceptions caught by the except branches
|
||||||
for i in 1 ..< n.len:
|
for i in 1 ..< n.len:
|
||||||
let b = n.sons[i]
|
let b = n.sons[i]
|
||||||
let blen = sonsLen(b)
|
let blen = sonsLen(b)
|
||||||
|
|
@ -368,12 +370,18 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
else:
|
else:
|
||||||
assert(b.sons[j].kind == nkType)
|
assert(b.sons[j].kind == nkType)
|
||||||
catches(tracked, b.sons[j].typ)
|
catches(tracked, b.sons[j].typ)
|
||||||
|
else:
|
||||||
|
assert b.kind == nkFinally
|
||||||
|
# Add any other exception raised in the except bodies
|
||||||
|
for i in 1 ..< n.len:
|
||||||
|
let b = n.sons[i]
|
||||||
|
let blen = sonsLen(b)
|
||||||
|
if b.kind == nkExceptBranch:
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
track(tracked, b.sons[blen-1])
|
track(tracked, b.sons[blen-1])
|
||||||
for i in oldState..<tracked.init.len:
|
for i in oldState..<tracked.init.len:
|
||||||
addToIntersection(inter, tracked.init[i])
|
addToIntersection(inter, tracked.init[i])
|
||||||
else:
|
else:
|
||||||
assert b.kind == nkFinally
|
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
track(tracked, b.sons[blen-1])
|
track(tracked, b.sons[blen-1])
|
||||||
hasFinally = true
|
hasFinally = true
|
||||||
|
|
@ -1013,7 +1021,7 @@ proc trackProc*(g: ModuleGraph; s: PSym, body: PNode) =
|
||||||
"declared lock level is $1, but real lock level is $2" %
|
"declared lock level is $1, but real lock level is $2" %
|
||||||
[$s.typ.lockLevel, $t.maxLockLevel])
|
[$s.typ.lockLevel, $t.maxLockLevel])
|
||||||
when defined(useDfa):
|
when defined(useDfa):
|
||||||
if s.kind == skFunc:
|
if s.name.s == "testp":
|
||||||
dataflowAnalysis(s, body)
|
dataflowAnalysis(s, body)
|
||||||
when false: trackWrites(s, body)
|
when false: trackWrites(s, body)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
||||||
for i in 1..last:
|
for i in 1..last:
|
||||||
var it = n.sons[i]
|
var it = n.sons[i]
|
||||||
let j = it.len-1
|
let j = it.len-1
|
||||||
it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
|
if not endsInNoReturn(it.sons[j]):
|
||||||
|
it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
|
||||||
result.typ = typ
|
result.typ = typ
|
||||||
|
|
||||||
proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
|
proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
|
||||||
|
|
@ -432,7 +433,6 @@ proc setVarType(c: PContext; v: PSym, typ: PType) =
|
||||||
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
var b: PNode
|
var b: PNode
|
||||||
result = copyNode(n)
|
result = copyNode(n)
|
||||||
var hasCompileTime = false
|
|
||||||
for i in countup(0, sonsLen(n)-1):
|
for i in countup(0, sonsLen(n)-1):
|
||||||
var a = n.sons[i]
|
var a = n.sons[i]
|
||||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
||||||
|
|
@ -440,11 +440,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
|
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
|
||||||
checkMinSonsLen(a, 3, c.config)
|
checkMinSonsLen(a, 3, c.config)
|
||||||
var length = sonsLen(a)
|
var length = sonsLen(a)
|
||||||
var typ: PType
|
|
||||||
|
var typ: PType = nil
|
||||||
if a.sons[length-2].kind != nkEmpty:
|
if a.sons[length-2].kind != nkEmpty:
|
||||||
typ = semTypeNode(c, a.sons[length-2], nil)
|
typ = semTypeNode(c, a.sons[length-2], nil)
|
||||||
else:
|
|
||||||
typ = nil
|
|
||||||
var def: PNode = c.graph.emptyNode
|
var def: PNode = c.graph.emptyNode
|
||||||
if a.sons[length-1].kind != nkEmpty:
|
if a.sons[length-1].kind != nkEmpty:
|
||||||
def = semExprWithType(c, a.sons[length-1], {efAllowDestructor})
|
def = semExprWithType(c, a.sons[length-1], {efAllowDestructor})
|
||||||
|
|
@ -556,13 +556,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
else: v.typ = tup
|
else: v.typ = tup
|
||||||
b.sons[j] = newSymNode(v)
|
b.sons[j] = newSymNode(v)
|
||||||
checkNilable(c, v)
|
checkNilable(c, v)
|
||||||
if sfCompileTime in v.flags: hasCompileTime = true
|
if sfCompileTime in v.flags:
|
||||||
|
var x = newNodeI(result.kind, v.info)
|
||||||
|
addSon(x, result[i])
|
||||||
|
vm.setupCompileTimeVar(c.module, c.graph, x)
|
||||||
if v.flags * {sfGlobal, sfThread} == {sfGlobal}:
|
if v.flags * {sfGlobal, sfThread} == {sfGlobal}:
|
||||||
message(c.config, v.info, hintGlobalVar)
|
message(c.config, v.info, hintGlobalVar)
|
||||||
if hasCompileTime:
|
|
||||||
vm.setupCompileTimeVar(c.module, c.graph, result)
|
|
||||||
# handled by the VM codegen:
|
|
||||||
#c.graph.recordStmt(c.graph, c.module, result)
|
|
||||||
|
|
||||||
proc semConst(c: PContext, n: PNode): PNode =
|
proc semConst(c: PContext, n: PNode): PNode =
|
||||||
result = copyNode(n)
|
result = copyNode(n)
|
||||||
|
|
@ -583,9 +582,19 @@ proc semConst(c: PContext, n: PNode): PNode =
|
||||||
if def == nil:
|
if def == nil:
|
||||||
localError(c.config, a.sons[length-1].info, errConstExprExpected)
|
localError(c.config, a.sons[length-1].info, errConstExprExpected)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
|
||||||
|
# prevent the all too common 'const x = int' bug:
|
||||||
|
localError(c.config, def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?")
|
||||||
|
def.typ = errorType(c)
|
||||||
|
|
||||||
# check type compatibility between def.typ and typ:
|
# check type compatibility between def.typ and typ:
|
||||||
if typ != nil:
|
if typ != nil:
|
||||||
def = fitRemoveHiddenConv(c, typ, def)
|
if typ.isMetaType:
|
||||||
|
def = inferWithMetatype(c, typ, def)
|
||||||
|
typ = def.typ
|
||||||
|
else:
|
||||||
|
def = fitRemoveHiddenConv(c, typ, def)
|
||||||
else:
|
else:
|
||||||
typ = def.typ
|
typ = def.typ
|
||||||
if typ == nil:
|
if typ == nil:
|
||||||
|
|
|
||||||
|
|
@ -58,25 +58,26 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode =
|
||||||
inc(i)
|
inc(i)
|
||||||
if i > 1: break
|
if i > 1: break
|
||||||
a = nextOverloadIter(o, c, n)
|
a = nextOverloadIter(o, c, n)
|
||||||
|
let info = getCallLineInfo(n)
|
||||||
if i <= 1 and r != scForceOpen:
|
if i <= 1 and r != scForceOpen:
|
||||||
# XXX this makes more sense but breaks bootstrapping for now:
|
# XXX this makes more sense but breaks bootstrapping for now:
|
||||||
# (s.kind notin routineKinds or s.magic != mNone):
|
# (s.kind notin routineKinds or s.magic != mNone):
|
||||||
# for instance 'nextTry' is both in tables.nim and astalgo.nim ...
|
# for instance 'nextTry' is both in tables.nim and astalgo.nim ...
|
||||||
result = newSymNode(s, n.info)
|
result = newSymNode(s, info)
|
||||||
markUsed(c.config, n.info, s, c.graph.usageSym)
|
markUsed(c.config, info, s, c.graph.usageSym)
|
||||||
onUse(n.info, s)
|
onUse(info, s)
|
||||||
else:
|
else:
|
||||||
# semantic checking requires a type; ``fitNode`` deals with it
|
# semantic checking requires a type; ``fitNode`` deals with it
|
||||||
# appropriately
|
# appropriately
|
||||||
let kind = if r == scClosed or n.kind == nkDotExpr: nkClosedSymChoice
|
let kind = if r == scClosed or n.kind == nkDotExpr: nkClosedSymChoice
|
||||||
else: nkOpenSymChoice
|
else: nkOpenSymChoice
|
||||||
result = newNodeIT(kind, n.info, newTypeS(tyNone, c))
|
result = newNodeIT(kind, info, newTypeS(tyNone, c))
|
||||||
a = initOverloadIter(o, c, n)
|
a = initOverloadIter(o, c, n)
|
||||||
while a != nil:
|
while a != nil:
|
||||||
if a.kind != skModule:
|
if a.kind != skModule:
|
||||||
incl(a.flags, sfUsed)
|
incl(a.flags, sfUsed)
|
||||||
addSon(result, newSymNode(a, n.info))
|
addSon(result, newSymNode(a, info))
|
||||||
onUse(n.info, a)
|
onUse(info, a)
|
||||||
a = nextOverloadIter(o, c, n)
|
a = nextOverloadIter(o, c, n)
|
||||||
|
|
||||||
proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode =
|
proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode =
|
||||||
|
|
|
||||||
|
|
@ -1015,8 +1015,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||||
result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false))
|
result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false))
|
||||||
|
|
||||||
of tyGenericParam:
|
of tyGenericParam:
|
||||||
markUsed(c.config, info, paramType.sym, c.graph.usageSym)
|
markUsed(c.config, paramType.sym.info, paramType.sym, c.graph.usageSym)
|
||||||
onUse(info, paramType.sym)
|
onUse(paramType.sym.info, paramType.sym)
|
||||||
if tfWildcard in paramType.flags:
|
if tfWildcard in paramType.flags:
|
||||||
paramType.flags.excl tfWildcard
|
paramType.flags.excl tfWildcard
|
||||||
paramType.sym.kind = skType
|
paramType.sym.kind = skType
|
||||||
|
|
@ -1474,7 +1474,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||||
|
|
||||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty: discard
|
of nkEmpty: result = n.typ
|
||||||
of nkTypeOfExpr:
|
of nkTypeOfExpr:
|
||||||
# for ``type(countup(1,3))``, see ``tests/ttoseq``.
|
# for ``type(countup(1,3))``, see ``tests/ttoseq``.
|
||||||
checkSonsLen(n, 1, c.config)
|
checkSonsLen(n, 1, c.config)
|
||||||
|
|
|
||||||
|
|
@ -490,7 +490,12 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
|
||||||
result.kind = tyUserTypeClassInst
|
result.kind = tyUserTypeClassInst
|
||||||
|
|
||||||
of tyGenericBody:
|
of tyGenericBody:
|
||||||
localError(cl.c.config, cl.info, "cannot instantiate: '" & typeToString(t) & "'")
|
localError(
|
||||||
|
cl.c.config,
|
||||||
|
cl.info,
|
||||||
|
"cannot instantiate: '" &
|
||||||
|
typeToString(t, preferDesc) &
|
||||||
|
"'; Maybe generic arguments are missing?")
|
||||||
result = errorType(cl.c)
|
result = errorType(cl.c)
|
||||||
#result = replaceTypeVarsT(cl, lastSon(t))
|
#result = replaceTypeVarsT(cl, lastSon(t))
|
||||||
|
|
||||||
|
|
@ -555,6 +560,14 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
|
||||||
|
|
||||||
for i in countup(0, sonsLen(result) - 1):
|
for i in countup(0, sonsLen(result) - 1):
|
||||||
if result.sons[i] != nil:
|
if result.sons[i] != nil:
|
||||||
|
if result.sons[i].kind == tyGenericBody:
|
||||||
|
localError(
|
||||||
|
cl.c.config,
|
||||||
|
t.sym.info,
|
||||||
|
"cannot instantiate '" &
|
||||||
|
typeToString(result.sons[i], preferDesc) &
|
||||||
|
"' inside of type definition: '" &
|
||||||
|
t.owner.name.s & "'; Maybe generic arguments are missing?")
|
||||||
var r = replaceTypeVarsT(cl, result.sons[i])
|
var r = replaceTypeVarsT(cl, result.sons[i])
|
||||||
if result.kind == tyObject:
|
if result.kind == tyObject:
|
||||||
# carefully coded to not skip the precious tyGenericInst:
|
# carefully coded to not skip the precious tyGenericInst:
|
||||||
|
|
|
||||||
|
|
@ -1316,7 +1316,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType,
|
||||||
if typeRel(c, f.sons[i], a.sons[i]) == isNone: return isNone
|
if typeRel(c, f.sons[i], a.sons[i]) == isNone: return isNone
|
||||||
result = typeRel(c, f.lastSon, a.lastSon, flags + {trNoCovariance})
|
result = typeRel(c, f.lastSon, a.lastSon, flags + {trNoCovariance})
|
||||||
subtypeCheck()
|
subtypeCheck()
|
||||||
if result <= isConvertible: result = isNone
|
if result <= isIntConv: result = isNone
|
||||||
elif tfNotNil in f.flags and tfNotNil notin a.flags:
|
elif tfNotNil in f.flags and tfNotNil notin a.flags:
|
||||||
result = isNilConversion
|
result = isNilConversion
|
||||||
elif a.kind == tyNil: result = f.allowsNil
|
elif a.kind == tyNil: result = f.allowsNil
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
# included from sigmatch.nim
|
# included from sigmatch.nim
|
||||||
|
|
||||||
import algorithm, prefixmatches, lineinfos, pathutils
|
import algorithm, prefixmatches, lineinfos, pathutils
|
||||||
from wordrecg import wDeprecated, wError
|
from wordrecg import wDeprecated, wError, wAddr, wYield, specialWords
|
||||||
|
|
||||||
when defined(nimsuggest):
|
when defined(nimsuggest):
|
||||||
import passes, tables # importer
|
import passes, tables # importer
|
||||||
|
|
@ -109,7 +109,11 @@ proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info
|
||||||
result.qualifiedPath.add(ow2.origModuleName)
|
result.qualifiedPath.add(ow2.origModuleName)
|
||||||
if ow != nil:
|
if ow != nil:
|
||||||
result.qualifiedPath.add(ow.origModuleName)
|
result.qualifiedPath.add(ow.origModuleName)
|
||||||
result.qualifiedPath.add(s.name.s)
|
if s.name.s[0] in OpChars + {'[', '{', '('} or
|
||||||
|
s.name.id in ord(wAddr)..ord(wYield):
|
||||||
|
result.qualifiedPath.add('`' & s.name.s & '`')
|
||||||
|
else:
|
||||||
|
result.qualifiedPath.add(s.name.s)
|
||||||
|
|
||||||
if s.typ != nil:
|
if s.typ != nil:
|
||||||
result.forth = typeToString(s.typ)
|
result.forth = typeToString(s.typ)
|
||||||
|
|
|
||||||
|
|
@ -456,12 +456,18 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||||
result = $t.n.intVal
|
result = $t.n.intVal
|
||||||
else:
|
else:
|
||||||
result = "int literal(" & $t.n.intVal & ")"
|
result = "int literal(" & $t.n.intVal & ")"
|
||||||
of tyGenericBody, tyGenericInst, tyGenericInvocation:
|
of tyGenericInst, tyGenericInvocation:
|
||||||
result = typeToString(t.sons[0]) & '['
|
result = typeToString(t.sons[0]) & '['
|
||||||
for i in countup(1, sonsLen(t)-1-ord(t.kind != tyGenericInvocation)):
|
for i in countup(1, sonsLen(t)-1-ord(t.kind != tyGenericInvocation)):
|
||||||
if i > 1: add(result, ", ")
|
if i > 1: add(result, ", ")
|
||||||
add(result, typeToString(t.sons[i], preferGenericArg))
|
add(result, typeToString(t.sons[i], preferGenericArg))
|
||||||
add(result, ']')
|
add(result, ']')
|
||||||
|
of tyGenericBody:
|
||||||
|
result = typeToString(t.lastSon) & '['
|
||||||
|
for i in countup(0, sonsLen(t)-2):
|
||||||
|
if i > 0: add(result, ", ")
|
||||||
|
add(result, typeToString(t.sons[i], preferTypeName))
|
||||||
|
add(result, ']')
|
||||||
of tyTypeDesc:
|
of tyTypeDesc:
|
||||||
if t.sons[0].kind == tyNone: result = "typedesc"
|
if t.sons[0].kind == tyNone: result = "typedesc"
|
||||||
else: result = "type " & typeToString(t.sons[0])
|
else: result = "type " & typeToString(t.sons[0])
|
||||||
|
|
@ -612,7 +618,6 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||||
result = typeToStr[t.kind]
|
result = typeToStr[t.kind]
|
||||||
result.addTypeFlags(t)
|
result.addTypeFlags(t)
|
||||||
|
|
||||||
|
|
||||||
proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
|
proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
|
||||||
case t.kind
|
case t.kind
|
||||||
of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy:
|
of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy:
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,11 @@ proc opConv(c: PCtx; dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType):
|
||||||
dest.floatVal = toBiggestFloat(src.intVal)
|
dest.floatVal = toBiggestFloat(src.intVal)
|
||||||
else:
|
else:
|
||||||
dest.floatVal = src.floatVal
|
dest.floatVal = src.floatVal
|
||||||
|
of tyObject:
|
||||||
|
if srctyp.skipTypes(abstractRange).kind != tyObject:
|
||||||
|
internalError(c.config, "invalid object-to-object conversion")
|
||||||
|
# A object-to-object conversion is essentially a no-op
|
||||||
|
moveConst(dest, src)
|
||||||
else:
|
else:
|
||||||
asgnComplex(dest, src)
|
asgnComplex(dest, src)
|
||||||
|
|
||||||
|
|
@ -929,7 +934,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||||
stackTrace(c, tos, pc, errNilAccess)
|
stackTrace(c, tos, pc, errNilAccess)
|
||||||
of opcGetImpl:
|
of opcGetImpl:
|
||||||
decodeB(rkNode)
|
decodeB(rkNode)
|
||||||
let a = regs[rb].node
|
var a = regs[rb].node
|
||||||
|
if a.kind == nkVarTy: a = a[0]
|
||||||
if a.kind == nkSym:
|
if a.kind == nkSym:
|
||||||
regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit)
|
regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit)
|
||||||
else: copyTree(a.sym.ast)
|
else: copyTree(a.sym.ast)
|
||||||
|
|
|
||||||
|
|
@ -326,8 +326,14 @@ proc genWhile(c: PCtx; n: PNode) =
|
||||||
c.patch(L2)
|
c.patch(L2)
|
||||||
|
|
||||||
proc genBlock(c: PCtx; n: PNode; dest: var TDest) =
|
proc genBlock(c: PCtx; n: PNode; dest: var TDest) =
|
||||||
|
let oldRegisterCount = c.prc.maxSlots
|
||||||
withBlock(n.sons[0].sym):
|
withBlock(n.sons[0].sym):
|
||||||
c.gen(n.sons[1], dest)
|
c.gen(n.sons[1], dest)
|
||||||
|
|
||||||
|
for i in oldRegisterCount ..< c.prc.maxSlots:
|
||||||
|
if c.prc.slots[i].kind in {slotFixedVar, slotFixedLet}:
|
||||||
|
c.prc.slots[i] = (inUse: false, kind: slotEmpty)
|
||||||
|
|
||||||
c.clearDest(n, dest)
|
c.clearDest(n, dest)
|
||||||
|
|
||||||
proc genBreak(c: PCtx; n: PNode) =
|
proc genBreak(c: PCtx; n: PNode) =
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ Advanced options:
|
||||||
--project document the whole project (doc2)
|
--project document the whole project (doc2)
|
||||||
--docSeeSrcUrl:url activate 'see source' for doc and doc2 commands
|
--docSeeSrcUrl:url activate 'see source' for doc and doc2 commands
|
||||||
(see doc.item.seesrc in config/nimdoc.cfg)
|
(see doc.item.seesrc in config/nimdoc.cfg)
|
||||||
|
--docInternal also generate documentation for non-exported symbols
|
||||||
--lineDir:on|off generation of #line directive on|off
|
--lineDir:on|off generation of #line directive on|off
|
||||||
--embedsrc:on|off embeds the original source code as comments
|
--embedsrc:on|off embeds the original source code as comments
|
||||||
in the generated output
|
in the generated output
|
||||||
|
|
@ -97,8 +98,9 @@ Advanced options:
|
||||||
symbol matching is fuzzy so
|
symbol matching is fuzzy so
|
||||||
that --dynlibOverride:lua matches
|
that --dynlibOverride:lua matches
|
||||||
dynlib: "liblua.so.3"
|
dynlib: "liblua.so.3"
|
||||||
--dynlibOverrideAll:on|off makes the dynlib pragma have no effect
|
--dynlibOverrideAll
|
||||||
--listCmd:on|off list the commands used to execute external programs
|
disables the effects of the dynlib pragma
|
||||||
|
--listCmd list the commands used to execute external programs
|
||||||
--parallelBuild:0|1|... perform a parallel build
|
--parallelBuild:0|1|... perform a parallel build
|
||||||
value = number of processors (0 for auto-detect)
|
value = number of processors (0 for auto-detect)
|
||||||
--incremental:on|off only recompile the changed modules (experimental!)
|
--incremental:on|off only recompile the changed modules (experimental!)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ Options:
|
||||||
--debugger:native|endb use native debugger (gdb) | ENDB (experimental)
|
--debugger:native|endb use native debugger (gdb) | ENDB (experimental)
|
||||||
--app:console|gui|lib|staticlib
|
--app:console|gui|lib|staticlib
|
||||||
generate a console app|GUI app|DLL|static library
|
generate a console app|GUI app|DLL|static library
|
||||||
-r, --run:on|off run the compiled program with given arguments
|
-r, --run run the compiled program with given arguments
|
||||||
--fullhelp show all command line switches
|
--fullhelp show all command line switches
|
||||||
-h, --help show this help
|
-h, --help show this help
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
============
|
||||||
Contributing
|
Contributing
|
||||||
============
|
============
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
|
||||||
|
|
||||||
Contributing happens via "Pull requests" (PR) on github. Every PR needs to be
|
Contributing happens via "Pull requests" (PR) on github. Every PR needs to be
|
||||||
reviewed before it can be merged and the Continuous Integration should be green.
|
reviewed before it can be merged and the Continuous Integration should be green.
|
||||||
|
|
||||||
|
|
@ -60,16 +64,17 @@ Compiler
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The tests for the compiler use a testing tool called ``testament``. They are all
|
The tests for the compiler use a testing tool called ``testament``. They are all
|
||||||
located in ``tests/`` (eg: ``tests/destructor/tdestructor3.nim``).
|
located in ``tests/`` (e.g.: ``tests/destructor/tdestructor3.nim``).
|
||||||
Each test has its own file. All test files are prefixed with ``t``. If you want
|
Each test has its own file. All test files are prefixed with ``t``. If you want
|
||||||
to create a file for import into another test only, use the prefix ``m``.
|
to create a file for import into another test only, use the prefix ``m``.
|
||||||
|
|
||||||
At the beginning of every test is the expected behavior of the test.
|
At the beginning of every test is the expected behavior of the test.
|
||||||
Possible keys are:
|
Possible keys are:
|
||||||
|
|
||||||
- output: The expected output, most likely via ``echo``
|
- cmd: A compilation command template e.g. "nim $target --threads:on $options $file"
|
||||||
|
- output: The expected output (stdout + stderr), most likely via ``echo``
|
||||||
- exitcode: Exit code of the test (via ``exit(number)``)
|
- exitcode: Exit code of the test (via ``exit(number)``)
|
||||||
- errormsg: The expected error message
|
- errormsg: The expected compiler error message
|
||||||
- file: The file the errormsg was produced at
|
- file: The file the errormsg was produced at
|
||||||
- line: The line the errormsg was produced at
|
- line: The line the errormsg was produced at
|
||||||
|
|
||||||
|
|
@ -115,6 +120,13 @@ list of these, see ``testament/categories.nim``, at the bottom.
|
||||||
|
|
||||||
./koch tests c lib
|
./koch tests c lib
|
||||||
|
|
||||||
|
To run a single test:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
./koch tests c <category>/<name>
|
||||||
|
|
||||||
|
E.g. ``./koch test run stdlib/thttpclient_ssl``
|
||||||
|
|
||||||
For reproducible tests (to reproduce an environment more similar to the one
|
For reproducible tests (to reproduce an environment more similar to the one
|
||||||
run by Continuous Integration on travis/appveyor), you may want to disable your
|
run by Continuous Integration on travis/appveyor), you may want to disable your
|
||||||
|
|
@ -398,6 +410,26 @@ Code reviews
|
||||||
saves time explaining the change or applying it; see also
|
saves time explaining the change or applying it; see also
|
||||||
https://forum.nim-lang.org/t/4317
|
https://forum.nim-lang.org/t/4317
|
||||||
|
|
||||||
|
2. When reviewing large diffs that may involve code moving around, github's interface
|
||||||
|
doesn't help much as it doesn't highlight moves. Instead you can use something
|
||||||
|
like this, see visual results `here <https://github.com/nim-lang/Nim/pull/10431#issuecomment-456968196>`_:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
git fetch origin pull/10431/head && git checkout FETCH_HEAD
|
||||||
|
git show --color-moved-ws=allow-indentation-change --color-moved=blocks HEAD^
|
||||||
|
|
||||||
|
3. In addition, you can view github-like diffs locally to identify what was changed
|
||||||
|
within a code block using `diff-highlight` or `diff-so-fancy`, eg:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
# put this in ~/.gitconfig:
|
||||||
|
[core]
|
||||||
|
pager = "diff-so-fancy | less -R" # or: use: `diff-highlight`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. include:: docstyle.rst
|
.. include:: docstyle.rst
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ symbols in the `system module <system.html>`_.
|
||||||
* ``const NimVersion = "0.0.0"`` **=>**
|
* ``const NimVersion = "0.0.0"`` **=>**
|
||||||
`#NimVersion <system.html#NimVersion>`_
|
`#NimVersion <system.html#NimVersion>`_
|
||||||
* ``proc getTotalMem(): int {.rtl, raises: [], tags: [].}`` **=>**
|
* ``proc getTotalMem(): int {.rtl, raises: [], tags: [].}`` **=>**
|
||||||
`#getTotalMem, <system.html#getTotalMem,>`_
|
`#getTotalMem, <system.html#getTotalMem>`_
|
||||||
* ``proc len[T](x: seq[T]): int {.magic: "LengthSeq", noSideEffect.}`` **=>**
|
* ``proc len[T](x: seq[T]): int {.magic: "LengthSeq", noSideEffect.}`` **=>**
|
||||||
`#len,seq[T] <system.html#len,seq[T]>`_
|
`#len,seq[T] <system.html#len,seq[T]>`_
|
||||||
* ``iterator pairs[T](a: seq[T]): tuple[key: int, val: T] {.inline.}`` **=>**
|
* ``iterator pairs[T](a: seq[T]): tuple[key: int, val: T] {.inline.}`` **=>**
|
||||||
|
|
|
||||||
38
doc/lib.rst
38
doc/lib.rst
|
|
@ -67,6 +67,8 @@ Core
|
||||||
* `lenientops <lenientops.html>`_
|
* `lenientops <lenientops.html>`_
|
||||||
Provides binary operators for mixed integer/float expressions for convenience.
|
Provides binary operators for mixed integer/float expressions for convenience.
|
||||||
|
|
||||||
|
* `bitops <bitops.html>`_
|
||||||
|
Provides a series of low level methods for bit manipulation.
|
||||||
|
|
||||||
|
|
||||||
Collections and algorithms
|
Collections and algorithms
|
||||||
|
|
@ -74,26 +76,38 @@ Collections and algorithms
|
||||||
|
|
||||||
* `algorithm <algorithm.html>`_
|
* `algorithm <algorithm.html>`_
|
||||||
Implements some common generic algorithms like sort or binary search.
|
Implements some common generic algorithms like sort or binary search.
|
||||||
|
|
||||||
* `tables <tables.html>`_
|
* `tables <tables.html>`_
|
||||||
Nim hash table support. Contains tables, ordered tables and count tables.
|
Nim hash table support. Contains tables, ordered tables and count tables.
|
||||||
|
|
||||||
* `sets <sets.html>`_
|
* `sets <sets.html>`_
|
||||||
Nim hash and bit set support.
|
Nim hash and bit set support.
|
||||||
|
|
||||||
* `lists <lists.html>`_
|
* `lists <lists.html>`_
|
||||||
Nim linked list support. Contains singly and doubly linked lists and
|
Nim linked list support. Contains singly and doubly linked lists and
|
||||||
circular lists ("rings").
|
circular lists ("rings").
|
||||||
|
|
||||||
* `deques <deques.html>`_
|
* `deques <deques.html>`_
|
||||||
Implementation of a double-ended queue.
|
Implementation of a double-ended queue.
|
||||||
The underlying implementation uses a ``seq``.
|
The underlying implementation uses a ``seq``.
|
||||||
|
|
||||||
|
* `heapqueue <heapqueue.html>`_
|
||||||
|
Implementation of a heap data structure that can be used as a priority queue.
|
||||||
|
|
||||||
* `intsets <intsets.html>`_
|
* `intsets <intsets.html>`_
|
||||||
Efficient implementation of a set of ints as a sparse bit set.
|
Efficient implementation of a set of ints as a sparse bit set.
|
||||||
|
|
||||||
* `critbits <critbits.html>`_
|
* `critbits <critbits.html>`_
|
||||||
This module implements a *crit bit tree* which is an efficient
|
This module implements a *crit bit tree* which is an efficient
|
||||||
container for a sorted set of strings, or for a sorted mapping of strings.
|
container for a sorted set of strings, or for a sorted mapping of strings.
|
||||||
|
|
||||||
* `sequtils <sequtils.html>`_
|
* `sequtils <sequtils.html>`_
|
||||||
This module implements operations for the built-in seq type
|
This module implements operations for the built-in seq type
|
||||||
which were inspired by functional programming languages.
|
which were inspired by functional programming languages.
|
||||||
|
|
||||||
* `sharedtables <sharedtables.html>`_
|
* `sharedtables <sharedtables.html>`_
|
||||||
Nim shared hash table support. Contains shared tables.
|
Nim shared hash table support. Contains shared tables.
|
||||||
|
|
||||||
* `sharedlist <sharedlist.html>`_
|
* `sharedlist <sharedlist.html>`_
|
||||||
Nim shared linked list support. Contains shared singly linked list.
|
Nim shared linked list support. Contains shared singly linked list.
|
||||||
|
|
||||||
|
|
@ -129,6 +143,13 @@ String handling
|
||||||
* `unicode <unicode.html>`_
|
* `unicode <unicode.html>`_
|
||||||
This module provides support to handle the Unicode UTF-8 encoding.
|
This module provides support to handle the Unicode UTF-8 encoding.
|
||||||
|
|
||||||
|
* `unidecode <unidecode.html>`_
|
||||||
|
It provides a single proc that does Unicode to ASCII transliterations.
|
||||||
|
Based on Python's Unidecode module.
|
||||||
|
|
||||||
|
* `punycode <punycode.html>`_
|
||||||
|
Implements a representation of Unicode with the limited ASCII character subset.
|
||||||
|
|
||||||
* `encodings <encodings.html>`_
|
* `encodings <encodings.html>`_
|
||||||
Converts between different character encodings. On UNIX, this uses
|
Converts between different character encodings. On UNIX, this uses
|
||||||
the ``iconv`` library, on Windows the Windows API.
|
the ``iconv`` library, on Windows the Windows API.
|
||||||
|
|
@ -194,10 +215,16 @@ Generic Operating System Services
|
||||||
``asyncdispatch``.
|
``asyncdispatch``.
|
||||||
|
|
||||||
* `distros <distros.html>`_
|
* `distros <distros.html>`_
|
||||||
This module implements the basics for OS distribution ("distro") detection and the OS's native package manager.
|
This module implements the basics for OS distribution ("distro") detection
|
||||||
Its primary purpose is to produce output for Nimble packages, but it also contains the widely used **Distribution** enum
|
and the OS's native package manager.
|
||||||
|
Its primary purpose is to produce output for Nimble packages,
|
||||||
|
but it also contains the widely used **Distribution** enum
|
||||||
that is useful for writing platform specific code.
|
that is useful for writing platform specific code.
|
||||||
|
|
||||||
|
* `volatile <volatile.html>`_
|
||||||
|
This module contains code for generating volatile loads and stores,
|
||||||
|
which are useful in embedded and systems programming.
|
||||||
|
|
||||||
|
|
||||||
Math libraries
|
Math libraries
|
||||||
--------------
|
--------------
|
||||||
|
|
@ -276,6 +303,7 @@ Internet Protocols and Support
|
||||||
This module implements a selector API with backends specific to each OS.
|
This module implements a selector API with backends specific to each OS.
|
||||||
Currently epoll on Linux and select on other operating systems.
|
Currently epoll on Linux and select on other operating systems.
|
||||||
|
|
||||||
|
|
||||||
Parsers
|
Parsers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
@ -402,6 +430,7 @@ Miscellaneous
|
||||||
* `segfaults <segfaults.html>`_
|
* `segfaults <segfaults.html>`_
|
||||||
Turns access violations or segfaults into a ``NilAccessError`` exception.
|
Turns access violations or segfaults into a ``NilAccessError`` exception.
|
||||||
|
|
||||||
|
|
||||||
Modules for JS backend
|
Modules for JS backend
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
@ -430,7 +459,6 @@ Regular expressions
|
||||||
expressions. The current implementation uses PCRE.
|
expressions. The current implementation uses PCRE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Database support
|
Database support
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
@ -447,13 +475,13 @@ Database support
|
||||||
for other databases too.
|
for other databases too.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Wrappers
|
Wrappers
|
||||||
========
|
========
|
||||||
|
|
||||||
The generated HTML for some of these wrappers is so huge that it is
|
The generated HTML for some of these wrappers is so huge that it is
|
||||||
not contained in the distribution. You can then find them on the website.
|
not contained in the distribution. You can then find them on the website.
|
||||||
|
|
||||||
|
|
||||||
Windows specific
|
Windows specific
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
@ -509,5 +537,5 @@ Nimble is a package manager for the Nim programming language.
|
||||||
For instructions on how to install Nimble packages see
|
For instructions on how to install Nimble packages see
|
||||||
`its README <https://github.com/nim-lang/nimble#readme>`_.
|
`its README <https://github.com/nim-lang/nimble#readme>`_.
|
||||||
|
|
||||||
To see a list of Nimble's packages, check out `https://nimble.directory/ <https://nimble.directory/>`_
|
To see a list of Nimble's packages, check out `<https://nimble.directory/>`_
|
||||||
or the `packages repo <https://github.com/nim-lang/packages>`_ on GitHub.
|
or the `packages repo <https://github.com/nim-lang/packages>`_ on GitHub.
|
||||||
|
|
|
||||||
|
|
@ -6922,7 +6922,7 @@ statement as seen in stack backtraces:
|
||||||
template myassert*(cond: untyped, msg = "") =
|
template myassert*(cond: untyped, msg = "") =
|
||||||
if not cond:
|
if not cond:
|
||||||
# change run-time line information of the 'raise' statement:
|
# change run-time line information of the 'raise' statement:
|
||||||
{.line: InstantiationInfo().}:
|
{.line: instantiationInfo().}:
|
||||||
raise newException(EAssertionFailed, msg)
|
raise newException(EAssertionFailed, msg)
|
||||||
|
|
||||||
If the ``line`` pragma is used with a parameter, the parameter needs be a
|
If the ``line`` pragma is used with a parameter, the parameter needs be a
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,7 @@ Define Effect
|
||||||
``useRealtimeGC`` Enables support of Nim's GC for *soft* realtime
|
``useRealtimeGC`` Enables support of Nim's GC for *soft* realtime
|
||||||
systems. See the documentation of the `gc <gc.html>`_
|
systems. See the documentation of the `gc <gc.html>`_
|
||||||
for further information.
|
for further information.
|
||||||
|
``logGC`` Enable GC logging to stdout.
|
||||||
``nodejs`` The JS target is actually ``node.js``.
|
``nodejs`` The JS target is actually ``node.js``.
|
||||||
``ssl`` Enables OpenSSL support for the sockets module.
|
``ssl`` Enables OpenSSL support for the sockets module.
|
||||||
``memProfiler`` Enables memory profiling for the native GC.
|
``memProfiler`` Enables memory profiling for the native GC.
|
||||||
|
|
|
||||||
69
doc/packaging.rst
Normal file
69
doc/packaging.rst
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
=============
|
||||||
|
Packaging Nim
|
||||||
|
=============
|
||||||
|
|
||||||
|
|
||||||
|
Supported architectures
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Nim runs on a wide variety of platforms. Support on amd64 and i386 is tested regularly, while less popular platforms are tested by the community.
|
||||||
|
|
||||||
|
- amd64
|
||||||
|
- arm64 (aka aarch64)
|
||||||
|
- armel
|
||||||
|
- armhf
|
||||||
|
- i386
|
||||||
|
- m68k
|
||||||
|
- mips64el
|
||||||
|
- mipsel
|
||||||
|
- powerpc
|
||||||
|
- ppc64
|
||||||
|
- ppc64el (aka ppc64le)
|
||||||
|
- riscv64
|
||||||
|
|
||||||
|
The following platforms are seldomly tested:
|
||||||
|
|
||||||
|
- alpha
|
||||||
|
- hppa
|
||||||
|
- ia64
|
||||||
|
- mips
|
||||||
|
- s390x
|
||||||
|
- sparc64
|
||||||
|
|
||||||
|
Packaging for Linux
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
See https://github.com/nim-lang/Nim/labels/Installation for installation-related bugs.
|
||||||
|
|
||||||
|
Build Nim from the released tarball at https://nim-lang.org/install_unix.html
|
||||||
|
It is different from the GitHub sources as it contains Nimble, C sources & other tools.
|
||||||
|
|
||||||
|
The Debian package ships bash and ksh completion and manpages that can be reused.
|
||||||
|
|
||||||
|
Hints on the build process:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# build from C sources and then using koch
|
||||||
|
./build.sh --os $os_type --cpu $cpu_arch
|
||||||
|
./bin/nim c koch
|
||||||
|
./koch boot -d:release
|
||||||
|
|
||||||
|
# optionally generate docs into doc/html
|
||||||
|
./koch docs
|
||||||
|
|
||||||
|
./koch tools -d:release
|
||||||
|
|
||||||
|
# extract files to be really installed
|
||||||
|
./install.sh <tempdir>
|
||||||
|
|
||||||
|
# also include the tools
|
||||||
|
for fn in nimble nimsuggest nimgrep; do cp ./bin/$fn <tempdir>/nim/bin/; done
|
||||||
|
|
||||||
|
What to install:
|
||||||
|
|
||||||
|
- The expected stdlib location is /usr/lib/nim
|
||||||
|
- Global configuration files under /etc/nim
|
||||||
|
- Optionally: manpages, documentation, shell completion
|
||||||
|
- When installing documentation, .idx files are not required
|
||||||
|
- The "compiler" directory contains compiler sources and should not be part of the compiler binary package
|
||||||
25
koch.nim
25
koch.nim
|
|
@ -80,6 +80,9 @@ let kochExe* = when isMainModule: os.getAppFilename() # always correct when koch
|
||||||
proc kochExec*(cmd: string) =
|
proc kochExec*(cmd: string) =
|
||||||
exec kochExe.quoteShell & " " & cmd
|
exec kochExe.quoteShell & " " & cmd
|
||||||
|
|
||||||
|
proc kochExecFold*(desc, cmd: string) =
|
||||||
|
execFold(desc, kochExe.quoteShell & " " & cmd)
|
||||||
|
|
||||||
template withDir(dir, body) =
|
template withDir(dir, body) =
|
||||||
let old = getCurrentDir()
|
let old = getCurrentDir()
|
||||||
try:
|
try:
|
||||||
|
|
@ -453,11 +456,11 @@ proc runCI(cmd: string) =
|
||||||
# note(@araq): Do not replace these commands with direct calls (eg boot())
|
# note(@araq): Do not replace these commands with direct calls (eg boot())
|
||||||
# as that would weaken our testing efforts.
|
# as that would weaken our testing efforts.
|
||||||
when defined(posix): # appveyor (on windows) didn't run this
|
when defined(posix): # appveyor (on windows) didn't run this
|
||||||
kochExec "boot"
|
kochExecFold("Boot", "boot")
|
||||||
kochExec "boot -d:release"
|
kochExecFold("Boot in release mode", "boot -d:release")
|
||||||
|
|
||||||
## build nimble early on to enable remainder to depend on it if needed
|
## build nimble early on to enable remainder to depend on it if needed
|
||||||
kochExec "nimble"
|
kochExecFold("Build Nimble", "nimble")
|
||||||
|
|
||||||
when false:
|
when false:
|
||||||
for pkg in "zip opengl sdl1 jester@#head niminst".split:
|
for pkg in "zip opengl sdl1 jester@#head niminst".split:
|
||||||
|
|
@ -466,23 +469,23 @@ proc runCI(cmd: string) =
|
||||||
buildTools() # altenatively, kochExec "tools --toolsNoNimble"
|
buildTools() # altenatively, kochExec "tools --toolsNoNimble"
|
||||||
|
|
||||||
## run tests
|
## run tests
|
||||||
exec "nim e tests/test_nimscript.nims"
|
execFold("Test nimscript", "nim e tests/test_nimscript.nims")
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
# note: will be over-written below
|
# note: will be over-written below
|
||||||
exec "nim c -d:nimCoroutines --os:genode -d:posix --compileOnly testament/tester"
|
execFold("Compile tester", "nim c -d:nimCoroutines --os:genode -d:posix --compileOnly testament/tester")
|
||||||
|
|
||||||
# main bottleneck here
|
# main bottleneck here
|
||||||
exec "nim c -r -d:nimCoroutines testament/tester --pedantic all -d:nimCoroutines"
|
execFold("Run tester", "nim c -r -d:nimCoroutines testament/tester --pedantic all -d:nimCoroutines")
|
||||||
|
|
||||||
exec "nim c -r nimdoc/tester"
|
execFold("Run nimdoc tests", "nim c -r nimdoc/tester")
|
||||||
exec "nim c -r nimpretty/tester.nim"
|
execFold("Run nimpretty tests", "nim c -r nimpretty/tester.nim")
|
||||||
when defined(posix):
|
when defined(posix):
|
||||||
exec "nim c -r nimsuggest/tester"
|
execFold("Run nimsuggest tests", "nim c -r nimsuggest/tester")
|
||||||
|
|
||||||
## remaining actions
|
## remaining actions
|
||||||
when defined(posix):
|
when defined(posix):
|
||||||
kochExec "docs --git.commit:devel"
|
kochExecFold("Docs", "docs --git.commit:devel")
|
||||||
kochExec "csource"
|
kochExecFold("C sources", "csource")
|
||||||
elif defined(windows):
|
elif defined(windows):
|
||||||
when false:
|
when false:
|
||||||
kochExec "csource"
|
kochExec "csource"
|
||||||
|
|
|
||||||
|
|
@ -1,711 +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.
|
|
||||||
#
|
|
||||||
|
|
||||||
include "system/inclrtl"
|
|
||||||
|
|
||||||
import sockets, os
|
|
||||||
|
|
||||||
##
|
|
||||||
## **Warning:** This module is deprecated since version 0.10.2.
|
|
||||||
## Use the brand new `asyncdispatch <asyncdispatch.html>`_ module together
|
|
||||||
## with the `asyncnet <asyncnet.html>`_ module.
|
|
||||||
|
|
||||||
## This module implements an asynchronous event loop together with asynchronous
|
|
||||||
## sockets which use this event loop.
|
|
||||||
## It is akin to Python's asyncore module. Many modules that use sockets
|
|
||||||
## have an implementation for this module, those modules should all have a
|
|
||||||
## ``register`` function which you should use to add the desired objects to a
|
|
||||||
## dispatcher which you created so
|
|
||||||
## that you can receive the events associated with that module's object.
|
|
||||||
##
|
|
||||||
## Once everything is registered in a dispatcher, you need to call the ``poll``
|
|
||||||
## function in a while loop.
|
|
||||||
##
|
|
||||||
## **Note:** Most modules have tasks which need to be ran regularly, this is
|
|
||||||
## why you should not call ``poll`` with a infinite timeout, or even a
|
|
||||||
## very long one. In most cases the default timeout is fine.
|
|
||||||
##
|
|
||||||
## **Note:** This module currently only supports select(), this is limited by
|
|
||||||
## FD_SETSIZE, which is usually 1024. So you may only be able to use 1024
|
|
||||||
## sockets at a time.
|
|
||||||
##
|
|
||||||
## Most (if not all) modules that use asyncio provide a userArg which is passed
|
|
||||||
## on with the events. The type that you set userArg to must be inheriting from
|
|
||||||
## ``RootObj``!
|
|
||||||
##
|
|
||||||
## **Note:** If you want to provide async ability to your module please do not
|
|
||||||
## use the ``Delegate`` object, instead use ``AsyncSocket``. It is possible
|
|
||||||
## that in the future this type's fields will not be exported therefore breaking
|
|
||||||
## your code.
|
|
||||||
##
|
|
||||||
## **Warning:** The API of this module is unstable, and therefore is subject
|
|
||||||
## to change.
|
|
||||||
##
|
|
||||||
## Asynchronous sockets
|
|
||||||
## ====================
|
|
||||||
##
|
|
||||||
## For most purposes you do not need to worry about the ``Delegate`` type. The
|
|
||||||
## ``AsyncSocket`` is what you are after. It's a reference to
|
|
||||||
## the ``AsyncSocketObj`` object. This object defines events which you should
|
|
||||||
## overwrite by your own procedures.
|
|
||||||
##
|
|
||||||
## For server sockets the only event you need to worry about is the ``handleAccept``
|
|
||||||
## event, in your handleAccept proc you should call ``accept`` on the server
|
|
||||||
## socket which will give you the client which is connecting. You should then
|
|
||||||
## set any events that you want to use on that client and add it to your dispatcher
|
|
||||||
## using the ``register`` procedure.
|
|
||||||
##
|
|
||||||
## An example ``handleAccept`` follows:
|
|
||||||
##
|
|
||||||
## .. code-block:: nim
|
|
||||||
##
|
|
||||||
## var disp = newDispatcher()
|
|
||||||
## ...
|
|
||||||
## proc handleAccept(s: AsyncSocket) =
|
|
||||||
## echo("Accepted client.")
|
|
||||||
## var client: AsyncSocket
|
|
||||||
## new(client)
|
|
||||||
## s.accept(client)
|
|
||||||
## client.handleRead = ...
|
|
||||||
## disp.register(client)
|
|
||||||
## ...
|
|
||||||
##
|
|
||||||
## For client sockets you should only be interested in the ``handleRead`` and
|
|
||||||
## ``handleConnect`` events. The former gets called whenever the socket has
|
|
||||||
## received messages and can be read from and the latter gets called whenever
|
|
||||||
## the socket has established a connection to a server socket; from that point
|
|
||||||
## it can be safely written to.
|
|
||||||
##
|
|
||||||
## Getting a blocking client from an AsyncSocket
|
|
||||||
## =============================================
|
|
||||||
##
|
|
||||||
## If you need a asynchronous server socket but you wish to process the clients
|
|
||||||
## synchronously then you can use the ``getSocket`` converter to get
|
|
||||||
## a ``Socket`` from the ``AsyncSocket`` object, this can then be combined
|
|
||||||
## with ``accept`` like so:
|
|
||||||
##
|
|
||||||
## .. code-block:: nim
|
|
||||||
##
|
|
||||||
## proc handleAccept(s: AsyncSocket) =
|
|
||||||
## var client: Socket
|
|
||||||
## getSocket(s).accept(client)
|
|
||||||
|
|
||||||
{.deprecated.}
|
|
||||||
|
|
||||||
when defined(windows):
|
|
||||||
from winlean import TimeVal, SocketHandle, FD_SET, FD_ZERO, TFdSet,
|
|
||||||
FD_ISSET, select
|
|
||||||
else:
|
|
||||||
from posix import TimeVal, Time, Suseconds, SocketHandle, FD_SET, FD_ZERO,
|
|
||||||
TFdSet, FD_ISSET, select
|
|
||||||
|
|
||||||
type
|
|
||||||
DelegateObj* = object
|
|
||||||
fd*: SocketHandle
|
|
||||||
deleVal*: RootRef
|
|
||||||
|
|
||||||
handleRead*: proc (h: RootRef) {.nimcall, gcsafe.}
|
|
||||||
handleWrite*: proc (h: RootRef) {.nimcall, gcsafe.}
|
|
||||||
handleError*: proc (h: RootRef) {.nimcall, gcsafe.}
|
|
||||||
hasDataBuffered*: proc (h: RootRef): bool {.nimcall, gcsafe.}
|
|
||||||
|
|
||||||
open*: bool
|
|
||||||
task*: proc (h: RootRef) {.nimcall, gcsafe.}
|
|
||||||
mode*: FileMode
|
|
||||||
|
|
||||||
Delegate* = ref DelegateObj
|
|
||||||
|
|
||||||
Dispatcher* = ref DispatcherObj
|
|
||||||
DispatcherObj = object
|
|
||||||
delegates: seq[Delegate]
|
|
||||||
|
|
||||||
AsyncSocket* = ref AsyncSocketObj
|
|
||||||
AsyncSocketObj* = object of RootObj
|
|
||||||
socket: Socket
|
|
||||||
info: SocketStatus
|
|
||||||
|
|
||||||
handleRead*: proc (s: AsyncSocket) {.closure, gcsafe.}
|
|
||||||
handleWrite: proc (s: AsyncSocket) {.closure, gcsafe.}
|
|
||||||
handleConnect*: proc (s: AsyncSocket) {.closure, gcsafe.}
|
|
||||||
|
|
||||||
handleAccept*: proc (s: AsyncSocket) {.closure, gcsafe.}
|
|
||||||
|
|
||||||
handleTask*: proc (s: AsyncSocket) {.closure, gcsafe.}
|
|
||||||
|
|
||||||
lineBuffer: TaintedString ## Temporary storage for ``readLine``
|
|
||||||
sendBuffer: string ## Temporary storage for ``send``
|
|
||||||
sslNeedAccept: bool
|
|
||||||
proto: Protocol
|
|
||||||
deleg: Delegate
|
|
||||||
|
|
||||||
SocketStatus* = enum
|
|
||||||
SockIdle, SockConnecting, SockConnected, SockListening, SockClosed,
|
|
||||||
SockUDPBound
|
|
||||||
|
|
||||||
|
|
||||||
proc newDelegate*(): Delegate =
|
|
||||||
## Creates a new delegate.
|
|
||||||
new(result)
|
|
||||||
result.handleRead = (proc (h: RootRef) = discard)
|
|
||||||
result.handleWrite = (proc (h: RootRef) = discard)
|
|
||||||
result.handleError = (proc (h: RootRef) = discard)
|
|
||||||
result.hasDataBuffered = (proc (h: RootRef): bool = return false)
|
|
||||||
result.task = (proc (h: RootRef) = discard)
|
|
||||||
result.mode = fmRead
|
|
||||||
|
|
||||||
proc newAsyncSocket(): AsyncSocket =
|
|
||||||
new(result)
|
|
||||||
result.info = SockIdle
|
|
||||||
|
|
||||||
result.handleRead = (proc (s: AsyncSocket) = discard)
|
|
||||||
result.handleWrite = nil
|
|
||||||
result.handleConnect = (proc (s: AsyncSocket) = discard)
|
|
||||||
result.handleAccept = (proc (s: AsyncSocket) = discard)
|
|
||||||
result.handleTask = (proc (s: AsyncSocket) = discard)
|
|
||||||
|
|
||||||
result.lineBuffer = "".TaintedString
|
|
||||||
result.sendBuffer = ""
|
|
||||||
|
|
||||||
proc asyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
|
|
||||||
protocol: Protocol = IPPROTO_TCP,
|
|
||||||
buffered = true): AsyncSocket =
|
|
||||||
## Initialises an AsyncSocket object. If a socket cannot be initialised
|
|
||||||
## OSError is raised.
|
|
||||||
result = newAsyncSocket()
|
|
||||||
result.socket = socket(domain, typ, protocol, buffered)
|
|
||||||
result.proto = protocol
|
|
||||||
if result.socket == invalidSocket: raiseOSError(osLastError())
|
|
||||||
result.socket.setBlocking(false)
|
|
||||||
|
|
||||||
proc toAsyncSocket*(sock: Socket, state: SocketStatus = SockConnected): AsyncSocket =
|
|
||||||
## 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
|
|
||||||
## adjusted properly. By default it will be assumed that the socket is
|
|
||||||
## connected. Please note this is only applicable to TCP client sockets, if
|
|
||||||
## ``sock`` is a different type of socket ``state`` needs to be adjusted!!!
|
|
||||||
##
|
|
||||||
## ================ ================================================================
|
|
||||||
## Value Meaning
|
|
||||||
## ================ ================================================================
|
|
||||||
## SockIdle Socket has only just been initialised, not connected or closed.
|
|
||||||
## SockConnected Socket is connected to a server.
|
|
||||||
## SockConnecting Socket is in the process of connecting to a server.
|
|
||||||
## SockListening Socket is a server socket and is listening for connections.
|
|
||||||
## SockClosed Socket has been closed.
|
|
||||||
## SockUDPBound Socket is a UDP socket which is listening for data.
|
|
||||||
## ================ ================================================================
|
|
||||||
##
|
|
||||||
## **Warning**: If ``state`` is set incorrectly the resulting ``AsyncSocket``
|
|
||||||
## object may not work properly.
|
|
||||||
##
|
|
||||||
## **Note**: This will set ``sock`` to be non-blocking.
|
|
||||||
result = newAsyncSocket()
|
|
||||||
result.socket = sock
|
|
||||||
result.proto = if state == SockUDPBound: IPPROTO_UDP else: IPPROTO_TCP
|
|
||||||
result.socket.setBlocking(false)
|
|
||||||
result.info = state
|
|
||||||
|
|
||||||
proc asyncSockHandleRead(h: RootRef) =
|
|
||||||
when defined(ssl):
|
|
||||||
if AsyncSocket(h).socket.isSSL and not
|
|
||||||
AsyncSocket(h).socket.gotHandshake:
|
|
||||||
return
|
|
||||||
|
|
||||||
if AsyncSocket(h).info != SockListening:
|
|
||||||
if AsyncSocket(h).info != SockConnecting:
|
|
||||||
AsyncSocket(h).handleRead(AsyncSocket(h))
|
|
||||||
else:
|
|
||||||
AsyncSocket(h).handleAccept(AsyncSocket(h))
|
|
||||||
|
|
||||||
proc close*(sock: AsyncSocket) {.gcsafe.}
|
|
||||||
proc asyncSockHandleWrite(h: RootRef) =
|
|
||||||
when defined(ssl):
|
|
||||||
if AsyncSocket(h).socket.isSSL and not
|
|
||||||
AsyncSocket(h).socket.gotHandshake:
|
|
||||||
return
|
|
||||||
|
|
||||||
if AsyncSocket(h).info == SockConnecting:
|
|
||||||
AsyncSocket(h).handleConnect(AsyncSocket(h))
|
|
||||||
AsyncSocket(h).info = SockConnected
|
|
||||||
# Stop receiving write events if there is no handleWrite event.
|
|
||||||
if AsyncSocket(h).handleWrite == nil:
|
|
||||||
AsyncSocket(h).deleg.mode = fmRead
|
|
||||||
else:
|
|
||||||
AsyncSocket(h).deleg.mode = fmReadWrite
|
|
||||||
else:
|
|
||||||
if AsyncSocket(h).sendBuffer != "":
|
|
||||||
let sock = AsyncSocket(h)
|
|
||||||
try:
|
|
||||||
let bytesSent = sock.socket.sendAsync(sock.sendBuffer)
|
|
||||||
if bytesSent == 0:
|
|
||||||
# Apparently the socket cannot be written to. Even though select
|
|
||||||
# just told us that it can be... This used to be an assert. Just
|
|
||||||
# do nothing instead.
|
|
||||||
discard
|
|
||||||
elif bytesSent != sock.sendBuffer.len:
|
|
||||||
sock.sendBuffer = sock.sendBuffer[bytesSent .. ^1]
|
|
||||||
elif bytesSent == sock.sendBuffer.len:
|
|
||||||
sock.sendBuffer = ""
|
|
||||||
|
|
||||||
if AsyncSocket(h).handleWrite != nil:
|
|
||||||
AsyncSocket(h).handleWrite(AsyncSocket(h))
|
|
||||||
except OSError:
|
|
||||||
# Most likely the socket closed before the full buffer could be sent to it.
|
|
||||||
sock.close() # TODO: Provide a handleError for users?
|
|
||||||
else:
|
|
||||||
if AsyncSocket(h).handleWrite != nil:
|
|
||||||
AsyncSocket(h).handleWrite(AsyncSocket(h))
|
|
||||||
else:
|
|
||||||
AsyncSocket(h).deleg.mode = fmRead
|
|
||||||
|
|
||||||
when defined(ssl):
|
|
||||||
proc asyncSockDoHandshake(h: RootRef) {.gcsafe.} =
|
|
||||||
if AsyncSocket(h).socket.isSSL and not
|
|
||||||
AsyncSocket(h).socket.gotHandshake:
|
|
||||||
if AsyncSocket(h).sslNeedAccept:
|
|
||||||
var d = ""
|
|
||||||
let ret = AsyncSocket(h).socket.acceptAddrSSL(AsyncSocket(h).socket, d)
|
|
||||||
assert ret != AcceptNoClient
|
|
||||||
if ret == AcceptSuccess:
|
|
||||||
AsyncSocket(h).info = SockConnected
|
|
||||||
else:
|
|
||||||
# handshake will set socket's ``sslNoHandshake`` field.
|
|
||||||
discard AsyncSocket(h).socket.handshake()
|
|
||||||
|
|
||||||
|
|
||||||
proc asyncSockTask(h: RootRef) =
|
|
||||||
when defined(ssl):
|
|
||||||
h.asyncSockDoHandshake()
|
|
||||||
|
|
||||||
AsyncSocket(h).handleTask(AsyncSocket(h))
|
|
||||||
|
|
||||||
proc toDelegate(sock: AsyncSocket): Delegate =
|
|
||||||
result = newDelegate()
|
|
||||||
result.deleVal = sock
|
|
||||||
result.fd = getFD(sock.socket)
|
|
||||||
# We need this to get write events, just to know when the socket connects.
|
|
||||||
result.mode = fmReadWrite
|
|
||||||
result.handleRead = asyncSockHandleRead
|
|
||||||
result.handleWrite = asyncSockHandleWrite
|
|
||||||
result.task = asyncSockTask
|
|
||||||
# TODO: Errors?
|
|
||||||
#result.handleError = (proc (h: PObject) = assert(false))
|
|
||||||
|
|
||||||
result.hasDataBuffered =
|
|
||||||
proc (h: RootRef): bool {.nimcall.} =
|
|
||||||
return AsyncSocket(h).socket.hasDataBuffered()
|
|
||||||
|
|
||||||
sock.deleg = result
|
|
||||||
if sock.info notin {SockIdle, SockClosed}:
|
|
||||||
sock.deleg.open = true
|
|
||||||
else:
|
|
||||||
sock.deleg.open = false
|
|
||||||
|
|
||||||
proc connect*(sock: AsyncSocket, name: string, port = Port(0),
|
|
||||||
af: Domain = AF_INET) =
|
|
||||||
## Begins connecting ``sock`` to ``name``:``port``.
|
|
||||||
sock.socket.connectAsync(name, port, af)
|
|
||||||
sock.info = SockConnecting
|
|
||||||
if sock.deleg != nil:
|
|
||||||
sock.deleg.open = true
|
|
||||||
|
|
||||||
proc close*(sock: AsyncSocket) =
|
|
||||||
## Closes ``sock``. Terminates any current connections.
|
|
||||||
sock.socket.close()
|
|
||||||
sock.info = SockClosed
|
|
||||||
if sock.deleg != nil:
|
|
||||||
sock.deleg.open = false
|
|
||||||
|
|
||||||
proc bindAddr*(sock: AsyncSocket, port = Port(0), address = "") =
|
|
||||||
## Equivalent to ``sockets.bindAddr``.
|
|
||||||
sock.socket.bindAddr(port, address)
|
|
||||||
if sock.proto == IPPROTO_UDP:
|
|
||||||
sock.info = SockUDPBound
|
|
||||||
if sock.deleg != nil:
|
|
||||||
sock.deleg.open = true
|
|
||||||
|
|
||||||
proc listen*(sock: AsyncSocket) =
|
|
||||||
## Equivalent to ``sockets.listen``.
|
|
||||||
sock.socket.listen()
|
|
||||||
sock.info = SockListening
|
|
||||||
if sock.deleg != nil:
|
|
||||||
sock.deleg.open = true
|
|
||||||
|
|
||||||
proc acceptAddr*(server: AsyncSocket, client: var AsyncSocket,
|
|
||||||
address: var string) =
|
|
||||||
## Equivalent to ``sockets.acceptAddr``. This procedure should be called in
|
|
||||||
## a ``handleAccept`` event handler **only** once.
|
|
||||||
##
|
|
||||||
## **Note**: ``client`` needs to be initialised.
|
|
||||||
assert(client != nil)
|
|
||||||
client = newAsyncSocket()
|
|
||||||
var c: Socket
|
|
||||||
new(c)
|
|
||||||
when defined(ssl):
|
|
||||||
if server.socket.isSSL:
|
|
||||||
var ret = server.socket.acceptAddrSSL(c, address)
|
|
||||||
# The following shouldn't happen because when this function is called
|
|
||||||
# it is guaranteed that there is a client waiting.
|
|
||||||
# (This should be called in handleAccept)
|
|
||||||
assert(ret != AcceptNoClient)
|
|
||||||
if ret == AcceptNoHandshake:
|
|
||||||
client.sslNeedAccept = true
|
|
||||||
else:
|
|
||||||
client.sslNeedAccept = false
|
|
||||||
client.info = SockConnected
|
|
||||||
else:
|
|
||||||
server.socket.acceptAddr(c, address)
|
|
||||||
client.sslNeedAccept = false
|
|
||||||
client.info = SockConnected
|
|
||||||
else:
|
|
||||||
server.socket.acceptAddr(c, address)
|
|
||||||
client.sslNeedAccept = false
|
|
||||||
client.info = SockConnected
|
|
||||||
|
|
||||||
if c == invalidSocket: raiseSocketError(server.socket)
|
|
||||||
c.setBlocking(false) # TODO: Needs to be tested.
|
|
||||||
|
|
||||||
# deleg.open is set in ``toDelegate``.
|
|
||||||
|
|
||||||
client.socket = c
|
|
||||||
client.lineBuffer = "".TaintedString
|
|
||||||
client.sendBuffer = ""
|
|
||||||
client.info = SockConnected
|
|
||||||
|
|
||||||
proc accept*(server: AsyncSocket, client: var AsyncSocket) =
|
|
||||||
## Equivalent to ``sockets.accept``.
|
|
||||||
var dummyAddr = ""
|
|
||||||
server.acceptAddr(client, dummyAddr)
|
|
||||||
|
|
||||||
proc acceptAddr*(server: AsyncSocket): tuple[sock: AsyncSocket,
|
|
||||||
address: string] {.deprecated.} =
|
|
||||||
## Equivalent to ``sockets.acceptAddr``.
|
|
||||||
##
|
|
||||||
## **Deprecated since version 0.9.0:** Please use the function above.
|
|
||||||
var client = newAsyncSocket()
|
|
||||||
var address: string = ""
|
|
||||||
acceptAddr(server, client, address)
|
|
||||||
return (client, address)
|
|
||||||
|
|
||||||
proc accept*(server: AsyncSocket): AsyncSocket {.deprecated.} =
|
|
||||||
## Equivalent to ``sockets.accept``.
|
|
||||||
##
|
|
||||||
## **Deprecated since version 0.9.0:** Please use the function above.
|
|
||||||
new(result)
|
|
||||||
var address = ""
|
|
||||||
server.acceptAddr(result, address)
|
|
||||||
|
|
||||||
proc newDispatcher*(): Dispatcher =
|
|
||||||
new(result)
|
|
||||||
result.delegates = @[]
|
|
||||||
|
|
||||||
proc register*(d: Dispatcher, deleg: Delegate) =
|
|
||||||
## Registers delegate ``deleg`` with dispatcher ``d``.
|
|
||||||
d.delegates.add(deleg)
|
|
||||||
|
|
||||||
proc register*(d: Dispatcher, sock: AsyncSocket): Delegate {.discardable.} =
|
|
||||||
## Registers async socket ``sock`` with dispatcher ``d``.
|
|
||||||
result = sock.toDelegate()
|
|
||||||
d.register(result)
|
|
||||||
|
|
||||||
proc unregister*(d: Dispatcher, deleg: Delegate) =
|
|
||||||
## Unregisters deleg ``deleg`` from dispatcher ``d``.
|
|
||||||
for i in 0..len(d.delegates)-1:
|
|
||||||
if d.delegates[i] == deleg:
|
|
||||||
d.delegates.del(i)
|
|
||||||
return
|
|
||||||
raise newException(IndexError, "Could not find delegate.")
|
|
||||||
|
|
||||||
proc isWriteable*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether socket ``s`` is ready to be written to.
|
|
||||||
var writeSock = @[s.socket]
|
|
||||||
return selectWrite(writeSock, 1) != 0 and s.socket notin writeSock
|
|
||||||
|
|
||||||
converter getSocket*(s: AsyncSocket): Socket =
|
|
||||||
return s.socket
|
|
||||||
|
|
||||||
proc isConnected*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether ``s`` is connected.
|
|
||||||
return s.info == SockConnected
|
|
||||||
proc isListening*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether ``s`` is listening for incoming connections.
|
|
||||||
return s.info == SockListening
|
|
||||||
proc isConnecting*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether ``s`` is connecting.
|
|
||||||
return s.info == SockConnecting
|
|
||||||
proc isClosed*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether ``s`` has been closed.
|
|
||||||
return s.info == SockClosed
|
|
||||||
proc isSendDataBuffered*(s: AsyncSocket): bool =
|
|
||||||
## Determines whether ``s`` has data waiting to be sent, i.e. whether this
|
|
||||||
## socket's sendBuffer contains data.
|
|
||||||
return s.sendBuffer.len != 0
|
|
||||||
|
|
||||||
proc setHandleWrite*(s: AsyncSocket,
|
|
||||||
handleWrite: proc (s: AsyncSocket) {.closure, gcsafe.}) =
|
|
||||||
## Setter for the ``handleWrite`` event.
|
|
||||||
##
|
|
||||||
## To remove this event you should use the ``delHandleWrite`` function.
|
|
||||||
## It is advised to use that function instead of just setting the event to
|
|
||||||
## ``proc (s: AsyncSocket) = nil`` as that would mean that that function
|
|
||||||
## would be called constantly.
|
|
||||||
s.deleg.mode = fmReadWrite
|
|
||||||
s.handleWrite = handleWrite
|
|
||||||
|
|
||||||
proc delHandleWrite*(s: AsyncSocket) =
|
|
||||||
## Removes the ``handleWrite`` event handler on ``s``.
|
|
||||||
s.handleWrite = nil
|
|
||||||
|
|
||||||
{.push warning[deprecated]: off.}
|
|
||||||
proc recvLine*(s: AsyncSocket, line: var TaintedString): bool {.deprecated.} =
|
|
||||||
## Behaves similar to ``sockets.recvLine``, however it handles non-blocking
|
|
||||||
## sockets properly. This function guarantees that ``line`` is a full line,
|
|
||||||
## if this function can only retrieve some data; it will save this data and
|
|
||||||
## add it to the result when a full line is retrieved.
|
|
||||||
##
|
|
||||||
## Unlike ``sockets.recvLine`` this function will raise an OSError or SslError
|
|
||||||
## exception if an error occurs.
|
|
||||||
##
|
|
||||||
## **Deprecated since version 0.9.2**: This function has been deprecated in
|
|
||||||
## favour of readLine.
|
|
||||||
setLen(line.string, 0)
|
|
||||||
var dataReceived = "".TaintedString
|
|
||||||
var ret = s.socket.recvLineAsync(dataReceived)
|
|
||||||
case ret
|
|
||||||
of RecvFullLine:
|
|
||||||
if s.lineBuffer.len > 0:
|
|
||||||
string(line).add(s.lineBuffer.string)
|
|
||||||
setLen(s.lineBuffer.string, 0)
|
|
||||||
string(line).add(dataReceived.string)
|
|
||||||
if string(line) == "":
|
|
||||||
line = "\c\L".TaintedString
|
|
||||||
result = true
|
|
||||||
of RecvPartialLine:
|
|
||||||
string(s.lineBuffer).add(dataReceived.string)
|
|
||||||
result = false
|
|
||||||
of RecvDisconnected:
|
|
||||||
result = true
|
|
||||||
of RecvFail:
|
|
||||||
s.raiseSocketError(async = true)
|
|
||||||
result = false
|
|
||||||
{.pop.}
|
|
||||||
|
|
||||||
proc readLine*(s: AsyncSocket, line: var TaintedString): bool =
|
|
||||||
## Behaves similar to ``sockets.readLine``, however it handles non-blocking
|
|
||||||
## sockets properly. This function guarantees that ``line`` is a full line,
|
|
||||||
## if this function can only retrieve some data; it will save this data and
|
|
||||||
## add it to the result when a full line is retrieved, when this happens
|
|
||||||
## False will be returned. True will only be returned if a full line has been
|
|
||||||
## retrieved or the socket has been disconnected in which case ``line`` will
|
|
||||||
## be set to "".
|
|
||||||
##
|
|
||||||
## This function will raise an OSError exception when a socket error occurs.
|
|
||||||
setLen(line.string, 0)
|
|
||||||
var dataReceived = "".TaintedString
|
|
||||||
var ret = s.socket.readLineAsync(dataReceived)
|
|
||||||
case ret
|
|
||||||
of ReadFullLine:
|
|
||||||
if s.lineBuffer.len > 0:
|
|
||||||
string(line).add(s.lineBuffer.string)
|
|
||||||
setLen(s.lineBuffer.string, 0)
|
|
||||||
string(line).add(dataReceived.string)
|
|
||||||
if string(line) == "":
|
|
||||||
line = "\c\L".TaintedString
|
|
||||||
result = true
|
|
||||||
of ReadPartialLine:
|
|
||||||
string(s.lineBuffer).add(dataReceived.string)
|
|
||||||
result = false
|
|
||||||
of ReadNone:
|
|
||||||
result = false
|
|
||||||
of ReadDisconnected:
|
|
||||||
result = true
|
|
||||||
|
|
||||||
proc send*(sock: AsyncSocket, data: string) =
|
|
||||||
## Sends ``data`` to socket ``sock``. This is basically a nicer implementation
|
|
||||||
## of ``sockets.sendAsync``.
|
|
||||||
##
|
|
||||||
## If ``data`` cannot be sent immediately it will be buffered and sent
|
|
||||||
## when ``sock`` becomes writeable (during the ``handleWrite`` event).
|
|
||||||
## It's possible that only a part of ``data`` will be sent immediately, while
|
|
||||||
## the rest of it will be buffered and sent later.
|
|
||||||
if sock.sendBuffer.len != 0:
|
|
||||||
sock.sendBuffer.add(data)
|
|
||||||
return
|
|
||||||
let bytesSent = sock.socket.sendAsync(data)
|
|
||||||
assert bytesSent >= 0
|
|
||||||
if bytesSent == 0:
|
|
||||||
sock.sendBuffer.add(data)
|
|
||||||
sock.deleg.mode = fmReadWrite
|
|
||||||
elif bytesSent != data.len:
|
|
||||||
sock.sendBuffer.add(data[bytesSent .. ^1])
|
|
||||||
sock.deleg.mode = fmReadWrite
|
|
||||||
|
|
||||||
proc timeValFromMilliseconds(timeout = 500): Timeval =
|
|
||||||
if timeout != -1:
|
|
||||||
var seconds = timeout div 1000
|
|
||||||
when defined(posix):
|
|
||||||
result.tv_sec = seconds.Time
|
|
||||||
result.tv_usec = ((timeout - seconds * 1000) * 1000).Suseconds
|
|
||||||
else:
|
|
||||||
result.tv_sec = seconds.int32
|
|
||||||
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
|
||||||
|
|
||||||
proc createFdSet(fd: var TFdSet, s: seq[Delegate], m: var int) =
|
|
||||||
FD_ZERO(fd)
|
|
||||||
for i in items(s):
|
|
||||||
m = max(m, int(i.fd))
|
|
||||||
FD_SET(i.fd, fd)
|
|
||||||
|
|
||||||
proc pruneSocketSet(s: var seq[Delegate], fd: var TFdSet) =
|
|
||||||
var i = 0
|
|
||||||
var L = s.len
|
|
||||||
while i < L:
|
|
||||||
if FD_ISSET(s[i].fd, fd) != 0'i32:
|
|
||||||
s[i] = s[L-1]
|
|
||||||
dec(L)
|
|
||||||
else:
|
|
||||||
inc(i)
|
|
||||||
setLen(s, L)
|
|
||||||
|
|
||||||
proc select(readfds, writefds, exceptfds: var seq[Delegate],
|
|
||||||
timeout = 500): int =
|
|
||||||
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 poll*(d: Dispatcher, timeout: int = 500): bool =
|
|
||||||
## This function checks for events on all the delegates in the `PDispatcher`.
|
|
||||||
## It then proceeds to call the correct event handler.
|
|
||||||
##
|
|
||||||
## This function returns ``True`` if there are file descriptors that are still
|
|
||||||
## open, otherwise ``False``. File descriptors that have been
|
|
||||||
## closed are immediately removed from the dispatcher automatically.
|
|
||||||
##
|
|
||||||
## **Note:** Each delegate has a task associated with it. This gets called
|
|
||||||
## after each select() call, if you set timeout to ``-1`` the tasks will
|
|
||||||
## only be executed after one or more file descriptors becomes readable or
|
|
||||||
## writeable.
|
|
||||||
result = true
|
|
||||||
var readDg, writeDg, errorDg: seq[Delegate] = @[]
|
|
||||||
var len = d.delegates.len
|
|
||||||
var dc = 0
|
|
||||||
|
|
||||||
while dc < len:
|
|
||||||
let deleg = d.delegates[dc]
|
|
||||||
if (deleg.mode != fmWrite or deleg.mode != fmAppend) and deleg.open:
|
|
||||||
readDg.add(deleg)
|
|
||||||
if (deleg.mode != fmRead) and deleg.open:
|
|
||||||
writeDg.add(deleg)
|
|
||||||
if deleg.open:
|
|
||||||
errorDg.add(deleg)
|
|
||||||
inc dc
|
|
||||||
else:
|
|
||||||
# File/socket has been closed. Remove it from dispatcher.
|
|
||||||
d.delegates[dc] = d.delegates[len-1]
|
|
||||||
dec len
|
|
||||||
|
|
||||||
d.delegates.setLen(len)
|
|
||||||
|
|
||||||
var hasDataBufferedCount = 0
|
|
||||||
for d in d.delegates:
|
|
||||||
if d.hasDataBuffered(d.deleVal):
|
|
||||||
hasDataBufferedCount.inc()
|
|
||||||
d.handleRead(d.deleVal)
|
|
||||||
if hasDataBufferedCount > 0: return true
|
|
||||||
|
|
||||||
if readDg.len() == 0 and writeDg.len() == 0:
|
|
||||||
## TODO: Perhaps this shouldn't return if errorDg has something?
|
|
||||||
return false
|
|
||||||
|
|
||||||
if select(readDg, writeDg, errorDg, timeout) != 0:
|
|
||||||
for i in 0..len(d.delegates)-1:
|
|
||||||
if i > len(d.delegates)-1: break # One delegate might've been removed.
|
|
||||||
let deleg = d.delegates[i]
|
|
||||||
if not deleg.open: continue # This delegate might've been closed.
|
|
||||||
if (deleg.mode != fmWrite or deleg.mode != fmAppend) and
|
|
||||||
deleg notin readDg:
|
|
||||||
deleg.handleRead(deleg.deleVal)
|
|
||||||
if (deleg.mode != fmRead) and deleg notin writeDg:
|
|
||||||
deleg.handleWrite(deleg.deleVal)
|
|
||||||
if deleg notin errorDg:
|
|
||||||
deleg.handleError(deleg.deleVal)
|
|
||||||
|
|
||||||
# Execute tasks
|
|
||||||
for i in items(d.delegates):
|
|
||||||
i.task(i.deleVal)
|
|
||||||
|
|
||||||
proc len*(disp: Dispatcher): int =
|
|
||||||
## Retrieves the amount of delegates in ``disp``.
|
|
||||||
return disp.delegates.len
|
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
|
||||||
|
|
||||||
proc testConnect(s: AsyncSocket, no: int) =
|
|
||||||
echo("Connected! " & $no)
|
|
||||||
|
|
||||||
proc testRead(s: AsyncSocket, no: int) =
|
|
||||||
echo("Reading! " & $no)
|
|
||||||
var data = ""
|
|
||||||
if not s.readLine(data): return
|
|
||||||
if data == "":
|
|
||||||
echo("Closing connection. " & $no)
|
|
||||||
s.close()
|
|
||||||
echo(data)
|
|
||||||
echo("Finished reading! " & $no)
|
|
||||||
|
|
||||||
proc testAccept(s: AsyncSocket, disp: Dispatcher, no: int) =
|
|
||||||
echo("Accepting client! " & $no)
|
|
||||||
var client: AsyncSocket
|
|
||||||
new(client)
|
|
||||||
var address = ""
|
|
||||||
s.acceptAddr(client, address)
|
|
||||||
echo("Accepted ", address)
|
|
||||||
client.handleRead =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
testRead(s, 2)
|
|
||||||
disp.register(client)
|
|
||||||
|
|
||||||
proc main =
|
|
||||||
var d = newDispatcher()
|
|
||||||
|
|
||||||
var s = asyncSocket()
|
|
||||||
s.connect("amber.tenthbit.net", Port(6667))
|
|
||||||
s.handleConnect =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
testConnect(s, 1)
|
|
||||||
s.handleRead =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
testRead(s, 1)
|
|
||||||
d.register(s)
|
|
||||||
|
|
||||||
var server = asyncSocket()
|
|
||||||
server.handleAccept =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
testAccept(s, d, 78)
|
|
||||||
server.bindAddr(Port(5555))
|
|
||||||
server.listen()
|
|
||||||
d.register(server)
|
|
||||||
|
|
||||||
while d.poll(-1): discard
|
|
||||||
main()
|
|
||||||
|
|
@ -1,669 +0,0 @@
|
||||||
#
|
|
||||||
#
|
|
||||||
# Nim's Runtime Library
|
|
||||||
# (c) Copyright 2015 Dominik Picheta
|
|
||||||
# See the file "copying.txt", included in this
|
|
||||||
# distribution, for details about the copyright.
|
|
||||||
#
|
|
||||||
|
|
||||||
include "system/inclrtl"
|
|
||||||
|
|
||||||
import sockets, strutils, parseutils, times, os, asyncio
|
|
||||||
|
|
||||||
from asyncnet import nil
|
|
||||||
from nativesockets import nil
|
|
||||||
from asyncdispatch import Future
|
|
||||||
## **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>`_.
|
|
||||||
##
|
|
||||||
## This module provides both a synchronous and asynchronous implementation.
|
|
||||||
## The asynchronous implementation requires you to use the ``asyncFTPClient``
|
|
||||||
## function. You are then required to register the ``AsyncFTPClient`` with a
|
|
||||||
## asyncio dispatcher using the ``register`` function. Take a look at the
|
|
||||||
## asyncio module documentation for more information.
|
|
||||||
##
|
|
||||||
## **Note**: The asynchronous implementation is only asynchronous for long
|
|
||||||
## file transfers, calls to functions which use the command socket will block.
|
|
||||||
##
|
|
||||||
## Here is some example usage of this module:
|
|
||||||
##
|
|
||||||
## .. code-block:: Nim
|
|
||||||
## var ftp = ftpClient("example.org", user = "user", pass = "pass")
|
|
||||||
## ftp.connect()
|
|
||||||
## ftp.retrFile("file.ext", "file.ext")
|
|
||||||
##
|
|
||||||
## **Warning:** The API of this module is unstable, and therefore is subject
|
|
||||||
## to change.
|
|
||||||
|
|
||||||
{.deprecated.}
|
|
||||||
|
|
||||||
type
|
|
||||||
FtpBase*[SockType] = ref FtpBaseObj[SockType]
|
|
||||||
FtpBaseObj*[SockType] = object
|
|
||||||
csock*: SockType
|
|
||||||
dsock*: SockType
|
|
||||||
when SockType is asyncio.AsyncSocket:
|
|
||||||
handleEvent*: proc (ftp: AsyncFTPClient, ev: FTPEvent){.closure,gcsafe.}
|
|
||||||
disp: Dispatcher
|
|
||||||
asyncDSockID: Delegate
|
|
||||||
user*, pass*: string
|
|
||||||
address*: string
|
|
||||||
when SockType is asyncnet.AsyncSocket:
|
|
||||||
port*: nativesockets.Port
|
|
||||||
else:
|
|
||||||
port*: Port
|
|
||||||
|
|
||||||
jobInProgress*: bool
|
|
||||||
job*: FTPJob[SockType]
|
|
||||||
|
|
||||||
dsockConnected*: bool
|
|
||||||
|
|
||||||
FTPJobType* = enum
|
|
||||||
JRetrText, JRetr, JStore
|
|
||||||
|
|
||||||
FtpJob[T] = ref FtpJobObj[T]
|
|
||||||
FTPJobObj[T] = object
|
|
||||||
prc: proc (ftp: FTPBase[T], async: bool): bool {.nimcall, gcsafe.}
|
|
||||||
case typ*: FTPJobType
|
|
||||||
of JRetrText:
|
|
||||||
lines: string
|
|
||||||
of JRetr, JStore:
|
|
||||||
file: File
|
|
||||||
filename: string
|
|
||||||
total: BiggestInt # In bytes.
|
|
||||||
progress: BiggestInt # In bytes.
|
|
||||||
oneSecond: BiggestInt # Bytes transferred in one second.
|
|
||||||
lastProgressReport: float # Time
|
|
||||||
toStore: string # Data left to upload (Only used with async)
|
|
||||||
|
|
||||||
FtpClientObj* = FtpBaseObj[Socket]
|
|
||||||
FtpClient* = ref FtpClientObj
|
|
||||||
|
|
||||||
AsyncFtpClient* = ref AsyncFtpClientObj ## Async alternative to TFTPClient.
|
|
||||||
AsyncFtpClientObj* = FtpBaseObj[asyncio.AsyncSocket]
|
|
||||||
|
|
||||||
FTPEventType* = enum
|
|
||||||
EvTransferProgress, EvLines, EvRetr, EvStore
|
|
||||||
|
|
||||||
FTPEvent* = object ## Event
|
|
||||||
filename*: string
|
|
||||||
case typ*: FTPEventType
|
|
||||||
of EvLines:
|
|
||||||
lines*: string ## Lines that have been transferred.
|
|
||||||
of EvRetr, EvStore: ## Retr/Store operation finished.
|
|
||||||
nil
|
|
||||||
of EvTransferProgress:
|
|
||||||
bytesTotal*: BiggestInt ## Bytes total.
|
|
||||||
bytesFinished*: BiggestInt ## Bytes transferred.
|
|
||||||
speed*: BiggestInt ## Speed in bytes/s
|
|
||||||
currentJob*: FTPJobType ## The current job being performed.
|
|
||||||
|
|
||||||
ReplyError* = object of IOError
|
|
||||||
FTPError* = object of IOError
|
|
||||||
|
|
||||||
|
|
||||||
const multiLineLimit = 10000
|
|
||||||
|
|
||||||
proc ftpClient*(address: string, port = Port(21),
|
|
||||||
user, pass = ""): FtpClient =
|
|
||||||
## Create a ``FtpClient`` object.
|
|
||||||
new(result)
|
|
||||||
result.user = user
|
|
||||||
result.pass = pass
|
|
||||||
result.address = address
|
|
||||||
result.port = port
|
|
||||||
|
|
||||||
result.dsockConnected = false
|
|
||||||
result.csock = socket()
|
|
||||||
if result.csock == invalidSocket: raiseOSError(osLastError())
|
|
||||||
|
|
||||||
template blockingOperation(sock: Socket, body: untyped) =
|
|
||||||
body
|
|
||||||
|
|
||||||
template blockingOperation(sock: asyncio.AsyncSocket, body: untyped) =
|
|
||||||
sock.setBlocking(true)
|
|
||||||
body
|
|
||||||
sock.setBlocking(false)
|
|
||||||
|
|
||||||
proc expectReply[T](ftp: FtpBase[T]): TaintedString =
|
|
||||||
result = TaintedString""
|
|
||||||
blockingOperation(ftp.csock):
|
|
||||||
when T is Socket:
|
|
||||||
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()
|
|
||||||
|
|
||||||
proc assertReply(received: TaintedString, expected: string) =
|
|
||||||
if not received.string.startsWith(expected):
|
|
||||||
raise newException(ReplyError,
|
|
||||||
"Expected reply '$1' got: $2" % [
|
|
||||||
expected, received.string])
|
|
||||||
|
|
||||||
proc assertReply(received: TaintedString, expected: varargs[string]) =
|
|
||||||
for i in items(expected):
|
|
||||||
if received.string.startsWith(i): return
|
|
||||||
raise newException(ReplyError,
|
|
||||||
"Expected reply '$1' got: $2" %
|
|
||||||
[expected.join("' or '"), received.string])
|
|
||||||
|
|
||||||
proc createJob[T](ftp: FtpBase[T],
|
|
||||||
prc: proc (ftp: FtpBase[T], async: bool): bool {.
|
|
||||||
nimcall,gcsafe.},
|
|
||||||
cmd: FTPJobType) =
|
|
||||||
if ftp.jobInProgress:
|
|
||||||
raise newException(FTPError, "Unable to do two jobs at once.")
|
|
||||||
ftp.jobInProgress = true
|
|
||||||
new(ftp.job)
|
|
||||||
ftp.job.prc = prc
|
|
||||||
ftp.job.typ = cmd
|
|
||||||
case cmd
|
|
||||||
of JRetrText:
|
|
||||||
ftp.job.lines = ""
|
|
||||||
of JRetr, JStore:
|
|
||||||
ftp.job.toStore = ""
|
|
||||||
|
|
||||||
proc deleteJob[T](ftp: FtpBase[T]) =
|
|
||||||
assert ftp.jobInProgress
|
|
||||||
ftp.jobInProgress = false
|
|
||||||
case ftp.job.typ
|
|
||||||
of JRetrText:
|
|
||||||
ftp.job.lines = ""
|
|
||||||
of JRetr, JStore:
|
|
||||||
ftp.job.file.close()
|
|
||||||
ftp.dsock.close()
|
|
||||||
|
|
||||||
proc handleTask(s: AsyncSocket, ftp: AsyncFTPClient) =
|
|
||||||
if ftp.jobInProgress:
|
|
||||||
if ftp.job.typ in {JRetr, JStore}:
|
|
||||||
if epochTime() - ftp.job.lastProgressReport >= 1.0:
|
|
||||||
var r: FTPEvent
|
|
||||||
ftp.job.lastProgressReport = epochTime()
|
|
||||||
r.typ = EvTransferProgress
|
|
||||||
r.bytesTotal = ftp.job.total
|
|
||||||
r.bytesFinished = ftp.job.progress
|
|
||||||
r.speed = ftp.job.oneSecond
|
|
||||||
r.filename = ftp.job.filename
|
|
||||||
r.currentJob = ftp.job.typ
|
|
||||||
ftp.job.oneSecond = 0
|
|
||||||
ftp.handleEvent(ftp, r)
|
|
||||||
|
|
||||||
proc handleWrite(s: AsyncSocket, ftp: AsyncFTPClient) =
|
|
||||||
if ftp.jobInProgress:
|
|
||||||
if ftp.job.typ == JStore:
|
|
||||||
assert (not ftp.job.prc(ftp, true))
|
|
||||||
|
|
||||||
proc handleConnect(s: AsyncSocket, ftp: AsyncFTPClient) =
|
|
||||||
ftp.dsockConnected = true
|
|
||||||
assert(ftp.jobInProgress)
|
|
||||||
if ftp.job.typ == JStore:
|
|
||||||
s.setHandleWrite(proc (s: AsyncSocket) = handleWrite(s, ftp))
|
|
||||||
else:
|
|
||||||
s.delHandleWrite()
|
|
||||||
|
|
||||||
proc handleRead(s: AsyncSocket, ftp: AsyncFTPClient) =
|
|
||||||
assert ftp.jobInProgress
|
|
||||||
assert ftp.job.typ != JStore
|
|
||||||
# This can never return true, because it shouldn't check for code
|
|
||||||
# 226 from csock.
|
|
||||||
assert(not ftp.job.prc(ftp, true))
|
|
||||||
|
|
||||||
proc pasv[T](ftp: FtpBase[T]) =
|
|
||||||
## Negotiate a data connection.
|
|
||||||
when T is Socket:
|
|
||||||
ftp.dsock = socket()
|
|
||||||
if ftp.dsock == invalidSocket: raiseOSError(osLastError())
|
|
||||||
elif T is AsyncSocket:
|
|
||||||
ftp.dsock = asyncSocket()
|
|
||||||
ftp.dsock.handleRead =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
handleRead(s, ftp)
|
|
||||||
ftp.dsock.handleConnect =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
handleConnect(s, ftp)
|
|
||||||
ftp.dsock.handleTask =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
handleTask(s, ftp)
|
|
||||||
ftp.disp.register(ftp.dsock)
|
|
||||||
else:
|
|
||||||
{.fatal: "Incorrect socket instantiation".}
|
|
||||||
|
|
||||||
var pasvMsg = ftp.send("PASV").string.strip.TaintedString
|
|
||||||
assertReply(pasvMsg, "227")
|
|
||||||
var betweenParens = captureBetween(pasvMsg.string, '(', ')')
|
|
||||||
var nums = betweenParens.split(',')
|
|
||||||
var ip = nums[0.. ^3]
|
|
||||||
var port = nums[^2.. ^1]
|
|
||||||
var properPort = port[0].parseInt()*256+port[1].parseInt()
|
|
||||||
ftp.dsock.connect(ip.join("."), Port(properPort.toU16))
|
|
||||||
when T is AsyncSocket:
|
|
||||||
ftp.dsockConnected = false
|
|
||||||
else:
|
|
||||||
ftp.dsockConnected = true
|
|
||||||
|
|
||||||
proc normalizePathSep(path: string): string =
|
|
||||||
return replace(path, '\\', '/')
|
|
||||||
|
|
||||||
proc connect*[T](ftp: FtpBase[T]) =
|
|
||||||
## Connect to the FTP server specified by ``ftp``.
|
|
||||||
when T is AsyncSocket:
|
|
||||||
blockingOperation(ftp.csock):
|
|
||||||
ftp.csock.connect(ftp.address, ftp.port)
|
|
||||||
elif T is Socket:
|
|
||||||
ftp.csock.connect(ftp.address, ftp.port)
|
|
||||||
else:
|
|
||||||
{.fatal: "Incorrect socket instantiation".}
|
|
||||||
|
|
||||||
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 != "":
|
|
||||||
assertReply(ftp.send("USER " & ftp.user), "230", "331")
|
|
||||||
|
|
||||||
if ftp.pass != "":
|
|
||||||
assertReply ftp.send("PASS " & ftp.pass), "230"
|
|
||||||
|
|
||||||
proc pwd*[T](ftp: FtpBase[T]): string =
|
|
||||||
## Returns the current working directory.
|
|
||||||
var wd = ftp.send("PWD")
|
|
||||||
assertReply wd, "257"
|
|
||||||
return wd.string.captureBetween('"') # "
|
|
||||||
|
|
||||||
proc cd*[T](ftp: FtpBase[T], dir: string) =
|
|
||||||
## Changes the current directory on the remote FTP server to ``dir``.
|
|
||||||
assertReply ftp.send("CWD " & dir.normalizePathSep), "250"
|
|
||||||
|
|
||||||
proc cdup*[T](ftp: FtpBase[T]) =
|
|
||||||
## Changes the current directory to the parent of the current directory.
|
|
||||||
assertReply ftp.send("CDUP"), "200"
|
|
||||||
|
|
||||||
proc getLines[T](ftp: FtpBase[T], async: bool = false): bool =
|
|
||||||
## Downloads text data in ASCII mode
|
|
||||||
## Returns true if the download is complete.
|
|
||||||
## It doesn't if `async` is true, because it doesn't check for 226 then.
|
|
||||||
if ftp.dsockConnected:
|
|
||||||
var r = TaintedString""
|
|
||||||
when T is AsyncSocket:
|
|
||||||
if ftp.asyncDSock.readLine(r):
|
|
||||||
if r.string == "":
|
|
||||||
ftp.dsockConnected = false
|
|
||||||
else:
|
|
||||||
ftp.job.lines.add(r.string & "\n")
|
|
||||||
elif T is Socket:
|
|
||||||
assert(not async)
|
|
||||||
ftp.dsock.readLine(r)
|
|
||||||
if r.string == "":
|
|
||||||
ftp.dsockConnected = false
|
|
||||||
else:
|
|
||||||
ftp.job.lines.add(r.string & "\n")
|
|
||||||
else:
|
|
||||||
{.fatal: "Incorrect socket instantiation".}
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
var readSocks: seq[Socket] = @[ftp.csock]
|
|
||||||
# This is only needed here. Asyncio gets this socket...
|
|
||||||
blockingOperation(ftp.csock):
|
|
||||||
if readSocks.select(1) != 0 and ftp.csock in readSocks:
|
|
||||||
assertReply ftp.expectReply(), "226"
|
|
||||||
return true
|
|
||||||
|
|
||||||
proc listDirs*[T](ftp: FtpBase[T], dir: string = "",
|
|
||||||
async = false): seq[string] =
|
|
||||||
## Returns a list of filenames in the given directory. If ``dir`` is "",
|
|
||||||
## the current directory is used. If ``async`` is true, this
|
|
||||||
## function will return immediately and it will be your job to
|
|
||||||
## use asyncio's ``poll`` to progress this operation.
|
|
||||||
|
|
||||||
ftp.createJob(getLines[T], JRetrText)
|
|
||||||
ftp.pasv()
|
|
||||||
|
|
||||||
assertReply ftp.send("NLST " & dir.normalizePathSep), ["125", "150"]
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
while not ftp.job.prc(ftp, false): discard
|
|
||||||
result = splitLines(ftp.job.lines)
|
|
||||||
ftp.deleteJob()
|
|
||||||
else: return @[]
|
|
||||||
|
|
||||||
proc fileExists*(ftp: FtpClient, file: string): bool {.deprecated.} =
|
|
||||||
## **Deprecated since version 0.9.0:** Please use ``existsFile``.
|
|
||||||
##
|
|
||||||
## Determines whether ``file`` exists.
|
|
||||||
##
|
|
||||||
## Warning: This function may block. Especially on directories with many
|
|
||||||
## files, because a full list of file names must be retrieved.
|
|
||||||
var files = ftp.listDirs()
|
|
||||||
for f in items(files):
|
|
||||||
if f.normalizePathSep == file.normalizePathSep: return true
|
|
||||||
|
|
||||||
proc existsFile*(ftp: FtpClient, file: string): bool =
|
|
||||||
## Determines whether ``file`` exists.
|
|
||||||
##
|
|
||||||
## Warning: This function may block. Especially on directories with many
|
|
||||||
## files, because a full list of file names must be retrieved.
|
|
||||||
var files = ftp.listDirs()
|
|
||||||
for f in items(files):
|
|
||||||
if f.normalizePathSep == file.normalizePathSep: return true
|
|
||||||
|
|
||||||
proc createDir*[T](ftp: FtpBase[T], dir: string, recursive: bool = false) =
|
|
||||||
## Creates a directory ``dir``. If ``recursive`` is true, the topmost
|
|
||||||
## subdirectory of ``dir`` will be created first, following the secondmost...
|
|
||||||
## etc. this allows you to give a full path as the ``dir`` without worrying
|
|
||||||
## about subdirectories not existing.
|
|
||||||
if not recursive:
|
|
||||||
assertReply ftp.send("MKD " & dir.normalizePathSep), "257"
|
|
||||||
else:
|
|
||||||
var reply = TaintedString""
|
|
||||||
var previousDirs = ""
|
|
||||||
for p in split(dir, {os.DirSep, os.AltSep}):
|
|
||||||
if p != "":
|
|
||||||
previousDirs.add(p)
|
|
||||||
reply = ftp.send("MKD " & previousDirs)
|
|
||||||
previousDirs.add('/')
|
|
||||||
assertReply reply, "257"
|
|
||||||
|
|
||||||
proc chmod*[T](ftp: FtpBase[T], path: string,
|
|
||||||
permissions: set[FilePermission]) =
|
|
||||||
## Changes permission of ``path`` to ``permissions``.
|
|
||||||
var userOctal = 0
|
|
||||||
var groupOctal = 0
|
|
||||||
var otherOctal = 0
|
|
||||||
for i in items(permissions):
|
|
||||||
case i
|
|
||||||
of fpUserExec: userOctal.inc(1)
|
|
||||||
of fpUserWrite: userOctal.inc(2)
|
|
||||||
of fpUserRead: userOctal.inc(4)
|
|
||||||
of fpGroupExec: groupOctal.inc(1)
|
|
||||||
of fpGroupWrite: groupOctal.inc(2)
|
|
||||||
of fpGroupRead: groupOctal.inc(4)
|
|
||||||
of fpOthersExec: otherOctal.inc(1)
|
|
||||||
of fpOthersWrite: otherOctal.inc(2)
|
|
||||||
of fpOthersRead: otherOctal.inc(4)
|
|
||||||
|
|
||||||
var perm = $userOctal & $groupOctal & $otherOctal
|
|
||||||
assertReply ftp.send("SITE CHMOD " & perm &
|
|
||||||
" " & path.normalizePathSep), "200"
|
|
||||||
|
|
||||||
proc list*[T](ftp: FtpBase[T], dir: string = "", async = false): string =
|
|
||||||
## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current
|
|
||||||
## working directory. If ``async`` is true, this function will return
|
|
||||||
## immediately and it will be your job to call asyncio's
|
|
||||||
## ``poll`` to progress this operation.
|
|
||||||
ftp.createJob(getLines[T], JRetrText)
|
|
||||||
ftp.pasv()
|
|
||||||
|
|
||||||
assertReply(ftp.send("LIST" & " " & dir.normalizePathSep), ["125", "150"])
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
while not ftp.job.prc(ftp, false): discard
|
|
||||||
result = ftp.job.lines
|
|
||||||
ftp.deleteJob()
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
proc retrText*[T](ftp: FtpBase[T], file: string, async = false): string =
|
|
||||||
## Retrieves ``file``. File must be ASCII text.
|
|
||||||
## If ``async`` is true, this function will return immediately and
|
|
||||||
## it will be your job to call asyncio's ``poll`` to progress this operation.
|
|
||||||
ftp.createJob(getLines[T], JRetrText)
|
|
||||||
ftp.pasv()
|
|
||||||
assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"]
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
while not ftp.job.prc(ftp, false): discard
|
|
||||||
result = ftp.job.lines
|
|
||||||
ftp.deleteJob()
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
proc getFile[T](ftp: FtpBase[T], async = false): bool =
|
|
||||||
if ftp.dsockConnected:
|
|
||||||
var r = "".TaintedString
|
|
||||||
var bytesRead = 0
|
|
||||||
var returned = false
|
|
||||||
if async:
|
|
||||||
when T is Socket:
|
|
||||||
raise newException(FTPError, "FTPClient must be async.")
|
|
||||||
else:
|
|
||||||
bytesRead = ftp.dsock.recvAsync(r, BufferSize)
|
|
||||||
returned = bytesRead != -1
|
|
||||||
else:
|
|
||||||
bytesRead = ftp.dsock.recv(r, BufferSize)
|
|
||||||
returned = true
|
|
||||||
let r2 = r.string
|
|
||||||
if r2 != "":
|
|
||||||
ftp.job.progress.inc(r2.len)
|
|
||||||
ftp.job.oneSecond.inc(r2.len)
|
|
||||||
ftp.job.file.write(r2)
|
|
||||||
elif returned and r2 == "":
|
|
||||||
ftp.dsockConnected = false
|
|
||||||
|
|
||||||
when T is Socket:
|
|
||||||
if not async:
|
|
||||||
var readSocks: seq[Socket] = @[ftp.csock]
|
|
||||||
blockingOperation(ftp.csock):
|
|
||||||
if readSocks.select(1) != 0 and ftp.csock in readSocks:
|
|
||||||
assertReply ftp.expectReply(), "226"
|
|
||||||
return true
|
|
||||||
|
|
||||||
proc retrFile*[T](ftp: FtpBase[T], file, dest: string, async = false) =
|
|
||||||
## Downloads ``file`` and saves it to ``dest``. Usage of this function
|
|
||||||
## asynchronously is recommended to view the progress of the download.
|
|
||||||
## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
|
|
||||||
## when the download is finished, and the ``filename`` field will be equal
|
|
||||||
## to ``file``.
|
|
||||||
ftp.createJob(getFile[T], JRetr)
|
|
||||||
ftp.job.file = open(dest, mode = fmWrite)
|
|
||||||
ftp.pasv()
|
|
||||||
var reply = ftp.send("RETR " & file.normalizePathSep)
|
|
||||||
assertReply reply, ["125", "150"]
|
|
||||||
if {'(', ')'} notin reply.string:
|
|
||||||
raise newException(ReplyError, "Reply has no file size.")
|
|
||||||
var fileSize: BiggestInt
|
|
||||||
if reply.string.captureBetween('(', ')').parseBiggestInt(fileSize) == 0:
|
|
||||||
raise newException(ReplyError, "Reply has no file size.")
|
|
||||||
|
|
||||||
ftp.job.total = fileSize
|
|
||||||
ftp.job.lastProgressReport = epochTime()
|
|
||||||
ftp.job.filename = file.normalizePathSep
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
while not ftp.job.prc(ftp, false): discard
|
|
||||||
ftp.deleteJob()
|
|
||||||
|
|
||||||
proc doUpload[T](ftp: FtpBase[T], async = false): bool =
|
|
||||||
if ftp.dsockConnected:
|
|
||||||
if ftp.job.toStore.len() > 0:
|
|
||||||
assert(async)
|
|
||||||
let bytesSent = ftp.dsock.sendAsync(ftp.job.toStore)
|
|
||||||
if bytesSent == ftp.job.toStore.len:
|
|
||||||
ftp.job.toStore = ""
|
|
||||||
elif bytesSent != ftp.job.toStore.len and bytesSent != 0:
|
|
||||||
ftp.job.toStore = ftp.job.toStore[bytesSent .. ^1]
|
|
||||||
ftp.job.progress.inc(bytesSent)
|
|
||||||
ftp.job.oneSecond.inc(bytesSent)
|
|
||||||
else:
|
|
||||||
var s = newStringOfCap(4000)
|
|
||||||
var len = ftp.job.file.readBuffer(addr(s[0]), 4000)
|
|
||||||
setLen(s, len)
|
|
||||||
if len == 0:
|
|
||||||
# File finished uploading.
|
|
||||||
ftp.dsock.close()
|
|
||||||
ftp.dsockConnected = false
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
assertReply ftp.expectReply(), "226"
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
ftp.dsock.send(s)
|
|
||||||
else:
|
|
||||||
let bytesSent = ftp.dsock.sendAsync(s)
|
|
||||||
if bytesSent == 0:
|
|
||||||
ftp.job.toStore.add(s)
|
|
||||||
elif bytesSent != s.len:
|
|
||||||
ftp.job.toStore.add(s[bytesSent .. ^1])
|
|
||||||
len = bytesSent
|
|
||||||
|
|
||||||
ftp.job.progress.inc(len)
|
|
||||||
ftp.job.oneSecond.inc(len)
|
|
||||||
|
|
||||||
proc store*[T](ftp: FtpBase[T], file, dest: string, async = false) =
|
|
||||||
## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
|
|
||||||
## function asynchronously is recommended to view the progress of
|
|
||||||
## the download.
|
|
||||||
## The ``EvStore`` event is passed to the specified ``handleEvent`` function
|
|
||||||
## when the upload is finished, and the ``filename`` field will be
|
|
||||||
## equal to ``file``.
|
|
||||||
ftp.createJob(doUpload[T], JStore)
|
|
||||||
ftp.job.file = open(file)
|
|
||||||
ftp.job.total = ftp.job.file.getFileSize()
|
|
||||||
ftp.job.lastProgressReport = epochTime()
|
|
||||||
ftp.job.filename = file
|
|
||||||
ftp.pasv()
|
|
||||||
|
|
||||||
assertReply ftp.send("STOR " & dest.normalizePathSep), ["125", "150"]
|
|
||||||
|
|
||||||
if not async:
|
|
||||||
while not ftp.job.prc(ftp, false): discard
|
|
||||||
ftp.deleteJob()
|
|
||||||
|
|
||||||
proc close*[T](ftp: FtpBase[T]) =
|
|
||||||
## Terminates the connection to the server.
|
|
||||||
assertReply ftp.send("QUIT"), "221"
|
|
||||||
if ftp.jobInProgress: ftp.deleteJob()
|
|
||||||
ftp.csock.close()
|
|
||||||
ftp.dsock.close()
|
|
||||||
|
|
||||||
proc csockHandleRead(s: AsyncSocket, ftp: AsyncFTPClient) =
|
|
||||||
if ftp.jobInProgress:
|
|
||||||
assertReply ftp.expectReply(), "226" # Make sure the transfer completed.
|
|
||||||
var r: FTPEvent
|
|
||||||
case ftp.job.typ
|
|
||||||
of JRetrText:
|
|
||||||
r.typ = EvLines
|
|
||||||
r.lines = ftp.job.lines
|
|
||||||
of JRetr:
|
|
||||||
r.typ = EvRetr
|
|
||||||
r.filename = ftp.job.filename
|
|
||||||
if ftp.job.progress != ftp.job.total:
|
|
||||||
raise newException(FTPError, "Didn't download full file.")
|
|
||||||
of JStore:
|
|
||||||
r.typ = EvStore
|
|
||||||
r.filename = ftp.job.filename
|
|
||||||
if ftp.job.progress != ftp.job.total:
|
|
||||||
raise newException(FTPError, "Didn't upload full file.")
|
|
||||||
ftp.deleteJob()
|
|
||||||
|
|
||||||
ftp.handleEvent(ftp, r)
|
|
||||||
|
|
||||||
proc asyncFTPClient*(address: string, port = Port(21),
|
|
||||||
user, pass = "",
|
|
||||||
handleEvent: proc (ftp: AsyncFTPClient, ev: FTPEvent) {.closure,gcsafe.} =
|
|
||||||
(proc (ftp: AsyncFTPClient, ev: FTPEvent) = discard)): AsyncFTPClient =
|
|
||||||
## Create a ``AsyncFTPClient`` object.
|
|
||||||
##
|
|
||||||
## Use this if you want to use asyncio's dispatcher.
|
|
||||||
var dres: AsyncFtpClient
|
|
||||||
new(dres)
|
|
||||||
dres.user = user
|
|
||||||
dres.pass = pass
|
|
||||||
dres.address = address
|
|
||||||
dres.port = port
|
|
||||||
dres.dsockConnected = false
|
|
||||||
dres.handleEvent = handleEvent
|
|
||||||
dres.csock = asyncSocket()
|
|
||||||
dres.csock.handleRead =
|
|
||||||
proc (s: AsyncSocket) =
|
|
||||||
csockHandleRead(s, dres)
|
|
||||||
result = dres
|
|
||||||
|
|
||||||
proc register*(d: Dispatcher, ftp: AsyncFTPClient): Delegate {.discardable.} =
|
|
||||||
## Registers ``ftp`` with dispatcher ``d``.
|
|
||||||
ftp.disp = d
|
|
||||||
return ftp.disp.register(ftp.csock)
|
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
|
||||||
proc main =
|
|
||||||
var d = newDispatcher()
|
|
||||||
let hev =
|
|
||||||
proc (ftp: AsyncFTPClient, event: FTPEvent) =
|
|
||||||
case event.typ
|
|
||||||
of EvStore:
|
|
||||||
echo("Upload finished!")
|
|
||||||
ftp.retrFile("payload.jpg", "payload2.jpg", async = true)
|
|
||||||
of EvTransferProgress:
|
|
||||||
var time: int64 = -1
|
|
||||||
if event.speed != 0:
|
|
||||||
time = (event.bytesTotal - event.bytesFinished) div event.speed
|
|
||||||
echo(event.currentJob)
|
|
||||||
echo(event.speed div 1000, " kb/s. - ",
|
|
||||||
event.bytesFinished, "/", event.bytesTotal,
|
|
||||||
" - ", time, " seconds")
|
|
||||||
echo(d.len)
|
|
||||||
of EvRetr:
|
|
||||||
echo("Download finished!")
|
|
||||||
ftp.close()
|
|
||||||
echo d.len
|
|
||||||
else: assert(false)
|
|
||||||
var ftp = asyncFTPClient("example.com", user = "foo", pass = "bar", handleEvent = hev)
|
|
||||||
|
|
||||||
d.register(ftp)
|
|
||||||
d.len.echo()
|
|
||||||
ftp.connect()
|
|
||||||
echo "connected"
|
|
||||||
ftp.store("payload.jpg", "payload.jpg", async = true)
|
|
||||||
d.len.echo()
|
|
||||||
echo "uploading..."
|
|
||||||
while true:
|
|
||||||
if not d.poll(): break
|
|
||||||
main()
|
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
|
||||||
var ftp = ftpClient("example.com", user = "foo", pass = "bar")
|
|
||||||
ftp.connect()
|
|
||||||
echo ftp.pwd()
|
|
||||||
echo ftp.list()
|
|
||||||
echo("uploading")
|
|
||||||
ftp.store("payload.jpg", "payload.jpg", async = false)
|
|
||||||
|
|
||||||
echo("Upload complete")
|
|
||||||
ftp.retrFile("payload.jpg", "payload2.jpg", async = false)
|
|
||||||
|
|
||||||
echo("Download complete")
|
|
||||||
sleep(5000)
|
|
||||||
ftp.close()
|
|
||||||
sleep(200)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -128,7 +128,7 @@ proc getErrInfo(db: var DbConn): tuple[res: int, ss, ne, msg: string] {.
|
||||||
cast[PSQLCHAR](sqlState.addr),
|
cast[PSQLCHAR](sqlState.addr),
|
||||||
cast[PSQLCHAR](nativeErr.addr),
|
cast[PSQLCHAR](nativeErr.addr),
|
||||||
cast[PSQLCHAR](errMsg.addr),
|
cast[PSQLCHAR](errMsg.addr),
|
||||||
511.TSqlSmallInt, retSz.addr.PSQLSMALLINT)
|
511.TSqlSmallInt, retSz.addr)
|
||||||
except:
|
except:
|
||||||
discard
|
discard
|
||||||
return (res.int, $(addr sqlState), $(addr nativeErr), $(addr errMsg))
|
return (res.int, $(addr sqlState), $(addr nativeErr), $(addr errMsg))
|
||||||
|
|
@ -277,14 +277,9 @@ iterator fastRows*(db: var DbConn, query: SqlQuery,
|
||||||
## Rows are retrieved from the server at each iteration.
|
## Rows are retrieved from the server at each iteration.
|
||||||
var
|
var
|
||||||
rowRes: Row
|
rowRes: Row
|
||||||
sz: TSqlSmallInt = 0
|
sz: TSqlInteger = 0
|
||||||
cCnt: TSqlSmallInt = 0.TSqlSmallInt
|
cCnt: TSqlSmallInt = 0
|
||||||
res: TSqlSmallInt = 0.TSqlSmallInt
|
res: TSqlSmallInt = 0
|
||||||
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
|
|
||||||
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
|
|
||||||
# after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
|
|
||||||
# so the values of the temporary variable to store the cCnt.
|
|
||||||
# After every cycle and specified to cCnt. To ensure the traversal of all fields.
|
|
||||||
res = db.prepareFetch(query, args)
|
res = db.prepareFetch(query, args)
|
||||||
if res == SQL_NO_DATA:
|
if res == SQL_NO_DATA:
|
||||||
discard
|
discard
|
||||||
|
|
@ -292,14 +287,13 @@ iterator fastRows*(db: var DbConn, query: SqlQuery,
|
||||||
res = SQLNumResultCols(db.stmt, cCnt)
|
res = SQLNumResultCols(db.stmt, cCnt)
|
||||||
rowRes = newRow(cCnt)
|
rowRes = newRow(cCnt)
|
||||||
rowRes.setLen(max(cCnt,0))
|
rowRes.setLen(max(cCnt,0))
|
||||||
tempcCnt = cCnt
|
|
||||||
while res == SQL_SUCCESS:
|
while res == SQL_SUCCESS:
|
||||||
for colId in 1..cCnt:
|
for colId in 1..cCnt:
|
||||||
buf[0] = '\0'
|
buf[0] = '\0'
|
||||||
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
||||||
cast[cstring](buf.addr), 4095.TSqlSmallInt, sz.addr))
|
cast[cstring](buf.addr), 4095.TSqlSmallInt,
|
||||||
|
sz.addr))
|
||||||
rowRes[colId-1] = $(addr buf)
|
rowRes[colId-1] = $(addr buf)
|
||||||
cCnt = tempcCnt
|
|
||||||
yield rowRes
|
yield rowRes
|
||||||
res = SQLFetch(db.stmt)
|
res = SQLFetch(db.stmt)
|
||||||
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
||||||
|
|
@ -312,14 +306,9 @@ iterator instantRows*(db: var DbConn, query: SqlQuery,
|
||||||
## on demand using []. Returned handle is valid only within the interator body.
|
## on demand using []. Returned handle is valid only within the interator body.
|
||||||
var
|
var
|
||||||
rowRes: Row = @[]
|
rowRes: Row = @[]
|
||||||
sz: TSqlSmallInt = 0
|
sz: TSqlInteger = 0
|
||||||
cCnt: TSqlSmallInt = 0.TSqlSmallInt
|
cCnt: TSqlSmallInt = 0
|
||||||
res: TSqlSmallInt = 0.TSqlSmallInt
|
res: TSqlSmallInt = 0
|
||||||
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
|
|
||||||
# tempcCnt,A field to store the number of temporary variables, for unknown reasons,
|
|
||||||
# after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
|
|
||||||
# so the values of the temporary variable to store the cCnt.
|
|
||||||
# After every cycle and specified to cCnt. To ensure the traversal of all fields.
|
|
||||||
res = db.prepareFetch(query, args)
|
res = db.prepareFetch(query, args)
|
||||||
if res == SQL_NO_DATA:
|
if res == SQL_NO_DATA:
|
||||||
discard
|
discard
|
||||||
|
|
@ -327,14 +316,13 @@ iterator instantRows*(db: var DbConn, query: SqlQuery,
|
||||||
res = SQLNumResultCols(db.stmt, cCnt)
|
res = SQLNumResultCols(db.stmt, cCnt)
|
||||||
rowRes = newRow(cCnt)
|
rowRes = newRow(cCnt)
|
||||||
rowRes.setLen(max(cCnt,0))
|
rowRes.setLen(max(cCnt,0))
|
||||||
tempcCnt = cCnt
|
|
||||||
while res == SQL_SUCCESS:
|
while res == SQL_SUCCESS:
|
||||||
for colId in 1..cCnt:
|
for colId in 1..cCnt:
|
||||||
buf[0] = '\0'
|
buf[0] = '\0'
|
||||||
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
||||||
cast[cstring](buf.addr), 4095.TSqlSmallInt, sz.addr))
|
cast[cstring](buf.addr), 4095.TSqlSmallInt,
|
||||||
|
sz.addr))
|
||||||
rowRes[colId-1] = $(addr buf)
|
rowRes[colId-1] = $(addr buf)
|
||||||
cCnt = tempcCnt
|
|
||||||
yield (row: rowRes, len: cCnt.int)
|
yield (row: rowRes, len: cCnt.int)
|
||||||
res = SQLFetch(db.stmt)
|
res = SQLFetch(db.stmt)
|
||||||
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
||||||
|
|
@ -355,14 +343,9 @@ proc getRow*(db: var DbConn, query: SqlQuery,
|
||||||
## will return a Row with empty strings for each column.
|
## will return a Row with empty strings for each column.
|
||||||
var
|
var
|
||||||
rowRes: Row
|
rowRes: Row
|
||||||
sz: TSqlSmallInt = 0.TSqlSmallInt
|
sz: TSqlInteger = 0
|
||||||
cCnt: TSqlSmallInt = 0.TSqlSmallInt
|
cCnt: TSqlSmallInt = 0
|
||||||
res: TSqlSmallInt = 0.TSqlSmallInt
|
res: TSqlSmallInt = 0
|
||||||
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
|
|
||||||
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
|
|
||||||
## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
|
|
||||||
## so the values of the temporary variable to store the cCnt.
|
|
||||||
## After every cycle and specified to cCnt. To ensure the traversal of all fields.
|
|
||||||
res = db.prepareFetch(query, args)
|
res = db.prepareFetch(query, args)
|
||||||
if res == SQL_NO_DATA:
|
if res == SQL_NO_DATA:
|
||||||
result = @[]
|
result = @[]
|
||||||
|
|
@ -370,13 +353,12 @@ proc getRow*(db: var DbConn, query: SqlQuery,
|
||||||
res = SQLNumResultCols(db.stmt, cCnt)
|
res = SQLNumResultCols(db.stmt, cCnt)
|
||||||
rowRes = newRow(cCnt)
|
rowRes = newRow(cCnt)
|
||||||
rowRes.setLen(max(cCnt,0))
|
rowRes.setLen(max(cCnt,0))
|
||||||
tempcCnt = cCnt
|
|
||||||
for colId in 1..cCnt:
|
for colId in 1..cCnt:
|
||||||
buf[0] = '\0'
|
buf[0] = '\0'
|
||||||
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
||||||
cast[cstring](buf.addr), 4095.TSqlSmallInt, sz.addr))
|
cast[cstring](buf.addr), 4095.TSqlSmallInt,
|
||||||
|
sz.addr))
|
||||||
rowRes[colId-1] = $(addr buf)
|
rowRes[colId-1] = $(addr buf)
|
||||||
cCnt = tempcCnt
|
|
||||||
res = SQLFetch(db.stmt)
|
res = SQLFetch(db.stmt)
|
||||||
result = rowRes
|
result = rowRes
|
||||||
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
properFreeResult(SQL_HANDLE_STMT, db.stmt)
|
||||||
|
|
@ -389,14 +371,9 @@ proc getAllRows*(db: var DbConn, query: SqlQuery,
|
||||||
var
|
var
|
||||||
rows: seq[Row] = @[]
|
rows: seq[Row] = @[]
|
||||||
rowRes: Row
|
rowRes: Row
|
||||||
sz: TSqlSmallInt = 0
|
sz: TSqlInteger = 0
|
||||||
cCnt: TSqlSmallInt = 0.TSqlSmallInt
|
cCnt: TSqlSmallInt = 0
|
||||||
res: TSqlSmallInt = 0.TSqlSmallInt
|
res: TSqlSmallInt = 0
|
||||||
tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled.
|
|
||||||
## tempcCnt,A field to store the number of temporary variables, for unknown reasons,
|
|
||||||
## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0,
|
|
||||||
## so the values of the temporary variable to store the cCnt.
|
|
||||||
## After every cycle and specified to cCnt. To ensure the traversal of all fields.
|
|
||||||
res = db.prepareFetch(query, args)
|
res = db.prepareFetch(query, args)
|
||||||
if res == SQL_NO_DATA:
|
if res == SQL_NO_DATA:
|
||||||
result = @[]
|
result = @[]
|
||||||
|
|
@ -404,14 +381,13 @@ proc getAllRows*(db: var DbConn, query: SqlQuery,
|
||||||
res = SQLNumResultCols(db.stmt, cCnt)
|
res = SQLNumResultCols(db.stmt, cCnt)
|
||||||
rowRes = newRow(cCnt)
|
rowRes = newRow(cCnt)
|
||||||
rowRes.setLen(max(cCnt,0))
|
rowRes.setLen(max(cCnt,0))
|
||||||
tempcCnt = cCnt
|
|
||||||
while res == SQL_SUCCESS:
|
while res == SQL_SUCCESS:
|
||||||
for colId in 1..cCnt:
|
for colId in 1..cCnt:
|
||||||
buf[0] = '\0'
|
buf[0] = '\0'
|
||||||
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
db.sqlCheck(SQLGetData(db.stmt, colId.SqlUSmallInt, SQL_C_CHAR,
|
||||||
cast[cstring](buf.addr), 4095.TSqlSmallInt, sz.addr))
|
cast[cstring](buf.addr), 4095.TSqlSmallInt,
|
||||||
|
sz.addr))
|
||||||
rowRes[colId-1] = $(addr buf)
|
rowRes[colId-1] = $(addr buf)
|
||||||
cCnt = tempcCnt
|
|
||||||
rows.add(rowRes)
|
rows.add(rowRes)
|
||||||
res = SQLFetch(db.stmt)
|
res = SQLFetch(db.stmt)
|
||||||
result = rows
|
result = rows
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,59 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
## This module implements some common generic algorithms.
|
## This module implements some common generic algorithms.
|
||||||
|
##
|
||||||
|
## Basic usage
|
||||||
|
## ===========
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import algorithm
|
||||||
|
##
|
||||||
|
## type People = tuple
|
||||||
|
## year: int
|
||||||
|
## name: string
|
||||||
|
##
|
||||||
|
## var a: seq[People]
|
||||||
|
##
|
||||||
|
## a.add((2000, "John"))
|
||||||
|
## a.add((2005, "Marie"))
|
||||||
|
## a.add((2010, "Jane"))
|
||||||
|
##
|
||||||
|
## # Sorting with default system.cmp
|
||||||
|
## a.sort()
|
||||||
|
## assert a == @[(year: 2000, name: "John"), (year: 2005, name: "Marie"),
|
||||||
|
## (year: 2010, name: "Jane")]
|
||||||
|
##
|
||||||
|
## proc myCmp(x, y: People): int =
|
||||||
|
## if x.name < y.name: -1 else: 1
|
||||||
|
##
|
||||||
|
## # Sorting with custom proc
|
||||||
|
## a.sort(myCmp)
|
||||||
|
## assert a == @[(year: 2010, name: "Jane"), (year: 2000, name: "John"),
|
||||||
|
## (year: 2005, name: "Marie")]
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## See also
|
||||||
|
## ========
|
||||||
|
## * `sequtils module<sequtils.html>`_ for working with the built-in seq type
|
||||||
|
## * `tables module<tables.html>`_ for sorting tables
|
||||||
|
|
||||||
type
|
type
|
||||||
SortOrder* = enum
|
SortOrder* = enum
|
||||||
Descending, Ascending
|
Descending, Ascending
|
||||||
|
|
||||||
proc `*`*(x: int, order: SortOrder): int {.inline.} =
|
proc `*`*(x: int, order: SortOrder): int {.inline.} =
|
||||||
## flips ``x`` if ``order == Descending``.
|
## Flips ``x`` if ``order == Descending``.
|
||||||
## If ``order == Ascending`` then ``x`` is returned.
|
## If ``order == Ascending`` then ``x`` is returned.
|
||||||
##
|
##
|
||||||
## ``x`` is supposed to be the result of a comparator, i.e.
|
## ``x`` is supposed to be the result of a comparator, i.e.
|
||||||
## | ``< 0`` for *less than*,
|
## | ``< 0`` for *less than*,
|
||||||
## | ``== 0`` for *equal*,
|
## | ``== 0`` for *equal*,
|
||||||
## | ``> 0`` for *greater than*.
|
## | ``> 0`` for *greater than*.
|
||||||
|
runnableExamples:
|
||||||
|
assert `*`(-123, Descending) == 123
|
||||||
|
assert `*`(123, Descending) == -123
|
||||||
|
assert `*`(-123, Ascending) == -123
|
||||||
|
assert `*`(123, Ascending) == 123
|
||||||
var y = order.ord - 1
|
var y = order.ord - 1
|
||||||
result = (x xor y) - y
|
result = (x xor y) - y
|
||||||
|
|
||||||
|
|
@ -31,28 +71,44 @@ template fillImpl[T](a: var openArray[T], first, last: int, value: T) =
|
||||||
inc(x)
|
inc(x)
|
||||||
|
|
||||||
proc fill*[T](a: var openArray[T], first, last: Natural, value: T) =
|
proc fill*[T](a: var openArray[T], first, last: Natural, value: T) =
|
||||||
## fills the slice ``a[first..last]`` with ``value``.
|
## Fills the slice ``a[first..last]`` with ``value``.
|
||||||
|
##
|
||||||
|
## If an invalid range is passed, it raises IndexError.
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var a: array[6, int]
|
var a: array[6, int]
|
||||||
a.fill(1, 3, 9)
|
a.fill(1, 3, 9)
|
||||||
doAssert a == [0, 9, 9, 9, 0, 0]
|
assert a == [0, 9, 9, 9, 0, 0]
|
||||||
|
a.fill(3, 5, 7)
|
||||||
|
assert a == [0, 9, 9, 7, 7, 7]
|
||||||
|
doAssertRaises(IndexError, a.fill(1, 7, 9))
|
||||||
fillImpl(a, first, last, value)
|
fillImpl(a, first, last, value)
|
||||||
|
|
||||||
proc fill*[T](a: var openArray[T], value: T) =
|
proc fill*[T](a: var openArray[T], value: T) =
|
||||||
## fills the container ``a`` with ``value``.
|
## Fills the container ``a`` with ``value``.
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var a: array[6, int]
|
var a: array[6, int]
|
||||||
a.fill(9)
|
a.fill(9)
|
||||||
doAssert a == [9, 9, 9, 9, 9, 9]
|
assert a == [9, 9, 9, 9, 9, 9]
|
||||||
|
a.fill(4)
|
||||||
|
assert a == [4, 4, 4, 4, 4, 4]
|
||||||
fillImpl(a, 0, a.high, value)
|
fillImpl(a, 0, a.high, value)
|
||||||
|
|
||||||
|
|
||||||
proc reverse*[T](a: var openArray[T], first, last: Natural) =
|
proc reverse*[T](a: var openArray[T], first, last: Natural) =
|
||||||
## reverses the slice ``a[first..last]``.
|
## Reverses the slice ``a[first..last]``.
|
||||||
|
##
|
||||||
|
## If an invalid range is passed, it raises IndexError.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `reversed proc<#reversed,openArray[T],Natural,int>`_ reverse a slice and returns a ``seq[T]``
|
||||||
|
## * `reversed proc<#reversed,openArray[T]>`_ reverse and returns a ``seq[T]``
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var a = [1, 2, 3, 4, 5, 6]
|
var a = [1, 2, 3, 4, 5, 6]
|
||||||
a.reverse(1, 3)
|
a.reverse(1, 3)
|
||||||
doAssert a == [1, 4, 3, 2, 5, 6]
|
assert a == [1, 4, 3, 2, 5, 6]
|
||||||
|
a.reverse(1, 3)
|
||||||
|
assert a == [1, 2, 3, 4, 5, 6]
|
||||||
|
doAssertRaises(IndexError, a.reverse(1, 7))
|
||||||
var x = first
|
var x = first
|
||||||
var y = last
|
var y = last
|
||||||
while x < y:
|
while x < y:
|
||||||
|
|
@ -61,20 +117,32 @@ proc reverse*[T](a: var openArray[T], first, last: Natural) =
|
||||||
inc(x)
|
inc(x)
|
||||||
|
|
||||||
proc reverse*[T](a: var openArray[T]) =
|
proc reverse*[T](a: var openArray[T]) =
|
||||||
## reverses the contents of the container ``a``.
|
## Reverses the contents of the container ``a``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `reversed proc<#reversed,openArray[T],Natural,int>`_ reverse a slice and returns a ``seq[T]``
|
||||||
|
## * `reversed proc<#reversed,openArray[T]>`_ reverse and returns a ``seq[T]``
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var a = [1, 2, 3, 4, 5, 6]
|
var a = [1, 2, 3, 4, 5, 6]
|
||||||
a.reverse()
|
a.reverse()
|
||||||
doAssert a == [6, 5, 4, 3, 2, 1]
|
assert a == [6, 5, 4, 3, 2, 1]
|
||||||
|
a.reverse()
|
||||||
|
assert a == [1, 2, 3, 4, 5, 6]
|
||||||
reverse(a, 0, max(0, a.high))
|
reverse(a, 0, max(0, a.high))
|
||||||
|
|
||||||
proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] =
|
proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] =
|
||||||
## returns the reverse of the slice ``a[first..last]``.
|
## Returns the reverse of the slice ``a[first..last]``.
|
||||||
|
##
|
||||||
|
## If an invalid range is passed, it raises IndexError.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `reverse proc<#reverse,openArray[T],Natural,Natural>`_ reverse a slice
|
||||||
|
## * `reverse proc<#reverse,openArray[T]>`_
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
let
|
let
|
||||||
a = [1, 2, 3, 4, 5, 6]
|
a = [1, 2, 3, 4, 5, 6]
|
||||||
b = reversed(a, 1, 3)
|
b = a.reversed(1, 3)
|
||||||
doAssert b == @[4, 3, 2]
|
assert b == @[4, 3, 2]
|
||||||
assert last >= first-1
|
assert last >= first-1
|
||||||
var i = last - first
|
var i = last - first
|
||||||
var x = first.int
|
var x = first.int
|
||||||
|
|
@ -85,12 +153,16 @@ proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] =
|
||||||
inc(x)
|
inc(x)
|
||||||
|
|
||||||
proc reversed*[T](a: openArray[T]): seq[T] =
|
proc reversed*[T](a: openArray[T]): seq[T] =
|
||||||
## returns the reverse of the container ``a``.
|
## Returns the reverse of the container ``a``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `reverse proc<#reverse,openArray[T],Natural,Natural>`_ reverse a slice
|
||||||
|
## * `reverse proc<#reverse,openArray[T]>`_
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
let
|
let
|
||||||
a = [1, 2, 3, 4, 5, 6]
|
a = [1, 2, 3, 4, 5, 6]
|
||||||
b = reversed(a)
|
b = reversed(a)
|
||||||
doAssert b == @[6, 5, 4, 3, 2, 1]
|
assert b == @[6, 5, 4, 3, 2, 1]
|
||||||
reversed(a, 0, a.high)
|
reversed(a, 0, a.high)
|
||||||
|
|
||||||
proc binarySearch*[T, K](a: openArray[T], key: K,
|
proc binarySearch*[T, K](a: openArray[T], key: K,
|
||||||
|
|
@ -99,6 +171,9 @@ proc binarySearch*[T, K](a: openArray[T], key: K,
|
||||||
##
|
##
|
||||||
## ``cmp`` is the comparator function to use, the expected return values are
|
## ``cmp`` is the comparator function to use, the expected return values are
|
||||||
## the same as that of system.cmp.
|
## the same as that of system.cmp.
|
||||||
|
runnableExamples:
|
||||||
|
assert binarySearch(["a","b","c","d"], "d", system.cmp[string]) == 3
|
||||||
|
assert binarySearch(["a","b","d","c"], "d", system.cmp[string]) == 2
|
||||||
if a.len == 0:
|
if a.len == 0:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
@ -141,31 +216,41 @@ proc binarySearch*[T, K](a: openArray[T], key: K,
|
||||||
|
|
||||||
proc binarySearch*[T](a: openArray[T], key: T): int =
|
proc binarySearch*[T](a: openArray[T], key: T): int =
|
||||||
## Binary search for ``key`` in ``a``. Returns -1 if not found.
|
## Binary search for ``key`` in ``a``. Returns -1 if not found.
|
||||||
|
runnableExamples:
|
||||||
|
assert binarySearch([0, 1, 2, 3, 4], 4) == 4
|
||||||
|
assert binarySearch([0, 1, 4, 2, 3], 4) == 2
|
||||||
binarySearch(a, key, cmp[T])
|
binarySearch(a, key, cmp[T])
|
||||||
|
|
||||||
proc smartBinarySearch*[T](a: openArray[T], key: T): int {.deprecated.} =
|
proc smartBinarySearch*[T](a: openArray[T], key: T): int {.deprecated.} =
|
||||||
## **Deprecated since version 0.18.1**; Use ``binarySearch`` instead.
|
## **Deprecated since version 0.18.1**; Use `binarySearch proc
|
||||||
|
## <#binarySearch,openArray[T],T>`_ instead.
|
||||||
binarySearch(a, key, cmp[T])
|
binarySearch(a, key, cmp[T])
|
||||||
|
|
||||||
const
|
const
|
||||||
onlySafeCode = true
|
onlySafeCode = true
|
||||||
|
|
||||||
proc lowerBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int =
|
proc lowerBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int =
|
||||||
## returns a position to the first element in the ``a`` that is greater than
|
## Returns a position to the first element in the ``a`` that is greater than
|
||||||
## ``key``, or last if no such element is found.
|
## ``key``, or last if no such element is found.
|
||||||
## In other words if you have a sorted sequence and you call
|
## In other words if you have a sorted sequence and you call
|
||||||
## ``insert(thing, elm, lowerBound(thing, elm))``
|
## ``insert(thing, elm, lowerBound(thing, elm))``
|
||||||
## the sequence will still be sorted.
|
## the sequence will still be sorted.
|
||||||
##
|
##
|
||||||
## The first version uses ``cmp`` to compare the elements.
|
## If an invalid range is passed, it raises IndexError.
|
||||||
|
##
|
||||||
|
## The version uses ``cmp`` to compare the elements.
|
||||||
## The expected return values are the same as that of ``system.cmp``.
|
## The expected return values are the same as that of ``system.cmp``.
|
||||||
## The second version uses the default comparison function ``cmp``.
|
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## **See also:**
|
||||||
##
|
## * `upperBound proc<#upperBound,openArray[T],K,proc(T,K)>`_ sorted by ``cmp`` in the specified order
|
||||||
## var arr = @[1,2,3,5,6,7,8,9]
|
## * `upperBound proc<#upperBound,openArray[T],T>`_
|
||||||
## arr.insert(4, arr.lowerBound(4))
|
runnableExamples:
|
||||||
## # after running the above arr is `[1,2,3,4,5,6,7,8,9]`
|
var arr = @[1,2,3,5,6,7,8,9]
|
||||||
|
assert arr.lowerBound(3, system.cmp[int]) == 2
|
||||||
|
assert arr.lowerBound(4, system.cmp[int]) == 3
|
||||||
|
assert arr.lowerBound(5, system.cmp[int]) == 3
|
||||||
|
arr.insert(4, arr.lowerBound(4, system.cmp[int]))
|
||||||
|
assert arr == [1,2,3,4,5,6,7,8,9]
|
||||||
result = a.low
|
result = a.low
|
||||||
var count = a.high - a.low + 1
|
var count = a.high - a.low + 1
|
||||||
var step, pos: int
|
var step, pos: int
|
||||||
|
|
@ -179,23 +264,40 @@ proc lowerBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.clo
|
||||||
count = step
|
count = step
|
||||||
|
|
||||||
proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
|
proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
|
||||||
|
## Returns a position to the first element in the ``a`` that is greater than
|
||||||
|
## ``key``, or last if no such element is found.
|
||||||
|
## In other words if you have a sorted sequence and you call
|
||||||
|
## ``insert(thing, elm, lowerBound(thing, elm))``
|
||||||
|
## the sequence will still be sorted.
|
||||||
|
##
|
||||||
|
## The version uses the default comparison function ``cmp``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `upperBound proc<#upperBound,openArray[T],K,proc(T,K)>`_ sorted by ``cmp`` in the specified order
|
||||||
|
## * `upperBound proc<#upperBound,openArray[T],T>`_
|
||||||
|
|
||||||
proc upperBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int =
|
proc upperBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int =
|
||||||
## returns a position to the first element in the ``a`` that is not less
|
## Returns a position to the first element in the ``a`` that is not less
|
||||||
## (i.e. greater or equal to) than ``key``, or last if no such element is found.
|
## (i.e. greater or equal to) than ``key``, or last if no such element is found.
|
||||||
## In other words if you have a sorted sequence and you call
|
## In other words if you have a sorted sequence and you call
|
||||||
## ``insert(thing, elm, upperBound(thing, elm))``
|
## ``insert(thing, elm, upperBound(thing, elm))``
|
||||||
## the sequence will still be sorted.
|
## the sequence will still be sorted.
|
||||||
##
|
##
|
||||||
## The first version uses ``cmp`` to compare the elements. The expected
|
## If an invalid range is passed, it raises IndexError.
|
||||||
|
##
|
||||||
|
## The version uses ``cmp`` to compare the elements. The expected
|
||||||
## return values are the same as that of ``system.cmp``.
|
## return values are the same as that of ``system.cmp``.
|
||||||
## The second version uses the default comparison function ``cmp``.
|
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## **See also:**
|
||||||
##
|
## * `lowerBound proc<#lowerBound,openArray[T],K,proc(T,K)>`_ sorted by ``cmp`` in the specified order
|
||||||
## var arr = @[1,2,3,4,6,7,8,9]
|
## * `lowerBound proc<#lowerBound,openArray[T],T>`_
|
||||||
## arr.insert(5, arr.upperBound(4))
|
runnableExamples:
|
||||||
## # after running the above arr is `[1,2,3,4,5,6,7,8,9]`
|
var arr = @[1,2,3,5,6,7,8,9]
|
||||||
|
assert arr.upperBound(2, system.cmp[int]) == 2
|
||||||
|
assert arr.upperBound(3, system.cmp[int]) == 3
|
||||||
|
assert arr.upperBound(4, system.cmp[int]) == 3
|
||||||
|
arr.insert(4, arr.upperBound(3, system.cmp[int]))
|
||||||
|
assert arr == [1,2,3,4,5,6,7,8,9]
|
||||||
result = a.low
|
result = a.low
|
||||||
var count = a.high - a.low + 1
|
var count = a.high - a.low + 1
|
||||||
var step, pos: int
|
var step, pos: int
|
||||||
|
|
@ -209,6 +311,17 @@ proc upperBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.clo
|
||||||
count = step
|
count = step
|
||||||
|
|
||||||
proc upperBound*[T](a: openArray[T], key: T): int = upperBound(a, key, cmp[T])
|
proc upperBound*[T](a: openArray[T], key: T): int = upperBound(a, key, cmp[T])
|
||||||
|
## Returns a position to the first element in the ``a`` that is not less
|
||||||
|
## (i.e. greater or equal to) than ``key``, or last if no such element is found.
|
||||||
|
## In other words if you have a sorted sequence and you call
|
||||||
|
## ``insert(thing, elm, upperBound(thing, elm))``
|
||||||
|
## the sequence will still be sorted.
|
||||||
|
##
|
||||||
|
## The version uses the default comparison function ``cmp``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `lowerBound proc<#lowerBound,openArray[T],K,proc(T,K)>`_ sorted by ``cmp`` in the specified order
|
||||||
|
## * `lowerBound proc<#lowerBound,openArray[T],T>`_
|
||||||
|
|
||||||
template `<-` (a, b) =
|
template `<-` (a, b) =
|
||||||
when false:
|
when false:
|
||||||
|
|
@ -263,6 +376,7 @@ func sort*[T](a: var openArray[T],
|
||||||
## Default Nim sort (an implementation of merge sort). The sorting
|
## Default Nim sort (an implementation of merge sort). The sorting
|
||||||
## is guaranteed to be stable and the worst case is guaranteed to
|
## is guaranteed to be stable and the worst case is guaranteed to
|
||||||
## be O(n log n).
|
## be O(n log n).
|
||||||
|
##
|
||||||
## The current implementation uses an iterative
|
## The current implementation uses an iterative
|
||||||
## mergesort to achieve this. It uses a temporary sequence of
|
## mergesort to achieve this. It uses a temporary sequence of
|
||||||
## length ``a.len div 2``. If you do not wish to provide your own
|
## length ``a.len div 2``. If you do not wish to provide your own
|
||||||
|
|
@ -272,7 +386,6 @@ func sort*[T](a: var openArray[T],
|
||||||
## .. code-block:: nim
|
## .. code-block:: nim
|
||||||
##
|
##
|
||||||
## sort(myIntArray, system.cmp[int])
|
## sort(myIntArray, system.cmp[int])
|
||||||
##
|
|
||||||
## # do not use cmp[string] here as we want to use the specialized
|
## # do not use cmp[string] here as we want to use the specialized
|
||||||
## # overload:
|
## # overload:
|
||||||
## sort(myStrArray, system.cmp)
|
## sort(myStrArray, system.cmp)
|
||||||
|
|
@ -286,6 +399,19 @@ func sort*[T](a: var openArray[T],
|
||||||
## result = cmp(x.surname, y.surname)
|
## result = cmp(x.surname, y.surname)
|
||||||
## if result == 0:
|
## if result == 0:
|
||||||
## result = cmp(x.name, y.name)
|
## result = cmp(x.name, y.name)
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sort proc<#sort,openArray[T]>`_
|
||||||
|
## * `sorted proc<#sorted,openArray[T],proc(T,T)>`_ sorted by ``cmp`` in the specified order
|
||||||
|
## * `sorted proc<#sorted,openArray[T]>`_
|
||||||
|
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||||
|
runnableExamples:
|
||||||
|
var d = ["boo", "fo", "barr", "qux"]
|
||||||
|
proc myCmp(x, y: string): int =
|
||||||
|
if x.len() > y.len() or x.len() == y.len(): 1
|
||||||
|
else: -1
|
||||||
|
sort(d, myCmp)
|
||||||
|
assert d == ["fo", "qux", "boo", "barr"]
|
||||||
var n = a.len
|
var n = a.len
|
||||||
var b: seq[T]
|
var b: seq[T]
|
||||||
newSeq(b, n div 2)
|
newSeq(b, n div 2)
|
||||||
|
|
@ -299,17 +425,30 @@ func sort*[T](a: var openArray[T],
|
||||||
|
|
||||||
proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a, system.cmp[T], order)
|
proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a, system.cmp[T], order)
|
||||||
## Shortcut version of ``sort`` that uses ``system.cmp[T]`` as the comparison function.
|
## Shortcut version of ``sort`` that uses ``system.cmp[T]`` as the comparison function.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sort func<#sort,openArray[T],proc(T,T)>`_
|
||||||
|
## * `sorted proc<#sorted,openArray[T],proc(T,T)>`_ sorted by ``cmp`` in the specified order
|
||||||
|
## * `sorted proc<#sorted,openArray[T]>`_
|
||||||
|
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||||
|
|
||||||
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
|
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
|
||||||
order = SortOrder.Ascending): seq[T] =
|
order = SortOrder.Ascending): seq[T] =
|
||||||
## returns ``a`` sorted by ``cmp`` in the specified ``order``.
|
## Returns ``a`` sorted by ``cmp`` in the specified ``order``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sort func<#sort,openArray[T],proc(T,T)>`_
|
||||||
|
## * `sort proc<#sort,openArray[T]>`_
|
||||||
|
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
let
|
let
|
||||||
a = [2, 3, 1, 5, 4]
|
a = [2, 3, 1, 5, 4]
|
||||||
b = sorted(a, system.cmp)
|
b = sorted(a, system.cmp[int])
|
||||||
c = sorted(a, system.cmp, Descending)
|
c = sorted(a, system.cmp[int], Descending)
|
||||||
doAssert b == @[1, 2, 3, 4, 5]
|
d = sorted(["adam", "dande", "brian", "cat"], system.cmp[string])
|
||||||
doAssert c == @[5, 4, 3, 2, 1]
|
assert b == @[1, 2, 3, 4, 5]
|
||||||
|
assert c == @[5, 4, 3, 2, 1]
|
||||||
|
assert d == @["adam", "brian", "cat", "dande"]
|
||||||
result = newSeq[T](a.len)
|
result = newSeq[T](a.len)
|
||||||
for i in 0 .. a.high:
|
for i in 0 .. a.high:
|
||||||
result[i] = a[i]
|
result[i] = a[i]
|
||||||
|
|
@ -317,33 +456,48 @@ proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
|
||||||
|
|
||||||
proc sorted*[T](a: openArray[T], order = SortOrder.Ascending): seq[T] =
|
proc sorted*[T](a: openArray[T], order = SortOrder.Ascending): seq[T] =
|
||||||
## Shortcut version of ``sorted`` that uses ``system.cmp[T]`` as the comparison function.
|
## Shortcut version of ``sorted`` that uses ``system.cmp[T]`` as the comparison function.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sort func<#sort,openArray[T],proc(T,T)>`_
|
||||||
|
## * `sort proc<#sort,openArray[T]>`_
|
||||||
|
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||||
|
runnableExamples:
|
||||||
|
let
|
||||||
|
a = [2, 3, 1, 5, 4]
|
||||||
|
b = sorted(a)
|
||||||
|
c = sorted(a, Descending)
|
||||||
|
d = sorted(["adam", "dande", "brian", "cat"])
|
||||||
|
assert b == @[1, 2, 3, 4, 5]
|
||||||
|
assert c == @[5, 4, 3, 2, 1]
|
||||||
|
assert d == @["adam", "brian", "cat", "dande"]
|
||||||
sorted[T](a, system.cmp[T], order)
|
sorted[T](a, system.cmp[T], order)
|
||||||
|
|
||||||
template sortedByIt*(seq1, op: untyped): untyped =
|
template sortedByIt*(seq1, op: untyped): untyped =
|
||||||
## Convenience template around the ``sorted`` proc to reduce typing.
|
## Convenience template around the ``sorted`` proc to reduce typing.
|
||||||
##
|
##
|
||||||
## The template injects the ``it`` variable which you can use directly in an
|
## The template injects the ``it`` variable which you can use directly in an
|
||||||
## expression. Example:
|
## expression.
|
||||||
##
|
|
||||||
## .. code-block:: nim
|
|
||||||
##
|
|
||||||
## type Person = tuple[name: string, age: int]
|
|
||||||
## var
|
|
||||||
## p1: Person = (name: "p1", age: 60)
|
|
||||||
## p2: Person = (name: "p2", age: 20)
|
|
||||||
## p3: Person = (name: "p3", age: 30)
|
|
||||||
## p4: Person = (name: "p4", age: 30)
|
|
||||||
## people = @[p1,p2,p4,p3]
|
|
||||||
##
|
|
||||||
## echo people.sortedByIt(it.name)
|
|
||||||
##
|
##
|
||||||
## Because the underlying ``cmp()`` is defined for tuples you can do
|
## Because the underlying ``cmp()`` is defined for tuples you can do
|
||||||
## a nested sort like in the following example:
|
## a nested sort.
|
||||||
##
|
|
||||||
## .. code-block:: nim
|
|
||||||
##
|
|
||||||
## echo people.sortedByIt((it.age, it.name))
|
|
||||||
##
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sort func<#sort,openArray[T],proc(T,T)>`_
|
||||||
|
## * `sort proc<#sort,openArray[T]>`_
|
||||||
|
## * `sorted proc<#sorted,openArray[T],proc(T,T)>`_ sorted by ``cmp`` in the specified order
|
||||||
|
## * `sorted proc<#sorted,openArray[T]>`_
|
||||||
|
runnableExamples:
|
||||||
|
type Person = tuple[name: string, age: int]
|
||||||
|
var
|
||||||
|
p1: Person = (name: "p1", age: 60)
|
||||||
|
p2: Person = (name: "p2", age: 20)
|
||||||
|
p3: Person = (name: "p3", age: 30)
|
||||||
|
p4: Person = (name: "p4", age: 30)
|
||||||
|
people = @[p1,p2,p4,p3]
|
||||||
|
|
||||||
|
assert people.sortedByIt(it.name) == @[(name: "p1", age: 60), (name: "p2", age: 20), (name: "p3", age: 30), (name: "p4", age: 30)]
|
||||||
|
# Nested sort
|
||||||
|
assert people.sortedByIt((it.age, it.name)) == @[(name: "p2", age: 20), (name: "p3", age: 30), (name: "p4", age: 30), (name: "p1", age: 60)]
|
||||||
var result = sorted(seq1, proc(x, y: type(seq1[0])): int =
|
var result = sorted(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
var it {.inject.} = x
|
var it {.inject.} = x
|
||||||
let a = op
|
let a = op
|
||||||
|
|
@ -355,9 +509,25 @@ template sortedByIt*(seq1, op: untyped): untyped =
|
||||||
func isSorted*[T](a: openArray[T],
|
func isSorted*[T](a: openArray[T],
|
||||||
cmp: proc(x, y: T): int {.closure.},
|
cmp: proc(x, y: T): int {.closure.},
|
||||||
order = SortOrder.Ascending): bool =
|
order = SortOrder.Ascending): bool =
|
||||||
## checks to see whether ``a`` is already sorted in ``order``
|
## Checks to see whether ``a`` is already sorted in ``order``
|
||||||
## using ``cmp`` for the comparison. Parameters identical
|
## using ``cmp`` for the comparison. Parameters identical
|
||||||
## to ``sort``.
|
## to ``sort``.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `isSorted proc<#isSorted,openArray[T]>`_
|
||||||
|
runnableExamples:
|
||||||
|
let
|
||||||
|
a = [2, 3, 1, 5, 4]
|
||||||
|
b = [1, 2, 3, 4, 5]
|
||||||
|
c = [5, 4, 3, 2, 1]
|
||||||
|
d = ["adam", "brian", "cat", "dande"]
|
||||||
|
e = ["adam", "dande", "brian", "cat"]
|
||||||
|
assert isSorted(a) == false
|
||||||
|
assert isSorted(b) == true
|
||||||
|
assert isSorted(c) == false
|
||||||
|
assert isSorted(c, Descending) == true
|
||||||
|
assert isSorted(d) == true
|
||||||
|
assert isSorted(e) == false
|
||||||
result = true
|
result = true
|
||||||
for i in 0..<len(a)-1:
|
for i in 0..<len(a)-1:
|
||||||
if cmp(a[i],a[i+1]) * order > 0:
|
if cmp(a[i],a[i+1]) * order > 0:
|
||||||
|
|
@ -365,11 +535,30 @@ func isSorted*[T](a: openArray[T],
|
||||||
|
|
||||||
proc isSorted*[T](a: openarray[T], order = SortOrder.Ascending): bool =
|
proc isSorted*[T](a: openarray[T], order = SortOrder.Ascending): bool =
|
||||||
## Shortcut version of ``isSorted`` that uses ``system.cmp[T]`` as the comparison function.
|
## Shortcut version of ``isSorted`` that uses ``system.cmp[T]`` as the comparison function.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `isSorted func<#isSorted,openArray[T],proc(T,T)>`_
|
||||||
|
runnableExamples:
|
||||||
|
let
|
||||||
|
a = [2, 3, 1, 5, 4]
|
||||||
|
b = [1, 2, 3, 4, 5]
|
||||||
|
c = [5, 4, 3, 2, 1]
|
||||||
|
d = ["adam", "brian", "cat", "dande"]
|
||||||
|
e = ["adam", "dande", "brian", "cat"]
|
||||||
|
assert isSorted(a) == false
|
||||||
|
assert isSorted(b) == true
|
||||||
|
assert isSorted(c) == false
|
||||||
|
assert isSorted(c, Descending) == true
|
||||||
|
assert isSorted(d) == true
|
||||||
|
assert isSorted(e) == false
|
||||||
isSorted(a, system.cmp[T], order)
|
isSorted(a, system.cmp[T], order)
|
||||||
|
|
||||||
proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
|
proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
|
||||||
## produces the Cartesian product of the array. Warning: complexity
|
## Produces the Cartesian product of the array. Warning: complexity
|
||||||
## may explode.
|
## may explode.
|
||||||
|
runnableExamples:
|
||||||
|
assert product(@[@[1], @[2]]) == @[@[1, 2]]
|
||||||
|
assert product(@[@["A", "K"], @["Q"]]) == @[@["K", "Q"], @["A", "Q"]]
|
||||||
result = newSeq[seq[T]]()
|
result = newSeq[seq[T]]()
|
||||||
if x.len == 0:
|
if x.len == 0:
|
||||||
return
|
return
|
||||||
|
|
@ -401,15 +590,26 @@ proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
|
||||||
indexes[index] -= 1
|
indexes[index] -= 1
|
||||||
|
|
||||||
proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} =
|
proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} =
|
||||||
## calculates the next lexicographic permutation, directly modifying ``x``.
|
## Calculates the next lexicographic permutation, directly modifying ``x``.
|
||||||
## The result is whether a permutation happened, otherwise we have reached
|
## The result is whether a permutation happened, otherwise we have reached
|
||||||
## the last-ordered permutation.
|
## the last-ordered permutation.
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## If you start with an unsorted array/seq, the repeated permutations
|
||||||
|
## will **not** give you all permutations but stop with last.
|
||||||
##
|
##
|
||||||
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
## **See also:**
|
||||||
## v.nextPermutation()
|
## * `prevPermutation proc<#prevPermutation,openArray[T]>`_
|
||||||
## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
|
runnableExamples:
|
||||||
|
var v = @[0, 1, 2, 3]
|
||||||
|
assert v.nextPermutation() == true
|
||||||
|
assert v == @[0, 1, 3, 2]
|
||||||
|
assert v.nextPermutation() == true
|
||||||
|
assert v == @[0, 2, 1, 3]
|
||||||
|
assert v.prevPermutation() == true
|
||||||
|
assert v == @[0, 1, 3, 2]
|
||||||
|
v = @[3, 2, 1, 0]
|
||||||
|
assert v.nextPermutation() == false
|
||||||
|
assert v == @[3, 2, 1, 0]
|
||||||
if x.len < 2:
|
if x.len < 2:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
@ -430,15 +630,20 @@ proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} =
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
|
proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
|
||||||
## calculates the previous lexicographic permutation, directly modifying
|
## Calculates the previous lexicographic permutation, directly modifying
|
||||||
## ``x``. The result is whether a permutation happened, otherwise we have
|
## ``x``. The result is whether a permutation happened, otherwise we have
|
||||||
## reached the first-ordered permutation.
|
## reached the first-ordered permutation.
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## **See also:**
|
||||||
##
|
## * `nextPermutation proc<#nextPermutation,openArray[T]>`_
|
||||||
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
|
runnableExamples:
|
||||||
## v.prevPermutation()
|
var v = @[0, 1, 2, 3]
|
||||||
## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
assert v.prevPermutation() == false
|
||||||
|
assert v == @[0, 1, 2, 3]
|
||||||
|
assert v.nextPermutation() == true
|
||||||
|
assert v == @[0, 1, 3, 2]
|
||||||
|
assert v.prevPermutation() == true
|
||||||
|
assert v == @[0, 1, 2, 3]
|
||||||
if x.len < 2:
|
if x.len < 2:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
@ -542,7 +747,7 @@ proc rotatedInternal[T](arg: openarray[T]; first, middle, last: int): seq[T] =
|
||||||
result[i] = arg[i]
|
result[i] = arg[i]
|
||||||
|
|
||||||
proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int): int {.discardable.} =
|
proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int): int {.discardable.} =
|
||||||
## performs a left rotation on a range of elements. If you want to rotate
|
## Performs a left rotation on a range of elements. If you want to rotate
|
||||||
## right, use a negative ``dist``. Specifically, ``rotateLeft`` rotates
|
## right, use a negative ``dist``. Specifically, ``rotateLeft`` rotates
|
||||||
## the elements at ``slice`` by ``dist`` positions.
|
## the elements at ``slice`` by ``dist`` positions.
|
||||||
##
|
##
|
||||||
|
|
@ -553,6 +758,7 @@ proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int):
|
||||||
##
|
##
|
||||||
## Elements outside of ``slice`` will be left unchanged.
|
## Elements outside of ``slice`` will be left unchanged.
|
||||||
## The time complexity is linear to ``slice.b - slice.a + 1``.
|
## The time complexity is linear to ``slice.b - slice.a + 1``.
|
||||||
|
## If an invalid range (``HSlice``) is passed, it raises IndexError.
|
||||||
##
|
##
|
||||||
## ``slice``
|
## ``slice``
|
||||||
## The indices of the element range that should be rotated.
|
## The indices of the element range that should be rotated.
|
||||||
|
|
@ -561,11 +767,18 @@ proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int):
|
||||||
## The distance in amount of elements that the data should be rotated.
|
## The distance in amount of elements that the data should be rotated.
|
||||||
## Can be negative, can be any number.
|
## Can be negative, can be any number.
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## **See also:**
|
||||||
##
|
## * `rotateLeft proc<#rotateLeft,openArray[T],int>`_ for a version which rotates the whole container
|
||||||
## var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
## * `rotatedLeft proc<#rotatedLeft,openArray[T],HSlice[int,int],int>`_ for a version which returns a ``seq[T]``
|
||||||
## list.rotateLeft(1 .. 8, 3)
|
runnableExamples:
|
||||||
## doAssert list == [0, 4, 5, 6, 7, 8, 1, 2, 3, 9, 10]
|
var a = [0, 1, 2, 3, 4, 5]
|
||||||
|
a.rotateLeft(1 .. 4, 3)
|
||||||
|
assert a == [0, 4, 1, 2, 3, 5]
|
||||||
|
a.rotateLeft(1 .. 4, 3)
|
||||||
|
assert a == [0, 3, 4, 1, 2, 5]
|
||||||
|
a.rotateLeft(1 .. 4, -3)
|
||||||
|
assert a == [0, 4, 1, 2, 3, 5]
|
||||||
|
doAssertRaises(IndexError, a.rotateLeft(1 .. 7, 2))
|
||||||
let sliceLen = slice.b + 1 - slice.a
|
let sliceLen = slice.b + 1 - slice.a
|
||||||
let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen
|
let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen
|
||||||
arg.rotateInternal(slice.a, slice.a+distLeft, slice.b + 1)
|
arg.rotateInternal(slice.a, slice.a+distLeft, slice.b + 1)
|
||||||
|
|
@ -573,10 +786,18 @@ proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int):
|
||||||
proc rotateLeft*[T](arg: var openarray[T]; dist: int): int {.discardable.} =
|
proc rotateLeft*[T](arg: var openarray[T]; dist: int): int {.discardable.} =
|
||||||
## Default arguments for slice, so that this procedure operates on the entire
|
## Default arguments for slice, so that this procedure operates on the entire
|
||||||
## ``arg``, and not just on a part of it.
|
## ``arg``, and not just on a part of it.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `rotateLeft proc<#rotateLeft,openArray[T],HSlice[int,int],int>`_ for a version which rotates a range
|
||||||
|
## * `rotatedLeft proc<#rotatedLeft,openArray[T],int>`_ for a version which returns a ``seq[T]``
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var a = [1, 2, 3, 4, 5]
|
var a = [1, 2, 3, 4, 5]
|
||||||
a.rotateLeft(2)
|
a.rotateLeft(2)
|
||||||
doAssert a == [3, 4, 5, 1, 2]
|
assert a == [3, 4, 5, 1, 2]
|
||||||
|
a.rotateLeft(4)
|
||||||
|
assert a == [2, 3, 4, 5, 1]
|
||||||
|
a.rotateLeft(-6)
|
||||||
|
assert a == [1, 2, 3, 4, 5]
|
||||||
let arglen = arg.len
|
let arglen = arg.len
|
||||||
let distLeft = ((dist mod arglen) + arglen) mod arglen
|
let distLeft = ((dist mod arglen) + arglen) mod arglen
|
||||||
arg.rotateInternal(0, distLeft, arglen)
|
arg.rotateInternal(0, distLeft, arglen)
|
||||||
|
|
@ -584,6 +805,28 @@ proc rotateLeft*[T](arg: var openarray[T]; dist: int): int {.discardable.} =
|
||||||
proc rotatedLeft*[T](arg: openarray[T]; slice: HSlice[int, int], dist: int): seq[T] =
|
proc rotatedLeft*[T](arg: openarray[T]; slice: HSlice[int, int], dist: int): seq[T] =
|
||||||
## Same as ``rotateLeft``, just with the difference that it does
|
## Same as ``rotateLeft``, just with the difference that it does
|
||||||
## not modify the argument. It creates a new ``seq`` instead.
|
## not modify the argument. It creates a new ``seq`` instead.
|
||||||
|
##
|
||||||
|
## Elements outside of ``slice`` will be left unchanged.
|
||||||
|
## If an invalid range (``HSlice``) is passed, it raises IndexError.
|
||||||
|
##
|
||||||
|
## ``slice``
|
||||||
|
## The indices of the element range that should be rotated.
|
||||||
|
##
|
||||||
|
## ``dist``
|
||||||
|
## The distance in amount of elements that the data should be rotated.
|
||||||
|
## Can be negative, can be any number.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `rotateLeft proc<#rotateLeft,openArray[T],HSlice[int,int],int>`_ for the in-place version of this proc
|
||||||
|
## * `rotatedLeft proc<#rotatedLeft,openArray[T],int>`_ for a version which rotates the whole container
|
||||||
|
runnableExamples:
|
||||||
|
var a = @[1, 2, 3, 4, 5]
|
||||||
|
a = rotatedLeft(a, 1 .. 4, 3)
|
||||||
|
assert a == @[1, 5, 2, 3, 4]
|
||||||
|
a = rotatedLeft(a, 1 .. 3, 2)
|
||||||
|
assert a == @[1, 3, 5, 2, 4]
|
||||||
|
a = rotatedLeft(a, 1 .. 3, -2)
|
||||||
|
assert a == @[1, 5, 2, 3, 4]
|
||||||
let sliceLen = slice.b + 1 - slice.a
|
let sliceLen = slice.b + 1 - slice.a
|
||||||
let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen
|
let distLeft = ((dist mod sliceLen) + sliceLen) mod sliceLen
|
||||||
arg.rotatedInternal(slice.a, slice.a+distLeft, slice.b+1)
|
arg.rotatedInternal(slice.a, slice.a+distLeft, slice.b+1)
|
||||||
|
|
@ -591,6 +834,18 @@ proc rotatedLeft*[T](arg: openarray[T]; slice: HSlice[int, int], dist: int): seq
|
||||||
proc rotatedLeft*[T](arg: openarray[T]; dist: int): seq[T] =
|
proc rotatedLeft*[T](arg: openarray[T]; dist: int): seq[T] =
|
||||||
## Same as ``rotateLeft``, just with the difference that it does
|
## Same as ``rotateLeft``, just with the difference that it does
|
||||||
## not modify the argument. It creates a new ``seq`` instead.
|
## not modify the argument. It creates a new ``seq`` instead.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `rotateLeft proc<#rotateLeft,openArray[T],int>`_ for the in-place version of this proc
|
||||||
|
## * `rotatedLeft proc<#rotatedLeft,openArray[T],HSlice[int,int],int>`_ for a version which rotates a range
|
||||||
|
runnableExamples:
|
||||||
|
var a = @[1, 2, 3, 4, 5]
|
||||||
|
a = rotatedLeft(a, 2)
|
||||||
|
assert a == @[3, 4, 5, 1, 2]
|
||||||
|
a = rotatedLeft(a, 4)
|
||||||
|
assert a == @[2, 3, 4, 5, 1]
|
||||||
|
a = rotatedLeft(a, -6)
|
||||||
|
assert a == @[1, 2, 3, 4, 5]
|
||||||
let arglen = arg.len
|
let arglen = arg.len
|
||||||
let distLeft = ((dist mod arglen) + arglen) mod arglen
|
let distLeft = ((dist mod arglen) + arglen) mod arglen
|
||||||
arg.rotatedInternal(0, distLeft, arg.len)
|
arg.rotatedInternal(0, distLeft, arg.len)
|
||||||
|
|
|
||||||
|
|
@ -1674,7 +1674,7 @@ template asyncAddrInfoLoop(addrInfo: ptr AddrInfo, fd: untyped,
|
||||||
curFd = fdPerDomain[ord(domain)]
|
curFd = fdPerDomain[ord(domain)]
|
||||||
if curFd == osInvalidSocket.AsyncFD:
|
if curFd == osInvalidSocket.AsyncFD:
|
||||||
try:
|
try:
|
||||||
curFd = newAsyncNativeSocket(domain, sockType, protocol)
|
curFd = createAsyncNativeSocket(domain, sockType, protocol)
|
||||||
except:
|
except:
|
||||||
freeAddrInfo(addrInfo)
|
freeAddrInfo(addrInfo)
|
||||||
closeUnusedFds()
|
closeUnusedFds()
|
||||||
|
|
@ -1806,47 +1806,6 @@ proc readAll*(future: FutureStream[string]): Future[string] {.async.} =
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} =
|
|
||||||
## Reads a line of data from ``socket``. Returned future will complete once
|
|
||||||
## a full line is read or an error occurs.
|
|
||||||
##
|
|
||||||
## 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 ``""``.
|
|
||||||
##
|
|
||||||
## If the socket is disconnected in the middle of a line (before ``\r\L``
|
|
||||||
## is read) then line will be set to ``""``.
|
|
||||||
## The partial line **will be lost**.
|
|
||||||
##
|
|
||||||
## **Warning**: This assumes that lines are delimited by ``\r\L``.
|
|
||||||
##
|
|
||||||
## **Note**: This procedure is mostly used for testing. You likely want to
|
|
||||||
## use ``asyncnet.recvLine`` instead.
|
|
||||||
##
|
|
||||||
## **Deprecated since version 0.15.0**: Use ``asyncnet.recvLine()`` instead.
|
|
||||||
|
|
||||||
template addNLIfEmpty(): typed =
|
|
||||||
if result.len == 0:
|
|
||||||
result.add("\c\L")
|
|
||||||
|
|
||||||
result = ""
|
|
||||||
var c = ""
|
|
||||||
while true:
|
|
||||||
c = await recv(socket, 1)
|
|
||||||
if c.len == 0:
|
|
||||||
return ""
|
|
||||||
if c == "\r":
|
|
||||||
c = await recv(socket, 1)
|
|
||||||
assert c == "\l"
|
|
||||||
addNLIfEmpty()
|
|
||||||
return
|
|
||||||
elif c == "\L":
|
|
||||||
addNLIfEmpty()
|
|
||||||
return
|
|
||||||
add(result, c)
|
|
||||||
|
|
||||||
proc callSoon*(cbproc: proc ()) =
|
proc callSoon*(cbproc: proc ()) =
|
||||||
## Schedule `cbproc` to be called as soon as possible.
|
## Schedule `cbproc` to be called as soon as possible.
|
||||||
## The callback is called when control returns to the event loop.
|
## The callback is called when control returns to the event loop.
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,54 @@
|
||||||
## waitFor(main())
|
## waitFor(main())
|
||||||
|
|
||||||
|
|
||||||
import asyncdispatch, asyncnet, strutils, parseutils, os, times
|
import asyncdispatch, asyncnet, nativesockets, strutils, parseutils, os, times
|
||||||
|
|
||||||
from ftpclient import FtpBaseObj, ReplyError, FtpEvent
|
|
||||||
from net import BufferSize
|
from net import BufferSize
|
||||||
|
|
||||||
type
|
type
|
||||||
AsyncFtpClientObj* = FtpBaseObj[AsyncSocket]
|
AsyncFtpClient* = ref object
|
||||||
AsyncFtpClient* = ref AsyncFtpClientObj
|
csock*: AsyncSocket
|
||||||
|
dsock*: AsyncSocket
|
||||||
|
user*, pass*: string
|
||||||
|
address*: string
|
||||||
|
port*: Port
|
||||||
|
jobInProgress*: bool
|
||||||
|
job*: FTPJob
|
||||||
|
dsockConnected*: bool
|
||||||
|
|
||||||
|
FTPJobType* = enum
|
||||||
|
JRetrText, JRetr, JStore
|
||||||
|
|
||||||
|
FtpJob = ref object
|
||||||
|
prc: proc (ftp: AsyncFtpClient, async: bool): bool {.nimcall, gcsafe.}
|
||||||
|
case typ*: FTPJobType
|
||||||
|
of JRetrText:
|
||||||
|
lines: string
|
||||||
|
of JRetr, JStore:
|
||||||
|
file: File
|
||||||
|
filename: string
|
||||||
|
total: BiggestInt # In bytes.
|
||||||
|
progress: BiggestInt # In bytes.
|
||||||
|
oneSecond: BiggestInt # Bytes transferred in one second.
|
||||||
|
lastProgressReport: float # Time
|
||||||
|
toStore: string # Data left to upload (Only used with async)
|
||||||
|
|
||||||
|
FTPEventType* = enum
|
||||||
|
EvTransferProgress, EvLines, EvRetr, EvStore
|
||||||
|
|
||||||
|
FTPEvent* = object ## Event
|
||||||
|
filename*: string
|
||||||
|
case typ*: FTPEventType
|
||||||
|
of EvLines:
|
||||||
|
lines*: string ## Lines that have been transferred.
|
||||||
|
of EvRetr, EvStore: ## Retr/Store operation finished.
|
||||||
|
nil
|
||||||
|
of EvTransferProgress:
|
||||||
|
bytesTotal*: BiggestInt ## Bytes total.
|
||||||
|
bytesFinished*: BiggestInt ## Bytes transferred.
|
||||||
|
speed*: BiggestInt ## Speed in bytes/s
|
||||||
|
currentJob*: FTPJobType ## The current job being performed.
|
||||||
|
|
||||||
|
ReplyError* = object of IOError
|
||||||
|
|
||||||
ProgressChangedProc* =
|
ProgressChangedProc* =
|
||||||
proc (total, progress: BiggestInt, speed: float):
|
proc (total, progress: BiggestInt, speed: float):
|
||||||
|
|
@ -183,7 +223,7 @@ proc listDirs*(ftp: AsyncFtpClient, dir = ""): Future[seq[string]] {.async.} =
|
||||||
## Returns a list of filenames in the given directory. If ``dir`` is "",
|
## Returns a list of filenames in the given directory. If ``dir`` is "",
|
||||||
## the current directory is used. If ``async`` is true, this
|
## the current directory is used. If ``async`` is true, this
|
||||||
## function will return immediately and it will be your job to
|
## function will return immediately and it will be your job to
|
||||||
## use asyncio's ``poll`` to progress this operation.
|
## use asyncdispatch's ``poll`` to progress this operation.
|
||||||
await ftp.pasv()
|
await ftp.pasv()
|
||||||
|
|
||||||
assertReply(await(ftp.send("NLST " & dir.normalizePathSep)), ["125", "150"])
|
assertReply(await(ftp.send("NLST " & dir.normalizePathSep)), ["125", "150"])
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,12 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
|
|
||||||
var outerProcBody = newNimNode(nnkStmtList, prc.body)
|
var outerProcBody = newNimNode(nnkStmtList, prc.body)
|
||||||
|
|
||||||
|
# Extract the documentation comment from the original procedure declaration.
|
||||||
|
# Note that we're not removing it from the body in order not to make this
|
||||||
|
# transformation even more complex.
|
||||||
|
if prc.body.len > 1 and prc.body[0].kind == nnkCommentStmt:
|
||||||
|
outerProcBody.add(prc.body[0])
|
||||||
|
|
||||||
# -> var retFuture = newFuture[T]()
|
# -> var retFuture = newFuture[T]()
|
||||||
var retFutureSym = genSym(nskVar, "retFuture")
|
var retFutureSym = genSym(nskVar, "retFuture")
|
||||||
var subRetType =
|
var subRetType =
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
## This module implements a series of low level methods for bit manipulation.
|
## This module implements a series of low level methods for bit manipulation.
|
||||||
## By default, this module use compiler intrinsics to improve performance
|
|
||||||
|
## By default, this module use compiler intrinsics where possible to improve performance
|
||||||
## on supported compilers: ``GCC``, ``LLVM_GCC``, ``CLANG``, ``VCC``, ``ICC``.
|
## on supported compilers: ``GCC``, ``LLVM_GCC``, ``CLANG``, ``VCC``, ``ICC``.
|
||||||
##
|
##
|
||||||
## The module will fallback to pure nim procs incase the backend is not supported.
|
## The module will fallback to pure nim procs incase the backend is not supported.
|
||||||
|
|
@ -32,6 +33,63 @@ const useICC_builtins = defined(icc) and useBuiltins
|
||||||
const useVCC_builtins = defined(vcc) and useBuiltins
|
const useVCC_builtins = defined(vcc) and useBuiltins
|
||||||
const arch64 = sizeof(int) == 8
|
const arch64 = sizeof(int) == 8
|
||||||
|
|
||||||
|
when defined(nimHasalignOf):
|
||||||
|
|
||||||
|
import macros
|
||||||
|
|
||||||
|
type BitsRange*[T] = range[0..sizeof(T)*8-1]
|
||||||
|
## Returns a range with all bit positions for type ``T``
|
||||||
|
|
||||||
|
proc setMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
|
||||||
|
## Returns ``v``, with all the ``1`` bits from ``mask`` set to 1
|
||||||
|
v = v or mask
|
||||||
|
|
||||||
|
proc clearMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
|
||||||
|
## Returns ``v``, with all the ``1`` bits from ``mask`` set to 0
|
||||||
|
v = v and not mask
|
||||||
|
|
||||||
|
proc flipMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
|
||||||
|
## Returns ``v``, with all the ``1`` bits from ``mask`` flipped
|
||||||
|
v = v xor mask
|
||||||
|
|
||||||
|
proc setBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
|
||||||
|
## Returns ``v``, with the bit at position ``bit`` set to 1
|
||||||
|
v.setMask(1.T shl bit)
|
||||||
|
|
||||||
|
proc clearBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
|
||||||
|
## Returns ``v``, with the bit at position ``bit`` set to 0
|
||||||
|
v.clearMask(1.T shl bit)
|
||||||
|
|
||||||
|
proc flipBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
|
||||||
|
## Returns ``v``, with the bit at position ``bit`` flipped
|
||||||
|
v.flipMask(1.T shl bit)
|
||||||
|
|
||||||
|
macro setBits*(v: typed, bits: varargs[typed]): untyped =
|
||||||
|
## Returns ``v``, with the bits at positions ``bits`` set to 1
|
||||||
|
bits.expectKind(nnkBracket)
|
||||||
|
result = newStmtList()
|
||||||
|
for bit in bits:
|
||||||
|
result.add newCall("setBit", v, bit)
|
||||||
|
|
||||||
|
macro clearBits*(v: typed, bits: varargs[typed]): untyped =
|
||||||
|
## Returns ``v``, with the bits at positions ``bits`` set to 0
|
||||||
|
bits.expectKind(nnkBracket)
|
||||||
|
result = newStmtList()
|
||||||
|
for bit in bits:
|
||||||
|
result.add newCall("clearBit", v, bit)
|
||||||
|
|
||||||
|
macro flipBits*(v: typed, bits: varargs[typed]): untyped =
|
||||||
|
## Returns ``v``, with the bits at positions ``bits`` set to 0
|
||||||
|
bits.expectKind(nnkBracket)
|
||||||
|
result = newStmtList()
|
||||||
|
for bit in bits:
|
||||||
|
result.add newCall("flipBit", v, bit)
|
||||||
|
|
||||||
|
proc testBit*[T: SomeInteger](v: var T, bit: BitsRange[T]): bool {.inline.} =
|
||||||
|
## Returns true if the bit in ``v`` at positions ``bit`` is set to 1
|
||||||
|
let mask = 1.T shl bit
|
||||||
|
return (v and mask) == mask
|
||||||
|
|
||||||
# #### Pure Nim version ####
|
# #### Pure Nim version ####
|
||||||
|
|
||||||
proc firstSetBit_nim(x: uint32): int {.inline, nosideeffect.} =
|
proc firstSetBit_nim(x: uint32): int {.inline, nosideeffect.} =
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Nim's Runtime Library
|
# Nim's Runtime Library
|
||||||
|
|
@ -7,32 +6,74 @@
|
||||||
# See the file "copying.txt", included in this
|
# See the file "copying.txt", included in this
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
|
|
||||||
##[ Heap queue algorithm (a.k.a. priority queue). Ported from Python heapq.
|
##[
|
||||||
|
The `heapqueue` module implements a
|
||||||
|
`heap data structure<https://en.wikipedia.org/wiki/Heap_(data_structure)>`_
|
||||||
|
that can be used as a
|
||||||
|
`priority queue<https://en.wikipedia.org/wiki/Priority_queue>`_.
|
||||||
|
Heaps are arrays for which `a[k] <= a[2*k+1]` and `a[k] <= a[2*k+2]` for
|
||||||
|
all `k`, counting elements from 0. The interesting property of a heap is that
|
||||||
|
`a[0]` is always its smallest element.
|
||||||
|
|
||||||
Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
|
Basic usage
|
||||||
all k, counting elements from 0. For the sake of comparison,
|
-----------
|
||||||
non-existing elements are considered to be infinite. The interesting
|
.. code-block:: Nim
|
||||||
property of a heap is that a[0] is always its smallest element.
|
import heapqueue
|
||||||
|
|
||||||
|
var heap = initHeapQueue[int]()
|
||||||
|
heap.push(8)
|
||||||
|
heap.push(2)
|
||||||
|
heap.push(5)
|
||||||
|
# The first element is the lowest element
|
||||||
|
assert heap[0] == 2
|
||||||
|
# Remove and return the lowest element
|
||||||
|
assert heap.pop() == 2
|
||||||
|
# The lowest element remaining is 5
|
||||||
|
assert heap[0] == 5
|
||||||
|
|
||||||
|
Usage with custom object
|
||||||
|
------------------------
|
||||||
|
To use a `HeapQueue` with a custom object, the `<` operator must be
|
||||||
|
implemented.
|
||||||
|
|
||||||
|
.. code-block:: Nim
|
||||||
|
import heapqueue
|
||||||
|
|
||||||
|
type Job = object
|
||||||
|
priority: int
|
||||||
|
|
||||||
|
proc `<`(a, b: Job): bool = a.priority < b.priority
|
||||||
|
|
||||||
|
var jobs = initHeapQueue[Job]()
|
||||||
|
jobs.push(Job(priority: 1))
|
||||||
|
jobs.push(Job(priority: 2))
|
||||||
|
|
||||||
|
assert jobs[0].priority == 1
|
||||||
]##
|
]##
|
||||||
|
|
||||||
type HeapQueue*[T] = distinct seq[T]
|
type HeapQueue*[T] = object
|
||||||
|
## A heap queue, commonly known as a priority queue.
|
||||||
|
data: seq[T]
|
||||||
|
|
||||||
proc newHeapQueue*[T](): HeapQueue[T] {.inline.} = HeapQueue[T](newSeq[T]())
|
proc initHeapQueue*[T](): HeapQueue[T] =
|
||||||
proc newHeapQueue*[T](h: var HeapQueue[T]) {.inline.} = h = HeapQueue[T](newSeq[T]())
|
## Create a new empty heap.
|
||||||
|
discard
|
||||||
|
|
||||||
proc len*[T](h: HeapQueue[T]): int {.inline.} = seq[T](h).len
|
proc len*[T](heap: HeapQueue[T]): int {.inline.} =
|
||||||
proc `[]`*[T](h: HeapQueue[T], i: int): T {.inline.} = seq[T](h)[i]
|
## Return the number of elements of `heap`.
|
||||||
proc `[]=`[T](h: var HeapQueue[T], i: int, v: T) {.inline.} = seq[T](h)[i] = v
|
heap.data.len
|
||||||
proc add[T](h: var HeapQueue[T], v: T) {.inline.} = seq[T](h).add(v)
|
|
||||||
|
proc `[]`*[T](heap: HeapQueue[T], i: Natural): T {.inline.} =
|
||||||
|
## Access the i-th element of `heap`.
|
||||||
|
heap.data[i]
|
||||||
|
|
||||||
proc heapCmp[T](x, y: T): bool {.inline.} =
|
proc heapCmp[T](x, y: T): bool {.inline.} =
|
||||||
return (x < y)
|
return (x < y)
|
||||||
|
|
||||||
# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos
|
|
||||||
# is the index of a leaf with a possibly out-of-order value. Restore the
|
|
||||||
# heap invariant.
|
|
||||||
proc siftdown[T](heap: var HeapQueue[T], startpos, p: int) =
|
proc siftdown[T](heap: var HeapQueue[T], startpos, p: int) =
|
||||||
|
## 'heap' is a heap at all indices >= startpos, except possibly for pos. pos
|
||||||
|
## is the index of a leaf with a possibly out-of-order value. Restore the
|
||||||
|
## heap invariant.
|
||||||
var pos = p
|
var pos = p
|
||||||
var newitem = heap[pos]
|
var newitem = heap[pos]
|
||||||
# Follow the path to the root, moving parents down until finding a place
|
# Follow the path to the root, moving parents down until finding a place
|
||||||
|
|
@ -41,11 +82,11 @@ proc siftdown[T](heap: var HeapQueue[T], startpos, p: int) =
|
||||||
let parentpos = (pos - 1) shr 1
|
let parentpos = (pos - 1) shr 1
|
||||||
let parent = heap[parentpos]
|
let parent = heap[parentpos]
|
||||||
if heapCmp(newitem, parent):
|
if heapCmp(newitem, parent):
|
||||||
heap[pos] = parent
|
heap.data[pos] = parent
|
||||||
pos = parentpos
|
pos = parentpos
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
heap[pos] = newitem
|
heap.data[pos] = newitem
|
||||||
|
|
||||||
proc siftup[T](heap: var HeapQueue[T], p: int) =
|
proc siftup[T](heap: var HeapQueue[T], p: int) =
|
||||||
let endpos = len(heap)
|
let endpos = len(heap)
|
||||||
|
|
@ -60,48 +101,50 @@ proc siftup[T](heap: var HeapQueue[T], p: int) =
|
||||||
if rightpos < endpos and not heapCmp(heap[childpos], heap[rightpos]):
|
if rightpos < endpos and not heapCmp(heap[childpos], heap[rightpos]):
|
||||||
childpos = rightpos
|
childpos = rightpos
|
||||||
# Move the smaller child up.
|
# Move the smaller child up.
|
||||||
heap[pos] = heap[childpos]
|
heap.data[pos] = heap[childpos]
|
||||||
pos = childpos
|
pos = childpos
|
||||||
childpos = 2*pos + 1
|
childpos = 2*pos + 1
|
||||||
# The leaf at pos is empty now. Put newitem there, and bubble it up
|
# The leaf at pos is empty now. Put newitem there, and bubble it up
|
||||||
# to its final resting place (by sifting its parents down).
|
# to its final resting place (by sifting its parents down).
|
||||||
heap[pos] = newitem
|
heap.data[pos] = newitem
|
||||||
siftdown(heap, startpos, pos)
|
siftdown(heap, startpos, pos)
|
||||||
|
|
||||||
proc push*[T](heap: var HeapQueue[T], item: T) =
|
proc push*[T](heap: var HeapQueue[T], item: T) =
|
||||||
## Push item onto heap, maintaining the heap invariant.
|
## Push `item` onto heap, maintaining the heap invariant.
|
||||||
(seq[T](heap)).add(item)
|
heap.data.add(item)
|
||||||
siftdown(heap, 0, len(heap)-1)
|
siftdown(heap, 0, len(heap)-1)
|
||||||
|
|
||||||
proc pop*[T](heap: var HeapQueue[T]): T =
|
proc pop*[T](heap: var HeapQueue[T]): T =
|
||||||
## Pop the smallest item off the heap, maintaining the heap invariant.
|
## Pop and return the smallest item from `heap`,
|
||||||
let lastelt = seq[T](heap).pop()
|
## maintaining the heap invariant.
|
||||||
|
let lastelt = heap.data.pop()
|
||||||
if heap.len > 0:
|
if heap.len > 0:
|
||||||
result = heap[0]
|
result = heap[0]
|
||||||
heap[0] = lastelt
|
heap.data[0] = lastelt
|
||||||
siftup(heap, 0)
|
siftup(heap, 0)
|
||||||
else:
|
else:
|
||||||
result = lastelt
|
result = lastelt
|
||||||
|
|
||||||
proc del*[T](heap: var HeapQueue[T], index: int) =
|
proc del*[T](heap: var HeapQueue[T], index: Natural) =
|
||||||
## Removes element at `index`, maintaining the heap invariant.
|
## Removes the element at `index` from `heap`, maintaining the heap invariant.
|
||||||
swap(seq[T](heap)[^1], seq[T](heap)[index])
|
swap(heap.data[^1], heap.data[index])
|
||||||
let newLen = heap.len - 1
|
let newLen = heap.len - 1
|
||||||
seq[T](heap).setLen(newLen)
|
heap.data.setLen(newLen)
|
||||||
if index < newLen:
|
if index < newLen:
|
||||||
heap.siftup(index)
|
heap.siftup(index)
|
||||||
|
|
||||||
proc replace*[T](heap: var HeapQueue[T], item: T): T =
|
proc replace*[T](heap: var HeapQueue[T], item: T): T =
|
||||||
## Pop and return the current smallest value, and add the new item.
|
## Pop and return the current smallest value, and add the new item.
|
||||||
## This is more efficient than pop() followed by push(), and can be
|
## This is more efficient than pop() followed by push(), and can be
|
||||||
## more appropriate when using a fixed-size heap. Note that the value
|
## more appropriate when using a fixed-size heap. Note that the value
|
||||||
## returned may be larger than item! That constrains reasonable uses of
|
## returned may be larger than item! That constrains reasonable uses of
|
||||||
## this routine unless written as part of a conditional replacement:
|
## this routine unless written as part of a conditional replacement:
|
||||||
|
##
|
||||||
|
## .. code-block:: nim
|
||||||
## if item > heap[0]:
|
## if item > heap[0]:
|
||||||
## item = replace(heap, item)
|
## item = replace(heap, item)
|
||||||
result = heap[0]
|
result = heap[0]
|
||||||
heap[0] = item
|
heap.data[0] = item
|
||||||
siftup(heap, 0)
|
siftup(heap, 0)
|
||||||
|
|
||||||
proc pushpop*[T](heap: var HeapQueue[T], item: T): T =
|
proc pushpop*[T](heap: var HeapQueue[T], item: T): T =
|
||||||
|
|
@ -111,6 +154,36 @@ proc pushpop*[T](heap: var HeapQueue[T], item: T): T =
|
||||||
siftup(heap, 0)
|
siftup(heap, 0)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
proc clear*[T](heap: var HeapQueue[T]) =
|
||||||
|
## Remove all elements from `heap`, making it empty.
|
||||||
|
runnableExamples:
|
||||||
|
var heap = initHeapQueue[int]()
|
||||||
|
heap.push(1)
|
||||||
|
heap.clear()
|
||||||
|
assert heap.len == 0
|
||||||
|
heap.data.setLen(0)
|
||||||
|
|
||||||
|
proc `$`*[T](heap: HeapQueue[T]): string =
|
||||||
|
## Turn a heap into its string representation.
|
||||||
|
runnableExamples:
|
||||||
|
var heap = initHeapQueue[int]()
|
||||||
|
heap.push(1)
|
||||||
|
heap.push(2)
|
||||||
|
assert $heap == "[1, 2]"
|
||||||
|
result = "["
|
||||||
|
for x in heap.data:
|
||||||
|
if result.len > 1: result.add(", ")
|
||||||
|
result.addQuoted(x)
|
||||||
|
result.add("]")
|
||||||
|
|
||||||
|
proc newHeapQueue*[T](): HeapQueue[T] {.deprecated.} =
|
||||||
|
## **Deprecated since v0.20.0:** use ``initHeapQueue`` instead.
|
||||||
|
initHeapQueue[T]()
|
||||||
|
|
||||||
|
proc newHeapQueue*[T](heap: var HeapQueue[T]) {.deprecated.} =
|
||||||
|
## **Deprecated since v0.20.0:** use ``clear`` instead.
|
||||||
|
heap.clear()
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
proc toSortedSeq[T](h: HeapQueue[T]): seq[T] =
|
proc toSortedSeq[T](h: HeapQueue[T]): seq[T] =
|
||||||
var tmp = h
|
var tmp = h
|
||||||
|
|
@ -119,7 +192,7 @@ when isMainModule:
|
||||||
result.add(pop(tmp))
|
result.add(pop(tmp))
|
||||||
|
|
||||||
block: # Simple sanity test
|
block: # Simple sanity test
|
||||||
var heap = newHeapQueue[int]()
|
var heap = initHeapQueue[int]()
|
||||||
let data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
|
let data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
|
||||||
for item in data:
|
for item in data:
|
||||||
push(heap, item)
|
push(heap, item)
|
||||||
|
|
@ -127,27 +200,27 @@ when isMainModule:
|
||||||
doAssert(heap.toSortedSeq == @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
doAssert(heap.toSortedSeq == @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||||
|
|
||||||
block: # Test del
|
block: # Test del
|
||||||
var heap = newHeapQueue[int]()
|
var heap = initHeapQueue[int]()
|
||||||
let data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
|
let data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
|
||||||
for item in data: push(heap, item)
|
for item in data: push(heap, item)
|
||||||
|
|
||||||
heap.del(0)
|
heap.del(0)
|
||||||
doAssert(heap[0] == 1)
|
doAssert(heap[0] == 1)
|
||||||
|
|
||||||
heap.del(seq[int](heap).find(7))
|
heap.del(heap.data.find(7))
|
||||||
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 5, 6, 8, 9])
|
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 5, 6, 8, 9])
|
||||||
|
|
||||||
heap.del(seq[int](heap).find(5))
|
heap.del(heap.data.find(5))
|
||||||
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 6, 8, 9])
|
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 6, 8, 9])
|
||||||
|
|
||||||
heap.del(seq[int](heap).find(6))
|
heap.del(heap.data.find(6))
|
||||||
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 8, 9])
|
doAssert(heap.toSortedSeq == @[1, 2, 3, 4, 8, 9])
|
||||||
|
|
||||||
heap.del(seq[int](heap).find(2))
|
heap.del(heap.data.find(2))
|
||||||
doAssert(heap.toSortedSeq == @[1, 3, 4, 8, 9])
|
doAssert(heap.toSortedSeq == @[1, 3, 4, 8, 9])
|
||||||
|
|
||||||
block: # Test del last
|
block: # Test del last
|
||||||
var heap = newHeapQueue[int]()
|
var heap = initHeapQueue[int]()
|
||||||
let data = [1, 2, 3]
|
let data = [1, 2, 3]
|
||||||
for item in data: push(heap, item)
|
for item in data: push(heap, item)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
## The ``intsets`` module implements an efficient int set implemented as a
|
## The ``intsets`` module implements an efficient `int` set implemented as a
|
||||||
## `sparse bit set`:idx:.
|
## `sparse bit set`:idx:.
|
||||||
|
##
|
||||||
## **Note**: Currently the assignment operator ``=`` for ``intsets``
|
## **Note**: Currently the assignment operator ``=`` for ``IntSet``
|
||||||
## performs some rather meaningless shallow copy. Since Nim currently does
|
## performs some rather meaningless shallow copy. Since Nim currently does
|
||||||
## not allow the assignment operator to be overloaded, use ``assign`` to
|
## not allow the assignment operator to be overloaded, use `assign proc
|
||||||
## get a deep copy.
|
## <#assign,IntSet,IntSet>`_ to get a deep copy.
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `sets module <sets.html>`_ for more general hash sets
|
||||||
|
|
||||||
|
|
||||||
import
|
import
|
||||||
|
|
@ -40,7 +43,7 @@ type
|
||||||
bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector
|
bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector
|
||||||
|
|
||||||
TrunkSeq = seq[PTrunk]
|
TrunkSeq = seq[PTrunk]
|
||||||
IntSet* = object ## an efficient set of 'int' implemented as a sparse bit set
|
IntSet* = object ## An efficient set of `int` implemented as a sparse bit set.
|
||||||
elems: int # only valid for small numbers
|
elems: int # only valid for small numbers
|
||||||
counter, max: int
|
counter, max: int
|
||||||
head: PTrunk
|
head: PTrunk
|
||||||
|
|
@ -96,18 +99,33 @@ proc intSetPut(t: var IntSet, key: int): PTrunk =
|
||||||
t.head = result
|
t.head = result
|
||||||
t.data[h] = result
|
t.data[h] = result
|
||||||
|
|
||||||
proc contains*(s: IntSet, key: int): bool =
|
proc bitincl(s: var IntSet, key: int) {.inline.} =
|
||||||
## Returns true iff `key` is in `s`.
|
var t = intSetPut(s, `shr`(key, TrunkShift))
|
||||||
|
var u = key and TrunkMask
|
||||||
|
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] or
|
||||||
|
`shl`(1, u and IntMask)
|
||||||
|
|
||||||
|
proc exclImpl(s: var IntSet, key: int) =
|
||||||
if s.elems <= s.a.len:
|
if s.elems <= s.a.len:
|
||||||
for i in 0..<s.elems:
|
for i in 0..<s.elems:
|
||||||
if s.a[i] == key: return true
|
if s.a[i] == key:
|
||||||
|
s.a[i] = s.a[s.elems-1]
|
||||||
|
dec s.elems
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
var t = intSetGet(s, `shr`(key, TrunkShift))
|
var t = intSetGet(s, `shr`(key, TrunkShift))
|
||||||
if t != nil:
|
if t != nil:
|
||||||
var u = key and TrunkMask
|
var u = key and TrunkMask
|
||||||
result = (t.bits[`shr`(u, IntShift)] and `shl`(1, u and IntMask)) != 0
|
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] and
|
||||||
else:
|
not `shl`(1, u and IntMask)
|
||||||
result = false
|
|
||||||
|
template dollarImpl(): untyped =
|
||||||
|
result = "{"
|
||||||
|
for key in items(s):
|
||||||
|
if result.len > 1: result.add(", ")
|
||||||
|
result.add($key)
|
||||||
|
result.add("}")
|
||||||
|
|
||||||
|
|
||||||
iterator items*(s: IntSet): int {.inline.} =
|
iterator items*(s: IntSet): int {.inline.} =
|
||||||
## Iterates over any included element of `s`.
|
## Iterates over any included element of `s`.
|
||||||
|
|
@ -131,14 +149,62 @@ iterator items*(s: IntSet): int {.inline.} =
|
||||||
inc(i)
|
inc(i)
|
||||||
r = r.next
|
r = r.next
|
||||||
|
|
||||||
proc bitincl(s: var IntSet, key: int) {.inline.} =
|
|
||||||
var t = intSetPut(s, `shr`(key, TrunkShift))
|
proc initIntSet*: IntSet =
|
||||||
var u = key and TrunkMask
|
## Returns an empty IntSet.
|
||||||
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] or
|
runnableExamples:
|
||||||
`shl`(1, u and IntMask)
|
var a = initIntSet()
|
||||||
|
assert len(a) == 0
|
||||||
|
|
||||||
|
# newSeq(result.data, InitIntSetSize)
|
||||||
|
# result.max = InitIntSetSize-1
|
||||||
|
result = IntSet(
|
||||||
|
elems: 0,
|
||||||
|
counter: 0,
|
||||||
|
max: 0,
|
||||||
|
head: nil,
|
||||||
|
data: when defined(nimNoNilSeqs): @[] else: nil)
|
||||||
|
# a: array[0..33, int] # profiling shows that 34 elements are enough
|
||||||
|
|
||||||
|
proc contains*(s: IntSet, key: int): bool =
|
||||||
|
## Returns true if `key` is in `s`.
|
||||||
|
##
|
||||||
|
## This allows the usage of `in` operator.
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
for x in [1, 3, 5]:
|
||||||
|
a.incl(x)
|
||||||
|
assert a.contains(3)
|
||||||
|
assert 3 in a
|
||||||
|
assert(not a.contains(8))
|
||||||
|
assert 8 notin a
|
||||||
|
|
||||||
|
if s.elems <= s.a.len:
|
||||||
|
for i in 0..<s.elems:
|
||||||
|
if s.a[i] == key: return true
|
||||||
|
else:
|
||||||
|
var t = intSetGet(s, `shr`(key, TrunkShift))
|
||||||
|
if t != nil:
|
||||||
|
var u = key and TrunkMask
|
||||||
|
result = (t.bits[`shr`(u, IntShift)] and `shl`(1, u and IntMask)) != 0
|
||||||
|
else:
|
||||||
|
result = false
|
||||||
|
|
||||||
proc incl*(s: var IntSet, key: int) =
|
proc incl*(s: var IntSet, key: int) =
|
||||||
## Includes an element `key` in `s`.
|
## Includes an element `key` in `s`.
|
||||||
|
##
|
||||||
|
## This doesn't do anything if `key` is already in `s`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `excl proc <#excl,IntSet,int>`_ for excluding an element
|
||||||
|
## * `incl proc <#incl,IntSet,IntSet>`_ for including other set
|
||||||
|
## * `containsOrIncl proc <#containsOrIncl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
a.incl(3)
|
||||||
|
a.incl(3)
|
||||||
|
assert len(a) == 1
|
||||||
|
|
||||||
if s.elems <= s.a.len:
|
if s.elems <= s.a.len:
|
||||||
for i in 0..<s.elems:
|
for i in 0..<s.elems:
|
||||||
if s.a[i] == key: return
|
if s.a[i] == key: return
|
||||||
|
|
@ -156,40 +222,42 @@ proc incl*(s: var IntSet, key: int) =
|
||||||
|
|
||||||
proc incl*(s: var IntSet, other: IntSet) =
|
proc incl*(s: var IntSet, other: IntSet) =
|
||||||
## Includes all elements from `other` into `s`.
|
## Includes all elements from `other` into `s`.
|
||||||
|
##
|
||||||
|
## This is the in-place version of `s + other <#+,IntSet,IntSet>`_.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `excl proc <#excl,IntSet,IntSet>`_ for excluding other set
|
||||||
|
## * `incl proc <#incl,IntSet,int>`_ for including an element
|
||||||
|
## * `containsOrIncl proc <#containsOrIncl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1)
|
||||||
|
b.incl(5)
|
||||||
|
a.incl(b)
|
||||||
|
assert len(a) == 2
|
||||||
|
assert 5 in a
|
||||||
|
|
||||||
for item in other: incl(s, item)
|
for item in other: incl(s, item)
|
||||||
|
|
||||||
proc exclImpl(s: var IntSet, key: int) =
|
|
||||||
if s.elems <= s.a.len:
|
|
||||||
for i in 0..<s.elems:
|
|
||||||
if s.a[i] == key:
|
|
||||||
s.a[i] = s.a[s.elems-1]
|
|
||||||
dec s.elems
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
var t = intSetGet(s, `shr`(key, TrunkShift))
|
|
||||||
if t != nil:
|
|
||||||
var u = key and TrunkMask
|
|
||||||
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] and
|
|
||||||
not `shl`(1, u and IntMask)
|
|
||||||
|
|
||||||
proc excl*(s: var IntSet, key: int) =
|
|
||||||
## Excludes `key` from the set `s`.
|
|
||||||
exclImpl(s, key)
|
|
||||||
|
|
||||||
proc excl*(s: var IntSet, other: IntSet) =
|
|
||||||
## Excludes all elements from `other` from `s`.
|
|
||||||
for item in other: excl(s, item)
|
|
||||||
|
|
||||||
proc missingOrExcl*(s: var IntSet, key: int) : bool =
|
|
||||||
## Returns true if `s` does not contain `key`, otherwise
|
|
||||||
## `key` is removed from `s` and false is returned.
|
|
||||||
var count = s.elems
|
|
||||||
exclImpl(s, key)
|
|
||||||
result = count == s.elems
|
|
||||||
|
|
||||||
proc containsOrIncl*(s: var IntSet, key: int): bool =
|
proc containsOrIncl*(s: var IntSet, key: int): bool =
|
||||||
## Returns true if `s` contains `key`, otherwise `key` is included in `s`
|
## Includes `key` in the set `s` and tells if `key` was already in `s`.
|
||||||
## and false is returned.
|
##
|
||||||
|
## The difference with regards to the `incl proc <#incl,IntSet,int>`_ is
|
||||||
|
## that this proc returns `true` if `s` already contained `key`. The
|
||||||
|
## proc will return `false` if `key` was added as a new value to `s` during
|
||||||
|
## this call.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `incl proc <#incl,IntSet,int>`_ for including an element
|
||||||
|
## * `missingOrExcl proc <#missingOrExcl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
assert a.containsOrIncl(3) == false
|
||||||
|
assert a.containsOrIncl(3) == true
|
||||||
|
assert a.containsOrIncl(4) == false
|
||||||
|
|
||||||
if s.elems <= s.a.len:
|
if s.elems <= s.a.len:
|
||||||
for i in 0..<s.elems:
|
for i in 0..<s.elems:
|
||||||
if s.a[i] == key:
|
if s.a[i] == key:
|
||||||
|
|
@ -208,25 +276,76 @@ proc containsOrIncl*(s: var IntSet, key: int): bool =
|
||||||
incl(s, key)
|
incl(s, key)
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
proc initIntSet*: IntSet =
|
proc excl*(s: var IntSet, key: int) =
|
||||||
## Returns an empty IntSet. Example:
|
## Excludes `key` from the set `s`.
|
||||||
##
|
##
|
||||||
## .. code-block ::
|
## This doesn't do anything if `key` is not found in `s`.
|
||||||
## var a = initIntSet()
|
##
|
||||||
## a.incl(2)
|
## See also:
|
||||||
|
## * `incl proc <#incl,IntSet,int>`_ for including an element
|
||||||
|
## * `excl proc <#excl,IntSet,IntSet>`_ for excluding other set
|
||||||
|
## * `missingOrExcl proc <#missingOrExcl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
a.incl(3)
|
||||||
|
a.excl(3)
|
||||||
|
a.excl(3)
|
||||||
|
a.excl(99)
|
||||||
|
assert len(a) == 0
|
||||||
|
exclImpl(s, key)
|
||||||
|
|
||||||
# newSeq(result.data, InitIntSetSize)
|
proc excl*(s: var IntSet, other: IntSet) =
|
||||||
# result.max = InitIntSetSize-1
|
## Excludes all elements from `other` from `s`.
|
||||||
result = IntSet(
|
##
|
||||||
elems: 0,
|
## This is the in-place version of `s - other <#-,IntSet,IntSet>`_.
|
||||||
counter: 0,
|
##
|
||||||
max: 0,
|
## See also:
|
||||||
head: nil,
|
## * `incl proc <#incl,IntSet,IntSet>`_ for including other set
|
||||||
data: when defined(nimNoNilSeqs): @[] else: nil)
|
## * `excl proc <#excl,IntSet,int>`_ for excluding an element
|
||||||
# a: array[0..33, int] # profiling shows that 34 elements are enough
|
## * `missingOrExcl proc <#missingOrExcl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1)
|
||||||
|
a.incl(5)
|
||||||
|
b.incl(5)
|
||||||
|
a.excl(b)
|
||||||
|
assert len(a) == 1
|
||||||
|
assert 5 notin a
|
||||||
|
|
||||||
|
for item in other: excl(s, item)
|
||||||
|
|
||||||
|
proc missingOrExcl*(s: var IntSet, key: int) : bool =
|
||||||
|
## Excludes `key` in the set `s` and tells if `key` was already missing from `s`.
|
||||||
|
##
|
||||||
|
## The difference with regards to the `excl proc <#excl,IntSet,int>`_ is
|
||||||
|
## that this proc returns `true` if `key` was missing from `s`.
|
||||||
|
## The proc will return `false` if `key` was in `s` and it was removed
|
||||||
|
## during this call.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `excl proc <#excl,IntSet,int>`_ for excluding an element
|
||||||
|
## * `excl proc <#excl,IntSet,IntSet>`_ for excluding other set
|
||||||
|
## * `containsOrIncl proc <#containsOrIncl,IntSet,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
a.incl(5)
|
||||||
|
assert a.missingOrExcl(5) == false
|
||||||
|
assert a.missingOrExcl(5) == true
|
||||||
|
|
||||||
|
var count = s.elems
|
||||||
|
exclImpl(s, key)
|
||||||
|
result = count == s.elems
|
||||||
|
|
||||||
proc clear*(result: var IntSet) =
|
proc clear*(result: var IntSet) =
|
||||||
## Clears the IntSet back to an empty state.
|
## Clears the IntSet back to an empty state.
|
||||||
|
runnableExamples:
|
||||||
|
var a = initIntSet()
|
||||||
|
a.incl(5)
|
||||||
|
a.incl(7)
|
||||||
|
clear(a)
|
||||||
|
assert len(a) == 0
|
||||||
|
|
||||||
# setLen(result.data, InitIntSetSize)
|
# setLen(result.data, InitIntSetSize)
|
||||||
# for i in 0..InitIntSetSize-1: result.data[i] = nil
|
# for i in 0..InitIntSetSize-1: result.data[i] = nil
|
||||||
|
|
@ -243,8 +362,17 @@ proc clear*(result: var IntSet) =
|
||||||
proc isNil*(x: IntSet): bool {.inline.} = x.head.isNil and x.elems == 0
|
proc isNil*(x: IntSet): bool {.inline.} = x.head.isNil and x.elems == 0
|
||||||
|
|
||||||
proc assign*(dest: var IntSet, src: IntSet) =
|
proc assign*(dest: var IntSet, src: IntSet) =
|
||||||
## copies `src` to `dest`. `dest` does not need to be initialized by
|
## Copies `src` to `dest`.
|
||||||
## `initIntSet`.
|
## `dest` does not need to be initialized by `initIntSet proc <#initIntSet>`_.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
b.incl(5)
|
||||||
|
b.incl(7)
|
||||||
|
a.assign(b)
|
||||||
|
assert len(a) == 2
|
||||||
|
|
||||||
if src.elems <= src.a.len:
|
if src.elems <= src.a.len:
|
||||||
when defined(nimNoNilSeqs):
|
when defined(nimNoNilSeqs):
|
||||||
dest.data = @[]
|
dest.data = @[]
|
||||||
|
|
@ -276,11 +404,33 @@ proc assign*(dest: var IntSet, src: IntSet) =
|
||||||
|
|
||||||
proc union*(s1, s2: IntSet): IntSet =
|
proc union*(s1, s2: IntSet): IntSet =
|
||||||
## Returns the union of the sets `s1` and `s2`.
|
## Returns the union of the sets `s1` and `s2`.
|
||||||
|
##
|
||||||
|
## The same as `s1 + s2 <#+,IntSet,IntSet>`_.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1); a.incl(2); a.incl(3)
|
||||||
|
b.incl(3); b.incl(4); b.incl(5)
|
||||||
|
assert union(a, b).len == 5
|
||||||
|
## {1, 2, 3, 4, 5}
|
||||||
|
|
||||||
result.assign(s1)
|
result.assign(s1)
|
||||||
incl(result, s2)
|
incl(result, s2)
|
||||||
|
|
||||||
proc intersection*(s1, s2: IntSet): IntSet =
|
proc intersection*(s1, s2: IntSet): IntSet =
|
||||||
## Returns the intersection of the sets `s1` and `s2`.
|
## Returns the intersection of the sets `s1` and `s2`.
|
||||||
|
##
|
||||||
|
## The same as `s1 * s2 <#*,IntSet,IntSet>`_.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1); a.incl(2); a.incl(3)
|
||||||
|
b.incl(3); b.incl(4); b.incl(5)
|
||||||
|
assert intersection(a, b).len == 1
|
||||||
|
## {3}
|
||||||
|
|
||||||
result = initIntSet()
|
result = initIntSet()
|
||||||
for item in s1:
|
for item in s1:
|
||||||
if contains(s2, item):
|
if contains(s2, item):
|
||||||
|
|
@ -288,6 +438,17 @@ proc intersection*(s1, s2: IntSet): IntSet =
|
||||||
|
|
||||||
proc difference*(s1, s2: IntSet): IntSet =
|
proc difference*(s1, s2: IntSet): IntSet =
|
||||||
## Returns the difference of the sets `s1` and `s2`.
|
## Returns the difference of the sets `s1` and `s2`.
|
||||||
|
##
|
||||||
|
## The same as `s1 - s2 <#-,IntSet,IntSet>`_.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1); a.incl(2); a.incl(3)
|
||||||
|
b.incl(3); b.incl(4); b.incl(5)
|
||||||
|
assert difference(a, b).len == 2
|
||||||
|
## {1, 2}
|
||||||
|
|
||||||
result = initIntSet()
|
result = initIntSet()
|
||||||
for item in s1:
|
for item in s1:
|
||||||
if not contains(s2, item):
|
if not contains(s2, item):
|
||||||
|
|
@ -295,31 +456,50 @@ proc difference*(s1, s2: IntSet): IntSet =
|
||||||
|
|
||||||
proc symmetricDifference*(s1, s2: IntSet): IntSet =
|
proc symmetricDifference*(s1, s2: IntSet): IntSet =
|
||||||
## Returns the symmetric difference of the sets `s1` and `s2`.
|
## Returns the symmetric difference of the sets `s1` and `s2`.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1); a.incl(2); a.incl(3)
|
||||||
|
b.incl(3); b.incl(4); b.incl(5)
|
||||||
|
assert symmetricDifference(a, b).len == 4
|
||||||
|
## {1, 2, 4, 5}
|
||||||
|
|
||||||
result.assign(s1)
|
result.assign(s1)
|
||||||
for item in s2:
|
for item in s2:
|
||||||
if containsOrIncl(result, item): excl(result, item)
|
if containsOrIncl(result, item): excl(result, item)
|
||||||
|
|
||||||
proc `+`*(s1, s2: IntSet): IntSet {.inline.} =
|
proc `+`*(s1, s2: IntSet): IntSet {.inline.} =
|
||||||
## Alias for `union(s1, s2) <#union>`_.
|
## Alias for `union(s1, s2) <#union,IntSet,IntSet>`_.
|
||||||
result = union(s1, s2)
|
result = union(s1, s2)
|
||||||
|
|
||||||
proc `*`*(s1, s2: IntSet): IntSet {.inline.} =
|
proc `*`*(s1, s2: IntSet): IntSet {.inline.} =
|
||||||
## Alias for `intersection(s1, s2) <#intersection>`_.
|
## Alias for `intersection(s1, s2) <#intersection,IntSet,IntSet>`_.
|
||||||
result = intersection(s1, s2)
|
result = intersection(s1, s2)
|
||||||
|
|
||||||
proc `-`*(s1, s2: IntSet): IntSet {.inline.} =
|
proc `-`*(s1, s2: IntSet): IntSet {.inline.} =
|
||||||
## Alias for `difference(s1, s2) <#difference>`_.
|
## Alias for `difference(s1, s2) <#difference,IntSet,IntSet>`_.
|
||||||
result = difference(s1, s2)
|
result = difference(s1, s2)
|
||||||
|
|
||||||
proc disjoint*(s1, s2: IntSet): bool =
|
proc disjoint*(s1, s2: IntSet): bool =
|
||||||
## Returns true if the sets `s1` and `s2` have no items in common.
|
## Returns true if the sets `s1` and `s2` have no items in common.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1); a.incl(2)
|
||||||
|
b.incl(2); b.incl(3)
|
||||||
|
assert disjoint(a, b) == false
|
||||||
|
b.excl(2)
|
||||||
|
assert disjoint(a, b) == true
|
||||||
|
|
||||||
for item in s1:
|
for item in s1:
|
||||||
if contains(s2, item):
|
if contains(s2, item):
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc len*(s: IntSet): int {.inline.} =
|
proc len*(s: IntSet): int {.inline.} =
|
||||||
## Returns the number of keys in `s`.
|
## Returns the number of elements in `s`.
|
||||||
if s.elems < s.a.len:
|
if s.elems < s.a.len:
|
||||||
result = s.elems
|
result = s.elems
|
||||||
else:
|
else:
|
||||||
|
|
@ -328,35 +508,59 @@ proc len*(s: IntSet): int {.inline.} =
|
||||||
inc(result)
|
inc(result)
|
||||||
|
|
||||||
proc card*(s: IntSet): int {.inline.} =
|
proc card*(s: IntSet): int {.inline.} =
|
||||||
## Alias for `len() <#len>` _.
|
## Alias for `len() <#len,IntSet>`_.
|
||||||
result = s.len()
|
result = s.len()
|
||||||
|
|
||||||
proc `<=`*(s1, s2: IntSet): bool =
|
proc `<=`*(s1, s2: IntSet): bool =
|
||||||
## Returns true iff `s1` is subset of `s2`.
|
## Returns true if `s1` is subset of `s2`.
|
||||||
|
##
|
||||||
|
## A subset `s1` has all of its elements in `s2`, and `s2` doesn't necessarily
|
||||||
|
## have more elements than `s1`. That is, `s1` can be equal to `s2`.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1)
|
||||||
|
b.incl(1); b.incl(2)
|
||||||
|
assert a <= b
|
||||||
|
a.incl(2)
|
||||||
|
assert a <= b
|
||||||
|
a.incl(3)
|
||||||
|
assert(not (a <= b))
|
||||||
|
|
||||||
for item in s1:
|
for item in s1:
|
||||||
if not s2.contains(item):
|
if not s2.contains(item):
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc `<`*(s1, s2: IntSet): bool =
|
proc `<`*(s1, s2: IntSet): bool =
|
||||||
## Returns true iff `s1` is proper subset of `s2`.
|
## Returns true if `s1` is proper subset of `s2`.
|
||||||
|
##
|
||||||
|
## A strict or proper subset `s1` has all of its elements in `s2`, but `s2` has
|
||||||
|
## more elements than `s1`.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = initIntSet()
|
||||||
|
b = initIntSet()
|
||||||
|
a.incl(1)
|
||||||
|
b.incl(1); b.incl(2)
|
||||||
|
assert a < b
|
||||||
|
a.incl(2)
|
||||||
|
assert(not (a < b))
|
||||||
return s1 <= s2 and not (s2 <= s1)
|
return s1 <= s2 and not (s2 <= s1)
|
||||||
|
|
||||||
proc `==`*(s1, s2: IntSet): bool =
|
proc `==`*(s1, s2: IntSet): bool =
|
||||||
## Returns true if both `s` and `t` have the same members and set size.
|
## Returns true if both `s1` and `s2` have the same elements and set size.
|
||||||
return s1 <= s2 and s2 <= s1
|
return s1 <= s2 and s2 <= s1
|
||||||
|
|
||||||
template dollarImpl(): untyped =
|
|
||||||
result = "{"
|
|
||||||
for key in items(s):
|
|
||||||
if result.len > 1: result.add(", ")
|
|
||||||
result.add($key)
|
|
||||||
result.add("}")
|
|
||||||
|
|
||||||
proc `$`*(s: IntSet): string =
|
proc `$`*(s: IntSet): string =
|
||||||
## The `$` operator for int sets.
|
## The `$` operator for int sets.
|
||||||
|
##
|
||||||
|
## Converts the set `s` to a string, mostly for logging and printing purposes.
|
||||||
dollarImpl()
|
dollarImpl()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import sequtils, algorithm
|
import sequtils, algorithm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,25 +93,25 @@ type
|
||||||
|
|
||||||
SinglyLinkedList*[T] = object ## A singly linked list.
|
SinglyLinkedList*[T] = object ## A singly linked list.
|
||||||
##
|
##
|
||||||
## Use `initSinglyLinkedList proc <#initSinglyLinkedList,>`_ to create
|
## Use `initSinglyLinkedList proc <#initSinglyLinkedList>`_ to create
|
||||||
## a new empty list.
|
## a new empty list.
|
||||||
head*, tail*: SinglyLinkedNode[T]
|
head*, tail*: SinglyLinkedNode[T]
|
||||||
|
|
||||||
DoublyLinkedList*[T] = object ## A doubly linked list.
|
DoublyLinkedList*[T] = object ## A doubly linked list.
|
||||||
##
|
##
|
||||||
## Use `initDoublyLinkedList proc <#initDoublyLinkedList,>`_ to create
|
## Use `initDoublyLinkedList proc <#initDoublyLinkedList>`_ to create
|
||||||
## a new empty list.
|
## a new empty list.
|
||||||
head*, tail*: DoublyLinkedNode[T]
|
head*, tail*: DoublyLinkedNode[T]
|
||||||
|
|
||||||
SinglyLinkedRing*[T] = object ## A singly linked ring.
|
SinglyLinkedRing*[T] = object ## A singly linked ring.
|
||||||
##
|
##
|
||||||
## Use `initSinglyLinkedRing proc <#initSinglyLinkedRing,>`_ to create
|
## Use `initSinglyLinkedRing proc <#initSinglyLinkedRing>`_ to create
|
||||||
## a new empty ring.
|
## a new empty ring.
|
||||||
head*, tail*: SinglyLinkedNode[T]
|
head*, tail*: SinglyLinkedNode[T]
|
||||||
|
|
||||||
DoublyLinkedRing*[T] = object ## A doubly linked ring.
|
DoublyLinkedRing*[T] = object ## A doubly linked ring.
|
||||||
##
|
##
|
||||||
## Use `initDoublyLinkedRing proc <#initDoublyLinkedRing,>`_ to create
|
## Use `initDoublyLinkedRing proc <#initDoublyLinkedRing>`_ to create
|
||||||
## a new empty ring.
|
## a new empty ring.
|
||||||
head*: DoublyLinkedNode[T]
|
head*: DoublyLinkedNode[T]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -846,6 +846,15 @@ template mapIt*(s: typed, op: untyped): untyped =
|
||||||
result.add(op)
|
result.add(op)
|
||||||
result
|
result
|
||||||
|
|
||||||
|
template mapIt*(s, typ, op: untyped): untyped {.error:
|
||||||
|
"Use 'mapIt(seq1, op)' - without specifying the type of the returned seqence".} =
|
||||||
|
## **Deprecated since version 0.12.0:** Use the `mapIt(seq1, op) template
|
||||||
|
## <#mapIt.t,typed,untyped>`_ instead.
|
||||||
|
var result: seq[typ] = @[]
|
||||||
|
for it {.inject.} in items(s):
|
||||||
|
result.add(op)
|
||||||
|
result
|
||||||
|
|
||||||
template applyIt*(varSeq, op: untyped) =
|
template applyIt*(varSeq, op: untyped) =
|
||||||
## Convenience template around the mutable ``apply`` proc to reduce typing.
|
## Convenience template around the mutable ``apply`` proc to reduce typing.
|
||||||
##
|
##
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,6 +17,7 @@
|
||||||
## ``http://google.com``:
|
## ``http://google.com``:
|
||||||
##
|
##
|
||||||
## .. code-block:: Nim
|
## .. code-block:: Nim
|
||||||
|
## import httpClient
|
||||||
## var client = newHttpClient()
|
## var client = newHttpClient()
|
||||||
## echo client.getContent("http://google.com")
|
## echo client.getContent("http://google.com")
|
||||||
##
|
##
|
||||||
|
|
@ -24,6 +25,7 @@
|
||||||
## ``AsyncHttpClient``:
|
## ``AsyncHttpClient``:
|
||||||
##
|
##
|
||||||
## .. code-block:: Nim
|
## .. code-block:: Nim
|
||||||
|
## import httpClient
|
||||||
## var client = newAsyncHttpClient()
|
## var client = newAsyncHttpClient()
|
||||||
## echo await client.getContent("http://google.com")
|
## echo await client.getContent("http://google.com")
|
||||||
##
|
##
|
||||||
|
|
|
||||||
|
|
@ -625,7 +625,7 @@ proc escapeJsonUnquoted*(s: string; result: var string) =
|
||||||
of '\r': result.add("\\r")
|
of '\r': result.add("\\r")
|
||||||
of '"': result.add("\\\"")
|
of '"': result.add("\\\"")
|
||||||
of '\0'..'\7': result.add("\\u000" & $ord(c))
|
of '\0'..'\7': result.add("\\u000" & $ord(c))
|
||||||
of '\14'..'\31': result.add("\\u00" & $ord(c))
|
of '\14'..'\31': result.add("\\u00" & toHex(ord(c), 2))
|
||||||
of '\\': result.add("\\\\")
|
of '\\': result.add("\\\\")
|
||||||
else: result.add(c)
|
else: result.add(c)
|
||||||
|
|
||||||
|
|
@ -1328,6 +1328,12 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
|
||||||
|
|
||||||
let obj = getType(typeSym[1])
|
let obj = getType(typeSym[1])
|
||||||
result = processType(newIdentNode(typeName), obj, jsonNode, true)
|
result = processType(newIdentNode(typeName), obj, jsonNode, true)
|
||||||
|
of "range":
|
||||||
|
let typeNode = typeSym
|
||||||
|
# Deduce the base type from one of the endpoints
|
||||||
|
let baseType = getType(typeNode[1])
|
||||||
|
|
||||||
|
result = createConstructor(baseType, jsonNode)
|
||||||
of "seq":
|
of "seq":
|
||||||
let seqT = typeSym[1]
|
let seqT = typeSym[1]
|
||||||
let forLoopI = genSym(nskForVar, "i")
|
let forLoopI = genSym(nskForVar, "i")
|
||||||
|
|
@ -1686,9 +1692,9 @@ when isMainModule:
|
||||||
doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
|
doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
|
||||||
|
|
||||||
doAssert escapeJsonUnquoted("\10Foo🎃barÄ") == "\\nFoo🎃barÄ"
|
doAssert escapeJsonUnquoted("\10Foo🎃barÄ") == "\\nFoo🎃barÄ"
|
||||||
doAssert escapeJsonUnquoted("\0\7\20") == "\\u0000\\u0007\\u0020" # for #7887
|
doAssert escapeJsonUnquoted("\0\7\20") == "\\u0000\\u0007\\u0014" # for #7887
|
||||||
doAssert escapeJson("\10Foo🎃barÄ") == "\"\\nFoo🎃barÄ\""
|
doAssert escapeJson("\10Foo🎃barÄ") == "\"\\nFoo🎃barÄ\""
|
||||||
doAssert escapeJson("\0\7\20") == "\"\\u0000\\u0007\\u0020\"" # for #7887
|
doAssert escapeJson("\0\7\20") == "\"\\u0000\\u0007\\u0014\"" # for #7887
|
||||||
|
|
||||||
# Test with extra data
|
# Test with extra data
|
||||||
when not defined(js):
|
when not defined(js):
|
||||||
|
|
@ -1721,3 +1727,21 @@ when isMainModule:
|
||||||
foo = js.to Foo
|
foo = js.to Foo
|
||||||
|
|
||||||
doAssert(foo.b == "abc")
|
doAssert(foo.b == "abc")
|
||||||
|
|
||||||
|
# Generate constructors for range[T] types
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Q1 = range[0..10]
|
||||||
|
Q2 = range[0'i8..10'i8]
|
||||||
|
Q3 = range[0'u16..10'u16]
|
||||||
|
X = object
|
||||||
|
m1: Q1
|
||||||
|
m2: Q2
|
||||||
|
m3: Q3
|
||||||
|
|
||||||
|
let
|
||||||
|
obj = X(m1: 1, m2: 2'i8, m3: 3'u16)
|
||||||
|
jsonObj = %obj
|
||||||
|
desObj = to(jsonObj, type(obj))
|
||||||
|
|
||||||
|
doAssert(desObj == obj)
|
||||||
|
|
|
||||||
|
|
@ -372,9 +372,8 @@ proc `==`*(x, y: MemSlice): bool =
|
||||||
|
|
||||||
proc `$`*(ms: MemSlice): string {.inline.} =
|
proc `$`*(ms: MemSlice): string {.inline.} =
|
||||||
## Return a Nim string built from a MemSlice.
|
## Return a Nim string built from a MemSlice.
|
||||||
var buf = newString(ms.size)
|
result.setLen(ms.size)
|
||||||
copyMem(addr(buf[0]), ms.data, ms.size)
|
copyMem(addr(result[0]), ms.data, ms.size)
|
||||||
result = buf
|
|
||||||
|
|
||||||
iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
|
iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
|
||||||
## Iterates over [optional `eat`] `delim`-delimited slices in MemFile `mfile`.
|
## Iterates over [optional `eat`] `delim`-delimited slices in MemFile `mfile`.
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,18 @@
|
||||||
## This module implements a simple high performance `CSV`:idx:
|
## This module implements a simple high performance `CSV`:idx:
|
||||||
## (`comma separated value`:idx:) parser.
|
## (`comma separated value`:idx:) parser.
|
||||||
##
|
##
|
||||||
## Example: How to use the parser
|
## Basic usage
|
||||||
## ==============================
|
## ===========
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## .. code-block:: nim
|
||||||
## import os, parsecsv, streams
|
## import parsecsv
|
||||||
|
## from os import paramStr
|
||||||
|
## from streams import newFileStream
|
||||||
|
##
|
||||||
## var s = newFileStream(paramStr(1), fmRead)
|
## var s = newFileStream(paramStr(1), fmRead)
|
||||||
## if s == nil: quit("cannot open the file" & paramStr(1))
|
## if s == nil:
|
||||||
|
## quit("cannot open the file" & paramStr(1))
|
||||||
|
##
|
||||||
## var x: CsvParser
|
## var x: CsvParser
|
||||||
## open(x, s, paramStr(1))
|
## open(x, s, paramStr(1))
|
||||||
## while readRow(x):
|
## while readRow(x):
|
||||||
|
|
@ -26,11 +31,11 @@
|
||||||
## close(x)
|
## close(x)
|
||||||
##
|
##
|
||||||
## For CSV files with a header row, the header can be read and then used as a
|
## For CSV files with a header row, the header can be read and then used as a
|
||||||
## reference for item access with `rowEntry <#rowEntry.CsvParser.string>`_:
|
## reference for item access with `rowEntry <#rowEntry,CsvParser,string>`_:
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## .. code-block:: nim
|
||||||
## import parsecsv
|
## import parsecsv
|
||||||
## import os
|
##
|
||||||
## # Prepare a file
|
## # Prepare a file
|
||||||
## let content = """One,Two,Three,Four
|
## let content = """One,Two,Three,Four
|
||||||
## 1,2,3,4
|
## 1,2,3,4
|
||||||
|
|
@ -47,24 +52,40 @@
|
||||||
## for col in items(p.headers):
|
## for col in items(p.headers):
|
||||||
## echo "##", col, ":", p.rowEntry(col), "##"
|
## echo "##", col, ":", p.rowEntry(col), "##"
|
||||||
## p.close()
|
## p.close()
|
||||||
|
##
|
||||||
|
## See also
|
||||||
|
## ========
|
||||||
|
##
|
||||||
|
## * `streams module <streams.html>`_ for using
|
||||||
|
## `open proc <#open,CsvParser,Stream,string,Char,Char,Char>`_
|
||||||
|
## and other stream processing (like `close proc <streams.html#close,Stream>`_)
|
||||||
|
## * `parseopt module <parseopt.html>`_ for a command line parser
|
||||||
|
## * `parsecfg module <parsecfg.html>`_ for a configuration file parser
|
||||||
|
## * `parsexml module <parsexml.html>`_ for a XML / HTML parser
|
||||||
|
## * `parsesql module <parsesql.html>`_ for a SQL parser
|
||||||
|
## * `other parsers <lib.html#pure-libraries-parsers>`_ for other parsers
|
||||||
|
|
||||||
import
|
import
|
||||||
lexbase, streams
|
lexbase, streams
|
||||||
|
|
||||||
type
|
type
|
||||||
CsvRow* = seq[string] ## a row in a CSV file
|
CsvRow* = seq[string] ## A row in a CSV file.
|
||||||
CsvParser* = object of BaseLexer ## the parser object.
|
CsvParser* = object of BaseLexer ## The parser object.
|
||||||
row*: CsvRow ## the current row
|
##
|
||||||
|
## It consists of two public fields:
|
||||||
|
## * `row` is the current row
|
||||||
|
## * `headers` are the columns that are defined in the csv file
|
||||||
|
## (read using `readHeaderRow <#readHeaderRow,CsvParser>`_).
|
||||||
|
## Used with `rowEntry <#rowEntry,CsvParser,string>`_).
|
||||||
|
row*: CsvRow
|
||||||
filename: string
|
filename: string
|
||||||
sep, quote, esc: char
|
sep, quote, esc: char
|
||||||
skipWhite: bool
|
skipWhite: bool
|
||||||
currRow: int
|
currRow: int
|
||||||
headers*: seq[string] ## The columns that are defined in the csv file
|
headers*: seq[string]
|
||||||
## (read using `readHeaderRow <#readHeaderRow.CsvParser>`_).
|
|
||||||
## Used with `rowEntry <#rowEntry.CsvParser.string>`_).
|
|
||||||
|
|
||||||
CsvError* = object of IOError ## exception that is raised if
|
CsvError* = object of IOError ## An exception that is raised if
|
||||||
## a parsing error occurs
|
## a parsing error occurs.
|
||||||
|
|
||||||
proc raiseEInvalidCsv(filename: string, line, col: int,
|
proc raiseEInvalidCsv(filename: string, line, col: int,
|
||||||
msg: string) {.noreturn.} =
|
msg: string) {.noreturn.} =
|
||||||
|
|
@ -82,7 +103,7 @@ proc error(my: CsvParser, pos: int, msg: string) =
|
||||||
proc open*(my: var CsvParser, input: Stream, filename: string,
|
proc open*(my: var CsvParser, input: Stream, filename: string,
|
||||||
separator = ',', quote = '"', escape = '\0',
|
separator = ',', quote = '"', escape = '\0',
|
||||||
skipInitialSpace = false) =
|
skipInitialSpace = false) =
|
||||||
## initializes the parser with an input stream. `Filename` is only used
|
## Initializes the parser with an input stream. `Filename` is only used
|
||||||
## for nice error messages. The parser's behaviour can be controlled by
|
## for nice error messages. The parser's behaviour can be controlled by
|
||||||
## the diverse optional parameters:
|
## the diverse optional parameters:
|
||||||
## - `separator`: character used to separate fields
|
## - `separator`: character used to separate fields
|
||||||
|
|
@ -94,6 +115,18 @@ proc open*(my: var CsvParser, input: Stream, filename: string,
|
||||||
## two `quote` characters are parsed one literal `quote` character.
|
## two `quote` characters are parsed one literal `quote` character.
|
||||||
## - `skipInitialSpace`: If true, whitespace immediately following the
|
## - `skipInitialSpace`: If true, whitespace immediately following the
|
||||||
## `separator` is ignored.
|
## `separator` is ignored.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `open proc <#open,CsvParser,string,Char,Char,Char>`_ which creates the
|
||||||
|
## file stream for you
|
||||||
|
runnableExamples:
|
||||||
|
import streams
|
||||||
|
var strm = newStringStream("One,Two,Three\n1,2,3\n10,20,30")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open(strm, "tmp.csv")
|
||||||
|
parser.close()
|
||||||
|
strm.close()
|
||||||
|
|
||||||
lexbase.open(my, input)
|
lexbase.open(my, input)
|
||||||
my.filename = filename
|
my.filename = filename
|
||||||
my.sep = separator
|
my.sep = separator
|
||||||
|
|
@ -106,7 +139,16 @@ proc open*(my: var CsvParser, input: Stream, filename: string,
|
||||||
proc open*(my: var CsvParser, filename: string,
|
proc open*(my: var CsvParser, filename: string,
|
||||||
separator = ',', quote = '"', escape = '\0',
|
separator = ',', quote = '"', escape = '\0',
|
||||||
skipInitialSpace = false) =
|
skipInitialSpace = false) =
|
||||||
## same as the other `open` but creates the file stream for you.
|
## Similar to the `other open proc<#open,CsvParser,Stream,string,Char,Char,Char>`_,
|
||||||
|
## but creates the file stream for you.
|
||||||
|
runnableExamples:
|
||||||
|
from os import removeFile
|
||||||
|
writeFile("tmp.csv", "One,Two,Three\n1,2,3\n10,20,300")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open("tmp.csv")
|
||||||
|
parser.close()
|
||||||
|
removeFile("tmp.csv")
|
||||||
|
|
||||||
var s = newFileStream(filename, fmRead)
|
var s = newFileStream(filename, fmRead)
|
||||||
if s == nil: my.error(0, "cannot open: " & filename)
|
if s == nil: my.error(0, "cannot open: " & filename)
|
||||||
open(my, s, filename, separator,
|
open(my, s, filename, separator,
|
||||||
|
|
@ -159,17 +201,66 @@ proc parseField(my: var CsvParser, a: var string) =
|
||||||
my.bufpos = pos
|
my.bufpos = pos
|
||||||
|
|
||||||
proc processedRows*(my: var CsvParser): int =
|
proc processedRows*(my: var CsvParser): int =
|
||||||
## returns number of the processed rows
|
## Returns number of the processed rows.
|
||||||
|
##
|
||||||
|
## But even if `readRow <#readRow,CsvParser,int>`_ arrived at EOF then
|
||||||
|
## processed rows counter is incremented.
|
||||||
|
runnableExamples:
|
||||||
|
import streams
|
||||||
|
|
||||||
|
var strm = newStringStream("One,Two,Three\n1,2,3")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open(strm, "tmp.csv")
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.processedRows() == 1
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.processedRows() == 2
|
||||||
|
## Even if `readRow` arrived at EOF then `processedRows` is incremented.
|
||||||
|
doAssert parser.readRow() == false
|
||||||
|
doAssert parser.processedRows() == 3
|
||||||
|
doAssert parser.readRow() == false
|
||||||
|
doAssert parser.processedRows() == 4
|
||||||
|
parser.close()
|
||||||
|
strm.close()
|
||||||
|
|
||||||
return my.currRow
|
return my.currRow
|
||||||
|
|
||||||
proc readRow*(my: var CsvParser, columns = 0): bool =
|
proc readRow*(my: var CsvParser, columns = 0): bool =
|
||||||
## reads the next row; if `columns` > 0, it expects the row to have
|
## Reads the next row; if `columns` > 0, it expects the row to have
|
||||||
## exactly this many columns. Returns false if the end of the file
|
## exactly this many columns. Returns false if the end of the file
|
||||||
## has been encountered else true.
|
## has been encountered else true.
|
||||||
##
|
##
|
||||||
## Blank lines are skipped.
|
## Blank lines are skipped.
|
||||||
|
runnableExamples:
|
||||||
|
import streams
|
||||||
|
var strm = newStringStream("One,Two,Three\n1,2,3\n\n10,20,30")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open(strm, "tmp.csv")
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.row == @["One", "Two", "Three"]
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.row == @["1", "2", "3"]
|
||||||
|
## Blank lines are skipped.
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.row == @["10", "20", "30"]
|
||||||
|
|
||||||
|
var emptySeq: seq[string]
|
||||||
|
doAssert parser.readRow() == false
|
||||||
|
doAssert parser.row == emptySeq
|
||||||
|
doAssert parser.readRow() == false
|
||||||
|
doAssert parser.row == emptySeq
|
||||||
|
|
||||||
|
parser.close()
|
||||||
|
strm.close()
|
||||||
|
|
||||||
var col = 0 # current column
|
var col = 0 # current column
|
||||||
let oldpos = my.bufpos
|
let oldpos = my.bufpos
|
||||||
|
# skip initial empty lines #8365
|
||||||
|
while true:
|
||||||
|
case my.buf[my.bufpos]
|
||||||
|
of '\c': my.bufpos = handleCR(my, my.bufpos)
|
||||||
|
of '\l': my.bufpos = handleLF(my, my.bufpos)
|
||||||
|
else: break
|
||||||
while my.buf[my.bufpos] != '\0':
|
while my.buf[my.bufpos] != '\0':
|
||||||
let oldlen = my.row.len
|
let oldlen = my.row.len
|
||||||
if oldlen < col+1:
|
if oldlen < col+1:
|
||||||
|
|
@ -200,12 +291,31 @@ proc readRow*(my: var CsvParser, columns = 0): bool =
|
||||||
inc(my.currRow)
|
inc(my.currRow)
|
||||||
|
|
||||||
proc close*(my: var CsvParser) {.inline.} =
|
proc close*(my: var CsvParser) {.inline.} =
|
||||||
## closes the parser `my` and its associated input stream.
|
## Closes the parser `my` and its associated input stream.
|
||||||
lexbase.close(my)
|
lexbase.close(my)
|
||||||
|
|
||||||
proc readHeaderRow*(my: var CsvParser) =
|
proc readHeaderRow*(my: var CsvParser) =
|
||||||
## Reads the first row and creates a look-up table for column numbers
|
## Reads the first row and creates a look-up table for column numbers
|
||||||
## See also `rowEntry <#rowEntry.CsvParser.string>`_.
|
## See also:
|
||||||
|
## * `rowEntry proc <#rowEntry,CsvParser,string>`_
|
||||||
|
runnableExamples:
|
||||||
|
import streams
|
||||||
|
|
||||||
|
var strm = newStringStream("One,Two,Three\n1,2,3")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open(strm, "tmp.csv")
|
||||||
|
|
||||||
|
parser.readHeaderRow()
|
||||||
|
doAssert parser.headers == @["One", "Two", "Three"]
|
||||||
|
doAssert parser.row == @["One", "Two", "Three"]
|
||||||
|
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.headers == @["One", "Two", "Three"]
|
||||||
|
doAssert parser.row == @["1", "2", "3"]
|
||||||
|
|
||||||
|
parser.close()
|
||||||
|
strm.close()
|
||||||
|
|
||||||
let present = my.readRow()
|
let present = my.readRow()
|
||||||
if present:
|
if present:
|
||||||
my.headers = my.row
|
my.headers = my.row
|
||||||
|
|
@ -213,8 +323,23 @@ proc readHeaderRow*(my: var CsvParser) =
|
||||||
proc rowEntry*(my: var CsvParser, entry: string): var string =
|
proc rowEntry*(my: var CsvParser, entry: string): var string =
|
||||||
## Acceses a specified `entry` from the current row.
|
## Acceses a specified `entry` from the current row.
|
||||||
##
|
##
|
||||||
## Assumes that `readHeaderRow <#readHeaderRow.CsvParser>`_ has already been
|
## Assumes that `readHeaderRow <#readHeaderRow,CsvParser>`_ has already been
|
||||||
## called.
|
## called.
|
||||||
|
runnableExamples:
|
||||||
|
import streams
|
||||||
|
var strm = newStringStream("One,Two,Three\n1,2,3\n\n10,20,30")
|
||||||
|
var parser: CsvParser
|
||||||
|
parser.open(strm, "tmp.csv")
|
||||||
|
## Need calling `readHeaderRow`.
|
||||||
|
parser.readHeaderRow()
|
||||||
|
doAssert parser.readRow()
|
||||||
|
doAssert parser.rowEntry("One") == "1"
|
||||||
|
doAssert parser.rowEntry("Two") == "2"
|
||||||
|
doAssert parser.rowEntry("Three") == "3"
|
||||||
|
## `parser.rowEntry("NotExistEntry")` causes SIGSEGV fault.
|
||||||
|
parser.close()
|
||||||
|
strm.close()
|
||||||
|
|
||||||
let index = my.headers.find(entry)
|
let index = my.headers.find(entry)
|
||||||
if index >= 0:
|
if index >= 0:
|
||||||
result = my.row[index]
|
result = my.row[index]
|
||||||
|
|
@ -235,7 +360,7 @@ when isMainModule:
|
||||||
import os
|
import os
|
||||||
import strutils
|
import strutils
|
||||||
block: # Tests for reading the header row
|
block: # Tests for reading the header row
|
||||||
let content = "One,Two,Three,Four\n1,2,3,4\n10,20,30,40,\n100,200,300,400\n"
|
let content = "\nOne,Two,Three,Four\n1,2,3,4\n10,20,30,40,\n100,200,300,400\n"
|
||||||
writeFile("temp.csv", content)
|
writeFile("temp.csv", content)
|
||||||
|
|
||||||
var p: CsvParser
|
var p: CsvParser
|
||||||
|
|
@ -262,4 +387,3 @@ when isMainModule:
|
||||||
|
|
||||||
# Tidy up
|
# Tidy up
|
||||||
removeFile("temp.csv")
|
removeFile("temp.csv")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,141 @@
|
||||||
## It supports one convenience iterator over all command line options and some
|
## It supports one convenience iterator over all command line options and some
|
||||||
## lower-level features.
|
## lower-level features.
|
||||||
##
|
##
|
||||||
## Supported syntax with default empty ``shortNoVal``/``longNoVal``:
|
## Supported Syntax
|
||||||
|
## ================
|
||||||
##
|
##
|
||||||
## 1. short options - ``-abcd``, where a, b, c, d are names
|
## The following syntax is supported when arguments for the ``shortNoVal`` and
|
||||||
## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo``
|
## ``longNoVal`` parameters, which are
|
||||||
## 3. argument - everything else
|
## `described later<#shortnoval-and-longnoval>`_, are not provided:
|
||||||
##
|
##
|
||||||
## When ``shortNoVal``/``longNoVal`` are non-empty then the ':' and '=' above
|
## 1. Short options: ``-abcd``, ``-e:5``, ``-e=5``
|
||||||
## are still accepted, but become optional. Note that these option key sets
|
## 2. Long options: ``--foo:bar``, ``--foo=bar``, ``--foo``
|
||||||
## must be updated along with the set of option keys taking no value, but
|
## 3. Arguments: everything that does not start with a ``-``
|
||||||
## keys which do take values need no special updates as their set evolves.
|
|
||||||
##
|
##
|
||||||
## When option values begin with ':' or '=' they need to be doubled up (as in
|
## These three kinds of tokens are enumerated in the
|
||||||
|
## `CmdLineKind enum<#CmdLineKind>`_.
|
||||||
|
##
|
||||||
|
## When option values begin with ':' or '=', they need to be doubled up (as in
|
||||||
## ``--delim::``) or alternated (as in ``--delim=:``).
|
## ``--delim::``) or alternated (as in ``--delim=:``).
|
||||||
##
|
##
|
||||||
## The common ``--`` non-option argument delimiter appears as an empty string
|
## The ``--`` option, commonly used to denote that every token that follows is
|
||||||
## long option key. ``OptParser.cmd``, ``OptParser.pos``, and
|
## an argument, is interpreted as a long option, and its name is the empty
|
||||||
## ``os.parseCmdLine`` may be used to complete parsing in that case.
|
## string.
|
||||||
|
##
|
||||||
|
## Parsing
|
||||||
|
## =======
|
||||||
|
##
|
||||||
|
## Use an `OptParser<#OptParser>`_ to parse command line options. It can be
|
||||||
|
## created with `initOptParser<#initOptParser,string,set[char],seq[string]>`_,
|
||||||
|
## and `next<#next,OptParser>`_ advances the parser by one token.
|
||||||
|
##
|
||||||
|
## For each token, the parser's ``kind``, ``key``, and ``val`` fields give
|
||||||
|
## information about that token. If the token is a long or short option, ``key``
|
||||||
|
## is the option's name, and ``val`` is either the option's value, if provided,
|
||||||
|
## or the empty string. For arguments, the ``key`` field contains the argument
|
||||||
|
## itself, and ``val`` is unused. To check if the end of the command line has
|
||||||
|
## been reached, check if ``kind`` is equal to ``cmdEnd``.
|
||||||
|
##
|
||||||
|
## Here is an example:
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import parseopt
|
||||||
|
##
|
||||||
|
## var p = initOptParser("-ab -e:5 --foo --bar=20 file.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## case p.kind
|
||||||
|
## of cmdEnd: break
|
||||||
|
## of cmdShortOption, cmdLongOption:
|
||||||
|
## if p.val == "":
|
||||||
|
## echo "Option: ", p.key
|
||||||
|
## else:
|
||||||
|
## echo "Option and value: ", p.key, ", ", p.val
|
||||||
|
## of cmdArgument:
|
||||||
|
## echo "Argument: ", p.key
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option: a
|
||||||
|
## # Option: b
|
||||||
|
## # Option and value: e, 5
|
||||||
|
## # Option: foo
|
||||||
|
## # Option and value: bar, 20
|
||||||
|
## # Argument: file.txt
|
||||||
|
##
|
||||||
|
## The `getopt iterator<#getopt.i,OptParser>`_, which is provided for
|
||||||
|
## convenience, can be used to iterate through all command line options as well.
|
||||||
|
##
|
||||||
|
## ``shortNoVal`` and ``longNoVal``
|
||||||
|
## ================================
|
||||||
|
##
|
||||||
|
## The optional ``shortNoVal`` and ``longNoVal`` parameters present in
|
||||||
|
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ are for
|
||||||
|
## specifying which short and long options do not accept values.
|
||||||
|
##
|
||||||
|
## When ``shortNoVal`` is non-empty, users are not required to separate short
|
||||||
|
## options and their values with a ':' or '=' since the parser knows which
|
||||||
|
## options accept values and which ones do not. This behavior also applies for
|
||||||
|
## long options if ``longNoVal`` is non-empty. For short options, ``-j4``
|
||||||
|
## becomes supported syntax, and for long options, ``--foo bar`` becomes
|
||||||
|
## supported. This is in addition to the `previously mentioned
|
||||||
|
## syntax<#supported-syntax>`_. Users can still separate options and their
|
||||||
|
## values with ':' or '=', but that becomes optional.
|
||||||
|
##
|
||||||
|
## As more options which do not accept values are added to your program,
|
||||||
|
## remember to amend ``shortNoVal`` and ``longNoVal`` accordingly.
|
||||||
|
##
|
||||||
|
## The following example illustrates the difference between having an empty
|
||||||
|
## ``shortNoVal`` and ``longNoVal``, which is the default, and providing
|
||||||
|
## arguments for those two parameters:
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import parseopt
|
||||||
|
##
|
||||||
|
## proc printToken(kind: CmdLineKind, key: string, val: string) =
|
||||||
|
## case kind
|
||||||
|
## of cmdEnd: doAssert(false) # Doesn't happen with getopt()
|
||||||
|
## of cmdShortOption, cmdLongOption:
|
||||||
|
## if val == "":
|
||||||
|
## echo "Option: ", key
|
||||||
|
## else:
|
||||||
|
## echo "Option and value: ", key, ", ", val
|
||||||
|
## of cmdArgument:
|
||||||
|
## echo "Argument: ", key
|
||||||
|
##
|
||||||
|
## let cmdLine = "-j4 --first bar"
|
||||||
|
##
|
||||||
|
## var emptyNoVal = initOptParser(cmdLine)
|
||||||
|
## for kind, key, val in emptyNoVal.getopt():
|
||||||
|
## printToken(kind, key, val)
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option: j
|
||||||
|
## # Option: 4
|
||||||
|
## # Option: first
|
||||||
|
## # Argument: bar
|
||||||
|
##
|
||||||
|
## var withNoVal = initOptParser(cmdLine, shortNoVal = {'c'},
|
||||||
|
## longNoVal = @["second"])
|
||||||
|
## for kind, key, val in withNoVal.getopt():
|
||||||
|
## printToken(kind, key, val)
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option and value: j, 4
|
||||||
|
## # Option and value: first, bar
|
||||||
|
##
|
||||||
|
## See also
|
||||||
|
## ========
|
||||||
|
##
|
||||||
|
## * `os module<os.html>`_ for lower-level command line parsing procs
|
||||||
|
## * `parseutils module<parseutils.html>`_ for helpers that parse tokens,
|
||||||
|
## numbers, identifiers, etc.
|
||||||
|
## * `strutils module<strutils.html>`_ for common string handling operations
|
||||||
|
## * `json module<json.html>`_ for a JSON parser
|
||||||
|
## * `parsecfg module<parsecfg.html>`_ for a configuration file parser
|
||||||
|
## * `parsecsv module<parsecsv.html>`_ for a simple CSV (comma separated value)
|
||||||
|
## parser
|
||||||
|
## * `parsexml module<parsexml.html>`_ for a XML / HTML parser
|
||||||
|
## * `other parsers<lib.html#pure-libraries-parsers>`_ for more parsers
|
||||||
|
|
||||||
{.push debugger: off.}
|
{.push debugger: off.}
|
||||||
|
|
||||||
|
|
@ -37,23 +155,26 @@ import
|
||||||
os, strutils
|
os, strutils
|
||||||
|
|
||||||
type
|
type
|
||||||
CmdLineKind* = enum ## the detected command line token
|
CmdLineKind* = enum ## The detected command line token.
|
||||||
cmdEnd, ## end of command line reached
|
cmdEnd, ## End of command line reached
|
||||||
cmdArgument, ## argument detected
|
cmdArgument, ## An argument such as a filename
|
||||||
cmdLongOption, ## a long option ``--option`` detected
|
cmdLongOption, ## A long option such as --option
|
||||||
cmdShortOption ## a short option ``-c`` detected
|
cmdShortOption ## A short option such as -c
|
||||||
OptParser* =
|
OptParser* =
|
||||||
object of RootObj ## this object implements the command line parser
|
object of RootObj ## Implementation of the command line parser.
|
||||||
pos*: int # ..empty key or subcmd cmdArg & handle specially
|
##
|
||||||
|
## To initialize it, use the
|
||||||
|
## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_.
|
||||||
|
pos*: int
|
||||||
inShortState: bool
|
inShortState: bool
|
||||||
allowWhitespaceAfterColon: bool
|
allowWhitespaceAfterColon: bool
|
||||||
shortNoVal: set[char]
|
shortNoVal: set[char]
|
||||||
longNoVal: seq[string]
|
longNoVal: seq[string]
|
||||||
cmds: seq[string]
|
cmds: seq[string]
|
||||||
idx: int
|
idx: int
|
||||||
kind*: CmdLineKind ## the dected command line token
|
kind*: CmdLineKind ## The detected command line token
|
||||||
key*, val*: TaintedString ## key and value pair; ``key`` is the option
|
key*, val*: TaintedString ## Key and value pair; the key is the option
|
||||||
## or the argument, ``value`` is not "" if
|
## or the argument, and the value is not "" if
|
||||||
## the option was given a value
|
## the option was given a value
|
||||||
|
|
||||||
proc parseWord(s: string, i: int, w: var string,
|
proc parseWord(s: string, i: int, w: var string,
|
||||||
|
|
@ -79,13 +200,24 @@ when declared(os.paramCount):
|
||||||
proc initOptParser*(cmdline = "", shortNoVal: set[char]={},
|
proc initOptParser*(cmdline = "", shortNoVal: set[char]={},
|
||||||
longNoVal: seq[string] = @[];
|
longNoVal: seq[string] = @[];
|
||||||
allowWhitespaceAfterColon = true): OptParser =
|
allowWhitespaceAfterColon = true): OptParser =
|
||||||
## inits the option parser. If ``cmdline == ""``, the real command line
|
## Initializes the command line parser.
|
||||||
## (as provided by the ``OS`` module) is taken. If ``shortNoVal`` is
|
##
|
||||||
## provided command users do not need to delimit short option keys and
|
## If ``cmdline == ""``, the real command line as provided by the
|
||||||
## values with a ':' or '='. If ``longNoVal`` is provided command users do
|
## ``os`` module is retrieved instead.
|
||||||
## not need to delimit long option keys and values with a ':' or '='
|
##
|
||||||
## (though they still need at least a space). In both cases, ':' or '='
|
## ``shortNoVal`` and ``longNoVal`` are used to specify which options
|
||||||
## may still be used if desired. They just become optional.
|
## do not take values. See the `documentation about these
|
||||||
|
## parameters<#shortnoval-and-longnoval>`_ for more information on
|
||||||
|
## how this affects parsing.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `getopt iterator<#getopt.i,OptParser>`_
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser()
|
||||||
|
p = initOptParser("--left --debug:3 -l -r:2")
|
||||||
|
p = initOptParser("--left --debug:3 -l -r:2",
|
||||||
|
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||||
|
|
||||||
result.pos = 0
|
result.pos = 0
|
||||||
result.idx = 0
|
result.idx = 0
|
||||||
result.inShortState = false
|
result.inShortState = false
|
||||||
|
|
@ -106,9 +238,21 @@ when declared(os.paramCount):
|
||||||
proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={},
|
proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={},
|
||||||
longNoVal: seq[string] = @[];
|
longNoVal: seq[string] = @[];
|
||||||
allowWhitespaceAfterColon = true): OptParser =
|
allowWhitespaceAfterColon = true): OptParser =
|
||||||
## inits the option parser. If ``cmdline.len == 0``, the real command line
|
## Initializes the command line parser.
|
||||||
## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and
|
##
|
||||||
## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``.
|
## If ``cmdline.len == 0``, the real command line as provided by the
|
||||||
|
## ``os`` module is retrieved instead. Behavior of the other parameters
|
||||||
|
## remains the same as in `initOptParser(string, ...)
|
||||||
|
## <#initOptParser,string,set[char],seq[string]>`_.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `getopt iterator<#getopt.i,seq[TaintedString],set[char],seq[string]>`_
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser()
|
||||||
|
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"])
|
||||||
|
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"],
|
||||||
|
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||||
|
|
||||||
result.pos = 0
|
result.pos = 0
|
||||||
result.idx = 0
|
result.idx = 0
|
||||||
result.inShortState = false
|
result.inShortState = false
|
||||||
|
|
@ -153,8 +297,21 @@ proc handleShortOption(p: var OptParser; cmd: string) =
|
||||||
inc p.idx
|
inc p.idx
|
||||||
|
|
||||||
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||||
## parses the first or next option; ``p.kind`` describes what token has been
|
## Parses the next token.
|
||||||
## parsed. ``p.key`` and ``p.val`` are set accordingly.
|
##
|
||||||
|
## ``p.kind`` describes what kind of token has been parsed. ``p.key`` and
|
||||||
|
## ``p.val`` are set accordingly.
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser("--left -r:2 file.txt")
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdLongOption and p.key == "left"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdShortOption and p.key == "r" and p.val == "2"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdArgument and p.key == "file.txt"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdEnd
|
||||||
|
|
||||||
if p.idx >= p.cmds.len:
|
if p.idx >= p.cmds.len:
|
||||||
p.kind = cmdEnd
|
p.kind = cmdEnd
|
||||||
return
|
return
|
||||||
|
|
@ -209,20 +366,61 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||||
|
|
||||||
when declared(os.paramCount):
|
when declared(os.paramCount):
|
||||||
proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} =
|
proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} =
|
||||||
## retrieves the rest of the command line that has not been parsed yet.
|
## Retrieves the rest of the command line that has not been parsed yet.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `remainingArgs proc<#remainingArgs,OptParser>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||||
|
## break
|
||||||
|
## else: continue
|
||||||
|
## doAssert p.cmdLineRest == "foo.txt bar.txt"
|
||||||
result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString
|
result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString
|
||||||
|
|
||||||
proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} =
|
proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} =
|
||||||
## retrieves the rest of the command line that has not been parsed yet.
|
## Retrieves a sequence of the arguments that have not been parsed yet.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `cmdLineRest proc<#cmdLineRest,OptParser>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||||
|
## break
|
||||||
|
## else: continue
|
||||||
|
## doAssert p.remainingArgs == @["foo.txt", "bar.txt"]
|
||||||
result = @[]
|
result = @[]
|
||||||
for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i])
|
for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i])
|
||||||
|
|
||||||
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] =
|
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||||
## This is an convenience iterator for iterating over the given OptParser object.
|
## Convenience iterator for iterating over the given
|
||||||
## Example:
|
## `OptParser<#OptParser>`_.
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## There is no need to check for ``cmdEnd`` while iterating.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## # these are placeholders, of course
|
||||||
|
## proc writeHelp() = discard
|
||||||
|
## proc writeVersion() = discard
|
||||||
|
##
|
||||||
|
## var filename: string
|
||||||
## var p = initOptParser("--left --debug:3 -l -r:2")
|
## var p = initOptParser("--left --debug:3 -l -r:2")
|
||||||
|
##
|
||||||
## for kind, key, val in p.getopt():
|
## for kind, key, val in p.getopt():
|
||||||
## case kind
|
## case kind
|
||||||
## of cmdArgument:
|
## of cmdArgument:
|
||||||
|
|
@ -233,7 +431,7 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt
|
||||||
## of "version", "v": writeVersion()
|
## of "version", "v": writeVersion()
|
||||||
## of cmdEnd: assert(false) # cannot happen
|
## of cmdEnd: assert(false) # cannot happen
|
||||||
## if filename == "":
|
## if filename == "":
|
||||||
## # no filename has been given, so we show the help:
|
## # no filename has been given, so we show the help
|
||||||
## writeHelp()
|
## writeHelp()
|
||||||
p.pos = 0
|
p.pos = 0
|
||||||
p.idx = 0
|
p.idx = 0
|
||||||
|
|
@ -246,15 +444,34 @@ when declared(initOptParser):
|
||||||
iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(),
|
iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(),
|
||||||
shortNoVal: set[char]={}, longNoVal: seq[string] = @[]):
|
shortNoVal: set[char]={}, longNoVal: seq[string] = @[]):
|
||||||
tuple[kind: CmdLineKind, key, val: TaintedString] =
|
tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||||
## This is an convenience iterator for iterating over command line arguments.
|
## Convenience iterator for iterating over command line arguments.
|
||||||
## This creates a new OptParser. See the above ``getopt(var OptParser)``
|
|
||||||
## example for using default empty ``NoVal`` parameters. This example is
|
|
||||||
## for the same option keys as that example but here option key-value
|
|
||||||
## separators become optional for command users:
|
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## This creates a new `OptParser<#OptParser>`_. If no command line
|
||||||
## for kind, key, val in getopt(shortNoVal = { 'l' },
|
## arguments are provided, the real command line as provided by the
|
||||||
## longNoVal = @[ "left" ]):
|
## ``os`` module is retrieved instead.
|
||||||
|
##
|
||||||
|
## ``shortNoVal`` and ``longNoVal`` are used to specify which options
|
||||||
|
## do not take values. See the `documentation about these
|
||||||
|
## parameters<#shortnoval-and-longnoval>`_ for more information on
|
||||||
|
## how this affects parsing.
|
||||||
|
##
|
||||||
|
## There is no need to check for ``cmdEnd`` while iterating.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `initOptParser proc<#initOptParser,seq[TaintedString],set[char],seq[string]>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
##
|
||||||
|
## # these are placeholders, of course
|
||||||
|
## proc writeHelp() = discard
|
||||||
|
## proc writeVersion() = discard
|
||||||
|
##
|
||||||
|
## var filename: string
|
||||||
|
## let params = @["--left", "--debug:3", "-l", "-r:2"]
|
||||||
|
##
|
||||||
|
## for kind, key, val in getopt(params):
|
||||||
## case kind
|
## case kind
|
||||||
## of cmdArgument:
|
## of cmdArgument:
|
||||||
## filename = key
|
## filename = key
|
||||||
|
|
@ -264,8 +481,8 @@ when declared(initOptParser):
|
||||||
## of "version", "v": writeVersion()
|
## of "version", "v": writeVersion()
|
||||||
## of cmdEnd: assert(false) # cannot happen
|
## of cmdEnd: assert(false) # cannot happen
|
||||||
## if filename == "":
|
## if filename == "":
|
||||||
|
## # no filename has been written, so we show the help
|
||||||
## writeHelp()
|
## writeHelp()
|
||||||
##
|
|
||||||
var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal)
|
var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal)
|
||||||
while true:
|
while true:
|
||||||
next(p)
|
next(p)
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@
|
||||||
## import uri
|
## import uri
|
||||||
## let res = parseUri("sftp://127.0.0.1:4343")
|
## let res = parseUri("sftp://127.0.0.1:4343")
|
||||||
## if isAbsolute(res):
|
## if isAbsolute(res):
|
||||||
## echo "Connect to port: " & res.port
|
## assert res.port == "4343"
|
||||||
## # --> Connect to port: 4343
|
|
||||||
## else:
|
## else:
|
||||||
## echo "Wrong format"
|
## echo "Wrong format"
|
||||||
|
|
||||||
|
|
@ -189,7 +188,7 @@ proc parseUri*(uri: string, result: var Uri) =
|
||||||
##
|
##
|
||||||
## **See also:**
|
## **See also:**
|
||||||
## * `Uri type <#Uri>`_ for available fields in the URI type
|
## * `Uri type <#Uri>`_ for available fields in the URI type
|
||||||
## * `initUri proc <#initUri,>`_ for initializing a URI
|
## * `initUri proc <#initUri>`_ for initializing a URI
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
var res = initUri()
|
var res = initUri()
|
||||||
parseUri("https://nim-lang.org/docs/manual.html", res)
|
parseUri("https://nim-lang.org/docs/manual.html", res)
|
||||||
|
|
@ -343,9 +342,9 @@ proc combine*(uris: varargs[Uri]): Uri =
|
||||||
## **See also:**
|
## **See also:**
|
||||||
## * `/ proc <#/,Uri,string>`_ for building URIs
|
## * `/ proc <#/,Uri,string>`_ for building URIs
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
let foo = combine(parseUri("https://nim-lang.org/blog.html"), parseUri("/install.html"))
|
let foo = combine(parseUri("https://nim-lang.org/"), parseUri("docs/"), parseUri("manual.html"))
|
||||||
assert foo.hostname == "nim-lang.org"
|
assert foo.hostname == "nim-lang.org"
|
||||||
assert foo.path == "/install.html"
|
assert foo.path == "/docs/manual.html"
|
||||||
result = uris[0]
|
result = uris[0]
|
||||||
for i in 1 ..< uris.len:
|
for i in 1 ..< uris.len:
|
||||||
result = combine(result, uris[i])
|
result = combine(result, uris[i])
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,52 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
## A simple XML tree.
|
## A simple XML tree generator.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import xmltree
|
||||||
|
##
|
||||||
|
## var g = newElement("myTag")
|
||||||
|
## g.add newText("some text")
|
||||||
|
## g.add newComment("this is comment")
|
||||||
|
##
|
||||||
|
## var h = newElement("secondTag")
|
||||||
|
## h.add newEntity("some entity")
|
||||||
|
##
|
||||||
|
## let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
## let k = newXmlTree("treeTag", [g, h], att)
|
||||||
|
##
|
||||||
|
## echo k
|
||||||
|
## # <treeTag key2="second value" key1="first value">
|
||||||
|
## # <myTag>some text<!-- this is comment --></myTag>
|
||||||
|
## # <secondTag>&some entity;</secondTag>
|
||||||
|
## # </treeTag>
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## **See also:**
|
||||||
|
## * `xmlparser module <xmlparser.html>`_ for high-level XML parsing
|
||||||
|
## * `parsexml module <parsexml.html>`_ for low-level XML parsing
|
||||||
|
## * `htmlgen module <htmlgen.html>`_ for html code generator
|
||||||
|
|
||||||
import macros, strtabs, strutils
|
import macros, strtabs, strutils
|
||||||
|
|
||||||
type
|
type
|
||||||
XmlNode* = ref XmlNodeObj ## an XML tree consists of ``XmlNode``'s.
|
XmlNode* = ref XmlNodeObj ## An XML tree consisting of XML nodes.
|
||||||
|
##
|
||||||
|
## Use `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_
|
||||||
|
## for creating a new tree.
|
||||||
|
|
||||||
XmlNodeKind* = enum ## different kinds of ``XmlNode``'s
|
XmlNodeKind* = enum ## Different kinds of XML nodes.
|
||||||
xnText, ## a text element
|
xnText, ## a text element
|
||||||
xnElement, ## an element with 0 or more children
|
xnElement, ## an element with 0 or more children
|
||||||
xnCData, ## a CDATA node
|
xnCData, ## a CDATA node
|
||||||
xnEntity, ## an entity (like ``&thing;``)
|
xnEntity, ## an entity (like ``&thing;``)
|
||||||
xnComment ## an XML comment
|
xnComment ## an XML comment
|
||||||
|
|
||||||
XmlAttributes* = StringTableRef ## an alias for a string to string mapping
|
XmlAttributes* = StringTableRef ## An alias for a string to string mapping.
|
||||||
|
##
|
||||||
|
## Use `toXmlAttributes proc <#toXmlAttributes,varargs[tuple[string,string]]>`_
|
||||||
|
## to create `XmlAttributes`.
|
||||||
|
|
||||||
XmlNodeObj {.acyclic.} = object
|
XmlNodeObj {.acyclic.} = object
|
||||||
case k: XmlNodeKind # private, use the kind() proc to read this field.
|
case k: XmlNodeKind # private, use the kind() proc to read this field.
|
||||||
|
|
@ -33,67 +64,203 @@ type
|
||||||
fAttr: XmlAttributes
|
fAttr: XmlAttributes
|
||||||
fClientData: int ## for other clients
|
fClientData: int ## for other clients
|
||||||
|
|
||||||
|
const
|
||||||
|
xmlHeader* = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
|
||||||
|
## Header to use for complete XML output.
|
||||||
|
|
||||||
proc newXmlNode(kind: XmlNodeKind): XmlNode =
|
proc newXmlNode(kind: XmlNodeKind): XmlNode =
|
||||||
## creates a new ``XmlNode``.
|
## Creates a new ``XmlNode``.
|
||||||
new(result)
|
new(result)
|
||||||
result.k = kind
|
result.k = kind
|
||||||
|
|
||||||
proc newElement*(tag: string): XmlNode =
|
proc newElement*(tag: string): XmlNode =
|
||||||
## creates a new ``PXmlNode`` of kind ``xnText`` with the given `tag`.
|
## Creates a new ``XmlNode`` of kind ``xnElement`` with the given `tag`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_
|
||||||
|
## * [<> macro](#<>.m,untyped)
|
||||||
|
runnableExamples:
|
||||||
|
var a = newElement("firstTag")
|
||||||
|
a.add newElement("childTag")
|
||||||
|
assert a.kind == xnElement
|
||||||
|
assert $a == "<firstTag><childTag /></firstTag>"
|
||||||
|
|
||||||
result = newXmlNode(xnElement)
|
result = newXmlNode(xnElement)
|
||||||
result.fTag = tag
|
result.fTag = tag
|
||||||
result.s = @[]
|
result.s = @[]
|
||||||
# init attributes lazily to safe memory
|
# init attributes lazily to save memory
|
||||||
|
|
||||||
proc newText*(text: string): XmlNode =
|
proc newText*(text: string): XmlNode =
|
||||||
## creates a new ``PXmlNode`` of kind ``xnText`` with the text `text`.
|
## Creates a new ``XmlNode`` of kind ``xnText`` with the text `text`.
|
||||||
|
runnableExamples:
|
||||||
|
var b = newText("my text")
|
||||||
|
assert b.kind == xnText
|
||||||
|
assert $b == "my text"
|
||||||
|
|
||||||
result = newXmlNode(xnText)
|
result = newXmlNode(xnText)
|
||||||
result.fText = text
|
result.fText = text
|
||||||
|
|
||||||
proc newComment*(comment: string): XmlNode =
|
proc newComment*(comment: string): XmlNode =
|
||||||
## creates a new ``PXmlNode`` of kind ``xnComment`` with the text `comment`.
|
## Creates a new ``XmlNode`` of kind ``xnComment`` with the text `comment`.
|
||||||
|
runnableExamples:
|
||||||
|
var c = newComment("my comment")
|
||||||
|
assert c.kind == xnComment
|
||||||
|
assert $c == "<!-- my comment -->"
|
||||||
|
|
||||||
result = newXmlNode(xnComment)
|
result = newXmlNode(xnComment)
|
||||||
result.fText = comment
|
result.fText = comment
|
||||||
|
|
||||||
proc newCData*(cdata: string): XmlNode =
|
proc newCData*(cdata: string): XmlNode =
|
||||||
## creates a new ``PXmlNode`` of kind ``xnComment`` with the text `cdata`.
|
## Creates a new ``XmlNode`` of kind ``xnCData`` with the text `cdata`.
|
||||||
|
runnableExamples:
|
||||||
|
var d = newCData("my cdata")
|
||||||
|
assert d.kind == xnCData
|
||||||
|
assert $d == "<![CDATA[my cdata]]>"
|
||||||
|
|
||||||
result = newXmlNode(xnCData)
|
result = newXmlNode(xnCData)
|
||||||
result.fText = cdata
|
result.fText = cdata
|
||||||
|
|
||||||
proc newEntity*(entity: string): XmlNode =
|
proc newEntity*(entity: string): XmlNode =
|
||||||
## creates a new ``PXmlNode`` of kind ``xnEntity`` with the text `entity`.
|
## Creates a new ``XmlNode`` of kind ``xnEntity`` with the text `entity`.
|
||||||
|
runnableExamples:
|
||||||
|
var e = newEntity("my entity")
|
||||||
|
assert e.kind == xnEntity
|
||||||
|
assert $e == "&my entity;"
|
||||||
|
|
||||||
result = newXmlNode(xnEntity)
|
result = newXmlNode(xnEntity)
|
||||||
result.fText = entity
|
result.fText = entity
|
||||||
|
|
||||||
|
proc newXmlTree*(tag: string, children: openArray[XmlNode],
|
||||||
|
attributes: XmlAttributes = nil): XmlNode =
|
||||||
|
## Creates a new XML tree with `tag`, `children` and `attributes`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `newElement proc <#newElement,string>`_
|
||||||
|
## * [<> macro](#<>.m,untyped)
|
||||||
|
runnableExamples:
|
||||||
|
from strutils import unindent
|
||||||
|
var g = newElement("myTag")
|
||||||
|
g.add newText("some text")
|
||||||
|
g.add newComment("this is comment")
|
||||||
|
var h = newElement("secondTag")
|
||||||
|
h.add newEntity("some entity")
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
let k = newXmlTree("treeTag", [g, h], att)
|
||||||
|
assert ($k).unindent == """<treeTag key2="second value" key1="first value">
|
||||||
|
<myTag>some text<!-- this is comment --></myTag>
|
||||||
|
<secondTag>&some entity;</secondTag>
|
||||||
|
</treeTag>""".unindent
|
||||||
|
|
||||||
|
result = newXmlNode(xnElement)
|
||||||
|
result.fTag = tag
|
||||||
|
newSeq(result.s, children.len)
|
||||||
|
for i in 0..children.len-1: result.s[i] = children[i]
|
||||||
|
result.fAttr = attributes
|
||||||
|
|
||||||
proc text*(n: XmlNode): string {.inline.} =
|
proc text*(n: XmlNode): string {.inline.} =
|
||||||
## gets the associated text with the node `n`. `n` can be a CDATA, Text,
|
## Gets the associated text with the node `n`.
|
||||||
## comment, or entity node.
|
##
|
||||||
|
## `n` can be a CDATA, Text, comment, or entity node.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `text= proc <#text=,XmlNode,string>`_ for text setter
|
||||||
|
## * `tag proc <#tag,XmlNode>`_ for tag getter
|
||||||
|
## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
|
||||||
|
## * `innerText proc <#innerText,XmlNode>`_
|
||||||
|
runnableExamples:
|
||||||
|
var c = newComment("my comment")
|
||||||
|
assert $c == "<!-- my comment -->"
|
||||||
|
assert c.text == "my comment"
|
||||||
|
|
||||||
assert n.k in {xnText, xnComment, xnCData, xnEntity}
|
assert n.k in {xnText, xnComment, xnCData, xnEntity}
|
||||||
result = n.fText
|
result = n.fText
|
||||||
|
|
||||||
proc `text=`*(n: XmlNode, text: string){.inline.} =
|
proc `text=`*(n: XmlNode, text: string){.inline.} =
|
||||||
## sets the associated text with the node `n`. `n` can be a CDATA, Text,
|
## Sets the associated text with the node `n`.
|
||||||
## comment, or entity node.
|
##
|
||||||
|
## `n` can be a CDATA, Text, comment, or entity node.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `text proc <#text,XmlNode>`_ for text getter
|
||||||
|
## * `tag proc <#tag,XmlNode>`_ for tag getter
|
||||||
|
## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
|
||||||
|
runnableExamples:
|
||||||
|
var e = newEntity("my entity")
|
||||||
|
assert $e == "&my entity;"
|
||||||
|
e.text = "a new entity text"
|
||||||
|
assert $e == "&a new entity text;"
|
||||||
|
|
||||||
assert n.k in {xnText, xnComment, xnCData, xnEntity}
|
assert n.k in {xnText, xnComment, xnCData, xnEntity}
|
||||||
n.fText = text
|
n.fText = text
|
||||||
|
|
||||||
|
proc tag*(n: XmlNode): string {.inline.} =
|
||||||
|
## Gets the tag name of `n`.
|
||||||
|
##
|
||||||
|
## `n` has to be an ``xnElement`` node.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `text proc <#text,XmlNode>`_ for text getter
|
||||||
|
## * `text= proc <#text=,XmlNode,string>`_ for text setter
|
||||||
|
## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
|
||||||
|
## * `innerText proc <#innerText,XmlNode>`_
|
||||||
|
runnableExamples:
|
||||||
|
var a = newElement("firstTag")
|
||||||
|
a.add newElement("childTag")
|
||||||
|
assert $a == "<firstTag><childTag /></firstTag>"
|
||||||
|
assert a.tag == "firstTag"
|
||||||
|
|
||||||
|
assert n.k == xnElement
|
||||||
|
result = n.fTag
|
||||||
|
|
||||||
|
proc `tag=`*(n: XmlNode, tag: string) {.inline.} =
|
||||||
|
## Sets the tag name of `n`.
|
||||||
|
##
|
||||||
|
## `n` has to be an ``xnElement`` node.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `text proc <#text,XmlNode>`_ for text getter
|
||||||
|
## * `text= proc <#text=,XmlNode,string>`_ for text setter
|
||||||
|
## * `tag proc <#tag,XmlNode>`_ for tag getter
|
||||||
|
runnableExamples:
|
||||||
|
var a = newElement("firstTag")
|
||||||
|
a.add newElement("childTag")
|
||||||
|
assert $a == "<firstTag><childTag /></firstTag>"
|
||||||
|
a.tag = "newTag"
|
||||||
|
assert $a == "<newTag><childTag /></newTag>"
|
||||||
|
|
||||||
|
assert n.k == xnElement
|
||||||
|
n.fTag = tag
|
||||||
|
|
||||||
proc rawText*(n: XmlNode): string {.inline.} =
|
proc rawText*(n: XmlNode): string {.inline.} =
|
||||||
## returns the underlying 'text' string by reference.
|
## Returns the underlying 'text' string by reference.
|
||||||
|
##
|
||||||
## This is only used for speed hacks.
|
## This is only used for speed hacks.
|
||||||
shallowCopy(result, n.fText)
|
shallowCopy(result, n.fText)
|
||||||
|
|
||||||
proc rawTag*(n: XmlNode): string {.inline.} =
|
proc rawTag*(n: XmlNode): string {.inline.} =
|
||||||
## returns the underlying 'tag' string by reference.
|
## Returns the underlying 'tag' string by reference.
|
||||||
|
##
|
||||||
## This is only used for speed hacks.
|
## This is only used for speed hacks.
|
||||||
shallowCopy(result, n.fTag)
|
shallowCopy(result, n.fTag)
|
||||||
|
|
||||||
proc innerText*(n: XmlNode): string =
|
proc innerText*(n: XmlNode): string =
|
||||||
## gets the inner text of `n`:
|
## Gets the inner text of `n`:
|
||||||
##
|
##
|
||||||
## - If `n` is `xnText` or `xnEntity`, returns its content.
|
## - If `n` is `xnText` or `xnEntity`, returns its content.
|
||||||
## - If `n` is `xnElement`, runs recursively on each child node and
|
## - If `n` is `xnElement`, runs recursively on each child node and
|
||||||
## concatenates the results.
|
## concatenates the results.
|
||||||
## - Otherwise returns an empty string.
|
## - Otherwise returns an empty string.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `text proc <#text,XmlNode>`_
|
||||||
|
runnableExamples:
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newText("my text")
|
||||||
|
f.add newComment("my comment")
|
||||||
|
f.add newEntity("my entity")
|
||||||
|
assert $f == "<myTag>my text<!-- my comment -->&my entity;</myTag>"
|
||||||
|
assert innerText(f) == "my textmy entity"
|
||||||
|
|
||||||
proc worker(res: var string, n: XmlNode) =
|
proc worker(res: var string, n: XmlNode) =
|
||||||
case n.k
|
case n.k
|
||||||
of xnText, xnEntity:
|
of xnText, xnEntity:
|
||||||
|
|
@ -107,89 +274,218 @@ proc innerText*(n: XmlNode): string =
|
||||||
result = ""
|
result = ""
|
||||||
worker(result, n)
|
worker(result, n)
|
||||||
|
|
||||||
proc tag*(n: XmlNode): string {.inline.} =
|
|
||||||
## gets the tag name of `n`. `n` has to be an ``xnElement`` node.
|
|
||||||
assert n.k == xnElement
|
|
||||||
result = n.fTag
|
|
||||||
|
|
||||||
proc `tag=`*(n: XmlNode, tag: string) {.inline.} =
|
|
||||||
## sets the tag name of `n`. `n` has to be an ``xnElement`` node.
|
|
||||||
assert n.k == xnElement
|
|
||||||
n.fTag = tag
|
|
||||||
|
|
||||||
proc add*(father, son: XmlNode) {.inline.} =
|
proc add*(father, son: XmlNode) {.inline.} =
|
||||||
## adds the child `son` to `father`.
|
## Adds the child `son` to `father`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `insert proc <#insert,XmlNode,XmlNode,int>`_
|
||||||
|
## * `delete proc <#delete,XmlNode,Natural>`_
|
||||||
|
runnableExamples:
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newText("my text")
|
||||||
|
f.add newElement("sonTag")
|
||||||
|
f.add newEntity("my entity")
|
||||||
|
assert $f == "<myTag>my text<sonTag />&my entity;</myTag>"
|
||||||
add(father.s, son)
|
add(father.s, son)
|
||||||
|
|
||||||
proc insert*(father, son: XmlNode, index: int) {.inline.} =
|
proc insert*(father, son: XmlNode, index: int) {.inline.} =
|
||||||
## insert the child `son` to a given position in `father`.
|
## Insert the child `son` to a given position in `father`.
|
||||||
|
##
|
||||||
|
## `father` and `son` must be of `xnElement` kind.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `add proc <#add,XmlNode,XmlNode>`_
|
||||||
|
## * `delete proc <#delete,XmlNode,Natural>`_
|
||||||
|
runnableExamples:
|
||||||
|
from strutils import unindent
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newElement("first")
|
||||||
|
f.insert(newElement("second"), 0)
|
||||||
|
assert ($f).unindent == "<myTag>\n<second />\n<first />\n</myTag>"
|
||||||
|
|
||||||
assert father.k == xnElement and son.k == xnElement
|
assert father.k == xnElement and son.k == xnElement
|
||||||
if len(father.s) > index:
|
if len(father.s) > index:
|
||||||
insert(father.s, son, index)
|
insert(father.s, son, index)
|
||||||
else:
|
else:
|
||||||
insert(father.s, son, len(father.s))
|
insert(father.s, son, len(father.s))
|
||||||
|
|
||||||
proc len*(n: XmlNode): int {.inline.} =
|
|
||||||
## returns the number `n`'s children.
|
|
||||||
if n.k == xnElement: result = len(n.s)
|
|
||||||
|
|
||||||
proc kind*(n: XmlNode): XmlNodeKind {.inline.} =
|
|
||||||
## returns `n`'s kind.
|
|
||||||
result = n.k
|
|
||||||
|
|
||||||
proc `[]`* (n: XmlNode, i: int): XmlNode {.inline.} =
|
|
||||||
## returns the `i`'th child of `n`.
|
|
||||||
assert n.k == xnElement
|
|
||||||
result = n.s[i]
|
|
||||||
|
|
||||||
proc delete*(n: XmlNode, i: Natural) {.noSideEffect.} =
|
proc delete*(n: XmlNode, i: Natural) {.noSideEffect.} =
|
||||||
## delete the `i`'th child of `n`.
|
## Delete the `i`'th child of `n`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `add proc <#add,XmlNode,XmlNode>`_
|
||||||
|
## * `insert proc <#insert,XmlNode,XmlNode,int>`_
|
||||||
|
runnableExamples:
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newElement("first")
|
||||||
|
f.insert(newElement("second"), 0)
|
||||||
|
f.delete(0)
|
||||||
|
assert $f == "<myTag><first /></myTag>"
|
||||||
|
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
n.s.delete(i)
|
n.s.delete(i)
|
||||||
|
|
||||||
|
proc len*(n: XmlNode): int {.inline.} =
|
||||||
|
## Returns the number of `n`'s children.
|
||||||
|
runnableExamples:
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newElement("first")
|
||||||
|
f.insert(newElement("second"), 0)
|
||||||
|
assert len(f) == 2
|
||||||
|
if n.k == xnElement: result = len(n.s)
|
||||||
|
|
||||||
|
proc kind*(n: XmlNode): XmlNodeKind {.inline.} =
|
||||||
|
## Returns `n`'s kind.
|
||||||
|
runnableExamples:
|
||||||
|
var a = newElement("firstTag")
|
||||||
|
assert a.kind == xnElement
|
||||||
|
var b = newText("my text")
|
||||||
|
assert b.kind == xnText
|
||||||
|
result = n.k
|
||||||
|
|
||||||
|
proc `[]`* (n: XmlNode, i: int): XmlNode {.inline.} =
|
||||||
|
## Returns the `i`'th child of `n`.
|
||||||
|
runnableExamples:
|
||||||
|
var f = newElement("myTag")
|
||||||
|
f.add newElement("first")
|
||||||
|
f.insert(newElement("second"), 0)
|
||||||
|
assert $f[1] == "<first />"
|
||||||
|
assert $f[0] == "<second />"
|
||||||
|
|
||||||
|
assert n.k == xnElement
|
||||||
|
result = n.s[i]
|
||||||
|
|
||||||
proc `[]`* (n: var XmlNode, i: int): var XmlNode {.inline.} =
|
proc `[]`* (n: var XmlNode, i: int): var XmlNode {.inline.} =
|
||||||
## returns the `i`'th child of `n` so that it can be modified
|
## Returns the `i`'th child of `n` so that it can be modified.
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
result = n.s[i]
|
result = n.s[i]
|
||||||
|
|
||||||
iterator items*(n: XmlNode): XmlNode {.inline.} =
|
iterator items*(n: XmlNode): XmlNode {.inline.} =
|
||||||
## iterates over any child of `n`.
|
## Iterates over any child of `n`.
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var g = newElement("myTag")
|
||||||
|
## g.add newText("some text")
|
||||||
|
## g.add newComment("this is comment")
|
||||||
|
##
|
||||||
|
## var h = newElement("secondTag")
|
||||||
|
## h.add newEntity("some entity")
|
||||||
|
## g.add h
|
||||||
|
##
|
||||||
|
## assert $g == "<myTag>some text<!-- this is comment --><secondTag>&some entity;</secondTag></myTag>"
|
||||||
|
## for x in g: # the same as `for x in items(g):`
|
||||||
|
## echo x
|
||||||
|
##
|
||||||
|
## # some text
|
||||||
|
## # <!-- this is comment -->
|
||||||
|
## # <secondTag>&some entity;<![CDATA[some cdata]]></secondTag>
|
||||||
|
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
for i in 0 .. n.len-1: yield n[i]
|
for i in 0 .. n.len-1: yield n[i]
|
||||||
|
|
||||||
iterator mitems*(n: var XmlNode): var XmlNode {.inline.} =
|
iterator mitems*(n: var XmlNode): var XmlNode {.inline.} =
|
||||||
## iterates over any child of `n`.
|
## Iterates over any child of `n` so that it can be modified.
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
for i in 0 .. n.len-1: yield n[i]
|
for i in 0 .. n.len-1: yield n[i]
|
||||||
|
|
||||||
|
proc toXmlAttributes*(keyValuePairs: varargs[tuple[key, val: string]]): XmlAttributes =
|
||||||
|
## Converts `{key: value}` pairs into `XmlAttributes`.
|
||||||
|
runnableExamples:
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
var j = newElement("myTag")
|
||||||
|
j.attrs = att
|
||||||
|
assert $j == """<myTag key2="second value" key1="first value" />"""
|
||||||
|
|
||||||
|
newStringTable(keyValuePairs)
|
||||||
|
|
||||||
proc attrs*(n: XmlNode): XmlAttributes {.inline.} =
|
proc attrs*(n: XmlNode): XmlAttributes {.inline.} =
|
||||||
## gets the attributes belonging to `n`.
|
## Gets the attributes belonging to `n`.
|
||||||
|
##
|
||||||
## Returns `nil` if attributes have not been initialised for this node.
|
## Returns `nil` if attributes have not been initialised for this node.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
|
||||||
|
## * `attrsLen proc <#attrsLen,XmlNode>`_ for numbef of attributes
|
||||||
|
## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
|
||||||
|
runnableExamples:
|
||||||
|
var j = newElement("myTag")
|
||||||
|
assert j.attrs == nil
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
j.attrs = att
|
||||||
|
assert j.attrs == att
|
||||||
|
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
result = n.fAttr
|
result = n.fAttr
|
||||||
|
|
||||||
proc `attrs=`*(n: XmlNode, attr: XmlAttributes) {.inline.} =
|
proc `attrs=`*(n: XmlNode, attr: XmlAttributes) {.inline.} =
|
||||||
## sets the attributes belonging to `n`.
|
## Sets the attributes belonging to `n`.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
|
||||||
|
## * `attrsLen proc <#attrsLen,XmlNode>`_ for numbef of attributes
|
||||||
|
## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
|
||||||
|
runnableExamples:
|
||||||
|
var j = newElement("myTag")
|
||||||
|
assert j.attrs == nil
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
j.attrs = att
|
||||||
|
assert j.attrs == att
|
||||||
|
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
n.fAttr = attr
|
n.fAttr = attr
|
||||||
|
|
||||||
proc attrsLen*(n: XmlNode): int {.inline.} =
|
proc attrsLen*(n: XmlNode): int {.inline.} =
|
||||||
## returns the number of `n`'s attributes.
|
## Returns the number of `n`'s attributes.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
|
||||||
|
## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
|
||||||
|
## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
|
||||||
|
runnableExamples:
|
||||||
|
var j = newElement("myTag")
|
||||||
|
assert j.attrsLen == 0
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
j.attrs = att
|
||||||
|
assert j.attrsLen == 2
|
||||||
|
|
||||||
assert n.k == xnElement
|
assert n.k == xnElement
|
||||||
if not isNil(n.fAttr): result = len(n.fAttr)
|
if not isNil(n.fAttr): result = len(n.fAttr)
|
||||||
|
|
||||||
|
proc attr*(n: XmlNode, name: string): string =
|
||||||
|
## Finds the first attribute of `n` with a name of `name`.
|
||||||
|
## Returns "" on failure.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
|
||||||
|
## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
|
||||||
|
## * `attrsLen proc <#attrsLen,XmlNode>`_ for numbef of attributes
|
||||||
|
runnableExamples:
|
||||||
|
var j = newElement("myTag")
|
||||||
|
let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
|
||||||
|
j.attrs = att
|
||||||
|
assert j.attr("key1") == "first value"
|
||||||
|
assert j.attr("key2") == "second value"
|
||||||
|
|
||||||
|
assert n.kind == xnElement
|
||||||
|
if n.attrs == nil: return ""
|
||||||
|
return n.attrs.getOrDefault(name)
|
||||||
|
|
||||||
proc clientData*(n: XmlNode): int {.inline.} =
|
proc clientData*(n: XmlNode): int {.inline.} =
|
||||||
## gets the client data of `n`. The client data field is used by the HTML
|
## Gets the client data of `n`.
|
||||||
## parser and generator.
|
##
|
||||||
|
## The client data field is used by the HTML parser and generator.
|
||||||
result = n.fClientData
|
result = n.fClientData
|
||||||
|
|
||||||
proc `clientData=`*(n: XmlNode, data: int) {.inline.} =
|
proc `clientData=`*(n: XmlNode, data: int) {.inline.} =
|
||||||
## sets the client data of `n`. The client data field is used by the HTML
|
## Sets the client data of `n`.
|
||||||
## parser and generator.
|
##
|
||||||
|
## The client data field is used by the HTML parser and generator.
|
||||||
n.fClientData = data
|
n.fClientData = data
|
||||||
|
|
||||||
proc addEscaped*(result: var string, s: string) =
|
proc addEscaped*(result: var string, s: string) =
|
||||||
## same as ``result.add(escape(s))``, but more efficient.
|
## The same as `result.add(escape(s)) <#escape,string>`_, but more efficient.
|
||||||
for c in items(s):
|
for c in items(s):
|
||||||
case c
|
case c
|
||||||
of '<': result.add("<")
|
of '<': result.add("<")
|
||||||
|
|
@ -201,7 +497,8 @@ proc addEscaped*(result: var string, s: string) =
|
||||||
else: result.add(c)
|
else: result.add(c)
|
||||||
|
|
||||||
proc escape*(s: string): string =
|
proc escape*(s: string): string =
|
||||||
## escapes `s` for inclusion into an XML document.
|
## Escapes `s` for inclusion into an XML document.
|
||||||
|
##
|
||||||
## Escapes these characters:
|
## Escapes these characters:
|
||||||
##
|
##
|
||||||
## ------------ -------------------
|
## ------------ -------------------
|
||||||
|
|
@ -214,6 +511,8 @@ proc escape*(s: string): string =
|
||||||
## ``'`` ``'``
|
## ``'`` ``'``
|
||||||
## ``/`` ``/``
|
## ``/`` ``/``
|
||||||
## ------------ -------------------
|
## ------------ -------------------
|
||||||
|
##
|
||||||
|
## You can also use `addEscaped proc <#addEscaped,string,string>`_.
|
||||||
result = newStringOfCap(s.len)
|
result = newStringOfCap(s.len)
|
||||||
addEscaped(result, s)
|
addEscaped(result, s)
|
||||||
|
|
||||||
|
|
@ -223,14 +522,22 @@ proc addIndent(result: var string, indent: int, addNewLines: bool) =
|
||||||
for i in 1..indent: result.add(' ')
|
for i in 1..indent: result.add(' ')
|
||||||
|
|
||||||
proc noWhitespace(n: XmlNode): bool =
|
proc noWhitespace(n: XmlNode): bool =
|
||||||
#for i in 1..n.len-1:
|
|
||||||
# if n[i].kind != n[0].kind: return true
|
|
||||||
for i in 0..n.len-1:
|
for i in 0..n.len-1:
|
||||||
if n[i].kind in {xnText, xnEntity}: return true
|
if n[i].kind in {xnText, xnEntity}: return true
|
||||||
|
|
||||||
proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
|
proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
|
||||||
addNewLines=true) =
|
addNewLines=true) =
|
||||||
## adds the textual representation of `n` to `result`.
|
## Adds the textual representation of `n` to string `result`.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
a = newElement("firstTag")
|
||||||
|
b = newText("my text")
|
||||||
|
c = newComment("my comment")
|
||||||
|
s = ""
|
||||||
|
s.add(c)
|
||||||
|
s.add(a)
|
||||||
|
s.add(b)
|
||||||
|
assert s == "<!-- my comment --><firstTag />my text"
|
||||||
|
|
||||||
proc addEscapedAttr(result: var string, s: string) =
|
proc addEscapedAttr(result: var string, s: string) =
|
||||||
# `addEscaped` alternative with less escaped characters.
|
# `addEscaped` alternative with less escaped characters.
|
||||||
|
|
@ -291,24 +598,76 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
|
||||||
result.add(n.fText)
|
result.add(n.fText)
|
||||||
result.add(';')
|
result.add(';')
|
||||||
|
|
||||||
const
|
|
||||||
xmlHeader* = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
|
|
||||||
## header to use for complete XML output
|
|
||||||
|
|
||||||
proc `$`*(n: XmlNode): string =
|
proc `$`*(n: XmlNode): string =
|
||||||
## converts `n` into its string representation. No ``<$xml ...$>`` declaration
|
## Converts `n` into its string representation.
|
||||||
## is produced, so that the produced XML fragments are composable.
|
##
|
||||||
|
## No ``<$xml ...$>`` declaration is produced, so that the produced
|
||||||
|
## XML fragments are composable.
|
||||||
result = ""
|
result = ""
|
||||||
result.add(n)
|
result.add(n)
|
||||||
|
|
||||||
proc newXmlTree*(tag: string, children: openArray[XmlNode],
|
proc child*(n: XmlNode, name: string): XmlNode =
|
||||||
attributes: XmlAttributes = nil): XmlNode =
|
## Finds the first child element of `n` with a name of `name`.
|
||||||
## creates a new XML tree with `tag`, `children` and `attributes`
|
## Returns `nil` on failure.
|
||||||
result = newXmlNode(xnElement)
|
runnableExamples:
|
||||||
result.fTag = tag
|
var f = newElement("myTag")
|
||||||
newSeq(result.s, children.len)
|
f.add newElement("firstSon")
|
||||||
for i in 0..children.len-1: result.s[i] = children[i]
|
f.add newElement("secondSon")
|
||||||
result.fAttr = attributes
|
f.add newElement("thirdSon")
|
||||||
|
assert $(f.child("secondSon")) == "<secondSon />"
|
||||||
|
|
||||||
|
assert n.kind == xnElement
|
||||||
|
for i in items(n):
|
||||||
|
if i.kind == xnElement:
|
||||||
|
if i.tag == name:
|
||||||
|
return i
|
||||||
|
|
||||||
|
proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode]) =
|
||||||
|
## Iterates over all the children of `n` returning those matching `tag`.
|
||||||
|
##
|
||||||
|
## Found nodes satisfying the condition will be appended to the `result`
|
||||||
|
## sequence.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
b = newElement("good")
|
||||||
|
c = newElement("bad")
|
||||||
|
d = newElement("bad")
|
||||||
|
e = newElement("good")
|
||||||
|
b.add newText("b text")
|
||||||
|
c.add newText("c text")
|
||||||
|
d.add newText("d text")
|
||||||
|
e.add newText("e text")
|
||||||
|
let a = newXmlTree("father", [b, c, d, e])
|
||||||
|
var s = newSeq[XmlNode]()
|
||||||
|
a.findAll("good", s)
|
||||||
|
assert $s == "@[<good>b text</good>, <good>e text</good>]"
|
||||||
|
|
||||||
|
assert n.k == xnElement
|
||||||
|
for child in n.items():
|
||||||
|
if child.k != xnElement:
|
||||||
|
continue
|
||||||
|
if child.tag == tag:
|
||||||
|
result.add(child)
|
||||||
|
child.findAll(tag, result)
|
||||||
|
|
||||||
|
proc findAll*(n: XmlNode, tag: string): seq[XmlNode] =
|
||||||
|
## A shortcut version to assign in let blocks.
|
||||||
|
runnableExamples:
|
||||||
|
var
|
||||||
|
b = newElement("good")
|
||||||
|
c = newElement("bad")
|
||||||
|
d = newElement("bad")
|
||||||
|
e = newElement("good")
|
||||||
|
b.add newText("b text")
|
||||||
|
c.add newText("c text")
|
||||||
|
d.add newText("d text")
|
||||||
|
e.add newText("e text")
|
||||||
|
let a = newXmlTree("father", [b, c, d, e])
|
||||||
|
assert $(a.findAll("good")) == "@[<good>b text</good>, <good>e text</good>]"
|
||||||
|
assert $(a.findAll("bad")) == "@[<bad>c text</bad>, <bad>d text</bad>]"
|
||||||
|
|
||||||
|
newSeq(result, 0)
|
||||||
|
findAll(n, tag, result)
|
||||||
|
|
||||||
proc xmlConstructor(a: NimNode): NimNode {.compileTime.} =
|
proc xmlConstructor(a: NimNode): NimNode {.compileTime.} =
|
||||||
if a.kind == nnkCall:
|
if a.kind == nnkCall:
|
||||||
|
|
@ -346,56 +705,6 @@ macro `<>`*(x: untyped): untyped =
|
||||||
##
|
##
|
||||||
result = xmlConstructor(x)
|
result = xmlConstructor(x)
|
||||||
|
|
||||||
proc child*(n: XmlNode, name: string): XmlNode =
|
|
||||||
## Finds the first child element of `n` with a name of `name`.
|
|
||||||
## Returns `nil` on failure.
|
|
||||||
assert n.kind == xnElement
|
|
||||||
for i in items(n):
|
|
||||||
if i.kind == xnElement:
|
|
||||||
if i.tag == name:
|
|
||||||
return i
|
|
||||||
|
|
||||||
proc attr*(n: XmlNode, name: string): string =
|
|
||||||
## Finds the first attribute of `n` with a name of `name`.
|
|
||||||
## Returns "" on failure.
|
|
||||||
assert n.kind == xnElement
|
|
||||||
if n.attrs == nil: return ""
|
|
||||||
return n.attrs.getOrDefault(name)
|
|
||||||
|
|
||||||
proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode]) =
|
|
||||||
## Iterates over all the children of `n` returning those matching `tag`.
|
|
||||||
##
|
|
||||||
## Found nodes satisfying the condition will be appended to the `result`
|
|
||||||
## sequence, which can't be nil or the proc will crash. Usage example:
|
|
||||||
##
|
|
||||||
## .. code-block::
|
|
||||||
## var
|
|
||||||
## html: XmlNode
|
|
||||||
## tags: seq[XmlNode] = @[]
|
|
||||||
##
|
|
||||||
## html = buildHtml()
|
|
||||||
## findAll(html, "img", tags)
|
|
||||||
## for imgTag in tags:
|
|
||||||
## process(imgTag)
|
|
||||||
assert n.k == xnElement
|
|
||||||
for child in n.items():
|
|
||||||
if child.k != xnElement:
|
|
||||||
continue
|
|
||||||
if child.tag == tag:
|
|
||||||
result.add(child)
|
|
||||||
child.findAll(tag, result)
|
|
||||||
|
|
||||||
proc findAll*(n: XmlNode, tag: string): seq[XmlNode] =
|
|
||||||
## Shortcut version to assign in let blocks. Example:
|
|
||||||
##
|
|
||||||
## .. code-block::
|
|
||||||
## var html: XmlNode
|
|
||||||
##
|
|
||||||
## html = buildHtml(html)
|
|
||||||
## for imgTag in html.findAll("img"):
|
|
||||||
## process(imgTag)
|
|
||||||
newSeq(result, 0)
|
|
||||||
findAll(n, tag, result)
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
assert """<a href="http://nim-lang.org">Nim rules.</a>""" ==
|
assert """<a href="http://nim-lang.org">Nim rules.</a>""" ==
|
||||||
|
|
|
||||||
|
|
@ -3463,7 +3463,7 @@ when not defined(JS): #and not defined(nimscript):
|
||||||
## allows you to override the behaviour of your application when CTRL+C
|
## allows you to override the behaviour of your application when CTRL+C
|
||||||
## is pressed. Only one such hook is supported.
|
## is pressed. Only one such hook is supported.
|
||||||
|
|
||||||
when not defined(useNimRtl):
|
when not defined(noSignalHandler) and not defined(useNimRtl):
|
||||||
proc unsetControlCHook*()
|
proc unsetControlCHook*()
|
||||||
## reverts a call to setControlCHook
|
## reverts a call to setControlCHook
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,26 +197,40 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
||||||
|
|
||||||
proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||||
asm """
|
asm """
|
||||||
mov eax, ecx
|
test edx, edx
|
||||||
mov ecx, edx
|
jne L_NOT_ZERO
|
||||||
xor edx, edx
|
call `raiseDivByZero`
|
||||||
idiv ecx
|
L_NOT_ZERO:
|
||||||
jno theEnd
|
cmp ecx, 0x80000000
|
||||||
call `raiseOverflow`
|
jne L_DO_DIV
|
||||||
theEnd:
|
cmp edx, -1
|
||||||
|
jne L_DO_DIV
|
||||||
|
call `raiseOverflow`
|
||||||
|
L_DO_DIV:
|
||||||
|
mov eax, ecx
|
||||||
|
mov ecx, edx
|
||||||
|
cdq
|
||||||
|
idiv ecx
|
||||||
ret
|
ret
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||||
asm """
|
asm """
|
||||||
mov eax, ecx
|
test edx, edx
|
||||||
mov ecx, edx
|
jne L_NOT_ZERO
|
||||||
xor edx, edx
|
call `raiseDivByZero`
|
||||||
idiv ecx
|
L_NOT_ZERO:
|
||||||
jno theEnd
|
cmp ecx, 0x80000000
|
||||||
call `raiseOverflow`
|
jne L_DO_DIV
|
||||||
theEnd:
|
cmp edx, -1
|
||||||
mov eax, edx
|
jne L_DO_DIV
|
||||||
|
call `raiseOverflow`
|
||||||
|
L_DO_DIV:
|
||||||
|
mov eax, ecx
|
||||||
|
mov ecx, edx
|
||||||
|
cdq
|
||||||
|
idiv ecx
|
||||||
|
mov eax, edx
|
||||||
ret
|
ret
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,24 +347,17 @@ proc raiseExceptionAux(e: ref Exception) =
|
||||||
if globalRaiseHook != nil:
|
if globalRaiseHook != nil:
|
||||||
if not globalRaiseHook(e): return
|
if not globalRaiseHook(e): return
|
||||||
when defined(cpp) and not defined(noCppExceptions):
|
when defined(cpp) and not defined(noCppExceptions):
|
||||||
if e[] of OutOfMemError:
|
pushCurrentException(e)
|
||||||
showErrorMessage(e.name)
|
raiseCounter.inc
|
||||||
quitOrDebug()
|
if raiseCounter == 0:
|
||||||
else:
|
raiseCounter.inc # skip zero at overflow
|
||||||
pushCurrentException(e)
|
e.raiseId = raiseCounter
|
||||||
raiseCounter.inc
|
{.emit: "`e`->raise();".}
|
||||||
if raiseCounter == 0:
|
|
||||||
raiseCounter.inc # skip zero at overflow
|
|
||||||
e.raiseId = raiseCounter
|
|
||||||
{.emit: "`e`->raise();".}
|
|
||||||
else:
|
else:
|
||||||
if excHandler != nil:
|
if excHandler != nil:
|
||||||
if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
|
if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
|
||||||
pushCurrentException(e)
|
pushCurrentException(e)
|
||||||
c_longjmp(excHandler.context, 1)
|
c_longjmp(excHandler.context, 1)
|
||||||
elif e[] of OutOfMemError:
|
|
||||||
showErrorMessage(e.name)
|
|
||||||
quitOrDebug()
|
|
||||||
else:
|
else:
|
||||||
when hasSomeStackTrace:
|
when hasSomeStackTrace:
|
||||||
var buf = newStringOfCap(2000)
|
var buf = newStringOfCap(2000)
|
||||||
|
|
@ -453,27 +446,21 @@ when not defined(gcDestructors):
|
||||||
## a ``seq``. This is not yet available for the JS backend.
|
## a ``seq``. This is not yet available for the JS backend.
|
||||||
shallowCopy(result, e.trace)
|
shallowCopy(result, e.trace)
|
||||||
|
|
||||||
when defined(nimRequiresNimFrame):
|
const nimCallDepthLimit {.intdefine.} = 2000
|
||||||
const nimCallDepthLimit {.intdefine.} = 2000
|
|
||||||
|
|
||||||
proc callDepthLimitReached() {.noinline.} =
|
proc callDepthLimitReached() {.noinline.} =
|
||||||
writeStackTrace()
|
writeStackTrace()
|
||||||
showErrorMessage("Error: call depth limit reached in a debug build (" &
|
showErrorMessage("Error: call depth limit reached in a debug build (" &
|
||||||
$nimCallDepthLimit & " function calls). You can change it with " &
|
$nimCallDepthLimit & " function calls). You can change it with " &
|
||||||
"-d:nimCallDepthLimit=<int> but really try to avoid deep " &
|
"-d:nimCallDepthLimit=<int> but really try to avoid deep " &
|
||||||
"recursions instead.\n")
|
"recursions instead.\n")
|
||||||
quitOrDebug()
|
quitOrDebug()
|
||||||
|
|
||||||
proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
|
proc nimFrame(s: PFrame) {.compilerRtl, inl.} =
|
||||||
s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
|
s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
|
||||||
s.prev = framePtr
|
s.prev = framePtr
|
||||||
framePtr = s
|
framePtr = s
|
||||||
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
||||||
else:
|
|
||||||
proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
|
|
||||||
# XXX only for backwards compatibility
|
|
||||||
s.prev = framePtr
|
|
||||||
framePtr = s
|
|
||||||
|
|
||||||
when defined(endb):
|
when defined(endb):
|
||||||
var
|
var
|
||||||
|
|
@ -537,7 +524,7 @@ proc setControlCHook(hook: proc () {.noconv.}) =
|
||||||
type SignalHandler = proc (sign: cint) {.noconv, benign.}
|
type SignalHandler = proc (sign: cint) {.noconv, benign.}
|
||||||
c_signal(SIGINT, cast[SignalHandler](hook))
|
c_signal(SIGINT, cast[SignalHandler](hook))
|
||||||
|
|
||||||
when not defined(useNimRtl):
|
when not defined(noSignalHandler) and not defined(useNimRtl):
|
||||||
proc unsetControlCHook() =
|
proc unsetControlCHook() =
|
||||||
# proc to unset a hook set by setControlCHook
|
# proc to unset a hook set by setControlCHook
|
||||||
c_signal(SIGINT, signalHandler)
|
c_signal(SIGINT, signalHandler)
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ template gcAssert(cond: bool, msg: string) =
|
||||||
when defined(useGcAssert):
|
when defined(useGcAssert):
|
||||||
if not cond:
|
if not cond:
|
||||||
echo "[GCASSERT] ", msg
|
echo "[GCASSERT] ", msg
|
||||||
|
when defined(logGC):
|
||||||
|
echo "[GCASSERT] statistics:\L", GC_getStatistics()
|
||||||
GC_disable()
|
GC_disable()
|
||||||
writeStackTrace()
|
writeStackTrace()
|
||||||
#var x: ptr int
|
#var x: ptr int
|
||||||
|
|
@ -159,6 +161,10 @@ when defined(logGC):
|
||||||
c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld; thread=%ld\n",
|
c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld; thread=%ld\n",
|
||||||
msg, c, kind, typName, c.refcount shr rcShift, gch.gcThreadId)
|
msg, c, kind, typName, c.refcount shr rcShift, gch.gcThreadId)
|
||||||
|
|
||||||
|
template logCell(msg: cstring, c: PCell) =
|
||||||
|
when defined(logGC):
|
||||||
|
writeCell(msg, c)
|
||||||
|
|
||||||
template gcTrace(cell, state: untyped) =
|
template gcTrace(cell, state: untyped) =
|
||||||
when traceGC: traceCell(cell, state)
|
when traceGC: traceCell(cell, state)
|
||||||
|
|
||||||
|
|
@ -174,7 +180,7 @@ proc incRef(c: PCell) {.inline.} =
|
||||||
gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr")
|
gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr")
|
||||||
c.refcount = c.refcount +% rcIncrement
|
c.refcount = c.refcount +% rcIncrement
|
||||||
# and not colorMask
|
# and not colorMask
|
||||||
#writeCell("incRef", c)
|
logCell("incRef", c)
|
||||||
|
|
||||||
proc nimGCref(p: pointer) {.compilerProc.} =
|
proc nimGCref(p: pointer) {.compilerProc.} =
|
||||||
# we keep it from being collected by pretending it's not even allocated:
|
# we keep it from being collected by pretending it's not even allocated:
|
||||||
|
|
@ -192,6 +198,7 @@ proc decRef(c: PCell) {.inline.} =
|
||||||
c.refcount = c.refcount -% rcIncrement
|
c.refcount = c.refcount -% rcIncrement
|
||||||
if c.refcount <% rcIncrement:
|
if c.refcount <% rcIncrement:
|
||||||
rtlAddZCT(c)
|
rtlAddZCT(c)
|
||||||
|
logCell("decRef", c)
|
||||||
|
|
||||||
proc nimGCunref(p: pointer) {.compilerProc.} =
|
proc nimGCunref(p: pointer) {.compilerProc.} =
|
||||||
let cell = usrToCell(p)
|
let cell = usrToCell(p)
|
||||||
|
|
@ -410,7 +417,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
|
||||||
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
|
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
|
||||||
# its refcount is zero, so add it to the ZCT:
|
# its refcount is zero, so add it to the ZCT:
|
||||||
addNewObjToZCT(res, gch)
|
addNewObjToZCT(res, gch)
|
||||||
when logGC: writeCell("new cell", res)
|
logCell("new cell", res)
|
||||||
track("rawNewObj", res, size)
|
track("rawNewObj", res, size)
|
||||||
gcTrace(res, csAllocated)
|
gcTrace(res, csAllocated)
|
||||||
when useCellIds:
|
when useCellIds:
|
||||||
|
|
@ -455,7 +462,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
|
||||||
setFrameInfo(res)
|
setFrameInfo(res)
|
||||||
res.refcount = rcIncrement # refcount is 1
|
res.refcount = rcIncrement # refcount is 1
|
||||||
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
|
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
|
||||||
when logGC: writeCell("new cell", res)
|
logCell("new cell", res)
|
||||||
track("newObjRC1", res, size)
|
track("newObjRC1", res, size)
|
||||||
gcTrace(res, csAllocated)
|
gcTrace(res, csAllocated)
|
||||||
when useCellIds:
|
when useCellIds:
|
||||||
|
|
@ -493,9 +500,8 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer =
|
||||||
# This can be wrong for intermediate temps that are nevertheless on the
|
# This can be wrong for intermediate temps that are nevertheless on the
|
||||||
# heap because of lambda lifting:
|
# heap because of lambda lifting:
|
||||||
#gcAssert(res.refcount shr rcShift <=% 1, "growObj: 4")
|
#gcAssert(res.refcount shr rcShift <=% 1, "growObj: 4")
|
||||||
when logGC:
|
logCell("growObj old cell", ol)
|
||||||
writeCell("growObj old cell", ol)
|
logCell("growObj new cell", res)
|
||||||
writeCell("growObj new cell", res)
|
|
||||||
gcTrace(ol, csZctFreed)
|
gcTrace(ol, csZctFreed)
|
||||||
gcTrace(res, csAllocated)
|
gcTrace(res, csAllocated)
|
||||||
track("growObj old", ol, 0)
|
track("growObj old", ol, 0)
|
||||||
|
|
@ -547,7 +553,7 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) =
|
||||||
prepareDealloc(c)
|
prepareDealloc(c)
|
||||||
gcTrace(c, csCycFreed)
|
gcTrace(c, csCycFreed)
|
||||||
track("cycle collector dealloc cell", c, 0)
|
track("cycle collector dealloc cell", c, 0)
|
||||||
when logGC: writeCell("cycle collector dealloc cell", c)
|
logCell("cycle collector dealloc cell", c)
|
||||||
when reallyDealloc:
|
when reallyDealloc:
|
||||||
sysAssert(allocInv(gch.region), "free cyclic cell")
|
sysAssert(allocInv(gch.region), "free cyclic cell")
|
||||||
beforeDealloc(gch, c, "freeCyclicCell: stack trash")
|
beforeDealloc(gch, c, "freeCyclicCell: stack trash")
|
||||||
|
|
@ -616,7 +622,7 @@ proc doOperation(p: pointer, op: WalkOp) =
|
||||||
# c_fprintf(stdout, "[GC] decref bug: %p", c)
|
# c_fprintf(stdout, "[GC] decref bug: %p", c)
|
||||||
gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
|
gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
|
||||||
gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
|
gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
|
||||||
when logGC: writeCell("decref (from doOperation)", c)
|
logCell("decref (from doOperation)", c)
|
||||||
track("waZctDecref", p, 0)
|
track("waZctDecref", p, 0)
|
||||||
decRef(c)
|
decRef(c)
|
||||||
of waPush:
|
of waPush:
|
||||||
|
|
@ -704,7 +710,7 @@ proc collectZCT(gch: var GcHeap): bool =
|
||||||
# as this might be too slow.
|
# as this might be too slow.
|
||||||
# In any case, it should be removed from the ZCT. But not
|
# In any case, it should be removed from the ZCT. But not
|
||||||
# freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!**
|
# freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!**
|
||||||
when logGC: writeCell("zct dealloc cell", c)
|
logCell("zct dealloc cell", c)
|
||||||
track("zct dealloc cell", c, 0)
|
track("zct dealloc cell", c, 0)
|
||||||
gcTrace(c, csZctFreed)
|
gcTrace(c, csZctFreed)
|
||||||
# We are about to free the object, call the finalizer BEFORE its
|
# We are about to free the object, call the finalizer BEFORE its
|
||||||
|
|
@ -858,6 +864,7 @@ when not defined(useNimRtl):
|
||||||
for stack in items(gch.stack):
|
for stack in items(gch.stack):
|
||||||
result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n"
|
result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n"
|
||||||
else:
|
else:
|
||||||
|
result.add "[GC] stack bottom: " & gch.stack.bottom.repr
|
||||||
result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||||
|
|
||||||
{.pop.} # profiler: off, stackTrace: off
|
{.pop.} # profiler: off, stackTrace: off
|
||||||
|
|
|
||||||
|
|
@ -257,14 +257,14 @@ proc deallocAll*() = tlRegion.deallocAll()
|
||||||
|
|
||||||
proc deallocOsPages(r: var MemRegion) = r.deallocAll()
|
proc deallocOsPages(r: var MemRegion) = r.deallocAll()
|
||||||
|
|
||||||
template withScratchRegion*(body: untyped) =
|
when false:
|
||||||
let obs = obstackPtr()
|
let obs = obstackPtr()
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
finally:
|
finally:
|
||||||
setObstackPtr(obs)
|
setObstackPtr(obs)
|
||||||
|
|
||||||
when false:
|
template withScratchRegion*(body: untyped) =
|
||||||
var scratch: MemRegion
|
var scratch: MemRegion
|
||||||
let oldRegion = tlRegion
|
let oldRegion = tlRegion
|
||||||
tlRegion = scratch
|
tlRegion = scratch
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
## To learn about scripting in Nim see `NimScript<nims.html>`_
|
||||||
|
|
||||||
# Nim's configuration system now uses Nim for scripting. This module provides
|
# Nim's configuration system now uses Nim for scripting. This module provides
|
||||||
# a few things that are required for this to work.
|
# a few things that are required for this to work.
|
||||||
|
|
|
||||||
|
|
@ -830,7 +830,7 @@ proc SQLStatistics*(hstmt: SqlHStmt, CatalogName: PSQLCHAR,
|
||||||
proc SQLErr*(henv: SqlHEnv, hdbc: SqlHDBC, hstmt: SqlHStmt,
|
proc SQLErr*(henv: SqlHEnv, hdbc: SqlHDBC, hstmt: SqlHStmt,
|
||||||
szSqlState, pfNativeError, szErrorMsg: PSQLCHAR,
|
szSqlState, pfNativeError, szErrorMsg: PSQLCHAR,
|
||||||
cbErrorMsgMax: TSqlSmallInt,
|
cbErrorMsgMax: TSqlSmallInt,
|
||||||
pcbErrorMsg: PSQLINTEGER): TSqlSmallInt {.
|
pcbErrorMsg: PSQLSMALLINT): TSqlSmallInt {.
|
||||||
dynlib: odbclib, importc: "SQLError".}
|
dynlib: odbclib, importc: "SQLError".}
|
||||||
|
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ proc test(dir: string; fixup = false) =
|
||||||
copyFile(produced, expected)
|
copyFile(produced, expected)
|
||||||
else:
|
else:
|
||||||
echo "SUCCESS: files identical: ", produced
|
echo "SUCCESS: files identical: ", produced
|
||||||
removeDir(dir / "htmldocs")
|
if failures == 0:
|
||||||
|
removeDir(dir / "htmldocs")
|
||||||
|
|
||||||
test("nimdoc/testproject", defined(fixup))
|
test("nimdoc/testproject", defined(fixup))
|
||||||
if failures > 0: quit($failures & " failures occurred.")
|
if failures > 0: quit($failures & " failures occurred.")
|
||||||
|
|
|
||||||
|
|
@ -1255,7 +1255,7 @@ function main() {
|
||||||
<li>
|
<li>
|
||||||
<a class="reference reference-toplevel" href="#12" id="62">Procs</a>
|
<a class="reference reference-toplevel" href="#12" id="62">Procs</a>
|
||||||
<ul class="simple simple-toc-section">
|
<ul class="simple simple-toc-section">
|
||||||
<li><a class="reference" href="#someType%2C"
|
<li><a class="reference" href="#someType_2"
|
||||||
title="someType(): SomeType"><wbr />some<wbr />Type<span class="attachedType" style="visibility:hidden">SomeType</span></a></li>
|
title="someType(): SomeType"><wbr />some<wbr />Type<span class="attachedType" style="visibility:hidden">SomeType</span></a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1263,9 +1263,9 @@ function main() {
|
||||||
<li>
|
<li>
|
||||||
<a class="reference reference-toplevel" href="#18" id="68">Templates</a>
|
<a class="reference reference-toplevel" href="#18" id="68">Templates</a>
|
||||||
<ul class="simple simple-toc-section">
|
<ul class="simple simple-toc-section">
|
||||||
<li><a class="reference" href="#aEnum.t%2C"
|
<li><a class="reference" href="#aEnum.t"
|
||||||
title="aEnum(): untyped"><wbr />a<wbr />Enum<span class="attachedType" style="visibility:hidden"></span></a></li>
|
title="aEnum(): untyped"><wbr />a<wbr />Enum<span class="attachedType" style="visibility:hidden"></span></a></li>
|
||||||
<li><a class="reference" href="#bEnum.t%2C"
|
<li><a class="reference" href="#bEnum.t"
|
||||||
title="bEnum(): untyped"><wbr />b<wbr />Enum<span class="attachedType" style="visibility:hidden"></span></a></li>
|
title="bEnum(): untyped"><wbr />b<wbr />Enum<span class="attachedType" style="visibility:hidden"></span></a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1304,8 +1304,8 @@ function main() {
|
||||||
<div class="section" id="12">
|
<div class="section" id="12">
|
||||||
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="someType,"></a>
|
<a id="someType_2"></a>
|
||||||
<dt><pre><span class="Keyword">proc</span> <span class="Identifier">someType</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <a href="utils.html#SomeType"><span class="Identifier">SomeType</span></a> <span><span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span></span><span class="pragmawrap"><span class="Other">{.</span><span class="pragma"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span><span class="Other">.}</span></span></pre></dt>
|
<dt><pre><span class="Keyword">proc</span> <a href="#someType_2"><span class="Identifier">someType</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <a href="utils.html#SomeType"><span class="Identifier">SomeType</span></a> <span><span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span></span><span class="pragmawrap"><span class="Other">{.</span><span class="pragma"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span><span class="Other">.}</span></span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
constructor.
|
constructor.
|
||||||
|
|
||||||
|
|
@ -1315,14 +1315,14 @@ constructor.
|
||||||
<div class="section" id="18">
|
<div class="section" id="18">
|
||||||
<h1><a class="toc-backref" href="#18">Templates</a></h1>
|
<h1><a class="toc-backref" href="#18">Templates</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="aEnum.t,"></a>
|
<a id="aEnum.t"></a>
|
||||||
<dt><pre><span class="Keyword">template</span> <span class="Identifier">aEnum</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
<dt><pre><span class="Keyword">template</span> <a href="#aEnum.t"><span class="Identifier">aEnum</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
<a id="bEnum.t,"></a>
|
<a id="bEnum.t"></a>
|
||||||
<dt><pre><span class="Keyword">template</span> <span class="Identifier">bEnum</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
<dt><pre><span class="Keyword">template</span> <a href="#bEnum.t"><span class="Identifier">bEnum</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1277,7 +1277,7 @@ function main() {
|
||||||
<li>
|
<li>
|
||||||
<a class="reference reference-toplevel" href="#13" id="63">Funcs</a>
|
<a class="reference reference-toplevel" href="#13" id="63">Funcs</a>
|
||||||
<ul class="simple simple-toc-section">
|
<ul class="simple simple-toc-section">
|
||||||
<li><a class="reference" href="#someFunc%2C"
|
<li><a class="reference" href="#someFunc"
|
||||||
title="someFunc()"><wbr />some<wbr />Func<span class="attachedType" style="visibility:hidden"></span></a></li>
|
title="someFunc()"><wbr />some<wbr />Func<span class="attachedType" style="visibility:hidden"></span></a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1285,7 +1285,7 @@ function main() {
|
||||||
<li>
|
<li>
|
||||||
<a class="reference reference-toplevel" href="#17" id="67">Macros</a>
|
<a class="reference reference-toplevel" href="#17" id="67">Macros</a>
|
||||||
<ul class="simple simple-toc-section">
|
<ul class="simple simple-toc-section">
|
||||||
<li><a class="reference" href="#bar.m%2C"
|
<li><a class="reference" href="#bar.m"
|
||||||
title="bar(): untyped"><wbr />bar<span class="attachedType" style="visibility:hidden"></span></a></li>
|
title="bar(): untyped"><wbr />bar<span class="attachedType" style="visibility:hidden"></span></a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1350,13 +1350,13 @@ The enum B.
|
||||||
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="bar,T,T"></a>
|
<a id="bar,T,T"></a>
|
||||||
<dt><pre><span class="Keyword">proc</span> <span class="Identifier">bar</span><span class="Other">[</span><span class="Identifier">T</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">a</span><span class="Other">,</span> <span class="Identifier">b</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span></pre></dt>
|
<dt><pre><span class="Keyword">proc</span> <a href="#bar%2CT%2CT"><span class="Identifier">bar</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">a</span><span class="Other">,</span> <span class="Identifier">b</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
<a id="isValid,T"></a>
|
<a id="isValid,T"></a>
|
||||||
<dt><pre><span class="Keyword">proc</span> <span class="Identifier">isValid</span><span class="Other">[</span><span class="Identifier">T</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">bool</span></pre></dt>
|
<dt><pre><span class="Keyword">proc</span> <a href="#isValid%2CT"><span class="Identifier">isValid</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">bool</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1366,8 +1366,8 @@ The enum B.
|
||||||
<div class="section" id="13">
|
<div class="section" id="13">
|
||||||
<h1><a class="toc-backref" href="#13">Funcs</a></h1>
|
<h1><a class="toc-backref" href="#13">Funcs</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="someFunc,"></a>
|
<a id="someFunc"></a>
|
||||||
<dt><pre><span class="Keyword">func</span> <span class="Identifier">someFunc</span><span class="Other">(</span><span class="Other">)</span> <span><span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span></span><span class="pragmawrap"><span class="Other">{.</span><span class="pragma"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span><span class="Other">.}</span></span></pre></dt>
|
<dt><pre><span class="Keyword">func</span> <a href="#someFunc"><span class="Identifier">someFunc</span></a><span class="Other">(</span><span class="Other">)</span> <span><span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span></span><span class="pragmawrap"><span class="Other">{.</span><span class="pragma"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span><span class="Other">.}</span></span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
My someFunc. Stuff in <tt class="docutils literal"><span class="pre">quotes</span></tt> here. <a class="reference external" href="https://nim-lang.org">Some link</a>
|
My someFunc. Stuff in <tt class="docutils literal"><span class="pre">quotes</span></tt> here. <a class="reference external" href="https://nim-lang.org">Some link</a>
|
||||||
|
|
||||||
|
|
@ -1377,8 +1377,8 @@ My someFunc. Stuff in <tt class="docutils literal"><span class="pre">quotes</spa
|
||||||
<div class="section" id="17">
|
<div class="section" id="17">
|
||||||
<h1><a class="toc-backref" href="#17">Macros</a></h1>
|
<h1><a class="toc-backref" href="#17">Macros</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="bar.m,"></a>
|
<a id="bar.m"></a>
|
||||||
<dt><pre><span class="Keyword">macro</span> <span class="Identifier">bar</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
<dt><pre><span class="Keyword">macro</span> <a href="#bar.m"><span class="Identifier">bar</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">untyped</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1389,7 +1389,7 @@ My someFunc. Stuff in <tt class="docutils literal"><span class="pre">quotes</spa
|
||||||
<h1><a class="toc-backref" href="#18">Templates</a></h1>
|
<h1><a class="toc-backref" href="#18">Templates</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<a id="foo.t,SomeType,SomeType"></a>
|
<a id="foo.t,SomeType,SomeType"></a>
|
||||||
<dt><pre><span class="Keyword">template</span> <span class="Identifier">foo</span><span class="Other">(</span><span class="Identifier">a</span><span class="Other">,</span> <span class="Identifier">b</span><span class="Other">:</span> <a href="subdir/subdir_b/utils.html#SomeType"><span class="Identifier">SomeType</span></a><span class="Other">)</span></pre></dt>
|
<dt><pre><span class="Keyword">template</span> <a href="#foo.t%2CSomeType%2CSomeType"><span class="Identifier">foo</span></a><span class="Other">(</span><span class="Identifier">a</span><span class="Other">,</span> <span class="Identifier">b</span><span class="Other">:</span> <a href="subdir/subdir_b/utils.html#SomeType"><span class="Identifier">SomeType</span></a><span class="Other">)</span></pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
This does nothing
|
This does nothing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1227,7 +1227,7 @@ function main() {
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="aEnum" href="#aEnum"><span>aEnum:</span></a></dt><dd><ul class="simple">
|
<dt><a name="aEnum" href="#aEnum"><span>aEnum:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="utils: aEnum(): untyped" href="subdir/subdir_b/utils.html#aEnum.t%2C">utils: aEnum(): untyped</a></li>
|
data-doc-search-tag="utils: aEnum(): untyped" href="subdir/subdir_b/utils.html#aEnum.t">utils: aEnum(): untyped</a></li>
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="aVariable" href="#aVariable"><span>aVariable:</span></a></dt><dd><ul class="simple">
|
<dt><a name="aVariable" href="#aVariable"><span>aVariable:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
|
|
@ -1241,11 +1241,11 @@ function main() {
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="testproject: bar[T](a, b: T): T" href="testproject.html#bar%2CT%2CT">testproject: bar[T](a, b: T): T</a></li>
|
data-doc-search-tag="testproject: bar[T](a, b: T): T" href="testproject.html#bar%2CT%2CT">testproject: bar[T](a, b: T): T</a></li>
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="testproject: bar(): untyped" href="testproject.html#bar.m%2C">testproject: bar(): untyped</a></li>
|
data-doc-search-tag="testproject: bar(): untyped" href="testproject.html#bar.m">testproject: bar(): untyped</a></li>
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="bEnum" href="#bEnum"><span>bEnum:</span></a></dt><dd><ul class="simple">
|
<dt><a name="bEnum" href="#bEnum"><span>bEnum:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="utils: bEnum(): untyped" href="subdir/subdir_b/utils.html#bEnum.t%2C">utils: bEnum(): untyped</a></li>
|
data-doc-search-tag="utils: bEnum(): untyped" href="subdir/subdir_b/utils.html#bEnum.t">utils: bEnum(): untyped</a></li>
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="enumValueA" href="#enumValueA"><span>enumValueA:</span></a></dt><dd><ul class="simple">
|
<dt><a name="enumValueA" href="#enumValueA"><span>enumValueA:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
|
|
@ -1269,7 +1269,7 @@ function main() {
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="someFunc" href="#someFunc"><span>someFunc:</span></a></dt><dd><ul class="simple">
|
<dt><a name="someFunc" href="#someFunc"><span>someFunc:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="testproject: someFunc()" href="testproject.html#someFunc%2C">testproject: someFunc()</a></li>
|
data-doc-search-tag="testproject: someFunc()" href="testproject.html#someFunc">testproject: someFunc()</a></li>
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="SomeType" href="#SomeType"><span>SomeType:</span></a></dt><dd><ul class="simple">
|
<dt><a name="SomeType" href="#SomeType"><span>SomeType:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
|
|
@ -1277,7 +1277,7 @@ function main() {
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
<dt><a name="someType" href="#someType"><span>someType:</span></a></dt><dd><ul class="simple">
|
<dt><a name="someType" href="#someType"><span>someType:</span></a></dt><dd><ul class="simple">
|
||||||
<li><a class="reference external"
|
<li><a class="reference external"
|
||||||
data-doc-search-tag="utils: someType(): SomeType" href="subdir/subdir_b/utils.html#someType%2C">utils: someType(): SomeType</a></li>
|
data-doc-search-tag="utils: someType(): SomeType" href="subdir/subdir_b/utils.html#someType_2">utils: someType(): SomeType</a></li>
|
||||||
</ul></dd>
|
</ul></dd>
|
||||||
</dl>
|
</dl>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,10 @@ proc skipDisabledTest(test: Test): bool =
|
||||||
proc runEpcTest(filename: string): int =
|
proc runEpcTest(filename: string): int =
|
||||||
let s = parseTest(filename, true)
|
let s = parseTest(filename, true)
|
||||||
if s.skipDisabledTest: return 0
|
if s.skipDisabledTest: return 0
|
||||||
|
for req, _ in items(s.script):
|
||||||
|
if req.startsWith("highlight"):
|
||||||
|
echo "disabled epc: " & s.filename
|
||||||
|
return 0
|
||||||
for cmd in s.startup:
|
for cmd in s.startup:
|
||||||
if not runCmd(cmd, s.dest):
|
if not runCmd(cmd, s.dest):
|
||||||
quit "invalid command: " & cmd
|
quit "invalid command: " & cmd
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
proc `$$$`#[!]#
|
proc `$$$`#[!]#
|
||||||
|
|
||||||
discard """
|
discard """
|
||||||
disabled:true
|
|
||||||
$nimsuggest --tester $file
|
$nimsuggest --tester $file
|
||||||
>highlight $1
|
>highlight $1
|
||||||
highlight;;skProc;;1;;6;;3
|
highlight;;skProc;;1;;6;;3
|
||||||
|
|
|
||||||
20
nimsuggest/tests/tgeneric_highlight.nim
Normal file
20
nimsuggest/tests/tgeneric_highlight.nim
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
newSeq[int]()
|
||||||
|
system.newSeq[int]()#[!]#
|
||||||
|
offsetOf[int]()
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $file
|
||||||
|
>highlight $1
|
||||||
|
highlight;;skType;;1;;7;;3
|
||||||
|
highlight;;skProc;;1;;0;;6
|
||||||
|
highlight;;skProc;;1;;0;;6
|
||||||
|
highlight;;skType;;1;;7;;3
|
||||||
|
highlight;;skProc;;1;;0;;6
|
||||||
|
highlight;;skType;;2;;14;;3
|
||||||
|
highlight;;skProc;;2;;7;;6
|
||||||
|
highlight;;skProc;;2;;7;;6
|
||||||
|
highlight;;skType;;2;;14;;3
|
||||||
|
highlight;;skProc;;2;;7;;6
|
||||||
|
highlight;;skTemplate;;3;;0;;8
|
||||||
|
highlight;;skType;;3;;9;;3
|
||||||
|
"""
|
||||||
13
nimsuggest/tests/tmacro_highlight.nim
Normal file
13
nimsuggest/tests/tmacro_highlight.nim
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
macro a(b: string): untyped = discard
|
||||||
|
|
||||||
|
a "string"#[!]#
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $file
|
||||||
|
>highlight $1
|
||||||
|
highlight;;skMacro;;1;;6;;1
|
||||||
|
highlight;;skType;;1;;11;;6
|
||||||
|
highlight;;skType;;1;;20;;7
|
||||||
|
highlight;;skMacro;;3;;0;;1
|
||||||
|
highlight;;skMacro;;3;;0;;1
|
||||||
|
"""
|
||||||
14
nimsuggest/tests/tqualified_highlight.nim
Normal file
14
nimsuggest/tests/tqualified_highlight.nim
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
system.echo#[!]#
|
||||||
|
system.once
|
||||||
|
system.`$` 1
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $file
|
||||||
|
>highlight $1
|
||||||
|
highlight;;skProc;;1;;7;;4
|
||||||
|
highlight;;skProc;;1;;7;;4
|
||||||
|
highlight;;skTemplate;;2;;7;;4
|
||||||
|
highlight;;skTemplate;;2;;7;;4
|
||||||
|
highlight;;skTemplate;;2;;7;;4
|
||||||
|
highlight;;skProc;;3;;8;;1
|
||||||
|
"""
|
||||||
10
nimsuggest/tests/tsug_accquote.nim
Normal file
10
nimsuggest/tests/tsug_accquote.nim
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
proc `%%%`(a: int) = discard
|
||||||
|
proc `cast`() = discard
|
||||||
|
tsug_accquote.#[!]#
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $file
|
||||||
|
>sug $1
|
||||||
|
sug;;skProc;;tsug_accquote.`%%%`;;proc (a: int);;$file;;1;;5;;"";;100;;None
|
||||||
|
sug;;skProc;;tsug_accquote.`cast`;;proc ();;$file;;2;;5;;"";;100;;None
|
||||||
|
"""
|
||||||
9
nimsuggest/tests/ttemplate_highlight.nim
Normal file
9
nimsuggest/tests/ttemplate_highlight.nim
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
doAssert true#[!]#
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $1
|
||||||
|
>highlight $1
|
||||||
|
highlight;;skTemplate;;1;;0;;8
|
||||||
|
highlight;;skTemplate;;1;;0;;8
|
||||||
|
highlight;;skEnumField;;1;;9;;4
|
||||||
|
"""
|
||||||
|
|
@ -6,7 +6,6 @@ type
|
||||||
TypeE* {.unchecked.} = array[0, int]#[!]#
|
TypeE* {.unchecked.} = array[0, int]#[!]#
|
||||||
|
|
||||||
discard """
|
discard """
|
||||||
disabled:true
|
|
||||||
$nimsuggest --tester $file
|
$nimsuggest --tester $file
|
||||||
>highlight $1
|
>highlight $1
|
||||||
highlight;;skType;;2;;2;;5
|
highlight;;skType;;2;;2;;5
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ For more information about Nim, including downloads and documentation for
|
||||||
the latest release, check out [Nim's website][nim-site] or [bleeding edge docs](https://nim-lang.github.io/Nim/).
|
the latest release, check out [Nim's website][nim-site] or [bleeding edge docs](https://nim-lang.github.io/Nim/).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
[![Join the IRC chat][badge-nim-irc]][nim-irc]
|
[![Join the IRC chat][badge-nim-irc]][nim-irc]
|
||||||
[![Join the Gitter chat][badge-nim-gitter]][nim-gitter]
|
[![Join the Gitter chat][badge-nim-gitter]][nim-gitter]
|
||||||
[![Get help][badge-nim-forum-gethelp]][nim-forum]
|
[![Get help][badge-nim-forum-gethelp]][nim-forum]
|
||||||
|
|
@ -23,6 +24,7 @@ the latest release, check out [Nim's website][nim-site] or [bleeding edge docs](
|
||||||
* [Github Wiki][nim-wiki] - Misc user-contributed content.
|
* [Github Wiki][nim-wiki] - Misc user-contributed content.
|
||||||
|
|
||||||
## Compiling
|
## Compiling
|
||||||
|
|
||||||
The compiler currently officially supports the following platform and
|
The compiler currently officially supports the following platform and
|
||||||
architecture combinations:
|
architecture combinations:
|
||||||
|
|
||||||
|
|
@ -56,6 +58,8 @@ Nim from source using ``gcc``, ``git`` and the ``koch`` build tool.
|
||||||
For most users, installing the latest stable version is enough. Check out
|
For most users, installing the latest stable version is enough. Check out
|
||||||
the installation instructions on the website to do so: https://nim-lang.org/install.html.
|
the installation instructions on the website to do so: https://nim-lang.org/install.html.
|
||||||
|
|
||||||
|
For package mantainers: see [packaging guidelines](https://nim-lang.github.io/Nim/packaging.html).
|
||||||
|
|
||||||
```
|
```
|
||||||
# step 1:
|
# step 1:
|
||||||
git clone https://github.com/nim-lang/Nim.git
|
git clone https://github.com/nim-lang/Nim.git
|
||||||
|
|
@ -83,6 +87,7 @@ Finally, once you have finished the build steps (on Windows, Mac or Linux) you
|
||||||
should add the ``bin`` directory to your PATH.
|
should add the ``bin`` directory to your PATH.
|
||||||
|
|
||||||
## Koch
|
## Koch
|
||||||
|
|
||||||
``koch`` is the build tool used to build various parts of Nim and to generate
|
``koch`` is the build tool used to build various parts of Nim and to generate
|
||||||
documentation and the website, among other things. The ``koch`` tool can also
|
documentation and the website, among other things. The ``koch`` tool can also
|
||||||
be used to run the Nim test suite.
|
be used to run the Nim test suite.
|
||||||
|
|
@ -106,6 +111,7 @@ This project exists thanks to all the people who contribute.
|
||||||
<a href="https://github.com/nim-lang/Nim/graphs/contributors"><img src="https://opencollective.com/Nim/contributors.svg?width=890" /></a>
|
<a href="https://github.com/nim-lang/Nim/graphs/contributors"><img src="https://opencollective.com/Nim/contributors.svg?width=890" /></a>
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
[](#backers) [](#sponsors)
|
[](#backers) [](#sponsors)
|
||||||
[![Setup a bounty via Bountysource][badge-nim-bountysource]][nim-bountysource]
|
[![Setup a bounty via Bountysource][badge-nim-bountysource]][nim-bountysource]
|
||||||
[![Donate Bitcoins][badge-nim-bitcoin]][nim-bitcoin]
|
[![Donate Bitcoins][badge-nim-bitcoin]][nim-bitcoin]
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ dflfdjkl__abcdefgasfsgdfgsgdfggsdfasdfsafewfkljdsfajs
|
||||||
dflfdjkl__abcdefgasfsgdfgsgdfggsdfasdfsafewfkljdsfajsdf
|
dflfdjkl__abcdefgasfsgdfgsgdfggsdfasdfsafewfkljdsfajsdf
|
||||||
kgdchlfniambejop
|
kgdchlfniambejop
|
||||||
fjpmholcibdgeakn
|
fjpmholcibdgeakn
|
||||||
|
2.0
|
||||||
'''
|
'''
|
||||||
joinable: false
|
joinable: false
|
||||||
"""
|
"""
|
||||||
|
|
@ -538,3 +539,12 @@ block trelaxedindextyp:
|
||||||
proc foo(x: seq[int]; idx: uint64) = echo x[idx]
|
proc foo(x: seq[int]; idx: uint64) = echo x[idx]
|
||||||
proc foo(x: string|cstring; idx: uint64) = echo x[idx]
|
proc foo(x: string|cstring; idx: uint64) = echo x[idx]
|
||||||
proc foo(x: openArray[int]; idx: uint64) = echo x[idx]
|
proc foo(x: openArray[int]; idx: uint64) = echo x[idx]
|
||||||
|
|
||||||
|
block t3899:
|
||||||
|
# https://github.com/nim-lang/Nim/issues/3899
|
||||||
|
type O = object
|
||||||
|
a: array[1..2,float]
|
||||||
|
template `[]`(x: O, i: int): float =
|
||||||
|
x.a[i]
|
||||||
|
const c = O(a: [1.0,2.0])
|
||||||
|
echo c[2]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
discard """
|
discard """
|
||||||
output: "5000"
|
output: "5000"
|
||||||
"""
|
"""
|
||||||
import asyncdispatch, nativesockets, net, strutils, os
|
import asyncdispatch, asyncnet, nativesockets, net, strutils, os
|
||||||
|
|
||||||
var msgCount = 0
|
var msgCount = 0
|
||||||
|
|
||||||
|
|
@ -12,20 +12,22 @@ const
|
||||||
var clientCount = 0
|
var clientCount = 0
|
||||||
|
|
||||||
proc sendMessages(client: AsyncFD) {.async.} =
|
proc sendMessages(client: AsyncFD) {.async.} =
|
||||||
for i in 0 .. <messagesToSend:
|
for i in 0 ..< messagesToSend:
|
||||||
await send(client, "Message " & $i & "\c\L")
|
await send(client, "Message " & $i & "\c\L")
|
||||||
|
|
||||||
proc launchSwarm(port: Port) {.async.} =
|
proc launchSwarm(port: Port) {.async.} =
|
||||||
for i in 0 .. <swarmSize:
|
for i in 0 ..< swarmSize:
|
||||||
var sock = newAsyncNativeSocket()
|
var sock = createAsyncNativeSocket()
|
||||||
|
|
||||||
await connect(sock, "localhost", port)
|
await connect(sock, "localhost", port)
|
||||||
await sendMessages(sock)
|
await sendMessages(sock)
|
||||||
closeSocket(sock)
|
closeSocket(sock)
|
||||||
|
|
||||||
proc readMessages(client: AsyncFD) {.async.} =
|
proc readMessages(client: AsyncFD) {.async.} =
|
||||||
|
# wrapping the AsyncFd into a AsyncSocket object
|
||||||
|
var sockObj = newAsyncSocket(client)
|
||||||
while true:
|
while true:
|
||||||
var line = await recvLine(client)
|
var line = await recvLine(sockObj)
|
||||||
if line == "":
|
if line == "":
|
||||||
closeSocket(client)
|
closeSocket(client)
|
||||||
clientCount.inc
|
clientCount.inc
|
||||||
|
|
@ -37,7 +39,7 @@ proc readMessages(client: AsyncFD) {.async.} =
|
||||||
doAssert false
|
doAssert false
|
||||||
|
|
||||||
proc createServer(port: Port) {.async.} =
|
proc createServer(port: Port) {.async.} =
|
||||||
var server = newAsyncNativeSocket()
|
var server = createAsyncNativeSocket()
|
||||||
block:
|
block:
|
||||||
var name: Sockaddr_in
|
var name: Sockaddr_in
|
||||||
name.sin_family = toInt(AF_INET).uint16
|
name.sin_family = toInt(AF_INET).uint16
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ type
|
||||||
AsyncScgiState* = object of RootObj ## SCGI state object
|
AsyncScgiState* = object of RootObj ## SCGI state object
|
||||||
|
|
||||||
#bug #442
|
#bug #442
|
||||||
import sockets, asyncio, strtabs
|
import asyncnet, strtabs
|
||||||
proc handleSCGIRequest[TScgi: ScgiState | AsyncScgiState](s: TScgi) =
|
proc handleSCGIRequest[TScgi: ScgiState | AsyncScgiState](s: TScgi) =
|
||||||
discard
|
discard
|
||||||
proc handleSCGIRequest(client: AsyncSocket, headers: StringTableRef,
|
proc handleSCGIRequest(client: AsyncSocket, headers: StringTableRef,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ discard """
|
||||||
|
|
||||||
# bug #5404
|
# bug #5404
|
||||||
|
|
||||||
import parseopt2
|
import parseopt
|
||||||
|
|
||||||
{.emit: """typedef struct {
|
{.emit: """typedef struct {
|
||||||
int key;
|
int key;
|
||||||
|
|
@ -12,5 +12,5 @@ import parseopt2
|
||||||
type foo* {.importc: "foo", nodecl.} = object
|
type foo* {.importc: "foo", nodecl.} = object
|
||||||
key* {.importc: "key".}: cint
|
key* {.importc: "key".}: cint
|
||||||
|
|
||||||
for kind, key, value in parseopt2.getopt():
|
for kind, key, value in parseopt.getopt():
|
||||||
discard
|
discard
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
discard """
|
||||||
|
disabled: true
|
||||||
|
"""
|
||||||
|
|
||||||
# This is a regression of the new lambda lifting; detected by Aporia
|
# This is a regression of the new lambda lifting; detected by Aporia
|
||||||
import asyncio, sockets
|
import asyncio, sockets
|
||||||
|
|
|
||||||
|
|
@ -166,3 +166,12 @@ seq4 =
|
||||||
var ii = 1
|
var ii = 1
|
||||||
let arr2 = [newMySeq(2, 5.0), if i > 1: newMySeq(3, 1.0) else: newMySeq(0, 0.0)]
|
let arr2 = [newMySeq(2, 5.0), if i > 1: newMySeq(3, 1.0) else: newMySeq(0, 0.0)]
|
||||||
var seqOfSeq2 = @[newMySeq(2, 5.0), newMySeq(3, 1.0)]
|
var seqOfSeq2 = @[newMySeq(2, 5.0), newMySeq(3, 1.0)]
|
||||||
|
|
||||||
|
|
||||||
|
## issue #10462
|
||||||
|
proc myfuncLoop(x: int): MySeqNonCopyable =
|
||||||
|
for i in 0..<x:
|
||||||
|
var cc = newMySeq(i, 5.0)
|
||||||
|
result = cc
|
||||||
|
|
||||||
|
discard myfuncLoop(3)
|
||||||
14
tests/effects/teffects7.nim
Normal file
14
tests/effects/teffects7.nim
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "can raise an unlisted exception: ref FloatingPointError"
|
||||||
|
line: 10
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc foo() {.raises: [].} =
|
||||||
|
try:
|
||||||
|
discard
|
||||||
|
except KeyError:
|
||||||
|
raise newException(FloatingPointError, "foo")
|
||||||
|
except Exception:
|
||||||
|
discard
|
||||||
|
|
||||||
|
foo()
|
||||||
31
tests/errmsgs/t10376.nim
Normal file
31
tests/errmsgs/t10376.nim
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "finalizer must be a direct reference to a procedure"
|
||||||
|
line: 29
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
A = ref object
|
||||||
|
|
||||||
|
proc my_callback(a: A) {. nimcall .} =
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc foo(callback: proc(a: A) {. nimcall .}) =
|
||||||
|
var x1: A
|
||||||
|
new(x1, proc (x: A) {.nimcall.} = discard)
|
||||||
|
var x2: A
|
||||||
|
new(x2, func (x: A) {.nimcall.} = discard)
|
||||||
|
|
||||||
|
var x3: A
|
||||||
|
proc foo1(a: A) {.nimcall.} = discard
|
||||||
|
new(x3, foo1)
|
||||||
|
var x4: A
|
||||||
|
func foo2(a: A) {.nimcall.} = discard
|
||||||
|
new(x4, foo2)
|
||||||
|
|
||||||
|
var x5: A
|
||||||
|
new(x5, my_callback)
|
||||||
|
|
||||||
|
var x6: A
|
||||||
|
new(x6, callback)
|
||||||
|
|
||||||
|
foo(my_callback)
|
||||||
5
tests/errmsgs/t8610.nim
Normal file
5
tests/errmsgs/t8610.nim
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
discard """
|
||||||
|
errmsg: "'typedesc' metatype is not valid here; typed '=' instead of ':'?"
|
||||||
|
"""
|
||||||
|
## issue #8610
|
||||||
|
const Foo = int
|
||||||
|
|
@ -64,3 +64,13 @@ proc return_in_except =
|
||||||
try: return_in_except()
|
try: return_in_except()
|
||||||
except: echo "RECOVER"
|
except: echo "RECOVER"
|
||||||
|
|
||||||
|
block: #10417
|
||||||
|
proc moo() {.noreturn.} = discard
|
||||||
|
|
||||||
|
let bar =
|
||||||
|
try:
|
||||||
|
1
|
||||||
|
except:
|
||||||
|
moo()
|
||||||
|
|
||||||
|
doAssert(bar == 1)
|
||||||
|
|
|
||||||
11
tests/generics/tsubclassgenericerror.nim
Normal file
11
tests/generics/tsubclassgenericerror.nim
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "cannot instantiate 'GenericParentType[T]' inside of type definition: 'GenericChildType'; Maybe generic arguments are missing?"
|
||||||
|
line: 8
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
GenericParentType[T] = ref object of RootObj
|
||||||
|
GenericChildType[T] = ref object of GenericParentType # missing the [T]
|
||||||
|
val: T
|
||||||
|
|
||||||
|
var instance : GenericChildType[int] = nil
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
discard """
|
discard """
|
||||||
errormsg: "cannot instantiate: 'GenericNodeObj'"
|
errormsg: "cannot instantiate: 'GenericNodeObj[T]'; Maybe generic arguments are missing?"
|
||||||
line: 21
|
line: 21
|
||||||
"""
|
"""
|
||||||
# bug #2509
|
# bug #2509
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ output: '''
|
||||||
Hallo Welt
|
Hallo Welt
|
||||||
Hallo Welt
|
Hallo Welt
|
||||||
1
|
1
|
||||||
|
()
|
||||||
'''
|
'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -34,3 +35,17 @@ macro t(): untyped =
|
||||||
t()
|
t()
|
||||||
|
|
||||||
echo tp()
|
echo tp()
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/nim-lang/Nim/issues/9866
|
||||||
|
type
|
||||||
|
# Foo = int # works
|
||||||
|
Foo = object # fails
|
||||||
|
|
||||||
|
macro dispatchGen(): untyped =
|
||||||
|
var shOpt: Foo
|
||||||
|
result = quote do:
|
||||||
|
let baz = `shOpt`
|
||||||
|
echo `shOpt`
|
||||||
|
|
||||||
|
dispatchGen()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import genpacket_enet, sockets, md5, enet
|
import genpacket_enet, nativesockets, net, md5, enet
|
||||||
defPacketImports()
|
defPacketImports()
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,7 @@ kind: cmdShortOption key:val -- r:1
|
||||||
kind: cmdShortOption key:val -- r:0
|
kind: cmdShortOption key:val -- r:0
|
||||||
kind: cmdShortOption key:val -- l:
|
kind: cmdShortOption key:val -- l:
|
||||||
kind: cmdShortOption key:val -- r:4
|
kind: cmdShortOption key:val -- r:4
|
||||||
parseopt2
|
'''
|
||||||
first round
|
|
||||||
kind: cmdLongOption key:val -- left:
|
|
||||||
second round
|
|
||||||
kind: cmdLongOption key:val -- left:
|
|
||||||
kind: cmdLongOption key:val -- debug:3
|
|
||||||
kind: cmdShortOption key:val -- l:4
|
|
||||||
kind: cmdShortOption key:val -- r:2'''
|
|
||||||
joinable: false
|
joinable: false
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -42,7 +35,6 @@ when defined(testament_tparseopt):
|
||||||
main()
|
main()
|
||||||
else:
|
else:
|
||||||
from parseopt import nil
|
from parseopt import nil
|
||||||
from parseopt2 import nil
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
echo "parseopt"
|
echo "parseopt"
|
||||||
|
|
@ -76,28 +68,11 @@ else:
|
||||||
for kind, key, val in parseopt.getopt(p):
|
for kind, key, val in parseopt.getopt(p):
|
||||||
echo "kind: ", kind, "\tkey:val -- ", key, ":", val
|
echo "kind: ", kind, "\tkey:val -- ", key, ":", val
|
||||||
|
|
||||||
block:
|
|
||||||
echo "parseopt2"
|
|
||||||
for kind, key, val in parseopt2.getopt():
|
|
||||||
echo "kind: ", kind, "\tkey:val -- ", key, ":", val
|
|
||||||
|
|
||||||
# pass custom cmdline arguments
|
|
||||||
echo "first round"
|
|
||||||
var argv: seq[string] = @["--left", "--debug:3", "-l=4", "-r:2"]
|
|
||||||
var p = parseopt2.initOptParser(argv)
|
|
||||||
for kind, key, val in parseopt2.getopt(p):
|
|
||||||
echo "kind: ", kind, "\tkey:val -- ", key, ":", val
|
|
||||||
break
|
|
||||||
# reset getopt iterator and check arguments are returned correctly.
|
|
||||||
echo "second round"
|
|
||||||
for kind, key, val in parseopt2.getopt(p):
|
|
||||||
echo "kind: ", kind, "\tkey:val -- ", key, ":", val
|
|
||||||
|
|
||||||
import osproc, os, strutils
|
import osproc, os, strutils
|
||||||
from stdtest/specialpaths import buildDir
|
from stdtest/specialpaths import buildDir
|
||||||
import "../.." / compiler/unittest_light
|
import "../.." / compiler/unittest_light
|
||||||
|
|
||||||
block: # fix #9951 (and make it work for parseopt and parseopt2)
|
block: # fix #9951
|
||||||
template runTest(parseoptCustom) =
|
template runTest(parseoptCustom) =
|
||||||
var p = parseoptCustom.initOptParser(@["echo \"quoted\""])
|
var p = parseoptCustom.initOptParser(@["echo \"quoted\""])
|
||||||
let expected = when defined(windows):
|
let expected = when defined(windows):
|
||||||
|
|
@ -117,7 +92,6 @@ else:
|
||||||
doAssert "a5'b" == "a5\'b"
|
doAssert "a5'b" == "a5\'b"
|
||||||
assertEquals parseoptCustom.cmdLineRest(p2), expected2
|
assertEquals parseoptCustom.cmdLineRest(p2), expected2
|
||||||
runTest(parseopt)
|
runTest(parseopt)
|
||||||
runTest(parseopt2)
|
|
||||||
|
|
||||||
block: # fix #9842
|
block: # fix #9842
|
||||||
let exe = buildDir / "D20190112T145450".addFileExt(ExeExt)
|
let exe = buildDir / "D20190112T145450".addFileExt(ExeExt)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
discard """
|
|
||||||
output: '''
|
|
||||||
100
|
|
||||||
200
|
|
||||||
300
|
|
||||||
400
|
|
||||||
'''
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threadpool, os
|
|
||||||
|
|
||||||
proc single(time: int) =
|
|
||||||
sleep time
|
|
||||||
echo time
|
|
||||||
|
|
||||||
proc sleepsort(nums: openArray[int]) =
|
|
||||||
parallel:
|
|
||||||
var i = 0
|
|
||||||
while i <= len(nums) + -1:
|
|
||||||
spawn single(nums[i])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
sleepsort([400,100,300,200])
|
|
||||||
|
|
@ -137,3 +137,20 @@ block:
|
||||||
type
|
type
|
||||||
Coord[N: static[int]] = tuple[col, row: range[0'i8 .. (N.int8-1)]]
|
Coord[N: static[int]] = tuple[col, row: range[0'i8 .. (N.int8-1)]]
|
||||||
Point[N: static[int]] = range[0'i16 .. N.int16 * N.int16 - 1]
|
Point[N: static[int]] = range[0'i16 .. N.int16 * N.int16 - 1]
|
||||||
|
|
||||||
|
# https://github.com/nim-lang/Nim/issues/10339
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
MicroKernel = object
|
||||||
|
a: float
|
||||||
|
b: int
|
||||||
|
|
||||||
|
macro extractA(ukernel: static MicroKernel): untyped =
|
||||||
|
result = newLit ukernel.a
|
||||||
|
|
||||||
|
proc tFunc[ukernel: static MicroKernel]() =
|
||||||
|
const x = ukernel.extractA
|
||||||
|
doAssert x == 5.5
|
||||||
|
|
||||||
|
const uk = MicroKernel(a: 5.5, b: 1)
|
||||||
|
tFunc[uk]()
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,63 @@ proc main() =
|
||||||
doAssert( U64A.rotateLeftBits(64) == U64A)
|
doAssert( U64A.rotateLeftBits(64) == U64A)
|
||||||
doAssert( U64A.rotateRightBits(64) == U64A)
|
doAssert( U64A.rotateRightBits(64) == U64A)
|
||||||
|
|
||||||
|
block:
|
||||||
|
# mask operations
|
||||||
|
var v: uint8
|
||||||
|
v.setMask(0b1100_0000)
|
||||||
|
v.setMask(0b0000_1100)
|
||||||
|
doAssert(v == 0b1100_1100)
|
||||||
|
v.flipMask(0b0101_0101)
|
||||||
|
doAssert(v == 0b1001_1001)
|
||||||
|
v.clearMask(0b1000_1000)
|
||||||
|
doAssert(v == 0b0001_0001)
|
||||||
|
v.clearMask(0b0001_0001)
|
||||||
|
doAssert(v == 0b0000_0000)
|
||||||
|
block:
|
||||||
|
# single bit operations
|
||||||
|
var v: uint8
|
||||||
|
v.setBit(0)
|
||||||
|
doAssert v == 0x0000_0001
|
||||||
|
v.setBit(1)
|
||||||
|
doAssert v == 0b0000_0011
|
||||||
|
v.flipBit(7)
|
||||||
|
doAssert v == 0b1000_0011
|
||||||
|
v.clearBit(0)
|
||||||
|
doAssert v == 0b1000_0010
|
||||||
|
v.flipBit(1)
|
||||||
|
doAssert v == 0b1000_0000
|
||||||
|
doAssert v.testbit(7)
|
||||||
|
doAssert not v.testbit(6)
|
||||||
|
block:
|
||||||
|
# multi bit operations
|
||||||
|
var v: uint8
|
||||||
|
v.setBits(0, 1, 7)
|
||||||
|
doAssert v == 0b1000_0011
|
||||||
|
v.flipBits(2, 3)
|
||||||
|
doAssert v == 0b1000_1111
|
||||||
|
v.clearBits(7, 0, 1)
|
||||||
|
doAssert v == 0b0000_1100
|
||||||
|
block:
|
||||||
|
# signed
|
||||||
|
var v: int8
|
||||||
|
v.setBit(7)
|
||||||
|
doAssert v == -128
|
||||||
|
block:
|
||||||
|
var v: uint64
|
||||||
|
v.setBit(63)
|
||||||
|
doAssert v == 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000'u64
|
||||||
|
block:
|
||||||
|
# Test if RangeError is thrown if indexing out of range
|
||||||
|
try:
|
||||||
|
var v: uint32
|
||||||
|
var i = 32
|
||||||
|
v.setBit(i)
|
||||||
|
doAssert false
|
||||||
|
except RangeError:
|
||||||
|
discard
|
||||||
|
except:
|
||||||
|
doAssert false
|
||||||
|
|
||||||
echo "OK"
|
echo "OK"
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
discard """
|
discard """
|
||||||
output: '''true
|
output: '''
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
All:
|
All:
|
||||||
__really_obscure_dir_name/are.x
|
__really_obscure_dir_name/are.x
|
||||||
__really_obscure_dir_name/created
|
__really_obscure_dir_name/created
|
||||||
|
|
@ -27,31 +19,13 @@ __really_obscure_dir_name/created
|
||||||
__really_obscure_dir_name/dirs
|
__really_obscure_dir_name/dirs
|
||||||
__really_obscure_dir_name/some
|
__really_obscure_dir_name/some
|
||||||
__really_obscure_dir_name/test
|
__really_obscure_dir_name/test
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
false
|
|
||||||
true
|
|
||||||
true
|
|
||||||
Raises
|
Raises
|
||||||
Raises
|
Raises
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
true
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
"""
|
"""
|
||||||
# test os path creation, iteration, and deletion
|
# test os path creation, iteration, and deletion
|
||||||
|
|
||||||
import os, strutils
|
import os, strutils, pathnorm
|
||||||
|
|
||||||
block fileOperations:
|
block fileOperations:
|
||||||
let files = @["these.txt", "are.x", "testing.r", "files.q"]
|
let files = @["these.txt", "are.x", "testing.r", "files.q"]
|
||||||
|
|
@ -60,17 +34,17 @@ block fileOperations:
|
||||||
let dname = "__really_obscure_dir_name"
|
let dname = "__really_obscure_dir_name"
|
||||||
|
|
||||||
createDir(dname)
|
createDir(dname)
|
||||||
echo dirExists(dname)
|
doAssert dirExists(dname)
|
||||||
|
|
||||||
# Test creating files and dirs
|
# Test creating files and dirs
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
createDir(dname/dir)
|
createDir(dname/dir)
|
||||||
echo dirExists(dname/dir)
|
doAssert dirExists(dname/dir)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
let fh = open(dname/file, fmReadWrite)
|
let fh = open(dname/file, fmReadWrite)
|
||||||
fh.close()
|
fh.close()
|
||||||
echo fileExists(dname/file)
|
doAssert fileExists(dname/file)
|
||||||
|
|
||||||
echo "All:"
|
echo "All:"
|
||||||
|
|
||||||
|
|
@ -93,23 +67,23 @@ block fileOperations:
|
||||||
# Test removal of files dirs
|
# Test removal of files dirs
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
removeDir(dname/dir)
|
removeDir(dname/dir)
|
||||||
echo dirExists(dname/dir)
|
doAssert: not dirExists(dname/dir)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
removeFile(dname/file)
|
removeFile(dname/file)
|
||||||
echo fileExists(dname/file)
|
doAssert: not fileExists(dname/file)
|
||||||
|
|
||||||
removeDir(dname)
|
removeDir(dname)
|
||||||
echo dirExists(dname)
|
doAssert: not dirExists(dname)
|
||||||
|
|
||||||
# createDir should create recursive directories
|
# createDir should create recursive directories
|
||||||
createDir(dirs[0] / dirs[1])
|
createDir(dirs[0] / dirs[1])
|
||||||
echo dirExists(dirs[0] / dirs[1]) # true
|
doAssert dirExists(dirs[0] / dirs[1]) # true
|
||||||
removeDir(dirs[0])
|
removeDir(dirs[0])
|
||||||
|
|
||||||
# createDir should properly handle trailing separator
|
# createDir should properly handle trailing separator
|
||||||
createDir(dname / "")
|
createDir(dname / "")
|
||||||
echo dirExists(dname) # true
|
doAssert dirExists(dname) # true
|
||||||
removeDir(dname)
|
removeDir(dname)
|
||||||
|
|
||||||
# createDir should raise IOError if the path exists
|
# createDir should raise IOError if the path exists
|
||||||
|
|
@ -138,10 +112,10 @@ block fileOperations:
|
||||||
copyDir("a", "../dest/a")
|
copyDir("a", "../dest/a")
|
||||||
removeDir("a")
|
removeDir("a")
|
||||||
|
|
||||||
echo dirExists("../dest/a/b")
|
doAssert dirExists("../dest/a/b")
|
||||||
echo fileExists("../dest/a/b/file.txt")
|
doAssert fileExists("../dest/a/b/file.txt")
|
||||||
|
|
||||||
echo fileExists("../dest/a/b/c/fileC.txt")
|
doAssert fileExists("../dest/a/b/c/fileC.txt")
|
||||||
removeDir("../dest")
|
removeDir("../dest")
|
||||||
|
|
||||||
# test copyDir:
|
# test copyDir:
|
||||||
|
|
@ -152,8 +126,8 @@ block fileOperations:
|
||||||
copyDir("a/", "../dest/a/")
|
copyDir("a/", "../dest/a/")
|
||||||
removeDir("a")
|
removeDir("a")
|
||||||
|
|
||||||
echo dirExists("../dest/a/b")
|
doAssert dirExists("../dest/a/b")
|
||||||
echo fileExists("../dest/a/file.txt")
|
doAssert fileExists("../dest/a/file.txt")
|
||||||
removeDir("../dest")
|
removeDir("../dest")
|
||||||
|
|
||||||
import times
|
import times
|
||||||
|
|
@ -165,9 +139,9 @@ block modificationTime:
|
||||||
setLastModificationTime("a", tm)
|
setLastModificationTime("a", tm)
|
||||||
|
|
||||||
when defined(macosx):
|
when defined(macosx):
|
||||||
echo "true"
|
doAssert true
|
||||||
else:
|
else:
|
||||||
echo getLastModificationTime("a") == tm
|
doAssert getLastModificationTime("a") == tm
|
||||||
removeFile("a")
|
removeFile("a")
|
||||||
|
|
||||||
block walkDirRec:
|
block walkDirRec:
|
||||||
|
|
@ -265,3 +239,97 @@ block splitFile:
|
||||||
doAssert splitFile("a/..") == ("a", "..", "")
|
doAssert splitFile("a/..") == ("a", "..", "")
|
||||||
|
|
||||||
# execShellCmd is tested in tosproc
|
# execShellCmd is tested in tosproc
|
||||||
|
|
||||||
|
block ospaths:
|
||||||
|
doAssert unixToNativePath("") == ""
|
||||||
|
doAssert unixToNativePath(".") == $CurDir
|
||||||
|
doAssert unixToNativePath("..") == $ParDir
|
||||||
|
doAssert isAbsolute(unixToNativePath("/"))
|
||||||
|
doAssert isAbsolute(unixToNativePath("/", "a"))
|
||||||
|
doAssert isAbsolute(unixToNativePath("/a"))
|
||||||
|
doAssert isAbsolute(unixToNativePath("/a", "a"))
|
||||||
|
doAssert isAbsolute(unixToNativePath("/a/b"))
|
||||||
|
doAssert isAbsolute(unixToNativePath("/a/b", "a"))
|
||||||
|
doAssert unixToNativePath("a/b") == joinPath("a", "b")
|
||||||
|
|
||||||
|
when defined(macos):
|
||||||
|
doAssert unixToNativePath("./") == ":"
|
||||||
|
doAssert unixToNativePath("./abc") == ":abc"
|
||||||
|
doAssert unixToNativePath("../abc") == "::abc"
|
||||||
|
doAssert unixToNativePath("../../abc") == ":::abc"
|
||||||
|
doAssert unixToNativePath("/abc", "a") == "abc"
|
||||||
|
doAssert unixToNativePath("/abc/def", "a") == "abc:def"
|
||||||
|
elif doslikeFileSystem:
|
||||||
|
doAssert unixToNativePath("./") == ".\\"
|
||||||
|
doAssert unixToNativePath("./abc") == ".\\abc"
|
||||||
|
doAssert unixToNativePath("../abc") == "..\\abc"
|
||||||
|
doAssert unixToNativePath("../../abc") == "..\\..\\abc"
|
||||||
|
doAssert unixToNativePath("/abc", "a") == "a:\\abc"
|
||||||
|
doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def"
|
||||||
|
else:
|
||||||
|
#Tests for unix
|
||||||
|
doAssert unixToNativePath("./") == "./"
|
||||||
|
doAssert unixToNativePath("./abc") == "./abc"
|
||||||
|
doAssert unixToNativePath("../abc") == "../abc"
|
||||||
|
doAssert unixToNativePath("../../abc") == "../../abc"
|
||||||
|
doAssert unixToNativePath("/abc", "a") == "/abc"
|
||||||
|
doAssert unixToNativePath("/abc/def", "a") == "/abc/def"
|
||||||
|
|
||||||
|
block extractFilenameTest:
|
||||||
|
doAssert extractFilename("") == ""
|
||||||
|
when defined(posix):
|
||||||
|
doAssert extractFilename("foo/bar") == "bar"
|
||||||
|
doAssert extractFilename("foo/bar.txt") == "bar.txt"
|
||||||
|
doAssert extractFilename("foo/") == ""
|
||||||
|
doAssert extractFilename("/") == ""
|
||||||
|
when doslikeFileSystem:
|
||||||
|
doAssert extractFilename(r"foo\bar") == "bar"
|
||||||
|
doAssert extractFilename(r"foo\bar.txt") == "bar.txt"
|
||||||
|
doAssert extractFilename(r"foo\") == ""
|
||||||
|
doAssert extractFilename(r"C:\") == ""
|
||||||
|
|
||||||
|
block lastPathPartTest:
|
||||||
|
doAssert lastPathPart("") == ""
|
||||||
|
when defined(posix):
|
||||||
|
doAssert lastPathPart("foo/bar.txt") == "bar.txt"
|
||||||
|
doAssert lastPathPart("foo/") == "foo"
|
||||||
|
doAssert lastPathPart("/") == ""
|
||||||
|
when doslikeFileSystem:
|
||||||
|
doAssert lastPathPart(r"foo\bar.txt") == "bar.txt"
|
||||||
|
doAssert lastPathPart(r"foo\") == "foo"
|
||||||
|
|
||||||
|
template canon(x): untyped = normalizePath(x, '/')
|
||||||
|
doAssert canon"/foo/../bar" == "/bar"
|
||||||
|
doAssert canon"foo/../bar" == "bar"
|
||||||
|
|
||||||
|
doAssert canon"/f/../bar///" == "/bar"
|
||||||
|
doAssert canon"f/..////bar" == "bar"
|
||||||
|
|
||||||
|
doAssert canon"../bar" == "../bar"
|
||||||
|
doAssert canon"/../bar" == "/../bar"
|
||||||
|
|
||||||
|
doAssert canon("foo/../../bar/") == "../bar"
|
||||||
|
doAssert canon("./bla/blob/") == "bla/blob"
|
||||||
|
doAssert canon(".hiddenFile") == ".hiddenFile"
|
||||||
|
doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim"
|
||||||
|
|
||||||
|
doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long"
|
||||||
|
doAssert canon("") == ""
|
||||||
|
doAssert canon("foobar") == "foobar"
|
||||||
|
doAssert canon("f/////////") == "f"
|
||||||
|
|
||||||
|
doAssert relativePath("/foo/bar//baz.nim", "/foo", '/') == "bar/baz.nim"
|
||||||
|
doAssert normalizePath("./foo//bar/../baz", '/') == "foo/baz"
|
||||||
|
|
||||||
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
|
||||||
|
|
||||||
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
|
||||||
|
doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
|
||||||
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
|
||||||
|
doAssert relativePath("", "/users/moo", '/') == ""
|
||||||
|
doAssert relativePath("foo", "", '/') == "foo"
|
||||||
|
|
||||||
|
doAssert joinPath("usr", "") == unixToNativePath"usr/"
|
||||||
|
doAssert joinPath("", "lib") == "lib"
|
||||||
|
doAssert joinPath("", "/lib") == unixToNativePath"/lib"
|
||||||
|
doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib"
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
discard """
|
|
||||||
output: ""
|
|
||||||
"""
|
|
||||||
# test the ospaths module
|
|
||||||
|
|
||||||
import os, pathnorm
|
|
||||||
|
|
||||||
doAssert unixToNativePath("") == ""
|
|
||||||
doAssert unixToNativePath(".") == $CurDir
|
|
||||||
doAssert unixToNativePath("..") == $ParDir
|
|
||||||
doAssert isAbsolute(unixToNativePath("/"))
|
|
||||||
doAssert isAbsolute(unixToNativePath("/", "a"))
|
|
||||||
doAssert isAbsolute(unixToNativePath("/a"))
|
|
||||||
doAssert isAbsolute(unixToNativePath("/a", "a"))
|
|
||||||
doAssert isAbsolute(unixToNativePath("/a/b"))
|
|
||||||
doAssert isAbsolute(unixToNativePath("/a/b", "a"))
|
|
||||||
doAssert unixToNativePath("a/b") == joinPath("a", "b")
|
|
||||||
|
|
||||||
when defined(macos):
|
|
||||||
doAssert unixToNativePath("./") == ":"
|
|
||||||
doAssert unixToNativePath("./abc") == ":abc"
|
|
||||||
doAssert unixToNativePath("../abc") == "::abc"
|
|
||||||
doAssert unixToNativePath("../../abc") == ":::abc"
|
|
||||||
doAssert unixToNativePath("/abc", "a") == "abc"
|
|
||||||
doAssert unixToNativePath("/abc/def", "a") == "abc:def"
|
|
||||||
elif doslikeFileSystem:
|
|
||||||
doAssert unixToNativePath("./") == ".\\"
|
|
||||||
doAssert unixToNativePath("./abc") == ".\\abc"
|
|
||||||
doAssert unixToNativePath("../abc") == "..\\abc"
|
|
||||||
doAssert unixToNativePath("../../abc") == "..\\..\\abc"
|
|
||||||
doAssert unixToNativePath("/abc", "a") == "a:\\abc"
|
|
||||||
doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def"
|
|
||||||
else:
|
|
||||||
#Tests for unix
|
|
||||||
doAssert unixToNativePath("./") == "./"
|
|
||||||
doAssert unixToNativePath("./abc") == "./abc"
|
|
||||||
doAssert unixToNativePath("../abc") == "../abc"
|
|
||||||
doAssert unixToNativePath("../../abc") == "../../abc"
|
|
||||||
doAssert unixToNativePath("/abc", "a") == "/abc"
|
|
||||||
doAssert unixToNativePath("/abc/def", "a") == "/abc/def"
|
|
||||||
|
|
||||||
block extractFilenameTest:
|
|
||||||
doAssert extractFilename("") == ""
|
|
||||||
when defined(posix):
|
|
||||||
doAssert extractFilename("foo/bar") == "bar"
|
|
||||||
doAssert extractFilename("foo/bar.txt") == "bar.txt"
|
|
||||||
doAssert extractFilename("foo/") == ""
|
|
||||||
doAssert extractFilename("/") == ""
|
|
||||||
when doslikeFileSystem:
|
|
||||||
doAssert extractFilename(r"foo\bar") == "bar"
|
|
||||||
doAssert extractFilename(r"foo\bar.txt") == "bar.txt"
|
|
||||||
doAssert extractFilename(r"foo\") == ""
|
|
||||||
doAssert extractFilename(r"C:\") == ""
|
|
||||||
|
|
||||||
block lastPathPartTest:
|
|
||||||
doAssert lastPathPart("") == ""
|
|
||||||
when defined(posix):
|
|
||||||
doAssert lastPathPart("foo/bar.txt") == "bar.txt"
|
|
||||||
doAssert lastPathPart("foo/") == "foo"
|
|
||||||
doAssert lastPathPart("/") == ""
|
|
||||||
when doslikeFileSystem:
|
|
||||||
doAssert lastPathPart(r"foo\bar.txt") == "bar.txt"
|
|
||||||
doAssert lastPathPart(r"foo\") == "foo"
|
|
||||||
|
|
||||||
template canon(x): untyped = normalizePath(x, '/')
|
|
||||||
doAssert canon"/foo/../bar" == "/bar"
|
|
||||||
doAssert canon"foo/../bar" == "bar"
|
|
||||||
|
|
||||||
doAssert canon"/f/../bar///" == "/bar"
|
|
||||||
doAssert canon"f/..////bar" == "bar"
|
|
||||||
|
|
||||||
doAssert canon"../bar" == "../bar"
|
|
||||||
doAssert canon"/../bar" == "/../bar"
|
|
||||||
|
|
||||||
doAssert canon("foo/../../bar/") == "../bar"
|
|
||||||
doAssert canon("./bla/blob/") == "bla/blob"
|
|
||||||
doAssert canon(".hiddenFile") == ".hiddenFile"
|
|
||||||
doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim"
|
|
||||||
|
|
||||||
doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long"
|
|
||||||
doAssert canon("") == ""
|
|
||||||
doAssert canon("foobar") == "foobar"
|
|
||||||
doAssert canon("f/////////") == "f"
|
|
||||||
|
|
||||||
doAssert relativePath("/foo/bar//baz.nim", "/foo", '/') == "bar/baz.nim"
|
|
||||||
doAssert normalizePath("./foo//bar/../baz", '/') == "foo/baz"
|
|
||||||
|
|
||||||
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
|
|
||||||
|
|
||||||
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
|
|
||||||
doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
|
|
||||||
doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
|
|
||||||
doAssert relativePath("", "/users/moo", '/') == ""
|
|
||||||
doAssert relativePath("foo", "", '/') == "foo"
|
|
||||||
|
|
||||||
doAssert joinPath("usr", "") == unixToNativePath"usr/"
|
|
||||||
doAssert joinPath("", "lib") == "lib"
|
|
||||||
doAssert joinPath("", "/lib") == unixToNativePath"/lib"
|
|
||||||
doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib"
|
|
||||||
|
|
@ -5,7 +5,7 @@ joinable: false
|
||||||
# not joinable because it executes itself with parameters
|
# not joinable because it executes itself with parameters
|
||||||
import os
|
import os
|
||||||
import osproc
|
import osproc
|
||||||
import parseopt2
|
import parseopt
|
||||||
import sequtils
|
import sequtils
|
||||||
|
|
||||||
let argv = commandLineParams()
|
let argv = commandLineParams()
|
||||||
|
|
@ -17,6 +17,6 @@ else:
|
||||||
let f = toSeq(getopt())
|
let f = toSeq(getopt())
|
||||||
doAssert f[0].kind == cmdArgument and f[0].key == "foo bar" and f[0].val == ""
|
doAssert f[0].kind == cmdArgument and f[0].key == "foo bar" and f[0].val == ""
|
||||||
doAssert f[1].kind == cmdLongOption and f[1].key == "aa" and f[1].val == "bar=a"
|
doAssert f[1].kind == cmdLongOption and f[1].key == "aa" and f[1].val == "bar=a"
|
||||||
doAssert f[2].kind == cmdLongOption and f[2].key == "a=c" and f[2].val == "d"
|
doAssert f[2].kind == cmdLongOption and f[2].key == "a" and f[2].val == "c:d"
|
||||||
doAssert f[3].kind == cmdLongOption and f[3].key == "ab" and f[3].val == ""
|
doAssert f[3].kind == cmdLongOption and f[3].key == "ab" and f[3].val == ""
|
||||||
doAssert f[4].kind == cmdShortOption and f[4].key == "c" and f[4].val == ""
|
doAssert f[4].kind == cmdShortOption and f[4].key == "c" and f[4].val == ""
|
||||||
|
|
|
||||||
8
tests/typerel/tptrs.nim
Normal file
8
tests/typerel/tptrs.nim
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "type mismatch: got <ptr int16> but expected 'ptr int'"
|
||||||
|
line: 8
|
||||||
|
"""
|
||||||
|
|
||||||
|
var
|
||||||
|
n: int16
|
||||||
|
p: ptr int = addr n
|
||||||
|
|
@ -5,7 +5,7 @@ discard """
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import events
|
import events
|
||||||
import sockets
|
import net
|
||||||
import strutils
|
import strutils
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,20 @@ let people = {
|
||||||
}.toTable()
|
}.toTable()
|
||||||
|
|
||||||
echo people["001"]
|
echo people["001"]
|
||||||
|
|
||||||
|
# Object downconversion should not copy
|
||||||
|
|
||||||
|
type
|
||||||
|
SomeBaseObj {.inheritable.} = object of RootObj
|
||||||
|
txt : string
|
||||||
|
InheritedFromBase = object of SomeBaseObj
|
||||||
|
other : string
|
||||||
|
|
||||||
|
proc initBase(sbo: var SomeBaseObj) =
|
||||||
|
sbo.txt = "Initialized string from base"
|
||||||
|
|
||||||
|
static:
|
||||||
|
var ifb2: InheritedFromBase
|
||||||
|
initBase(SomeBaseObj(ifb2))
|
||||||
|
echo repr(ifb2)
|
||||||
|
doAssert(ifb2.txt == "Initialized string from base")
|
||||||
|
|
|
||||||
15
tests/vm/tvarsection.nim
Normal file
15
tests/vm/tvarsection.nim
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
discard """
|
||||||
|
output: '''-1abc'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
var
|
||||||
|
a {.compileTime.} = 2
|
||||||
|
b = -1
|
||||||
|
c {.compileTime.} = 3
|
||||||
|
d = "abc"
|
||||||
|
|
||||||
|
static:
|
||||||
|
assert a == 2
|
||||||
|
assert c == 3
|
||||||
|
|
||||||
|
echo b, d
|
||||||
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