diff --git a/.gitignore b/.gitignore
index e4cec0ef6..57b8a68d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
*
!**/
!*.*
+
+# Cache
nimcache/
+rnimcache/
+dnimcache/
*.o
!/icons/*.o
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 000000000..76c94c8e7
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,61 @@
+image: ubuntu:16.04
+
+stages:
+ - pre-build
+ - build
+ - deploy
+ - test
+
+.linux_set_path: &linux_set_path_def
+ before_script:
+ - export PATH=$(pwd)/bin:$PATH
+ tags:
+ - linux
+
+.windows_set_path: &win_set_path_def
+ before_script:
+ - set PATH=%CD%\bin;%PATH%
+ tags:
+ - windows
+
+
+build-windows:
+ stage: build
+ script:
+ - ci\build.bat
+ artifacts:
+ paths:
+ - bin\nim.exe
+ - bin\nimd.exe
+ - compiler\nim.exe
+ - koch.exe
+ expire_in: 1 week
+ tags:
+ - windows
+
+deploy-windows:
+ stage: deploy
+ script:
+ - koch.exe winrelease
+ artifacts:
+ paths:
+ - build/*.exe
+ - build/*.zip
+ expire_in: 1 week
+ tags:
+ - windows
+ - fast
+
+
+
+test-windows:
+ stage: test
+ <<: *win_set_path_def
+ script:
+ - call ci\deps.bat
+ - nim c --taintMode:on tests\testament\tester
+ - tests\testament\tester.exe --pedantic all
+ tags:
+ - windows
+ - fast
+
diff --git a/.travis.yml b/.travis.yml
index 0ebeeb995..ebf287502 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,7 +28,7 @@ script:
- nimble install zip
- nimble install opengl
- nimble install sdl1
- - nimble install jester
+ - nimble install jester@#head
- nimble install niminst
- nim c --taintMode:on tests/testament/tester
- tests/testament/tester --pedantic all
diff --git a/bootstrap.sh b/bootstrap.sh
index dd551b52d..5d05ddbf8 100644
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -3,7 +3,7 @@ set -e
set -x
if [ ! -e csources/.git ]; then
- git clone --depth 1 git://github.com/nim-lang/csources.git csources
+ git clone --depth 1 https://github.com/nim-lang/csources.git csources
fi
cd "csources"
diff --git a/build_tools.sh b/build_tools.sh
new file mode 100644
index 000000000..4d867b0ad
--- /dev/null
+++ b/build_tools.sh
@@ -0,0 +1,3 @@
+./bin/nim c --noNimblePath -p:compiler -o:./bin/nimble dist/nimble/src/nimble.nim
+./bin/nim c --noNimblePath -p:compiler -o:./bin/nimsuggest dist/nimsuggest/nimsuggest.nim
+./bin/nim c -o:./bin/nimgrep tools/nimgrep.nim
diff --git a/ci/build.bat b/ci/build.bat
new file mode 100644
index 000000000..cdce8d3d2
--- /dev/null
+++ b/ci/build.bat
@@ -0,0 +1,14 @@
+REM Some debug info
+echo "Running on %CI_RUNNER_ID% (%CI_RUNNER_DESCRIPTION%) with tags %CI_RUNNER_TAGS%."
+gcc -v
+
+git clone --depth 1 https://github.com/nim-lang/csources.git
+cd csources
+call build64.bat
+cd ..
+set PATH=%CD%\bin;%PATH%
+nim -v
+nim c koch
+koch.exe boot
+copy bin/nim bin/nimd
+koch.exe boot -d:release
diff --git a/ci/build.sh b/ci/build.sh
new file mode 100644
index 000000000..a0fee1497
--- /dev/null
+++ b/ci/build.sh
@@ -0,0 +1,15 @@
+sh ci/deps.sh
+
+# Build from C sources.
+git clone --depth 1 https://github.com/nim-lang/csources.git
+cd csources
+sh build.sh
+cd ..
+# Add Nim to the PATH
+export PATH=$(pwd)/bin:$PATH
+# Bootstrap.
+nim -v
+nim c koch
+./koch boot
+cp bin/nim bin/nimd
+./koch boot -d:release
diff --git a/ci/deps.bat b/ci/deps.bat
new file mode 100644
index 000000000..bda1fe14f
--- /dev/null
+++ b/ci/deps.bat
@@ -0,0 +1,4 @@
+nim e install_nimble.nims
+nim e tests/test_nimscript.nims
+nimble update
+nimble install -y zip opengl sdl1 jester@#head niminst
diff --git a/ci/deps.sh b/ci/deps.sh
new file mode 100644
index 000000000..3385a213b
--- /dev/null
+++ b/ci/deps.sh
@@ -0,0 +1,17 @@
+# Some debug info
+echo "Running on $CI_RUNNER_ID ($CI_RUNNER_DESCRIPTION) with tags $CI_RUNNER_TAGS."
+
+# Packages
+apt-get update -qq
+apt-get install -y -qq build-essential git libcurl4-openssl-dev libsdl1.2-dev libgc-dev nodejs fasm
+
+gcc -v
+
+fasm -v
+export PATH=$(pwd)/bin:$PATH
+
+# Nimble deps
+nim e install_nimble.nims
+nim e tests/test_nimscript.nims
+nimble update
+nimble install zip opengl sdl1 jester@#head niminst
diff --git a/ci/nsis_build.bat b/ci/nsis_build.bat
new file mode 100644
index 000000000..657b2b90a
--- /dev/null
+++ b/ci/nsis_build.bat
@@ -0,0 +1,62 @@
+REM - Run the full testsuite; tests\testament\tester all
+
+REM - Uncomment the list of changes in news.txt
+REM - write a news ticker entry
+REM - Update the version
+
+REM - Generate the full docs; koch web0
+REM - Generate the installers;
+REM - Update the version in system.nim
+REM - Test the installers
+REM - Tag the release
+REM - Merge devel into master
+REM - Update csources
+
+set NIMVER=%1
+
+Rem Build -docs file:
+koch web0
+cd web\upload
+7z a -tzip docs-%NIMVER%.zip *.html
+move /y docs-%NIMVER%.zip download
+cd ..\..
+
+Rem Build .zip file:
+rem koch csources -d:release
+rem koch xz -d:release
+rem move /y build\nim-%NIMVER%.zip web\upload\download
+
+
+rem Grab C sources and nimsuggest
+git clone --depth 1 https://github.com/nim-lang/csources.git
+
+set PATH=%CD%\bin;%PATH%
+
+ReM Build Win32 version:
+
+set PATH=C:\Users\araq\projects\mingw32\bin;%PATH%
+cd csources
+call build.bat
+cd ..
+ReM Rebuilding koch is necessary because it uses its pointer size to determine
+ReM which mingw link to put in the NSIS installer.
+nim c --out:koch_temp koch || exit /b
+koch_temp boot -d:release || exit /b
+koch_temp nsis -d:release || exit /b
+koch_temp zip -d:release || exit /b
+dir build
+move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x32.exe || exit /b
+move /y build\nim_%NIMVER%.zip build\nim-%NIMVER%_x32.zip || exit /b
+
+
+ReM Build Win64 version:
+set PATH=C:\Users\araq\projects\mingw64\bin;%PATH%
+cd csources
+call build64.bat
+cd ..
+nim c --out:koch_temp koch || exit /b
+koch_temp boot -d:release || exit /b
+koch_temp nsis -d:release || exit /b
+koch_temp zip -d:release || exit /b
+move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x64.exe || exit /b
+move /y build\nim_%NIMVER%.zip build\nim-%NIMVER%_x64.zip || exit /b
diff --git a/compiler.nimble b/compiler.nimble
index c63fe49bf..66d2ee7c2 100644
--- a/compiler.nimble
+++ b/compiler.nimble
@@ -1,6 +1,6 @@
[Package]
name = "compiler"
-version = "0.14.3"
+version = "0.15.1"
author = "Andreas Rumpf"
description = "Compiler package providing the compiler sources as a library."
license = "MIT"
@@ -8,4 +8,4 @@ license = "MIT"
InstallDirs = "doc, compiler"
[Deps]
-Requires: "nim >= 0.13.0"
+Requires: "nim >= 0.14.0"
diff --git a/compiler/aliases.nim b/compiler/aliases.nim
index 4b592ee60..4186900ec 100644
--- a/compiler/aliases.nim
+++ b/compiler/aliases.nim
@@ -46,7 +46,8 @@ proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult =
if compareTypes(a, b, dcEqIgnoreDistinct): return arYes
case a.kind
of tyObject:
- result = isPartOfAux(a.sons[0], b, marker)
+ if a.sons[0] != nil:
+ result = isPartOfAux(a.sons[0].skipTypes(skipPtrs), b, marker)
if result == arNo: result = isPartOfAux(a.n, b, marker)
of tyGenericInst, tyDistinct:
result = isPartOfAux(lastSon(a), b, marker)
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 277a21ba5..d8939fc60 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -437,7 +437,7 @@ type
nfExplicitCall # x.y() was used instead of x.y
nfExprCall # this is an attempt to call a regular expression
nfIsRef # this node is a 'ref' node; used for the VM
- nfIsCursor # this node is attached a cursor; used for idetools
+ nfPreventCg # this node should be ignored by the codegen
TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 28)
@@ -725,10 +725,7 @@ type
s*: TStorageLoc
flags*: TLocFlags # location's flags
t*: PType # type of location
- r*: Rope # rope value of location (code generators)
- heapRoot*: Rope # keeps track of the enclosing heap object that
- # owns this location (required by GC algorithms
- # employing heap snapshots or sliding views)
+ r*: Rope # rope value of location (code generators)
# ---------------- end of backend information ------------------------------
@@ -746,10 +743,6 @@ type
TInstantiation* = object
sym*: PSym
concreteTypes*: seq[PType]
- usedBy*: seq[int32] # list of modules using the generic
- # needed in caas mode for purging the cache
- # XXX: it's possible to switch to a
- # simple ref count here
compilesId*: CompilesId
PInstantiation* = ref TInstantiation
@@ -767,7 +760,6 @@ type
case kind*: TSymKind
of skType, skGenericParam:
typeInstCache*: seq[PType]
- typScope*: PScope
of routineKinds:
procInstCache*: seq[PInstantiation]
gcUnsafetyReason*: PSym # for better error messages wrt gcsafe
@@ -862,9 +854,6 @@ type
key*, val*: RootRef
TPairSeq* = seq[TPair]
- TTable* = object # the same as table[PObject] of PObject
- counter*: int
- data*: TPairSeq
TIdPair* = object
key*: PIdObj
@@ -936,7 +925,7 @@ const
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias}
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
nfDotSetter, nfDotField,
- nfIsRef, nfIsCursor, nfLL}
+ nfIsRef, nfPreventCg, nfLL}
namePos* = 0
patternPos* = 1 # empty except for term rewriting macros
genericParamsPos* = 2
@@ -1108,12 +1097,6 @@ proc copyIdTable*(dest: var TIdTable, src: TIdTable) =
newSeq(dest.data, len(src.data))
for i in countup(0, high(src.data)): dest.data[i] = src.data[i]
-proc copyTable*(dest: var TTable, src: TTable) =
- dest.counter = src.counter
- if isNil(src.data): return
- setLen(dest.data, len(src.data))
- for i in countup(0, high(src.data)): dest.data[i] = src.data[i]
-
proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) =
dest.counter = src.counter
if isNil(src.data): return
@@ -1327,10 +1310,6 @@ proc initStrTable*(x: var TStrTable) =
proc newStrTable*: TStrTable =
initStrTable(result)
-proc initTable(x: var TTable) =
- x.counter = 0
- newSeq(x.data, StartSize)
-
proc initIdTable*(x: var TIdTable) =
x.counter = 0
newSeq(x.data, StartSize)
@@ -1429,6 +1408,7 @@ proc copyNode*(src: PNode): PNode =
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
+ result.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
@@ -1447,6 +1427,7 @@ proc shallowCopy*(src: PNode): PNode =
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
+ result.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
@@ -1466,6 +1447,7 @@ proc copyTree*(src: PNode): PNode =
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
+ result.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
@@ -1511,19 +1493,9 @@ proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
return true
result = false
-proc replaceSons(n: PNode, oldKind, newKind: TNodeKind) =
- for i in countup(0, sonsLen(n) - 1):
- if n.sons[i].kind == oldKind: n.sons[i].kind = newKind
-
-proc sonsNotNil(n: PNode): bool =
- for i in countup(0, sonsLen(n) - 1):
- if n.sons[i] == nil:
- return false
- result = true
-
proc getInt*(a: PNode): BiggestInt =
case a.kind
- of nkIntLit..nkUInt64Lit: result = a.intVal
+ of nkCharLit..nkUInt64Lit: result = a.intVal
else:
internalError(a.info, "getInt")
result = 0
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index 3ca44ea7e..7c07b2995 100644
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -31,17 +31,6 @@ proc objectSetIncl*(t: var TObjectSet, obj: RootRef)
proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool
# more are not needed ...
-# ----------------------- (key, val)-Hashtables ----------------------------
-proc tablePut*(t: var TTable, key, val: RootRef)
-proc tableGet*(t: TTable, key: RootRef): RootRef
-type
- TCmpProc* = proc (key, closure: RootRef): bool {.nimcall.} # true if found
-
-proc tableSearch*(t: TTable, key, closure: RootRef,
- comparator: TCmpProc): RootRef
- # return val as soon as comparator returns true; if this never happens,
- # nil is returned
-
# ----------------------- str table -----------------------------------------
proc strTableContains*(t: TStrTable, n: PSym): bool
proc strTableAdd*(t: var TStrTable, n: PSym)
@@ -251,20 +240,6 @@ proc symToYamlAux(n: PSym, marker: var IntSet,
indent, maxRecDepth: int): Rope
proc typeToYamlAux(n: PType, marker: var IntSet,
indent, maxRecDepth: int): Rope
-proc strTableToYaml(n: TStrTable, marker: var IntSet, indent: int,
- maxRecDepth: int): Rope =
- var istr = rspaces(indent + 2)
- result = rope("[")
- var mycount = 0
- for i in countup(0, high(n.data)):
- if n.data[i] != nil:
- if mycount > 0: add(result, ",")
- addf(result, "$N$1$2",
- [istr, symToYamlAux(n.data[i], marker, indent + 2, maxRecDepth - 1)])
- inc(mycount)
- if mycount > 0: addf(result, "$N$1", [rspaces(indent)])
- add(result, "]")
- assert(mycount == n.counter)
proc ropeConstr(indent: int, c: openArray[Rope]): Rope =
# array of (name, value) pairs
@@ -463,9 +438,6 @@ proc debug(n: PType) =
proc debug(n: PNode) =
echo($debugTree(n, 0, 100))
-const
- EmptySeq = @[]
-
proc nextTry(h, maxHash: Hash): Hash =
result = ((5 * h) + 1) and maxHash
# For any initial h in range(maxHash), repeating that maxHash times
@@ -519,55 +491,6 @@ proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool =
inc(t.counter)
result = false
-proc tableRawGet(t: TTable, key: RootRef): int =
- var h: Hash = hashNode(key) and high(t.data) # start with real hash value
- while t.data[h].key != nil:
- if t.data[h].key == key:
- return h
- h = nextTry(h, high(t.data))
- result = -1
-
-proc tableSearch(t: TTable, key, closure: RootRef,
- comparator: TCmpProc): RootRef =
- var h: Hash = hashNode(key) and high(t.data) # start with real hash value
- while t.data[h].key != nil:
- if t.data[h].key == key:
- if comparator(t.data[h].val, closure):
- # BUGFIX 1
- return t.data[h].val
- h = nextTry(h, high(t.data))
- result = nil
-
-proc tableGet(t: TTable, key: RootRef): RootRef =
- var index = tableRawGet(t, key)
- if index >= 0: result = t.data[index].val
- else: result = nil
-
-proc tableRawInsert(data: var TPairSeq, key, val: RootRef) =
- var h: Hash = hashNode(key) and high(data)
- while data[h].key != nil:
- assert(data[h].key != key)
- h = nextTry(h, high(data))
- assert(data[h].key == nil)
- data[h].key = key
- data[h].val = val
-
-proc tableEnlarge(t: var TTable) =
- var n: TPairSeq
- newSeq(n, len(t.data) * GrowthFactor)
- for i in countup(0, high(t.data)):
- if t.data[i].key != nil: tableRawInsert(n, t.data[i].key, t.data[i].val)
- swap(t.data, n)
-
-proc tablePut(t: var TTable, key, val: RootRef) =
- var index = tableRawGet(t, key)
- if index >= 0:
- t.data[index].val = val
- else:
- if mustRehash(len(t.data), t.counter): tableEnlarge(t)
- tableRawInsert(t.data, key, val)
- inc(t.counter)
-
proc strTableContains(t: TStrTable, n: PSym): bool =
var h: Hash = n.name.h and high(t.data) # start with real hash value
while t.data[h] != nil:
diff --git a/compiler/canonicalizer.nim b/compiler/canonicalizer.nim
index 089bce302..2abe0a0e6 100644
--- a/compiler/canonicalizer.nim
+++ b/compiler/canonicalizer.nim
@@ -383,18 +383,28 @@ proc createDb() =
interfHash varchar(256) not null,
fullHash varchar(256) not null,
- created timestamp not null default (DATETIME('now')),
+ created timestamp not null default (DATETIME('now'))
);""")
db.exec(sql"""
+ create table if not exists Backend(
+ id integer primary key,
+ strongdeps varchar(max) not null,
+ weakdeps varchar(max) not null,
+ header varchar(max) not null,
+ code varchar(max) not null
+ )
+
create table if not exists Symbol(
id integer primary key,
module integer not null,
+ backend integer not null,
name varchar(max) not null,
data varchar(max) not null,
created timestamp not null default (DATETIME('now')),
- foreign key (module) references module(id)
+ foreign key (module) references Module(id),
+ foreign key (backend) references Backend(id)
);""")
db.exec(sql"""
@@ -409,7 +419,3 @@ proc createDb() =
);""")
- #db.exec(sql"""
- # --create unique index if not exists TsstNameIx on TestResult(name);
- # """, [])
-
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim
index dffb8a9a5..48157925c 100644
--- a/compiler/ccgcalls.nim
+++ b/compiler/ccgcalls.nim
@@ -411,7 +411,7 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope =
add(result, substr(pat, start, i - 1))
proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
- var op, a: TLoc
+ var op: TLoc
initLocExpr(p, ri.sons[0], op)
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.sons[0].typ, abstractInst)
@@ -458,7 +458,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
# generates a crappy ObjC call
- var op, a: TLoc
+ var op: TLoc
initLocExpr(p, ri.sons[0], op)
var pl = ~"["
# getUniqueType() is too expensive here:
@@ -536,8 +536,6 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) =
else:
genPrefixCall(p, nil, e, d)
postStmtActions(p)
- when false:
- if d.s == onStack and containsGarbageCollectedRef(d.t): keepAlive(p, d)
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
if ri.sons[0].typ.skipTypes({tyGenericInst}).callConv == ccClosure:
@@ -549,6 +547,3 @@ proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
else:
genPrefixCall(p, le, ri, d)
postStmtActions(p)
- when false:
- if d.s == onStack and containsGarbageCollectedRef(d.t): keepAlive(p, d)
-
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 6c96f209e..be49ddc87 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -30,19 +30,6 @@ proc intLiteral(i: BiggestInt): Rope =
else:
result = ~"(IL64(-9223372036854775807) - IL64(1))"
-proc int32Literal(i: int): Rope =
- if i == int(low(int32)):
- result = ~"(-2147483647 -1)"
- else:
- result = rope(i)
-
-proc genHexLiteral(v: PNode): Rope =
- # hex literals are unsigned in C
- # so we don't generate hex literals any longer.
- if v.kind notin {nkIntLit..nkUInt64Lit}:
- internalError(v.info, "genHexLiteral")
- result = intLiteral(v.intVal)
-
proc getStrLit(m: BModule, s: string): Rope =
discard cgsym(m, "TGenericSeq")
result = getTempName(m)
@@ -171,7 +158,6 @@ proc getStorageLoc(n: PNode): TStorageLoc =
proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
if dest.s == OnStack or not usesNativeGC():
linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
- if needToKeepAlive in flags: keepAlive(p, dest)
elif dest.s == OnHeap:
# location is on heap
# now the writer barrier is inlined for performance:
@@ -198,7 +184,6 @@ proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
else:
linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n",
addrLoc(dest), rdLoc(src))
- if needToKeepAlive in flags: keepAlive(p, dest)
proc asgnComplexity(n: PNode): int =
if n != nil:
@@ -218,7 +203,6 @@ proc optAsgnLoc(a: TLoc, t: PType, field: Rope): TLoc =
result.s = a.s
result.t = t
result.r = rdLoc(a) & "." & field
- result.heapRoot = a.heapRoot
proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
let newflags =
@@ -268,7 +252,6 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
linefmt(p, cpsStmts,
"memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
addrLoc(dest), addrLoc(src), rdLoc(dest))
- if needToKeepAlive in flags: keepAlive(p, dest)
else:
linefmt(p, cpsStmts, "#genericShallowAssign((void*)$1, (void*)$2, $3);$n",
addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t))
@@ -299,7 +282,6 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
else:
if dest.s == OnStack or not usesNativeGC():
linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc)
- if needToKeepAlive in flags: keepAlive(p, dest)
elif dest.s == OnHeap:
# we use a temporary to care for the dreaded self assignment:
var tmp: TLoc
@@ -310,7 +292,6 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
else:
linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n",
addrLoc(dest), rdLoc(src))
- if needToKeepAlive in flags: keepAlive(p, dest)
of tyProc:
if needsComplexAssignment(dest.t):
# optimize closure assignment:
@@ -400,9 +381,6 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) =
linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
else: internalError("genDeepCopy: " & $ty.kind)
-proc getDestLoc(p: BProc, d: var TLoc, typ: PType) =
- if d.k == locNone: getTemp(p, typ, d)
-
proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) =
if d.k != locNone:
if lfNoDeepCopy in d.flags: genAssignment(p, d, s, {})
@@ -453,13 +431,6 @@ proc unaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) =
initLocExpr(p, e.sons[1], a)
lineCg(p, cpsStmts, frmt, [rdLoc(a)])
-proc binaryStmtChar(p: BProc, e: PNode, d: var TLoc, frmt: string) =
- var a, b: TLoc
- if (d.k != locNone): internalError(e.info, "binaryStmtChar")
- initLocExpr(p, e.sons[1], a)
- initLocExpr(p, e.sons[2], b)
- lineCg(p, cpsStmts, frmt, [rdCharLoc(a), rdCharLoc(b)])
-
proc binaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: string) =
var a, b: TLoc
assert(e.sons[1].typ != nil)
@@ -728,8 +699,6 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) =
template inheritLocation(d: var TLoc, a: TLoc) =
if d.k == locNone: d.s = a.s
- if d.heapRoot == nil:
- d.heapRoot = if a.heapRoot != nil: a.heapRoot else: a.r
proc genRecordFieldAux(p: BProc, e: PNode, d, a: var TLoc): PType =
initLocExpr(p, e.sons[0], a)
@@ -757,6 +726,7 @@ proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope): PSym =
var ty = ty
assert r != nil
while ty != nil:
+ ty = ty.skipTypes(skipPtrs)
assert(ty.kind in {tyTuple, tyObject})
result = lookupInRecord(ty.n, field.name)
if result != nil: break
@@ -893,7 +863,6 @@ proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) =
"if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n",
rdLoc(b), rdLoc(a), lenField(p))
if d.k == locNone: d.s = OnHeap
- d.heapRoot = a.r
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
a.r = rfmt(nil, "(*$1)", a.r)
putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)),
@@ -1008,9 +977,8 @@ proc genStrConcat(p: BProc, e: PNode, d: var TLoc) =
add(p.s(cpsStmts), appends)
if d.k == locNone:
d = tmp
- keepAlive(p, tmp)
else:
- genAssignment(p, d, tmp, {needToKeepAlive}) # no need for deep copying
+ genAssignment(p, d, tmp, {}) # no need for deep copying
gcUsage(e)
proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
@@ -1047,7 +1015,6 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
rdLoc(dest), rdLoc(a)))
linefmt(p, cpsStmts, "$1 = #resizeString($1, $2$3);$n",
rdLoc(dest), lens, rope(L))
- keepAlive(p, dest)
add(p.s(cpsStmts), appends)
gcUsage(e)
@@ -1067,7 +1034,6 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) =
rdLoc(a),
getTypeDesc(p.module, skipTypes(e.sons[1].typ, abstractVar)),
getTypeDesc(p.module, bt)])
- keepAlive(p, a)
#if bt != b.t:
# echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t)
initLoc(dest, locExpr, bt, OnHeap)
@@ -1094,7 +1060,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
genTypeInfo(p.module, refType),
sizeExpr]
if a.s == OnHeap and usesNativeGC():
- # use newObjRC1 as an optimization; and we don't need 'keepAlive' either
+ # use newObjRC1 as an optimization
if canFormAcycle(a.t):
linefmt(p, cpsStmts, "if ($1) #nimGCunref($1);$n", a.rdLoc)
else:
@@ -1103,7 +1069,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc)
else:
b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
- genAssignment(p, a, b, {needToKeepAlive}) # set the object type:
+ genAssignment(p, a, b, {}) # set the object type:
let bt = skipTypes(refType.sons[0], abstractRange)
genObjectInit(p, cpsStmts, bt, a, false)
@@ -1134,7 +1100,7 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope) =
linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc)
else:
call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args)
- genAssignment(p, dest, call, {needToKeepAlive})
+ genAssignment(p, dest, call, {})
proc genNewSeq(p: BProc, e: PNode) =
var a, b: TLoc
@@ -1199,7 +1165,6 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
tmp2.k = locTemp
tmp2.t = field.loc.t
tmp2.s = if isRef: OnHeap else: OnStack
- tmp2.heapRoot = tmp.r
expr(p, it.sons[1], tmp2)
if d.k == locNone:
@@ -1246,7 +1211,6 @@ proc genNewFinalize(p: BProc, e: PNode) =
a, b, f: TLoc
refType, bt: PType
ti: Rope
- oldModule: BModule
refType = skipTypes(e.sons[1].typ, abstractVarRange)
initLocExpr(p, e.sons[1], a)
initLocExpr(p, e.sons[2], f)
@@ -1256,7 +1220,7 @@ proc genNewFinalize(p: BProc, e: PNode) =
b.r = ropecg(p.module, "($1) #newObj($2, sizeof($3))", [
getTypeDesc(p.module, refType),
ti, getTypeDesc(p.module, skipTypes(refType.lastSon, abstractRange))])
- genAssignment(p, a, b, {needToKeepAlive}) # set the object type:
+ genAssignment(p, a, b, {}) # set the object type:
bt = skipTypes(refType.lastSon, abstractRange)
genObjectInit(p, cpsStmts, bt, a, false)
gcUsage(e)
@@ -1294,7 +1258,7 @@ proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) =
if not p.module.compileToCpp:
while t.kind == tyObject and t.sons[0] != nil:
add(r, ~".Sup")
- t = skipTypes(t.sons[0], typedescInst)
+ t = skipTypes(t.sons[0], skipPtrs)
if isObjLackingTypeField(t):
globalError(x.info, errGenerated,
"no 'of' operator available for pure objects")
@@ -1366,7 +1330,7 @@ proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) =
initLocExpr(p, n.sons[1], a)
a.r = ropecg(p.module, frmt, [rdLoc(a)])
if d.k == locNone: getTemp(p, n.typ, d)
- genAssignment(p, d, a, {needToKeepAlive})
+ genAssignment(p, d, a, {})
gcUsage(n)
proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
@@ -1408,12 +1372,10 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) =
lineCg(p, cpsStmts, setLenPattern, [
rdLoc(a), rdLoc(b), getTypeDesc(p.module, t),
getTypeDesc(p.module, t.sons[0])])
- keepAlive(p, a)
gcUsage(e)
proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) =
binaryStmt(p, e, d, "$1 = #setLengthStr($1, $2);$n")
- keepAlive(p, d)
gcUsage(e)
proc genSwap(p: BProc, e: PNode, d: var TLoc) =
@@ -1683,7 +1645,6 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
binaryArith(p, e, d, m)
proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
- var line, filen: Rope
case op
of mOr, mAnd: genAndOr(p, e, d, op)
of mNot..mToBiggestInt: unaryArith(p, e, d, op)
@@ -1721,10 +1682,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
getTypeDesc(p.module, ranged), res])
of mConStrStr: genStrConcat(p, e, d)
- of mAppendStrCh:
- binaryStmt(p, e, d, "$1 = #addChar($1, $2);$n")
- # strictly speaking we need to generate "keepAlive" here too, but this
- # very likely not needed and would slow down the code too much I fear
+ of mAppendStrCh: binaryStmt(p, e, d, "$1 = #addChar($1, $2);$n")
of mAppendStrStr: genStrAppend(p, e, d)
of mAppendSeqElem: genSeqElemAppend(p, e, d)
of mEqStr: genStrEquals(p, e, d)
@@ -1918,7 +1876,7 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
if not p.module.compileToCpp:
while t.kind == tyObject and t.sons[0] != nil:
add(r, ".Sup")
- t = skipTypes(t.sons[0], abstractInst)
+ t = skipTypes(t.sons[0], skipPtrs)
if nilCheck != nil:
linefmt(p, cpsStmts, "if ($1) #chckObj($2.m_type, $3);$n",
nilCheck, r, genTypeInfo(p.module, dest))
@@ -2018,7 +1976,8 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
of skEnumField:
putIntoDest(p, d, n.typ, rope(sym.position))
of skVar, skForVar, skResult, skLet:
- if sfGlobal in sym.flags: genVarPrototype(p.module, sym)
+ if {sfGlobal, sfThread} * sym.flags != {}:
+ genVarPrototype(p.module, sym)
if sym.loc.r == nil or sym.loc.t == nil:
#echo "FAILED FOR PRCO ", p.prc.name.s
#echo renderTree(p.prc.ast, {renderIds})
@@ -2126,11 +2085,14 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
of nkCaseStmt: genCase(p, n, d)
of nkReturnStmt: genReturnStmt(p, n)
of nkBreakStmt: genBreakStmt(p, n)
- of nkAsgn: genAsgn(p, n, fastAsgn=false)
+ of nkAsgn:
+ if nfPreventCg notin n.flags:
+ genAsgn(p, n, fastAsgn=false)
of nkFastAsgn:
- # transf is overly aggressive with 'nkFastAsgn', so we work around here.
- # See tests/run/tcnstseq3 for an example that would fail otherwise.
- genAsgn(p, n, fastAsgn=p.prc != nil)
+ if nfPreventCg notin n.flags:
+ # transf is overly aggressive with 'nkFastAsgn', so we work around here.
+ # See tests/run/tcnstseq3 for an example that would fail otherwise.
+ genAsgn(p, n, fastAsgn=p.prc != nil)
of nkDiscardStmt:
if n.sons[0].kind != nkEmpty:
genLineDir(p, n)
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index a5ce147c3..10b5641c5 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -21,7 +21,7 @@ proc registerGcRoot(p: BProc, v: PSym) =
# we register a specialized marked proc here; this has the advantage
# that it works out of the box for thread local storage then :-)
let prc = genTraverseProcForGlobal(p.module, v)
- appcg(p.module, p.module.initProc.procSec(cpsStmts),
+ appcg(p.module, p.module.initProc.procSec(cpsInit),
"#nimRegisterGlobalMarker($1);$n", [prc])
proc isAssignedImmediately(n: PNode): bool {.inline.} =
@@ -293,6 +293,8 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
genLineDir(p, n)
let lend = getLabel(p)
for i in countup(0, sonsLen(n) - 1):
+ # bug #4230: avoid false sharing between branches:
+ if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
let it = n.sons[i]
if it.len == 2:
when newScopeForIf: startBlock(p)
@@ -359,6 +361,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
linefmt(p, cpsStmts, "#popCurrentException();$n")
proc genReturnStmt(p: BProc, t: PNode) =
+ if nfPreventCg in t.flags: return
p.beforeRetNeeded = true
genLineDir(p, t)
if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0])
@@ -459,7 +462,6 @@ proc genWhileStmt(p: BProc, t: PNode) =
# significantly worse code
var
a: TLoc
- labl: TLabel
assert(sonsLen(t) == 2)
inc(p.withinLoop)
genLineDir(p, t)
@@ -488,16 +490,20 @@ proc genWhileStmt(p: BProc, t: PNode) =
dec(p.withinLoop)
-proc genBlock(p: BProc, t: PNode, d: var TLoc) =
+proc genBlock(p: BProc, n: PNode, d: var TLoc) =
+ # bug #4505: allocate the temp in the outer scope
+ # so that it can escape the generated {}:
+ if not isEmptyType(n.typ) and d.k == locNone:
+ getTemp(p, n.typ, d)
preserveBreakIdx:
p.breakIdx = startBlock(p)
- if t.sons[0].kind != nkEmpty:
+ if n.sons[0].kind != nkEmpty:
# named block?
- assert(t.sons[0].kind == nkSym)
- var sym = t.sons[0].sym
+ assert(n.sons[0].kind == nkSym)
+ var sym = n.sons[0].sym
sym.loc.k = locOther
sym.position = p.breakIdx+1
- expr(p, t.sons[1], d)
+ expr(p, n.sons[1], d)
endBlock(p)
proc genParForStmt(p: BProc, t: PNode) =
@@ -592,6 +598,8 @@ proc genCaseSecondPass(p: BProc, t: PNode, d: var TLoc,
labId, until: int): TLabel =
var lend = getLabel(p)
for i in 1..until:
+ # bug #4230: avoid false sharing between branches:
+ if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
lineF(p, cpsStmts, "LA$1: ;$n", [rope(labId + i)])
if t.sons[i].kind == nkOfBranch:
var length = sonsLen(t.sons[i])
@@ -727,6 +735,8 @@ proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) =
lineF(p, cpsStmts, "switch ($1) {$n", [rdCharLoc(a)])
var hasDefault = false
for i in splitPoint+1 .. < n.len:
+ # bug #4230: avoid false sharing between branches:
+ if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
var branch = n[i]
if branch.kind == nkOfBranch:
genCaseRange(p, branch)
@@ -757,16 +767,6 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
else:
genOrdinalCase(p, t, d)
-proc hasGeneralExceptSection(t: PNode): bool =
- var length = sonsLen(t)
- var i = 1
- while (i < length) and (t.sons[i].kind == nkExceptBranch):
- var blen = sonsLen(t.sons[i])
- if blen == 1:
- return true
- inc(i)
- result = false
-
proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
# code to generate:
#
@@ -807,6 +807,8 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
var i = 1
var catchAllPresent = false
while (i < length) and (t.sons[i].kind == nkExceptBranch):
+ # bug #4230: avoid false sharing between branches:
+ if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
let blen = sonsLen(t.sons[i])
if i > 1: addf(p.s(cpsStmts), "else ", [])
if blen == 1:
@@ -912,6 +914,8 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
inc p.inExceptBlock
var i = 1
while (i < length) and (t.sons[i].kind == nkExceptBranch):
+ # bug #4230: avoid false sharing between branches:
+ if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
var blen = sonsLen(t.sons[i])
if blen == 1:
# general except section:
@@ -1089,7 +1093,6 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType,
proc asgnFieldDiscriminant(p: BProc, e: PNode) =
var a, tmp: TLoc
var dotExpr = e.sons[0]
- var d: PSym
if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr.sons[0]
initLocExpr(p, e.sons[0], a)
getTemp(p, a.t, tmp)
diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim
index c4d47f148..a8c079b35 100644
--- a/compiler/ccgtrav.nim
+++ b/compiler/ccgtrav.nim
@@ -73,7 +73,9 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) =
lineF(p, cpsStmts, "}$n", [])
of tyObject:
for i in countup(0, sonsLen(typ) - 1):
- genTraverseProc(c, accessor.parentObj(c.p.module), typ.sons[i])
+ var x = typ.sons[i]
+ if x != nil: x = x.skipTypes(skipPtrs)
+ genTraverseProc(c, accessor.parentObj(c.p.module), x)
if typ.n != nil: genTraverseProc(c, accessor, typ.n)
of tyTuple:
let typ = getUniqueType(typ)
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 3d1c2affc..eac734b3d 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -475,11 +475,11 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
hasField = true
elif m.compileToCpp:
appcg(m, result, " : public $1 {$n",
- [getTypeDescAux(m, typ.sons[0], check)])
+ [getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)])
hasField = true
else:
appcg(m, result, " {$n $1 Sup;$n",
- [getTypeDescAux(m, typ.sons[0], check)])
+ [getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)])
hasField = true
else:
addf(result, " {$n", [name])
@@ -546,7 +546,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope =
of tyRef, tyPtr, tyVar:
var star = if t.kind == tyVar and tfVarIsPtr notin typ.flags and
compileToCpp(m): "&" else: "*"
- var et = t.lastSon
+ var et = typ.skipTypes(abstractInst).lastSon
var etB = et.skipTypes(abstractInst)
if etB.kind in {tyArrayConstr, tyArray, tyOpenArray, tyVarargs}:
# this is correct! sets have no proper base type, so we treat
@@ -744,14 +744,6 @@ proc getClosureType(m: BModule, t: PType, kind: TClosureTypeKind): Rope =
"void* ClEnv;$n} $1;$n",
[result, rettype, desc])
-proc getTypeDesc(m: BModule, magic: string): Rope =
- var sym = magicsys.getCompilerProc(magic)
- if sym != nil:
- result = getTypeDesc(m, sym.typ)
- else:
- rawMessage(errSystemNeeds, magic)
- result = nil
-
proc finishTypeDescriptions(m: BModule) =
var i = 0
while i < len(m.typeStack):
@@ -820,7 +812,9 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; name, base: Rope) =
proc genTypeInfoAux(m: BModule, typ, origType: PType, name: Rope) =
var base: Rope
if (sonsLen(typ) > 0) and (typ.sons[0] != nil):
- base = genTypeInfo(m, typ.sons[0])
+ var x = typ.sons[0]
+ if typ.kind == tyObject: x = x.skipTypes(skipPtrs)
+ base = genTypeInfo(m, x)
else:
base = rope("0")
genTypeInfoAuxBase(m, typ, origType, name, base)
@@ -894,10 +888,11 @@ proc genObjectFields(m: BModule, typ: PType, n: PNode, expr: Rope) =
else: internalError(n.info, "genObjectFields(nkRecCase)")
of nkSym:
var field = n.sym
- addf(m.s[cfsTypeInit3], "$1.kind = 1;$n" &
- "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" &
- "$1.name = $5;$n", [expr, getTypeDesc(m, typ),
- field.loc.r, genTypeInfo(m, field.typ), makeCString(field.name.s)])
+ if field.bitsize == 0:
+ addf(m.s[cfsTypeInit3], "$1.kind = 1;$n" &
+ "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" &
+ "$1.name = $5;$n", [expr, getTypeDesc(m, typ),
+ field.loc.r, genTypeInfo(m, field.typ), makeCString(field.name.s)])
else: internalError(n.info, "genObjectFields")
proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope) =
@@ -909,7 +904,7 @@ proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope) =
addf(m.s[cfsTypeInit3], "$1.node = &$2;$n", [name, tmp])
var t = typ.sons[0]
while t != nil:
- t = t.skipTypes(abstractInst)
+ t = t.skipTypes(skipPtrs)
t.flags.incl tfObjHasKids
t = t.sons[0]
@@ -1000,9 +995,6 @@ proc fakeClosureType(owner: PSym): PType =
type
TTypeInfoReason = enum ## for what do we need the type info?
tiNew, ## for 'new'
- tiNewSeq, ## for 'newSeq'
- tiNonVariantAsgn, ## for generic assignment without variants
- tiVariantAsgn ## for generic assignment with variants
include ccgtrav
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 9851ab0e2..d80a68609 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -167,10 +167,6 @@ proc linefmt(p: BProc, s: TCProcSection, frmt: FormatStr,
args: varargs[Rope]) =
add(p.s(s), indentLine(p, ropecg(p.module, frmt, args)))
-proc appLineCg(p: BProc, r: var Rope, frmt: FormatStr,
- args: varargs[Rope]) =
- add(r, indentLine(p, ropecg(p.module, frmt, args)))
-
proc safeLineNm(info: TLineInfo): int =
result = toLinenumber(info)
if result < 0: result = 0 # negative numbers are not allowed in #line
@@ -250,7 +246,7 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
if not p.module.compileToCpp:
while (s.kind == tyObject) and (s.sons[0] != nil):
add(r, ".Sup")
- s = skipTypes(s.sons[0], abstractInst)
+ s = skipTypes(s.sons[0], skipPtrs)
linefmt(p, section, "$1.m_type = $2;$n", r, genTypeInfo(p.module, t))
of frEmbedded:
# worst case for performance:
@@ -259,8 +255,7 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
type
TAssignmentFlag = enum
- needToCopy, needForSubtypeCheck, afDestIsNil, afDestIsNotNil, afSrcIsNil,
- afSrcIsNotNil, needToKeepAlive
+ needToCopy, afDestIsNil, afDestIsNotNil, afSrcIsNil, afSrcIsNotNil
TAssignmentFlags = set[TAssignmentFlag]
proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
@@ -328,8 +323,9 @@ proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) =
proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) =
inc(p.labels)
result.r = "LOC" & rope(p.labels)
- addf(p.blocks[0].sections[cpsLocals],
- "$1 $2;$n", [getTypeDesc(p.module, t), result.r])
+ #addf(p.blocks[0].sections[cpsLocals],
+ # "$1 $2;$n", [getTypeDesc(p.module, t), result.r])
+ linefmt(p, cpsLocals, "$1 $2;$n", getTypeDesc(p.module, t), result.r)
result.k = locTemp
#result.a = - 1
result.t = t
@@ -338,31 +334,6 @@ proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) =
result.flags = {}
constructLoc(p, result, not needsInit)
-proc keepAlive(p: BProc, toKeepAlive: TLoc) =
- when false:
- # deactivated because of the huge slowdown this causes; GC will take care
- # of interior pointers instead
- if optRefcGC notin gGlobalOptions: return
- var result: TLoc
- var fid = rope(p.gcFrameId)
- result.r = "GCFRAME.F" & fid
- addf(p.gcFrameType, " $1 F$2;$n",
- [getTypeDesc(p.module, toKeepAlive.t), fid])
- inc(p.gcFrameId)
- result.k = locTemp
- #result.a = -1
- result.t = toKeepAlive.t
- result.s = OnStack
- result.flags = {}
-
- if not isComplexValueType(skipTypes(toKeepAlive.t, abstractVarRange)):
- linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(result), rdLoc(toKeepAlive))
- else:
- useStringh(p.module)
- linefmt(p, cpsStmts,
- "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
- addrLoc(result), addrLoc(toKeepAlive), rdLoc(result))
-
proc initGCFrame(p: BProc): Rope =
if p.gcFrameId > 0: result = "struct {$1} GCFRAME;$n" % [p.gcFrameType]
@@ -620,9 +591,6 @@ proc generateHeaders(m: BModule) =
addf(m.s[cfsHeaders], "#include $1$N", [rope(it.data)])
it = PStrEntry(it.next)
-proc retIsNotVoid(s: PSym): bool =
- result = (s.typ.sons[0] != nil) and not isInvalidReturnType(s.typ.sons[0])
-
proc initFrame(p: BProc, procname, filename: Rope): Rope =
discard cgsym(p.module, "nimFrame")
if p.maxFrameLen > 0:
@@ -649,6 +617,24 @@ proc closureSetup(p: BProc, prc: PSym) =
linefmt(p, cpsStmts, "$1 = ($2) ClEnv;$n",
rdLoc(env.loc), getTypeDesc(p.module, env.typ))
+proc easyResultAsgn(n: PNode): PNode =
+ const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} +
+ declarativeDefs
+ case n.kind
+ of nkStmtList, nkStmtListExpr:
+ var i = 0
+ while i < n.len and n[i].kind in harmless: inc i
+ if i < n.len: result = easyResultAsgn(n[i])
+ of nkAsgn, nkFastAsgn:
+ if n[0].kind == nkSym and skResult == n[0].sym.kind:
+ incl n.flags, nfPreventCg
+ return n[1]
+ of nkReturnStmt:
+ if n.len > 0:
+ result = easyResultAsgn(n[0])
+ if result != nil: incl n.flags, nfPreventCg
+ else: discard
+
proc genProcAux(m: BModule, prc: PSym) =
var p = newProc(prc, m)
var header = genProcHeader(m, prc)
@@ -660,11 +646,17 @@ proc genProcAux(m: BModule, prc: PSym) =
var res = prc.ast.sons[resultPos].sym # get result symbol
if not isInvalidReturnType(prc.typ.sons[0]):
if sfNoInit in prc.flags: incl(res.flags, sfNoInit)
- # declare the result symbol:
- assignLocalVar(p, res)
- assert(res.loc.r != nil)
+ if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil):
+ var decl = localVarDecl(p, res)
+ var a: TLoc
+ initLocExprSingleUse(p, val, a)
+ linefmt(p, cpsStmts, "$1 = $2;$n", decl, rdLoc(a))
+ else:
+ # declare the result symbol:
+ assignLocalVar(p, res)
+ assert(res.loc.r != nil)
+ initLocalVar(p, res, immediateAsgn=false)
returnStmt = rfmt(nil, "\treturn $1;$n", rdLoc(res.loc))
- initLocalVar(p, res, immediateAsgn=false)
else:
fillResult(res)
assignParam(p, res)
@@ -796,7 +788,7 @@ proc genProc(m: BModule, prc: PSym) =
genProcAux(generatedHeader, prc)
proc genVarPrototypeAux(m: BModule, sym: PSym) =
- assert(sfGlobal in sym.flags)
+ #assert(sfGlobal in sym.flags)
useHeader(m, sym)
fillLoc(sym.loc, locGlobalVar, sym.typ, mangleName(sym), OnHeap)
if (lfNoDecl in sym.loc.flags) or containsOrIncl(m.declaredThings, sym.id):
@@ -824,12 +816,12 @@ proc addIntTypes(result: var Rope) {.inline.} =
proc getCopyright(cfile: string): Rope =
if optCompileOnly in gGlobalOptions:
result = ("/* Generated by Nim Compiler v$1 */$N" &
- "/* (c) 2015 Andreas Rumpf */$N" &
+ "/* (c) 2016 Andreas Rumpf */$N" &
"/* The generated code is subject to the original license. */$N") %
[rope(VersionAsString)]
else:
result = ("/* Generated by Nim Compiler v$1 */$N" &
- "/* (c) 2015 Andreas Rumpf */$N" &
+ "/* (c) 2016 Andreas Rumpf */$N" &
"/* The generated code is subject to the original license. */$N" &
"/* Compiled for: $2, $3, $4 */$N" &
"/* Command for C compiler:$n $5 */$N") %
@@ -875,10 +867,11 @@ proc genMainProc(m: BModule) =
MainProcsWithResult =
MainProcs & "\treturn nim_program_result;$N"
- NimMainBody =
- "N_CDECL(void, NimMainInner)(void) {$N" &
+ NimMainInner = "N_CDECL(void, NimMainInner)(void) {$N" &
"$1" &
- "}$N$N" &
+ "}$N$N"
+
+ NimMainProc =
"N_CDECL(void, NimMain)(void) {$N" &
"\tvoid (*volatile inner)();$N" &
"\tPreMain();$N" &
@@ -887,6 +880,8 @@ proc genMainProc(m: BModule) =
"\t(*inner)();$N" &
"}$N$N"
+ NimMainBody = NimMainInner & NimMainProc
+
PosixNimMain =
"int cmdCount;$N" &
"char** cmdLine;$N" &
@@ -914,7 +909,7 @@ proc genMainProc(m: BModule) =
" LPSTR lpCmdLine, int nCmdShow) {$N" &
MainProcsWithResult & "}$N$N"
- WinNimDllMain = "N_LIB_EXPORT " & NimMainBody
+ WinNimDllMain = NimMainInner & "N_LIB_EXPORT " & NimMainProc
WinCDllMain =
"BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, $N" &
@@ -1020,7 +1015,7 @@ proc genInitCode(m: BModule) =
var procname = makeCString(m.module.name.s)
add(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename))
else:
- add(prc, ~"\tTFrame F; FR.len = 0;$N")
+ add(prc, ~"\tTFrame FR; FR.len = 0;$N")
add(prc, genSectionStart(cpsInit))
add(prc, m.preInitProc.s(cpsInit))
diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim
index 624c5b183..bcf0b535b 100644
--- a/compiler/cgmeth.nim
+++ b/compiler/cgmeth.nim
@@ -145,15 +145,16 @@ proc fixupDispatcher(meth, disp: PSym) =
disp.typ.lockLevel = meth.typ.lockLevel
proc methodDef*(s: PSym, fromCache: bool) =
- var L = len(gMethods)
+ let L = len(gMethods)
var witness: PSym
for i in countup(0, L - 1):
- var disp = gMethods[i].dispatcher
+ let disp = gMethods[i].dispatcher
case sameMethodBucket(disp, s)
of Yes:
add(gMethods[i].methods, s)
attachDispatcher(s, lastSon(disp.ast))
fixupDispatcher(s, disp)
+ #echo "fixup ", disp.name.s, " ", disp.id
when useEffectSystem: checkMethodEffects(disp, s)
if sfBase in s.flags and gMethods[i].methods[0] != s:
# already exists due to forwarding definition?
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index bbbec081a..c220902ff 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -27,6 +27,7 @@ type
seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
jArray: JsonNode
types: TStrTable
+ isPureRst: bool
PDoc* = ref TDocumentor ## Alias to type less.
@@ -440,11 +441,6 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
dispA(result, "$1", "\\spanOther{$1}",
[rope(esc(d.target, literal))])
- if k in routineKinds and nameNode.kind == nkSym:
- let att = attachToType(d, nameNode.sym)
- if att != nil:
- dispA(result, """$1""", "",
- [rope esc(d.target, att.name.s)])
inc(d.id)
let
plainNameRope = rope(xmltree.escape(plainName.strip))
@@ -459,25 +455,35 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
var seeSrcRope: Rope = nil
let docItemSeeSrc = getConfigVar("doc.item.seesrc")
- if docItemSeeSrc.len > 0 and options.docSeeSrcUrl.len > 0:
- let path = n.info.toFilename.extractFilename.rope
- let urlRope = ropeFormatNamedVars(options.docSeeSrcUrl,
- ["path", "line"], [path, rope($n.info.line)])
+ if docItemSeeSrc.len > 0:
+ let cwd = getCurrentDir().canonicalizePath()
+ var path = n.info.toFullPath
+ if path.startsWith(cwd):
+ path = path[cwd.len+1 .. ^1].replace('\\', '/')
+ var commit = getConfigVar("git.commit")
+ if commit.len == 0: commit = "master"
dispA(seeSrcRope, "$1", "", [ropeFormatNamedVars(docItemSeeSrc,
- ["path", "line", "url"], [path,
- rope($n.info.line), urlRope])])
+ ["path", "line", "url", "commit"], [rope path,
+ rope($n.info.line), rope getConfigVar("git.url"),
+ rope commit])])
add(d.section[k], ropeFormatNamedVars(getConfigVar("doc.item"),
["name", "header", "desc", "itemID", "header_plain", "itemSym",
"itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc"],
[nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope,
symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope]))
+
+ var attype: Rope
+ if k in routineKinds and nameNode.kind == nkSym:
+ let att = attachToType(d, nameNode.sym)
+ if att != nil:
+ attype = rope esc(d.target, att.name.s)
add(d.toc[k], ropeFormatNamedVars(getConfigVar("doc.item.toc"),
["name", "header", "desc", "itemID", "header_plain", "itemSym",
- "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc"],
+ "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"],
[rope(getName(d, nameNode, d.splitAfter)), result, comm,
itemIDRope, plainNameRope, plainSymbolRope, symbolOrIdRope,
- plainSymbolEncRope, symbolOrIdEncRope]))
+ plainSymbolEncRope, symbolOrIdEncRope, attype]))
# Ironically for types the complexSymbol is *cleaner* than the plainName
# because it doesn't include object fields or documentation comments. So we
@@ -629,7 +635,9 @@ proc genOutFile(d: PDoc): Rope =
# Modules get an automatic title for the HTML, but no entry in the index.
title = "Module " & extractFilename(changeFileExt(d.filename, ""))
- let bodyname = if d.hasToc: "doc.body_toc" else: "doc.body_no_toc"
+ let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group"
+ elif d.hasToc: "doc.body_toc"
+ else: "doc.body_no_toc"
content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
"tableofcontents", "moduledesc", "date", "time", "content"],
[title.rope, toc, d.modDesc, rope(getDateStr()),
@@ -694,6 +702,7 @@ proc commandDoc*() =
proc commandRstAux(filename, outExt: string) =
var filen = addFileExt(filename, "txt")
var d = newDocumentor(filen, options.gConfigVars)
+ d.isPureRst = true
var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
{roSupportRawDirective})
var modDesc = newStringOfCap(30_000)
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 2dcfc0226..6f8b0b197 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -505,10 +505,6 @@ proc noAbsolutePaths: bool {.inline.} =
# `optGenMapping` is included here for niminst.
result = gGlobalOptions * {optGenScript, optGenMapping} != {}
-const
- specialFileA = 42
- specialFileB = 42
-
var fileCounter: int
proc add(s: var string, many: openArray[string]) =
diff --git a/compiler/idgen.nim b/compiler/idgen.nim
index 333772705..c6b1a4d07 100644
--- a/compiler/idgen.nim
+++ b/compiler/idgen.nim
@@ -54,6 +54,6 @@ proc loadMaxIds*(project: string) =
if f.readLine(line):
var frontEndId = parseInt(line)
if f.readLine(line):
- var backEndId = parseInt(line)
+ # var backEndId = parseInt(line)
gFrontEndId = max(gFrontEndId, frontEndId)
f.close()
diff --git a/compiler/importer.nim b/compiler/importer.nim
index dd2c4d954..87415733b 100644
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -163,7 +163,10 @@ proc myImportModule(c: PContext, n: PNode): PSym =
var f = checkModuleName(n)
if f != InvalidFileIDX:
result = importModuleAs(n, gImportModule(c.module, f))
- if result.info.fileIndex == n.info.fileIndex:
+ # we cannot perform this check reliably because of
+ # test: modules/import_in_config)
+ if result.info.fileIndex == c.module.info.fileIndex and
+ result.info.fileIndex == n.info.fileIndex:
localError(n.info, errGenerated, "A module cannot import itself")
if sfDeprecated in result.flags:
message(n.info, warnDeprecated, result.name.s)
diff --git a/compiler/installer.ini b/compiler/installer.ini
index b802f08f1..acc82002d 100644
--- a/compiler/installer.ini
+++ b/compiler/installer.ini
@@ -50,6 +50,7 @@ Files: "readme.txt;install.txt;contributors.txt;copying.txt"
Files: "makefile"
Files: "koch.nim"
Files: "install_nimble.nims"
+Files: "install_tools.nims"
Files: "icons/nim.ico"
Files: "icons/nim.rc"
@@ -77,7 +78,8 @@ Files: "lib"
[Other]
Files: "examples"
-#Files: "dist/nimble"
+Files: "dist/nimble"
+Files: "dist/nimsuggest"
Files: "tests"
@@ -87,10 +89,11 @@ Files: "bin/c2nim.exe"
Files: "bin/nimgrep.exe"
Files: "bin/nimsuggest.exe"
Files: "bin/nimble.exe"
+Files: "bin/makelink.exe"
Files: "bin/*.dll"
-Files: "dist/*.dll"
Files: "koch.exe"
+Files: "finish.exe"
; Files: "dist/mingw"
Files: "start.bat"
BinPath: r"bin;dist\mingw\bin;dist"
@@ -98,8 +101,8 @@ BinPath: r"bin;dist\mingw\bin;dist"
; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry
Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|overview.html"
Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip"
-Download: r"Support DLL's|bin|nim_dlls.zip|479|http://nim-lang.org/download/dlls.zip"
-Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.4.0.zip|aporia-0.4.0\bin\aporia.exe"
+Download: r"Support DLLs|bin|nim_dlls.zip|479|http://nim-lang.org/download/dlls.zip"
+Download: r"Aporia Text Editor|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.4.0.zip|aporia-0.4.0\bin\aporia.exe"
; for now only NSIS supports optional downloads
[UnixBin]
@@ -116,7 +119,6 @@ path = r"c:\Program Files (x86)\Inno Setup 5\iscc.exe"
flags = "/Q"
[NSIS]
-path = r"c:\Program Files (x86)\NSIS\makensis.exe"
flags = "/V0"
[C_Compiler]
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 80bcd2b0e..e7fe8cc27 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -512,6 +512,10 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
arithAux(p, n, r, op, jsOps)
r.kind = resExpr
+proc hasFrameInfo(p: PProc): bool =
+ ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and
+ ((p.prc == nil) or not (sfPure in p.prc.flags))
+
proc genLineDir(p: PProc, n: PNode) =
let line = toLinenumber(n.info)
if optLineDir in p.options:
@@ -521,9 +525,7 @@ proc genLineDir(p: PProc, n: PNode) =
((p.prc == nil) or sfPure notin p.prc.flags):
useMagic(p, "endb")
addf(p.body, "endb($1);$n", [rope(line)])
- elif ({optLineTrace, optStackTrace} * p.options ==
- {optLineTrace, optStackTrace}) and
- ((p.prc == nil) or not (sfPure in p.prc.flags)):
+ elif hasFrameInfo(p):
addf(p.body, "F.line = $1;$n" | "$$F['line'] = $1;$n", [rope(line)])
proc genWhileStmt(p: PProc, n: PNode) =
@@ -558,10 +560,13 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
# code to generate:
#
# ++excHandler;
+ # var tmpFramePtr = framePtr;
# try {
# stmts;
+ # --excHandler;
# } catch (EXC) {
# var prevJSError = lastJSError; lastJSError = EXC;
+ # framePtr = tmpFramePtr;
# --excHandler;
# if (e.typ && e.typ == NTI433 || e.typ == NTI2321) {
# stmts;
@@ -572,6 +577,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
# }
# lastJSError = prevJSError;
# } finally {
+ # framePtr = tmpFramePtr;
# stmts;
# }
genLineDir(p, n)
@@ -584,8 +590,10 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
var catchBranchesExist = length > 1 and n.sons[i].kind == nkExceptBranch
if catchBranchesExist and p.target == targetJS:
add(p.body, "++excHandler;" & tnl)
- var safePoint = "Tmp$1" % [rope(p.unique)]
- if optStackTrace in p.options: add(p.body, "framePtr = F;" & tnl)
+ var tmpFramePtr = rope"F"
+ if optStackTrace notin p.options:
+ tmpFramePtr = p.getTemp(true)
+ add(p.body, tmpFramePtr & " = framePtr;" & tnl)
addf(p.body, "try {$n", [])
if p.target == targetPHP and p.globals == nil:
p.globals = "global $lastJSError; global $prevJSError;".rope
@@ -595,8 +603,9 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
var generalCatchBranchExists = false
let dollar = rope(if p.target == targetJS: "" else: "$")
if p.target == targetJS and catchBranchesExist:
- addf(p.body, "} catch (EXC) {$n var prevJSError = lastJSError;$n" &
+ addf(p.body, "--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" &
" lastJSError = EXC;$n --excHandler;$n", [])
+ add(p.body, "framePtr = $1;$n" % [tmpFramePtr])
elif p.target == targetPHP:
addf(p.body, "} catch (Exception $$EXC) {$n $$prevJSError = $$lastJSError;$n $$lastJSError = $$EXC;$n", [])
while i < length and n.sons[i].kind == nkExceptBranch:
@@ -618,8 +627,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
addf(orExpr, "isObj($2lastJSError.m_type, $1)",
[genTypeInfo(p, n.sons[i].sons[j].typ), dollar])
if i > 1: add(p.body, "else ")
- addf(p.body, "if ($3lastJSError && ($2)) {$n",
- [safePoint, orExpr, dollar])
+ addf(p.body, "if ($1lastJSError && ($2)) {$n", [dollar, orExpr])
gen(p, n.sons[i].sons[blen - 1], a)
moveInto(p, a, r)
addf(p.body, "}$n", [])
@@ -631,6 +639,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
addf(p.body, "$1lastJSError = $1prevJSError;$n", [dollar])
if p.target == targetJS:
add(p.body, "} finally {" & tnl)
+ add(p.body, "framePtr = $1;$n" % [tmpFramePtr])
if p.target == targetPHP:
# XXX ugly hack for PHP codegen
add(p.body, "}" & tnl)
@@ -1355,7 +1364,7 @@ proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: v
if output.len > 0: output.add(", ")
addf(output, "m_type: $1" | "'m_type' => $#", [genTypeInfo(p, t)])
while t != nil:
- createRecordVarAux(p, t.n, excludedFieldIDs, output)
+ createRecordVarAux(p, t.skipTypes(skipPtrs).n, excludedFieldIDs, output)
t = t.sons[0]
proc arrayTypeForElemType(typ: PType): string =
@@ -1462,13 +1471,21 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
useMagic(p, "nimCopy")
s = "nimCopy(null, $1, $2)" % [a.res, genTypeInfo(p, n.typ)]
of etyBaseIndex:
- if (a.typ != etyBaseIndex): internalError(n.info, "genVarInit")
- if {sfAddrTaken, sfGlobal} * v.flags != {}:
- addf(p.body, "var $1 = [$2, $3];$n",
- [v.loc.r, a.address, a.res])
+ let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {}
+ if a.typ == etyBaseIndex:
+ if targetBaseIndex:
+ addf(p.body, "var $1 = $2, $1_Idx = $3;$n", [
+ v.loc.r, a.address, a.res])
+ else:
+ addf(p.body, "var $1 = [$2, $3];$n",
+ [v.loc.r, a.address, a.res])
else:
- addf(p.body, "var $1 = $2; var $1_Idx = $3;$n", [
- v.loc.r, a.address, a.res])
+ if targetBaseIndex:
+ let tmp = p.getTemp
+ addf(p.body, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n",
+ [tmp, a.res, v.loc.r])
+ else:
+ addf(p.body, "var $1 = $2;$n", [v.loc.r, a.res])
return
else:
s = a.res
@@ -1918,10 +1935,10 @@ proc frameCreate(p: PProc; procname, filename: Rope): Rope =
procname, filename]
proc frameDestroy(p: PProc): Rope =
- result = rope(("framePtr = framePtr.prev;" | "$framePtr = $framePtr['prev'];") & tnl)
+ result = rope(("framePtr = F.prev;" | "$framePtr = $F['prev'];") & tnl)
proc genProcBody(p: PProc, prc: PSym): Rope =
- if optStackTrace in prc.options:
+ if hasFrameInfo(p):
result = frameCreate(p,
makeJSString(prc.owner.name.s & '.' & prc.name.s),
makeJSString(toFilename(prc.info)))
@@ -1935,7 +1952,7 @@ proc genProcBody(p: PProc, prc: PSym): Rope =
if prc.typ.callConv == ccSysCall and p.target == targetJS:
result = ("try {$n$1} catch (e) {$n" &
" alert(\"Unhandled exception:\\n\" + e.message + \"\\n\"$n}") % [result]
- if optStackTrace in prc.options:
+ if hasFrameInfo(p):
add(result, frameDestroy(p))
proc genProc(oldProc: PProc, prc: PSym): Rope =
diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim
index cf1679ee4..0aaf93579 100644
--- a/compiler/jstypes.nim
+++ b/compiler/jstypes.nim
@@ -76,7 +76,7 @@ proc genObjectInfo(p: PProc, typ: PType, name: Rope) =
addf(p.g.typeInfo, "$1.node = NNI$2;$n", [name, rope(typ.id)])
if (typ.kind == tyObject) and (typ.sons[0] != nil):
addf(p.g.typeInfo, "$1.base = $2;$n",
- [name, genTypeInfo(p, typ.sons[0])])
+ [name, genTypeInfo(p, typ.sons[0].skipTypes(skipPtrs))])
proc genTupleFields(p: PProc, typ: PType): Rope =
var s: Rope = nil
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index 753602c80..36ad2e0a6 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -226,8 +226,14 @@ proc interestingIterVar(s: PSym): bool {.inline.} =
template isIterator*(owner: PSym): bool =
owner.kind == skIterator and owner.typ.callConv == ccClosure
+proc liftingHarmful(owner: PSym): bool {.inline.} =
+ ## lambda lifting can be harmful for JS-like code generators.
+ let isCompileTime = sfCompileTime in owner.flags or owner.kind == skMacro
+ result = gCmd in {cmdCompileToPHP, cmdCompileToJS} and not isCompileTime
+
proc liftIterSym*(n: PNode; owner: PSym): PNode =
# transforms (iter) to (let env = newClosure[iter](); (iter, env))
+ if liftingHarmful(owner): return n
let iter = n.sym
assert iter.isIterator
@@ -838,6 +844,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
nkBreakState(cl.state)
...
"""
+ if liftingHarmful(owner): return body
var L = body.len
if not (body.kind == nkForStmt and body[L-2].kind in nkCallKinds):
localError(body.info, "ignored invalid for loop")
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 0eee5004e..9c513034b 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -211,9 +211,6 @@ proc closeLexer*(lex: var TLexer) =
inc(gLinesCompiled, lex.lineNumber)
closeBaseLexer(lex)
-proc getColumn(L: TLexer): int =
- result = getColNumber(L, L.bufpos)
-
proc getLineInfo(L: TLexer): TLineInfo =
result = newLineInfo(L.fileIdx, L.lineNumber, getColNumber(L, L.bufpos))
@@ -237,12 +234,6 @@ proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") =
proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool =
result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
-proc isFloatLiteral(s: string): bool =
- for i in countup(0, len(s) - 1):
- if s[i] in {'.', 'e', 'E'}:
- return true
- result = false
-
{.push overflowChecks: off.}
# We need to parse the largest uint literal without overflow checks
proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int =
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index 36eb24653..9db4383f6 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -172,7 +172,7 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode =
if field != nil: break
t = t.sons[0]
if t == nil: break
- t = t.skipTypes(abstractInst)
+ t = t.skipTypes(skipPtrs)
#if field == nil:
# echo "FIELD ", b
# debug deref.typ
@@ -193,7 +193,7 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
if result != nil: break
t = t.sons[0]
if t == nil: break
- t = t.skipTypes(abstractInst)
+ t = t.skipTypes(skipPtrs)
proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node
diff --git a/compiler/modules.nim b/compiler/modules.nim
index 9120bd1b6..711fb6aa4 100644
--- a/compiler/modules.nim
+++ b/compiler/modules.nim
@@ -38,9 +38,6 @@ proc getModule*(fileIdx: int32): PSym =
if fileIdx >= 0 and fileIdx < gCompiledModules.len:
result = gCompiledModules[fileIdx]
-template hash(x: PSym): untyped =
- gMemCacheData[x.position].hash
-
proc hashChanged(fileIdx: int32): bool =
internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len
@@ -189,14 +186,11 @@ proc compileModule*(fileIdx: int32, flags: TSymFlags): PSym =
return
else:
result.id = getID()
- if sfMainModule in flags and gProjectIsStdin:
- processModule(result, llStreamOpen(stdin), rd)
- else:
- processModule(result, nil, rd)
+ let validFile = processModule(result, if sfMainModule in flags and gProjectIsStdin: llStreamOpen(stdin) else: nil, rd)
if optCaasEnabled in gGlobalOptions:
gMemCacheData[fileIdx].compiledAt = gLastCmdTime
gMemCacheData[fileIdx].needsRecompile = Recompiled
- doHash fileIdx
+ if validFile: doHash fileIdx
else:
if checkDepMem(fileIdx) == Yes:
result = compileModule(fileIdx, flags)
@@ -220,12 +214,6 @@ proc includeModule*(s: PSym, fileIdx: int32): PNode {.procvar.} =
addDep(s, fileIdx)
doHash(fileIdx)
-proc `==^`(a, b: string): bool =
- try:
- result = sameFile(a, b)
- except OSError:
- result = false
-
proc compileSystemModule* =
if magicsys.systemModule == nil:
systemFileIdx = fileInfoIdx(options.libpath/"system.nim")
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 4a9980066..fd0aafccb 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -65,7 +65,7 @@ type
errPureTypeMismatch, errTypeMismatch, errButExpected, errButExpectedX,
errAmbiguousCallXYZ, errWrongNumberOfArguments,
errXCannotBePassedToProcVar,
- errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProc, errImplOfXNotAllowed,
+ errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProcX, errImplOfXNotAllowed,
errImplOfXexpected, errNoSymbolToBorrowFromFound, errDiscardValueX,
errInvalidDiscard, errIllegalConvFromXtoY, errCannotBindXTwice,
errInvalidOrderInArrayConstructor,
@@ -274,7 +274,7 @@ const
errWrongNumberOfArguments: "wrong number of arguments",
errXCannotBePassedToProcVar: "\'$1\' cannot be passed to a procvar",
errXCannotBeInParamDecl: "$1 cannot be declared in parameter declaration",
- errPragmaOnlyInHeaderOfProc: "pragmas are only allowed in the header of a proc",
+ errPragmaOnlyInHeaderOfProcX: "pragmas are only allowed in the header of a proc; redefinition of $1",
errImplOfXNotAllowed: "implementation of \'$1\' is not allowed",
errImplOfXexpected: "implementation of \'$1\' expected",
errNoSymbolToBorrowFromFound: "no symbol to borrow from found",
@@ -521,7 +521,7 @@ const
hintCodeBegin, hintCodeEnd,
hintSource, hintStackTrace,
hintGCStats},
- {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace},
+ {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace, warnUninit},
{low(TNoteKind)..high(TNoteKind)}]
const
@@ -660,8 +660,6 @@ const
WarningColor = fgYellow
HintTitle = "Hint: "
HintColor = fgGreen
- InfoTitle = "Info: "
- InfoColor = fgCyan
proc getInfoContextLen*(): int = return msgContext.len
proc setInfoContextLen*(L: int) = setLen(msgContext, L)
diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim
index c6c2ab058..9c5c17287 100644
--- a/compiler/nimblecmd.nim
+++ b/compiler/nimblecmd.nim
@@ -63,18 +63,6 @@ proc addNimblePath(p: string, info: TLineInfo) =
message(info, hintPath, p)
lists.prependStr(options.lazyPaths, p)
-proc addPathWithNimFiles(p: string, info: TLineInfo) =
- proc hasNimFile(dir: string): bool =
- for kind, path in walkDir(dir):
- if kind == pcFile and path.endsWith(".nim"):
- result = true
- break
- if hasNimFile(p):
- addNimblePath(p, info)
- else:
- for kind, p2 in walkDir(p):
- if hasNimFile(p2): addNimblePath(p2, info)
-
proc addPathRec(dir: string, info: TLineInfo) =
var packages = newStringTable(modeStyleInsensitive)
var pos = dir.len-1
diff --git a/compiler/nversion.nim b/compiler/nversion.nim
index adeb0fb6d..d69e1e553 100644
--- a/compiler/nversion.nim
+++ b/compiler/nversion.nim
@@ -13,5 +13,5 @@
const
MaxSetElements* = 1 shl 16 # (2^16) to support unicode character sets?
VersionAsString* = system.NimVersion
- RodFileVersion* = "1215" # modify this if the rod-format changes!
+ RodFileVersion* = "1221" # modify this if the rod-format changes!
diff --git a/compiler/options.nim b/compiler/options.nim
index a5154cb48..7cf707945 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -70,6 +70,12 @@ type # please make sure we have under 32 options
optExcessiveStackTrace # fully qualified module filenames
TGlobalOptions* = set[TGlobalOption]
+
+const
+ harmlessOptions* = {optForceFullMake, optNoLinking, optReportConceptFailures,
+ optRun, optUseColors, optStdout}
+
+type
TCommands* = enum # Nim's commands
# **keep binary compatible**
cmdNone, cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 19ef0960a..40862eb63 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -340,26 +340,6 @@ proc parseSymbol(p: var TParser, allowNil = false): PNode =
if not isKeyword(p.tok.tokType): getTok(p)
result = ast.emptyNode
-proc indexExpr(p: var TParser): PNode =
- #| indexExpr = expr
- result = parseExpr(p)
-
-proc indexExprList(p: var TParser, first: PNode, k: TNodeKind,
- endToken: TTokType): PNode =
- #| indexExprList = indexExpr ^+ comma
- result = newNodeP(k, p)
- addSon(result, first)
- getTok(p)
- optInd(p, result)
- while p.tok.tokType notin {endToken, tkEof}:
- var a = indexExpr(p)
- addSon(result, a)
- if p.tok.tokType != tkComma: break
- getTok(p)
- skipComment(p, a)
- optPar(p)
- eat(p, endToken)
-
proc colonOrEquals(p: var TParser, a: PNode): PNode =
if p.tok.tokType == tkColon:
result = newNodeP(nkExprColonExpr, p)
diff --git a/compiler/passes.nim b/compiler/passes.nim
index ceb3e2b8a..b7642e3e4 100644
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -46,12 +46,6 @@ proc makePass*(open: TPassOpen = nil,
result.close = close
result.process = process
- # This implements a memory preserving scheme: Top level statements are
- # processed in a pipeline. The compiler never looks at a whole module
- # any longer. However, this is simple to change, as new passes may perform
- # whole program optimizations. For now, we avoid it to save a lot of memory.
-proc processModule*(module: PSym, stream: PLLStream, rd: PRodReader)
-
# the semantic checker needs these:
var
gImportModule*: proc (m: PSym, fileIdx: int32): PSym {.nimcall.}
@@ -160,7 +154,8 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
importStmt.addSon str
if not processTopLevelStmt(importStmt, a): break
-proc processModule(module: PSym, stream: PLLStream, rd: PRodReader) =
+proc processModule*(module: PSym, stream: PLLStream,
+ rd: PRodReader): bool {.discardable.} =
var
p: TParsers
a: TPassContextArray
@@ -173,7 +168,7 @@ proc processModule(module: PSym, stream: PLLStream, rd: PRodReader) =
s = llStreamOpen(filename, fmRead)
if s == nil:
rawMessage(errCannotOpenFile, filename)
- return
+ return false
else:
s = stream
while true:
@@ -211,4 +206,4 @@ proc processModule(module: PSym, stream: PLLStream, rd: PRodReader) =
var n = loadInitSection(rd)
for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a)
closePassesCached(a)
-
+ result = true
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index db5f8e727..f4109b26d 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -567,11 +567,13 @@ proc deprecatedStmt(c: PContext; pragma: PNode) =
for n in pragma:
if n.kind in {nkExprColonExpr, nkExprEqExpr}:
let dest = qualifiedLookUp(c, n[1], {checkUndeclared})
+ assert dest != nil
let src = considerQuotedIdent(n[0])
let alias = newSym(skAlias, src, dest, n[0].info)
incl(alias.flags, sfExported)
if sfCompilerProc in dest.flags: markCompilerProc(alias)
addInterfaceDecl(c, alias)
+ n.sons[1] = newSymNode(dest)
else:
localError(n.info, "key:value pair expected")
@@ -749,7 +751,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
noVal(it)
incl(sym.flags, sfThread)
incl(sym.flags, sfProcvar)
- if sym.typ != nil: incl(sym.typ.flags, tfThread)
+ if sym.typ != nil:
+ incl(sym.typ.flags, tfThread)
+ if sym.typ.callConv == ccClosure: sym.typ.callConv = ccDefault
of wGcSafe:
noVal(it)
if sym.kind != skType: incl(sym.flags, sfThread)
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index 0e733d643..a116a8afe 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -146,12 +146,6 @@ proc put(g: var TSrcGen, kind: TTokType, s: string) =
else:
g.pendingWhitespace = s.len
-proc putLong(g: var TSrcGen, kind: TTokType, s: string, lineLen: int) =
- # use this for tokens over multiple lines.
- addPendingNL(g)
- addTok(g, kind, s)
- g.lineLen = lineLen
-
proc toNimChar(c: char): string =
case c
of '\0': result = "\\0"
@@ -264,9 +258,6 @@ proc pushCom(g: var TSrcGen, n: PNode) =
proc popAllComs(g: var TSrcGen) =
setLen(g.comStack, 0)
-proc popCom(g: var TSrcGen) =
- setLen(g.comStack, len(g.comStack) - 1)
-
const
Space = " "
@@ -492,7 +483,7 @@ proc fits(g: TSrcGen, x: int): bool =
type
TSubFlag = enum
- rfLongMode, rfNoIndent, rfInConstExpr
+ rfLongMode, rfInConstExpr
TSubFlags = set[TSubFlag]
TContext = tuple[spacing: int, flags: TSubFlags]
@@ -675,16 +666,6 @@ proc gfor(g: var TSrcGen, n: PNode) =
gcoms(g)
gstmts(g, n.sons[length - 1], c)
-proc gmacro(g: var TSrcGen, n: PNode) =
- var c: TContext
- initContext(c)
- gsub(g, n.sons[0])
- putWithSpace(g, tkColon, ":")
- if longMode(n) or (lsub(n.sons[1]) + g.lineLen > MaxLineLen):
- incl(c.flags, rfLongMode)
- gcoms(g)
- gsons(g, n, c, 1)
-
proc gcase(g: var TSrcGen, n: PNode) =
var c: TContext
initContext(c)
diff --git a/compiler/rodread.nim b/compiler/rodread.nim
index 004b30b41..679e7ba15 100644
--- a/compiler/rodread.nim
+++ b/compiler/rodread.nim
@@ -145,13 +145,7 @@ type
PRodReader* = ref TRodReader
-var rodCompilerprocs*: TStrTable
-
-proc handleSymbolFile*(module: PSym): PRodReader
-# global because this is needed by magicsys
-proc loadInitSection*(r: PRodReader): PNode
-
-# implementation
+var rodCompilerprocs*: TStrTable # global because this is needed by magicsys
proc rawLoadStub(s: PSym)
@@ -206,7 +200,7 @@ proc decodeNodeLazyBody(r: PRodReader, fInfo: TLineInfo,
var id = decodeVInt(r.s, r.pos)
result.typ = rrGetType(r, id, result.info)
case result.kind
- of nkCharLit..nkInt64Lit:
+ of nkCharLit..nkUInt64Lit:
if r.s[r.pos] == '!':
inc(r.pos)
result.intVal = decodeVBiggestInt(r.s, r.pos)
@@ -324,6 +318,29 @@ proc decodeType(r: PRodReader, info: TLineInfo): PType =
result.align = decodeVInt(r.s, r.pos).int16
else:
result.align = 2
+
+ if r.s[r.pos] == '\14':
+ inc(r.pos)
+ result.lockLevel = decodeVInt(r.s, r.pos).TLockLevel
+ else:
+ result.lockLevel = UnspecifiedLockLevel
+
+ if r.s[r.pos] == '\15':
+ inc(r.pos)
+ result.destructor = rrGetSym(r, decodeVInt(r.s, r.pos), info)
+ if r.s[r.pos] == '\16':
+ inc(r.pos)
+ result.deepCopy = rrGetSym(r, decodeVInt(r.s, r.pos), info)
+ if r.s[r.pos] == '\17':
+ inc(r.pos)
+ result.assignment = rrGetSym(r, decodeVInt(r.s, r.pos), info)
+ while r.s[r.pos] == '\18':
+ inc(r.pos)
+ let x = decodeVInt(r.s, r.pos)
+ doAssert r.s[r.pos] == '\19'
+ inc(r.pos)
+ let y = rrGetSym(r, decodeVInt(r.s, r.pos), info)
+ result.methods.safeAdd((x, y))
decodeLoc(r, result.loc, info)
while r.s[r.pos] == '^':
inc(r.pos)
@@ -349,6 +366,22 @@ proc decodeLib(r: PRodReader, info: TLineInfo): PLib =
inc(r.pos)
result.path = decodeNode(r, info)
+proc decodeInstantiations(r: PRodReader; info: TLineInfo;
+ s: var seq[PInstantiation]) =
+ while r.s[r.pos] == '\15':
+ inc(r.pos)
+ var ii: PInstantiation
+ new ii
+ ii.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info)
+ ii.concreteTypes = @[]
+ while r.s[r.pos] == '\17':
+ inc(r.pos)
+ ii.concreteTypes.add rrGetType(r, decodeVInt(r.s, r.pos), info)
+ if r.s[r.pos] == '\20':
+ inc(r.pos)
+ ii.compilesId = decodeVInt(r.s, r.pos)
+ s.safeAdd ii
+
proc decodeSym(r: PRodReader, info: TLineInfo): PSym =
var
id: int
@@ -423,6 +456,27 @@ proc decodeSym(r: PRodReader, info: TLineInfo): PSym =
if r.s[r.pos] == '#':
inc(r.pos)
result.constraint = decodeNode(r, unknownLineInfo())
+ case result.kind
+ of skType, skGenericParam:
+ while r.s[r.pos] == '\14':
+ inc(r.pos)
+ result.typeInstCache.safeAdd rrGetType(r, decodeVInt(r.s, r.pos), result.info)
+ of routineKinds:
+ decodeInstantiations(r, result.info, result.procInstCache)
+ if r.s[r.pos] == '\16':
+ inc(r.pos)
+ result.gcUnsafetyReason = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
+ of skModule, skPackage:
+ decodeInstantiations(r, result.info, result.usedGenerics)
+ of skLet, skVar, skField, skForVar:
+ if r.s[r.pos] == '\18':
+ inc(r.pos)
+ result.guard = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
+ if r.s[r.pos] == '\19':
+ inc(r.pos)
+ result.bitsize = decodeVInt(r.s, r.pos).int16
+ else: discard
+
if r.s[r.pos] == '(':
if result.kind in routineKinds:
result.ast = decodeNodeLazyBody(r, result.info, result)
@@ -566,7 +620,8 @@ proc processRodFile(r: PRodReader, hash: SecureHash) =
of "GOPTIONS":
inc(r.pos) # skip ':'
var dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos)))
- if gGlobalOptions != dep: r.reason = rrOptions
+ if gGlobalOptions-harmlessOptions != dep-harmlessOptions:
+ r.reason = rrOptions
of "CMD":
inc(r.pos) # skip ':'
var dep = cast[TCommands](int32(decodeVInt(r.s, r.pos)))
@@ -580,14 +635,14 @@ proc processRodFile(r: PRodReader, hash: SecureHash) =
if not condsyms.isDefined(getIdent(w)):
r.reason = rrDefines #MessageOut('not defined, but should: ' + w);
if r.s[r.pos] == ' ': inc(r.pos)
- if (d != countDefinedSymbols()): r.reason = rrDefines
+ if d != countDefinedSymbols(): r.reason = rrDefines
of "FILES":
inc(r.pos, 2) # skip "(\10"
inc(r.line)
while r.s[r.pos] != ')':
- let relativePath = decodeStr(r.s, r.pos)
- let resolvedPath = relativePath.findModule(r.origFile)
- let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath
+ let finalPath = decodeStr(r.s, r.pos)
+ #let resolvedPath = relativePath.findModule(r.origFile)
+ #let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath
r.files.add(finalPath.fileInfoIdx)
inc(r.pos) # skip #10
inc(r.line)
@@ -683,8 +738,11 @@ proc newRodReader(modfilename: string, hash: SecureHash,
if version != RodFileVersion:
# since ROD files are only for caching, no backwards compatibility is
# needed
+ #echo "expected version ", version, " ", RodFileVersion
+ result.memfile.close
result = nil
else:
+ result.memfile.close
result = nil
proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType =
@@ -762,7 +820,7 @@ proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym =
result = decodeSymSafePos(r, d, info)
if result != nil and result.kind == skStub: rawLoadStub(result)
-proc loadInitSection(r: PRodReader): PNode =
+proc loadInitSection*(r: PRodReader): PNode =
if r.initIdx == 0 or r.dataIdx == 0: internalError("loadInitSection")
var oldPos = r.pos
r.pos = r.initIdx
@@ -818,9 +876,8 @@ proc checkDep(fileIdx: int32): TReasonForRecompile =
var hash = getHash(fileIdx)
gMods[fileIdx].reason = rrNone # we need to set it here to avoid cycles
result = rrNone
- var r: PRodReader = nil
var rodfile = toGeneratedFile(filename.withPackageName, RodExt)
- r = newRodReader(rodfile, hash, fileIdx)
+ var r = newRodReader(rodfile, hash, fileIdx)
if r == nil:
result = (if existsFile(rodfile): rrRodInvalid else: rrRodDoesNotExist)
else:
@@ -847,7 +904,7 @@ proc checkDep(fileIdx: int32): TReasonForRecompile =
gMods[fileIdx].rd = r
gMods[fileIdx].reason = result # now we know better
-proc handleSymbolFile(module: PSym): PRodReader =
+proc handleSymbolFile*(module: PSym): PRodReader =
let fileIdx = module.fileIdx
if optSymbolFiles notin gGlobalOptions:
module.id = getID()
@@ -923,7 +980,7 @@ proc writeNode(f: File; n: PNode) =
f.write('^')
f.write(n.typ.id)
case n.kind
- of nkCharLit..nkInt64Lit:
+ of nkCharLit..nkUInt64Lit:
if n.intVal != 0:
f.write('!')
f.write(n.intVal)
diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim
index d48a9ba40..addbdade6 100644
--- a/compiler/rodwrite.nim
+++ b/compiler/rodwrite.nim
@@ -16,8 +16,6 @@ import
condsyms, ropes, idents, securehash, rodread, passes, importer, idgen,
rodutils
-# implementation
-
type
TRodWriter = object of TPassContext
module: PSym
@@ -39,13 +37,6 @@ type
PRodWriter = ref TRodWriter
-proc newRodWriter(hash: SecureHash, module: PSym): PRodWriter
-proc addModDep(w: PRodWriter, dep: string)
-proc addInclDep(w: PRodWriter, dep: string)
-proc addInterfaceSym(w: PRodWriter, s: PSym)
-proc addStmt(w: PRodWriter, n: PNode)
-proc writeRod(w: PRodWriter)
-
proc getDefines(): string =
result = ""
for d in definedSymbolNames():
@@ -83,19 +74,20 @@ proc newRodWriter(hash: SecureHash, module: PSym): PRodWriter =
result.converters = ""
result.methods = ""
result.init = ""
- result.origFile = module.info.toFilename
+ result.origFile = module.info.toFullPath
result.data = newStringOfCap(12_000)
-proc addModDep(w: PRodWriter, dep: string) =
+proc addModDep(w: PRodWriter, dep: string; info: TLineInfo) =
if w.modDeps.len != 0: add(w.modDeps, ' ')
- encodeVInt(fileIdx(w, dep), w.modDeps)
+ let resolved = dep.findModule(info.toFullPath)
+ encodeVInt(fileIdx(w, resolved), w.modDeps)
const
rodNL = "\x0A"
-proc addInclDep(w: PRodWriter, dep: string) =
- var resolved = dep.findModule(w.module.info.toFullPath)
- encodeVInt(fileIdx(w, dep), w.inclDeps)
+proc addInclDep(w: PRodWriter, dep: string; info: TLineInfo) =
+ let resolved = dep.findModule(info.toFullPath)
+ encodeVInt(fileIdx(w, resolved), w.inclDeps)
add(w.inclDeps, " ")
encodeStr($secureHashFile(resolved), w.inclDeps)
add(w.inclDeps, rodNL)
@@ -108,6 +100,10 @@ proc pushType(w: PRodWriter, t: PType) =
proc pushSym(w: PRodWriter, s: PSym) =
# check so that the stack does not grow too large:
if iiTableGet(w.index.tab, s.id) == InvalidKey:
+ when false:
+ if s.kind == skMethod:
+ echo "encoding ", s.id, " ", s.name.s
+ writeStackTrace()
w.sstack.add(s)
proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
@@ -127,7 +123,7 @@ proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
result.add(',')
encodeVInt(n.info.line, result)
result.add(',')
- encodeVInt(fileIdx(w, toFilename(n.info)), result)
+ encodeVInt(fileIdx(w, toFullPath(n.info)), result)
elif fInfo.line != n.info.line:
result.add('?')
encodeVInt(n.info.col, result)
@@ -147,7 +143,7 @@ proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
encodeVInt(n.typ.id, result)
pushType(w, n.typ)
case n.kind
- of nkCharLit..nkInt64Lit:
+ of nkCharLit..nkUInt64Lit:
if n.intVal != 0:
result.add('!')
encodeVBiggestInt(n.intVal, result)
@@ -229,6 +225,27 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) =
if t.align != 2:
add(result, '=')
encodeVInt(t.align, result)
+ if t.lockLevel.ord != UnspecifiedLockLevel.ord:
+ add(result, '\14')
+ encodeVInt(t.lockLevel.int16, result)
+ if t.destructor != nil and t.destructor.id != 0:
+ add(result, '\15')
+ encodeVInt(t.destructor.id, result)
+ pushSym(w, t.destructor)
+ if t.deepCopy != nil:
+ add(result, '\16')
+ encodeVInt(t.deepcopy.id, result)
+ pushSym(w, t.deepcopy)
+ if t.assignment != nil:
+ add(result, '\17')
+ encodeVInt(t.assignment.id, result)
+ pushSym(w, t.assignment)
+ for i, s in items(t.methods):
+ add(result, '\18')
+ encodeVInt(i, result)
+ add(result, '\19')
+ encodeVInt(s.id, result)
+ pushSym(w, s)
encodeLoc(w, t.loc, result)
for i in countup(0, sonsLen(t) - 1):
if t.sons[i] == nil:
@@ -246,6 +263,19 @@ proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) =
add(result, '|')
encodeNode(w, info, lib.path, result)
+proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation];
+ result: var string) =
+ for t in s:
+ result.add('\15')
+ encodeVInt(t.sym.id, result)
+ pushSym(w, t.sym)
+ for tt in t.concreteTypes:
+ result.add('\17')
+ encodeVInt(tt.id, result)
+ pushType(w, tt)
+ result.add('\20')
+ encodeVInt(t.compilesId, result)
+
proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
if s == nil:
# nil nodes have to be stored too:
@@ -266,7 +296,7 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
result.add(',')
if s.info.line != -1'i16: encodeVInt(s.info.line, result)
result.add(',')
- encodeVInt(fileIdx(w, toFilename(s.info)), result)
+ encodeVInt(fileIdx(w, toFullPath(s.info)), result)
if s.owner != nil:
result.add('*')
encodeVInt(s.owner.id, result)
@@ -291,6 +321,31 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
if s.constraint != nil:
add(result, '#')
encodeNode(w, unknownLineInfo(), s.constraint, result)
+ case s.kind
+ of skType, skGenericParam:
+ for t in s.typeInstCache:
+ result.add('\14')
+ encodeVInt(t.id, result)
+ pushType(w, t)
+ of routineKinds:
+ encodeInstantiations(w, s.procInstCache, result)
+ if s.gcUnsafetyReason != nil:
+ result.add('\16')
+ encodeVInt(s.gcUnsafetyReason.id, result)
+ pushSym(w, s.gcUnsafetyReason)
+ of skModule, skPackage:
+ encodeInstantiations(w, s.usedGenerics, result)
+ # we don't serialize:
+ #tab*: TStrTable # interface table for modules
+ of skLet, skVar, skField, skForVar:
+ if s.guard != nil:
+ result.add('\18')
+ encodeVInt(s.guard.id, result)
+ pushSym(w, s.guard)
+ if s.bitsize != 0:
+ result.add('\19')
+ encodeVInt(s.bitsize, result)
+ else: discard
# lazy loading will soon reload the ast lazily, so the ast needs to be
# the last entry of a symbol:
if s.ast != nil:
@@ -298,16 +353,6 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
# it is not necessary, but Nim's heavy compile-time evaluation features
# make that unfeasible nowadays:
encodeNode(w, s.info, s.ast, result)
- when false:
- var codeAst: PNode = nil
- if not astNeeded(s):
- codeAst = s.ast.sons[codePos]
- # ugly hack to not store the AST:
- s.ast.sons[codePos] = ast.emptyNode
- encodeNode(w, s.info, s.ast, result)
- if codeAst != nil:
- # resore the AST:
- s.ast.sons[codePos] = codeAst
proc addToIndex(w: var TIndex, key, val: int) =
if key - w.lastIdxKey == 1:
@@ -562,13 +607,15 @@ proc process(c: PPassContext, n: PNode): PNode =
# addInterfaceSym(w, a.sons[j].sym);
# end
of nkImportStmt:
- for i in countup(0, sonsLen(n) - 1): addModDep(w, getModuleName(n.sons[i]))
+ for i in countup(0, sonsLen(n) - 1):
+ addModDep(w, getModuleName(n.sons[i]), n.info)
addStmt(w, n)
- of nkFromStmt:
- addModDep(w, getModuleName(n.sons[0]))
+ of nkFromStmt, nkImportExceptStmt:
+ addModDep(w, getModuleName(n.sons[0]), n.info)
addStmt(w, n)
of nkIncludeStmt:
- for i in countup(0, sonsLen(n) - 1): addInclDep(w, getModuleName(n.sons[i]))
+ for i in countup(0, sonsLen(n) - 1):
+ addInclDep(w, getModuleName(n.sons[i]), n.info)
of nkPragma:
addStmt(w, n)
else:
diff --git a/compiler/ropes.nim b/compiler/ropes.nim
index bfae7aaa4..d84b59f78 100644
--- a/compiler/ropes.nim
+++ b/compiler/ropes.nim
@@ -310,15 +310,19 @@ proc equalsFile*(r: Rope, f: File): bool =
buf: array[bufSize, char]
bpos = buf.len
blen = buf.len
+ btotal = 0
+ rtotal = 0
for s in leaves(r):
var spos = 0
let slen = s.len
+ rtotal += slen
while spos < slen:
if bpos == blen:
# Read more data
bpos = 0
blen = readBuffer(f, addr(buf[0]), buf.len)
+ btotal += blen
if blen == 0: # no more data in file
result = false
return
@@ -330,7 +334,8 @@ proc equalsFile*(r: Rope, f: File): bool =
spos += n
bpos += n
- result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all
+ result = readBuffer(f, addr(buf[0]), 1) == 0 and
+ btotal == rtotal # check that we've read all
proc equalsFile*(r: Rope, filename: string): bool =
## returns true if the contents of the file `f` equal `r`. If `f` does not
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index 8b2653bc9..f04cef0ee 100644
--- a/compiler/scriptconfig.nim
+++ b/compiler/scriptconfig.nim
@@ -43,6 +43,7 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext =
template cbos(name, body) {.dirty.} =
result.registerCallback "stdlib.system." & astToStr(name),
proc (a: VmArgs) =
+ errorMsg = nil
try:
body
except OSError:
@@ -150,7 +151,7 @@ proc runNimScript*(scriptName: string; freshDefines=true) =
vm.globalCtx = setupVM(m, scriptName)
compileSystemModule()
- processModule(m, llStreamOpen(scriptName, fmRead), nil)
+ discard processModule(m, llStreamOpen(scriptName, fmRead), nil)
# ensure we load 'system.nim' again for the real non-config stuff!
resetAllModulesHard()
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 7db4ae47e..7768833b3 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -39,7 +39,6 @@ proc semStmt(c: PContext, n: PNode): PNode
proc semParamList(c: PContext, n, genericParams: PNode, s: PSym)
proc addParams(c: PContext, n: PNode, kind: TSymKind)
proc maybeAddResult(c: PContext, s: PSym, n: PNode)
-proc instGenericContainer(c: PContext, n: PNode, header: PType): PType
proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
proc activate(c: PContext, n: PNode)
proc semQuoteAst(c: PContext, n: PNode): PNode
@@ -47,6 +46,10 @@ proc finishMethod(c: PContext, s: PSym)
proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode
+proc isArrayConstr(n: PNode): bool {.inline.} =
+ result = n.kind == nkBracket and
+ n.typ.skipTypes(abstractInst).kind == tyArray
+
template semIdeForTemplateOrGenericCheck(n, requiresCheck) =
# we check quickly if the node is where the cursor is
when defined(nimsuggest):
@@ -187,7 +190,6 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
# identifier with visibility
proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
allowed: TSymFlags): PSym
-proc semStmtScope(c: PContext, n: PNode): PNode
proc typeAllowedCheck(info: TLineInfo; typ: PType; kind: TSymKind) =
let t = typeAllowed(typ, kind)
@@ -252,7 +254,7 @@ proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode): PNode =
result = arg
# for 'tcnstseq' we support [] to become 'seq'
if eOrig.typ.skipTypes(abstractInst).kind == tySequence and
- arg.typ.skipTypes(abstractInst).kind == tyArrayConstr:
+ isArrayConstr(arg):
arg.typ = eOrig.typ
proc tryConstExpr(c: PContext, n: PNode): PNode =
@@ -445,6 +447,8 @@ proc isImportSystemStmt(n: PNode): bool =
else: discard
proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
+ if n.kind == nkDefer:
+ localError(n.info, "defer statement not supported at top level")
if c.topStmts == 0 and not isImportSystemStmt(n):
if sfSystemModule notin c.module.flags and
n.kind notin {nkEmpty, nkCommentStmt}:
diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim
index 9702128ba..2e925e386 100644
--- a/compiler/semasgn.nim
+++ b/compiler/semasgn.nim
@@ -203,7 +203,8 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) =
defaultOp(c, t, body, x, y)
of tyObject, tyDistinct:
if not considerOverloadedOp(c, t, body, x, y):
- if t.sons[0] != nil: liftBodyAux(c, t.sons[0], body, x, y)
+ if t.sons[0] != nil:
+ liftBodyAux(c, t.sons[0].skipTypes(skipPtrs), body, x, y)
if t.kind == tyObject: liftBodyObj(c, t.n, body, x, y)
of tyTuple:
liftBodyTup(c, t, body, x, y)
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index 97209167d..3037a6ecc 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -34,6 +34,22 @@ proc sameMethodDispatcher(a, b: PSym): bool =
proc determineType(c: PContext, s: PSym)
+proc initCandidateSymbols(c: PContext, headSymbol: PNode,
+ initialBinding: PNode,
+ filter: TSymKinds,
+ best, alt: var TCandidate,
+ o: var TOverloadIter): seq[tuple[s: PSym, scope: int]] =
+ result = @[]
+ var symx = initOverloadIter(o, c, headSymbol)
+ while symx != nil:
+ if symx.kind in filter:
+ result.add((symx, o.lastOverloadScope))
+ symx = nextOverloadIter(o, c, headSymbol)
+ if result.len > 0:
+ initCandidate(c, best, result[0].s, initialBinding, result[0].scope)
+ initCandidate(c, alt, result[0].s, initialBinding, result[0].scope)
+ best.state = csNoMatch
+
proc pickBestCandidate(c: PContext, headSymbol: PNode,
n, orig: PNode,
initialBinding: PNode,
@@ -41,66 +57,62 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
best, alt: var TCandidate,
errors: var CandidateErrors) =
var o: TOverloadIter
- # thanks to the lazy semchecking for operands, we need to iterate over the
- # symbol table *before* any call to 'initCandidate' which might invoke
- # semExpr which might modify the symbol table in cases like
- # 'init(a, 1, (var b = new(Type2); b))'.
- var symx = initOverloadIter(o, c, headSymbol)
- let symScope = o.lastOverloadScope
-
- var syms: seq[tuple[a: PSym, b: int]] = @[]
- while symx != nil:
- if symx.kind in filter:
- syms.add((symx, o.lastOverloadScope))
- symx = nextOverloadIter(o, c, headSymbol)
- if syms.len == 0:
- when false:
- if skIterator notin filter:
- # also try iterators, but these are 2nd class:
- symx = initOverloadIter(o, c, headSymbol)
- while symx != nil:
- if symx.kind == skIterator:
- syms.add((symx, 100))
- symx = nextOverloadIter(o, c, headSymbol)
- if syms.len == 0: return
+ var sym = initOverloadIter(o, c, headSymbol)
+ var scope = o.lastOverloadScope
+ # Thanks to the lazy semchecking for operands, we need to check whether
+ # 'initCandidate' modifies the symbol table (via semExpr).
+ # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))'
+ let counterInitial = c.currentScope.symbols.counter
+ var syms: seq[tuple[s: PSym, scope: int]]
+ var nextSymIndex = 0
+ while sym != nil:
+ if sym.kind in filter:
+ # Initialise 'best' and 'alt' with the first available symbol
+ initCandidate(c, best, sym, initialBinding, scope)
+ initCandidate(c, alt, sym, initialBinding, scope)
+ best.state = csNoMatch
+ break
else:
- return
-
+ sym = nextOverloadIter(o, c, headSymbol)
+ scope = o.lastOverloadScope
var z: TCandidate
- initCandidate(c, best, syms[0][0], initialBinding, symScope)
- initCandidate(c, alt, syms[0][0], initialBinding, symScope)
- best.state = csNoMatch
-
- for i in 0 .. "),
a.owner, reason.info)
+when true:
+ template markSideEffect(a: PEffects; reason: typed) =
+ a.hasSideEffect = true
+else:
+ template markSideEffect(a: PEffects; reason: typed) =
+ a.hasSideEffect = true
+ markGcUnsafe(a, reason)
+
proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet) =
let u = s.gcUnsafetyReason
if u != nil and not cycleCheck.containsOrIncl(u.id):
@@ -226,12 +234,16 @@ proc useVar(a: PEffects, n: PNode) =
message(n.info, warnUninit, s.name.s)
# prevent superfluous warnings about the same variable:
a.init.add s.id
- if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet}:
+ if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
+ s.magic != mNimVm:
if s.guard != nil: guardGlobal(a, n, s.guard)
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
#if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
markGcUnsafe(a, s)
+ else:
+ markSideEffect(a, s)
+
type
TIntersection = seq[tuple[id, count: int]] # a simple count table
@@ -268,10 +280,6 @@ proc createTag(n: PNode): PNode =
result.typ = sysTypeFromName"TEffect"
if not n.isNil: result.info = n.info
-proc createAnyGlobal(n: PNode): PNode =
- result = newSymNode(anyGlobal)
- result.info = n.info
-
proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
assert e.kind != nkRaiseStmt
var aa = a.exc
@@ -495,6 +503,8 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
if notGcSafe(s.typ) and sfImportc notin s.flags:
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
markGcUnsafe(tracked, s)
+ if tfNoSideEffect notin s.typ.flags:
+ markSideEffect(tracked, s)
mergeLockLevels(tracked, n, s.getLockLevel)
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
@@ -550,12 +560,16 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner):
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
markGcUnsafe(tracked, a)
+ elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
+ markSideEffect(tracked, a)
else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n)
if notGcSafe(op):
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
markGcUnsafe(tracked, a)
+ elif tfNoSideEffect notin op.flags:
+ markSideEffect(tracked, a)
notNilCheck(tracked, n, paramType)
proc breaksBlock(n: PNode): bool =
@@ -684,6 +698,7 @@ proc track(tracked: PEffects, n: PNode) =
if a.sym == tracked.owner: tracked.isRecursive = true
# even for recursive calls we need to check the lock levels (!):
mergeLockLevels(tracked, n, a.sym.getLockLevel)
+ if sfSideEffect in a.sym.flags: markSideEffect(tracked, a)
else:
mergeLockLevels(tracked, n, op.lockLevel)
var effectList = op.n.sons[0]
@@ -702,6 +717,10 @@ proc track(tracked: PEffects, n: PNode) =
if not (a.kind == nkSym and a.sym == tracked.owner):
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
markGcUnsafe(tracked, a)
+ if tfNoSideEffect notin op.flags and not importedFromC(a):
+ # and it's not a recursive call:
+ if not (a.kind == nkSym and a.sym == tracked.owner):
+ markSideEffect(tracked, a)
for i in 1 .. s.typ.lockLevel:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 5f9989ea2..ebcff643f 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -155,7 +155,10 @@ proc discardCheck(c: PContext, result: PNode) =
else:
var n = result
while n.kind in skipForDiscardable: n = n.lastSon
- localError(n.info, errDiscardValueX, result.typ.typeToString)
+ if result.typ.kind == tyProc:
+ localError(n.info, "value of type '" & result.typ.typeToString & "' has to be discarded; for a function call use ()")
+ else:
+ localError(n.info, errDiscardValueX, result.typ.typeToString)
proc semIf(c: PContext, n: PNode): PNode =
result = n
@@ -659,7 +662,7 @@ proc semRaise(c: PContext, n: PNode): PNode =
if n.sons[0].kind != nkEmpty:
n.sons[0] = semExprWithType(c, n.sons[0])
var typ = n.sons[0].typ
- if typ.kind != tyRef or typ.sons[0].kind != tyObject:
+ if typ.kind != tyRef or typ.lastSon.kind != tyObject:
localError(n.info, errExprCannotBeRaised)
proc addGenericParamListToScope(c: PContext, n: PNode) =
@@ -1141,11 +1144,6 @@ type
TProcCompilationSteps = enum
stepRegisterSymbol,
stepDetermineType,
- stepCompileBody
-
-proc isForwardDecl(s: PSym): bool =
- internalAssert s.kind == skProc
- result = s.ast[bodyPos].kind != nkEmpty
proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
validPragmas: TSpecialWords,
@@ -1183,8 +1181,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
s.ast = n
#s.scope = c.currentScope
- # if typeIsDetermined: assert phase == stepCompileBody
- # else: assert phase == stepDetermineType
# before compiling the proc body, set as current the scope
# where the proc was declared
let oldScope = c.currentScope
@@ -1234,7 +1230,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
implicitPragmas(c, s, n, validPragmas)
else:
if n.sons[pragmasPos].kind != nkEmpty:
- localError(n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProc)
+ localError(n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX,
+ "'" & proto.name.s & "' from " & $proto.info)
if sfForward notin proto.flags:
wrongRedefinition(n.info, proto.name.s)
excl(proto.flags, sfForward)
@@ -1403,6 +1400,11 @@ proc semMacroDef(c: PContext, n: PNode): PNode =
if namePos >= result.safeLen: return result
var s = result.sons[namePos].sym
var t = s.typ
+ var allUntyped = true
+ for i in 1 .. t.n.len-1:
+ let param = t.n.sons[i].sym
+ if param.typ.kind != tyExpr: allUntyped = false
+ if allUntyped: incl(s.flags, sfAllUntyped)
if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "macro")
if n.sons[bodyPos].kind == nkEmpty:
localError(n.info, errImplOfXexpected, s.name.s)
@@ -1566,8 +1568,3 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
proc semStmt(c: PContext, n: PNode): PNode =
# now: simply an alias:
result = semExprNoType(c, n)
-
-proc semStmtScope(c: PContext, n: PNode): PNode =
- openScope(c)
- result = semStmt(c, n)
- closeScope(c)
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 20b5071ac..dfe3ded0d 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -287,35 +287,6 @@ proc semTemplBodySons(c: var TemplCtx, n: PNode): PNode =
for i in 0.. < n.len:
result.sons[i] = semTemplBody(c, n.sons[i])
-proc wrapInBind(c: var TemplCtx; n: PNode; opr: string): PNode =
- let ident = getIdent(opr)
- if ident.id in c.toInject: return n
-
- let s = searchInScopes(c.c, ident)
- if s != nil:
- var callee: PNode
- if contains(c.toBind, s.id):
- callee = symChoice(c.c, n, s, scClosed)
- elif contains(c.toMixin, s.name.id):
- callee = symChoice(c.c, n, s, scForceOpen)
- elif s.owner == c.owner and sfGenSym in s.flags:
- # template tmp[T](x: var seq[T]) =
- # var yz: T
- incl(s.flags, sfUsed)
- callee = newSymNode(s, n.info)
- styleCheckUse(n.info, s)
- else:
- callee = semTemplSymbol(c.c, n, s)
-
- let call = newNodeI(nkCall, n.info)
- call.add(callee)
- for i in 0 .. n.len-1: call.add(n[i])
- result = newNodeI(nkBind, n.info, 2)
- result.sons[0] = n
- result.sons[1] = call
- else:
- result = n
-
proc oprIsRoof(n: PNode): bool =
const roof = "^"
case n.kind
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 9d00c06ca..5f47bca5c 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -110,6 +110,8 @@ proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string,
result = newOrPrevType(kind, prev, c)
if sonsLen(n) == 2:
var base = semTypeNode(c, n.sons[1], nil)
+ if base.kind == tyVoid:
+ localError(n.info, errTIsNotAConcreteType, typeToString(base))
addSonSkipIntLit(result, base)
else:
localError(n.info, errXExpectsOneTypeParam, kindStr)
@@ -659,11 +661,12 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
if n.sonsLen == 0: return newConstraint(c, tyObject)
var check = initIntSet()
var pos = 0
- var base: PType = nil
+ var base, realBase: PType = nil
# n.sons[0] contains the pragmas (if any). We process these later...
checkSonsLen(n, 3)
if n.sons[1].kind != nkEmpty:
- base = skipTypesOrNil(semTypeNode(c, n.sons[1].sons[0], nil), skipPtrs)
+ realBase = semTypeNode(c, n.sons[1].sons[0], nil)
+ base = skipTypesOrNil(realBase, skipPtrs)
if base.isNil:
localError(n.info, errIllegalRecursionInTypeX, "object")
else:
@@ -674,9 +677,10 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
if concreteBase.kind != tyError:
localError(n.sons[1].info, errInheritanceOnlyWithNonFinalObjects)
base = nil
+ realBase = nil
if n.kind != nkObjectTy: internalError(n.info, "semObjectNode")
result = newOrPrevType(tyObject, prev, c)
- rawAddSon(result, base)
+ rawAddSon(result, realBase)
if result.n.isNil:
result.n = newNodeI(nkRecList, n.info)
else:
@@ -1137,14 +1141,12 @@ proc semProcTypeWithScope(c: PContext, n: PNode,
checkSonsLen(n, 2)
openScope(c)
result = semProcTypeNode(c, n.sons[0], nil, prev, kind, isType=true)
+ # start with 'ccClosure', but of course pragmas can overwrite this:
+ result.callConv = ccClosure
# dummy symbol for `pragma`:
var s = newSymS(kind, newIdentNode(getIdent("dummy"), n.info), c)
s.typ = result
- if n.sons[1].kind == nkEmpty or n.sons[1].len == 0:
- if result.callConv == ccDefault:
- result.callConv = ccClosure
- #Message(n.info, warnImplicitClosure, renderTree(n))
- else:
+ if n.sons[1].kind != nkEmpty and n.sons[1].len > 0:
pragma(c, s, n.sons[1], procTypePragmas)
when useEffectSystem: setEffectsForProcType(result, n.sons[1])
closeScope(c)
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index 12620d55d..b42d58474 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -86,7 +86,7 @@ type
proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType
proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym
-proc replaceTypeVarsN*(cl: var TReplTypeVars, n: PNode): PNode
+proc replaceTypeVarsN*(cl: var TReplTypeVars, n: PNode; start=0): PNode
template checkMetaInvariants(cl: TReplTypeVars, t: PType) =
when false:
@@ -151,7 +151,7 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
return n
-proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode =
+proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode =
if n == nil: return
result = copyNode(n)
if n.typ != nil:
@@ -195,7 +195,9 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode =
var length = sonsLen(n)
if length > 0:
newSons(result, length)
- for i in countup(0, length - 1):
+ if start > 0:
+ result.sons[0] = n.sons[0]
+ for i in countup(start, length - 1):
result.sons[i] = replaceTypeVarsN(cl, n.sons[i])
proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym =
@@ -462,8 +464,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
r = skipTypes(r2, {tyPtr, tyRef})
result.sons[i] = r
propagateToOwner(result, r)
-
- result.n = replaceTypeVarsN(cl, result.n)
+ # bug #4677: Do not instantiate effect lists
+ result.n = replaceTypeVarsN(cl, result.n, ord(result.kind==tyProc))
case result.kind
of tyArray:
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index ec429968c..df5a76a57 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -352,18 +352,28 @@ proc handleFloatRange(f, a: PType): TTypeRelation =
else: result = isIntConv
else: result = isNone
-proc isObjectSubtype(a, f: PType): int =
+proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
var t = a
assert t.kind == tyObject
var depth = 0
+ var last = a
while t != nil and not sameObjectTypes(f, t):
assert t.kind == tyObject
t = t.sons[0]
if t == nil: break
- t = skipTypes(t, {tyGenericInst})
+ last = t
+ t = skipTypes(t, skipPtrs)
inc depth
if t != nil:
+ if fGenericOrigin != nil and last.kind == tyGenericInst and
+ last.len-1 == fGenericOrigin.len:
+ for i in countup(1, sonsLen(fGenericOrigin) - 1):
+ let x = PType(idTableGet(c.bindings, fGenericOrigin.sons[i]))
+ if x == nil:
+ put(c, fGenericOrigin.sons[i], last.sons[i])
result = depth
+ else:
+ result = -1
type
SkippedPtr = enum skippedNone, skippedRef, skippedPtr
@@ -840,12 +850,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
of tyOpenArray, tyVarargs:
result = typeRel(c, base(f), base(a))
if result < isGeneric: result = isNone
- of tyArrayConstr:
- if (f.sons[0].kind != tyGenericParam) and (a.sons[1].kind == tyEmpty):
- result = isSubtype # [] is allowed here
- elif typeRel(c, base(f), a.sons[1]) >= isGeneric:
- result = isSubtype
- of tyArray:
+ of tyArray, tyArrayConstr:
if (f.sons[0].kind != tyGenericParam) and (a.sons[1].kind == tyEmpty):
result = isSubtype
elif typeRel(c, base(f), a.sons[1]) >= isGeneric:
@@ -896,7 +901,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
result = isEqual
# elif tfHasMeta in f.flags: result = recordRel(c, f, a)
else:
- var depth = isObjectSubtype(a, f)
+ var depth = isObjectSubtype(c, a, f, nil)
if depth > 0:
inc(c.inheritancePenalty, depth)
result = isSubtype
@@ -1012,6 +1017,15 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
result = isGeneric
else:
let genericBody = f.sons[0]
+ var askip = skippedNone
+ var fskip = skippedNone
+ let aobj = x.skipToObject(askip)
+ let fobj = genericBody.lastSon.skipToObject(fskip)
+ if fobj != nil and aobj != nil and askip == fskip:
+ let depth = isObjectSubtype(c, aobj, fobj, f)
+ if depth >= 0:
+ c.inheritancePenalty += depth
+ return if depth == 0: isGeneric else: isSubtype
result = typeRel(c, genericBody, x)
if result != isNone:
# see tests/generics/tgeneric3.nim for an example that triggers this
@@ -1075,7 +1089,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
of tyBuiltInTypeClass:
considerPreviousT:
let targetKind = f.sons[0].kind
- if targetKind == a.skipTypes({tyRange, tyGenericInst}).kind or
+ if targetKind == a.skipTypes({tyRange, tyGenericInst, tyBuiltInTypeClass}).kind or
(targetKind in {tyProc, tyPointer} and a.kind == tyNil):
put(c, f, a)
return isGeneric
@@ -1310,8 +1324,9 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
var call = newNodeI(nkCall, arg.info)
call.add(f.n.copyTree)
call.add(arg.copyTree)
- result = c.semOverloadedCall(c, call, call, routineKinds)
+ result = c.semExpr(c, call)
if result != nil:
+ if result.typ == nil: return nil
# resulting type must be consistent with the other arguments:
var r = typeRel(m, f.sons[0], result.typ)
if r < isGeneric: return nil
@@ -1320,13 +1335,6 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
if r == isGeneric:
result.typ = getInstantiatedType(c, arg, m, base(f))
m.baseTypeMatch = true
- # bug #4545: allow the call to go through a 'var T':
- let vt = result.sons[0].typ.sons[1]
- if vt.kind == tyVar:
- let x = result.sons[1]
- let va = newNodeIT(nkHiddenAddr, x.info, vt)
- va.add x
- result.sons[1] = va
proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) =
case r
@@ -1559,8 +1567,13 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
proc setSon(father: PNode, at: int, son: PNode) =
- if sonsLen(father) <= at: setLen(father.sons, at + 1)
+ let oldLen = father.len
+ if oldLen <= at:
+ setLen(father.sons, at + 1)
father.sons[at] = son
+ # insert potential 'void' parameters:
+ #for i in oldLen ..< at:
+ # father.sons[i] = newNodeIT(nkEmpty, son.info, getSysType(tyVoid))
# we are allowed to modify the calling node in the 'prepare*' procs:
proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode =
@@ -1591,17 +1604,17 @@ proc prepareNamedParam(a: PNode) =
a.sons[0] = newIdentNode(considerQuotedIdent(a.sons[0]), info)
proc arrayConstr(c: PContext, n: PNode): PType =
- result = newTypeS(tyArrayConstr, c)
+ result = newTypeS(tyArray, c)
rawAddSon(result, makeRangeType(c, 0, 0, n.info))
addSonSkipIntLit(result, skipTypes(n.typ, {tyGenericInst, tyVar, tyOrdinal}))
proc arrayConstr(c: PContext, info: TLineInfo): PType =
- result = newTypeS(tyArrayConstr, c)
+ result = newTypeS(tyArray, c)
rawAddSon(result, makeRangeType(c, 0, -1, info))
rawAddSon(result, newTypeS(tyEmpty, c)) # needs an empty basetype!
proc incrIndexType(t: PType) =
- assert t.kind == tyArrayConstr
+ assert t.kind == tyArray
inc t.sons[0].n.sons[1].intVal
template isVarargsUntyped(x): untyped =
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index c127b968c..52f00550b 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -273,7 +273,7 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
while true:
suggestObject(c, t.n, outputs)
if t.sons[0] == nil: break
- t = skipTypes(t.sons[0], {tyGenericInst})
+ t = skipTypes(t.sons[0], skipPtrs)
suggestOperations(c, n, typ, outputs)
elif typ.kind == tyTuple and typ.n != nil:
suggestSymList(c, typ.n, outputs)
@@ -413,17 +413,16 @@ proc safeSemExpr*(c: PContext, n: PNode): PNode =
result = ast.emptyNode
proc suggestExpr*(c: PContext, node: PNode) =
- if nfIsCursor notin node.flags:
- if gTrackPos.line < 0: return
- var cp = inCheckpoint(node.info)
- if cp == cpNone: return
+ if gTrackPos.line < 0: return
+ var cp = inCheckpoint(node.info)
+ if cp == cpNone: return
var outputs = 0
# This keeps semExpr() from coming here recursively:
if c.compilesContextId > 0: return
inc(c.compilesContextId)
if gIdeCmd == ideSug:
- var n = if nfIsCursor in node.flags: node else: findClosestDot(node)
+ var n = findClosestDot(node)
if n == nil: n = node
if n.kind == nkDotExpr:
var obj = safeSemExpr(c, n.sons[0])
@@ -436,7 +435,7 @@ proc suggestExpr*(c: PContext, node: PNode) =
suggestEverything(c, n, outputs)
elif gIdeCmd == ideCon:
- var n = if nfIsCursor in node.flags: node else: findClosestCall(node)
+ var n = findClosestCall(node)
if n == nil: n = node
if n.kind in nkCallKinds:
var a = copyNode(n)
diff --git a/compiler/transf.nim b/compiler/transf.nim
index fc400c524..5cd5e298b 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -594,13 +594,6 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
popTransCon(c)
# echo "transformed: ", stmtList.PNode.renderTree
-proc getMagicOp(call: PNode): TMagic =
- if call.sons[0].kind == nkSym and
- call.sons[0].sym.kind in {skProc, skMethod, skConverter}:
- result = call.sons[0].sym.magic
- else:
- result = mNone
-
proc transformCase(c: PTransf, n: PNode): PTransNode =
# removes `elif` branches of a case stmt
# adds ``else: nil`` if needed for the code generator
diff --git a/compiler/trees.nim b/compiler/trees.nim
index fdd88c348..a629b3834 100644
--- a/compiler/trees.nim
+++ b/compiler/trees.nim
@@ -12,32 +12,21 @@
import
ast, astalgo, lexer, msgs, strutils, wordrecg
-proc hasSon(father, son: PNode): bool =
- for i in countup(0, sonsLen(father) - 1):
- if father.sons[i] == son:
- return true
- result = false
-
-proc cyclicTreeAux(n, s: PNode): bool =
- if n == nil:
- return false
- if hasSon(s, n):
- return true
- var m = sonsLen(s)
- addSon(s, n)
+proc cyclicTreeAux(n: PNode, visited: var seq[PNode]): bool =
+ if n == nil: return
+ for v in visited:
+ if v == n: return true
if not (n.kind in {nkEmpty..nkNilLit}):
- for i in countup(0, sonsLen(n) - 1):
- if cyclicTreeAux(n.sons[i], s):
- return true
- result = false
- delSon(s, m)
+ visited.add(n)
+ for nSon in n.sons:
+ if cyclicTreeAux(nSon, visited): return true
+ discard visited.pop()
proc cyclicTree*(n: PNode): bool =
- var s = newNodeI(nkEmpty, n.info)
- result = cyclicTreeAux(n, s)
+ var visited: seq[PNode] = @[]
+ cyclicTreeAux(n, visited)
proc exprStructuralEquivalent*(a, b: PNode; strictSymEquality=false): bool =
- result = false
if a == b:
result = true
elif (a != nil) and (b != nil) and (a.kind == b.kind):
@@ -61,7 +50,6 @@ proc exprStructuralEquivalent*(a, b: PNode; strictSymEquality=false): bool =
result = true
proc sameTree*(a, b: PNode): bool =
- result = false
if a == b:
result = true
elif a != nil and b != nil and a.kind == b.kind:
@@ -84,17 +72,6 @@ proc sameTree*(a, b: PNode): bool =
if not sameTree(a.sons[i], b.sons[i]): return
result = true
-proc getProcSym*(call: PNode): PSym =
- result = call.sons[0].sym
-
-proc getOpSym*(op: PNode): PSym =
- if op.kind notin {nkCall, nkHiddenCallConv, nkCommand, nkCallStrLit}:
- result = nil
- else:
- if sonsLen(op) <= 0: internalError(op.info, "getOpSym")
- elif op.sons[0].kind == nkSym: result = op.sons[0].sym
- else: result = nil
-
proc getMagic*(op: PNode): TMagic =
case op.kind
of nkCallKinds:
@@ -104,9 +81,8 @@ proc getMagic*(op: PNode): TMagic =
else: result = mNone
proc isConstExpr*(n: PNode): bool =
- result = (n.kind in
- {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
- nkFloatLit..nkFloat64Lit, nkNilLit}) or (nfAllConst in n.flags)
+ const atomKinds = {nkCharLit..nkNilLit} # Char, Int, UInt, Str, Float and Nil literals
+ n.kind in atomKinds or nfAllConst in n.flags
proc isCaseObj*(n: PNode): bool =
if n.kind == nkRecCase: return true
@@ -131,25 +107,6 @@ proc isDeepConstExpr*(n: PNode): bool =
result = true
else: discard
-proc flattenTreeAux(d, a: PNode, op: TMagic) =
- if (getMagic(a) == op): # a is a "leaf", so add it:
- for i in countup(1, sonsLen(a) - 1): # BUGFIX
- flattenTreeAux(d, a.sons[i], op)
- else:
- addSon(d, copyTree(a))
-
-proc flattenTree*(root: PNode, op: TMagic): PNode =
- result = copyNode(root)
- if getMagic(root) == op:
- # BUGFIX: forget to copy prc
- addSon(result, copyNode(root.sons[0]))
- flattenTreeAux(result, root, op)
-
-proc swapOperands*(op: PNode) =
- var tmp = op.sons[1]
- op.sons[1] = op.sons[2]
- op.sons[2] = tmp
-
proc isRange*(n: PNode): bool {.inline.} =
if n.kind in nkCallKinds:
if n[0].kind == nkIdent and n[0].ident.id == ord(wDotDot) or
@@ -168,7 +125,6 @@ proc unnestStmts(n, result: PNode) =
result.add(n)
proc flattenStmts*(n: PNode): PNode =
- ## flattens a nested statement list; used for pattern matching
result = newNodeI(nkStmtList, n.info)
unnestStmts(n, result)
if result.len == 1:
diff --git a/compiler/types.nim b/compiler/types.nim
index 312e36ae5..3db0c4507 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -94,7 +94,8 @@ proc invalidGenericInst(f: PType): bool =
proc isPureObject(typ: PType): bool =
var t = typ
- while t.kind == tyObject and t.sons[0] != nil: t = t.sons[0]
+ while t.kind == tyObject and t.sons[0] != nil:
+ t = t.sons[0].skipTypes(skipPtrs)
result = t.sym != nil and sfPure in t.sym.flags
proc getOrdValue(n: PNode): BiggestInt =
@@ -145,10 +146,6 @@ proc elemType*(t: PType): PType =
else: result = t.lastSon
assert(result != nil)
-proc skipGeneric(t: PType): PType =
- result = t
- while result.kind == tyGenericInst: result = lastSon(result)
-
proc isOrdinalType(t: PType): bool =
assert(t != nil)
const
@@ -232,7 +229,8 @@ proc searchTypeForAux(t: PType, predicate: TTypePredicate,
if result: return
case t.kind
of tyObject:
- result = searchTypeForAux(t.sons[0], predicate, marker)
+ if t.sons[0] != nil:
+ result = searchTypeForAux(t.sons[0].skipTypes(skipPtrs), predicate, marker)
if not result: result = searchTypeNodeForAux(t.n, predicate, marker)
of tyGenericInst, tyDistinct:
result = searchTypeForAux(lastSon(t), predicate, marker)
@@ -269,7 +267,9 @@ proc analyseObjectWithTypeFieldAux(t: PType,
if searchTypeNodeForAux(t.n, isObjectWithTypeFieldPredicate, marker):
return frEmbedded
for i in countup(0, sonsLen(t) - 1):
- res = analyseObjectWithTypeFieldAux(t.sons[i], marker)
+ var x = t.sons[i]
+ if x != nil: x = x.skipTypes(skipPtrs)
+ res = analyseObjectWithTypeFieldAux(x, marker)
if res == frEmbedded:
return frEmbedded
if res == frHeader: result = frHeader
@@ -578,10 +578,6 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
result = typeToStr[t.kind]
result.addTypeFlags(t)
-proc resultType(t: PType): PType =
- assert(t.kind == tyProc)
- result = t.sons[0] # nil is allowed
-
proc base(t: PType): PType =
result = t.sons[0]
@@ -661,7 +657,14 @@ proc lengthOrd(t: PType): BiggestInt =
case t.kind
of tyInt64, tyInt32, tyInt: result = lastOrd(t)
of tyDistinct, tyConst, tyMutable: result = lengthOrd(t.sons[0])
- else: result = lastOrd(t) - firstOrd(t) + 1
+ else:
+ let last = lastOrd t
+ let first = firstOrd t
+ # XXX use a better overflow check here:
+ if last == high(BiggestInt) and first <= 0:
+ result = last
+ else:
+ result = lastOrd(t) - firstOrd(t) + 1
# -------------- type equality -----------------------------------------------
@@ -772,18 +775,6 @@ proc equalParams(a, b: PNode): TParamsEquality =
result = paramsIncompatible # overloading by different
# result types does not work
-proc sameLiteral(x, y: PNode): bool =
- if x.kind == y.kind:
- case x.kind
- of nkCharLit..nkInt64Lit: result = x.intVal == y.intVal
- of nkFloatLit..nkFloat64Lit: result = x.floatVal == y.floatVal
- of nkNilLit: result = true
- else: assert(false)
-
-proc sameRanges(a, b: PNode): bool =
- result = sameLiteral(a.sons[0], b.sons[0]) and
- sameLiteral(a.sons[1], b.sons[1])
-
proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool =
# two tuples are equivalent iff the names, types and positions are the same;
# however, both types may not have any field names (t.n may be nil) which
@@ -1070,10 +1061,10 @@ proc typeAllowedNode(marker: var IntSet, n: PNode, kind: TSymKind,
of nkNone..nkNilLit:
discard
else:
+ if n.kind == nkRecCase and kind in {skProc, skConst}:
+ return n[0].typ
for i in countup(0, sonsLen(n) - 1):
let it = n.sons[i]
- if it.kind == nkRecCase and kind in {skProc, skConst}:
- return n.typ
result = typeAllowedNode(marker, it, kind, flags)
if result != nil: break
@@ -1306,7 +1297,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt =
a = maxAlign
of tyObject:
if typ.sons[0] != nil:
- result = computeSizeAux(typ.sons[0], a)
+ result = computeSizeAux(typ.sons[0].skipTypes(skipPtrs), a)
if result < 0: return
maxAlign = a
elif isObjectWithTypeFieldPredicate(typ):
diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim
index d050a86b2..438744b1c 100644
--- a/compiler/typesrenderer.nim
+++ b/compiler/typesrenderer.nim
@@ -19,20 +19,14 @@ proc renderPlainSymbolName*(n: PNode): string =
## for the HTML hyperlinks.
result = ""
case n.kind
- of nkPostfix:
- for i in 0 .. 0:
- return
+ of nkPostfix, nkAccQuoted:
+ result = renderPlainSymbolName(n[= 0:
@@ -282,7 +284,8 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame):
pc2 = nextExceptOrFinally
if c.code[pc2].opcode == opcFinally:
# execute the corresponding handler, but don't quit walking the stack:
- return (pc2, f)
+ discard f.safePoints.pop
+ return (pc2+1, f)
# not the right one:
discard f.safePoints.pop
@@ -964,7 +967,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
# we'll execute in the 'raise' handler
let rbx = instr.regBx - wordExcess - 1 # -1 for the following 'inc pc'
inc pc, rbx
- assert c.code[pc+1].opcode in {opcExcept, opcFinally}
+ while c.code[pc+1].opcode == opcExcept:
+ let rbx = c.code[pc+1].regBx - wordExcess - 1
+ inc pc, rbx
+ #assert c.code[pc+1].opcode in {opcExcept, opcFinally}
+ if c.code[pc+1].opcode != opcFinally:
+ # in an except handler there is no active safe point for the 'try':
+ tos.popSafePoint()
of opcFinally:
# just skip it; it's followed by the code we need to execute anyway
tos.popSafePoint()
@@ -1063,7 +1072,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
of opcRepr:
decodeB(rkNode)
createStr regs[ra]
- regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments})
+ regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments, renderDocComments})
of opcQuit:
if c.mode in {emRepl, emStaticExpr, emStaticStmt}:
message(c.debug[pc], hintQuitCalled)
diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim
index 7dbf6f801..b40ca1058 100644
--- a/compiler/vmdeps.nim
+++ b/compiler/vmdeps.nim
@@ -109,10 +109,6 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
mapTypeToBracketX(name, m, t, info, inst)
template newNodeX(kind): untyped =
newNodeIT(kind, if t.n.isNil: info else: t.n.info, t)
- template newIdent(s): untyped =
- var r = newNodeX(nkIdent)
- r.add !s
- r
template newIdentDefs(n,t): untyped =
var id = newNodeX(nkIdentDefs)
id.add n # name
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 61ab65360..6bfc33f00 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -748,7 +748,7 @@ proc genConv(c: PCtx; n, arg: PNode; dest: var TDest; opc=opcConv) =
let tmp = c.genx(arg)
if dest < 0: dest = c.getTemp(n.typ)
c.gABC(n, opc, dest, tmp)
- c.gABx(n, opc, 0, genType(c, n.typ))
+ c.gABx(n, opc, 0, genType(c, n.typ.skipTypes({tyStatic})))
c.gABx(n, opc, 0, genType(c, arg.typ.skipTypes({tyStatic})))
c.freeTemp(tmp)
@@ -1127,14 +1127,6 @@ proc fitsRegister*(t: PType): bool =
t.skipTypes(abstractInst-{tyTypeDesc}).kind in {
tyRange, tyEnum, tyBool, tyInt..tyUInt64, tyChar}
-proc requiresCopy(n: PNode): bool =
- if n.typ.skipTypes(abstractInst-{tyTypeDesc}).kind in atomicTypes:
- result = false
- elif n.kind in ({nkCurly, nkBracket, nkPar, nkObjConstr}+nkCallKinds):
- result = false
- else:
- result = true
-
proc unneededIndirection(n: PNode): bool =
n.typ.skipTypes(abstractInst-{tyTypeDesc}).kind == tyRef
@@ -1215,8 +1207,6 @@ proc whichAsgnOpc(n: PNode): TOpcode =
else:
opcAsgnComplex
-proc isRef(t: PType): bool = t.skipTypes(abstractRange-{tyTypeDesc}).kind == tyRef
-
proc whichAsgnOpc(n: PNode; opc: TOpcode): TOpcode = opc
proc genAsgn(c: PCtx; dest: TDest; ri: PNode; requiresCopy: bool) =
@@ -1268,9 +1258,6 @@ proc isTemp(c: PCtx; dest: TDest): bool =
template needsAdditionalCopy(n): untyped =
not c.isTemp(dest) and not fitsRegister(n.typ)
-proc skipDeref(n: PNode): PNode =
- result = if n.kind in {nkDerefExpr, nkHiddenDeref}: n.sons[0] else: n
-
proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode;
dest, idx, value: TRegister) =
# opcLdObj et al really means "load address". We sometimes have to create a
@@ -1848,7 +1835,6 @@ proc genParams(c: PCtx; params: PNode) =
# res.sym.position is already 0
c.prc.slots[0] = (inUse: true, kind: slotFixedVar)
for i in 1.. $name
+ title="$header_plain">$name$attype
"""
# HTML rendered for doc.item's seeSrc variable. Note that this will render to
@@ -60,8 +60,10 @@ doc.item.toc = """
# * $url: whatever you did pass through the --docSeeSrcUrl switch (which also
# gets variables path/line replaced!)
doc.item.seesrc = """ Source"""
+href="${url}/tree/${commit}/${path}#L${line}"
+class="link-seesrc" target="_blank">Source
+Edit
+"""
doc.toc = """
@@ -72,9 +74,38 @@ $content
doc.body_toc = """
+
+ Search:
+
$tableofcontents
+
+
$moduledesc
+ $content
+
+
+"""
+
+doc.body_toc_group = """
+
+
+
+ Search:
+
+
+ Group by:
+
+
+ $tableofcontents
+
+
+
$moduledesc
$content
@@ -93,7 +124,7 @@ doc.listing_end = ""
doc.file = """
-
+
@@ -1244,8 +1275,16 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
span.pragmaend {
cursor: pointer;
}
+
+div.search_results {
+ background-color: antiquewhite;
+ margin: 3em;
+ padding: 1em;
+ border: 1px solid #4d4d4d;
+}
+
-
+
diff --git a/doc/manual/lexing.txt b/doc/manual/lexing.txt
index 4d03023c3..7ffd5eb1c 100644
--- a/doc/manual/lexing.txt
+++ b/doc/manual/lexing.txt
@@ -28,7 +28,7 @@ The parser uses a stack of indentation levels: the stack consists of integers
counting the spaces. The indentation information is queried at strategic
places in the parser but ignored otherwise: The pseudo terminal ``IND{>}``
denotes an indentation that consists of more spaces than the entry at the top
-of the stack; IND{=} an indentation that has the same number of spaces. ``DED``
+of the stack; ``IND{=}`` an indentation that has the same number of spaces. ``DED``
is another pseudo terminal that describes the *action* of popping a value
from the stack, ``IND{>}`` then implies to push onto the stack.
@@ -399,7 +399,7 @@ These keywords are also operators:
`=`:tok:, `:`:tok:, `::`:tok: are not available as general operators; they
are used for other notational purposes.
-``*:`` is as a special case the two tokens `*`:tok: and `:`:tok:
+``*:`` is as a special case treated as the two tokens `*`:tok: and `:`:tok:
(to support ``var v*: T``).
diff --git a/doc/manual/pragmas.txt b/doc/manual/pragmas.txt
index 70fc4a914..a19f41a34 100644
--- a/doc/manual/pragmas.txt
+++ b/doc/manual/pragmas.txt
@@ -161,7 +161,7 @@ requires full qualification.
asmNoStackFrame pragma
----------------------
-A proc can be marked with the ``AsmNoStackFrame`` pragma to tell the compiler
+A proc can be marked with the ``asmNoStackFrame`` pragma to tell the compiler
it should not generate a stack frame for the proc. There are also no exit
statements like ``return result;`` generated and the generated C function is
declared as ``__declspec(naked)`` or ``__attribute__((naked))`` (depending on
diff --git a/doc/manual/procs.txt b/doc/manual/procs.txt
index ea6866845..181b1b1e5 100644
--- a/doc/manual/procs.txt
+++ b/doc/manual/procs.txt
@@ -61,14 +61,14 @@ Calling a procedure can be done in many different ways:
.. code-block:: nim
proc callme(x, y: int, s: string = "", c: char, b: bool = false) = ...
- # call with positional arguments # parameter bindings:
- callme(0, 1, "abc", '\t', true) # (x=0, y=1, s="abc", c='\t', b=true)
+ # call with positional arguments # parameter bindings:
+ callme(0, 1, "abc", '\t', true) # (x=0, y=1, s="abc", c='\t', b=true)
# call with named and positional arguments:
- callme(y=1, x=0, "abd", '\t') # (x=0, y=1, s="abd", c='\t', b=false)
+ callme(y=1, x=0, "abd", '\t') # (x=0, y=1, s="abd", c='\t', b=false)
# call with named arguments (order is not relevant):
- callme(c='\t', y=1, x=0) # (x=0, y=1, s="", c='\t', b=false)
+ callme(c='\t', y=1, x=0) # (x=0, y=1, s="", c='\t', b=false)
# call as a command statement: no () needed:
- callme 0, 1, "abc", '\t'
+ callme 0, 1, "abc", '\t' # (x=0, y=1, s="abc", c='\t', b=false)
A procedure may call itself recursively.
diff --git a/doc/manual/stmts.txt b/doc/manual/stmts.txt
index 318738063..fa1cac8e1 100644
--- a/doc/manual/stmts.txt
+++ b/doc/manual/stmts.txt
@@ -462,10 +462,10 @@ While statement
Example:
.. code-block:: nim
- echo "Please tell me your password: \n"
+ echo "Please tell me your password:"
var pw = readLine(stdin)
while pw != "12345":
- echo "Wrong password! Next try: \n"
+ echo "Wrong password! Next try:"
pw = readLine(stdin)
diff --git a/doc/manual/syntax.txt b/doc/manual/syntax.txt
index 89f8ca707..f3ace9f56 100644
--- a/doc/manual/syntax.txt
+++ b/doc/manual/syntax.txt
@@ -62,7 +62,8 @@ Precedence level Operators First charact
================ =============================================== ================== ===============
-Whether an operator is used a prefix operator is also affected by preceeding whitespace (this parsing change was introduced with version 0.13.0):
+Whether an operator is used a prefix operator is also affected by preceding
+whitespace (this parsing change was introduced with version 0.13.0):
.. code-block:: nim
echo $foo
diff --git a/doc/manual/templates.txt b/doc/manual/templates.txt
index be5c6fa18..f01a703cd 100644
--- a/doc/manual/templates.txt
+++ b/doc/manual/templates.txt
@@ -406,11 +406,11 @@ builtin can be used for that:
macro debug(n: varargs[typed]): untyped =
result = newNimNode(nnkStmtList, n)
- for i in 0..n.len-1:
+ for x in n:
# we can bind symbols in scope via 'bindSym':
- add(result, newCall(bindSym"write", bindSym"stdout", toStrLit(n[i])))
+ add(result, newCall(bindSym"write", bindSym"stdout", toStrLit(x)))
add(result, newCall(bindSym"write", bindSym"stdout", newStrLitNode(": ")))
- add(result, newCall(bindSym"writeLine", bindSym"stdout", n[i]))
+ add(result, newCall(bindSym"writeLine", bindSym"stdout", x))
var
a: array [0..10, int]
diff --git a/doc/manual/type_sections.txt b/doc/manual/type_sections.txt
index 34bbe6bd5..af761c77e 100644
--- a/doc/manual/type_sections.txt
+++ b/doc/manual/type_sections.txt
@@ -13,7 +13,7 @@ Example:
Sym = object # a symbol
name: string # the symbol's name
line: int # the line the symbol was declared in
- code: Node # the symbol's abstract syntax tree
+ code: Node # the symbol's abstract syntax tree
A type section begins with the ``type`` keyword. It contains multiple
type definitions. A type definition binds a type to a name. Type definitions
diff --git a/doc/manual/types.txt b/doc/manual/types.txt
index 1e2dc857f..02426e0d9 100644
--- a/doc/manual/types.txt
+++ b/doc/manual/types.txt
@@ -227,8 +227,8 @@ floating pointer values at compile time; this means expressions like
Boolean type
------------
The boolean type is named `bool`:idx: in Nim and can be one of the two
-pre-defined values ``true`` and ``false``. Conditions in while,
-if, elif, when statements need to be of type bool.
+pre-defined values ``true`` and ``false``. Conditions in ``while``,
+``if``, ``elif``, ``when``-statements need to be of type ``bool``.
This condition holds::
@@ -390,7 +390,10 @@ i-th *unichar*. The iterator ``runes`` from the `unicode module
cstring type
------------
-The ``cstring`` type represents a pointer to a zero-terminated char array
+
+The ``cstring`` type meaning `compatible string` is the native representation
+of a string for the compilation backend. For the C backend the ``cstring`` type
+represents a pointer to a zero-terminated char array
compatible to the type ``char*`` in Ansi C. Its primary purpose lies in easy
interfacing with C. The index operation ``s[i]`` means the i-th *char* of
``s``; however no bounds checking for ``cstring`` is performed making the
@@ -421,7 +424,6 @@ string from a cstring:
var cstr: cstring = str
var newstr: string = $cstr
-
Structured types
----------------
A variable of a structured type can hold multiple values at the same
@@ -607,7 +609,7 @@ is similar to the ``instanceof`` operator in Java.
age: int # no * means that the field is hidden
Student = ref object of Person # a student is a person
- id: int # with an id field
+ id: int # with an id field
var
student: Student
@@ -1260,4 +1262,4 @@ Is the same as:
However later versions of the language might change this to mean "infer the
parameters' types from the body". Then the above ``foo`` would be rejected as
-the parameters' types can not be infered from an empty ``discard`` statement.
+the parameters' types can not be inferred from an empty ``discard`` statement.
diff --git a/doc/nimsuggest.rst b/doc/nimsuggest.rst
index 2b52196b9..4da39b5d0 100644
--- a/doc/nimsuggest.rst
+++ b/doc/nimsuggest.rst
@@ -80,7 +80,7 @@ a location. A query location consists of:
``col``
An integer with the column you are going to query. For the
- compiler columns start at **1**.
+ compiler columns start at **0**.
Definitions
@@ -160,7 +160,7 @@ tab characters (``\t``). The values of each column are:
6. Line where the symbol is located in the file. Lines start to
count at **1**.
7. Column where the symbol is located in the file. Columns start
- to count at **1**.
+ to count at **0**.
8. Docstring for the symbol if available or the empty string. To
differentiate the docstring from end of answer,
the docstring is always provided enclosed in double quotes, and
diff --git a/doc/sets_fragment.txt b/doc/sets_fragment.txt
index a6fe7555d..435807e1a 100644
--- a/doc/sets_fragment.txt
+++ b/doc/sets_fragment.txt
@@ -1,5 +1,5 @@
-The set type models the mathematical notion of a set. The set's
-basetype can only be an ordinal type of a certain size, namely:
+The set type models the mathematical notion of a set. The set's basetype can
+only be an ordinal type of a certain size, namely:
* ``int8``-``int16``
* ``uint8``/``byte``-``uint16``
* ``char``
diff --git a/doc/tut2.rst b/doc/tut2.rst
index 3f94325ff..845feb6d1 100644
--- a/doc/tut2.rst
+++ b/doc/tut2.rst
@@ -674,17 +674,18 @@ variable number of arguments:
macro debug(n: varargs[expr]): stmt =
# `n` is a Nim AST that contains a list of expressions;
- # this macro returns a list of statements:
+ # this macro returns a list of statements (n is passed for proper line
+ # information):
result = newNimNode(nnkStmtList, n)
# iterate over any argument that is passed to this macro:
- for i in 0..n.len-1:
+ for x in n:
# add a call to the statement list that writes the expression;
# `toStrLit` converts an AST to its string representation:
- result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i])))
+ result.add(newCall("write", newIdentNode("stdout"), toStrLit(x)))
# add a call to the statement list that writes ": "
result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": ")))
# add a call to the statement list that writes the expressions value:
- result.add(newCall("writeLine", newIdentNode("stdout"), n[i]))
+ result.add(newCall("writeLine", newIdentNode("stdout"), x))
var
a: array[0..10, int]
diff --git a/finish.nim b/finish.nim
new file mode 100644
index 000000000..34580e171
--- /dev/null
+++ b/finish.nim
@@ -0,0 +1,158 @@
+
+# -------------- post unzip steps ---------------------------------------------
+
+import strutils, os, osproc
+
+when defined(windows):
+ import registry
+
+ proc askBool(m: string): bool =
+ stdout.write m
+ while true:
+ let answer = stdin.readLine().normalize
+ case answer
+ of "y", "yes":
+ return true
+ of "n", "no":
+ return false
+ else:
+ echo "Please type 'y' or 'n'"
+
+ proc askNumber(m: string; a, b: int): int =
+ stdout.write m
+ stdout.write " [" & $a & ".." & $b & "] "
+ while true:
+ let answer = stdin.readLine()
+ try:
+ result = parseInt answer
+ if result < a or result > b:
+ raise newException(ValueError, "number out of range")
+ break
+ except ValueError:
+ echo "Please type in a number between ", a, " and ", b
+
+ proc patchConfig(mingw: string) =
+ const
+ cfgFile = "config/nim.cfg"
+ lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
+ replacePattern = """gcc.path = r"$1""""
+ try:
+ let cfg = readFile(cfgFile)
+ let newCfg = cfg.replace(lookFor, replacePattern % mingw)
+ if newCfg == cfg:
+ echo "Could not patch 'config/nim.cfg' [Error]"
+ echo "Reason: patch substring not found:"
+ echo lookFor
+ else:
+ writeFile(cfgFile, newCfg)
+ except IOError:
+ echo "Could not access 'config/nim.cfg' [Error]"
+
+ proc addToPathEnv(e: string) =
+ let p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
+ let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
+ setUnicodeValue(r"Environment", "Path", p & ";" & x, HKEY_CURRENT_USER)
+
+ proc createShortcut(src, dest: string; icon = "") =
+ var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
+ ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
+ if icon.len != 0:
+ cmd.add " \"" & icon & "\" 0"
+ discard execShellCmd(cmd)
+
+ proc createStartMenuEntry() =
+ let appdata = getEnv("APPDATA")
+ if appdata.len == 0: return
+ let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
+ NimVersion
+ if dirExists(dest): return
+ if askBool("Would like to add Nim-" & NimVersion &
+ " to your start menu? (y/n) "):
+ createDir(dest)
+ createShortcut(getCurrentDir() / "start.bat", dest / "Nim",
+ getCurrentDir() / r"icons\nim.ico")
+ if fileExists("doc/overview.html"):
+ createShortcut(getCurrentDir() / "doc" / "overview.html",
+ dest / "Overview")
+ if dirExists(r"dist\aporia-0.4.0"):
+ createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
+ dest / "Aporia")
+
+ proc checkGccArch(mingw: string): bool =
+ let gccExe = mingw / r"gcc.exe"
+ if fileExists(gccExe):
+ try:
+ let arch = execProcess(gccExe, ["-dumpmachine"], nil, {poStdErrToStdOut,
+ poUsePath})
+ when hostCPU == "i386":
+ result = arch.startsWith("i686-")
+ elif hostCPU == "amd64":
+ result = arch.startsWith("x86_64-")
+ else:
+ {.error: "Unknown CPU for Windows.".}
+ except OSError, IOError:
+ result = false
+
+ proc tryDirs(dirs: varargs[string]): string =
+ let bits = $(sizeof(pointer)*8)
+ for d in dirs:
+ if dirExists d:
+ let x = expandFilename(d / "bin")
+ if checkGccArch(x): return x
+ elif dirExists(d & bits):
+ let x = expandFilename((d & bits) / "bin")
+ if checkGccArch(x): return x
+
+proc main() =
+ when defined(windows):
+ let desiredPath = expandFilename(getCurrentDir() / "bin")
+ let p = getUnicodeValue(r"Environment", "Path",
+ HKEY_CURRENT_USER)
+ var alreadyInPath = false
+ var mingWchoices: seq[string] = @[]
+ for x in p.split(';'):
+ let y = expandFilename(if x[0] == '"' and x[^1] == '"':
+ substr(x, 1, x.len-2) else: x)
+ if y == desiredPath: alreadyInPath = true
+ if y.toLowerAscii.contains("mingw"):
+ if dirExists(y) and checkGccArch(y):
+ mingWchoices.add y
+
+ if alreadyInPath:
+ echo "bin/nim.exe is already in your PATH [Skipping]"
+ else:
+ if askBool("nim.exe is not in your PATH environment variable.\n" &
+ " Should it be added permanently? (y/n) "):
+ addToPathEnv(desiredPath)
+ if mingWchoices.len == 0:
+ # No mingw in path, so try a few locations:
+ let alternative = tryDirs("dist/mingw", "../mingw", r"C:\mingw")
+ if alternative.len == 0:
+ echo "No MingW found in PATH and no candidate found " &
+ " in the standard locations [Error]"
+ else:
+ if askBool("Found a MingW directory that is not in your PATH.\n" &
+ alternative &
+ "\nShould it be added to your PATH permanently? (y/n) "):
+ addToPathEnv(alternative)
+ elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
+ patchConfig(alternative)
+ elif mingWchoices.len == 1:
+ if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
+ "Do you want to patch Nim's config to use this?\n" &
+ "(Not required since it's in your PATH!) (y/n) "):
+ patchConfig(mingWchoices[0])
+ else:
+ echo "Multiple MingW installations found: "
+ for i in 0..high(mingWchoices):
+ echo "[", i, "] ", mingWchoices[i]
+ if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
+ let idx = askNumber("Which one do you want to use for Nim? ",
+ 1, len(mingWchoices))
+ patchConfig(mingWchoices[idx-1])
+ createStartMenuEntry()
+ else:
+ echo("Add ", getCurrentDir(), "/bin to your PATH...")
+
+when isMainModule:
+ main()
diff --git a/install.txt b/install.txt
index 67be2221b..833fbf0fb 100644
--- a/install.txt
+++ b/install.txt
@@ -30,6 +30,15 @@ There are also ``install.sh`` and ``deinstall.sh`` scripts for distributing
the files over the UNIX hierarchy. However, updating your Nim installation
is more cumbersome then.
+To complete the installation you should also build Nim's tools like
+``nimsuggest``, ``nimble`` or ``nimgrep`` via::
+
+ nim e install_tools.nims
+
+Note that these tools should also end up in your ``PATH`` so adding
+``$your_install_dir/bin/nim`` to your ``PATH`` is preferred over the symlink
+solution.
+
Installation on the Macintosh
-----------------------------
diff --git a/install_nimble.nims b/install_nimble.nims
index 5d028726b..05024c046 100644
--- a/install_nimble.nims
+++ b/install_nimble.nims
@@ -16,4 +16,8 @@ mkDir "bin/nimblepkg"
for file in listFiles("nimble" & $id & "/src/nimblepkg/"):
cpFile file, "bin/nimblepkg/" & file.extractFilename
-mvFile "nimble" & $id & "/src/nimble".toExe, "bin/nimble".toExe
+try:
+ mvFile "nimble" & $id & "/src/nimble".toExe, "bin/nimble".toExe
+except OSError:
+ cpFile "nimble" & $id & "/src/nimble".toExe, "bin/nimble".toExe
+
diff --git a/install_tools.nims b/install_tools.nims
new file mode 100644
index 000000000..e211ff491
--- /dev/null
+++ b/install_tools.nims
@@ -0,0 +1,18 @@
+
+import ospaths
+
+mode = ScriptMode.Verbose
+
+if not dirExists"dist/nimble":
+ echo "[Error] This script only works for the tarball."
+else:
+ let nimbleExe = "./bin/nimble".toExe
+ selfExec "c --noNimblePath -p:compiler -o:" & nimbleExe &
+ " dist/nimble/src/nimble.nim"
+
+ let nimsugExe = "./bin/nimsuggest".toExe
+ selfExec "c --noNimblePath -p:compiler -o:" & nimsugExe &
+ " dist/nimsuggest/nimsuggest.nim"
+
+ let nimgrepExe = "./bin/nimgrep".toExe
+ selfExec "c -o:./bin/nimgrep tools/nimgrep.nim"
diff --git a/koch.nim b/koch.nim
index 04f6a4e4e..25c02937f 100644
--- a/koch.nim
+++ b/koch.nim
@@ -20,11 +20,6 @@ import
const VersionAsString = system.NimVersion #"0.10.2"
-when defined(withUpdate):
- import httpclient
-when defined(haveZipLib):
- import zipfiles
-
const
HelpText = """
+-----------------------------------------------------------------+
@@ -40,7 +35,7 @@ Options:
--help, -h shows this help and quits
Possible Commands:
boot [options] bootstraps with given command line options
- install [bindir] installs to given directory; Unix only!
+ distrohelper [bindir] helper for distro packagers
geninstall generate ./install.sh; Unix only!
testinstall test tar.xz package; Unix only! Only for devs!
clean cleans Nim project; removes generated files
@@ -52,8 +47,6 @@ Possible Commands:
xz builds the installation XZ package
nsis [options] builds the NSIS Setup installer (for Windows)
tests [options] run the testsuite
- update updates nim to the latest version from github
- (compile koch with -d:withUpdate to enable)
temp options creates a temporary compiler for testing
winrelease creates a release (for coredevs only)
Boot options:
@@ -69,7 +62,10 @@ Web options:
build the official docs, use UA-48159761-1
"""
-proc exe(f: string): string = return addFileExt(f, ExeExt)
+proc exe(f: string): string =
+ result = addFileExt(f, ExeExt)
+ when defined(windows):
+ result = result.replace('/','\\')
proc findNim(): string =
var nim = "nim".exe
@@ -123,6 +119,8 @@ proc testUnixInstall() =
execCleanPath("./koch boot -d:release", destDir / "bin")
# check the docs build:
execCleanPath("./koch web", destDir / "bin")
+ # check nimble builds:
+ execCleanPath("./bin/nim e install_tools.nims")
# check the tests work:
execCleanPath("./koch tests", destDir / "bin")
else:
@@ -143,13 +141,13 @@ proc copyExe(source, dest: string) =
inclFilePermissions(dest, {fpUserExec})
const
- compileNimInst = "-d:useLibzipSrc tools/niminst/niminst"
+ compileNimInst = "tools/niminst/niminst"
proc csource(args: string) =
exec("$4 cc $1 -r $3 --var:version=$2 --var:mingw=none csource --main:compiler/nim.nim compiler/installer.ini $1" %
[args, VersionAsString, compileNimInst, findNim()])
-proc bundleNimble() =
+proc bundleNimbleSrc() =
if dirExists("dist/nimble/.git"):
exec("git --git-dir dist/nimble/.git pull")
else:
@@ -157,20 +155,38 @@ proc bundleNimble() =
let tags = execProcess("git --git-dir dist/nimble/.git tag -l v*").splitLines
let tag = tags[^1]
exec("git --git-dir dist/nimble/.git checkout " & tag)
+
+proc bundleNimbleExe() =
+ bundleNimbleSrc()
# now compile Nimble and copy it to $nim/bin for the installer.ini
# to pick it up:
exec(findNim() & " c dist/nimble/src/nimble.nim")
copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe)
+proc bundleNimsuggest(buildExe: bool) =
+ if dirExists("dist/nimsuggest/.git"):
+ exec("git --git-dir dist/nimsuggest/.git pull")
+ else:
+ exec("git clone https://github.com/nim-lang/nimsuggest.git dist/nimsuggest")
+ if buildExe:
+ exec(findNim() & " c --noNimblePath -p:compiler dist/nimsuggest/nimsuggest.nim")
+ copyExe("dist/nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe)
+
+proc bundleFinishExe() =
+ exec(findNim() & " c finish.nim")
+
proc zip(args: string) =
- bundleNimble()
+ bundleNimbleSrc()
+ bundleNimsuggest(false)
+ bundleFinishExe()
exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
[VersionAsString, compileNimInst, findNim()])
exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" %
["tools/niminst/niminst".exe, VersionAsString])
proc xz(args: string) =
- bundleNimble()
+ bundleNimbleSrc()
+ bundleNimsuggest(false)
exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
[VersionAsString, compileNimInst, findNim()])
exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim xz compiler/installer.ini" %
@@ -181,7 +197,9 @@ proc buildTool(toolname, args: string) =
copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe)
proc nsis(args: string) =
- bundleNimble()
+ bundleNimbleExe()
+ bundleNimsuggest(true)
+ bundleFinishExe()
# make sure we have generated the niminst executables:
buildTool("tools/niminst/niminst", args)
#buildTool("tools/nimgrep", args)
@@ -195,11 +213,8 @@ proc geninstall(args="") =
exec("$# cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
[findNim(), compileNimInst, VersionAsString, args])
-proc install(args: string) =
- geninstall()
- exec("sh ./install.sh $#" % args)
-
proc web(args: string) =
+ exec("$# js tools/dochack/dochack.nim" % findNim())
exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
[findNim(), args, VersionAsString])
@@ -244,11 +259,13 @@ proc boot(args: string) =
var finalDest = "bin" / "nim".exe
# default to use the 'c' command:
let bootOptions = if args.len == 0 or args.startsWith("-"): "c" else: ""
+ let smartNimcache = if "release" in args: "rnimcache" else: "dnimcache"
copyExe(findStartNim(), 0.thVersion)
for i in 0..2:
echo "iteration: ", i+1
- exec i.thVersion & " $# $# compiler" / "nim.nim" % [bootOptions, args]
+ exec i.thVersion & " $# $# --nimcache:$# compiler" / "nim.nim" % [bootOptions, args,
+ smartNimcache]
if sameFileContent(output, i.thVersion):
copyExe(output, finalDest)
echo "executables are equal: SUCCESS!"
@@ -273,7 +290,7 @@ proc cleanAux(dir: string) =
for kind, path in walkDir(dir):
case kind
of pcFile:
- var (dir, name, ext) = splitFile(path)
+ var (_, name, ext) = splitFile(path)
if ext == "" or cleanExt.contains(ext):
if not ignore.contains(name):
echo "removing: ", path
@@ -302,99 +319,14 @@ proc clean(args: string) =
echo "removing dir: ", path
removeDir(path)
-# -------------- update -------------------------------------------------------
-
-when defined(withUpdate):
- when defined(windows):
- {.warning: "Windows users: Make sure to run 'koch update' in Bash.".}
-
- proc update(args: string) =
- when defined(windows):
- echo("Windows users: Make sure to be running this in Bash. ",
- "If you aren't, press CTRL+C now.")
-
- var thisDir = getAppDir()
- var git = findExe("git")
- echo("Checking for git repo and git executable...")
- if existsDir(thisDir & "/.git") and git != "":
- echo("Git repo found!")
- # use git to download latest source
- echo("Checking for updates...")
- discard startCmd(git & " fetch origin master")
- var procs = startCmd(git & " diff origin/master master")
- var errcode = procs.waitForExit()
- var output = readLine(procs.outputStream)
- echo(output)
- if errcode == 0:
- if output == "":
- # No changes
- echo("No update. Exiting...")
- return
- else:
- echo("Fetching updates from repo...")
- var pullout = execCmdEx(git & " pull origin master")
- if pullout[1] != 0:
- quit("An error has occurred.")
- else:
- if pullout[0].startsWith("Already up-to-date."):
- quit("No new changes fetched from the repo. " &
- "Local branch must be ahead of it. Exiting...")
- else:
- quit("An error has occurred.")
-
- else:
- echo("No repo or executable found!")
- when defined(haveZipLib):
- echo("Falling back.. Downloading source code from repo...")
- # use dom96's httpclient to download zip
- downloadFile("https://github.com/Araq/Nim/zipball/master",
- thisDir / "update.zip")
- try:
- echo("Extracting source code from archive...")
- var zip: TZipArchive
- discard open(zip, thisDir & "/update.zip", fmRead)
- extractAll(zip, thisDir & "/")
- except:
- quit("Error reading archive.")
- else:
- quit("No failback available. Exiting...")
-
- echo("Starting update...")
- boot(args)
- echo("Update complete!")
-
# -------------- builds a release ---------------------------------------------
-proc run7z(platform: string, patterns: varargs[string]) =
- const tmpDir = "nim-" & VersionAsString
- createDir tmpDir
- try:
- for pattern in patterns:
- for f in walkFiles(pattern):
- if "nimcache" notin f:
- copyFile(f, tmpDir / f)
- exec("7z a -tzip $1-$2.zip $1" % [tmpDir, platform])
- finally:
- removeDir tmpDir
-
proc winRelease() =
- boot(" -d:release")
- #buildTool("tools/niminst/niminst", " -d:release")
- buildTool("tools/nimgrep", " -d:release")
- buildTool("compiler/nimfix/nimfix", " -d:release")
- buildTool("compiler/nimsuggest/nimsuggest", " -d:release")
-
- #run7z("win32", "bin/nim.exe", "bin/c2nim.exe", "bin/nimgrep.exe",
- # "bin/nimfix.exe",
- # "bin/nimble.exe", "bin/*.dll",
- # "config", "dist/*.dll", "examples", "lib",
- # "readme.txt", "contributors.txt", "copying.txt")
-
- # second step: XXX build 64 bit version
+ exec(r"call ci\nsis_build.bat " & VersionAsString)
# -------------- tests --------------------------------------------------------
-template `|`(a, b): expr = (if a.len > 0: a else: b)
+template `|`(a, b): string = (if a.len > 0: a else: b)
proc tests(args: string) =
# we compile the tester with taintMode:on to have a basic
@@ -444,14 +376,9 @@ of cmdArgument:
of "xz": xz(op.cmdLineRest)
of "nsis": nsis(op.cmdLineRest)
of "geninstall": geninstall(op.cmdLineRest)
- of "install": install(op.cmdLineRest)
+ of "distrohelper": geninstall()
of "testinstall": testUnixInstall()
of "test", "tests": tests(op.cmdLineRest)
- of "update":
- when defined(withUpdate):
- update(op.cmdLineRest)
- else:
- quit "this Koch has not been compiled with -d:withUpdate"
of "temp": temp(op.cmdLineRest)
of "winrelease": winRelease()
else: showHelp()
diff --git a/lib/arch/arch.nim b/lib/arch/arch.nim
index a11bfb21f..c8ae3da1c 100644
--- a/lib/arch/arch.nim
+++ b/lib/arch/arch.nim
@@ -6,6 +6,9 @@
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
+# Architecture-specific optimizations and features.
+# arch.nim can be imported by only a subset of the
+# architectures supported by Nim.
when defined(windows):
const
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index 4296cb0ae..19452b4a8 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -333,7 +333,7 @@ proc parseStmt*(s: string): NimNode {.noSideEffect, compileTime.} =
let x = internalErrorFlag()
if x.len > 0: raise newException(ValueError, x)
-proc getAst*(macroOrTemplate: expr): NimNode {.magic: "ExpandToAst", noSideEffect.}
+proc getAst*(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEffect.}
## Obtains the AST nodes returned from a macro or template invocation.
## Example:
##
@@ -342,7 +342,7 @@ proc getAst*(macroOrTemplate: expr): NimNode {.magic: "ExpandToAst", noSideEffec
## macro FooMacro() =
## var ast = getAst(BarTemplate())
-proc quote*(bl: stmt, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
+proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
## Quasi-quoting operator.
## Accepts an expression or a block and returns the AST that represents it.
## Within the quoted AST, you are able to interpolate NimNode expressions
@@ -663,7 +663,7 @@ proc copyChildrenTo*(src, dest: NimNode) {.compileTime.}=
for i in 0 .. < src.len:
dest.add src[i].copyNimTree
-template expectRoutine(node: NimNode): stmt =
+template expectRoutine(node: NimNode) =
expectKind(node, RoutineNodes)
proc name*(someProc: NimNode): NimNode {.compileTime.} =
@@ -870,7 +870,7 @@ proc hasArgOfName* (params: NimNode; name: string): bool {.compiletime.}=
## Search nnkFormalParams for an argument.
assert params.kind == nnkFormalParams
for i in 1 .. $1:
\n",
+ let keyword = symbols[i].keyword
+ let cleaned_keyword = keyword.escapeLink
+ result.addf("
\n")
+ title.link & "\">" & title.keyword & "\n" & tocList & "\n")
proc generateDocumentationJumps(docs: IndexedDocs): string =
## Returns a plain list of hyperlinks to documentation TOCs in HTML.
@@ -783,7 +794,7 @@ proc renderImage(d: PDoc, n: PRstNode, result: var string) =
if s.valid: dispA(d.target, options, " align=\"$1\"", "", [strip(s)])
if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options])
-
+
if arg.valid:
let htmlOut = if isObject:
""
diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim
index bd69b2328..b96e88b6c 100644
--- a/lib/posix/posix.nim
+++ b/lib/posix/posix.nim
@@ -1694,6 +1694,8 @@ var
INADDR_ANY* {.importc, header: "".}: InAddrScalar
## IPv4 local host address.
+ INADDR_LOOPBACK* {.importc, header: "".}: InAddrScalar
+ ## IPv4 loopback address.
INADDR_BROADCAST* {.importc, header: "".}: InAddrScalar
## IPv4 broadcast address.
@@ -2653,6 +2655,12 @@ proc poll*(a1: ptr TPollfd, a2: Tnfds, a3: int): cint {.
proc realpath*(name, resolved: cstring): cstring {.
importc: "realpath", header: "".}
+proc mkstemp*(tmpl: cstring): cint {.importc, header: "".}
+ ## Create a temporary file.
+ ##
+ ## **Warning**: The `tmpl` argument is written to by `mkstemp` and thus
+ ## can't be a string literal. If in doubt copy the string before passing it.
+
proc utimes*(path: cstring, times: ptr array[2, Timeval]): int {.
importc: "utimes", header: "".}
## Sets file access and modification times.
diff --git a/lib/posix/termios.nim b/lib/posix/termios.nim
index c3934c6a9..1fbccba9c 100644
--- a/lib/posix/termios.nim
+++ b/lib/posix/termios.nim
@@ -259,3 +259,13 @@ proc tcFlow*(fd: cint; action: cint): cint {.importc: "tcflow",
# Get process group ID for session leader for controlling terminal FD.
proc tcGetSid*(fd: cint): Pid {.importc: "tcgetsid", header: "".}
+
+# Window size ioctl. Should work on on any Unix that xterm has been ported to.
+var TIOCGWINSZ*{.importc, header: "".}: culong
+
+type IOctl_WinSize* {.importc: "struct winsize", header: "",
+ final, pure.} = object
+ ws_row*, ws_col*, ws_xpixel*, ws_ypixel*: cushort
+
+proc ioctl*(fd: cint, request: culong, reply: ptr IOctl_WinSize): int {.
+ importc: "ioctl", header: "", varargs.}
diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim
index b83daf245..739cdc16b 100644
--- a/lib/pure/algorithm.nim
+++ b/lib/pure/algorithm.nim
@@ -179,7 +179,7 @@ proc sort*[T](a: var openArray[T],
## sort(myStrArray, system.cmp)
##
## You can inline adhoc comparison procs with the `do notation
- ## `_. Example:
+ ## `_. Example:
##
## .. code-block:: nim
##
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index 79bc1b96d..8336da1fb 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -9,7 +9,7 @@
include "system/inclrtl"
-import os, oids, tables, strutils, macros, times, heapqueue
+import os, oids, tables, strutils, times, heapqueue
import nativesockets, net, queues
@@ -105,6 +105,36 @@ export Port, SocketFlag
## be used to await a procedure returning a ``Future[void]``:
## ``await socket.send("foobar")``.
##
+## If an awaited future completes with an error, then ``await`` will re-raise
+## this error. To avoid this, you can use the ``yield`` keyword instead of
+## ``await``. The following section shows different ways that you can handle
+## exceptions in async procs.
+##
+## Handling Exceptions
+## ~~~~~~~~~~~~~~~~~~~
+##
+## The most reliable way to handle exceptions is to use ``yield`` on a future
+## then check the future's ``failed`` property. For example:
+##
+## .. code-block:: Nim
+## var future = sock.recv(100)
+## yield future
+## if future.failed:
+## # Handle exception
+##
+## The ``async`` procedures also offer limited support for the try statement.
+##
+## .. code-block:: Nim
+## try:
+## let data = await sock.recv(100)
+## echo("Received ", data)
+## except:
+## # Handle exception
+##
+## Unfortunately the semantics of the try statement may not always be correct,
+## and occassionally the compilation may fail altogether.
+## As such it is better to use the former style when possible.
+##
## Discarding futures
## ------------------
##
@@ -126,279 +156,10 @@ export Port, SocketFlag
## * Can't await in a ``except`` body
## * Forward declarations for async procs are broken,
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
-## * FutureVar[T] needs to be completed manually.
# TODO: Check if yielded future is nil and throw a more meaningful exception
-# -- Futures
-
-type
- FutureBase* = ref object of RootObj ## Untyped future.
- cb: proc () {.closure,gcsafe.}
- finished: bool
- error*: ref Exception ## Stored exception
- errorStackTrace*: string
- when not defined(release):
- stackTrace: string ## For debugging purposes only.
- id: int
- fromProc: string
-
- Future*[T] = ref object of FutureBase ## Typed future.
- value: T ## Stored value
-
- FutureVar*[T] = distinct Future[T]
-
- FutureError* = object of Exception
- cause*: FutureBase
-
-{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
-
-when not defined(release):
- var currentID = 0
-
-proc callSoon*(cbproc: proc ()) {.gcsafe.}
-
-proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
- ## Creates a new future.
- ##
- ## Specifying ``fromProc``, which is a string specifying the name of the proc
- ## that this future belongs to, is a good habit as it helps with debugging.
- new(result)
- result.finished = false
- when not defined(release):
- result.stackTrace = getStackTrace()
- result.id = currentID
- result.fromProc = fromProc
- currentID.inc()
-
-proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
- ## Create a new ``FutureVar``. This Future type is ideally suited for
- ## situations where you want to avoid unnecessary allocations of Futures.
- ##
- ## Specifying ``fromProc``, which is a string specifying the name of the proc
- ## that this future belongs to, is a good habit as it helps with debugging.
- result = FutureVar[T](newFuture[T](fromProc))
-
-proc clean*[T](future: FutureVar[T]) =
- ## Resets the ``finished`` status of ``future``.
- Future[T](future).finished = false
- Future[T](future).error = nil
-
-proc checkFinished[T](future: Future[T]) =
- ## Checks whether `future` is finished. If it is then raises a
- ## ``FutureError``.
- when not defined(release):
- if future.finished:
- var msg = ""
- msg.add("An attempt was made to complete a Future more than once. ")
- msg.add("Details:")
- msg.add("\n Future ID: " & $future.id)
- msg.add("\n Created in proc: " & future.fromProc)
- msg.add("\n Stack trace to moment of creation:")
- msg.add("\n" & indent(future.stackTrace.strip(), 4))
- when T is string:
- msg.add("\n Contents (string): ")
- msg.add("\n" & indent(future.value.repr, 4))
- msg.add("\n Stack trace to moment of secondary completion:")
- msg.add("\n" & indent(getStackTrace().strip(), 4))
- var err = newException(FutureError, msg)
- err.cause = future
- raise err
-
-proc complete*[T](future: Future[T], val: T) =
- ## Completes ``future`` with value ``val``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- assert(future.error == nil)
- future.value = val
- future.finished = true
- if future.cb != nil:
- future.cb()
-
-proc complete*(future: Future[void]) =
- ## Completes a void ``future``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- assert(future.error == nil)
- future.finished = true
- if future.cb != nil:
- future.cb()
-
-proc complete*[T](future: FutureVar[T]) =
- ## Completes a ``FutureVar``.
- template fut: expr = Future[T](future)
- checkFinished(fut)
- assert(fut.error == nil)
- fut.finished = true
- if fut.cb != nil:
- fut.cb()
-
-proc fail*[T](future: Future[T], error: ref Exception) =
- ## Completes ``future`` with ``error``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- future.finished = true
- future.error = error
- future.errorStackTrace =
- if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
- if future.cb != nil:
- future.cb()
- else:
- # This is to prevent exceptions from being silently ignored when a future
- # is discarded.
- # TODO: This may turn out to be a bad idea.
- # Turns out this is a bad idea.
- #raise error
- discard
-
-proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
- ## Sets the callback proc to be called when the future completes.
- ##
- ## If future has already completed then ``cb`` will be called immediately.
- ##
- ## **Note**: You most likely want the other ``callback`` setter which
- ## passes ``future`` as a param to the callback.
- future.cb = cb
- if future.finished:
- callSoon(future.cb)
-
-proc `callback=`*[T](future: Future[T],
- cb: proc (future: Future[T]) {.closure,gcsafe.}) =
- ## Sets the callback proc to be called when the future completes.
- ##
- ## If future has already completed then ``cb`` will be called immediately.
- future.callback = proc () = cb(future)
-
-proc injectStacktrace[T](future: Future[T]) =
- # TODO: Come up with something better.
- when not defined(release):
- var msg = ""
- msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
-
- if not future.errorStackTrace.isNil and future.errorStackTrace != "":
- msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
- else:
- msg.add("\n Empty or nil stack trace.")
- future.error.msg.add(msg)
-
-proc read*[T](future: Future[T]): T =
- ## Retrieves the value of ``future``. Future must be finished otherwise
- ## this function will fail with a ``ValueError`` exception.
- ##
- ## If the result of the future is an error then that error will be raised.
- if future.finished:
- if future.error != nil:
- injectStacktrace(future)
- raise future.error
- when T isnot void:
- return future.value
- else:
- # TODO: Make a custom exception type for this?
- raise newException(ValueError, "Future still in progress.")
-
-proc readError*[T](future: Future[T]): ref Exception =
- ## Retrieves the exception stored in ``future``.
- ##
- ## An ``ValueError`` exception will be thrown if no exception exists
- ## in the specified Future.
- if future.error != nil: return future.error
- else:
- raise newException(ValueError, "No error in future.")
-
-proc mget*[T](future: FutureVar[T]): var T =
- ## Returns a mutable value stored in ``future``.
- ##
- ## Unlike ``read``, this function will not raise an exception if the
- ## Future has not been finished.
- result = Future[T](future).value
-
-proc finished*[T](future: Future[T]): bool =
- ## Determines whether ``future`` has completed.
- ##
- ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
- future.finished
-
-proc failed*(future: FutureBase): bool =
- ## Determines whether ``future`` completed with an error.
- return future.error != nil
-
-proc asyncCheck*[T](future: Future[T]) =
- ## Sets a callback on ``future`` which raises an exception if the future
- ## finished with an error.
- ##
- ## This should be used instead of ``discard`` to discard void futures.
- future.callback =
- proc () =
- if future.failed:
- injectStacktrace(future)
- raise future.error
-
-proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
- ## Returns a future which will complete once both ``fut1`` and ``fut2``
- ## complete.
- var retFuture = newFuture[void]("asyncdispatch.`and`")
- fut1.callback =
- proc () =
- if fut2.finished: retFuture.complete()
- fut2.callback =
- proc () =
- if fut1.finished: retFuture.complete()
- return retFuture
-
-proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
- ## Returns a future which will complete once either ``fut1`` or ``fut2``
- ## complete.
- var retFuture = newFuture[void]("asyncdispatch.`or`")
- proc cb() =
- if not retFuture.finished: retFuture.complete()
- fut1.callback = cb
- fut2.callback = cb
- return retFuture
-
-proc all*[T](futs: varargs[Future[T]]): auto =
- ## Returns a future which will complete once
- ## all futures in ``futs`` complete.
- ##
- ## If the awaited futures are not ``Future[void]``, the returned future
- ## will hold the values of all awaited futures in a sequence.
- ##
- ## If the awaited futures *are* ``Future[void]``,
- ## this proc returns ``Future[void]``.
-
- when T is void:
- var
- retFuture = newFuture[void]("asyncdispatch.all")
- completedFutures = 0
-
- let totalFutures = len(futs)
-
- for fut in futs:
- fut.callback = proc(f: Future[T]) =
- inc(completedFutures)
-
- if completedFutures == totalFutures:
- retFuture.complete()
-
- return retFuture
-
- else:
- var
- retFuture = newFuture[seq[T]]("asyncdispatch.all")
- retValues = newSeq[T](len(futs))
- completedFutures = 0
-
- for i, fut in futs:
- proc setCallback(i: int) =
- fut.callback = proc(f: Future[T]) =
- retValues[i] = f.read()
- inc(completedFutures)
-
- if completedFutures == len(retValues):
- retFuture.complete(retValues)
-
- setCallback(i)
-
- return retFuture
+include includes/asyncfutures
type
PDispatcherBase = ref object of RootRef
@@ -500,48 +261,49 @@ when defined(windows) or defined(nimdoc):
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
- let at = p.adjustedTimeout(timeout)
- var llTimeout =
- if at == -1: winlean.INFINITE
- else: at.int32
+ if p.handles.len != 0:
+ let at = p.adjustedTimeout(timeout)
+ var llTimeout =
+ if at == -1: winlean.INFINITE
+ else: at.int32
- var lpNumberOfBytesTransferred: Dword
- var lpCompletionKey: ULONG_PTR
- var customOverlapped: PCustomOverlapped
- let res = getQueuedCompletionStatus(p.ioPort,
- addr lpNumberOfBytesTransferred, addr lpCompletionKey,
- cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
+ var lpNumberOfBytesTransferred: Dword
+ var lpCompletionKey: ULONG_PTR
+ var customOverlapped: PCustomOverlapped
+ let res = getQueuedCompletionStatus(p.ioPort,
+ addr lpNumberOfBytesTransferred, addr lpCompletionKey,
+ cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
- # http://stackoverflow.com/a/12277264/492186
- # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
- if res:
- # This is useful for ensuring the reliability of the overlapped struct.
- assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
-
- customOverlapped.data.cb(customOverlapped.data.fd,
- lpNumberOfBytesTransferred, OSErrorCode(-1))
-
- # If cell.data != nil, then system.protect(rawEnv(cb)) was called,
- # so we need to dispose our `cb` environment, because it is not needed
- # anymore.
- if customOverlapped.data.cell.data != nil:
- system.dispose(customOverlapped.data.cell)
-
- GC_unref(customOverlapped)
- else:
- let errCode = osLastError()
- if customOverlapped != nil:
+ # http://stackoverflow.com/a/12277264/492186
+ # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
+ if res:
+ # This is useful for ensuring the reliability of the overlapped struct.
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+
customOverlapped.data.cb(customOverlapped.data.fd,
- lpNumberOfBytesTransferred, errCode)
+ lpNumberOfBytesTransferred, OSErrorCode(-1))
+
+ # If cell.data != nil, then system.protect(rawEnv(cb)) was called,
+ # so we need to dispose our `cb` environment, because it is not needed
+ # anymore.
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
+
GC_unref(customOverlapped)
else:
- if errCode.int32 == WAIT_TIMEOUT:
- # Timed out
- discard
- else: raiseOSError(errCode)
+ let errCode = osLastError()
+ if customOverlapped != nil:
+ assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+ customOverlapped.data.cb(customOverlapped.data.fd,
+ lpNumberOfBytesTransferred, errCode)
+ if customOverlapped.data.cell.data != nil:
+ system.dispose(customOverlapped.data.cell)
+ GC_unref(customOverlapped)
+ else:
+ if errCode.int32 == WAIT_TIMEOUT:
+ # Timed out
+ discard
+ else: raiseOSError(errCode)
# Timer processing.
processTimers(p)
@@ -749,8 +511,8 @@ when defined(windows) or defined(nimdoc):
retFuture.complete("")
return retFuture
- proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
- flags = {SocketFlag.SafeDisconn}): Future[int] =
+ proc recvInto*(socket: AsyncFD, buf: pointer, size: int,
+ flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the
## data requested is read, a part of the data has been read, or the socket
@@ -773,7 +535,7 @@ when defined(windows) or defined(nimdoc):
#buf[] = '\0'
var dataBuf: TWSABuf
- dataBuf.buf = buf
+ dataBuf.buf = cast[cstring](buf)
dataBuf.len = size.ULONG
var bytesReceived: Dword
@@ -819,16 +581,18 @@ when defined(windows) or defined(nimdoc):
retFuture.complete(bytesReceived)
return retFuture
- proc send*(socket: AsyncFD, data: string,
+ proc send*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[void] =
- ## Sends ``data`` to ``socket``. The returned future will complete once all
+ ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all
## data has been sent.
+ ## **WARNING**: Use it with caution. If ``buf`` refers to GC'ed object, you must use GC_ref/GC_unref calls
+ ## to avoid early freeing of the buffer
verifyPresence(socket)
var retFuture = newFuture[void]("send")
var dataBuf: TWSABuf
- dataBuf.buf = data # since this is not used in a callback, this is fine
- dataBuf.len = data.len.ULONG
+ dataBuf.buf = cast[cstring](buf)
+ dataBuf.len = size.ULONG
var bytesReceived, lowFlags: Dword
var ol = PCustomOverlapped()
@@ -1281,43 +1045,45 @@ else:
proc poll*(timeout = 500) =
let p = getGlobalDispatcher()
- for info in p.selector.select(p.adjustedTimeout(timeout)):
- let data = PData(info.key.data)
- assert data.fd == info.key.fd.AsyncFD
- #echo("In poll ", data.fd.cint)
- # There may be EvError here, but we handle them in callbacks,
- # so that exceptions can be raised from `send(...)` and
- # `recv(...)` routines.
- if EvRead in info.events:
- # Callback may add items to ``data.readCBs`` which causes issues if
- # we are iterating over ``data.readCBs`` at the same time. We therefore
- # make a copy to iterate over.
- let currentCBs = data.readCBs
- data.readCBs = @[]
- for cb in currentCBs:
- if not cb(data.fd):
- # Callback wants to be called again.
- data.readCBs.add(cb)
+ if p.selector.len > 0:
+ for info in p.selector.select(p.adjustedTimeout(timeout)):
+ let data = PData(info.key.data)
+ assert data.fd == info.key.fd.AsyncFD
+ #echo("In poll ", data.fd.cint)
+ # There may be EvError here, but we handle them in callbacks,
+ # so that exceptions can be raised from `send(...)` and
+ # `recv(...)` routines.
- if EvWrite in info.events:
- let currentCBs = data.writeCBs
- data.writeCBs = @[]
- for cb in currentCBs:
- if not cb(data.fd):
- # Callback wants to be called again.
- data.writeCBs.add(cb)
+ if EvRead in info.events:
+ # Callback may add items to ``data.readCBs`` which causes issues if
+ # we are iterating over ``data.readCBs`` at the same time. We therefore
+ # make a copy to iterate over.
+ let currentCBs = data.readCBs
+ data.readCBs = @[]
+ for cb in currentCBs:
+ if not cb(data.fd):
+ # Callback wants to be called again.
+ data.readCBs.add(cb)
- if info.key in p.selector:
- var newEvents: set[Event]
- if data.readCBs.len != 0: newEvents = {EvRead}
- if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite}
- if newEvents != info.key.events:
- update(data.fd, newEvents)
- else:
- # FD no longer a part of the selector. Likely been closed
- # (e.g. socket disconnected).
- discard
+ if EvWrite in info.events:
+ let currentCBs = data.writeCBs
+ data.writeCBs = @[]
+ for cb in currentCBs:
+ if not cb(data.fd):
+ # Callback wants to be called again.
+ data.writeCBs.add(cb)
+
+ if info.key in p.selector:
+ var newEvents: set[Event]
+ if data.readCBs.len != 0: newEvents = {EvRead}
+ if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite}
+ if newEvents != info.key.events:
+ update(data.fd, newEvents)
+ else:
+ # FD no longer a part of the selector. Likely been closed
+ # (e.g. socket disconnected).
+ discard
# Timer processing.
processTimers(p)
@@ -1398,7 +1164,7 @@ else:
addRead(socket, cb)
return retFuture
- proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
+ proc recvInto*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto")
@@ -1422,7 +1188,7 @@ else:
addRead(socket, cb)
return retFuture
- proc send*(socket: AsyncFD, data: string,
+ proc send*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send")
@@ -1430,8 +1196,8 @@ else:
proc cb(sock: AsyncFD): bool =
result = true
- let netSize = data.len-written
- var d = data.cstring
+ let netSize = size-written
+ var d = cast[cstring](buf)
let res = send(sock.SocketHandle, addr d[written], netSize.cint,
MSG_NOSIGNAL)
if res < 0:
@@ -1576,368 +1342,31 @@ proc accept*(socket: AsyncFD,
retFut.complete(future.read.client)
return retFut
+proc send*(socket: AsyncFD, data: string,
+ flags = {SocketFlag.SafeDisconn}): Future[void] =
+ ## Sends ``data`` to ``socket``. The returned future will complete once all
+ ## data has been sent.
+ var retFuture = newFuture[void]("send")
+
+ var copiedData = data
+ GC_ref(copiedData) # we need to protect data until send operation is completed
+ # or failed.
+
+ let sendFut = socket.send(addr copiedData[0], data.len, flags)
+ sendFut.callback =
+ proc () =
+ GC_unref(copiedData)
+ if sendFut.failed:
+ retFuture.fail(sendFut.error)
+ else:
+ retFuture.complete()
+
+ return retFuture
+
# -- Await Macro
+include asyncmacro
-proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
- # Skips a nest of StmtList's.
- result = node
- if node[0].kind == nnkStmtList:
- result = skipUntilStmtList(node[0])
-
-proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
- result = node
- if node[0].kind == nnkStmtList:
- result = node[0]
-
-template createCb(retFutureSym, iteratorNameSym,
- name: untyped) =
- var nameIterVar = iteratorNameSym
- #{.push stackTrace: off.}
- proc cb {.closure,gcsafe.} =
- try:
- if not nameIterVar.finished:
- var next = nameIterVar()
- if next == nil:
- assert retFutureSym.finished, "Async procedure's (" &
- name & ") return Future was not finished."
- else:
- next.callback = cb
- except:
- if retFutureSym.finished:
- # Take a look at tasyncexceptions for the bug which this fixes.
- # That test explains it better than I can here.
- raise
- else:
- retFutureSym.fail(getCurrentException())
- cb()
- #{.pop.}
-proc generateExceptionCheck(futSym,
- tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
- if tryStmt.kind == nnkNilLit:
- result = rootReceiver
- else:
- var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[]
- let errorNode = newDotExpr(futSym, newIdentNode("error"))
- for i in 1 .. -> else: raise futSym.error
- exceptionChecks.add((newIdentNode("true"),
- newNimNode(nnkRaiseStmt).add(errorNode)))
- # Read the future if there is no error.
- # -> else: futSym.read
- let elseNode = newNimNode(nnkElse, fromNode)
- elseNode.add newNimNode(nnkStmtList, fromNode)
- elseNode[0].add rootReceiver
-
- let ifBody = newStmtList()
- ifBody.add newCall(newIdentNode("setCurrentException"), errorNode)
- ifBody.add newIfStmt(exceptionChecks)
- ifBody.add newCall(newIdentNode("setCurrentException"), newNilLit())
-
- result = newIfStmt(
- (newDotExpr(futSym, newIdentNode("failed")), ifBody)
- )
- result.add elseNode
-
-template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
- rootReceiver: expr, fromNode: NimNode) =
- ## Params:
- ## futureVarNode: The NimNode which is a symbol identifying the Future[T]
- ## variable to yield.
- ## fromNode: Used for better debug information (to give context).
- ## valueReceiver: The node which defines an expression that retrieves the
- ## future's value.
- ##
- ## rootReceiver: ??? TODO
- # -> yield future
- result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
- # -> future.read
- valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
- result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver,
- fromNode)
-
-template createVar(result: var NimNode, futSymName: string,
- asyncProc: NimNode,
- valueReceiver, rootReceiver: expr,
- fromNode: NimNode) =
- result = newNimNode(nnkStmtList, fromNode)
- var futSym = genSym(nskVar, "future")
- result.add newVarStmt(futSym, asyncProc) # -> var future = y
- useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
-
-proc processBody(node, retFutureSym: NimNode,
- subTypeIsVoid: bool,
- tryStmt: NimNode): NimNode {.compileTime.} =
- #echo(node.treeRepr)
- result = node
- case node.kind
- of nnkReturnStmt:
- result = newNimNode(nnkStmtList, node)
- if node[0].kind == nnkEmpty:
- if not subTypeIsVoid:
- result.add newCall(newIdentNode("complete"), retFutureSym,
- newIdentNode("result"))
- else:
- result.add newCall(newIdentNode("complete"), retFutureSym)
- else:
- let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt)
- if x.kind == nnkYieldStmt: result.add x
- else:
- result.add newCall(newIdentNode("complete"), retFutureSym, x)
-
- result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
- return # Don't process the children of this return stmt
- of nnkCommand, nnkCall:
- if node[0].kind == nnkIdent and node[0].ident == !"await":
- case node[1].kind
- of nnkIdent, nnkInfix, nnkDotExpr:
- # await x
- # await x or y
- result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x
- of nnkCall, nnkCommand:
- # await foo(p, x)
- # await foo p, x
- var futureValue: NimNode
- result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
- futureValue, node)
- else:
- error("Invalid node kind in 'await', got: " & $node[1].kind)
- elif node.len > 1 and node[1].kind == nnkCommand and
- node[1][0].kind == nnkIdent and node[1][0].ident == !"await":
- # foo await x
- var newCommand = node
- result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
- newCommand, node)
-
- of nnkVarSection, nnkLetSection:
- case node[0][2].kind
- of nnkCommand:
- if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await":
- # var x = await y
- var newVarSection = node # TODO: Should this use copyNimNode?
- result.createVar("future" & $node[0][0].ident, node[0][2][1],
- newVarSection[0][2], newVarSection, node)
- else: discard
- of nnkAsgn:
- case node[1].kind
- of nnkCommand:
- if node[1][0].ident == !"await":
- # x = await y
- var newAsgn = node
- result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node)
- else: discard
- of nnkDiscardStmt:
- # discard await x
- if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
- node[0][0].ident == !"await":
- var newDiscard = node
- result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
- newDiscard[0], newDiscard, node)
- of nnkTryStmt:
- # try: await x; except: ...
- result = newNimNode(nnkStmtList, node)
- template wrapInTry(n, tryBody: expr) =
- var temp = n
- n[0] = tryBody
- tryBody = temp
-
- # Transform ``except`` body.
- # TODO: Could we perform some ``await`` transformation here to get it
- # working in ``except``?
- tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil)
-
- proc processForTry(n: NimNode, i: var int,
- res: NimNode): bool {.compileTime.} =
- ## Transforms the body of the tryStmt. Does not transform the
- ## body in ``except``.
- ## Returns true if the tryStmt node was transformed into an ifStmt.
- result = false
- var skipped = n.skipStmtList()
- while i < skipped.len:
- var processed = processBody(skipped[i], retFutureSym,
- subTypeIsVoid, n)
-
- # Check if we transformed the node into an exception check.
- # This suggests skipped[i] contains ``await``.
- if processed.kind != skipped[i].kind or processed.len != skipped[i].len:
- processed = processed.skipUntilStmtList()
- expectKind(processed, nnkStmtList)
- expectKind(processed[2][1], nnkElse)
- i.inc
-
- if not processForTry(n, i, processed[2][1][0]):
- # We need to wrap the nnkElse nodes back into a tryStmt.
- # As they are executed if an exception does not happen
- # inside the awaited future.
- # The following code will wrap the nodes inside the
- # original tryStmt.
- wrapInTry(n, processed[2][1][0])
-
- res.add processed
- result = true
- else:
- res.add skipped[i]
- i.inc
- var i = 0
- if not processForTry(node, i, result):
- # If the tryStmt hasn't been transformed we can just put the body
- # back into it.
- wrapInTry(node, result)
- return
- else: discard
-
- for i in 0 .. var retFuture = newFuture[T]()
- var retFutureSym = genSym(nskVar, "retFuture")
- var subRetType =
- if returnType.kind == nnkEmpty: newIdentNode("void")
- else: baseType
- outerProcBody.add(
- newVarStmt(retFutureSym,
- newCall(
- newNimNode(nnkBracketExpr, prc[6]).add(
- newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
- subRetType),
- newLit(prc[0].getName)))) # Get type from return type of this proc
-
- # -> iterator nameIter(): FutureBase {.closure.} =
- # -> {.push warning[resultshadowed]: off.}
- # -> var result: T
- # -> {.pop.}
- # ->
- # -> complete(retFuture, result)
- var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
- var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
- # don't do anything with forward bodies (empty)
- if procBody.kind != nnkEmpty:
- if not subtypeIsVoid:
- procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
- newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
- newIdentNode("warning"), newIdentNode("resultshadowed")),
- newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
-
- procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
- newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
-
- procBody.insert(2, newNimNode(nnkPragma).add(
- newIdentNode("pop"))) # -> {.pop.})
-
- procBody.add(
- newCall(newIdentNode("complete"),
- retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
- else:
- # -> complete(retFuture)
- procBody.add(newCall(newIdentNode("complete"), retFutureSym))
-
- var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
- procBody, nnkIteratorDef)
- closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
- outerProcBody.add(closureIterator)
-
- # -> createCb(retFuture)
- #var cbName = newIdentNode("cb")
- var procCb = getAst createCb(retFutureSym, iteratorNameSym,
- newStrLitNode(prc[0].getName))
- outerProcBody.add procCb
-
- # -> return retFuture
- outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
-
- result = prc
-
- # Remove the 'async' pragma.
- for i in 0 .. 0
+ assert bytesCount <= size
+ f.offset.inc bytesCount
+ retFuture.complete(bytesCount)
+ else:
+ if errcode.int32 == ERROR_HANDLE_EOF:
+ retFuture.complete(0)
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+ )
+ ol.offset = DWord(f.offset and 0xffffffff)
+ ol.offsetHigh = DWord(f.offset shr 32)
+
+ # According to MSDN we're supposed to pass nil to lpNumberOfBytesRead.
+ let ret = readFile(f.fd.Handle, buf, size.int32, nil,
+ cast[POVERLAPPED](ol))
+ if not ret.bool:
+ let err = osLastError()
+ if err.int32 != ERROR_IO_PENDING:
+ GC_unref(ol)
+ retFuture.fail(newException(OSError, osErrorMsg(err)))
+ else:
+ # Request completed immediately.
+ var bytesRead: DWord
+ let overlappedRes = getOverlappedResult(f.fd.Handle,
+ cast[POverlapped](ol), bytesRead, false.WinBool)
+ if not overlappedRes.bool:
+ let err = osLastError()
+ if err.int32 == ERROR_HANDLE_EOF:
+ retFuture.complete(0)
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+ else:
+ assert bytesRead > 0
+ assert bytesRead <= size
+ f.offset.inc bytesRead
+ retFuture.complete(bytesRead)
+ else:
+ proc cb(fd: AsyncFD): bool =
+ result = true
+ let res = read(fd.cint, cast[cstring](buf), size.cint)
+ if res < 0:
+ let lastError = osLastError()
+ if lastError.int32 != EAGAIN:
+ retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+ else:
+ result = false # We still want this callback to be called.
+ elif res == 0:
+ # EOF
+ retFuture.complete(0)
+ else:
+ f.offset.inc(res)
+ retFuture.complete(res)
+
+ if not cb(f.fd):
+ addRead(f.fd, cb)
+
+ return retFuture
+
proc read*(f: AsyncFile, size: int): Future[string] =
## Read ``size`` bytes from the specified file asynchronously starting at
## the current position of the file pointer.
@@ -238,6 +314,73 @@ proc readAll*(f: AsyncFile): Future[string] {.async.} =
return
result.add data
+proc writeBuffer*(f: AsyncFile, buf: pointer, size: int): Future[void] =
+ ## Writes ``size`` bytes from ``buf`` to the file specified asynchronously.
+ ##
+ ## The returned Future will complete once all data has been written to the
+ ## specified file.
+ var retFuture = newFuture[void]("asyncfile.writeBuffer")
+ when defined(windows) or defined(nimdoc):
+ var ol = PCustomOverlapped()
+ GC_ref(ol)
+ ol.data = CompletionData(fd: f.fd, cb:
+ proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
+ if not retFuture.finished:
+ if errcode == OSErrorCode(-1):
+ assert bytesCount == size.int32
+ f.offset.inc(size)
+ retFuture.complete()
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+ )
+ ol.offset = DWord(f.offset and 0xffffffff)
+ ol.offsetHigh = DWord(f.offset shr 32)
+
+ # According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten.
+ let ret = writeFile(f.fd.Handle, buf, size.int32, nil,
+ cast[POVERLAPPED](ol))
+ if not ret.bool:
+ let err = osLastError()
+ if err.int32 != ERROR_IO_PENDING:
+ GC_unref(ol)
+ retFuture.fail(newException(OSError, osErrorMsg(err)))
+ else:
+ # Request completed immediately.
+ var bytesWritten: DWord
+ let overlappedRes = getOverlappedResult(f.fd.Handle,
+ cast[POverlapped](ol), bytesWritten, false.WinBool)
+ if not overlappedRes.bool:
+ retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
+ else:
+ assert bytesWritten == size.int32
+ f.offset.inc(size)
+ retFuture.complete()
+ else:
+ var written = 0
+
+ proc cb(fd: AsyncFD): bool =
+ result = true
+ let remainderSize = size-written
+ var cbuf = cast[cstring](buf)
+ let res = write(fd.cint, addr cbuf[written], remainderSize.cint)
+ if res < 0:
+ let lastError = osLastError()
+ if lastError.int32 != EAGAIN:
+ retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+ else:
+ result = false # We still want this callback to be called.
+ else:
+ written.inc res
+ f.offset.inc res
+ if res != remainderSize:
+ result = false # We still have data to write.
+ else:
+ retFuture.complete()
+
+ if not cb(f.fd):
+ addWrite(f.fd, cb)
+ return retFuture
+
proc write*(f: AsyncFile, data: string): Future[void] =
## Writes ``data`` to the file specified asynchronously.
##
diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim
index fd899e080..019a18f55 100644
--- a/lib/pure/asyncftpclient.nim
+++ b/lib/pure/asyncftpclient.nim
@@ -281,7 +281,7 @@ proc getFile(ftp: AsyncFtpClient, file: File, total: BiggestInt,
assertReply(await(ftp.expectReply()), "226")
proc defaultOnProgressChanged*(total, progress: BiggestInt,
- speed: float): Future[void] {.nimcall,gcsafe.} =
+ speed: float): Future[void] {.nimcall,gcsafe,procvar.} =
## Default FTP ``onProgressChanged`` handler. Does nothing.
result = newFuture[void]()
#echo(total, " ", progress, " ", speed)
@@ -354,6 +354,20 @@ proc store*(ftp: AsyncFtpClient, file, dest: string,
await doUpload(ftp, destFile, onProgressChanged)
+proc rename*(ftp: AsyncFtpClient, nameFrom: string, nameTo: string) {.async.} =
+ ## Rename a file or directory on the remote FTP Server from current name
+ ## ``name_from`` to new name ``name_to``
+ assertReply(await ftp.send("RNFR " & name_from), "350")
+ assertReply(await ftp.send("RNTO " & name_to), "250")
+
+proc removeFile*(ftp: AsyncFtpClient, filename: string) {.async.} =
+ ## Delete a file ``filename`` on the remote FTP server
+ assertReply(await ftp.send("DELE " & filename), "250")
+
+proc removeDir*(ftp: AsyncFtpClient, dir: string) {.async.} =
+ ## Delete a directory ``dir`` on the remote FTP server
+ assertReply(await ftp.send("RMD " & dir), "250")
+
proc newAsyncFtpClient*(address: string, port = Port(21),
user, pass = ""): AsyncFtpClient =
## Creates a new ``AsyncFtpClient`` object.
@@ -373,6 +387,11 @@ when not defined(testing) and isMainModule:
echo await ftp.listDirs()
await ftp.store("payload.jpg", "payload.jpg")
await ftp.retrFile("payload.jpg", "payload2.jpg")
+ await ftp.rename("payload.jpg", "payload_renamed.jpg")
+ await ftp.store("payload.jpg", "payload_remove.jpg")
+ await ftp.removeFile("payload_remove.jpg")
+ await ftp.createDir("deleteme")
+ await ftp.removeDir("deleteme")
echo("Finished")
waitFor main(ftp)
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim
index 6a7326e83..a658097f9 100644
--- a/lib/pure/asynchttpserver.nim
+++ b/lib/pure/asynchttpserver.nim
@@ -9,6 +9,12 @@
## This module implements a high performance asynchronous HTTP server.
##
+## This HTTP server has not been designed to be used in production, but
+## for testing applications locally. Because of this, when deploying your
+## application you should use a reverse proxy (for example nginx) instead of
+## allowing users to connect directly to this server.
+##
+##
## Examples
## --------
##
@@ -38,7 +44,7 @@ export httpcore except parseHeader
type
Request* = object
client*: AsyncSocket # TODO: Separate this into a Response object?
- reqMethod*: string
+ reqMethod*: HttpMethod
headers*: HttpHeaders
protocol*: tuple[orig: string, major, minor: int]
url*: Uri
@@ -127,7 +133,14 @@ proc processClient(client: AsyncSocket, address: string,
var i = 0
for linePart in lineFut.mget.split(' '):
case i
- of 0: request.reqMethod.shallowCopy(linePart.normalize)
+ of 0:
+ try:
+ # TODO: this is likely slow.
+ request.reqMethod = parseEnum[HttpMethod]("http" & linePart)
+ except ValueError:
+ asyncCheck request.respond(Http400, "Invalid request method. Got: " &
+ linePart)
+ continue
of 1: parseUri(linePart, request.url)
of 2:
try:
@@ -159,7 +172,7 @@ proc processClient(client: AsyncSocket, address: string,
request.client.close()
return
- if request.reqMethod == "post":
+ if request.reqMethod == HttpPost:
# Check for Expect header
if request.headers.hasKey("Expect"):
if "100-continue" in request.headers["Expect"]:
@@ -178,17 +191,12 @@ proc processClient(client: AsyncSocket, address: string,
else:
request.body = await client.recv(contentLength)
assert request.body.len == contentLength
- elif request.reqMethod == "post":
+ elif request.reqMethod == HttpPost:
await request.respond(Http400, "Bad Request. No Content-Length.")
continue
- case request.reqMethod
- of "get", "post", "head", "put", "delete", "trace", "options",
- "connect", "patch":
- await callback(request)
- else:
- await request.respond(Http400, "Invalid request method. Got: " &
- request.reqMethod)
+ # Call the user's callback.
+ await callback(request)
if "upgrade" in request.headers.getOrDefault("connection"):
return
diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim
new file mode 100644
index 000000000..3d004e84c
--- /dev/null
+++ b/lib/pure/asyncmacro.nim
@@ -0,0 +1,515 @@
+#
+#
+# Nim's Runtime Library
+# (c) Copyright 2015 Dominik Picheta
+#
+# See the file "copying.txt", included in this
+# distribution, for details about the copyright.
+#
+
+## AsyncMacro
+## *************
+## `asyncdispatch` module depends on the `asyncmacro` module to work properly.
+
+import macros, strutils
+
+proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
+ # Skips a nest of StmtList's.
+ result = node
+ if node[0].kind == nnkStmtList:
+ result = skipUntilStmtList(node[0])
+
+proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
+ result = node
+ if node[0].kind == nnkStmtList:
+ result = node[0]
+
+template createCb(retFutureSym, iteratorNameSym,
+ name, futureVarCompletions: untyped) =
+ var nameIterVar = iteratorNameSym
+ #{.push stackTrace: off.}
+ proc cb {.closure,gcsafe.} =
+ try:
+ if not nameIterVar.finished:
+ var next = nameIterVar()
+ if next == nil:
+ assert retFutureSym.finished, "Async procedure's (" &
+ name & ") return Future was not finished."
+ else:
+ next.callback = cb
+ except:
+ if retFutureSym.finished:
+ # Take a look at tasyncexceptions for the bug which this fixes.
+ # That test explains it better than I can here.
+ raise
+ else:
+ retFutureSym.fail(getCurrentException())
+
+ futureVarCompletions
+ cb()
+ #{.pop.}
+proc generateExceptionCheck(futSym,
+ tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
+ if tryStmt.kind == nnkNilLit:
+ result = rootReceiver
+ else:
+ var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[]
+ let errorNode = newDotExpr(futSym, newIdentNode("error"))
+ for i in 1 .. -> else: raise futSym.error
+ exceptionChecks.add((newIdentNode("true"),
+ newNimNode(nnkRaiseStmt).add(errorNode)))
+ # Read the future if there is no error.
+ # -> else: futSym.read
+ let elseNode = newNimNode(nnkElse, fromNode)
+ elseNode.add newNimNode(nnkStmtList, fromNode)
+ elseNode[0].add rootReceiver
+
+ let ifBody = newStmtList()
+ ifBody.add newCall(newIdentNode("setCurrentException"), errorNode)
+ ifBody.add newIfStmt(exceptionChecks)
+ ifBody.add newCall(newIdentNode("setCurrentException"), newNilLit())
+
+ result = newIfStmt(
+ (newDotExpr(futSym, newIdentNode("failed")), ifBody)
+ )
+ result.add elseNode
+
+template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
+ rootReceiver: expr, fromNode: NimNode) =
+ ## Params:
+ ## futureVarNode: The NimNode which is a symbol identifying the Future[T]
+ ## variable to yield.
+ ## fromNode: Used for better debug information (to give context).
+ ## valueReceiver: The node which defines an expression that retrieves the
+ ## future's value.
+ ##
+ ## rootReceiver: ??? TODO
+ # -> yield future
+ result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
+ # -> future.read
+ valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
+ result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver,
+ fromNode)
+
+template createVar(result: var NimNode, futSymName: string,
+ asyncProc: NimNode,
+ valueReceiver, rootReceiver: expr,
+ fromNode: NimNode) =
+ result = newNimNode(nnkStmtList, fromNode)
+ var futSym = genSym(nskVar, "future")
+ result.add newVarStmt(futSym, asyncProc) # -> var future = y
+ useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
+
+proc createFutureVarCompletions(futureVarIdents: seq[NimNode]): NimNode
+ {.compileTime.} =
+ result = newStmtList()
+ # Add calls to complete each FutureVar parameter.
+ for ident in futureVarIdents:
+ # Only complete them if they have not been completed already by the user.
+ result.add newIfStmt(
+ (
+ newCall(newIdentNode("not"),
+ newDotExpr(ident, newIdentNode("finished"))),
+ newCall(newIdentNode("complete"), ident)
+ )
+ )
+
+proc processBody(node, retFutureSym: NimNode,
+ subTypeIsVoid: bool, futureVarIdents: seq[NimNode],
+ tryStmt: NimNode): NimNode {.compileTime.} =
+ #echo(node.treeRepr)
+ result = node
+ case node.kind
+ of nnkReturnStmt:
+ result = newNimNode(nnkStmtList, node)
+ if node[0].kind == nnkEmpty:
+ if not subTypeIsVoid:
+ result.add newCall(newIdentNode("complete"), retFutureSym,
+ newIdentNode("result"))
+ else:
+ result.add newCall(newIdentNode("complete"), retFutureSym)
+ else:
+ let x = node[0].processBody(retFutureSym, subTypeIsVoid,
+ futureVarIdents, tryStmt)
+ if x.kind == nnkYieldStmt: result.add x
+ else:
+ result.add newCall(newIdentNode("complete"), retFutureSym, x)
+
+ result.add createFutureVarCompletions(futureVarIdents)
+
+ result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
+ return # Don't process the children of this return stmt
+ of nnkCommand, nnkCall:
+ if node[0].kind == nnkIdent and node[0].ident == !"await":
+ case node[1].kind
+ of nnkIdent, nnkInfix, nnkDotExpr, nnkCall, nnkCommand:
+ # await x
+ # await x or y
+ # await foo(p, x)
+ # await foo p, x
+ var futureValue: NimNode
+ result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
+ futureValue, node)
+ else:
+ error("Invalid node kind in 'await', got: " & $node[1].kind)
+ elif node.len > 1 and node[1].kind == nnkCommand and
+ node[1][0].kind == nnkIdent and node[1][0].ident == !"await":
+ # foo await x
+ var newCommand = node
+ result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
+ newCommand, node)
+
+ of nnkVarSection, nnkLetSection:
+ case node[0][2].kind
+ of nnkCommand:
+ if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await":
+ # var x = await y
+ var newVarSection = node # TODO: Should this use copyNimNode?
+ result.createVar("future" & $node[0][0].ident, node[0][2][1],
+ newVarSection[0][2], newVarSection, node)
+ else: discard
+ of nnkAsgn:
+ case node[1].kind
+ of nnkCommand:
+ if node[1][0].ident == !"await":
+ # x = await y
+ var newAsgn = node
+ result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node)
+ else: discard
+ of nnkDiscardStmt:
+ # discard await x
+ if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
+ node[0][0].ident == !"await":
+ var newDiscard = node
+ result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
+ newDiscard[0], newDiscard, node)
+ of nnkTryStmt:
+ # try: await x; except: ...
+ result = newNimNode(nnkStmtList, node)
+ template wrapInTry(n, tryBody: expr) =
+ var temp = n
+ n[0] = tryBody
+ tryBody = temp
+
+ # Transform ``except`` body.
+ # TODO: Could we perform some ``await`` transformation here to get it
+ # working in ``except``?
+ tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid,
+ futureVarIdents, nil)
+
+ proc processForTry(n: NimNode, i: var int,
+ res: NimNode): bool {.compileTime.} =
+ ## Transforms the body of the tryStmt. Does not transform the
+ ## body in ``except``.
+ ## Returns true if the tryStmt node was transformed into an ifStmt.
+ result = false
+ var skipped = n.skipStmtList()
+ while i < skipped.len:
+ var processed = processBody(skipped[i], retFutureSym,
+ subTypeIsVoid, futureVarIdents, n)
+
+ # Check if we transformed the node into an exception check.
+ # This suggests skipped[i] contains ``await``.
+ if processed.kind != skipped[i].kind or processed.len != skipped[i].len:
+ processed = processed.skipUntilStmtList()
+ expectKind(processed, nnkStmtList)
+ expectKind(processed[2][1], nnkElse)
+ i.inc
+
+ if not processForTry(n, i, processed[2][1][0]):
+ # We need to wrap the nnkElse nodes back into a tryStmt.
+ # As they are executed if an exception does not happen
+ # inside the awaited future.
+ # The following code will wrap the nodes inside the
+ # original tryStmt.
+ wrapInTry(n, processed[2][1][0])
+
+ res.add processed
+ result = true
+ else:
+ res.add skipped[i]
+ i.inc
+ var i = 0
+ if not processForTry(node, i, result):
+ # If the tryStmt hasn't been transformed we can just put the body
+ # back into it.
+ wrapInTry(node, result)
+ return
+ else: discard
+
+ for i in 0 .. var retFuture = newFuture[T]()
+ var retFutureSym = genSym(nskVar, "retFuture")
+ var subRetType =
+ if returnType.kind == nnkEmpty: newIdentNode("void")
+ else: baseType
+ outerProcBody.add(
+ newVarStmt(retFutureSym,
+ newCall(
+ newNimNode(nnkBracketExpr, prc[6]).add(
+ newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
+ subRetType),
+ newLit(prc[0].getName)))) # Get type from return type of this proc
+
+ # -> iterator nameIter(): FutureBase {.closure.} =
+ # -> {.push warning[resultshadowed]: off.}
+ # -> var result: T
+ # -> {.pop.}
+ # ->
+ # -> complete(retFuture, result)
+ var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
+ var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid,
+ futureVarIdents, nil)
+ # don't do anything with forward bodies (empty)
+ if procBody.kind != nnkEmpty:
+ if not subtypeIsVoid:
+ procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
+ newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
+ newIdentNode("warning"), newIdentNode("resultshadowed")),
+ newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
+
+ procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
+ newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
+
+ procBody.insert(2, newNimNode(nnkPragma).add(
+ newIdentNode("pop"))) # -> {.pop.})
+
+ procBody.add(
+ newCall(newIdentNode("complete"),
+ retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
+ else:
+ # -> complete(retFuture)
+ procBody.add(newCall(newIdentNode("complete"), retFutureSym))
+
+ procBody.add(createFutureVarCompletions(futureVarIdents))
+
+ var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
+ procBody, nnkIteratorDef)
+ closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
+ outerProcBody.add(closureIterator)
+
+ # -> createCb(retFuture)
+ #var cbName = newIdentNode("cb")
+ var procCb = getAst createCb(retFutureSym, iteratorNameSym,
+ newStrLitNode(prc[0].getName),
+ createFutureVarCompletions(futureVarIdents))
+ outerProcBody.add procCb
+
+ # -> return retFuture
+ outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
+
+ result = prc
+
+ # Remove the 'async' pragma.
+ for i in 0 .. 1 and node[1].kind == nnkCommand and
+ node[1][0].kind == nnkIdent and node[1][0].ident == !"await":
+ # foo await x
+ node[1][0] = emptyNoopSym
+ of nnkVarSection, nnkLetSection:
+ case node[0][2].kind
+ of nnkCommand:
+ if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await":
+ # var x = await y
+ node[0][2][0] = emptyNoopSym
+ else: discard
+ of nnkAsgn:
+ case node[1].kind
+ of nnkCommand:
+ if node[1][0].ident == !"await":
+ # x = await y
+ node[1][0] = emptyNoopSym
+ else: discard
+ of nnkDiscardStmt:
+ # discard await x
+ if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
+ node[0][0].ident == !"await":
+ node[0][0] = emptyNoopSym
+ else: discard
+
+ for i in 0 .. 0:
+ let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint)
+ if ret < 0:
+ raiseSSLError()
+ elif length == 0:
+ # connection not properly closed by remote side or connection dropped
+ SSL_set_shutdown(socket.sslHandle, SSL_RECEIVED_SHUTDOWN)
+ result = false
else:
raiseSSLError("Cannot appease SSL.")
@@ -173,13 +183,27 @@ when defineSsl:
op: expr) =
var opResult {.inject.} = -1.cint
while opResult < 0:
+ # Call the desired operation.
opResult = op
# Bit hackish here.
# TODO: Introduce an async template transformation pragma?
+
+ # Send any remaining pending SSL data.
yield sendPendingSslData(socket, flags)
+
+ # If the operation failed, try to see if SSL has some data to read
+ # or write.
if opResult < 0:
let err = getSslError(socket.sslHandle, opResult.cint)
- yield appeaseSsl(socket, flags, err.cint)
+ let fut = appeaseSsl(socket, flags, err.cint)
+ yield fut
+ if not fut.read():
+ # Socket disconnected.
+ if SocketFlag.SafeDisconn in flags:
+ break
+ else:
+ raiseSSLError("Socket has been disconnected")
+
proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``.
@@ -193,7 +217,7 @@ proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
sslSetConnectState(socket.sslHandle)
sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
-template readInto(buf: cstring, size: int, socket: AsyncSocket,
+template readInto(buf: pointer, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): int =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that
## this is a template and not a proc.
@@ -202,10 +226,10 @@ template readInto(buf: cstring, size: int, socket: AsyncSocket,
when defineSsl:
# SSL mode.
sslLoop(socket, flags,
- sslRead(socket.sslHandle, buf, size.cint))
+ sslRead(socket.sslHandle, cast[cstring](buf), size.cint))
res = opResult
else:
- var recvIntoFut = recvInto(socket.fd.AsyncFD, buf, size, flags)
+ var recvIntoFut = asyncdispatch.recvInto(socket.fd.AsyncFD, buf, size, flags)
yield recvIntoFut
# Not in SSL mode.
res = recvIntoFut.read()
@@ -218,6 +242,54 @@ template readIntoBuf(socket: AsyncSocket,
socket.bufLen = size
size
+proc recvInto*(socket: AsyncSocket, buf: pointer, size: int,
+ flags = {SocketFlag.SafeDisconn}): Future[int] {.async.} =
+ ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``.
+ ##
+ ## For buffered sockets this function will attempt to read all the requested
+ ## data. It will read this data in ``BufferSize`` chunks.
+ ##
+ ## For unbuffered sockets this function makes no effort to read
+ ## all the data requested. It will return as much data as the operating system
+ ## gives it.
+ ##
+ ## If socket is disconnected during the
+ ## recv operation then the future may complete with only a part of the
+ ## requested data.
+ ##
+ ## If socket is disconnected and no data is available
+ ## to be read then the future will complete with a value of ``0``.
+ if socket.isBuffered:
+ let originalBufPos = socket.currPos
+
+ if socket.bufLen == 0:
+ let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
+ if res == 0:
+ return 0
+
+ var read = 0
+ var cbuf = cast[cstring](buf)
+ while read < size:
+ if socket.currPos >= socket.bufLen:
+ if SocketFlag.Peek in flags:
+ # We don't want to get another buffer if we're peeking.
+ break
+ let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
+ if res == 0:
+ break
+
+ let chunk = min(socket.bufLen-socket.currPos, size-read)
+ copyMem(addr(cbuf[read]), addr(socket.buffer[socket.currPos]), chunk)
+ read.inc(chunk)
+ socket.currPos.inc(chunk)
+
+ if SocketFlag.Peek in flags:
+ # Restore old buffer cursor position.
+ socket.currPos = originalBufPos
+ result = read
+ else:
+ result = readInto(buf, size, socket, flags)
+
proc recv*(socket: AsyncSocket, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
## Reads **up to** ``size`` bytes from ``socket``.
@@ -270,6 +342,19 @@ proc recv*(socket: AsyncSocket, size: int,
let read = readInto(addr result[0], size, socket, flags)
result.setLen(read)
+proc send*(socket: AsyncSocket, buf: pointer, size: int,
+ flags = {SocketFlag.SafeDisconn}) {.async.} =
+ ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all
+ ## data has been sent.
+ assert socket != nil
+ if socket.isSsl:
+ when defineSsl:
+ sslLoop(socket, flags,
+ sslWrite(socket.sslHandle, cast[cstring](buf), size.cint))
+ await sendPendingSslData(socket, flags)
+ else:
+ await send(socket.fd.AsyncFD, buf, size, flags)
+
proc send*(socket: AsyncSocket, data: string,
flags = {SocketFlag.SafeDisconn}) {.async.} =
## Sends ``data`` to ``socket``. The returned future will complete once all
@@ -320,7 +405,7 @@ proc accept*(socket: AsyncSocket,
return retFut
proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
- flags = {SocketFlag.SafeDisconn}) {.async.} =
+ flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.async.} =
## Reads a line of data from ``socket`` into ``resString``.
##
## If a full line is read ``\r\L`` is not
@@ -333,13 +418,14 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
## is read) then line will be set to ``""``.
## The partial line **will be lost**.
##
+ ## The ``maxLength`` parameter determines the maximum amount of characters
+ ## that can be read before a ``ValueError`` is raised. This prevents Denial
+ ## of Service (DOS) attacks.
+ ##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the
## protocol uses ``\r\L`` to delimit a new line.
- ##
- ## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
- ## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO:
assert(not resString.mget.isNil(),
"String inside resString future needs to be initialised")
@@ -386,6 +472,12 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
else:
resString.mget.add socket.buffer[socket.currPos]
socket.currPos.inc()
+
+ # Verify that this isn't a DOS attack: #3847.
+ if resString.mget.len > maxLength:
+ let msg = "recvLine received more than the specified `maxLength` " &
+ "allowed."
+ raise newException(ValueError, msg)
else:
var c = ""
while true:
@@ -407,10 +499,17 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
resString.complete()
return
resString.mget.add c
+
+ # Verify that this isn't a DOS attack: #3847.
+ if resString.mget.len > maxLength:
+ let msg = "recvLine received more than the specified `maxLength` " &
+ "allowed."
+ raise newException(ValueError, msg)
resString.complete()
proc recvLine*(socket: AsyncSocket,
- flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
+ flags = {SocketFlag.SafeDisconn},
+ maxLength = MaxLineLength): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
##
@@ -424,6 +523,10 @@ proc recvLine*(socket: AsyncSocket,
## is read) then line will be set to ``""``.
## The partial line **will be lost**.
##
+ ## The ``maxLength`` parameter determines the maximum amount of characters
+ ## that can be read before a ``ValueError`` is raised. This prevents Denial
+ ## of Service (DOS) attacks.
+ ##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
@@ -433,7 +536,7 @@ proc recvLine*(socket: AsyncSocket,
# TODO: Optimise this
var resString = newFutureVar[string]("asyncnet.recvLine")
resString.mget() = ""
- await socket.recvLineInto(resString, flags)
+ await socket.recvLineInto(resString, flags, maxLength)
result = resString.mget()
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
diff --git a/lib/pure/basic2d.nim b/lib/pure/basic2d.nim
index 7d74424fa..e4696c6a8 100644
--- a/lib/pure/basic2d.nim
+++ b/lib/pure/basic2d.nim
@@ -117,13 +117,13 @@ proc safeArccos(v:float):float=
template makeBinOpVector(s:expr)=
- ## implements binary operators + , - , * and / for vectors
+ ## implements binary operators ``+``, ``-``, ``*`` and ``/`` for vectors
proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b))
proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y))
template makeBinOpAssignVector(s:expr)=
- ## implements inplace binary operators += , -= , /= and *= for vectors
+ ## implements inplace binary operators ``+=``, ``-=``, ``/=`` and ``*=`` for vectors
proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)
diff --git a/lib/pure/basic3d.nim b/lib/pure/basic3d.nim
index 424c191f8..f7a9c237c 100644
--- a/lib/pure/basic3d.nim
+++ b/lib/pure/basic3d.nim
@@ -117,7 +117,6 @@ proc safeArccos(v:float):float=
return arccos(clamp(v,-1.0,1.0))
template makeBinOpVector(s:expr)=
- ## implements binary operators + , - , * and / for vectors
proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z))
proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} =
@@ -126,11 +125,10 @@ template makeBinOpVector(s:expr)=
vector3d(s(a,b.x),s(a,b.y),s(a,b.z))
template makeBinOpAssignVector(s:expr)=
- ## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var Vector3d,b:Vector3d) {.inline.} =
- s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z)
+ s(a.x,b.x); s(a.y,b.y); s(a.z,b.z)
proc s*(a:var Vector3d,b:float) {.inline.} =
- s(a.x,b) ; s(a.y,b) ; s(a.z,b)
+ s(a.x,b); s(a.y,b); s(a.z,b)
diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim
index 17600b272..28509caa1 100644
--- a/lib/pure/collections/sharedtables.nim
+++ b/lib/pure/collections/sharedtables.nim
@@ -15,6 +15,8 @@
import
hashes, math, locks
+include "system/inclrtl"
+
type
KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B]
KeyValuePairSeq[A, B] = ptr array[10_000_000, KeyValuePair[A, B]]
@@ -35,9 +37,12 @@ proc enlarge[A, B](t: var SharedTable[A, B]) =
t.dataLen = size
swap(t.data, n)
for i in 0.. expected")
-template elemName(x: expr): expr = rawData(x)
+template elemName(x: untyped): untyped = rawData(x)
+
+template adderr(x: untyped) =
+ errors.add(x)
proc untilElementEnd(x: var XmlParser, result: XmlNode,
errors: var seq[string]) =
@@ -469,24 +472,24 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
# allow ``
`` in `
`, `
` and ``
`` in next case
if htmlTag(x.elemName) in {tagLi, tagP, tagDt, tagDd, tagInput,
tagOption}:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
of tagDd, tagDt, tagLi:
if htmlTag(x.elemName) in {tagLi, tagDt, tagDd, tagInput,
tagOption}:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
of tagTd, tagTh:
if htmlTag(x.elemName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
of tagTr:
if htmlTag(x.elemName) == tagTr:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
of tagOptgroup:
if htmlTag(x.elemName) in {tagOption, tagOptgroup}:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
else: discard
result.addNode(parse(x, errors))
@@ -495,11 +498,11 @@ proc untilElementEnd(x: var XmlParser, result: XmlNode,
next(x)
else:
#echo "5; expected: ", result.htmltag, " ", x.elemName
- errors.add(expected(x, result))
+ adderr(expected(x, result))
# do not skip it here!
break
of xmlEof:
- errors.add(expected(x, result))
+ adderr(expected(x, result))
break
else:
result.addNode(parse(x, errors))
@@ -516,14 +519,14 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
# we just ignore processing instructions for now
next(x)
of xmlError:
- errors.add(errorMsg(x))
+ adderr(errorMsg(x))
next(x)
of xmlElementStart:
result = newElement(toLowerAscii(x.elemName))
next(x)
untilElementEnd(x, result, errors)
of xmlElementEnd:
- errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
+ adderr(errorMsg(x, "unexpected ending tag: " & x.elemName))
of xmlElementOpen:
result = newElement(toLowerAscii(x.elemName))
next(x)
@@ -537,16 +540,16 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
next(x)
break
of xmlError:
- errors.add(errorMsg(x))
+ adderr(errorMsg(x))
next(x)
break
else:
- errors.add(errorMsg(x, "'>' expected"))
+ adderr(errorMsg(x, "'>' expected"))
next(x)
break
untilElementEnd(x, result, errors)
of xmlAttribute, xmlElementClose:
- errors.add(errorMsg(x, " expected"))
+ adderr(errorMsg(x, " expected"))
next(x)
of xmlCData:
result = newCData(x.rawData)
@@ -570,7 +573,7 @@ proc parseHtml*(s: Stream, filename: string,
result = newElement("document")
result.addNode(parse(x, errors))
#if x.kind != xmlEof:
- # errors.add(errorMsg(x, "EOF expected"))
+ # adderr(errorMsg(x, "EOF expected"))
while x.kind != xmlEof:
var oldPos = x.bufpos # little hack to see if we made any progess
result.addNode(parse(x, errors))
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 778ca2cbb..4404a9426 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -1,98 +1,130 @@
#
#
# Nim's Runtime Library
-# (c) Copyright 2010 Dominik Picheta, Andreas Rumpf
+# (c) Copyright 2016 Dominik Picheta, Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a simple HTTP client that can be used to retrieve
-## webpages/other data.
-##
-##
-## **Note**: This module is not ideal, connection is not kept alive so sites with
-## many redirects are expensive. As such in the future this module may change,
-## and the current procedures will be deprecated.
+## webpages and other data.
##
## Retrieving a website
## ====================
##
## This example uses HTTP GET to retrieve
-## ``http://google.com``
+## ``http://google.com``:
##
## .. code-block:: Nim
+## var client = newHttpClient()
## echo(getContent("http://google.com"))
##
+## The same action can also be performed asynchronously, simply use the
+## ``AsyncHttpClient``:
+##
+## .. code-block:: Nim
+## var client = newAsyncHttpClient()
+## echo(await getContent("http://google.com"))
+##
+## The functionality implemented by ``HttpClient`` and ``AsyncHttpClient``
+## is the same, so you can use whichever one suits you best in the examples
+## shown here.
+##
+## **Note:** You will need to run asynchronous examples in an async proc
+## otherwise you will get an ``Undeclared identifier: 'await'`` error.
+##
## Using HTTP POST
## ===============
##
## This example demonstrates the usage of the W3 HTML Validator, it
-## uses ``multipart/form-data`` as the ``Content-Type`` to send the HTML to
-## the server.
+## uses ``multipart/form-data`` as the ``Content-Type`` to send the HTML to be
+## validated to the server.
##
## .. code-block:: Nim
+## var client = newHttpClient()
## var data = newMultipartData()
## data["output"] = "soap12"
## data["uploaded_file"] = ("test.html", "text/html",
## "
test
")
##
-## echo postContent("http://validator.w3.org/check", multipart=data)
+## echo client.postContent("http://validator.w3.org/check", multipart=data)
##
-## Asynchronous HTTP requests
-## ==========================
+## Progress reporting
+## ==================
##
-## You simply have to create a new instance of the ``AsyncHttpClient`` object.
-## You may then use ``await`` on the functions defined for that object.
-## Keep in mind that the following code needs to be inside an asynchronous
-## procedure.
-##
-## .. code-block::nim
+## You may specify a callback procedure to be called during an HTTP request.
+## This callback will be executed every second with information about the
+## progress of the HTTP request.
##
+## .. code-block:: Nim
## var client = newAsyncHttpClient()
-## var resp = await client.request("http://google.com")
+## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} =
+## echo("Downloaded ", progress, " of ", total)
+## echo("Current rate: ", speed div 1000, "kb/s")
+## client.onProgressChanged = onProgressChanged
+## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test")
+##
+## If you would like to remove the callback simply set it to ``nil``.
+##
+## .. code-block:: Nim
+## client.onProgressChanged = nil
##
## SSL/TLS support
## ===============
## This requires the OpenSSL library, fortunately it's widely used and installed
## on many operating systems. httpclient will use SSL automatically if you give
## any of the functions a url with the ``https`` schema, for example:
-## ``https://github.com/``, you also have to compile with ``ssl`` defined like so:
+## ``https://github.com/``.
+##
+## You will also have to compile with ``ssl`` defined like so:
## ``nim c -d:ssl ...``.
##
## Timeouts
## ========
-## Currently all functions support an optional timeout, by default the timeout is set to
-## `-1` which means that the function will never time out. The timeout is
+##
+## Currently only the synchronous functions support a timeout.
+## The timeout is
## measured in milliseconds, once it is set any call on a socket which may
-## block will be susceptible to this timeout, however please remember that the
+## block will be susceptible to this timeout.
+##
+## It may be surprising but the
## function as a whole can take longer than the specified timeout, only
## individual internal calls on the socket are affected. In practice this means
## that as long as the server is sending data an exception will not be raised,
-## if however data does not reach client within the specified timeout an ETimeout
-## exception will then be raised.
+## if however data does not reach the client within the specified timeout a
+## ``TimeoutError`` exception will be raised.
##
## Proxy
## =====
##
-## A proxy can be specified as a param to any of these procedures, the ``newProxy``
-## constructor should be used for this purpose. However,
-## currently only basic authentication is supported.
+## A proxy can be specified as a param to any of the procedures defined in
+## this module. To do this, use the ``newProxy`` constructor. Unfortunately,
+## only basic authentication is supported at the moment.
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes,
- math, random, httpcore
+ math, random, httpcore, times
import asyncnet, asyncdispatch
import nativesockets
export httpcore except parseHeader # TODO: The ``except`` doesn't work
type
- Response* = tuple[
- version: string,
- status: string,
- headers: HttpHeaders,
- body: string]
+ Response* = object
+ version*: string
+ status*: string
+ headers*: HttpHeaders
+ body*: string
+proc code*(response: Response): HttpCode
+ {.raises: [ValueError, OverflowError].} =
+ ## Retrieves the specified response's ``HttpCode``.
+ ##
+ ## Raises a ``ValueError`` if the response's ``status`` does not have a
+ ## corresponding ``HttpCode``.
+ return response.status[0 .. 2].parseInt.HttpCode
+
+type
Proxy* = ref object
url*: Uri
auth*: string
@@ -253,25 +285,6 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
else:
result.body = ""
-type
- HttpMethod* = enum ## the requested HttpMethod
- httpHEAD, ## Asks for the response identical to the one that would
- ## correspond to a GET request, but without the response
- ## body.
- httpGET, ## Retrieves the specified resource.
- httpPOST, ## Submits data to be processed to the identified
- ## resource. The data is included in the body of the
- ## request.
- httpPUT, ## Uploads a representation of the specified resource.
- httpDELETE, ## Deletes the specified resource.
- httpTRACE, ## Echoes back the received request, so that a client
- ## can see what intermediate servers are adding or
- ## changing in the request.
- httpOPTIONS, ## Returns the HTTP methods that the server supports
- ## for specified address.
- httpCONNECT ## Converts the request connection to a transparent
- ## TCP/IP tunnel, usually used for proxies.
-
{.deprecated: [THttpMethod: HttpMethod].}
when not defined(ssl):
@@ -389,15 +402,18 @@ proc format(p: MultipartData): tuple[header, body: string] =
proc request*(url: string, httpMethod: string, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1,
- userAgent = defUserAgent, proxy: Proxy = nil): Response =
+ userAgent = defUserAgent, proxy: Proxy = nil): Response
+ {.deprecated.} =
## | Requests ``url`` with the custom method string specified by the
## | ``httpMethod`` parameter.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead.
var r = if proxy == nil: parseUri(url) else: proxy.url
var hostUrl = if proxy == nil: r else: parseUri(url)
- var headers = substr(httpMethod, len("http"))
+ var headers = httpMethod.toUpper()
# TODO: Use generateHeaders further down once it supports proxies.
var s = newSocket()
@@ -481,15 +497,18 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
if body != "":
s.send(body)
- result = parseResponse(s, httpMethod != "httpHEAD", timeout)
+ result = parseResponse(s, httpMethod != "HEAD", timeout)
proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1,
- userAgent = defUserAgent, proxy: Proxy = nil): Response =
+ userAgent = defUserAgent, proxy: Proxy = nil): Response
+ {.deprecated.} =
## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead.
result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout,
userAgent, proxy)
@@ -512,12 +531,14 @@ proc getNewLocation(lastURL: string, headers: HttpHeaders): string =
proc get*(url: string, extraHeaders = "", maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
- proxy: Proxy = nil): Response =
+ proxy: Proxy = nil): Response {.deprecated.} =
## | GETs the ``url`` and returns a ``Response`` object
## | This proc also handles redirection
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
+ ##
+ ## ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead.
result = request(url, httpGET, extraHeaders, "", sslContext, timeout,
userAgent, proxy)
var lastURL = url
@@ -531,12 +552,14 @@ proc get*(url: string, extraHeaders = "", maxRedirects = 5,
proc getContent*(url: string, extraHeaders = "", maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
- proxy: Proxy = nil): string =
+ proxy: Proxy = nil): string {.deprecated.} =
## | GETs the body and returns it as a string.
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in milliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``HttpClient.getContent`` instead.
var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent,
proxy)
if r.status[0] in {'4','5'}:
@@ -549,7 +572,7 @@ proc post*(url: string, extraHeaders = "", body = "",
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil,
- multipart: MultipartData = nil): Response =
+ multipart: MultipartData = nil): Response {.deprecated.} =
## | POSTs ``body`` to the ``url`` and returns a ``Response`` object.
## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection.
@@ -558,6 +581,8 @@ proc post*(url: string, extraHeaders = "", body = "",
## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead.
let (mpHeaders, mpBody) = format(multipart)
template withNewLine(x): expr =
@@ -587,7 +612,8 @@ proc postContent*(url: string, extraHeaders = "", body = "",
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil,
- multipart: MultipartData = nil): string =
+ multipart: MultipartData = nil): string
+ {.deprecated.} =
## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``.
@@ -595,6 +621,9 @@ proc postContent*(url: string, extraHeaders = "", body = "",
## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
+ ##
+ ## **Deprecated since version 0.15.0**: use ``HttpClient.postContent``
+ ## instead.
var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout,
userAgent, proxy, multipart)
if r.status[0] in {'4','5'}:
@@ -617,46 +646,116 @@ proc downloadFile*(url: string, outputFilename: string,
else:
fileError("Unable to open file")
-proc generateHeaders(r: Uri, httpMethod: string,
- headers: StringTableRef, body: string): string =
- # TODO: Use this in the blocking HttpClient once it supports proxies.
- result = substr(httpMethod, len("http"))
- # TODO: Proxies
+proc generateHeaders(requestUrl: Uri, httpMethod: string,
+ headers: HttpHeaders, body: string, proxy: Proxy): string =
+ # GET
+ result = httpMethod.toUpper()
result.add ' '
- if r.path[0] != '/': result.add '/'
- result.add(r.path)
- if r.query.len > 0:
- result.add("?" & r.query)
+
+ if proxy.isNil:
+ # /path?query
+ if requestUrl.path[0] != '/': result.add '/'
+ result.add(requestUrl.path)
+ if requestUrl.query.len > 0:
+ result.add("?" & requestUrl.query)
+ else:
+ # Remove the 'http://' from the URL for CONNECT requests.
+ var modifiedUrl = requestUrl
+ modifiedUrl.scheme = ""
+ result.add($modifiedUrl)
+
+ # HTTP/1.1\c\l
result.add(" HTTP/1.1\c\L")
- if r.port == "":
- add(result, "Host: " & r.hostname & "\c\L")
+ # Host header.
+ if requestUrl.port == "":
+ add(result, "Host: " & requestUrl.hostname & "\c\L")
else:
- add(result, "Host: " & r.hostname & ":" & r.port & "\c\L")
+ add(result, "Host: " & requestUrl.hostname & ":" & requestUrl.port & "\c\L")
- add(result, "Connection: Keep-Alive\c\L")
+ # Connection header.
+ if not headers.hasKey("Connection"):
+ add(result, "Connection: Keep-Alive\c\L")
+
+ # Content length header.
if body.len > 0 and not headers.hasKey("Content-Length"):
add(result, "Content-Length: " & $body.len & "\c\L")
+
+ # Proxy auth header.
+ if not proxy.isNil and proxy.auth != "":
+ let auth = base64.encode(proxy.auth, newline = "")
+ add(result, "Proxy-Authorization: basic " & auth & "\c\L")
+
for key, val in headers:
add(result, key & ": " & val & "\c\L")
add(result, "\c\L")
type
- AsyncHttpClient* = ref object
- socket: AsyncSocket
+ ProgressChangedProc*[ReturnType] =
+ proc (total, progress, speed: BiggestInt):
+ ReturnType {.closure, gcsafe.}
+
+ HttpClientBase*[SocketType] = ref object
+ socket: SocketType
connected: bool
currentURL: Uri ## Where we are currently connected.
- headers*: StringTableRef
+ headers*: HttpHeaders ## Headers to send in requests.
maxRedirects: int
userAgent: string
+ timeout: int ## Only used for blocking HttpClient for now.
+ proxy: Proxy
+ ## ``nil`` or the callback to call when request progress changes.
+ when SocketType is Socket:
+ onProgressChanged*: ProgressChangedProc[void]
+ else:
+ onProgressChanged*: ProgressChangedProc[Future[void]]
when defined(ssl):
sslContext: net.SslContext
+ contentTotal: BiggestInt
+ contentProgress: BiggestInt
+ oneSecondProgress: BiggestInt
+ lastProgressReport: float
+
+type
+ HttpClient* = HttpClientBase[Socket]
+
+proc newHttpClient*(userAgent = defUserAgent,
+ maxRedirects = 5, sslContext = defaultSslContext, proxy: Proxy = nil,
+ timeout = -1): HttpClient =
+ ## Creates a new HttpClient instance.
+ ##
+ ## ``userAgent`` specifies the user agent that will be used when making
+ ## requests.
+ ##
+ ## ``maxRedirects`` specifies the maximum amount of redirects to follow,
+ ## default is 5.
+ ##
+ ## ``sslContext`` specifies the SSL context to use for HTTPS requests.
+ ##
+ ## ``proxy`` specifies an HTTP proxy to use for this HTTP client's
+ ## connections.
+ ##
+ ## ``timeout`` specifies the number of miliseconds to allow before a
+ ## ``TimeoutError`` is raised.
+ new result
+ result.headers = newHttpHeaders()
+ result.userAgent = userAgent
+ result.maxRedirects = maxRedirects
+ result.proxy = proxy
+ result.timeout = timeout
+ result.onProgressChanged = nil
+ when defined(ssl):
+ result.sslContext = sslContext
+
+type
+ AsyncHttpClient* = HttpClientBase[AsyncSocket]
{.deprecated: [PAsyncHttpClient: AsyncHttpClient].}
proc newAsyncHttpClient*(userAgent = defUserAgent,
- maxRedirects = 5, sslContext = defaultSslContext): AsyncHttpClient =
+ maxRedirects = 5, sslContext = defaultSslContext,
+ proxy: Proxy = nil): AsyncHttpClient =
## Creates a new AsyncHttpClient instance.
##
## ``userAgent`` specifies the user agent that will be used when making
@@ -666,29 +765,58 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
## default is 5.
##
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
+ ##
+ ## ``proxy`` specifies an HTTP proxy to use for this HTTP client's
+ ## connections.
new result
- result.headers = newStringTable(modeCaseInsensitive)
+ result.headers = newHttpHeaders()
result.userAgent = userAgent
result.maxRedirects = maxRedirects
+ result.proxy = proxy
+ result.timeout = -1 # TODO
+ result.onProgressChanged = nil
when defined(ssl):
result.sslContext = sslContext
-proc close*(client: AsyncHttpClient) =
+proc close*(client: HttpClient | AsyncHttpClient) =
## Closes any connections held by the HTTP client.
if client.connected:
client.socket.close()
client.connected = false
-proc recvFull(socket: AsyncSocket, size: int): Future[string] {.async.} =
+proc reportProgress(client: HttpClient | AsyncHttpClient,
+ progress: BiggestInt) {.multisync.} =
+ client.contentProgress += progress
+ client.oneSecondProgress += progress
+ if epochTime() - client.lastProgressReport >= 1.0:
+ if not client.onProgressChanged.isNil:
+ await client.onProgressChanged(client.contentTotal,
+ client.contentProgress,
+ client.oneSecondProgress)
+ client.oneSecondProgress = 0
+ client.lastProgressReport = epochTime()
+
+proc recvFull(client: HttpClient | AsyncHttpClient,
+ size: int, timeout: int): Future[string] {.multisync.} =
## Ensures that all the data requested is read and returned.
result = ""
while true:
if size == result.len: break
- let data = await socket.recv(size - result.len)
+
+ let remainingSize = size - result.len
+ let sizeToRecv = min(remainingSize, net.BufferSize)
+
+ when client.socket is Socket:
+ let data = client.socket.recv(sizeToRecv, timeout)
+ else:
+ let data = await client.socket.recv(sizeToRecv)
if data == "": break # We've been disconnected.
result.add data
-proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
+ await reportProgress(client, data.len)
+
+proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string]
+ {.multisync.} =
result = ""
while true:
var chunkSize = 0
@@ -714,17 +842,23 @@ proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
httpError("Invalid chunk size: " & chunkSizeStr)
inc(i)
if chunkSize <= 0:
- discard await recvFull(client.socket, 2) # Skip \c\L
+ discard await recvFull(client, 2, client.timeout) # Skip \c\L
break
- result.add await recvFull(client.socket, chunkSize)
- discard await recvFull(client.socket, 2) # Skip \c\L
+ result.add await recvFull(client, chunkSize, client.timeout)
+ discard await recvFull(client, 2, client.timeout) # Skip \c\L
# Trailer headers will only be sent if the request specifies that we want
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
-proc parseBody(client: AsyncHttpClient,
+proc parseBody(client: HttpClient | AsyncHttpClient,
headers: HttpHeaders,
- httpVersion: string): Future[string] {.async.} =
+ httpVersion: string): Future[string] {.multisync.} =
result = ""
+ # Reset progress from previous requests.
+ client.contentTotal = 0
+ client.contentProgress = 0
+ client.oneSecondProgress = 0
+ client.lastProgressReport = 0
+
if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = await parseChunks(client)
else:
@@ -733,8 +867,9 @@ proc parseBody(client: AsyncHttpClient,
var contentLengthHeader = headers.getOrDefault"Content-Length"
if contentLengthHeader != "":
var length = contentLengthHeader.parseint()
+ client.contentTotal = length
if length > 0:
- result = await client.socket.recvFull(length)
+ result = await client.recvFull(length, client.timeout)
if result == "":
httpError("Got disconnected while trying to read body.")
if result.len != length:
@@ -748,12 +883,12 @@ proc parseBody(client: AsyncHttpClient,
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
var buf = ""
while true:
- buf = await client.socket.recvFull(4000)
+ buf = await client.recvFull(4000, client.timeout)
if buf == "": break
result.add(buf)
-proc parseResponse(client: AsyncHttpClient,
- getBody: bool): Future[Response] {.async.} =
+proc parseResponse(client: HttpClient | AsyncHttpClient,
+ getBody: bool): Future[Response] {.multisync.} =
var parsedStatus = false
var linei = 0
var fullyRead = false
@@ -761,7 +896,10 @@ proc parseResponse(client: AsyncHttpClient,
result.headers = newHttpHeaders()
while true:
linei = 0
- line = await client.socket.recvLine()
+ when client is HttpClient:
+ line = await client.socket.recvLine(client.timeout)
+ else:
+ line = await client.socket.recvLine()
if line == "": break # We've been disconnected.
if line == "\c\L":
fullyRead = true
@@ -803,11 +941,17 @@ proc parseResponse(client: AsyncHttpClient,
else:
result.body = ""
-proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
+proc newConnection(client: HttpClient | AsyncHttpClient,
+ url: Uri) {.multisync.} =
if client.currentURL.hostname != url.hostname or
client.currentURL.scheme != url.scheme:
if client.connected: client.close()
- client.socket = newAsyncSocket()
+
+ when client is HttpClient:
+ client.socket = newSocket()
+ elif client is AsyncHttpClient:
+ client.socket = newAsyncSocket()
+ else: {.fatal: "Unsupported client type".}
# TODO: I should be able to write 'net.Port' here...
let port =
@@ -829,60 +973,106 @@ proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
client.currentURL = url
client.connected = true
-proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
- body = ""): Future[Response] {.async.} =
+proc request*(client: HttpClient | AsyncHttpClient, url: string,
+ httpMethod: string, body = ""): Future[Response] {.multisync.} =
## Connects to the hostname specified by the URL and performs a request
## using the custom method string specified by ``httpMethod``.
##
## Connection will kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The
## connection can be closed by using the ``close`` procedure.
- ##
- ## The returned future will complete once the request is completed.
- let r = parseUri(url)
- await newConnection(client, r)
+ let connectionUrl =
+ if client.proxy.isNil: parseUri(url) else: client.proxy.url
+ let requestUrl = parseUri(url)
+
+ let savedProxy = client.proxy # client's proxy may be overwritten.
+
+ if requestUrl.scheme == "https" and not client.proxy.isNil:
+ when defined(ssl):
+ client.proxy.url = connectionUrl
+ var connectUrl = requestUrl
+ connectUrl.scheme = "http"
+ connectUrl.port = "443"
+ let proxyResp = await request(client, $connectUrl, $HttpConnect)
+
+ if not proxyResp.status.startsWith("200"):
+ raise newException(HttpRequestError,
+ "The proxy server rejected a CONNECT request, " &
+ "so a secure connection could not be established.")
+ client.sslContext.wrapConnectedSocket(client.socket, handshakeAsClient)
+ client.proxy = nil
+ else:
+ raise newException(HttpRequestError,
+ "SSL support not available. Cannot connect to https site over proxy.")
+ else:
+ await newConnection(client, connectionUrl)
if not client.headers.hasKey("user-agent") and client.userAgent != "":
client.headers["User-Agent"] = client.userAgent
- var headers = generateHeaders(r, $httpMethod, client.headers, body)
+ var headers = generateHeaders(requestUrl, httpMethod,
+ client.headers, body, client.proxy)
await client.socket.send(headers)
if body != "":
await client.socket.send(body)
- result = await parseResponse(client, httpMethod != "httpHEAD")
+ result = await parseResponse(client,
+ httpMethod.toLower() notin ["head", "connect"])
-proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
- body = ""): Future[Response] =
+ # Restore the clients proxy in case it was overwritten.
+ client.proxy = savedProxy
+
+proc request*(client: HttpClient | AsyncHttpClient, url: string,
+ httpMethod = HttpGET, body = ""): Future[Response] {.multisync.} =
## Connects to the hostname specified by the URL and performs a request
## using the method specified.
##
- ## Connection will kept alive. Further requests on the same ``client`` to
+ ## Connection will be kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The
## connection can be closed by using the ``close`` procedure.
##
- ## The returned future will complete once the request is completed.
- result = request(client, url, $httpMethod, body)
+ ## When a request is made to a different hostname, the current connection will
+ ## be closed.
+ result = await request(client, url, $httpMethod, body)
-proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
+proc get*(client: HttpClient | AsyncHttpClient,
+ url: string): Future[Response] {.multisync.} =
## Connects to the hostname specified by the URL and performs a GET request.
##
## This procedure will follow redirects up to a maximum number of redirects
- ## specified in ``newAsyncHttpClient``.
- result = await client.request(url, httpGET)
+ ## specified in ``client.maxRedirects``.
+ result = await client.request(url, HttpGET)
+
+ # Handle redirects.
var lastURL = url
for i in 1..client.maxRedirects:
if result.status.redirection():
let redirectTo = getNewLocation(lastURL, result.headers)
- result = await client.request(redirectTo, httpGET)
+ result = await client.request(redirectTo, HttpGET)
lastURL = redirectTo
-proc post*(client: AsyncHttpClient, url: string, body = "", multipart: MultipartData = nil): Future[Response] {.async.} =
+proc getContent*(client: HttpClient | AsyncHttpClient,
+ url: string): Future[string] {.multisync.} =
+ ## Connects to the hostname specified by the URL and performs a GET request.
+ ##
+ ## This procedure will follow redirects up to a maximum number of redirects
+ ## specified in ``client.maxRedirects``.
+ ##
+ ## A ``HttpRequestError`` will be raised if the server responds with a
+ ## client error (status code 4xx) or a server error (status code 5xx).
+ let resp = await get(client, url)
+ if resp.code.is4xx or resp.code.is5xx:
+ raise newException(HttpRequestError, resp.status)
+ else:
+ return resp.body
+
+proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
+ multipart: MultipartData = nil): Future[Response] {.multisync.} =
## Connects to the hostname specified by the URL and performs a POST request.
##
## This procedure will follow redirects up to a maximum number of redirects
- ## specified in ``newAsyncHttpClient``.
+ ## specified in ``client.maxRedirects``.
let (mpHeader, mpBody) = format(multipart)
template withNewLine(x): expr =
@@ -895,45 +1085,29 @@ proc post*(client: AsyncHttpClient, url: string, body = "", multipart: Multipart
client.headers["Content-Type"] = mpHeader.split(": ")[1]
client.headers["Content-Length"] = $len(xb)
- result = await client.request(url, httpPOST, xb)
-
-when not defined(testing) and isMainModule:
- when true:
- # Async
- proc main() {.async.} =
- var client = newAsyncHttpClient()
- var resp = await client.request("http://picheta.me")
-
- echo("Got response: ", resp.status)
- echo("Body:\n")
- echo(resp.body)
-
- resp = await client.request("http://picheta.me/asfas.html")
- echo("Got response: ", resp.status)
-
- resp = await client.request("http://picheta.me/aboutme.html")
- echo("Got response: ", resp.status)
-
- resp = await client.request("http://nim-lang.org/")
- echo("Got response: ", resp.status)
-
- resp = await client.request("http://nim-lang.org/download.html")
- echo("Got response: ", resp.status)
-
- waitFor main()
+ result = await client.request(url, HttpPOST, xb)
+ # Handle redirects.
+ var lastURL = url
+ for i in 1..client.maxRedirects:
+ if result.status.redirection():
+ let redirectTo = getNewLocation(lastURL, result.headers)
+ var meth = if result.status != "307": HttpGet else: HttpPost
+ result = await client.request(redirectTo, meth, xb)
+ lastURL = redirectTo
+proc postContent*(client: HttpClient | AsyncHttpClient, url: string,
+ body = "",
+ multipart: MultipartData = nil): Future[string]
+ {.multisync.} =
+ ## Connects to the hostname specified by the URL and performs a POST request.
+ ##
+ ## This procedure will follow redirects up to a maximum number of redirects
+ ## specified in ``client.maxRedirects``.
+ ##
+ ## A ``HttpRequestError`` will be raised if the server responds with a
+ ## client error (status code 4xx) or a server error (status code 5xx).
+ let resp = await post(client, url, body, multipart)
+ if resp.code.is4xx or resp.code.is5xx:
+ raise newException(HttpRequestError, resp.status)
else:
- #downloadFile("http://force7.de/nim/index.html", "nimindex.html")
- #downloadFile("http://www.httpwatch.com/", "ChunkTest.html")
- #downloadFile("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com",
- # "validator.html")
-
- #var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
- # charset=%28detect+automatically%29&doctype=Inline&group=0")
-
- var data = newMultipartData()
- data["output"] = "soap12"
- data["uploaded_file"] = ("test.html", "text/html",
- "
test
")
-
- echo postContent("http://validator.w3.org/check", multipart=data)
+ return resp.body
diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim
index 562d16c19..8147f1c50 100644
--- a/lib/pure/httpcore.nim
+++ b/lib/pure/httpcore.nim
@@ -18,59 +18,87 @@ type
HttpHeaderValues* = distinct seq[string]
- HttpCode* = enum
- Http100 = "100 Continue",
- Http101 = "101 Switching Protocols",
- Http200 = "200 OK",
- Http201 = "201 Created",
- Http202 = "202 Accepted",
- Http203 = "203 Non-Authoritative Information",
- Http204 = "204 No Content",
- Http205 = "205 Reset Content",
- Http206 = "206 Partial Content",
- Http300 = "300 Multiple Choices",
- Http301 = "301 Moved Permanently",
- Http302 = "302 Found",
- Http303 = "303 See Other",
- Http304 = "304 Not Modified",
- Http305 = "305 Use Proxy",
- Http307 = "307 Temporary Redirect",
- Http400 = "400 Bad Request",
- Http401 = "401 Unauthorized",
- Http403 = "403 Forbidden",
- Http404 = "404 Not Found",
- Http405 = "405 Method Not Allowed",
- Http406 = "406 Not Acceptable",
- Http407 = "407 Proxy Authentication Required",
- Http408 = "408 Request Timeout",
- Http409 = "409 Conflict",
- Http410 = "410 Gone",
- Http411 = "411 Length Required",
- Http412 = "412 Precondition Failed",
- Http413 = "413 Request Entity Too Large",
- Http414 = "414 Request-URI Too Long",
- Http415 = "415 Unsupported Media Type",
- Http416 = "416 Requested Range Not Satisfiable",
- Http417 = "417 Expectation Failed",
- Http418 = "418 I'm a teapot",
- Http421 = "421 Misdirected Request",
- Http422 = "422 Unprocessable Entity",
- Http426 = "426 Upgrade Required",
- Http428 = "428 Precondition Required",
- Http429 = "429 Too Many Requests",
- Http431 = "431 Request Header Fields Too Large",
- Http451 = "451 Unavailable For Legal Reasons",
- Http500 = "500 Internal Server Error",
- Http501 = "501 Not Implemented",
- Http502 = "502 Bad Gateway",
- Http503 = "503 Service Unavailable",
- Http504 = "504 Gateway Timeout",
- Http505 = "505 HTTP Version Not Supported"
+ # The range starts at '0' so that we don't have to explicitly initialise
+ # it. See: http://irclogs.nim-lang.org/19-09-2016.html#19:48:27 for context.
+ HttpCode* = distinct range[0 .. 599]
HttpVersion* = enum
HttpVer11,
HttpVer10
+ HttpMethod* = enum ## the requested HttpMethod
+ HttpHead, ## Asks for the response identical to the one that would
+ ## correspond to a GET request, but without the response
+ ## body.
+ HttpGet, ## Retrieves the specified resource.
+ HttpPost, ## Submits data to be processed to the identified
+ ## resource. The data is included in the body of the
+ ## request.
+ HttpPut, ## Uploads a representation of the specified resource.
+ HttpDelete, ## Deletes the specified resource.
+ HttpTrace, ## Echoes back the received request, so that a client
+ ## can see what intermediate servers are adding or
+ ## changing in the request.
+ HttpOptions, ## Returns the HTTP methods that the server supports
+ ## for specified address.
+ HttpConnect, ## Converts the request connection to a transparent
+ ## TCP/IP tunnel, usually used for proxies.
+ HttpPatch ## Applies partial modifications to a resource.
+
+{.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost,
+ httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace,
+ httpOptions: HttpOptions, httpConnect: HttpConnect].}
+
+
+const
+ Http100* = HttpCode(100)
+ Http101* = HttpCode(101)
+ Http200* = HttpCode(200)
+ Http201* = HttpCode(201)
+ Http202* = HttpCode(202)
+ Http203* = HttpCode(203)
+ Http204* = HttpCode(204)
+ Http205* = HttpCode(205)
+ Http206* = HttpCode(206)
+ Http300* = HttpCode(300)
+ Http301* = HttpCode(301)
+ Http302* = HttpCode(302)
+ Http303* = HttpCode(303)
+ Http304* = HttpCode(304)
+ Http305* = HttpCode(305)
+ Http307* = HttpCode(307)
+ Http400* = HttpCode(400)
+ Http401* = HttpCode(401)
+ Http403* = HttpCode(403)
+ Http404* = HttpCode(404)
+ Http405* = HttpCode(405)
+ Http406* = HttpCode(406)
+ Http407* = HttpCode(407)
+ Http408* = HttpCode(408)
+ Http409* = HttpCode(409)
+ Http410* = HttpCode(410)
+ Http411* = HttpCode(411)
+ Http412* = HttpCode(412)
+ Http413* = HttpCode(413)
+ Http414* = HttpCode(414)
+ Http415* = HttpCode(415)
+ Http416* = HttpCode(416)
+ Http417* = HttpCode(417)
+ Http418* = HttpCode(418)
+ Http421* = HttpCode(421)
+ Http422* = HttpCode(422)
+ Http426* = HttpCode(426)
+ Http428* = HttpCode(428)
+ Http429* = HttpCode(429)
+ Http431* = HttpCode(431)
+ Http451* = HttpCode(451)
+ Http500* = HttpCode(500)
+ Http501* = HttpCode(501)
+ Http502* = HttpCode(502)
+ Http503* = HttpCode(503)
+ Http504* = HttpCode(504)
+ Http505* = HttpCode(505)
+
const headerLimit* = 10_000
proc newHttpHeaders*(): HttpHeaders =
@@ -81,7 +109,7 @@ proc newHttpHeaders*(keyValuePairs:
openarray[tuple[key: string, val: string]]): HttpHeaders =
var pairs: seq[tuple[key: string, val: seq[string]]] = @[]
for pair in keyValuePairs:
- pairs.add((pair.key.toLower(), @[pair.val]))
+ pairs.add((pair.key.toLowerAscii(), @[pair.val]))
new result
result.table = newTable[string, seq[string]](pairs)
@@ -96,7 +124,7 @@ proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
##
## To access multiple values of a key, use the overloaded ``[]`` below or
## to get all of them access the ``table`` field directly.
- return headers.table[key.toLower].HttpHeaderValues
+ return headers.table[key.toLowerAscii].HttpHeaderValues
converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0]
@@ -105,26 +133,26 @@ proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
## Returns the ``i``'th value associated with the given key. If there are
## no values associated with the key or the ``i``'th value doesn't exist,
## an exception is raised.
- return headers.table[key.toLower][i]
+ return headers.table[key.toLowerAscii][i]
proc `[]=`*(headers: HttpHeaders, key, value: string) =
## Sets the header entries associated with ``key`` to the specified value.
## Replaces any existing values.
- headers.table[key.toLower] = @[value]
+ headers.table[key.toLowerAscii] = @[value]
proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
## Sets the header entries associated with ``key`` to the specified list of
## values.
## Replaces any existing values.
- headers.table[key.toLower] = value
+ headers.table[key.toLowerAscii] = value
proc add*(headers: HttpHeaders, key, value: string) =
## Adds the specified value to the specified key. Appends to any existing
## values associated with the key.
- if not headers.table.hasKey(key.toLower):
- headers.table[key.toLower] = @[value]
+ if not headers.table.hasKey(key.toLowerAscii):
+ headers.table[key.toLowerAscii] = @[value]
else:
- headers.table[key.toLower].add(value)
+ headers.table[key.toLowerAscii].add(value)
iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
## Yields each key, value pair.
@@ -136,10 +164,10 @@ proc contains*(values: HttpHeaderValues, value: string): bool =
## Determines if ``value`` is one of the values inside ``values``. Comparison
## is performed without case sensitivity.
for val in seq[string](values):
- if val.toLower == value.toLower: return true
+ if val.toLowerAscii == value.toLowerAscii: return true
proc hasKey*(headers: HttpHeaders, key: string): bool =
- return headers.table.hasKey(key.toLower())
+ return headers.table.hasKey(key.toLowerAscii())
proc getOrDefault*(headers: HttpHeaders, key: string,
default = @[""].HttpHeaderValues): HttpHeaderValues =
@@ -188,6 +216,90 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int],
of HttpVer10: 0
result = protocol.major == major and protocol.minor == minor
+proc contains*(methods: set[HttpMethod], x: string): bool =
+ return parseEnum[HttpMethod](x) in methods
+
+proc `$`*(code: HttpCode): string =
+ ## Converts the specified ``HttpCode`` into a HTTP status.
+ ##
+ ## For example:
+ ##
+ ## .. code-block:: nim
+ ## doAssert($Http404 == "404 Not Found")
+ case code.int
+ of 100: "100 Continue"
+ of 101: "101 Switching Protocols"
+ of 200: "200 OK"
+ of 201: "201 Created"
+ of 202: "202 Accepted"
+ of 203: "203 Non-Authoritative Information"
+ of 204: "204 No Content"
+ of 205: "205 Reset Content"
+ of 206: "206 Partial Content"
+ of 300: "300 Multiple Choices"
+ of 301: "301 Moved Permanently"
+ of 302: "302 Found"
+ of 303: "303 See Other"
+ of 304: "304 Not Modified"
+ of 305: "305 Use Proxy"
+ of 307: "307 Temporary Redirect"
+ of 400: "400 Bad Request"
+ of 401: "401 Unauthorized"
+ of 403: "403 Forbidden"
+ of 404: "404 Not Found"
+ of 405: "405 Method Not Allowed"
+ of 406: "406 Not Acceptable"
+ of 407: "407 Proxy Authentication Required"
+ of 408: "408 Request Timeout"
+ of 409: "409 Conflict"
+ of 410: "410 Gone"
+ of 411: "411 Length Required"
+ of 412: "412 Precondition Failed"
+ of 413: "413 Request Entity Too Large"
+ of 414: "414 Request-URI Too Long"
+ of 415: "415 Unsupported Media Type"
+ of 416: "416 Requested Range Not Satisfiable"
+ of 417: "417 Expectation Failed"
+ of 418: "418 I'm a teapot"
+ of 421: "421 Misdirected Request"
+ of 422: "422 Unprocessable Entity"
+ of 426: "426 Upgrade Required"
+ of 428: "428 Precondition Required"
+ of 429: "429 Too Many Requests"
+ of 431: "431 Request Header Fields Too Large"
+ of 451: "451 Unavailable For Legal Reasons"
+ of 500: "500 Internal Server Error"
+ of 501: "501 Not Implemented"
+ of 502: "502 Bad Gateway"
+ of 503: "503 Service Unavailable"
+ of 504: "504 Gateway Timeout"
+ of 505: "505 HTTP Version Not Supported"
+ else: $(int(code))
+
+proc `==`*(a, b: HttpCode): bool {.borrow.}
+
+proc `==`*(rawCode: string, code: HttpCode): bool =
+ return rawCode.toLower() == ($code).toLower()
+
+proc is2xx*(code: HttpCode): bool =
+ ## Determines whether ``code`` is a 2xx HTTP status code.
+ return code.int in {200 .. 299}
+
+proc is3xx*(code: HttpCode): bool =
+ ## Determines whether ``code`` is a 3xx HTTP status code.
+ return code.int in {300 .. 399}
+
+proc is4xx*(code: HttpCode): bool =
+ ## Determines whether ``code`` is a 4xx HTTP status code.
+ return code.int in {400 .. 499}
+
+proc is5xx*(code: HttpCode): bool =
+ ## Determines whether ``code`` is a 5xx HTTP status code.
+ return code.int in {500 .. 599}
+
+proc `$`*(httpMethod: HttpMethod): string =
+ return (system.`$`(httpMethod))[4 .. ^1].toUpper()
+
when isMainModule:
var test = newHttpHeaders()
test["Connection"] = @["Upgrade", "Close"]
diff --git a/lib/pure/includes/asyncfutures.nim b/lib/pure/includes/asyncfutures.nim
new file mode 100644
index 000000000..d78464a91
--- /dev/null
+++ b/lib/pure/includes/asyncfutures.nim
@@ -0,0 +1,295 @@
+
+# TODO: This shouldn't need to be included, but should ideally be exported.
+type
+ FutureBase* = ref object of RootObj ## Untyped future.
+ cb: proc () {.closure,gcsafe.}
+ finished: bool
+ error*: ref Exception ## Stored exception
+ errorStackTrace*: string
+ when not defined(release):
+ stackTrace: string ## For debugging purposes only.
+ id: int
+ fromProc: string
+
+ Future*[T] = ref object of FutureBase ## Typed future.
+ value: T ## Stored value
+
+ FutureVar*[T] = distinct Future[T]
+
+ FutureError* = object of Exception
+ cause*: FutureBase
+
+{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
+
+when not defined(release):
+ var currentID = 0
+
+proc callSoon*(cbproc: proc ()) {.gcsafe.}
+
+proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
+ ## Creates a new future.
+ ##
+ ## Specifying ``fromProc``, which is a string specifying the name of the proc
+ ## that this future belongs to, is a good habit as it helps with debugging.
+ new(result)
+ result.finished = false
+ when not defined(release):
+ result.stackTrace = getStackTrace()
+ result.id = currentID
+ result.fromProc = fromProc
+ currentID.inc()
+
+proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
+ ## Create a new ``FutureVar``. This Future type is ideally suited for
+ ## situations where you want to avoid unnecessary allocations of Futures.
+ ##
+ ## Specifying ``fromProc``, which is a string specifying the name of the proc
+ ## that this future belongs to, is a good habit as it helps with debugging.
+ result = FutureVar[T](newFuture[T](fromProc))
+
+proc clean*[T](future: FutureVar[T]) =
+ ## Resets the ``finished`` status of ``future``.
+ Future[T](future).finished = false
+ Future[T](future).error = nil
+
+proc checkFinished[T](future: Future[T]) =
+ ## Checks whether `future` is finished. If it is then raises a
+ ## ``FutureError``.
+ when not defined(release):
+ if future.finished:
+ var msg = ""
+ msg.add("An attempt was made to complete a Future more than once. ")
+ msg.add("Details:")
+ msg.add("\n Future ID: " & $future.id)
+ msg.add("\n Created in proc: " & future.fromProc)
+ msg.add("\n Stack trace to moment of creation:")
+ msg.add("\n" & indent(future.stackTrace.strip(), 4))
+ when T is string:
+ msg.add("\n Contents (string): ")
+ msg.add("\n" & indent(future.value.repr, 4))
+ msg.add("\n Stack trace to moment of secondary completion:")
+ msg.add("\n" & indent(getStackTrace().strip(), 4))
+ var err = newException(FutureError, msg)
+ err.cause = future
+ raise err
+
+proc complete*[T](future: Future[T], val: T) =
+ ## Completes ``future`` with value ``val``.
+ #assert(not future.finished, "Future already finished, cannot finish twice.")
+ checkFinished(future)
+ assert(future.error == nil)
+ future.value = val
+ future.finished = true
+ if future.cb != nil:
+ future.cb()
+
+proc complete*(future: Future[void]) =
+ ## Completes a void ``future``.
+ #assert(not future.finished, "Future already finished, cannot finish twice.")
+ checkFinished(future)
+ assert(future.error == nil)
+ future.finished = true
+ if future.cb != nil:
+ future.cb()
+
+proc complete*[T](future: FutureVar[T]) =
+ ## Completes a ``FutureVar``.
+ template fut: expr = Future[T](future)
+ checkFinished(fut)
+ assert(fut.error == nil)
+ fut.finished = true
+ if fut.cb != nil:
+ fut.cb()
+
+proc complete*[T](future: FutureVar[T], val: T) =
+ ## Completes a ``FutureVar`` with value ``val``.
+ ##
+ ## Any previously stored value will be overwritten.
+ template fut: expr = Future[T](future)
+ checkFinished(fut)
+ assert(fut.error == nil)
+ fut.finished = true
+ fut.value = val
+ if fut.cb != nil:
+ fut.cb()
+
+proc fail*[T](future: Future[T], error: ref Exception) =
+ ## Completes ``future`` with ``error``.
+ #assert(not future.finished, "Future already finished, cannot finish twice.")
+ checkFinished(future)
+ future.finished = true
+ future.error = error
+ future.errorStackTrace =
+ if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
+ if future.cb != nil:
+ future.cb()
+ else:
+ # This is to prevent exceptions from being silently ignored when a future
+ # is discarded.
+ # TODO: This may turn out to be a bad idea.
+ # Turns out this is a bad idea.
+ #raise error
+ discard
+
+proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
+ ## Sets the callback proc to be called when the future completes.
+ ##
+ ## If future has already completed then ``cb`` will be called immediately.
+ ##
+ ## **Note**: You most likely want the other ``callback`` setter which
+ ## passes ``future`` as a param to the callback.
+ future.cb = cb
+ if future.finished:
+ callSoon(future.cb)
+
+proc `callback=`*[T](future: Future[T],
+ cb: proc (future: Future[T]) {.closure,gcsafe.}) =
+ ## Sets the callback proc to be called when the future completes.
+ ##
+ ## If future has already completed then ``cb`` will be called immediately.
+ future.callback = proc () = cb(future)
+
+proc injectStacktrace[T](future: Future[T]) =
+ # TODO: Come up with something better.
+ when not defined(release):
+ var msg = ""
+ msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
+
+ if not future.errorStackTrace.isNil and future.errorStackTrace != "":
+ msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
+ else:
+ msg.add("\n Empty or nil stack trace.")
+ future.error.msg.add(msg)
+
+proc read*[T](future: Future[T] | FutureVar[T]): T =
+ ## Retrieves the value of ``future``. Future must be finished otherwise
+ ## this function will fail with a ``ValueError`` exception.
+ ##
+ ## If the result of the future is an error then that error will be raised.
+ {.push hint[ConvFromXtoItselfNotNeeded]: off.}
+ let fut = Future[T](future)
+ {.pop.}
+ if fut.finished:
+ if fut.error != nil:
+ injectStacktrace(fut)
+ raise fut.error
+ when T isnot void:
+ return fut.value
+ else:
+ # TODO: Make a custom exception type for this?
+ raise newException(ValueError, "Future still in progress.")
+
+proc readError*[T](future: Future[T]): ref Exception =
+ ## Retrieves the exception stored in ``future``.
+ ##
+ ## An ``ValueError`` exception will be thrown if no exception exists
+ ## in the specified Future.
+ if future.error != nil: return future.error
+ else:
+ raise newException(ValueError, "No error in future.")
+
+proc mget*[T](future: FutureVar[T]): var T =
+ ## Returns a mutable value stored in ``future``.
+ ##
+ ## Unlike ``read``, this function will not raise an exception if the
+ ## Future has not been finished.
+ result = Future[T](future).value
+
+proc finished*[T](future: Future[T] | FutureVar[T]): bool =
+ ## Determines whether ``future`` has completed.
+ ##
+ ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
+ (Future[T](future)).finished
+
+proc failed*(future: FutureBase): bool =
+ ## Determines whether ``future`` completed with an error.
+ return future.error != nil
+
+proc asyncCheck*[T](future: Future[T]) =
+ ## Sets a callback on ``future`` which raises an exception if the future
+ ## finished with an error.
+ ##
+ ## This should be used instead of ``discard`` to discard void futures.
+ future.callback =
+ proc () =
+ if future.failed:
+ injectStacktrace(future)
+ raise future.error
+
+proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
+ ## Returns a future which will complete once both ``fut1`` and ``fut2``
+ ## complete.
+ var retFuture = newFuture[void]("asyncdispatch.`and`")
+ fut1.callback =
+ proc () =
+ if not retFuture.finished:
+ if fut1.failed: retFuture.fail(fut1.error)
+ elif fut2.finished: retFuture.complete()
+ fut2.callback =
+ proc () =
+ if not retFuture.finished:
+ if fut2.failed: retFuture.fail(fut2.error)
+ elif fut1.finished: retFuture.complete()
+ return retFuture
+
+proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
+ ## Returns a future which will complete once either ``fut1`` or ``fut2``
+ ## complete.
+ var retFuture = newFuture[void]("asyncdispatch.`or`")
+ proc cb[X](fut: Future[X]) =
+ if fut.failed: retFuture.fail(fut.error)
+ if not retFuture.finished: retFuture.complete()
+ fut1.callback = cb[T]
+ fut2.callback = cb[Y]
+ return retFuture
+
+proc all*[T](futs: varargs[Future[T]]): auto =
+ ## Returns a future which will complete once
+ ## all futures in ``futs`` complete.
+ ##
+ ## If the awaited futures are not ``Future[void]``, the returned future
+ ## will hold the values of all awaited futures in a sequence.
+ ##
+ ## If the awaited futures *are* ``Future[void]``,
+ ## this proc returns ``Future[void]``.
+
+ when T is void:
+ var
+ retFuture = newFuture[void]("asyncdispatch.all")
+ completedFutures = 0
+
+ let totalFutures = len(futs)
+
+ for fut in futs:
+ fut.callback = proc(f: Future[T]) =
+ if f.failed:
+ retFuture.fail(f.error)
+ elif not retFuture.finished:
+ inc(completedFutures)
+
+ if completedFutures == totalFutures:
+ retFuture.complete()
+
+ return retFuture
+
+ else:
+ var
+ retFuture = newFuture[seq[T]]("asyncdispatch.all")
+ retValues = newSeq[T](len(futs))
+ completedFutures = 0
+
+ for i, fut in futs:
+ proc setCallback(i: int) =
+ fut.callback = proc(f: Future[T]) =
+ if f.failed:
+ retFuture.fail(f.error)
+ elif not retFuture.finished:
+ retValues[i] = f.read()
+ inc(completedFutures)
+
+ if completedFutures == len(retValues):
+ retFuture.complete(retValues)
+
+ setCallback(i)
+
+ return retFuture
diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim
index a5d5d2c01..adb3497ac 100644
--- a/lib/pure/ioselectors.nim
+++ b/lib/pure/ioselectors.nim
@@ -44,14 +44,21 @@ when defined(nimdoc):
Event* {.pure.} = enum
## An enum which hold event types
- Read, ## Descriptor is available for read
- Write, ## Descriptor is available for write
- Timer, ## Timer descriptor is completed
- Signal, ## Signal is raised
- Process, ## Process is finished
- Vnode, ## Currently not supported
- User, ## User event is raised
- Error ## Error happens while waiting, for descriptor
+ Read, ## Descriptor is available for read
+ Write, ## Descriptor is available for write
+ Timer, ## Timer descriptor is completed
+ Signal, ## Signal is raised
+ Process, ## Process is finished
+ Vnode, ## BSD specific file change happens
+ User, ## User event is raised
+ Error, ## Error happens while waiting, for descriptor
+ VnodeWrite, ## NOTE_WRITE (BSD specific, write to file occured)
+ VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occured)
+ VnodeExtend, ## NOTE_EXTEND (BSD specific, file extended)
+ VnodeAttrib, ## NOTE_ATTRIB (BSD specific, file attributes changed)
+ VnodeLink, ## NOTE_LINK (BSD specific, file link count changed)
+ VnodeRename, ## NOTE_RENAME (BSD specific, file renamed)
+ VnodeRevoke ## NOTE_REVOKE (BSD specific, file revoke occured)
ReadyKey*[T] = object
## An object which holds result for descriptor
@@ -107,6 +114,15 @@ when defined(nimdoc):
## ``data`` application-defined data, which to be passed, when
## ``ev`` happens.
+ proc registerVnode*[T](s: Selector[T], fd: cint, events: set[Event],
+ data: T) =
+ ## Registers selector BSD/MacOSX specific vnode events for file
+ ## descriptor ``fd`` and events ``events``.
+ ## ``data`` application-defined data, which to be passed, when
+ ## vnode event happens.
+ ##
+ ## This function is supported only by BSD and MacOSX.
+
proc newSelectEvent*(): SelectEvent =
## Creates new event ``SelectEvent``.
@@ -194,7 +210,9 @@ else:
deallocShared(cast[pointer](sa))
type
Event* {.pure.} = enum
- Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot
+ Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot,
+ VnodeWrite, VnodeDelete, VnodeExtend, VnodeAttrib, VnodeLink,
+ VnodeRename, VnodeRevoke
ReadyKey*[T] = object
fd* : int
diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim
index 92b2cdc07..4bc6e9d51 100644
--- a/lib/pure/ioselects/ioselectors_epoll.nim
+++ b/lib/pure/ioselects/ioselectors_epoll.nim
@@ -407,8 +407,8 @@ proc selectInto*[T](s: Selector[T], timeout: int,
inc(i)
continue
elif Event.User in skey.events:
- var data: uint = 0
- if posix.read(fdi.cint, addr data, sizeof(uint)) != sizeof(uint):
+ var data: uint64 = 0
+ if posix.read(fdi.cint, addr data, sizeof(uint64)) != sizeof(uint64):
let err = osLastError()
if err == OSErrorCode(EAGAIN):
inc(i)
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
index 3e86f19aa..cdaeeae26 100644
--- a/lib/pure/ioselects/ioselectors_kqueue.nim
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -262,6 +262,30 @@ proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
inc(s.count)
+template processVnodeEvents(events: set[Event]): cuint =
+ var rfflags = 0.cuint
+ if events == {Event.VnodeWrite, Event.VnodeDelete, Event.VnodeExtend,
+ Event.VnodeAttrib, Event.VnodeLink, Event.VnodeRename,
+ Event.VnodeRevoke}:
+ rfflags = NOTE_DELETE or NOTE_WRITE or NOTE_EXTEND or NOTE_ATTRIB or
+ NOTE_LINK or NOTE_RENAME or NOTE_REVOKE
+ else:
+ if Event.VnodeDelete in events: rfflags = rfflags or NOTE_DELETE
+ if Event.VnodeWrite in events: rfflags = rfflags or NOTE_WRITE
+ if Event.VnodeExtend in events: rfflags = rfflags or NOTE_EXTEND
+ if Event.VnodeAttrib in events: rfflags = rfflags or NOTE_ATTRIB
+ if Event.VnodeLink in events: rfflags = rfflags or NOTE_LINK
+ if Event.VnodeRename in events: rfflags = rfflags or NOTE_RENAME
+ if Event.VnodeRevoke in events: rfflags = rfflags or NOTE_REVOKE
+ rfflags
+
+proc registerVnode*[T](s: Selector[T], fd: cint, events: set[Event], data: T) =
+ let fdi = fd.int
+ setKey(s, fdi, fdi, {Event.Vnode} + events, 0, data)
+ var fflags = processVnodeEvents(events)
+ modifyKQueue(s, fdi.uint, EVFILT_VNODE, EV_ADD or EV_CLEAR, fflags, 0, nil)
+ inc(s.count)
+
proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
let fdi = int(fd)
s.checkFd(fdi)
@@ -295,6 +319,9 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
discard posix.close(cint(pkey.key.fd))
modifyKQueue(s, fdi.uint, EVFILT_PROC, EV_DELETE, 0, 0, nil)
dec(s.count)
+ elif Event.Vnode in pkey.events:
+ modifyKQueue(s, fdi.uint, EVFILT_VNODE, EV_DELETE, 0, 0, nil)
+ dec(s.count)
elif Event.User in pkey.events:
modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
dec(s.count)
@@ -392,6 +419,20 @@ proc selectInto*[T](s: Selector[T], timeout: int,
of EVFILT_VNODE:
pkey = addr(s.fds[kevent.ident.int])
pkey.key.events = {Event.Vnode}
+ if (kevent.fflags and NOTE_DELETE) != 0:
+ pkey.key.events.incl(Event.VnodeDelete)
+ if (kevent.fflags and NOTE_WRITE) != 0:
+ pkey.key.events.incl(Event.VnodeWrite)
+ if (kevent.fflags and NOTE_EXTEND) != 0:
+ pkey.key.events.incl(Event.VnodeExtend)
+ if (kevent.fflags and NOTE_ATTRIB) != 0:
+ pkey.key.events.incl(Event.VnodeAttrib)
+ if (kevent.fflags and NOTE_LINK) != 0:
+ pkey.key.events.incl(Event.VnodeLink)
+ if (kevent.fflags and NOTE_RENAME) != 0:
+ pkey.key.events.incl(Event.VnodeRename)
+ if (kevent.fflags and NOTE_REVOKE) != 0:
+ pkey.key.events.incl(Event.VnodeRevoke)
of EVFILT_SIGNAL:
pkey = addr(s.fds[cast[int](kevent.udata)])
pkey.key.events = {Event.Signal}
diff --git a/lib/pure/ioselects/ioselectors_poll.nim b/lib/pure/ioselects/ioselectors_poll.nim
index d2a0a1273..56be35c70 100644
--- a/lib/pure/ioselects/ioselectors_poll.nim
+++ b/lib/pure/ioselects/ioselectors_poll.nim
@@ -245,7 +245,7 @@ proc selectInto*[T](s: Selector[T], timeout: int,
skey.key.events.incl(Event.Read)
if Event.User in skey.events:
var data: uint64 = 0
- if posix.read(fd, addr data, sizeof(int)) != sizeof(int):
+ if posix.read(fd, addr data, sizeof(uint64)) != sizeof(uint64):
let err = osLastError()
if err != OSErrorCode(EAGAIN):
raiseOSError(osLastError())
diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim
index f8099f9a0..ddb70b507 100644
--- a/lib/pure/ioselects/ioselectors_select.nim
+++ b/lib/pure/ioselects/ioselectors_select.nim
@@ -155,9 +155,9 @@ when defined(windows):
result.wsock = wsock
proc setEvent*(ev: SelectEvent) =
- var data: int = 1
+ var data: uint64 = 1
if winlean.send(ev.wsock, cast[pointer](addr data),
- cint(sizeof(int)), 0) != sizeof(int):
+ cint(sizeof(uint64)), 0) != sizeof(uint64):
raiseOSError(osLastError())
proc close*(ev: SelectEvent) =
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index 19947fbc2..0b7908c02 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -24,6 +24,8 @@
## jobj["test"] = newJFloat(0.7) # create or update
## echo($jobj["test"].fnum)
## echo($jobj["key2"].bval)
+## echo jobj{"missing key"}.getFNum(0.1) # read a float value using a default
+## jobj{"a", "b", "c"} = newJFloat(3.3) # created nested keys
##
## Results in:
##
@@ -580,7 +582,7 @@ type
of JNull:
nil
of JObject:
- fields*: Table[string, JsonNode]
+ fields*: OrderedTable[string, JsonNode]
of JArray:
elems*: seq[JsonNode]
@@ -630,7 +632,7 @@ proc newJObject*(): JsonNode =
## Creates a new `JObject JsonNode`
new(result)
result.kind = JObject
- result.fields = initTable[string, JsonNode](4)
+ result.fields = initOrderedTable[string, JsonNode](4)
proc newJArray*(): JsonNode =
## Creates a new `JArray JsonNode`
@@ -670,8 +672,8 @@ proc getBVal*(n: JsonNode, default: bool = false): bool =
else: return n.bval
proc getFields*(n: JsonNode,
- default = initTable[string, JsonNode](4)):
- Table[string, JsonNode] =
+ default = initOrderedTable[string, JsonNode](4)):
+ OrderedTable[string, JsonNode] =
## Retrieves the key, value pairs of a `JObject JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JObject``, or if ``n`` is nil.
@@ -760,12 +762,12 @@ proc toJson(x: NimNode): NimNode {.compiletime.} =
result = prefix(result, "%")
-macro `%*`*(x: expr): expr =
+macro `%*`*(x: untyped): untyped =
## Convert an expression to a JsonNode directly, without having to specify
## `%` for every element.
result = toJson(x)
-proc `==`* (a,b: JsonNode): bool =
+proc `==`* (a, b: JsonNode): bool =
## Check two nodes for equality
if a.isNil:
if b.isNil: return true
@@ -773,23 +775,29 @@ proc `==`* (a,b: JsonNode): bool =
elif b.isNil or a.kind != b.kind:
return false
else:
- return case a.kind
+ case a.kind
of JString:
- a.str == b.str
+ result = a.str == b.str
of JInt:
- a.num == b.num
+ result = a.num == b.num
of JFloat:
- a.fnum == b.fnum
+ result = a.fnum == b.fnum
of JBool:
- a.bval == b.bval
+ result = a.bval == b.bval
of JNull:
- true
+ result = true
of JArray:
- a.elems == b.elems
+ result = a.elems == b.elems
of JObject:
- a.fields == b.fields
+ # we cannot use OrderedTable's equality here as
+ # the order does not matter for equality here.
+ if a.fields.len != b.fields.len: return false
+ for key, val in a.fields:
+ if not b.fields.hasKey(key): return false
+ if b.fields[key] != val: return false
+ result = true
-proc hash*(n: Table[string, JsonNode]): Hash {.noSideEffect.}
+proc hash*(n: OrderedTable[string, JsonNode]): Hash {.noSideEffect.}
proc hash*(n: JsonNode): Hash =
## Compute the hash for a JSON node
@@ -807,9 +815,9 @@ proc hash*(n: JsonNode): Hash =
of JString:
result = hash(n.str)
of JNull:
- result = hash(0)
+ result = Hash(0)
-proc hash*(n: Table[string, JsonNode]): Hash =
+proc hash*(n: OrderedTable[string, JsonNode]): Hash =
for key, val in n:
result = result xor (hash(key) !& hash(val))
result = !$result
@@ -1195,19 +1203,19 @@ else:
proc len(x: JSObject): int =
assert x.getVarType == JArray
asm """
- return `x`.length;
+ `result` = `x`.length;
"""
proc `[]`(x: JSObject, y: string): JSObject =
assert x.getVarType == JObject
asm """
- return `x`[`y`];
+ `result` = `x`[`y`];
"""
proc `[]`(x: JSObject, y: int): JSObject =
assert x.getVarType == JArray
asm """
- return `x`[`y`];
+ `result` = `x`[`y`];
"""
proc convertObject(x: JSObject): JsonNode =
diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim
index 6a27e6af8..b23b1e5bb 100644
--- a/lib/pure/logging.nim
+++ b/lib/pure/logging.nim
@@ -47,7 +47,9 @@
## **Warning:** The global list of handlers is a thread var, this means that
## the handlers must be re-added in each thread.
-import strutils, os, times
+import strutils, times
+when not defined(js):
+ import os
type
Level* = enum ## logging level
@@ -77,21 +79,24 @@ type
ConsoleLogger* = ref object of Logger ## logger that writes the messages to the
## console
- FileLogger* = ref object of Logger ## logger that writes the messages to a file
- file*: File ## the wrapped file.
+when not defined(js):
+ type
+ FileLogger* = ref object of Logger ## logger that writes the messages to a file
+ file*: File ## the wrapped file.
- RollingFileLogger* = ref object of FileLogger ## logger that writes the
- ## messages to a file and
- ## performs log rotation
- maxLines: int # maximum number of lines
- curLine : int
- baseName: string # initial filename
- baseMode: FileMode # initial file mode
- logFiles: int # how many log files already created, e.g. basename.1, basename.2...
- bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
+ RollingFileLogger* = ref object of FileLogger ## logger that writes the
+ ## messages to a file and
+ ## performs log rotation
+ maxLines: int # maximum number of lines
+ curLine : int
+ baseName: string # initial filename
+ baseMode: FileMode # initial file mode
+ logFiles: int # how many log files already created, e.g. basename.1, basename.2...
+ bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
-{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger,
- PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
+ {.deprecated: [PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
+
+{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger].}
var
level {.threadvar.}: Level ## global log filter
@@ -112,7 +117,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
else:
inc(i)
var v = ""
- var app = getAppFilename()
+ let app = when defined(js): "" else: getAppFilename()
while frmt[i] in IdentChars:
v.add(toLower(frmt[i]))
inc(i)
@@ -121,8 +126,10 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str
of "time": result.add(getClockStr())
of "datetime": result.add(getDateStr() & "T" & getClockStr())
of "app": result.add(app)
- of "appdir": result.add(app.splitFile.dir)
- of "appname": result.add(app.splitFile.name)
+ of "appdir":
+ when not defined(js): result.add(app.splitFile.dir)
+ of "appname":
+ when not defined(js): result.add(app.splitFile.name)
of "levelid": result.add(LevelNames[level][0])
of "levelname": result.add(LevelNames[level])
else: discard
@@ -139,19 +146,13 @@ method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
## Logs to the console using ``logger`` only.
if level >= logging.level and level >= logger.levelThreshold:
- writeLine(stdout, substituteLog(logger.fmtStr, level, args))
- if level in {lvlError, lvlFatal}: flushFile(stdout)
-
-method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
- ## Logs to a file using ``logger`` only.
- if level >= logging.level and level >= logger.levelThreshold:
- writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
- if level in {lvlError, lvlFatal}: flushFile(logger.file)
-
-proc defaultFilename*(): string =
- ## Returns the default filename for a logger.
- var (path, name, _) = splitFile(getAppFilename())
- result = changeFileExt(path / name, "log")
+ let ln = substituteLog(logger.fmtStr, level, args)
+ when defined(js):
+ let cln: cstring = ln
+ {.emit: "console.log(`cln`);".}
+ else:
+ writeLine(stdout, ln)
+ if level in {lvlError, lvlFatal}: flushFile(stdout)
proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger =
## Creates a new console logger. This logger logs to the console.
@@ -159,87 +160,99 @@ proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): Console
result.fmtStr = fmtStr
result.levelThreshold = levelThreshold
-proc newFileLogger*(filename = defaultFilename(),
- mode: FileMode = fmAppend,
- levelThreshold = lvlAll,
- fmtStr = defaultFmtStr,
- bufSize: int = -1): FileLogger =
- ## Creates a new file logger. This logger logs to a file.
- ## Use ``bufSize`` as size of the output buffer when writing the file
- ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
- new(result)
- result.levelThreshold = levelThreshold
- result.file = open(filename, mode, bufSize = bufSize)
- result.fmtStr = fmtStr
+when not defined(js):
+ method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
+ ## Logs to a file using ``logger`` only.
+ if level >= logging.level and level >= logger.levelThreshold:
+ writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
+ if level in {lvlError, lvlFatal}: flushFile(logger.file)
-# ------
+ proc defaultFilename*(): string =
+ ## Returns the default filename for a logger.
+ var (path, name, _) = splitFile(getAppFilename())
+ result = changeFileExt(path / name, "log")
-proc countLogLines(logger: RollingFileLogger): int =
- result = 0
- for line in logger.file.lines():
- result.inc()
+ proc newFileLogger*(filename = defaultFilename(),
+ mode: FileMode = fmAppend,
+ levelThreshold = lvlAll,
+ fmtStr = defaultFmtStr,
+ bufSize: int = -1): FileLogger =
+ ## Creates a new file logger. This logger logs to a file.
+ ## Use ``bufSize`` as size of the output buffer when writing the file
+ ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
+ new(result)
+ result.levelThreshold = levelThreshold
+ result.file = open(filename, mode, bufSize = bufSize)
+ result.fmtStr = fmtStr
-proc countFiles(filename: string): int =
- # Example: file.log.1
- result = 0
- let (dir, name, ext) = splitFile(filename)
- for kind, path in walkDir(dir):
- if kind == pcFile:
- let llfn = name & ext & ExtSep
- if path.extractFilename.startsWith(llfn):
- let numS = path.extractFilename[llfn.len .. ^1]
- try:
- let num = parseInt(numS)
- if num > result:
- result = num
- except ValueError: discard
+ # ------
-proc newRollingFileLogger*(filename = defaultFilename(),
- mode: FileMode = fmReadWrite,
- levelThreshold = lvlAll,
- fmtStr = defaultFmtStr,
- maxLines = 1000,
- bufSize: int = -1): RollingFileLogger =
- ## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines
- ## a new log file will be started and the old will be renamed.
- ## Use ``bufSize`` as size of the output buffer when writing the file
- ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
- new(result)
- result.levelThreshold = levelThreshold
- result.fmtStr = fmtStr
- result.maxLines = maxLines
- result.bufSize = bufSize
- result.file = open(filename, mode, bufSize=result.bufSize)
- result.curLine = 0
- result.baseName = filename
- result.baseMode = mode
+ proc countLogLines(logger: RollingFileLogger): int =
+ result = 0
+ for line in logger.file.lines():
+ result.inc()
- result.logFiles = countFiles(filename)
+ proc countFiles(filename: string): int =
+ # Example: file.log.1
+ result = 0
+ let (dir, name, ext) = splitFile(filename)
+ for kind, path in walkDir(dir):
+ if kind == pcFile:
+ let llfn = name & ext & ExtSep
+ if path.extractFilename.startsWith(llfn):
+ let numS = path.extractFilename[llfn.len .. ^1]
+ try:
+ let num = parseInt(numS)
+ if num > result:
+ result = num
+ except ValueError: discard
- if mode == fmAppend:
- # We need to get a line count because we will be appending to the file.
- result.curLine = countLogLines(result)
+ proc newRollingFileLogger*(filename = defaultFilename(),
+ mode: FileMode = fmReadWrite,
+ levelThreshold = lvlAll,
+ fmtStr = defaultFmtStr,
+ maxLines = 1000,
+ bufSize: int = -1): RollingFileLogger =
+ ## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines
+ ## a new log file will be started and the old will be renamed.
+ ## Use ``bufSize`` as size of the output buffer when writing the file
+ ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
+ new(result)
+ result.levelThreshold = levelThreshold
+ result.fmtStr = fmtStr
+ result.maxLines = maxLines
+ result.bufSize = bufSize
+ result.file = open(filename, mode, bufSize=result.bufSize)
+ result.curLine = 0
+ result.baseName = filename
+ result.baseMode = mode
-proc rotate(logger: RollingFileLogger) =
- let (dir, name, ext) = splitFile(logger.baseName)
- for i in countdown(logger.logFiles, 0):
- let srcSuff = if i != 0: ExtSep & $i else: ""
- moveFile(dir / (name & ext & srcSuff),
- dir / (name & ext & ExtSep & $(i+1)))
+ result.logFiles = countFiles(filename)
-method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
- ## Logs to a file using rolling ``logger`` only.
- if level >= logging.level and level >= logger.levelThreshold:
- if logger.curLine >= logger.maxLines:
- logger.file.close()
- rotate(logger)
- logger.logFiles.inc
- logger.curLine = 0
- logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
+ if mode == fmAppend:
+ # We need to get a line count because we will be appending to the file.
+ result.curLine = countLogLines(result)
- writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
- if level in {lvlError, lvlFatal}: flushFile(logger.file)
- logger.curLine.inc
+ proc rotate(logger: RollingFileLogger) =
+ let (dir, name, ext) = splitFile(logger.baseName)
+ for i in countdown(logger.logFiles, 0):
+ let srcSuff = if i != 0: ExtSep & $i else: ""
+ moveFile(dir / (name & ext & srcSuff),
+ dir / (name & ext & ExtSep & $(i+1)))
+
+ method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
+ ## Logs to a file using rolling ``logger`` only.
+ if level >= logging.level and level >= logger.levelThreshold:
+ if logger.curLine >= logger.maxLines:
+ logger.file.close()
+ rotate(logger)
+ logger.logFiles.inc
+ logger.curLine = 0
+ logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
+
+ writeLine(logger.file, substituteLog(logger.fmtStr, level, args))
+ if level in {lvlError, lvlFatal}: flushFile(logger.file)
+ logger.curLine.inc
# --------
@@ -323,10 +336,11 @@ proc getLogFilter*(): Level =
when not defined(testing) and isMainModule:
var L = newConsoleLogger()
- var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
- var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
+ when not defined(js):
+ var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
+ var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
+ addHandler(fL)
+ addHandler(rL)
addHandler(L)
- addHandler(fL)
- addHandler(rL)
for i in 0 .. 25:
info("hello", i)
diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim
index 134581a06..36e6cf52f 100644
--- a/lib/pure/marshal.nim
+++ b/lib/pure/marshal.nim
@@ -31,7 +31,7 @@
##
## **Note**: The ``to`` and ``$$`` operations are available at compile-time!
-import streams, typeinfo, json, intsets, tables
+import streams, typeinfo, json, intsets, tables, unicode
proc ptrToInt(x: pointer): int {.inline.} =
result = cast[int](x) # don't skip alignment
@@ -92,7 +92,15 @@ proc storeAny(s: Stream, a: Any, stored: var IntSet) =
of akString:
var x = getString(a)
if isNil(x): s.write("null")
- else: s.write(escapeJson(x))
+ elif x.validateUtf8() == -1: s.write(escapeJson(x))
+ else:
+ s.write("[")
+ var i = 0
+ for c in x:
+ if i > 0: s.write(", ")
+ s.write($ord(c))
+ inc(i)
+ s.write("]")
of akInt..akInt64, akUInt..akUInt64: s.write($getBiggestInt(a))
of akFloat..akFloat128: s.write($getBiggestFloat(a))
@@ -207,6 +215,18 @@ proc loadAny(p: var JsonParser, a: Any, t: var Table[BiggestInt, pointer]) =
of jsonString:
setString(a, p.str)
next(p)
+ of jsonArrayStart:
+ next(p)
+ var str = ""
+ while p.kind == jsonInt:
+ let code = p.getInt()
+ if code < 0 or code > 255:
+ raiseParseErr(p, "invalid charcode: " & $code)
+ str.add(chr(code))
+ next(p)
+ if p.kind == jsonArrayEnd: next(p)
+ else: raiseParseErr(p, "an array of charcodes expected for string")
+ setString(a, str)
else: raiseParseErr(p, "string expected")
of akInt..akInt64, akUInt..akUInt64:
if p.kind == jsonInt:
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index bd208761b..d4f239c49 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -106,9 +106,13 @@ when defineSsl:
{.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext,
TSSLAcceptResult: SSLAcceptResult].}
+else:
+ type
+ SslContext* = void # TODO: Workaround #4797.
const
BufferSize*: int = 4000 ## size of a buffered socket's buffer
+ MaxLineLength* = 1_000_000
type
SocketImpl* = object ## socket type
@@ -206,7 +210,7 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
result.currPos = 0
# Set SO_NOSIGPIPE on OS X.
- when defined(macosx):
+ when defined(macosx) and not defined(nimdoc):
setSockOptInt(fd, SOL_SOCKET, SO_NOSIGPIPE, 1)
proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
@@ -966,6 +970,22 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
socket.socketError(result, lastError = lastError)
data.setLen(result)
+proc recv*(socket: Socket, size: int, timeout = -1,
+ flags = {SocketFlag.SafeDisconn}): string {.inline.} =
+ ## Higher-level version of ``recv`` which returns a string.
+ ##
+ ## When ``""`` is returned the socket's connection has been closed.
+ ##
+ ## This function will throw an EOS exception when an error occurs.
+ ##
+ ## A timeout may be specified in milliseconds, if enough data is not received
+ ## within the time specified an ETimeout exception will be raised.
+ ##
+ ##
+ ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
+ result = newString(size)
+ discard recv(socket, result, size, timeout, flags)
+
proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
if socket.isBuffered:
result = 1
@@ -987,7 +1007,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
result = recv(socket.fd, addr(c), 1, MSG_PEEK)
proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
- flags = {SocketFlag.SafeDisconn}) {.
+ flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.
tags: [ReadIOEffect, TimeEffect].} =
## Reads a line of data from ``socket``.
##
@@ -1002,6 +1022,10 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised.
##
+ ## The ``maxLength`` parameter determines the maximum amount of characters
+ ## that can be read before a ``ValueError`` is raised. This prevents Denial
+ ## of Service (DOS) attacks.
+ ##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
template addNLIfEmpty() =
@@ -1035,6 +1059,36 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
return
add(line.string, c)
+ # Verify that this isn't a DOS attack: #3847.
+ if line.string.len > maxLength:
+ let msg = "recvLine received more than the specified `maxLength` " &
+ "allowed."
+ raise newException(ValueError, msg)
+
+proc recvLine*(socket: Socket, timeout = -1,
+ flags = {SocketFlag.SafeDisconn},
+ maxLength = MaxLineLength): TaintedString =
+ ## Reads a line of data from ``socket``.
+ ##
+ ## If a full line is read ``\r\L`` is not
+ ## added to the result, however if solely ``\r\L`` is read then the result
+ ## will be set to it.
+ ##
+ ## If the socket is disconnected, the result will be set to ``""``.
+ ##
+ ## An EOS exception will be raised in the case of a socket error.
+ ##
+ ## A timeout can be specified in milliseconds, if data is not received within
+ ## the specified time an ETimeout exception will be raised.
+ ##
+ ## The ``maxLength`` parameter determines the maximum amount of characters
+ ## that can be read before a ``ValueError`` is raised. This prevents Denial
+ ## of Service (DOS) attacks.
+ ##
+ ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
+ result = ""
+ readLine(socket, result, timeout, flags, maxLength)
+
proc recvFrom*(socket: Socket, data: var string, length: int,
address: var string, port: var Port, flags = 0'i32): int {.
tags: [ReadIOEffect].} =
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 1e474f4d4..cdbe170cc 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -580,9 +580,9 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
when useWinUnicode:
let s = newWideCString(source)
let d = newWideCString(dest)
- if moveFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
+ if moveFileW(s, d) == 0'i32: raiseOSError(osLastError())
else:
- if moveFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
+ if moveFileA(source, dest) == 0'i32: raiseOSError(osLastError())
else:
if c_rename(source, dest) != 0'i32:
raiseOSError(osLastError(), $strerror(errno))
@@ -595,12 +595,12 @@ when not declared(ENOENT) and not defined(Windows):
when defined(Windows):
when useWinUnicode:
- template deleteFile(file: expr): expr {.immediate.} = deleteFileW(file)
- template setFileAttributes(file, attrs: expr): expr {.immediate.} =
+ template deleteFile(file: untyped): untyped = deleteFileW(file)
+ template setFileAttributes(file, attrs: untyped): untyped =
setFileAttributesW(file, attrs)
else:
- template deleteFile(file: expr): expr {.immediate.} = deleteFileA(file)
- template setFileAttributes(file, attrs: expr): expr {.immediate.} =
+ template deleteFile(file: untyped): untyped = deleteFileA(file)
+ template setFileAttributes(file, attrs: untyped): untyped =
setFileAttributesA(file, attrs)
proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
@@ -865,6 +865,20 @@ type
{.deprecated: [TPathComponent: PathComponent].}
+
+when defined(posix):
+ proc getSymlinkFileKind(path: string): PathComponent =
+ # Helper function.
+ var s: Stat
+ assert(path != "")
+ if stat(path, s) < 0'i32:
+ raiseOSError(osLastError())
+ if S_ISDIR(s.st_mode):
+ result = pcLinkToDir
+ else:
+ result = pcLinkToFile
+
+
proc staticWalkDir(dir: string; relative: bool): seq[
tuple[kind: PathComponent, path: string]] =
discard
@@ -931,13 +945,15 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
if x.d_type == DT_DIR: k = pcDir
if x.d_type == DT_LNK:
if dirExists(y): k = pcLinkToDir
- else: k = succ(k)
+ else: k = pcLinkToFile
yield (k, y)
continue
if lstat(y, s) < 0'i32: break
- if S_ISDIR(s.st_mode): k = pcDir
- if S_ISLNK(s.st_mode): k = succ(k)
+ if S_ISDIR(s.st_mode):
+ k = pcDir
+ elif S_ISLNK(s.st_mode):
+ k = getSymlinkFileKind(y)
yield (k, y)
iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {.
@@ -1047,7 +1063,7 @@ proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
proc createSymlink*(src, dest: string) =
## Create a symbolic link at `dest` which points to the item specified
- ## by `src`. On most operating systems, will fail if a lonk
+ ## by `src`. On most operating systems, will fail if a link already exists.
##
## **Warning**:
## Some OS's (such as Microsoft Windows) restrict the creation
@@ -1070,8 +1086,8 @@ proc createHardlink*(src, dest: string) =
## Create a hard link at `dest` which points to the item specified
## by `src`.
##
- ## **Warning**: Most OS's restrict the creation of hard links to
- ## root users (administrators) .
+ ## **Warning**: Some OS's restrict the creation of hard links to
+ ## root users (administrators).
when defined(Windows):
when useWinUnicode:
var wSrc = newWideCString(src)
@@ -1502,7 +1518,7 @@ type
lastWriteTime*: Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems!
-template rawToFormalFileInfo(rawInfo, formalInfo): untyped =
+template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
## Transforms the native file info structure into the one nim uses.
## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix
@@ -1557,8 +1573,11 @@ template rawToFormalFileInfo(rawInfo, formalInfo): untyped =
checkAndIncludeMode(S_IXOTH, fpOthersExec)
formalInfo.kind = pcFile
- if S_ISDIR(rawInfo.st_mode): formalInfo.kind = pcDir
- if S_ISLNK(rawInfo.st_mode): formalInfo.kind.inc()
+ if S_ISDIR(rawInfo.st_mode):
+ formalInfo.kind = pcDir
+ elif S_ISLNK(rawInfo.st_mode):
+ assert(path != "") # symlinks can't occur for file handles
+ formalInfo.kind = getSymlinkFileKind(path)
proc getFileInfo*(handle: FileHandle): FileInfo =
## Retrieves file information for the file object represented by the given
@@ -1574,12 +1593,12 @@ proc getFileInfo*(handle: FileHandle): FileInfo =
var realHandle = get_osfhandle(handle)
if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, result)
+ rawToFormalFileInfo(rawInfo, "", result)
else:
var rawInfo: Stat
if fstat(handle, rawInfo) < 0'i32:
raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, result)
+ rawToFormalFileInfo(rawInfo, "", result)
proc getFileInfo*(file: File): FileInfo =
if file.isNil:
@@ -1608,7 +1627,7 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo =
raiseOSError(osLastError())
if getFileInformationByHandle(handle, addr rawInfo) == 0:
raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, result)
+ rawToFormalFileInfo(rawInfo, path, result)
discard closeHandle(handle)
else:
var rawInfo: Stat
@@ -1618,7 +1637,7 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo =
else:
if lstat(path, rawInfo) < 0'i32:
raiseOSError(osLastError())
- rawToFormalFileInfo(rawInfo, result)
+ rawToFormalFileInfo(rawInfo, path, result)
proc isHidden*(path: string): bool =
## Determines whether a given path is hidden or not. Returns false if the
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index 7378520e3..6c2debb1b 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -48,7 +48,7 @@ type
inHandle, outHandle, errHandle: FileHandle
inStream, outStream, errStream: Stream
id: Pid
- exitCode: cint
+ exitStatus: cint
options: set[ProcessOption]
Process* = ref ProcessObj ## represents an operating system process
@@ -731,7 +731,7 @@ elif not defined(useNimRtl):
pStdin, pStdout, pStderr: array[0..1, cint]
new(result)
result.options = options
- result.exitCode = -3 # for ``waitForExit``
+ result.exitStatus = -3 # for ``waitForExit``
if poParentStreams notin options:
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
pipe(pStderr) != 0'i32:
@@ -957,13 +957,10 @@ elif not defined(useNimRtl):
proc running(p: Process): bool =
var ret : int
- when not defined(freebsd):
- ret = waitpid(p.id, p.exitCode, WNOHANG)
- else:
- var status : cint = 1
- ret = waitpid(p.id, status, WNOHANG)
- if WIFEXITED(status):
- p.exitCode = status
+ var status : cint = 1
+ ret = waitpid(p.id, status, WNOHANG)
+ if WIFEXITED(status):
+ p.exitStatus = status
if ret == 0: return true # Can't establish status. Assume running.
result = ret == int(p.id)
@@ -980,11 +977,12 @@ elif not defined(useNimRtl):
import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int =
- if p.exitCode != -3: return p.exitCode
+ if p.exitStatus != -3: return int(p.exitStatus) shr 8
if timeout == -1:
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ var status : cint = 1
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
else:
var kqFD = kqueue()
if kqFD == -1:
@@ -1004,6 +1002,7 @@ elif not defined(useNimRtl):
try:
while true:
+ var status : cint = 1
var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
addr(tmspec))
if count < 0:
@@ -1014,22 +1013,22 @@ elif not defined(useNimRtl):
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
break
else:
if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
break
else:
raiseOSError(osLastError())
finally:
discard posix.close(kqFD)
- result = int(p.exitCode) shr 8
+ result = int(p.exitStatus) shr 8
else:
import times
@@ -1061,15 +1060,16 @@ elif not defined(useNimRtl):
s.tv_sec = b.tv_sec
s.tv_nsec = b.tv_nsec
- #if waitPid(p.id, p.exitCode, 0) == int(p.id):
+ #if waitPid(p.id, p.exitStatus, 0) == int(p.id):
# ``waitPid`` fails if the process is not running anymore. But then
- # ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is
+ # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is
# initialized with -3, wrong success exit codes are prevented.
- if p.exitCode != -3: return p.exitCode
+ if p.exitStatus != -3: return int(p.exitStatus) shr 8
if timeout == -1:
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ var status : cint = 1
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
else:
var nmask, omask: Sigset
var sinfo: SigInfo
@@ -1100,9 +1100,10 @@ elif not defined(useNimRtl):
let res = sigtimedwait(nmask, sinfo, tmspec)
if res == SIGCHLD:
if sinfo.si_pid == p.id:
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ var status : cint = 1
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
break
else:
# we have SIGCHLD, but not for process we are waiting,
@@ -1122,9 +1123,10 @@ elif not defined(useNimRtl):
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
- if waitpid(p.id, p.exitCode, 0) < 0:
- p.exitCode = -3
+ var status : cint = 1
+ if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
+ p.exitStatus = status
break
else:
raiseOSError(err)
@@ -1136,15 +1138,19 @@ elif not defined(useNimRtl):
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
- result = int(p.exitCode) shr 8
+ result = int(p.exitStatus) shr 8
proc peekExitCode(p: Process): int =
- if p.exitCode != -3: return p.exitCode
- var ret = waitpid(p.id, p.exitCode, WNOHANG)
+ var status : cint = 1
+ if p.exitStatus != -3: return int(p.exitStatus) shr 8
+ var ret = waitpid(p.id, status, WNOHANG)
var b = ret == int(p.id)
if b: result = -1
- if not WIFEXITED(p.exitCode): result = -1
- else: result = p.exitCode.int shr 8
+ if WIFEXITED(status):
+ p.exitStatus = status
+ result = p.exitStatus.int shr 8
+ else:
+ result = -1
proc createStream(stream: var Stream, handle: var FileHandle,
fileMode: FileMode) =
diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim
index bf134f2ae..c2ba2b1f3 100644
--- a/lib/pure/rationals.nim
+++ b/lib/pure/rationals.nim
@@ -349,4 +349,4 @@ when isMainModule:
assert toRational(0.98765432) == 12345679 // 12500000
assert toRational(0.1, 1000000) == 1 // 10
assert toRational(0.9, 1000000) == 9 // 10
- assert toRational(PI) == 80143857 // 25510582
+ #assert toRational(PI) == 80143857 // 25510582
diff --git a/lib/pure/securehash.nim b/lib/pure/securehash.nim
index a30fad92a..1f00ce8d3 100644
--- a/lib/pure/securehash.nim
+++ b/lib/pure/securehash.nim
@@ -15,30 +15,6 @@ type
Sha1Digest = array[0 .. Sha1DigestSize-1, uint8]
SecureHash* = distinct Sha1Digest
-proc sha1(src: string) : Sha1Digest
-
-proc secureHash*(str: string): SecureHash = SecureHash(sha1(str))
-proc secureHashFile*(filename: string): SecureHash = secureHash(readFile(filename))
-proc `$`*(self: SecureHash): string =
- result = ""
- for v in Sha1Digest(self):
- result.add(toHex(int(v), 2))
-
-proc parseSecureHash*(hash: string): SecureHash =
- for i in 0.. = 0: result = t.data[index].val
else: result = ""
-proc hasKey*(t: StringTableRef, key: string): bool {.rtl, extern: "nst$1".} =
+proc hasKey*(t: StringTableRef, key: string): bool {.rtlFunc, extern: "nst$1".} =
## returns true iff `key` is in the table `t`.
result = rawGet(t, key) >= 0
@@ -145,7 +152,7 @@ proc enlarge(t: StringTableRef) =
if not isNil(t.data[i].key): rawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n)
-proc `[]=`*(t: StringTableRef, key, val: string) {.rtl, extern: "nstPut".} =
+proc `[]=`*(t: StringTableRef, key, val: string) {.rtlFunc, extern: "nstPut".} =
## puts a (key, value)-pair into `t`.
var index = rawGet(t, key)
if index >= 0:
@@ -164,14 +171,17 @@ proc raiseFormatException(s: string) =
proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string =
if hasKey(t, key): return t.getOrDefault(key)
# hm difficult: assume safety in taint mode here. XXX This is dangerous!
- if useEnvironment in flags: result = os.getEnv(key).string
- else: result = ""
+ when defined(js):
+ result = ""
+ else:
+ if useEnvironment in flags: result = os.getEnv(key).string
+ else: result = ""
if result.len == 0:
if useKey in flags: result = '$' & key
elif useEmpty notin flags: raiseFormatException(key)
proc newStringTable*(mode: StringTableMode): StringTableRef {.
- rtl, extern: "nst$1".} =
+ rtlFunc, extern: "nst$1".} =
## creates a new string table that is empty.
new(result)
result.mode = mode
@@ -189,7 +199,7 @@ proc clear*(s: StringTableRef, mode: StringTableMode) =
proc newStringTable*(keyValuePairs: varargs[string],
mode: StringTableMode): StringTableRef {.
- rtl, extern: "nst$1WithPairs".} =
+ rtlFunc, extern: "nst$1WithPairs".} =
## creates a new string table with given key value pairs.
## Example::
## var mytab = newStringTable("key1", "val1", "key2", "val2",
@@ -202,7 +212,7 @@ proc newStringTable*(keyValuePairs: varargs[string],
proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]],
mode: StringTableMode = modeCaseSensitive): StringTableRef {.
- rtl, extern: "nst$1WithTableConstr".} =
+ rtlFunc, extern: "nst$1WithTableConstr".} =
## creates a new string table with given key value pairs.
## Example::
## var mytab = newStringTable({"key1": "val1", "key2": "val2"},
@@ -211,7 +221,7 @@ proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]],
for key, val in items(keyValuePairs): result[key] = val
proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {.
- rtl, extern: "nstFormat".} =
+ rtlFunc, extern: "nstFormat".} =
## The `%` operator for string tables.
const
PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
@@ -240,7 +250,7 @@ proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {.
add(result, f[i])
inc(i)
-proc `$`*(t: StringTableRef): string {.rtl, extern: "nstDollar".} =
+proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
## The `$` operator for string tables.
if t.len == 0:
result = "{:}"
diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim
index 1f34ec07e..d4734c3e3 100644
--- a/lib/pure/terminal.nim
+++ b/lib/pure/terminal.nim
@@ -60,6 +60,21 @@ when defined(windows):
lpConsoleScreenBufferInfo: ptr CONSOLE_SCREEN_BUFFER_INFO): WINBOOL{.stdcall,
dynlib: "kernel32", importc: "GetConsoleScreenBufferInfo".}
+ proc terminalWidthIoctl*(handles: openArray[Handle]): int =
+ var csbi: CONSOLE_SCREEN_BUFFER_INFO
+ for h in handles:
+ if getConsoleScreenBufferInfo(h, addr csbi) != 0:
+ return int(csbi.srWindow.Right - csbi.srWindow.Left + 1)
+ return 0
+
+ proc terminalWidth*(): int =
+ var w: int = 0
+ w = terminalWidthIoctl([ getStdHandle(STD_INPUT_HANDLE),
+ getStdHandle(STD_OUTPUT_HANDLE),
+ getStdHandle(STD_ERROR_HANDLE) ] )
+ if w > 0: return w
+ return 80
+
proc setConsoleCursorPosition(hConsoleOutput: HANDLE,
dwCursorPosition: COORD): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "SetConsoleCursorPosition".}
@@ -123,7 +138,7 @@ when defined(windows):
if f == stderr: hStderr else: hStdout
else:
- import termios
+ import termios, posix, os, parseutils
proc setRaw(fd: FileHandle, time: cint = TCSAFLUSH) =
var mode: Termios
@@ -137,6 +152,33 @@ else:
mode.c_cc[VTIME] = 0.cuchar
discard fd.tcsetattr(time, addr mode)
+ proc terminalWidthIoctl*(fds: openArray[int]): int =
+ ## Returns terminal width from first fd that supports the ioctl.
+
+ var win: IOctl_WinSize
+ for fd in fds:
+ if ioctl(cint(fd), TIOCGWINSZ, addr win) != -1:
+ return int(win.ws_col)
+ return 0
+
+ var L_ctermid{.importc, header: "".}: cint
+ proc terminalWidth*(): int =
+ ## Returns some reasonable terminal width from either standard file
+ ## descriptors, controlling terminal, environment variables or tradition.
+
+ var w = terminalWidthIoctl([0, 1, 2]) #Try standard file descriptors
+ if w > 0: return w
+ var cterm = newString(L_ctermid) #Try controlling tty
+ var fd = open(ctermid(cstring(cterm)), O_RDONLY)
+ if fd != -1:
+ w = terminalWidthIoctl([ int(fd) ])
+ discard close(fd)
+ if w > 0: return w
+ var s = getEnv("COLUMNS") #Try standard env var
+ if len(s) > 0 and parseInt(string(s), w) > 0 and w > 0:
+ return w
+ return 80 #Finally default to venerable value
+
proc setCursorPos*(f: File, x, y: int) =
## Sets the terminal's cursor to the (x,y) position.
## (0,0) is the upper left of the screen.
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index d6eb29e1c..b78a2b966 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -1248,8 +1248,11 @@ proc parse*(value, layout: string): TimeInfo =
else:
parseToken(info, token, value, j)
token = ""
- # Reset weekday as it might not have been provided and the default may be wrong
- info.weekday = getLocalTime(toTime(info)).weekday
+ # Reset weekday (might not have been provided and the default may be wrong)
+ # and yearday (is never provided directly and therefore probably wrong)
+ let processed = getLocalTime(toTime(info))
+ info.weekday = processed.weekday
+ info.yearday = processed.yearday
return info
# Leap year calculations are adapted from:
diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim
index 0cbe8de7a..6ba966816 100644
--- a/lib/pure/unicode.nim
+++ b/lib/pure/unicode.nim
@@ -24,7 +24,7 @@ proc `<=%`*(a, b: Rune): bool = return int(a) <=% int(b)
proc `<%`*(a, b: Rune): bool = return int(a) <% int(b)
proc `==`*(a, b: Rune): bool = return int(a) == int(b)
-template ones(n: expr): expr = ((1 shl n)-1)
+template ones(n: untyped): untyped = ((1 shl n)-1)
proc runeLen*(s: string): int {.rtl, extern: "nuc$1".} =
## Returns the number of Unicode characters of the string ``s``
@@ -49,7 +49,7 @@ proc runeLenAt*(s: string, i: Natural): int =
elif ord(s[i]) shr 1 == 0b1111110: result = 6
else: result = 1
-template fastRuneAt*(s: string, i: int, result: expr, doInc = true) =
+template fastRuneAt*(s: string, i: int, result: untyped, doInc = true) =
## Returns the Unicode character ``s[i]`` in ``result``. If ``doInc == true``
## ``i`` is incremented by the number of bytes that have been processed.
bind ones
@@ -1628,7 +1628,7 @@ proc reversed*(s: string): string =
blockPos = 0
r: Rune
- template reverseUntil(pos): stmt =
+ template reverseUntil(pos) =
var j = pos - 1
while j > blockPos:
result[newPos] = s[j]
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index 92ddc3e75..0fc2e441e 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -77,6 +77,15 @@ checkpoints = @[]
proc shouldRun(testName: string): bool =
result = true
+proc startSuite(name: string) =
+ template rawPrint() = echo("\n[Suite] ", name)
+ when not defined(ECMAScript):
+ if colorOutput:
+ styledEcho styleBright, fgBlue, "\n[Suite] ", fgWhite, name
+ else: rawPrint()
+ else: rawPrint()
+
+
template suite*(name, body) {.dirty.} =
## Declare a test suite identified by `name` with optional ``setup``
## and/or ``teardown`` section.
@@ -103,9 +112,11 @@ template suite*(name, body) {.dirty.} =
##
## .. code-block::
##
- ## [OK] 2 + 2 = 4
- ## [OK] (2 + -2) != 4
+ ## [Suite] test suite for addition
+ ## [OK] 2 + 2 = 4
+ ## [OK] (2 + -2) != 4
block:
+ bind startSuite
template setup(setupBody: untyped) {.dirty.} =
var testSetupIMPLFlag = true
template testSetupIMPL: untyped {.dirty.} = setupBody
@@ -114,14 +125,16 @@ template suite*(name, body) {.dirty.} =
var testTeardownIMPLFlag = true
template testTeardownIMPL: untyped {.dirty.} = teardownBody
+ let testInSuiteImplFlag = true
+ startSuite name
body
-proc testDone(name: string, s: TestStatus) =
+proc testDone(name: string, s: TestStatus, indent: bool) =
if s == FAILED:
programResult += 1
-
+ let prefix = if indent: " " else: ""
if outputLevel != PRINT_NONE and (outputLevel == PRINT_ALL or s == FAILED):
- template rawPrint() = echo("[", $s, "] ", name)
+ template rawPrint() = echo(prefix, "[", $s, "] ", name)
when not defined(ECMAScript):
if colorOutput and not defined(ECMAScript):
var color = case s
@@ -129,7 +142,7 @@ proc testDone(name: string, s: TestStatus) =
of FAILED: fgRed
of SKIPPED: fgYellow
else: fgWhite
- styledEcho styleBright, color, "[", $s, "] ", fgWhite, name
+ styledEcho styleBright, color, prefix, "[", $s, "] ", fgWhite, name
else:
rawPrint()
else:
@@ -168,7 +181,7 @@ template test*(name, body) {.dirty.} =
fail()
finally:
- testDone name, testStatusIMPL
+ testDone name, testStatusIMPL, declared(testInSuiteImplFlag)
proc checkpoint*(msg: string) =
## Set a checkpoint identified by `msg`. Upon test failure all
@@ -198,8 +211,9 @@ template fail* =
##
## outputs "Checkpoint A" before quitting.
bind checkpoints
+ let prefix = if declared(testInSuiteImplFlag): " " else: ""
for msg in items(checkpoints):
- echo msg
+ echo prefix, msg
when not defined(ECMAScript):
if abortOnError: quit(1)
diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim
index a9fc8998a..3c6eb14e3 100644
--- a/lib/pure/xmltree.nim
+++ b/lib/pure/xmltree.nim
@@ -226,6 +226,18 @@ proc noWhitespace(n: XmlNode): bool =
proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2) =
## adds the textual representation of `n` to `result`.
+
+ proc addEscapedAttr(result: var string, s: string) =
+ # `addEscaped` alternative with less escaped characters.
+ # Only to be used for escaping attribute values enclosed in double quotes!
+ for c in items(s):
+ case c
+ of '<': result.add("<")
+ of '>': result.add(">")
+ of '&': result.add("&")
+ of '"': result.add(""")
+ else: result.add(c)
+
if n == nil: return
case n.k
of xnElement:
@@ -236,7 +248,7 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2) =
result.add(' ')
result.add(key)
result.add("=\"")
- result.addEscaped(val)
+ result.addEscapedAttr(val)
result.add('"')
if n.len > 0:
result.add('>')
@@ -381,6 +393,5 @@ proc findAll*(n: XmlNode, tag: string): seq[XmlNode] =
findAll(n, tag, result)
when isMainModule:
- let link = "http://nim-lang.org"
- assert """Nim rules.""" ==
+ assert """Nim rules.""" ==
$(<>a(href="http://nim-lang.org", newText("Nim rules.")))
diff --git a/lib/stdlib.nimble b/lib/stdlib.nimble
index 4b0066ee8..bd1b78efe 100644
--- a/lib/stdlib.nimble
+++ b/lib/stdlib.nimble
@@ -1,6 +1,6 @@
[Package]
name = "stdlib"
-version = "0.14.3"
+version = "0.15.1"
author = "Dominik Picheta"
description = "Nim's standard library."
license = "MIT"
diff --git a/lib/system.nim b/lib/system.nim
index 26feb5ce8..fae111ce2 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -226,6 +226,7 @@ proc high*[T](x: T): T {.magic: "High", noSideEffect.}
## var arr = [1,2,3,4,5,6,7]
## high(arr) #=> 6
## high(2) #=> 9223372036854775807
+ ## high(int) #=> 9223372036854775807
proc low*[T](x: T): T {.magic: "Low", noSideEffect.}
## returns the lowest possible index of an array, a sequence, a string or
@@ -236,6 +237,7 @@ proc low*[T](x: T): T {.magic: "Low", noSideEffect.}
## var arr = [1,2,3,4,5,6,7]
## low(arr) #=> 0
## low(2) #=> -9223372036854775808
+ ## low(int) #=> -9223372036854775808
type
range*{.magic: "Range".}[T] ## Generic type to construct range types.
@@ -603,7 +605,7 @@ proc `<`*[T](x: Ordinal[T]): T {.magic: "UnaryLt", noSideEffect.}
## unary ``<`` that can be used for nice looking excluding ranges:
##
## .. code-block:: nim
- ## for i in 0 .. <10: echo i
+ ## for i in 0 .. <10: echo i #=> 0 1 2 3 4 5 6 7 8 9
##
## Semantically this is the same as ``pred``.
@@ -889,8 +891,8 @@ proc `shl` *(x, y: int64): int64 {.magic: "ShlI", noSideEffect.}
## computes the `shift left` operation of `x` and `y`.
##
## .. code-block:: Nim
- ## 1'i32 shl 4 == 0x0000_0010
- ## 1'i64 shl 4 == 0x0000_0000_0000_0010
+ ## 1'i32 shl 4 == 0x0000_0010
+ ## 1'i64 shl 4 == 0x0000_0000_0000_0010
proc `and` *(x, y: int): int {.magic: "BitandI", noSideEffect.}
proc `and` *(x, y: int8): int8 {.magic: "BitandI", noSideEffect.}
@@ -1313,20 +1315,20 @@ when defined(boehmgc):
when taintMode:
type TaintedString* = distinct string ## a distinct string type that
- ## is `tainted`:idx:. It is an alias for
+ ## is `tainted`:idx:, see `taint mode
+ ## `_ for
+ ## details. It is an alias for
## ``string`` if the taint mode is not
- ## turned on. Use the ``-d:taintMode``
- ## command line switch to turn the taint
- ## mode on.
+ ## turned on.
proc len*(s: TaintedString): int {.borrow.}
else:
type TaintedString* = string ## a distinct string type that
- ## is `tainted`:idx:. It is an alias for
+ ## is `tainted`:idx:, see `taint mode
+ ## `_ for
+ ## details. It is an alias for
## ``string`` if the taint mode is not
- ## turned on. Use the ``-d:taintMode``
- ## command line switch to turn the taint
- ## mode on.
+ ## turned on.
when defined(profiler):
proc nimProfile() {.compilerProc, noinline.}
@@ -1410,7 +1412,7 @@ proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} =
## This is an O(1) operation.
##
## .. code-block:: nim
- ## var i = @[1,2,3,4,5]
+ ## var i = @[1, 2, 3, 4, 5]
## i.del(2) #=> @[1, 2, 5, 4]
let xl = x.len - 1
shallowCopy(x[i], x[xl])
@@ -1421,7 +1423,7 @@ proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} =
## This is an O(n) operation.
##
## .. code-block:: nim
- ## var i = @[1,2,3,4,5]
+ ## var i = @[1, 2, 3, 4, 5]
## i.delete(2) #=> @[1, 2, 4, 5]
template defaultImpl =
let xl = x.len
@@ -1440,8 +1442,8 @@ proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} =
## inserts `item` into `x` at position `i`.
##
## .. code-block:: nim
- ## var i = @[1,2,3,4,5]
- ## i.insert(2,4) #=> @[1, 2, 3, 4, 2, 5]
+ ## var i = @[1, 2, 3, 4, 5]
+ ## i.insert(2, 4) #=> @[1, 2, 3, 4, 2, 5]
template defaultImpl =
let xl = x.len
setLen(x, xl+1)
@@ -1465,8 +1467,8 @@ proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}
## debugging tool.
##
## .. code-block:: nim
- ## var s: seq[string] = @["test2","test2"]
- ## var i = @[1,2,3,4,5]
+ ## var s: seq[string] = @["test2", "test2"]
+ ## var i = @[1, 2, 3, 4, 5]
## repr(s) #=> 0x1055eb050[0x1055ec050"test2", 0x1055ec078"test2"]
## repr(i) #=> 0x1055ed050[1, 2, 3, 4, 5]
@@ -1519,7 +1521,7 @@ type # these work for most platforms:
## This is the same as the type ``double`` in *C*.
clongdouble* {.importc: "long double", nodecl.} = BiggestFloat
## This is the same as the type ``long double`` in *C*.
- ## This C type is not supported by Nim's code generator
+ ## This C type is not supported by Nim's code generator.
cuchar* {.importc: "unsigned char", nodecl.} = char
## This is the same as the type ``unsigned char`` in *C*.
@@ -1822,18 +1824,15 @@ const
NimMajor*: int = 0
## is the major number of Nim's version.
- NimMinor*: int = 14
+ NimMinor*: int = 15
## is the minor number of Nim's version.
- NimPatch*: int = 3
+ NimPatch*: int = 1
## is the patch number of Nim's version.
NimVersion*: string = $NimMajor & "." & $NimMinor & "." & $NimPatch
## is the version of Nim as a string.
-{.deprecated: [TEndian: Endianness, NimrodVersion: NimVersion,
- NimrodMajor: NimMajor, NimrodMinor: NimMinor, NimrodPatch: NimPatch].}
-
# GC interface:
when not defined(nimscript) and hasAlloc:
@@ -2654,12 +2653,11 @@ when not defined(JS): #and not defined(nimscript):
when defined(nimscript):
proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.}
- ## Opens a file named `filename` for reading.
- ##
- ## Then calls `readAll <#readAll>`_ and closes the file afterwards.
- ## Returns the string. Raises an IO exception in case of an error. If
- ## you need to call this inside a compile time macro you can use
- ## `staticRead <#staticRead>`_.
+ ## Opens a file named `filename` for reading, calls `readAll
+ ## <#readAll>`_ and closes the file afterwards. Returns the string.
+ ## Raises an IO exception in case of an error. If # you need to call
+ ## this inside a compile time macro you can use `staticRead
+ ## <#staticRead>`_.
proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.}
## Opens a file named `filename` for writing. Then writes the
@@ -2733,7 +2731,7 @@ when not defined(JS): #and not defined(nimscript):
proc setStdIoUnbuffered*() {.tags: [], benign.}
## Configures `stdin`, `stdout` and `stderr` to be unbuffered.
- proc close*(f: File) {.tags: [].}
+ proc close*(f: File) {.tags: [], gcsafe.}
## Closes the file.
proc endOfFile*(f: File): bool {.tags: [], benign.}
@@ -2795,7 +2793,7 @@ when not defined(JS): #and not defined(nimscript):
proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline,
tags: [WriteIOEffect], benign.}
- ## writes the values `x` to `f` and then writes "\n".
+ ## writes the values `x` to `f` and then writes "\\n".
## May throw an IO exception.
proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.}
@@ -2926,14 +2924,14 @@ when not defined(JS): #and not defined(nimscript):
## allows you to override the behaviour of your application when CTRL+C
## is pressed. Only one such hook is supported.
- proc writeStackTrace*() {.tags: [WriteIOEffect].}
+ proc writeStackTrace*() {.tags: [WriteIOEffect], gcsafe.}
## writes the current stack trace to ``stderr``. This is only works
## for debug builds.
when hostOS != "standalone":
- proc getStackTrace*(): string
+ proc getStackTrace*(): string {.gcsafe.}
## gets the current stack trace. This only works for debug builds.
- proc getStackTrace*(e: ref Exception): string
+ proc getStackTrace*(e: ref Exception): string {.gcsafe.}
## gets the stack trace associated with `e`, which is the stack that
## lead to the ``raise`` statement. This only works for debug builds.
diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim
index bed9fd906..745bbbf62 100644
--- a/lib/system/alloc.nim
+++ b/lib/system/alloc.nim
@@ -101,8 +101,8 @@ type
# shared:
var
- bottomData: AvlNode
- bottom: PAvlNode
+ bottomData {.threadvar.}: AvlNode
+ bottom {.threadvar.}: PAvlNode
{.push stack_trace: off.}
proc initAllocator() =
diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim
index a79dcb152..3a43729dc 100644
--- a/lib/system/atomics.nim
+++ b/lib/system/atomics.nim
@@ -14,6 +14,7 @@ const someGcc = defined(gcc) or defined(llvm_gcc) or defined(clang)
when someGcc and hasThreadSupport:
type AtomMemModel* = distinct cint
+
var ATOMIC_RELAXED* {.importc: "__ATOMIC_RELAXED", nodecl.}: AtomMemModel
## No barriers or synchronization.
var ATOMIC_CONSUME* {.importc: "__ATOMIC_CONSUME", nodecl.}: AtomMemModel
@@ -165,7 +166,8 @@ when someGcc and hasThreadSupport:
template fence*() = atomicThreadFence(ATOMIC_SEQ_CST)
elif defined(vcc) and hasThreadSupport:
proc addAndFetch*(p: ptr int, val: int): int {.
- importc: "NimXadd", nodecl.}
+ importc: "_InterlockedExchangeAdd", header: "".}
+
proc fence*() {.importc: "_ReadWriteBarrier", header: "".}
else:
@@ -176,6 +178,8 @@ else:
proc atomicInc*(memLoc: var int, x: int = 1): int =
when someGcc and hasThreadSupport:
result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED)
+ elif defined(vcc) and hasThreadSupport:
+ result = addAndFetch(memLoc.addr, x)
else:
inc(memLoc, x)
result = memLoc
@@ -186,17 +190,33 @@ proc atomicDec*(memLoc: var int, x: int = 1): int =
result = atomic_sub_fetch(memLoc.addr, x, ATOMIC_RELAXED)
else:
result = atomic_add_fetch(memLoc.addr, -x, ATOMIC_RELAXED)
+ elif defined(vcc) and hasThreadSupport:
+ result = addAndFetch(memLoc.addr, -x)
else:
dec(memLoc, x)
result = memLoc
-when defined(windows) and not someGcc:
- proc interlockedCompareExchange(p: pointer; exchange, comparand: int): int
- {.importc: "InterlockedCompareExchange", header: "", cdecl.}
+when defined(vcc):
+ proc interlockedCompareExchange64(p: pointer; exchange, comparand: int64): int64
+ {.importc: "_InterlockedCompareExchange64", header: "".}
+ proc interlockedCompareExchange32(p: pointer; exchange, comparand: int32): int32
+ {.importc: "_InterlockedCompareExchange", header: "".}
+ proc interlockedCompareExchange8(p: pointer; exchange, comparand: byte): byte
+ {.importc: "_InterlockedCompareExchange64", header: "".}
proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool =
- interlockedCompareExchange(p, cast[int](newValue), cast[int](oldValue)) != 0
- # XXX fix for 64 bit build
+ when sizeof(T) == 8:
+ interlockedCompareExchange64(p, cast[int64](newValue), cast[int64](oldValue)) ==
+ cast[int64](oldValue)
+ elif sizeof(T) == 4:
+ interlockedCompareExchange32(p, cast[int32](newValue), cast[int32](oldValue)) ==
+ cast[int32](oldValue)
+ elif sizeof(T) == 1:
+ interlockedCompareExchange8(p, cast[byte](newValue), cast[byte](oldValue)) ==
+ cast[byte](oldValue)
+ else:
+ {.error: "invalid CAS instruction".}
+
elif defined(tcc) and not defined(windows):
when defined(amd64):
{.emit:"""
@@ -237,7 +257,7 @@ static int __tcc_cas(int *ptr, int oldVal, int newVal)
return 1;
}
""".}
-
+
proc tcc_cas(p: ptr int; oldValue, newValue: int): bool
{.importc: "__tcc_cas", nodecl.}
proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool =
@@ -249,14 +269,14 @@ else:
# XXX is this valid for 'int'?
-when (defined(x86) or defined(amd64)) and someGcc:
+when (defined(x86) or defined(amd64)) and defined(vcc):
+ proc cpuRelax* {.importc: "YieldProcessor", header: "".}
+elif (defined(x86) or defined(amd64)) and someGcc:
proc cpuRelax* {.inline.} =
{.emit: """asm volatile("pause" ::: "memory");""".}
elif someGcc or defined(tcc):
proc cpuRelax* {.inline.} =
{.emit: """asm volatile("" ::: "memory");""".}
-elif (defined(x86) or defined(amd64)) and defined(vcc):
- proc cpuRelax* {.importc: "YieldProcessor", header: "".}
elif defined(icl):
proc cpuRelax* {.importc: "_mm_pause", header: "xmmintrin.h".}
elif false:
diff --git a/lib/system/avltree.nim b/lib/system/avltree.nim
index d5c901542..50faada26 100644
--- a/lib/system/avltree.nim
+++ b/lib/system/avltree.nim
@@ -9,7 +9,7 @@
# not really an AVL tree anymore, but still balanced ...
-template isBottom(n: PAvlNode): bool = n == bottom
+template isBottom(n: PAvlNode): bool = n.link[0] == n
proc lowGauge(n: PAvlNode): int =
var it = n
@@ -52,7 +52,7 @@ proc split(t: var PAvlNode) =
inc t.level
proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.benign.} =
- if t == bottom:
+ if t.isBottom:
t = allocAvlNode(a, key, upperBound)
else:
if key <% t.key:
@@ -65,14 +65,14 @@ proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.benign.} =
split(t)
proc del(a: var MemRegion, t: var PAvlNode, x: int) {.benign.} =
- if t == bottom: return
+ if isBottom(t): return
a.last = t
if x <% t.key:
del(a, t.link[0], x)
else:
a.deleted = t
del(a, t.link[1], x)
- if t == a.last and a.deleted != bottom and x == a.deleted.key:
+ if t == a.last and not isBottom(a.deleted) and x == a.deleted.key:
a.deleted.key = t.key
a.deleted.upperBound = t.upperBound
a.deleted = bottom
diff --git a/lib/system/channels.nim b/lib/system/channels.nim
index 68c0e32d2..4b8b895a5 100644
--- a/lib/system/channels.nim
+++ b/lib/system/channels.nim
@@ -52,6 +52,7 @@ proc deinitRawChannel(p: pointer) =
proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
mode: LoadStoreMode) {.benign.}
+
proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel,
mode: LoadStoreMode) {.benign.} =
var
@@ -71,6 +72,9 @@ proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel,
proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
mode: LoadStoreMode) =
+ template `+!`(p: pointer; x: int): pointer =
+ cast[pointer](cast[int](p) +% x)
+
var
d = cast[ByteAddress](dest)
s = cast[ByteAddress](src)
@@ -93,7 +97,9 @@ proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
if s2 == nil:
unsureAsgnRef(x, s2)
else:
- unsureAsgnRef(x, copyString(cast[NimString](s2)))
+ let y = copyDeepString(cast[NimString](s2))
+ #echo "loaded ", cast[int](y), " ", cast[string](y)
+ unsureAsgnRef(x, y)
dealloc(t.region, s2)
of tySequence:
var s2 = cast[PPointer](src)[]
@@ -107,27 +113,27 @@ proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
else:
sysAssert(dest != nil, "dest == nil")
if mode == mStore:
- x[] = alloc(t.region, seq.len *% mt.base.size +% GenericSeqSize)
+ x[] = alloc0(t.region, seq.len *% mt.base.size +% GenericSeqSize)
else:
unsureAsgnRef(x, newObj(mt, seq.len * mt.base.size + GenericSeqSize))
var dst = cast[ByteAddress](cast[PPointer](dest)[])
+ var dstseq = cast[PGenericSeq](dst)
+ dstseq.len = seq.len
+ dstseq.reserved = seq.len
for i in 0..seq.len-1:
storeAux(
cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +%
GenericSeqSize),
mt.base, t, mode)
- var dstseq = cast[PGenericSeq](dst)
- dstseq.len = seq.len
- dstseq.reserved = seq.len
if mode != mStore: dealloc(t.region, s2)
of tyObject:
- # copy type field:
- var pint = cast[ptr PNimType](dest)
- # XXX use dynamic type here!
- pint[] = mt
if mt.base != nil:
storeAux(dest, src, mt.base, t, mode)
+ else:
+ # copy type field:
+ var pint = cast[ptr PNimType](dest)
+ pint[] = cast[ptr PNimType](src)[]
storeAux(dest, src, mt.node, t, mode)
of tyTuple:
storeAux(dest, src, mt.node, t, mode)
@@ -144,16 +150,24 @@ proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
else:
unsureAsgnRef(x, nil)
else:
+ #let size = if mt.base.kind == tyObject: cast[ptr PNimType](s)[].size
+ # else: mt.base.size
if mode == mStore:
- x[] = alloc(t.region, mt.base.size)
+ let dyntype = when declared(usrToCell): usrToCell(s).typ
+ else: mt
+ let size = dyntype.base.size
+ # we store the real dynamic 'ref type' at offset 0, so that
+ # no information is lost
+ let a = alloc0(t.region, size+sizeof(pointer))
+ x[] = a
+ cast[PPointer](a)[] = dyntype
+ storeAux(a +! sizeof(pointer), s, dyntype.base, t, mode)
else:
- # XXX we should use the dynamic type here too, but that is not stored
- # in the inbox at all --> use source[]'s object type? but how? we need
- # a tyRef to the object!
- var obj = newObj(mt, mt.base.size)
+ let dyntype = cast[ptr PNimType](s)[]
+ var obj = newObj(dyntype, dyntype.base.size)
unsureAsgnRef(x, obj)
- storeAux(x[], s, mt.base, t, mode)
- if mode != mStore: dealloc(t.region, s)
+ storeAux(x[], s +! sizeof(pointer), dyntype.base, t, mode)
+ dealloc(t.region, s)
else:
copyMem(dest, src, mt.size) # copy raw bits
@@ -196,10 +210,8 @@ template sendImpl(q: expr) {.immediate.} =
if q.mask == ChannelDeadMask:
sysFatal(DeadThreadError, "cannot send message; thread died")
acquireSys(q.lock)
- var m: TMsg
- shallowCopy(m, msg)
var typ = cast[PNimType](getTypeInfo(msg))
- rawSend(q, addr(m), typ)
+ rawSend(q, unsafeAddr(msg), typ)
q.elemType = typ
releaseSys(q.lock)
signalSysCond(q.cond)
@@ -230,8 +242,10 @@ proc recv*[TMsg](c: var Channel[TMsg]): TMsg =
proc tryRecv*[TMsg](c: var Channel[TMsg]): tuple[dataAvailable: bool,
msg: TMsg] =
- ## try to receives a message from the channel `c` if available. Otherwise
- ## it returns ``(false, default(msg))``.
+ ## Tries to receive a message from the channel `c`, but this can fail
+ ## for all sort of reasons, including contention. If it fails,
+ ## it returns ``(false, default(msg))`` otherwise it
+ ## returns ``(true, msg)``.
var q = cast[PRawChannel](addr(c))
if q.mask != ChannelDeadMask:
if tryAcquireSys(q.lock):
diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim
index 5445a067c..38cc8cbf3 100644
--- a/lib/system/deepcopy.nim
+++ b/lib/system/deepcopy.nim
@@ -32,12 +32,6 @@ proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.benign.} =
genericDeepCopyAux(dest, src, m)
of nkNone: sysAssert(false, "genericDeepCopyAux")
-proc copyDeepString(src: NimString): NimString {.inline.} =
- if src != nil:
- result = rawNewStringNoInit(src.len)
- result.len = src.len
- copyMem(addr(result.data), addr(src.data), src.len + 1)
-
proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
var
d = cast[ByteAddress](dest)
@@ -70,10 +64,11 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
of tyObject:
# we need to copy m_type field for tyObject, as it could be empty for
# sequence reallocations:
- var pint = cast[ptr PNimType](dest)
- pint[] = cast[ptr PNimType](src)[]
if mt.base != nil:
genericDeepCopyAux(dest, src, mt.base)
+ else:
+ var pint = cast[ptr PNimType](dest)
+ pint[] = cast[ptr PNimType](src)[]
genericDeepCopyAux(dest, src, mt.node)
of tyTuple:
genericDeepCopyAux(dest, src, mt.node)
@@ -103,16 +98,16 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
else:
let realType = x.typ
let z = newObj(realType, realType.base.size)
-
unsureAsgnRef(cast[PPointer](dest), z)
x.typ = cast[PNimType](cast[int](z) or 1)
genericDeepCopyAux(z, s2, realType.base)
x.typ = realType
else:
- let realType = mt
- let z = newObj(realType, realType.base.size)
+ let size = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[].size
+ else: mt.base.size
+ let z = newObj(mt, size)
unsureAsgnRef(cast[PPointer](dest), z)
- genericDeepCopyAux(z, s2, realType.base)
+ genericDeepCopyAux(z, s2, mt.base)
of tyPtr:
# no cycle check here, but also not really required
let s2 = cast[PPointer](src)[]
diff --git a/lib/system/dyncalls.nim b/lib/system/dyncalls.nim
index 0a994efac..fa997e982 100644
--- a/lib/system/dyncalls.nim
+++ b/lib/system/dyncalls.nim
@@ -26,6 +26,8 @@ proc nimLoadLibraryError(path: string) =
stderr.rawWrite("could not load: ")
stderr.rawWrite(path)
stderr.rawWrite("\n")
+ when not(defined(nimDebugDlOpen)):
+ stderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n")
quit(1)
proc procAddrError(name: cstring) {.noinline.} =
@@ -74,7 +76,8 @@ when defined(posix):
when defined(nimDebugDlOpen):
let error = dlerror()
if error != nil:
- c_fprintf(c_stderr, "%s\n", error)
+ stderr.write(error)
+ stderr.rawWrite("\n")
proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr =
result = dlsym(lib, name)
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index b89729850..dcf41b67d 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -11,7 +11,8 @@
# use the heap (and nor exceptions) do not include the GC or memory allocator.
var
- errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign.})
+ errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign,
+ nimcall.})
## Function that will be called
## instead of stdmsg.write when printing stacktrace.
## Unstable API.
@@ -26,7 +27,7 @@ else:
proc writeToStdErr(msg: cstring) =
discard MessageBoxA(0, msg, nil, 0)
-proc showErrorMessage(data: cstring) =
+proc showErrorMessage(data: cstring) {.gcsafe.} =
if errorMessageWriter != nil:
errorMessageWriter($data)
else:
diff --git a/lib/system/gc.nim b/lib/system/gc.nim
index ba6b2fcf9..11897ce80 100644
--- a/lib/system/gc.nim
+++ b/lib/system/gc.nim
@@ -116,6 +116,8 @@ template gcAssert(cond: bool, msg: string) =
echo "[GCASSERT] ", msg
GC_disable()
writeStackTrace()
+ #var x: ptr int
+ #echo x[]
quit 1
proc addZCT(s: var CellSeq, c: PCell) {.noinline.} =
diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim
index 089c9c915..ce2bfc2ae 100644
--- a/lib/system/gc2.nim
+++ b/lib/system/gc2.nim
@@ -33,6 +33,9 @@ when withRealTime and not declared(getTicks):
when defined(memProfiler):
proc nimProfile(requestedSize: int) {.benign.}
+when hasThreadSupport:
+ include sharedlist
+
type
ObjectSpaceIter = object
state: range[-1..0]
@@ -96,7 +99,7 @@ type
stat: GcStat
additionalRoots: CellSeq # dummy roots for GC_ref/unref
spaceIter: ObjectSpaceIter
- dumpHeapFile: File # File that is used for GC_dumpHeap
+ pDumpHeapFile: pointer # File that is used for GC_dumpHeap
when hasThreadSupport:
toDispose: SharedList[pointer]
@@ -612,6 +615,9 @@ template checkTime {.dirty.} =
# ---------------- dump heap ----------------
+template dumpHeapFile(gch: var GcHeap): File =
+ cast[File](gch.pDumpHeapFile)
+
proc debugGraph(s: PCell) =
c_fprintf(gch.dumpHeapFile, "child %p\n", s)
@@ -625,7 +631,7 @@ proc GC_dumpHeap*(file: File) =
## Dumps the GCed heap's content to a file. Can be useful for
## debugging. Produces an undocumented text file format that
## can be translated into "dot" syntax via the "heapdump2dot" tool.
- gch.dumpHeapFile = file
+ gch.pDumpHeapFile = file
var spaceIter: ObjectSpaceIter
var d = gch.decStack.d
for i in 0 .. < gch.decStack.len:
@@ -643,7 +649,7 @@ proc GC_dumpHeap*(file: File) =
writeCell(file, "cell ", c)
forAllChildren(c, waDebug)
c_fprintf(file, "end\n")
- gch.dumpHeapFile = nil
+ gch.pDumpHeapFile = nil
proc GC_dumpHeap() =
var f: File
diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim
index 7a1b88c84..513ede173 100644
--- a/lib/system/gc_common.nim
+++ b/lib/system/gc_common.nim
@@ -120,6 +120,7 @@ when allowForeignThreadGc:
## switches are used
if not localGcInitialized:
localGcInitialized = true
+ initAllocator()
var stackTop {.volatile.}: pointer
setStackBottom(addr(stackTop))
initGC()
diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim
index 9c8a18bfe..39ce1183b 100644
--- a/lib/system/jssys.nim
+++ b/lib/system/jssys.nim
@@ -7,11 +7,6 @@
# distribution, for details about the copyright.
#
-when defined(nodejs):
- proc alert*(s: cstring) {.importc: "console.log", nodecl.}
-else:
- proc alert*(s: cstring) {.importc, nodecl.}
-
proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.}
type
@@ -52,11 +47,30 @@ proc nimCharToStr(x: char): string {.compilerproc.} =
result = newString(1)
result[0] = x
+proc isNimException(): bool {.asmNoStackFrame.} =
+ when defined(nimphp):
+ asm "return isset(`lastJSError`['m_type']);"
+ else:
+ asm "return `lastJSError`.m_type;"
+
+proc getCurrentException*(): ref Exception =
+ if isNimException(): result = cast[ref Exception](lastJSError)
+
proc getCurrentExceptionMsg*(): string =
if lastJSError != nil:
- return $lastJSError.message
- else:
- return ""
+ if isNimException():
+ return cast[Exception](lastJSError).msg
+ else:
+ when not defined(nimphp):
+ var msg: cstring
+ {.emit: """
+ if (`lastJSError`.message !== undefined) {
+ `msg` = `lastJSError`.message;
+ }
+ """.}
+ if not msg.isNil:
+ return $msg
+ return ""
proc auxWriteStackTrace(f: PCallFrame): string =
type
@@ -91,54 +105,56 @@ proc auxWriteStackTrace(f: PCallFrame): string =
proc rawWriteStackTrace(): string =
if framePtr != nil:
result = "Traceback (most recent call last)\n" & auxWriteStackTrace(framePtr)
- framePtr = nil
- elif lastJSError != nil:
- result = $lastJSError.stack
else:
result = "No stack traceback available\n"
proc getStackTrace*(): string = rawWriteStackTrace()
+proc getStackTrace*(e: ref Exception): string = e.trace
proc unhandledException(e: ref Exception) {.
compilerproc, asmNoStackFrame.} =
- when NimStackTrace:
- var buf = rawWriteStackTrace()
+ var buf = ""
+ if e.msg != nil and e.msg[0] != '\0':
+ add(buf, "Error: unhandled exception: ")
+ add(buf, e.msg)
else:
- var buf = ""
- if e.msg != nil and e.msg[0] != '\0':
- add(buf, "Error: unhandled exception: ")
- add(buf, e.msg)
- else:
- add(buf, "Error: unhandled exception")
- add(buf, " [")
- add(buf, e.name)
- add(buf, "]\n")
- alert(buf)
+ add(buf, "Error: unhandled exception")
+ add(buf, " [")
+ add(buf, e.name)
+ add(buf, "]\n")
+ when NimStackTrace:
+ add(buf, rawWriteStackTrace())
+ let cbuf : cstring = buf
+ framePtr = nil
+ {.emit: """
+ if (typeof(Error) !== "undefined") {
+ throw new Error(`cbuf`);
+ }
+ else {
+ throw `cbuf`;
+ }
+ """.}
proc raiseException(e: ref Exception, ename: cstring) {.
compilerproc, asmNoStackFrame.} =
e.name = ename
- when not defined(noUnhandledHandler):
- if excHandler == 0:
- unhandledException(e)
+ if excHandler == 0:
+ unhandledException(e)
when defined(nimphp):
asm """throw new Exception($`e`["message"]);"""
else:
+ when NimStackTrace:
+ e.trace = rawWriteStackTrace()
asm "throw `e`;"
proc reraiseException() {.compilerproc, asmNoStackFrame.} =
if lastJSError == nil:
raise newException(ReraiseError, "no exception to reraise")
else:
- when not defined(noUnhandledHandler):
- if excHandler == 0:
- var isNimException: bool
- when defined(nimphp):
- asm "`isNimException` = isset(`lastJSError`['m_type']);"
- else:
- asm "`isNimException` = lastJSError.m_type;"
- if isNimException:
- unhandledException(cast[ref Exception](lastJSError))
+ if excHandler == 0:
+ if isNimException():
+ unhandledException(cast[ref Exception](lastJSError))
+
asm "throw lastJSError;"
proc raiseOverflow {.exportc: "raiseOverflow", noreturn.} =
@@ -873,3 +889,10 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, start = 0): int {
# evaluate sign
number = number * sign
result = i - start
+
+when defined(nodejs):
+ # Deprecated. Use `alert` defined in dom.nim
+ proc alert*(s: cstring) {.importc: "console.log", nodecl, deprecated.}
+else:
+ # Deprecated. Use `alert` defined in dom.nim
+ proc alert*(s: cstring) {.importc, nodecl, deprecated.}
diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim
index 186349152..63af49e35 100644
--- a/lib/system/mmdisp.nim
+++ b/lib/system/mmdisp.nim
@@ -21,7 +21,7 @@ const
alwaysGC = defined(fulldebug) # collect after every memory
# allocation (for debugging)
leakDetector = false
- overwriteFree = false
+ overwriteFree = defined(nimBurnFree) # overwrite memory with 0xFF before free
trackAllocationSource = leakDetector
cycleGC = true # (de)activate the cycle GC
diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim
index fc6b8c99d..29466c34d 100644
--- a/lib/system/nimscript.nim
+++ b/lib/system/nimscript.nim
@@ -301,7 +301,7 @@ var
author*: string ## Nimble support: The package's author.
description*: string ## Nimble support: The package's description.
license*: string ## Nimble support: The package's license.
- srcdir*: string ## Nimble support: The package's source directory.
+ srcDir*: string ## Nimble support: The package's source directory.
binDir*: string ## Nimble support: The package's binary directory.
backend*: string ## Nimble support: The package's backend.
diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim
index b07a362a0..316dd74d7 100644
--- a/lib/system/osalloc.nim
+++ b/lib/system/osalloc.nim
@@ -87,8 +87,6 @@ elif defined(posix):
const MAP_ANONYMOUS = 0x1000
elif defined(solaris):
const MAP_ANONYMOUS = 0x100
- elif defined(linux):
- const MAP_ANONYMOUS = 0x20
else:
var
MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "".}: cint
diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim
index eb3d276e0..3a93221e0 100644
--- a/lib/system/sysstr.nim
+++ b/lib/system/sysstr.nim
@@ -110,6 +110,11 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} =
result.len = src.len
copyMem(addr(result.data), addr(src.data), src.len + 1)
+proc copyDeepString(src: NimString): NimString {.inline.} =
+ if src != nil:
+ result = rawNewStringNoInit(src.len)
+ result.len = src.len
+ copyMem(addr(result.data), addr(src.data), src.len + 1)
proc hashString(s: string): int {.compilerproc.} =
# the compiler needs exactly the same hash function!
@@ -292,7 +297,7 @@ proc nimFloatToStr(f: float): string {.compilerproc.} =
buf[n+2] = '\0'
# On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' are produced.
# We want to get rid of these here:
- if buf[n-1] == 'N':
+ if buf[n-1] in {'n', 'N'}:
result = "nan"
elif buf[n-1] == 'F':
if buf[0] == '-':
diff --git a/lib/system/threads.nim b/lib/system/threads.nim
index 583e3ae86..6f5bb38b1 100644
--- a/lib/system/threads.nim
+++ b/lib/system/threads.nim
@@ -331,7 +331,9 @@ else:
when TArg is void:
thrd.dataFn()
else:
- thrd.dataFn(thrd.data)
+ var x: TArg
+ deepCopy(x, thrd.data)
+ thrd.dataFn(x)
proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) =
when defined(boehmgc):
@@ -354,6 +356,8 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) =
template threadProcWrapperBody(closure: expr) {.immediate.} =
when declared(globalsSlot): threadVarSetValue(globalsSlot, closure)
+ when declared(initAllocator):
+ initAllocator()
var thrd = cast[ptr Thread[TArg]](closure)
threadProcWrapStackFrame(thrd)
# Since an unhandled exception terminates the whole process (!), there is
@@ -419,7 +423,7 @@ when false:
when hostOS == "windows":
proc createThread*[TArg](t: var Thread[TArg],
- tp: proc (arg: TArg) {.thread.},
+ tp: proc (arg: TArg) {.thread, nimcall.},
param: TArg) =
## creates a new thread `t` and starts its execution. Entry point is the
## proc `tp`. `param` is passed to `tp`. `TArg` can be ``void`` if you
@@ -441,7 +445,7 @@ when hostOS == "windows":
else:
proc createThread*[TArg](t: var Thread[TArg],
- tp: proc (arg: TArg) {.thread.},
+ tp: proc (arg: TArg) {.thread, nimcall.},
param: TArg) =
## creates a new thread `t` and starts its execution. Entry point is the
## proc `tp`. `param` is passed to `tp`. `TArg` can be ``void`` if you
@@ -464,7 +468,7 @@ else:
cpusetIncl(cpu.cint, s)
setAffinity(t.sys, sizeof(s), s)
-proc createThread*(t: var Thread[void], tp: proc () {.thread.}) =
+proc createThread*(t: var Thread[void], tp: proc () {.thread, nimcall.}) =
createThread[void](t, tp)
proc threadId*[TArg](t: var Thread[TArg]): ThreadId[TArg] {.inline.} =
diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim
index 19c9815d2..29b955c46 100644
--- a/lib/upcoming/asyncdispatch.nim
+++ b/lib/upcoming/asyncdispatch.nim
@@ -9,7 +9,7 @@
include "system/inclrtl"
-import os, oids, tables, strutils, macros, times, heapqueue
+import os, oids, tables, strutils, times, heapqueue
import nativesockets, net, queues
@@ -130,275 +130,7 @@ export Port, SocketFlag
# TODO: Check if yielded future is nil and throw a more meaningful exception
-# -- Futures
-
-type
- FutureBase* = ref object of RootObj ## Untyped future.
- cb: proc () {.closure,gcsafe.}
- finished: bool
- error*: ref Exception ## Stored exception
- errorStackTrace*: string
- when not defined(release):
- stackTrace: string ## For debugging purposes only.
- id: int
- fromProc: string
-
- Future*[T] = ref object of FutureBase ## Typed future.
- value: T ## Stored value
-
- FutureVar*[T] = distinct Future[T]
-
- FutureError* = object of Exception
- cause*: FutureBase
-
-{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
-
-when not defined(release):
- var currentID = 0
-
-proc callSoon*(cbproc: proc ()) {.gcsafe.}
-
-proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
- ## Creates a new future.
- ##
- ## Specifying ``fromProc``, which is a string specifying the name of the proc
- ## that this future belongs to, is a good habit as it helps with debugging.
- new(result)
- result.finished = false
- when not defined(release):
- result.stackTrace = getStackTrace()
- result.id = currentID
- result.fromProc = fromProc
- currentID.inc()
-
-proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
- ## Create a new ``FutureVar``. This Future type is ideally suited for
- ## situations where you want to avoid unnecessary allocations of Futures.
- ##
- ## Specifying ``fromProc``, which is a string specifying the name of the proc
- ## that this future belongs to, is a good habit as it helps with debugging.
- result = FutureVar[T](newFuture[T](fromProc))
-
-proc clean*[T](future: FutureVar[T]) =
- ## Resets the ``finished`` status of ``future``.
- Future[T](future).finished = false
- Future[T](future).error = nil
-
-proc checkFinished[T](future: Future[T]) =
- ## Checks whether `future` is finished. If it is then raises a
- ## ``FutureError``.
- when not defined(release):
- if future.finished:
- var msg = ""
- msg.add("An attempt was made to complete a Future more than once. ")
- msg.add("Details:")
- msg.add("\n Future ID: " & $future.id)
- msg.add("\n Created in proc: " & future.fromProc)
- msg.add("\n Stack trace to moment of creation:")
- msg.add("\n" & indent(future.stackTrace.strip(), 4))
- when T is string:
- msg.add("\n Contents (string): ")
- msg.add("\n" & indent(future.value.repr, 4))
- msg.add("\n Stack trace to moment of secondary completion:")
- msg.add("\n" & indent(getStackTrace().strip(), 4))
- var err = newException(FutureError, msg)
- err.cause = future
- raise err
-
-proc complete*[T](future: Future[T], val: T) =
- ## Completes ``future`` with value ``val``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- assert(future.error == nil)
- future.value = val
- future.finished = true
- if future.cb != nil:
- future.cb()
-
-proc complete*(future: Future[void]) =
- ## Completes a void ``future``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- assert(future.error == nil)
- future.finished = true
- if future.cb != nil:
- future.cb()
-
-proc complete*[T](future: FutureVar[T]) =
- ## Completes a ``FutureVar``.
- template fut: expr = Future[T](future)
- checkFinished(fut)
- assert(fut.error == nil)
- fut.finished = true
- if fut.cb != nil:
- fut.cb()
-
-proc fail*[T](future: Future[T], error: ref Exception) =
- ## Completes ``future`` with ``error``.
- #assert(not future.finished, "Future already finished, cannot finish twice.")
- checkFinished(future)
- future.finished = true
- future.error = error
- future.errorStackTrace =
- if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
- if future.cb != nil:
- future.cb()
- else:
- # This is to prevent exceptions from being silently ignored when a future
- # is discarded.
- # TODO: This may turn out to be a bad idea.
- # Turns out this is a bad idea.
- #raise error
- discard
-
-proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
- ## Sets the callback proc to be called when the future completes.
- ##
- ## If future has already completed then ``cb`` will be called immediately.
- ##
- ## **Note**: You most likely want the other ``callback`` setter which
- ## passes ``future`` as a param to the callback.
- future.cb = cb
- if future.finished:
- callSoon(future.cb)
-
-proc `callback=`*[T](future: Future[T],
- cb: proc (future: Future[T]) {.closure,gcsafe.}) =
- ## Sets the callback proc to be called when the future completes.
- ##
- ## If future has already completed then ``cb`` will be called immediately.
- future.callback = proc () = cb(future)
-
-proc injectStacktrace[T](future: Future[T]) =
- # TODO: Come up with something better.
- when not defined(release):
- var msg = ""
- msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
-
- if not future.errorStackTrace.isNil and future.errorStackTrace != "":
- msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
- else:
- msg.add("\n Empty or nil stack trace.")
- future.error.msg.add(msg)
-
-proc read*[T](future: Future[T]): T =
- ## Retrieves the value of ``future``. Future must be finished otherwise
- ## this function will fail with a ``ValueError`` exception.
- ##
- ## If the result of the future is an error then that error will be raised.
- if future.finished:
- if future.error != nil:
- injectStacktrace(future)
- raise future.error
- when T isnot void:
- return future.value
- else:
- # TODO: Make a custom exception type for this?
- raise newException(ValueError, "Future still in progress.")
-
-proc readError*[T](future: Future[T]): ref Exception =
- ## Retrieves the exception stored in ``future``.
- ##
- ## An ``ValueError`` exception will be thrown if no exception exists
- ## in the specified Future.
- if future.error != nil: return future.error
- else:
- raise newException(ValueError, "No error in future.")
-
-proc mget*[T](future: FutureVar[T]): var T =
- ## Returns a mutable value stored in ``future``.
- ##
- ## Unlike ``read``, this function will not raise an exception if the
- ## Future has not been finished.
- result = Future[T](future).value
-
-proc finished*[T](future: Future[T]): bool =
- ## Determines whether ``future`` has completed.
- ##
- ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
- future.finished
-
-proc failed*(future: FutureBase): bool =
- ## Determines whether ``future`` completed with an error.
- return future.error != nil
-
-proc asyncCheck*[T](future: Future[T]) =
- ## Sets a callback on ``future`` which raises an exception if the future
- ## finished with an error.
- ##
- ## This should be used instead of ``discard`` to discard void futures.
- future.callback =
- proc () =
- if future.failed:
- injectStacktrace(future)
- raise future.error
-
-proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
- ## Returns a future which will complete once both ``fut1`` and ``fut2``
- ## complete.
- var retFuture = newFuture[void]("asyncdispatch.`and`")
- fut1.callback =
- proc () =
- if fut2.finished: retFuture.complete()
- fut2.callback =
- proc () =
- if fut1.finished: retFuture.complete()
- return retFuture
-
-proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
- ## Returns a future which will complete once either ``fut1`` or ``fut2``
- ## complete.
- var retFuture = newFuture[void]("asyncdispatch.`or`")
- proc cb() =
- if not retFuture.finished: retFuture.complete()
- fut1.callback = cb
- fut2.callback = cb
- return retFuture
-
-proc all*[T](futs: varargs[Future[T]]): auto =
- ## Returns a future which will complete once
- ## all futures in ``futs`` complete.
- ##
- ## If the awaited futures are not ``Future[void]``, the returned future
- ## will hold the values of all awaited futures in a sequence.
- ##
- ## If the awaited futures *are* ``Future[void]``,
- ## this proc returns ``Future[void]``.
-
- when T is void:
- var
- retFuture = newFuture[void]("asyncdispatch.all")
- completedFutures = 0
-
- let totalFutures = len(futs)
-
- for fut in futs:
- fut.callback = proc(f: Future[T]) =
- inc(completedFutures)
-
- if completedFutures == totalFutures:
- retFuture.complete()
-
- return retFuture
-
- else:
- var
- retFuture = newFuture[seq[T]]("asyncdispatch.all")
- retValues = newSeq[T](len(futs))
- completedFutures = 0
-
- for i, fut in futs:
- proc setCallback(i: int) =
- fut.callback = proc(f: Future[T]) =
- retValues[i] = f.read()
- inc(completedFutures)
-
- if completedFutures == len(retValues):
- retFuture.complete(retValues)
-
- setCallback(i)
-
- return retFuture
+include "../includes/asyncfutures"
type
PDispatcherBase = ref object of RootRef
@@ -511,43 +243,44 @@ when defined(windows) or defined(nimdoc):
if at == -1: winlean.INFINITE
else: at.int32
- var lpNumberOfBytesTransferred: Dword
- var lpCompletionKey: ULONG_PTR
- var customOverlapped: PCustomOverlapped
- let res = getQueuedCompletionStatus(p.ioPort,
- addr lpNumberOfBytesTransferred, addr lpCompletionKey,
- cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
+ if p.handles.len != 0:
+ var lpNumberOfBytesTransferred: Dword
+ var lpCompletionKey: ULONG_PTR
+ var customOverlapped: PCustomOverlapped
+ let res = getQueuedCompletionStatus(p.ioPort,
+ addr lpNumberOfBytesTransferred, addr lpCompletionKey,
+ cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
- # http://stackoverflow.com/a/12277264/492186
- # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
- if res:
- # This is useful for ensuring the reliability of the overlapped struct.
- assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
-
- customOverlapped.data.cb(customOverlapped.data.fd,
- lpNumberOfBytesTransferred, OSErrorCode(-1))
-
- # If cell.data != nil, then system.protect(rawEnv(cb)) was called,
- # so we need to dispose our `cb` environment, because it is not needed
- # anymore.
- if customOverlapped.data.cell.data != nil:
- system.dispose(customOverlapped.data.cell)
-
- GC_unref(customOverlapped)
- else:
- let errCode = osLastError()
- if customOverlapped != nil:
+ # http://stackoverflow.com/a/12277264/492186
+ # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
+ if res:
+ # This is useful for ensuring the reliability of the overlapped struct.
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+
customOverlapped.data.cb(customOverlapped.data.fd,
- lpNumberOfBytesTransferred, errCode)
+ lpNumberOfBytesTransferred, OSErrorCode(-1))
+
+ # If cell.data != nil, then system.protect(rawEnv(cb)) was called,
+ # so we need to dispose our `cb` environment, because it is not needed
+ # anymore.
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
+
GC_unref(customOverlapped)
else:
- if errCode.int32 == WAIT_TIMEOUT:
- # Timed out
- discard
- else: raiseOSError(errCode)
+ let errCode = osLastError()
+ if customOverlapped != nil:
+ assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+ customOverlapped.data.cb(customOverlapped.data.fd,
+ lpNumberOfBytesTransferred, errCode)
+ if customOverlapped.data.cell.data != nil:
+ system.dispose(customOverlapped.data.cell)
+ GC_unref(customOverlapped)
+ else:
+ if errCode.int32 == WAIT_TIMEOUT:
+ # Timed out
+ discard
+ else: raiseOSError(errCode)
# Timer processing.
processTimers(p)
@@ -718,7 +451,7 @@ when defined(windows) or defined(nimdoc):
retFuture.complete("")
return retFuture
- proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
+ proc recvInto*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the
@@ -742,7 +475,7 @@ when defined(windows) or defined(nimdoc):
#buf[] = '\0'
var dataBuf: TWSABuf
- dataBuf.buf = buf
+ dataBuf.buf = cast[cstring](buf)
dataBuf.len = size.ULONG
var bytesReceived: Dword
@@ -753,10 +486,7 @@ when defined(windows) or defined(nimdoc):
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
- if bytesCount == 0 and dataBuf.buf[0] == '\0':
- retFuture.complete(0)
- else:
- retFuture.complete(bytesCount)
+ retFuture.complete(bytesCount)
else:
if flags.isDisconnectionError(errcode):
retFuture.complete(0)
@@ -788,16 +518,18 @@ when defined(windows) or defined(nimdoc):
retFuture.complete(bytesReceived)
return retFuture
- proc send*(socket: AsyncFD, data: string,
+ proc send*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[void] =
- ## Sends ``data`` to ``socket``. The returned future will complete once all
+ ## Sends ``size`` bytes from ``buf`` to ``socket``. The returned future will complete once all
## data has been sent.
+ ## **WARNING**: Use it with caution. If ``buf`` refers to GC'ed object, you must use GC_ref/GC_unref calls
+ ## to avoid early freeing of the buffer
verifyPresence(socket)
var retFuture = newFuture[void]("send")
var dataBuf: TWSABuf
- dataBuf.buf = data # since this is not used in a callback, this is fine
- dataBuf.len = data.len.ULONG
+ dataBuf.buf = cast[cstring](buf)
+ dataBuf.len = size.ULONG
var bytesReceived, lowFlags: Dword
var ol = PCustomOverlapped()
@@ -831,6 +563,54 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
+ proc send*(socket: AsyncFD, data: string,
+ flags = {SocketFlag.SafeDisconn}): Future[void] =
+ ## Sends ``data`` to ``socket``. The returned future will complete once all
+ ## data has been sent.
+ verifyPresence(socket)
+ var retFuture = newFuture[void]("send")
+
+ var dataBuf: TWSABuf
+ dataBuf.buf = data
+ GC_ref(data) # we need to protect data until send operation is completed
+ # or failed.
+ dataBuf.len = data.len.ULONG
+
+ var bytesReceived, lowFlags: Dword
+ var ol = PCustomOverlapped()
+ GC_ref(ol)
+ ol.data = CompletionData(fd: socket, cb:
+ proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+ GC_unref(data) # if operation completed `data` must be released.
+ if not retFuture.finished:
+ if errcode == OSErrorCode(-1):
+ retFuture.complete()
+ else:
+ if flags.isDisconnectionError(errcode):
+ retFuture.complete()
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+ )
+
+ let ret = WSASend(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
+ lowFlags, cast[POVERLAPPED](ol), nil)
+ if ret == -1:
+ let err = osLastError()
+ if err.int32 != ERROR_IO_PENDING:
+ GC_unref(ol)
+ GC_unref(data) # if operation failed `data` must be released, because
+ # completion routine will not be called.
+ if flags.isDisconnectionError(err):
+ retFuture.complete()
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(err)))
+ else:
+ retFuture.complete()
+ # We don't deallocate ``ol`` here because even though this completed
+ # immediately poll will still be notified about its completion and it will
+ # free ``ol``.
+ return retFuture
+
proc sendTo*(socket: AsyncFD, data: pointer, size: int, saddr: ptr SockAddr,
saddrLen: Socklen,
flags = {SocketFlag.SafeDisconn}): Future[void] =
@@ -952,7 +732,7 @@ when defined(windows) or defined(nimdoc):
let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16)
- template completeAccept(): stmt {.immediate, dirty.} =
+ template completeAccept() {.dirty.} =
var listenSock = socket
let setoptRet = setsockopt(clientSock, SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
@@ -972,7 +752,7 @@ when defined(windows) or defined(nimdoc):
client: clientSock.AsyncFD)
)
- template failAccept(errcode): stmt =
+ template failAccept(errcode) =
if flags.isDisconnectionError(errcode):
var newAcceptFut = acceptAddr(socket, flags)
newAcceptFut.callback =
@@ -1158,7 +938,7 @@ when defined(windows) or defined(nimdoc):
## receiving notifies.
registerWaitableEvent(FD_WRITE or FD_CONNECT or FD_CLOSE)
- template registerWaitableHandle(p, hEvent, flags, pcd, handleCallback) =
+ template registerWaitableHandle(p, hEvent, flags, pcd, timeout, handleCallback) =
let handleFD = AsyncFD(hEvent)
pcd.ioPort = p.ioPort
pcd.handleFd = handleFD
@@ -1172,10 +952,10 @@ when defined(windows) or defined(nimdoc):
pcd.ovl = ol
if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
cast[WAITORTIMERCALLBACK](waitableCallback),
- cast[pointer](pcd), INFINITE, flags):
+ cast[pointer](pcd), timeout.Dword, flags):
GC_unref(ol)
deallocShared(cast[pointer](pcd))
- discard wsaCloseEvent(hEvent)
+ discard closeHandle(hEvent)
raiseOSError(osLastError())
p.handles.incl(handleFD)
@@ -1207,7 +987,7 @@ when defined(windows) or defined(nimdoc):
deallocShared(cast[pointer](pcd))
p.handles.excl(fd)
- registerWaitableHandle(p, hEvent, flags, pcd, timercb)
+ registerWaitableHandle(p, hEvent, flags, pcd, timeout, timercb)
proc addProcess*(pid: int, cb: Callback) =
## Registers callback ``cb`` to be called when process with pid ``pid``
@@ -1231,10 +1011,12 @@ when defined(windows) or defined(nimdoc):
p.handles.excl(fd)
discard cb(fd)
- registerWaitableHandle(p, hProcess, flags, pcd, proccb)
+ registerWaitableHandle(p, hProcess, flags, pcd, INFINITE, proccb)
proc newAsyncEvent*(): AsyncEvent =
## Creates new ``AsyncEvent`` object.
+ ## New ``AsyncEvent`` object is not automatically registered with
+ ## dispatcher like ``AsyncSocket``.
var sa = SECURITY_ATTRIBUTES(
nLength: sizeof(SECURITY_ATTRIBUTES).cint,
bInheritHandle: 1
@@ -1243,14 +1025,15 @@ when defined(windows) or defined(nimdoc):
if event == INVALID_HANDLE_VALUE:
raiseOSError(osLastError())
result = cast[AsyncEvent](allocShared0(sizeof(AsyncEventImpl)))
+ result.hEvent = event
proc setEvent*(ev: AsyncEvent) =
## Set event ``ev`` to signaled state.
if setEvent(ev.hEvent) == 0:
raiseOSError(osLastError())
- proc close*(ev: AsyncEvent) =
- ## Closes event ``ev``.
+ proc unregister*(ev: AsyncEvent) =
+ ## Unregisters event ``ev``.
if ev.hWaiter != 0:
let p = getGlobalDispatcher()
if unregisterWait(ev.hWaiter) == 0:
@@ -1258,7 +1041,12 @@ when defined(windows) or defined(nimdoc):
if err.int32 != ERROR_IO_PENDING:
raiseOSError(osLastError())
p.handles.excl(AsyncFD(ev.hEvent))
+ ev.hWaiter = 0
+ else:
+ raise newException(ValueError, "Event is not registered!")
+ proc close*(ev: AsyncEvent) =
+ ## Closes event ``ev``.
if closeHandle(ev.hEvent) == 0:
raiseOSError(osLastError())
deallocShared(cast[pointer](ev))
@@ -1276,15 +1064,12 @@ when defined(windows) or defined(nimdoc):
proc eventcb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if cb(fd):
- if unregisterWait(pcd.waitFd) == 0:
- let err = osLastError()
- if err.int32 != ERROR_IO_PENDING:
- raiseOSError(osLastError())
- ev.hWaiter = 0
+ # we need this check to avoid exception, if `unregister(event)` was
+ # called in callback.
+ if ev.hWaiter != 0: unregister(ev)
deallocShared(cast[pointer](pcd))
- p.handles.excl(fd)
- registerWaitableHandle(p, hEvent, flags, pcd, eventcb)
+ registerWaitableHandle(p, hEvent, flags, pcd, INFINITE, eventcb)
ev.hWaiter = pcd.waitFd
initAll()
@@ -1314,13 +1099,14 @@ else:
readCB: Callback
writeCB: Callback
- AsyncEvent* = SelectEvent
+ AsyncEvent* = distinct SelectEvent
PDispatcher* = ref object of PDispatcherBase
selector: Selector[AsyncData]
{.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].}
proc `==`*(x, y: AsyncFD): bool {.borrow.}
+ proc `==`*(x, y: AsyncEvent): bool {.borrow.}
proc newDispatcher*(): PDispatcher =
new result
@@ -1363,24 +1149,30 @@ else:
proc unregister*(fd: AsyncFD) =
getGlobalDispatcher().selector.unregister(fd.SocketHandle)
- # proc unregister*(ev: AsyncEvent) =
- # getGlobalDispatcher().selector.unregister(SelectEvent(ev))
+ proc unregister*(ev: AsyncEvent) =
+ getGlobalDispatcher().selector.unregister(SelectEvent(ev))
proc addRead*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher()
+ var newEvents = {Event.Read}
withData(p.selector, fd.SocketHandle, adata) do:
adata.readCB = cb
+ if adata.writeCB != nil:
+ newEvents.incl(Event.Write)
do:
raise newException(ValueError, "File descriptor not registered.")
- p.selector.updateHandle(fd.SocketHandle, {Event.Read})
+ p.selector.updateHandle(fd.SocketHandle, newEvents)
proc addWrite*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher()
+ var newEvents = {Event.Write}
withData(p.selector, fd.SocketHandle, adata) do:
adata.writeCB = cb
+ if adata.readCB != nil:
+ newEvents.incl(Event.Read)
do:
raise newException(ValueError, "File descriptor not registered.")
- p.selector.updateHandle(fd.SocketHandle, {Event.Write})
+ p.selector.updateHandle(fd.SocketHandle, newEvents)
proc poll*(timeout = 500) =
var keys: array[64, ReadyKey[AsyncData]]
@@ -1398,7 +1190,7 @@ else:
var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys)
var i = 0
while i < count:
- var update = false
+ var custom = false
var fd = keys[i].fd.SocketHandle
let events = keys[i].events
@@ -1409,7 +1201,6 @@ else:
p.selector.withData(fd, adata) do:
if adata.readCB == cb:
adata.readCB = nil
- update = true
if Event.Write in events:
let cb = keys[i].data.writeCB
@@ -1418,24 +1209,29 @@ else:
p.selector.withData(fd, adata) do:
if adata.writeCB == cb:
adata.writeCB = nil
- update = true
when supportedPlatform:
if (customSet * events) != {}:
let cb = keys[i].data.readCB
doAssert(cb != nil)
+ custom = true
if cb(fd.AsyncFD):
p.selector.withData(fd, adata) do:
if adata.readCB == cb:
adata.readCB = nil
p.selector.unregister(fd)
- if update:
+ # because state `data` can be modified in callback we need to update
+ # descriptor events with currently registered callbacks.
+ if not custom:
+ var update = false
var newEvents: set[Event] = {}
p.selector.withData(fd, adata) do:
if adata.readCB != nil: incl(newEvents, Event.Read)
if adata.writeCB != nil: incl(newEvents, Event.Write)
- p.selector.updateHandle(fd, newEvents)
+ update = true
+ if update:
+ p.selector.updateHandle(fd, newEvents)
inc(i)
# Timer processing.
@@ -1517,7 +1313,7 @@ else:
addRead(socket, cb)
return retFuture
- proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
+ proc recvInto*(socket: AsyncFD, buf: pointer, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto")
@@ -1541,6 +1337,38 @@ else:
addRead(socket, cb)
return retFuture
+ proc send*(socket: AsyncFD, buf: pointer, size: int,
+ flags = {SocketFlag.SafeDisconn}): Future[void] =
+ var retFuture = newFuture[void]("send")
+
+ var written = 0
+
+ proc cb(sock: AsyncFD): bool =
+ result = true
+ let netSize = size-written
+ var d = cast[cstring](buf)
+ let res = send(sock.SocketHandle, addr d[written], netSize.cint,
+ MSG_NOSIGNAL)
+ if res < 0:
+ let lastError = osLastError()
+ if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+ if flags.isDisconnectionError(lastError):
+ retFuture.complete()
+ else:
+ retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+ else:
+ result = false # We still want this callback to be called.
+ else:
+ written.inc(res)
+ if res != netSize:
+ result = false # We still have data to send.
+ else:
+ retFuture.complete()
+ # TODO: The following causes crashes.
+ #if not cb(socket):
+ addWrite(socket, cb)
+ return retFuture
+
proc send*(socket: AsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send")
@@ -1682,15 +1510,15 @@ else:
proc newAsyncEvent*(): AsyncEvent =
## Creates new ``AsyncEvent``.
- result = AsyncEvent(ioselectors.newSelectEvent())
+ result = AsyncEvent(newSelectEvent())
proc setEvent*(ev: AsyncEvent) =
## Sets new ``AsyncEvent`` to signaled state.
- ioselectors.setEvent(SelectEvent(ev))
+ setEvent(SelectEvent(ev))
proc close*(ev: AsyncEvent) =
## Closes ``AsyncEvent``
- ioselectors.close(SelectEvent(ev))
+ close(SelectEvent(ev))
proc addEvent*(ev: AsyncEvent, cb: Callback) =
## Start watching for event ``ev``, and call callback ``cb``, when
@@ -1742,360 +1570,7 @@ proc accept*(socket: AsyncFD,
return retFut
# -- Await Macro
-
-proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
- # Skips a nest of StmtList's.
- result = node
- if node[0].kind == nnkStmtList:
- result = skipUntilStmtList(node[0])
-
-proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
- result = node
- if node[0].kind == nnkStmtList:
- result = node[0]
-
-template createCb(retFutureSym, iteratorNameSym,
- name: expr): stmt {.immediate.} =
- var nameIterVar = iteratorNameSym
- #{.push stackTrace: off.}
- proc cb {.closure,gcsafe.} =
- try:
- if not nameIterVar.finished:
- var next = nameIterVar()
- if next == nil:
- assert retFutureSym.finished, "Async procedure's (" &
- name & ") return Future was not finished."
- else:
- next.callback = cb
- except:
- if retFutureSym.finished:
- # Take a look at tasyncexceptions for the bug which this fixes.
- # That test explains it better than I can here.
- raise
- else:
- retFutureSym.fail(getCurrentException())
- cb()
- #{.pop.}
-proc generateExceptionCheck(futSym,
- tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
- if tryStmt.kind == nnkNilLit:
- result = rootReceiver
- else:
- var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[]
- let errorNode = newDotExpr(futSym, newIdentNode("error"))
- for i in 1 .. -> else: raise futSym.error
- exceptionChecks.add((newIdentNode("true"),
- newNimNode(nnkRaiseStmt).add(errorNode)))
- # Read the future if there is no error.
- # -> else: futSym.read
- let elseNode = newNimNode(nnkElse, fromNode)
- elseNode.add newNimNode(nnkStmtList, fromNode)
- elseNode[0].add rootReceiver
-
- let ifBody = newStmtList()
- ifBody.add newCall(newIdentNode("setCurrentException"), errorNode)
- ifBody.add newIfStmt(exceptionChecks)
- ifBody.add newCall(newIdentNode("setCurrentException"), newNilLit())
-
- result = newIfStmt(
- (newDotExpr(futSym, newIdentNode("failed")), ifBody)
- )
- result.add elseNode
-
-template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
- rootReceiver: expr, fromNode: NimNode) =
- ## Params:
- ## futureVarNode: The NimNode which is a symbol identifying the Future[T]
- ## variable to yield.
- ## fromNode: Used for better debug information (to give context).
- ## valueReceiver: The node which defines an expression that retrieves the
- ## future's value.
- ##
- ## rootReceiver: ??? TODO
- # -> yield future
- result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
- # -> future.read
- valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
- result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver,
- fromNode)
-
-template createVar(result: var NimNode, futSymName: string,
- asyncProc: NimNode,
- valueReceiver, rootReceiver: expr,
- fromNode: NimNode) =
- result = newNimNode(nnkStmtList, fromNode)
- var futSym = genSym(nskVar, "future")
- result.add newVarStmt(futSym, asyncProc) # -> var future = y
- useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
-
-proc processBody(node, retFutureSym: NimNode,
- subTypeIsVoid: bool,
- tryStmt: NimNode): NimNode {.compileTime.} =
- #echo(node.treeRepr)
- result = node
- case node.kind
- of nnkReturnStmt:
- result = newNimNode(nnkStmtList, node)
- if node[0].kind == nnkEmpty:
- if not subTypeIsVoid:
- result.add newCall(newIdentNode("complete"), retFutureSym,
- newIdentNode("result"))
- else:
- result.add newCall(newIdentNode("complete"), retFutureSym)
- else:
- result.add newCall(newIdentNode("complete"), retFutureSym,
- node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt))
-
- result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
- return # Don't process the children of this return stmt
- of nnkCommand, nnkCall:
- if node[0].kind == nnkIdent and node[0].ident == !"await":
- case node[1].kind
- of nnkIdent, nnkInfix, nnkDotExpr:
- # await x
- # await x or y
- result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x
- of nnkCall, nnkCommand:
- # await foo(p, x)
- # await foo p, x
- var futureValue: NimNode
- result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
- futureValue, node)
- else:
- error("Invalid node kind in 'await', got: " & $node[1].kind)
- elif node.len > 1 and node[1].kind == nnkCommand and
- node[1][0].kind == nnkIdent and node[1][0].ident == !"await":
- # foo await x
- var newCommand = node
- result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
- newCommand, node)
-
- of nnkVarSection, nnkLetSection:
- case node[0][2].kind
- of nnkCommand:
- if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await":
- # var x = await y
- var newVarSection = node # TODO: Should this use copyNimNode?
- result.createVar("future" & $node[0][0].ident, node[0][2][1],
- newVarSection[0][2], newVarSection, node)
- else: discard
- of nnkAsgn:
- case node[1].kind
- of nnkCommand:
- if node[1][0].ident == !"await":
- # x = await y
- var newAsgn = node
- result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node)
- else: discard
- of nnkDiscardStmt:
- # discard await x
- if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
- node[0][0].ident == !"await":
- var newDiscard = node
- result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
- newDiscard[0], newDiscard, node)
- of nnkTryStmt:
- # try: await x; except: ...
- result = newNimNode(nnkStmtList, node)
- template wrapInTry(n, tryBody: expr) =
- var temp = n
- n[0] = tryBody
- tryBody = temp
-
- # Transform ``except`` body.
- # TODO: Could we perform some ``await`` transformation here to get it
- # working in ``except``?
- tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil)
-
- proc processForTry(n: NimNode, i: var int,
- res: NimNode): bool {.compileTime.} =
- ## Transforms the body of the tryStmt. Does not transform the
- ## body in ``except``.
- ## Returns true if the tryStmt node was transformed into an ifStmt.
- result = false
- var skipped = n.skipStmtList()
- while i < skipped.len:
- var processed = processBody(skipped[i], retFutureSym,
- subTypeIsVoid, n)
-
- # Check if we transformed the node into an exception check.
- # This suggests skipped[i] contains ``await``.
- if processed.kind != skipped[i].kind or processed.len != skipped[i].len:
- processed = processed.skipUntilStmtList()
- expectKind(processed, nnkStmtList)
- expectKind(processed[2][1], nnkElse)
- i.inc
-
- if not processForTry(n, i, processed[2][1][0]):
- # We need to wrap the nnkElse nodes back into a tryStmt.
- # As they are executed if an exception does not happen
- # inside the awaited future.
- # The following code will wrap the nodes inside the
- # original tryStmt.
- wrapInTry(n, processed[2][1][0])
-
- res.add processed
- result = true
- else:
- res.add skipped[i]
- i.inc
- var i = 0
- if not processForTry(node, i, result):
- # If the tryStmt hasn't been transformed we can just put the body
- # back into it.
- wrapInTry(node, result)
- return
- else: discard
-
- for i in 0 .. var retFuture = newFuture[T]()
- var retFutureSym = genSym(nskVar, "retFuture")
- var subRetType =
- if returnType.kind == nnkEmpty: newIdentNode("void")
- else: baseType
- outerProcBody.add(
- newVarStmt(retFutureSym,
- newCall(
- newNimNode(nnkBracketExpr, prc[6]).add(
- newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
- subRetType),
- newLit(prc[0].getName)))) # Get type from return type of this proc
-
- # -> iterator nameIter(): FutureBase {.closure.} =
- # -> {.push warning[resultshadowed]: off.}
- # -> var result: T
- # -> {.pop.}
- # ->
- # -> complete(retFuture, result)
- var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
- var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
- if not subtypeIsVoid:
- procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
- newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
- newIdentNode("warning"), newIdentNode("resultshadowed")),
- newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
-
- procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
- newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
-
- procBody.insert(2, newNimNode(nnkPragma).add(
- newIdentNode("pop"))) # -> {.pop.})
-
- procBody.add(
- newCall(newIdentNode("complete"),
- retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
- else:
- # -> complete(retFuture)
- procBody.add(newCall(newIdentNode("complete"), retFutureSym))
-
- var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
- procBody, nnkIteratorDef)
- closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
- outerProcBody.add(closureIterator)
-
- # -> createCb(retFuture)
- #var cbName = newIdentNode("cb")
- var procCb = newCall(bindSym"createCb", retFutureSym, iteratorNameSym,
- newStrLitNode(prc[0].getName))
- outerProcBody.add procCb
-
- # -> return retFuture
- outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
-
- result = prc
-
- # Remove the 'async' pragma.
- for i in 0 ..
#include
#include
-#include "clinenoise.h"
+#ifndef __LINENOISE_H
+# include "clinenoise.h"
+#endif
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
diff --git a/lib/wrappers/linenoise/linenoise.nim b/lib/wrappers/linenoise/linenoise.nim
index b7004c6e2..4ac7bf4a8 100644
--- a/lib/wrappers/linenoise/linenoise.nim
+++ b/lib/wrappers/linenoise/linenoise.nim
@@ -14,7 +14,8 @@ type
CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.}
-{.compile: "clinenoise.c".}
+{.emit: staticRead"clinenoise.h".}
+{.emit: staticRead"clinenoise.c".}
proc setCompletionCallback*(a2: ptr CompletionCallback) {.
importc: "linenoiseSetCompletionCallback".}
diff --git a/lib/wrappers/mysql.nim b/lib/wrappers/mysql.nim
index af504864d..6dbed23b3 100644
--- a/lib/wrappers/mysql.nim
+++ b/lib/wrappers/mysql.nim
@@ -13,10 +13,10 @@
when defined(Unix):
when defined(macosx):
const
- lib = "libmysqlclient.(15|16|17|18).dylib"
+ lib = "libmysqlclient.(15|16|17|18|19|20).dylib"
else:
const
- lib = "libmysqlclient.so.(15|16|17|18)"
+ lib = "libmysqlclient.so.(15|16|17|18|19|20)"
when defined(Windows):
const
lib = "libmysql.dll"
diff --git a/readme.md b/readme.md
index f56b054d6..36d1ab0f9 100644
--- a/readme.md
+++ b/readme.md
@@ -51,6 +51,7 @@ instead of ``build.sh``.
The ``koch`` tool is the Nim build tool, more ``koch`` related options are
documented in [doc/koch.rst](doc/koch.rst).
+
## Nimble
[Nimble](https://github.com/nim-lang/nimble) is Nim's package manager. For the
source based installations where you added Nim's ``bin`` directory to your PATH
@@ -60,21 +61,18 @@ the easiest way of installing Nimble is via:
$ nim e install_nimble.nims
```
-**Warning:** If you install Nimble this way, you will not be able to use binary
-Nimble packages or update Nimble easily.
-The [Nimble readme](https://github.com/nim-lang/nimble#installation)
-provides thorough instructions on how to install Nimble, so that this isn't a
-problem.
-
## Community
[](https://webchat.freenode.net/?channels=nim)
+[](https://gitter.im/nim-lang/Nim)
[](http://forum.nim-lang.org)
[](http://stackoverflow.com/questions/tagged/nim?sort=newest&pageSize=15)
[](https://twitter.com/nim_lang)
* The [forum](http://forum.nim-lang.org/) - the best place to ask questions and to discuss Nim.
-* [IRC (Freenode#nim)](https://webchat.freenode.net/?channels=nim) - the best place to discuss
+* [IRC (Freenode#nim)](https://webchat.freenode.net/?channels=nim) - a place to discuss
Nim in real-time, this is also where most development decision get made!
+* [Gitter](https://gitter.im/nim-lang/Nim) allows to discuss Nim from your browser, one click to join.
+ There is a bridge between Gitter and IRC channels.
* [Stackoverflow](http://stackoverflow.com/questions/tagged/nim)
## Contributing
diff --git a/tests/array/troof2.nim b/tests/array/troof2.nim
index d4c1a4982..e4b4f4b3c 100644
--- a/tests/array/troof2.nim
+++ b/tests/array/troof2.nim
@@ -2,8 +2,8 @@ discard """
errormsg: "invalid context for '^' as 'foo()' has side effects"
line: "9"
"""
-
-proc foo(): seq[int] =
+# XXX This needs to be fixed properly!
+proc foo(): seq[int] {.sideEffect.} =
echo "ha"
let f = foo()[^1]
diff --git a/tests/async/config.nims b/tests/async/config.nims
new file mode 100644
index 000000000..97c2e0aa4
--- /dev/null
+++ b/tests/async/config.nims
@@ -0,0 +1,2 @@
+when defined(upcoming):
+ patchFile("stdlib", "asyncdispatch", "$lib/upcoming/asyncdispatch")
diff --git a/tests/async/tasyncdiscard.nim b/tests/async/tasyncdiscard.nim
index e7c87ad42..64e6021c3 100644
--- a/tests/async/tasyncdiscard.nim
+++ b/tests/async/tasyncdiscard.nim
@@ -10,7 +10,7 @@ discard """
6
'''
"""
-import asyncio, asyncdispatch, asyncnet
+import asyncdispatch, asyncnet
proc main {.async.} =
proc f: Future[int] {.async.} =
diff --git a/tests/async/tasynciossl.nim b/tests/async/tasynciossl.nim
deleted file mode 100644
index ba856760e..000000000
--- a/tests/async/tasynciossl.nim
+++ /dev/null
@@ -1,92 +0,0 @@
-discard """
- file: "tasynciossl.nim"
- cmd: "nim $target --hints:on --define:ssl $options $file"
- output: "20000"
-"""
-import sockets, asyncio, strutils, times
-
-var disp {.threadvar.}: PDispatcher
-disp = newDispatcher()
-var msgCount = 0
-
-when defined(ssl):
- var ctx = newContext(verifyMode = CVerifyNone,
- certFile = "tests/testdata/mycert.pem", keyFile = "tests/testdata/mycert.pem")
-
- var ctx1 = newContext(verifyMode = CVerifyNone)
-
-const
- swarmSize = 50
- messagesToSend = 100
-
-proc swarmConnect(s: PAsyncSocket) =
- #echo("Connected")
- for i in 1..messagesToSend:
- s.send("Message " & $i & "\c\L")
- s.close()
-
-proc serverRead(s: PAsyncSocket) =
- var line = ""
- assert s.readLine(line)
- if line != "":
- #echo(line)
- if line.startsWith("Message "):
- msgCount.inc()
- else:
- assert(false)
- else:
- s.close()
-
-proc serverAccept(s: PAsyncSocket) =
- var client: PAsyncSocket
- new(client)
- s.accept(client)
- client.handleRead = serverRead
- disp.register(client)
-
-proc launchSwarm(disp: var PDispatcher, port: TPort, count: int,
- buffered = true, useSSL = false) =
- for i in 1..count:
- var client = asyncSocket()
- when defined(ssl):
- if useSSL:
- ctx1.wrapSocket(client)
- client.handleConnect = swarmConnect
- disp.register(client)
- client.connect("localhost", port)
-
-proc createSwarm(port: TPort, buffered = true, useSSL = false) =
- var server = asyncSocket()
- when defined(ssl):
- if useSSL:
- ctx.wrapSocket(server)
- server.handleAccept = serverAccept
- disp.register(server)
- server.bindAddr(port)
- server.listen()
- disp.launchSwarm(port, swarmSize, buffered, useSSL)
-
-when defined(ssl):
- const serverCount = 4
-else:
- const serverCount = 2
-
-createSwarm(TPort(10235))
-createSwarm(TPort(10236), false)
-
-when defined(ssl):
- createSwarm(TPort(10237), true, true)
- createSwarm(TPort(10238), false, true)
-
-var startTime = epochTime()
-while true:
- if epochTime() - startTime >= 300.0:
- break
- if not disp.poll(): break
- if disp.len == serverCount:
- # Only the servers are left in the dispatcher. All clients finished,
- # we need to therefore break.
- break
-
-assert msgCount == (swarmSize * messagesToSend) * serverCount
-echo(msgCount)
diff --git a/tests/async/tasyncsend4757.nim b/tests/async/tasyncsend4757.nim
new file mode 100644
index 000000000..1066f38e5
--- /dev/null
+++ b/tests/async/tasyncsend4757.nim
@@ -0,0 +1,14 @@
+discard """
+ file: "tasyncsend4754.nim"
+ output: "Finished"
+"""
+
+import asyncdispatch
+
+proc f(): Future[void] {.async.} =
+ let s = newAsyncNativeSocket()
+ await s.connect("example.com", 80.Port)
+ await s.send("123")
+ echo "Finished"
+
+waitFor f()
diff --git a/tests/async/tasyncssl.nim b/tests/async/tasyncssl.nim
new file mode 100644
index 000000000..3dc131036
--- /dev/null
+++ b/tests/async/tasyncssl.nim
@@ -0,0 +1,66 @@
+discard """
+ file: "tasyncssl.nim"
+ cmd: "nim $target --hints:on --define:ssl $options $file"
+ output: "500"
+"""
+import asyncdispatch, asyncnet, net, strutils, os
+
+when defined(ssl):
+ var msgCount = 0
+
+ const
+ swarmSize = 10
+ messagesToSend = 50
+
+ var clientCount = 0
+
+ proc sendMessages(client: AsyncSocket) {.async.} =
+ for i in 0 .. = 300.0:
- break
-
- if not disp.poll():
- break
-
- if (msgCount div messagesToSend) * serverCount == currentClient:
- createClient(disp, TPort(10335), false)
- createClient(disp, TPort(10336), true)
-
- if msgCount == messagesToSend * serverCount * swarmSize:
- break
-
-assert msgCount == messagesToSend * serverCount * swarmSize
-echo(msgCount)
diff --git a/tests/async/tawaitsemantics.nim b/tests/async/tawaitsemantics.nim
index 3e0c3903e..98fb5dfd5 100644
--- a/tests/async/tawaitsemantics.nim
+++ b/tests/async/tawaitsemantics.nim
@@ -2,17 +2,21 @@ discard """
file: "tawaitsemantics.nim"
exitcode: 0
output: '''
-Error caught
-Test infix
-Test call
+Error can be caught using yield
+Infix `or` raises
+Infix `and` raises
+All() raises
+Awaiting a async procedure call raises
+Awaiting a future raises
'''
"""
import asyncdispatch
# This tests the behaviour of 'await' under different circumstances.
-# For example, when awaiting Future variable and this future has failed the
-# exception shouldn't be raised as described here
+# Specifically, when an awaited future raises an exception then `await` should
+# also raise that exception by `read`'ing that future. In cases where you don't
+# want this behaviour, you can use `yield`.
# https://github.com/nim-lang/Nim/issues/4170
proc thrower(): Future[void] =
@@ -23,37 +27,71 @@ proc dummy: Future[void] =
result = newFuture[void]()
result.complete()
-proc testInfix() {.async.} =
- # Test the infix operator semantics.
+proc testInfixOr() {.async.} =
+ # Test the infix `or` operator semantics.
var fut = thrower()
var fut2 = dummy()
- await fut or fut2 # Shouldn't raise.
- # TODO: what about: await thrower() or fut2?
+ await fut or fut2 # Should raise!
+
+proc testInfixAnd() {.async.} =
+ # Test the infix `and` operator semantics.
+ var fut = thrower()
+ var fut2 = dummy()
+ await fut and fut2 # Should raise!
+
+proc testAll() {.async.} =
+ # Test the `all` semantics.
+ var fut = thrower()
+ var fut2 = dummy()
+ await all(fut, fut2) # Should raise!
proc testCall() {.async.} =
await thrower()
+proc testAwaitFut() {.async.} =
+ var fut = thrower()
+ await fut # This should raise.
+
proc tester() {.async.} =
# Test that we can handle exceptions without 'try'
var fut = thrower()
doAssert fut.finished
doAssert fut.failed
doAssert fut.error.msg == "Test"
- await fut # We are awaiting a 'Future', so no `read` occurs.
+ yield fut # We are yielding a 'Future', so no `read` occurs.
doAssert fut.finished
doAssert fut.failed
doAssert fut.error.msg == "Test"
- echo("Error caught")
+ echo("Error can be caught using yield")
- fut = testInfix()
- await fut
+ fut = testInfixOr()
+ yield fut
doAssert fut.finished
- doAssert(not fut.failed)
- echo("Test infix")
+ doAssert fut.failed
+ echo("Infix `or` raises")
+
+ fut = testInfixAnd()
+ yield fut
+ doAssert fut.finished
+ doAssert fut.failed
+ echo("Infix `and` raises")
+
+ fut = testAll()
+ yield fut
+ doAssert fut.finished
+ doAssert fut.failed
+ echo("All() raises")
fut = testCall()
- await fut
+ yield fut
doAssert fut.failed
- echo("Test call")
+ echo("Awaiting a async procedure call raises")
+
+ # Test that await will read the future and raise an exception.
+ fut = testAwaitFut()
+ yield fut
+ doAssert fut.failed
+ echo("Awaiting a future raises")
+
waitFor(tester())
diff --git a/tests/async/tfuturevar.nim b/tests/async/tfuturevar.nim
new file mode 100644
index 000000000..73c0fddf7
--- /dev/null
+++ b/tests/async/tfuturevar.nim
@@ -0,0 +1,47 @@
+import asyncdispatch
+
+proc completeOnReturn(fut: FutureVar[string], x: bool) {.async.} =
+ if x:
+ fut.mget() = ""
+ fut.mget.add("foobar")
+ return
+
+proc completeOnImplicitReturn(fut: FutureVar[string], x: bool) {.async.} =
+ if x:
+ fut.mget() = ""
+ fut.mget.add("foobar")
+
+proc failureTest(fut: FutureVar[string], x: bool) {.async.} =
+ if x:
+ raise newException(Exception, "Test")
+
+proc manualComplete(fut: FutureVar[string], x: bool) {.async.} =
+ if x:
+ fut.mget() = "Hello World"
+ fut.complete()
+ return
+
+proc main() {.async.} =
+ var fut: FutureVar[string]
+
+ fut = newFutureVar[string]()
+ await completeOnReturn(fut, true)
+ doAssert(fut.read() == "foobar")
+
+ fut = newFutureVar[string]()
+ await completeOnImplicitReturn(fut, true)
+ doAssert(fut.read() == "foobar")
+
+ fut = newFutureVar[string]()
+ let retFut = failureTest(fut, true)
+ yield retFut
+ doAssert(fut.read().isNil)
+ doAssert(fut.finished)
+
+ fut = newFutureVar[string]()
+ await manualComplete(fut, true)
+ doAssert(fut.read() == "Hello World")
+
+
+waitFor main()
+
diff --git a/tests/async/tioselectors.nim b/tests/async/tioselectors.nim
index 2237de01a..71d901e69 100644
--- a/tests/async/tioselectors.nim
+++ b/tests/async/tioselectors.nim
@@ -217,6 +217,216 @@ when not defined(windows):
assert(selector.isEmpty())
result = true
+ when defined(macosx) or defined(freebsd) or defined(openbsd) or
+ defined(netbsd):
+
+ proc rename(frompath: cstring, topath: cstring): cint
+ {.importc: "rename", header: "".}
+
+ proc createFile(name: string): cint =
+ result = posix.open(cstring(name), posix.O_CREAT or posix.O_RDWR)
+ if result == -1:
+ raiseOsError(osLastError())
+
+ proc writeFile(name: string, data: string) =
+ let fd = posix.open(cstring(name), posix.O_APPEND or posix.O_RDWR)
+ if fd == -1:
+ raiseOsError(osLastError())
+ let length = len(data).cint
+ if posix.write(fd, cast[pointer](unsafeAddr data[0]),
+ len(data).cint) != length:
+ raiseOsError(osLastError())
+ if posix.close(fd) == -1:
+ raiseOsError(osLastError())
+
+ proc closeFile(fd: cint) =
+ if posix.close(fd) == -1:
+ raiseOsError(osLastError())
+
+ proc removeFile(name: string) =
+ let err = posix.unlink(cstring(name))
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc createDir(name: string) =
+ let err = posix.mkdir(cstring(name), 0x1FF)
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc removeDir(name: string) =
+ let err = posix.rmdir(cstring(name))
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc chmodPath(name: string, mode: cint) =
+ let err = posix.chmod(cstring(name), Mode(mode))
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc renameFile(names: string, named: string) =
+ let err = rename(cstring(names), cstring(named))
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc symlink(names: string, named: string) =
+ let err = posix.symlink(cstring(names), cstring(named))
+ if err == -1:
+ raiseOsError(osLastError())
+
+ proc openWatch(name: string): cint =
+ result = posix.open(cstring(name), posix.O_RDONLY)
+ if result == -1:
+ raiseOsError(osLastError())
+
+ const
+ testDirectory = "/tmp/kqtest"
+
+ type
+ valType = object
+ fd: cint
+ events: set[Event]
+
+ proc vnode_test(): bool =
+ proc validate[T](test: openarray[ReadyKey[T]],
+ check: openarray[valType]): bool =
+ result = false
+ if len(test) == len(check):
+ for checkItem in check:
+ result = false
+ for testItem in test:
+ if testItem.fd == checkItem.fd and
+ checkItem.events <= testItem.events:
+ result = true
+ break
+ if not result:
+ break
+
+ var res: seq[ReadyKey[int]]
+ var selector = newSelector[int]()
+ var events = {Event.VnodeWrite, Event.VnodeDelete, Event.VnodeExtend,
+ Event.VnodeAttrib, Event.VnodeLink, Event.VnodeRename,
+ Event.VnodeRevoke}
+
+ result = true
+ discard posix.unlink(testDirectory)
+
+ createDir(testDirectory)
+ var dirfd = posix.open(cstring(testDirectory), posix.O_RDONLY)
+ if dirfd == -1:
+ raiseOsError(osLastError())
+
+ selector.registerVnode(dirfd, events, 1)
+ selector.flush()
+
+ # chmod testDirectory to 0777
+ chmodPath(testDirectory, 0x1FF)
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeAttrib} <= res[0].events)
+
+ # create subdirectory
+ createDir(testDirectory & "/test")
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeWrite,
+ Event.VnodeLink} <= res[0].events)
+
+ # open test directory for watching
+ var testfd = openWatch(testDirectory & "/test")
+ selector.registerVnode(testfd, events, 2)
+ selector.flush()
+ doAssert(len(selector.select(0)) == 0)
+
+ # rename test directory
+ renameFile(testDirectory & "/test", testDirectory & "/renamed")
+ res = selector.select(0)
+ doAssert(len(res) == 2)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(validate(res,
+ [valType(fd: dirfd, events: {Event.Vnode, Event.VnodeWrite}),
+ valType(fd: testfd,
+ events: {Event.Vnode, Event.VnodeRename})])
+ )
+
+ # remove test directory
+ removeDir(testDirectory & "/renamed")
+ res = selector.select(0)
+ doAssert(len(res) == 2)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(validate(res,
+ [valType(fd: dirfd, events: {Event.Vnode, Event.VnodeWrite,
+ Event.VnodeLink}),
+ valType(fd: testfd,
+ events: {Event.Vnode, Event.VnodeDelete})])
+ )
+ # create file new test file
+ testfd = createFile(testDirectory & "/testfile")
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeWrite} <= res[0].events)
+
+ # close new test file
+ closeFile(testfd)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(len(selector.select(0)) == 0)
+
+ # chmod test file with 0666
+ chmodPath(testDirectory & "/testfile", 0x1B6)
+ doAssert(len(selector.select(0)) == 0)
+
+ testfd = openWatch(testDirectory & "/testfile")
+ selector.registerVnode(testfd, events, 1)
+ selector.flush()
+
+ # write data to test file
+ writeFile(testDirectory & "/testfile", "TESTDATA")
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == testfd and
+ {Event.Vnode, Event.VnodeWrite,
+ Event.VnodeExtend} <= res[0].events)
+
+ # symlink test file
+ symlink(testDirectory & "/testfile", testDirectory & "/testlink")
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeWrite} <= res[0].events)
+
+ # remove test file
+ removeFile(testDirectory & "/testfile")
+ res = selector.select(0)
+ doAssert(len(res) == 2)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(validate(res,
+ [valType(fd: testfd, events: {Event.Vnode, Event.VnodeDelete}),
+ valType(fd: dirfd, events: {Event.Vnode, Event.VnodeWrite})])
+ )
+
+ # remove symlink
+ removeFile(testDirectory & "/testlink")
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeWrite} <= res[0].events)
+
+ # remove testDirectory
+ removeDir(testDirectory)
+ res = selector.select(0)
+ doAssert(len(res) == 1)
+ doAssert(len(selector.select(0)) == 0)
+ doAssert(res[0].fd == dirfd and
+ {Event.Vnode, Event.VnodeDelete} <= res[0].events)
+
when hasThreadSupport:
var counter = 0
@@ -256,6 +466,9 @@ when not defined(windows):
processTest("Timer notification test...", timer_notification_test())
processTest("Process notification test...", process_notification_test())
processTest("Signal notification test...", signal_notification_test())
+ when defined(macosx) or defined(freebsd) or defined(openbsd) or
+ defined(netbsd):
+ processTest("File notification test...", vnode_test())
echo("All tests passed!")
else:
import nativesockets, winlean, os, osproc
diff --git a/tests/async/tmultisync.nim b/tests/async/tmultisync.nim
new file mode 100644
index 000000000..9ef9b105c
--- /dev/null
+++ b/tests/async/tmultisync.nim
@@ -0,0 +1,8 @@
+import asyncdispatch, net, asyncnet
+
+proc recvTwice(socket: Socket | AsyncSocket,
+ size: int): Future[string] {.multisync.} =
+ var x = await socket.recv(size)
+ var y = await socket.recv(size+1)
+ return x & "aboo" & y
+
diff --git a/tests/async/tnewasyncudp.nim b/tests/async/tnewasyncudp.nim
index 7025fa20d..6277e877c 100644
--- a/tests/async/tnewasyncudp.nim
+++ b/tests/async/tnewasyncudp.nim
@@ -39,8 +39,11 @@ proc prepareAddress(intaddr: uint32, intport: uint16): ptr Sockaddr_in =
proc launchSwarm(name: ptr SockAddr) {.async.} =
var i = 0
var k = 0
+ var buffer: array[16384, char]
+ var slen = sizeof(Sockaddr_in).SockLen
+ var saddr = Sockaddr_in()
while i < swarmSize:
- var peeraddr = prepareAddress(INADDR_ANY, 0)
+ var peeraddr = prepareAddress(INADDR_LOOPBACK, 0)
var sock = newAsyncNativeSocket(nativesockets.AF_INET,
nativesockets.SOCK_DGRAM,
Protocol.IPPROTO_UDP)
@@ -50,10 +53,19 @@ proc launchSwarm(name: ptr SockAddr) {.async.} =
let sockport = getSockName(sock.SocketHandle).int
k = 0
while k < messagesToSend:
+ zeroMem(addr(buffer[0]), 16384)
+ zeroMem(cast[pointer](addr(saddr)), sizeof(Sockaddr_in))
var message = "Message " & $(i * messagesToSend + k)
await sendTo(sock, addr message[0], len(message),
name, sizeof(Sockaddr_in).SockLen)
- saveSendingPort(sockport)
+ var size = await recvFromInto(sock, cast[pointer](addr buffer[0]),
+ 16384, cast[ptr SockAddr](addr saddr),
+ addr slen)
+ size = 0
+ var grammString = $buffer
+ if grammString == message:
+ saveSendingPort(sockport)
+ inc(recvCount)
inc(k)
closeSocket(sock)
inc(i)
@@ -75,13 +87,14 @@ proc readMessages(server: AsyncFD) {.async.} =
var grammString = $buffer
if grammString.startswith("Message ") and
saddr.sin_addr.s_addr == 0x100007F:
+ await sendTo(server, addr grammString[0], len(grammString),
+ cast[ptr SockAddr](addr saddr), slen)
inc(msgCount)
saveReceivedPort(ntohs(saddr.sin_port).int)
- inc(recvCount)
inc(i)
proc createServer() {.async.} =
- var name = prepareAddress(INADDR_ANY, serverPort)
+ var name = prepareAddress(INADDR_LOOPBACK, serverPort)
var server = newAsyncNativeSocket(nativesockets.AF_INET,
nativesockets.SOCK_DGRAM,
Protocol.IPPROTO_UDP)
@@ -90,7 +103,7 @@ proc createServer() {.async.} =
raiseOSError(osLastError())
asyncCheck readMessages(server)
-var name = prepareAddress(0x7F000001, serverPort) # 127.0.0.1
+var name = prepareAddress(INADDR_LOOPBACK, serverPort) # 127.0.0.1
asyncCheck createServer()
asyncCheck launchSwarm(cast[ptr SockAddr](name))
while true:
diff --git a/tests/async/tpolltimeouts.nim b/tests/async/tpolltimeouts.nim
new file mode 100644
index 000000000..dac33732d
--- /dev/null
+++ b/tests/async/tpolltimeouts.nim
@@ -0,0 +1,19 @@
+discard """
+ output: "true"
+"""
+# Issue https://github.com/nim-lang/Nim/issues/4262
+import asyncdispatch, times
+
+proc foo(): Future[int] {.async.} =
+ return 1
+
+proc bar(): Future[int] {.async.} =
+ return await foo()
+
+let start = epochTime()
+let barFut = bar()
+
+while not barFut.finished:
+ poll(2000)
+
+echo(epochTime() - start < 1.0)
diff --git a/tests/async/tupcoming_async.nim b/tests/async/tupcoming_async.nim
new file mode 100644
index 000000000..137794afd
--- /dev/null
+++ b/tests/async/tupcoming_async.nim
@@ -0,0 +1,114 @@
+discard """
+ output: '''
+OK
+OK
+OK
+OK
+'''
+"""
+
+when defined(upcoming):
+ import asyncdispatch, times, osproc, streams
+
+ const supportedPlatform = defined(linux) or defined(freebsd) or
+ defined(netbsd) or defined(openbsd) or
+ defined(macosx)
+
+ proc waitEvent(ev: AsyncEvent, closeEvent = false): Future[void] =
+ var retFuture = newFuture[void]("waitEvent")
+ proc cb(fd: AsyncFD): bool =
+ retFuture.complete()
+ if closeEvent:
+ return true
+ else:
+ return false
+ addEvent(ev, cb)
+ return retFuture
+
+ proc waitTimer(timeout: int): Future[void] =
+ var retFuture = newFuture[void]("waitTimer")
+ proc cb(fd: AsyncFD): bool =
+ retFuture.complete()
+ addTimer(timeout, true, cb)
+ return retFuture
+
+ proc waitProcess(p: Process): Future[void] =
+ var retFuture = newFuture[void]("waitProcess")
+ proc cb(fd: AsyncFD): bool =
+ retFuture.complete()
+ addProcess(p.processID(), cb)
+ return retFuture
+
+ proc delayedSet(ev: AsyncEvent, timeout: int): Future[void] {.async.} =
+ await waitTimer(timeout)
+ ev.setEvent()
+
+ proc timerTest() =
+ waitFor(waitTimer(200))
+ echo "OK"
+
+ proc eventTest() =
+ var event = newAsyncEvent()
+ var fut = waitEvent(event)
+ asyncCheck(delayedSet(event, 500))
+ waitFor(fut or waitTimer(1000))
+ if fut.finished:
+ echo "OK"
+ else:
+ echo "eventTest: Timeout expired before event received!"
+
+ proc processTest() =
+ when defined(windows):
+ var process = startProcess("ping.exe", "",
+ ["127.0.0.1", "-n", "2", "-w", "100"], nil,
+ {poStdErrToStdOut, poUsePath, poInteractive,
+ poDemon})
+ else:
+ var process = startProcess("/bin/sleep", "", ["1"], nil,
+ {poStdErrToStdOut, poUsePath})
+ var fut = waitProcess(process)
+ waitFor(fut or waitTimer(2000))
+ if fut.finished and process.peekExitCode() == 0:
+ echo "OK"
+ else:
+ echo "processTest: Timeout expired before process exited!"
+
+ when supportedPlatform:
+ import posix
+
+ proc waitSignal(signal: int): Future[void] =
+ var retFuture = newFuture[void]("waitSignal")
+ proc cb(fd: AsyncFD): bool =
+ retFuture.complete()
+ addSignal(signal, cb)
+ return retFuture
+
+ proc delayedSignal(signal: int, timeout: int): Future[void] {.async.} =
+ await waitTimer(timeout)
+ var pid = posix.getpid()
+ discard posix.kill(pid, signal.cint)
+
+ proc signalTest() =
+ var fut = waitSignal(posix.SIGINT)
+ asyncCheck(delayedSignal(posix.SIGINT, 500))
+ waitFor(fut or waitTimer(1000))
+ if fut.finished:
+ echo "OK"
+ else:
+ echo "signalTest: Timeout expired before signal received!"
+
+ when supportedPlatform:
+ timerTest()
+ eventTest()
+ processTest()
+ signalTest()
+ elif defined(windows):
+ timerTest()
+ eventTest()
+ processTest()
+ echo "OK"
+ else:
+ eventTest()
+ echo "OK\nOK\nOK"
+else:
+ echo "OK\nOK\nOK\nOK"
diff --git a/tests/bind/tnicerrorforsymchoice.nim b/tests/bind/tnicerrorforsymchoice.nim
index 5145fdcff..bd00188fa 100644
--- a/tests/bind/tnicerrorforsymchoice.nim
+++ b/tests/bind/tnicerrorforsymchoice.nim
@@ -1,6 +1,6 @@
discard """
line: 18
- errormsg: "type mismatch: got (proc (s: TScgi) | proc (client: AsyncSocket, headers: StringTableRef, input: string){.gcsafe, locks: 0.}"
+ errormsg: "type mismatch: got (proc (s: TScgi) | proc (client: AsyncSocket, headers: StringTableRef, input: string){.noSideEffect, gcsafe, locks: 0.}"
"""
#bug #442
diff --git a/tests/ccgbugs/tescaping_temps.nim b/tests/ccgbugs/tescaping_temps.nim
index ef078913b..ea09261ea 100644
--- a/tests/ccgbugs/tescaping_temps.nim
+++ b/tests/ccgbugs/tescaping_temps.nim
@@ -18,3 +18,14 @@ test proc() =
test proc() = discard
else:
test proc() = discard
+
+# ensure 'case' does not trigger the same bug:
+test proc() =
+ let f = 15
+ case f
+ of 10:
+ test proc() = discard
+ of 3:
+ test proc() = discard
+ else:
+ test proc() = discard
diff --git a/tests/ccgbugs/tuple_canon.nim b/tests/ccgbugs/tuple_canon.nim
index 960e2aae9..a607f9cab 100644
--- a/tests/ccgbugs/tuple_canon.nim
+++ b/tests/ccgbugs/tuple_canon.nim
@@ -1,3 +1,10 @@
+
+
+# bug #4626
+var foo: (int, array[1, int]) # Tuple must be of length > 1
+let bar = (1, [1])
+foo = bar # No error if assigned directly
+
# bug #2250
import
diff --git a/tests/closure/tclosure4.nim b/tests/closure/tclosure4.nim
index 69c076cd5..bc134ded6 100644
--- a/tests/closure/tclosure4.nim
+++ b/tests/closure/tclosure4.nim
@@ -1,7 +1,7 @@
import json, tables, sequtils
-proc run(json_params: Table) =
+proc run(json_params: OrderedTable) =
let json_elems = json_params["files"].elems
# These fail compilation.
var files = map(json_elems, proc (x: JsonNode): string = x.str)
diff --git a/tests/collections/tableadds.nim b/tests/collections/tableadds.nim
new file mode 100644
index 000000000..71f1fad7d
--- /dev/null
+++ b/tests/collections/tableadds.nim
@@ -0,0 +1,13 @@
+discard """
+ output: '''done'''
+"""
+
+import tables
+
+proc main =
+ var tab = newTable[string, string]()
+ for i in 0..1000:
+ tab.add "key", "value " & $i
+
+main()
+echo "done"
diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim
index a8a182a78..59fef4920 100644
--- a/tests/collections/ttables.nim
+++ b/tests/collections/ttables.nim
@@ -134,6 +134,29 @@ block mpairsTableTest1:
block SyntaxTest:
var x = toTable[int, string]({:})
+# Until #4448 is fixed, these tests will fail
+when false:
+ block clearTableTest:
+ var t = data.toTable
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
+ block clearOrderedTableTest:
+ var t = data.toOrderedTable
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
+ block clearCountTableTest:
+ var t = initCountTable[string]()
+ t.inc("90", 3)
+ t.inc("12", 2)
+ t.inc("34", 1)
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
proc orderedTableSortTest() =
var t = initOrderedTable[string, int](2)
for key, val in items(data): t[key] = val
diff --git a/tests/collections/ttablesref.nim b/tests/collections/ttablesref.nim
index 32494f1f2..12af1ccbb 100644
--- a/tests/collections/ttablesref.nim
+++ b/tests/collections/ttablesref.nim
@@ -141,6 +141,31 @@ block anonZipTest:
let values = @[1, 2, 3]
doAssert "{a: 1, b: 2, c: 3}" == $ toTable zip(keys, values)
+block clearTableTest:
+ var t = newTable[string, float]()
+ t["test"] = 1.2345
+ t["111"] = 1.000043
+ t["123"] = 1.23
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
+block clearOrderedTableTest:
+ var t = newOrderedTable[string, int](2)
+ for key, val in items(data): t[key] = val
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
+block clearCountTableTest:
+ var t = newCountTable[string]()
+ t.inc("90", 3)
+ t.inc("12", 2)
+ t.inc("34", 1)
+ assert t.len() != 0
+ t.clear()
+ assert t.len() == 0
+
orderedTableSortTest()
echo "true"
diff --git a/tests/cpp/tempty_generic_obj.nim b/tests/cpp/tempty_generic_obj.nim
new file mode 100644
index 000000000..e2957a5cd
--- /dev/null
+++ b/tests/cpp/tempty_generic_obj.nim
@@ -0,0 +1,22 @@
+discard """
+ cmd: "nim cpp $file"
+ output: '''int
+float'''
+"""
+
+import typetraits
+
+# bug #4625
+type
+ Vector {.importcpp: "std::vector<'0 >", header: "vector".} [T] = object
+
+proc initVector[T](): Vector[T] {.importcpp: "'0(@)", header: "vector", constructor.}
+
+proc doSomething[T](v: var Vector[T]) =
+ echo T.name
+
+var v = initVector[int]()
+v.doSomething()
+
+var vf = initVector[float]()
+vf.doSomething() # Nim uses doSomething[int] here in C++
diff --git a/tests/distinct/tcurrncy.nim b/tests/distinct/tcurrncy.nim
index 7ad4caea4..2675de739 100644
--- a/tests/distinct/tcurrncy.nim
+++ b/tests/distinct/tcurrncy.nim
@@ -2,7 +2,7 @@ discard """
file: "tcurrncy.nim"
output: "25"
"""
-template Additive(typ: typeDesc): stmt =
+template Additive(typ: untyped) =
proc `+` *(x, y: typ): typ {.borrow.}
proc `-` *(x, y: typ): typ {.borrow.}
@@ -10,18 +10,18 @@ template Additive(typ: typeDesc): stmt =
proc `+` *(x: typ): typ {.borrow.}
proc `-` *(x: typ): typ {.borrow.}
-template Multiplicative(typ, base: typeDesc): stmt {.immediate.} =
+template Multiplicative(typ, base: untyped) =
proc `*` *(x: typ, y: base): typ {.borrow.}
proc `*` *(x: base, y: typ): typ {.borrow.}
proc `div` *(x: typ, y: base): typ {.borrow.}
proc `mod` *(x: typ, y: base): typ {.borrow.}
-template Comparable(typ: typeDesc): stmt =
+template Comparable(typ: untyped) =
proc `<` * (x, y: typ): bool {.borrow.}
proc `<=` * (x, y: typ): bool {.borrow.}
proc `==` * (x, y: typ): bool {.borrow.}
-template DefineCurrency(typ, base: expr): stmt {.immediate.} =
+template DefineCurrency(typ, base: untyped) =
type
typ* = distinct base
Additive(typ)
diff --git a/tests/effects/tsidee4.nim b/tests/effects/tsidee4.nim
index 2cb88a23e..ecc79580c 100644
--- a/tests/effects/tsidee4.nim
+++ b/tests/effects/tsidee4.nim
@@ -1,13 +1,13 @@
discard """
file: "tsidee4.nim"
- line: 15
- errormsg: "type mismatch"
+ line: 12
+ errormsg: "'noSideEffect' can have side effects"
"""
var
global: int
-proc dontcare(x: int): int = return x
+proc dontcare(x: int): int = return global
proc noSideEffect(x, y: int, p: proc (a: int): int {.noSideEffect.}): int {.noSideEffect.} =
return x + y + dontcare(x)
diff --git a/tests/exception/texceptions.nim b/tests/exception/texceptions.nim
index bdf338599..b30b3874b 100644
--- a/tests/exception/texceptions.nim
+++ b/tests/exception/texceptions.nim
@@ -9,7 +9,7 @@ FINALLY
RECOVER
BEFORE
-EXCEPT
+EXCEPT: IOError: hi
FINALLY
'''
"""
@@ -52,10 +52,10 @@ echo ""
proc return_in_except =
try:
echo "BEFORE"
- raise newException(IOError, "")
+ raise newException(IOError, "hi")
except:
- echo "EXCEPT"
+ echo "EXCEPT: ", getCurrentException().name, ": ", getCurrentExceptionMsg()
return
finally:
diff --git a/tests/exception/tunhandledexc.nim b/tests/exception/tunhandledexc.nim
index 63a402414..c318aec81 100644
--- a/tests/exception/tunhandledexc.nim
+++ b/tests/exception/tunhandledexc.nim
@@ -14,10 +14,9 @@ proc genErrors(s: string) =
raise newException(EsomeotherErr, "bla")
when true:
+ try: discard except: discard
+
try:
genErrors("errssor!")
except ESomething:
echo("Error happened")
-
-
-
diff --git a/tests/float/tfloat4.nim b/tests/float/tfloat4.nim
index 006b4d88f..d7783ce26 100644
--- a/tests/float/tfloat4.nim
+++ b/tests/float/tfloat4.nim
@@ -30,8 +30,10 @@ let testFloats = [
"0.00097656250000000021684043449710088680149056017398834228515625"
]
-for num in testFloats:
- doAssert num.parseFloat.floatToStr.parseFloat == num.parseFloat
+when not defined(windows):
+ # Windows' sprintf produces niceties like -1.#INF...
+ for num in testFloats:
+ doAssert num.parseFloat.floatToStr.parseFloat == num.parseFloat
doAssert "0".parseFloat == 0.0
doAssert "-.1".parseFloat == -0.1
diff --git a/tests/generics/tlamba_in_generic.nim b/tests/generics/tlamba_in_generic.nim
new file mode 100644
index 000000000..91d417b5e
--- /dev/null
+++ b/tests/generics/tlamba_in_generic.nim
@@ -0,0 +1,13 @@
+discard """
+ output: '''!!Hi!!'''
+"""
+# bug #4658
+import future
+
+var x = 123
+
+proc twice[T](f: T -> T): T -> T = (x: T) => f(f(x))
+
+proc quote(s: string): string = "!" & s & "!"
+
+echo twice(quote)("Hi")
diff --git a/tests/generics/ttypeclass_to_typeclass.nim b/tests/generics/ttypeclass_to_typeclass.nim
new file mode 100644
index 000000000..a5f010450
--- /dev/null
+++ b/tests/generics/ttypeclass_to_typeclass.nim
@@ -0,0 +1,12 @@
+# bug #4672
+type
+ EnumContainer[T: enum] = object
+ v: T
+ SomeEnum {.pure.} = enum
+ A,B,C
+
+proc value[T: enum](this: EnumContainer[T]): T =
+ this.v
+
+var enumContainer: EnumContainer[SomeEnum]
+discard enumContainer.value()
diff --git a/tests/import_in_config/nim.cfg b/tests/import_in_config/nim.cfg
new file mode 100644
index 000000000..af112d112
--- /dev/null
+++ b/tests/import_in_config/nim.cfg
@@ -0,0 +1,2 @@
+import = "other"
+path = "$projectDir"
diff --git a/tests/import_in_config/other.nim b/tests/import_in_config/other.nim
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/import_in_config/tmain.nim b/tests/import_in_config/tmain.nim
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/js/taddr.nim b/tests/js/taddr.nim
index 1fba30d55..d8ff4c11b 100644
--- a/tests/js/taddr.nim
+++ b/tests/js/taddr.nim
@@ -70,3 +70,10 @@ proc testPtr(p: pointer, a: int) =
var i = 123
testPtr(addr i, 5)
doAssert(i == 124)
+
+var someGlobal = 5
+proc getSomeGlobalPtr(): ptr int = addr someGlobal
+let someGlobalPtr = getSomeGlobalPtr()
+doAssert(someGlobalPtr[] == 5)
+someGlobalPtr[] = 10
+doAssert(someGlobal == 10)
diff --git a/tests/js/tbyvar.nim b/tests/js/tbyvar.nim
index 9714cd56b..40aebd13b 100644
--- a/tests/js/tbyvar.nim
+++ b/tests/js/tbyvar.nim
@@ -41,3 +41,11 @@ proc bar(s: var seq[int], a: int) =
foo(s)
s.bar(5)
doAssert(s == @[123, 1])
+
+import tables
+block: # Test get addr of byvar return value
+ var t = initTable[string, int]()
+ t["hi"] = 5
+ let a = addr t["hi"]
+ a[] = 10
+ doAssert(t["hi"] == 10)
diff --git a/tests/js/tunittests.nim b/tests/js/tunittests.nim
index 4b09c99a9..7c2e70563 100644
--- a/tests/js/tunittests.nim
+++ b/tests/js/tunittests.nim
@@ -1,5 +1,7 @@
discard """
- output: '''[OK] >:)'''
+ output: '''
+[Suite] Bacon
+ [OK] >:)'''
"""
import unittest
diff --git a/tests/macros/tgettype2.nim b/tests/macros/tgettype2.nim
new file mode 100644
index 000000000..f129e6e1b
--- /dev/null
+++ b/tests/macros/tgettype2.nim
@@ -0,0 +1,67 @@
+
+import macros, typetraits
+
+type Foo = distinct int
+type Bar = distinct int
+type Baz = int
+
+let foo = 0.Foo
+let bar = 1.Bar
+let baz = 2.Baz
+
+type MyType[T] = distinct tuple[a,b:T]
+type MySimpleType = distinct tuple[a,b: int]
+
+var v: seq[int]
+var vv: seq[float]
+var t: MyType[int]
+var tt: MyType[float]
+var s: MySimpleType
+
+echo "############"
+echo "#### gt ####"
+echo "############"
+
+macro gt(a: typed): string =
+ let b = a.getType
+ var str = "gt(" & $a & "):\t" & b.repr
+ if b.kind == nnkSym: # bad predicat to check weather the type has an implementation
+ str = str & ", " & b.getType.repr # append the implementation to the result
+ result = newLit(str)
+
+echo gt(Foo) # typeDesc[Foo]
+echo gt(Bar) # typeDesc[Bar]
+echo gt(Baz) # typeDesc[int] shouldn't it be typeDesc[Baz]?
+echo gt(foo) # distinct[int] I would prefer Foo, distinct[int]
+echo gt(bar) # distinct[int] I would prefer Bar, distinct[int]
+echo gt(baz) # int, int I would prefer Baz, int
+
+echo gt(v) # seq[int], ok
+echo gt(vv) # seq[float], ok
+echo gt(t) # MyType, distinct[tuple[int, int]] I would prefer MyType[int], distinct[tuple[int, int]]
+echo gt(tt) # MyType, distinct[tuple[float, float]] I would prefer MyType[float], distinct[tuple[int, int]]
+echo gt(s) # distinct[tuple[int, int]] I would prefer MySimpleType, distinct[tuple[int,int]]
+
+echo "#############"
+echo "#### gt2 ####"
+echo "#############"
+
+# get type name via typetraits
+
+macro gt2(a: typed): string =
+ let prefix = "gt2(" & $a & "): \t"
+ result = quote do:
+ `prefix` & `a`.type.name
+
+echo gt2(Foo) # Foo shouldn't this be typeDesc[Foo] ?
+echo gt2(Bar) # Bar shouldn't this be typeDesc[Bar] ?
+echo gt2(Baz) # Baz shouldn't this be typeDesc[Baz] ?
+echo gt2(foo) # Foo
+echo gt2(bar) # Bar
+echo gt2(baz) # Baz
+
+echo gt2(v) # seq[int]
+echo gt2(vv) # seq[float]
+echo gt2(t) # MyType[system.int] why is it system.int and not just int like in seq?
+echo gt2(tt) # MyType[system.float] why is it system.float and not just float like in seq?
+echo gt2(s) # MySimpleType
diff --git a/tests/manyloc/keineschweine/README.md b/tests/manyloc/keineschweine/README.md
index 1323f4ae3..20facbced 100644
--- a/tests/manyloc/keineschweine/README.md
+++ b/tests/manyloc/keineschweine/README.md
@@ -11,7 +11,7 @@ Just a dumb little game
### How to build?
-* `git clone --recursive git://github.com/fowlmouth/keineSchweine.git somedir`
+* `git clone --recursive https://github.com/fowlmouth/keineSchweine.git somedir`
* `cd somedir`
* `nim c -r nakefile test` or `nim c -r keineschweine && ./keineschweine`
diff --git a/tests/metatype/twildtypedesc.nim b/tests/metatype/twildtypedesc.nim
new file mode 100644
index 000000000..268bff0d8
--- /dev/null
+++ b/tests/metatype/twildtypedesc.nim
@@ -0,0 +1,43 @@
+discard """
+ output: '''123
+123
+123
+123
+123
+123'''
+"""
+
+import strutils
+
+proc unpack(t: typedesc[string], v: string): string = $v
+proc unpack(t: typedesc[int], v: string): int = parseInt(v)
+
+proc unpack[T](v: string): T =
+ unpack T, v
+
+var s = "123"
+
+assert(unpack[string](s) is string)
+assert(unpack[int](s) is int)
+
+echo unpack[int](s)
+echo unpack[string](s)
+
+echo unpack(int,s)
+echo unpack(string,s)
+
+template `as`*(x: untyped, t: typedesc): untyped = unpack(t,x)
+
+echo s as int
+echo s as string
+
+# bug #4534
+
+proc unit(t: typedesc[int]): t = 0
+proc unit(t: typedesc[string]): t = ""
+proc unit(t: typedesc[float]): t = 0.0
+
+assert unit(int) == 0
+assert unit(string) == ""
+assert unit(float) == 0.0
+
diff --git a/tests/objects/tinherit_from_generic.nim b/tests/objects/tinherit_from_generic.nim
new file mode 100644
index 000000000..6e0e929ce
--- /dev/null
+++ b/tests/objects/tinherit_from_generic.nim
@@ -0,0 +1,13 @@
+discard """
+ output: '''true'''
+"""
+
+# bug #4673
+type
+ BaseObj[T] = ref object of RootObj
+ SomeObj = ref object of BaseObj[int]
+
+proc doSomething[T](o: BaseObj[T]) =
+ echo "true"
+var o = new(SomeObj)
+o.doSomething() # Error: cannot instantiate: 'T'
diff --git a/tests/osproc/tafalse.nim b/tests/osproc/tafalse.nim
new file mode 100644
index 000000000..24fd4fb2e
--- /dev/null
+++ b/tests/osproc/tafalse.nim
@@ -0,0 +1,3 @@
+# 'tafalse.nim' to ensure it is compiled before texitcode.nim
+import system
+quit(QuitFailure)
diff --git a/tests/osproc/texitcode.nim b/tests/osproc/texitcode.nim
new file mode 100644
index 000000000..1e83658c2
--- /dev/null
+++ b/tests/osproc/texitcode.nim
@@ -0,0 +1,18 @@
+discard """
+ file: "texitcode.nim"
+ output: ""
+"""
+import osproc, os
+
+const filename = when defined(Windows): "tafalse.exe" else: "tafalse"
+let dir = getCurrentDir() / "tests" / "osproc"
+doAssert fileExists(dir / filename)
+
+var p = startProcess(filename, dir)
+doAssert(waitForExit(p) == QuitFailure)
+
+p = startProcess(filename, dir)
+var running = true
+while running:
+ running = running(p)
+doAssert(waitForExit(p) == QuitFailure)
diff --git a/tests/overload/importA.nim b/tests/overload/importA.nim
new file mode 100644
index 000000000..f045d11b4
--- /dev/null
+++ b/tests/overload/importA.nim
@@ -0,0 +1,5 @@
+type
+ Field* = object
+ elemSize*: int
+
+template `+`*(x: untyped, y: Field): untyped = x
diff --git a/tests/overload/importB.nim b/tests/overload/importB.nim
new file mode 100644
index 000000000..2dc3adf7a
--- /dev/null
+++ b/tests/overload/importB.nim
@@ -0,0 +1,15 @@
+type
+ Foo*[T] = object
+ v*: T
+
+template `+`*(x: Foo, y: Foo): untyped = x
+
+template newvar*(r: untyped): untyped {.dirty.} =
+ var r: float
+
+template t1*(x: Foo): untyped =
+ newvar(y1)
+ x
+template t2*(x: Foo): untyped =
+ newvar(y2)
+ x
diff --git a/tests/overload/timport.nim b/tests/overload/timport.nim
new file mode 100644
index 000000000..8ea65e54d
--- /dev/null
+++ b/tests/overload/timport.nim
@@ -0,0 +1,7 @@
+# issue 4675
+import importA # comment this out to make it work
+import importB
+
+var x: Foo[float]
+var y: Foo[float]
+let r = t1(x) + t2(y)
diff --git a/tests/overload/tselfderef.nim b/tests/overload/tselfderef.nim
new file mode 100644
index 000000000..708e4043b
--- /dev/null
+++ b/tests/overload/tselfderef.nim
@@ -0,0 +1,17 @@
+# bug #4671
+{.experimental.}
+{.this: self.}
+
+type
+ SomeObj = object
+ f: int
+
+proc f(num: int) =
+ discard
+
+var intptr: ptr int
+intptr.f() # compiles fine
+
+proc doSomething(self: var SomeObj) =
+ var pint: ptr int
+ pint.f() # Error: expression '.(pint, "f")' cannot be called
diff --git a/tests/parallel/tsendtwice.nim b/tests/parallel/tsendtwice.nim
new file mode 100644
index 000000000..0700fc4da
--- /dev/null
+++ b/tests/parallel/tsendtwice.nim
@@ -0,0 +1,71 @@
+discard """
+ output: '''obj2 nil
+obj nil
+obj3 nil
+3
+obj2 nil
+obj nil
+obj3 nil'''
+ cmd: "nim c -r --threads:on $file"
+"""
+
+# bug #4776
+
+import tables
+
+type
+ Base* = ref object of RootObj
+ someSeq: seq[int]
+ baseData: array[400000, byte]
+ Derived* = ref object of Base
+ data: array[400000, byte]
+
+type
+ ThreadPool = ref object
+ threads: seq[ptr Thread[ThreadArg]]
+ channels: seq[ThreadArg]
+ TableChannel = Channel[TableRef[string, Base]]
+ ThreadArg = ptr TableChannel
+
+var globalTable {.threadvar.}: TableRef[string, Base]
+globalTable = newTable[string, Base]()
+let d = new(Derived)
+globalTable.add("obj", d)
+globalTable.add("obj2", d)
+globalTable.add("obj3", d)
+
+proc testThread(channel: ptr TableChannel) {.thread.} =
+ globalTable = channel[].recv()
+ for k, v in pairs globaltable:
+ echo k, " ", v.someSeq
+ var myObj: Base
+ deepCopy(myObj, globalTable["obj"])
+ myObj.someSeq = newSeq[int](100)
+ let table = channel[].recv() # same table
+ echo table.len
+ for k, v in mpairs table:
+ echo k, " ", v.someSeq
+ assert(table.contains("obj")) # fails!
+ assert(table.contains("obj2")) # fails!
+ assert(table.contains("obj3")) # fails!
+
+var channel: TableChannel
+
+proc newThreadPool(threadCount: int) = #: ThreadPool =
+ #new(result)
+ #result.threads = newSeq[ptr Thread[ThreadArg]](threadCount)
+ #var channel = cast[ptr TableChannel](allocShared0(sizeof(TableChannel)))
+ channel.open()
+ channel.send(globalTable)
+ channel.send(globalTable)
+ #createThread(threadPtr[], testThread, addr channel)
+ testThread(addr channel)
+ #result.threads[i] = threadPtr
+
+proc stop(p: ThreadPool) =
+ for t in p.threads:
+ joinThread(t[])
+ dealloc(t)
+
+
+newThreadPool(1)#.stop()
diff --git a/tests/rodfiles/amethods.nim b/tests/rodfiles/amethods.nim
index ecd36d491..29cf757f7 100644
--- a/tests/rodfiles/amethods.nim
+++ b/tests/rodfiles/amethods.nim
@@ -1,11 +1,11 @@
type
- TBaseClass* = object of TObject
+ TBaseClass* = object of RootObj
proc newBaseClass*: ref TBaseClass =
new result
-method echoType*(x: ref TBaseClass) =
+method echoType*(x: ref TBaseClass) {.base.} =
echo "base class"
proc echoAlias*(x: ref TBaseClass) =
diff --git a/tests/rodfiles/gtkex1.nim b/tests/rodfiles/gtkex1.nim
index 156ba5322..50779cb9e 100644
--- a/tests/rodfiles/gtkex1.nim
+++ b/tests/rodfiles/gtkex1.nim
@@ -1,12 +1,12 @@
import
cairo, glib2, gtk2
-proc destroy(widget: pWidget, data: pgpointer) {.cdecl.} =
+proc destroy(widget: PWidget, data: Pgpointer) {.cdecl.} =
main_quit()
var
- window: pWidget
-nimrod_init()
+ window: PWidget
+nim_init()
window = window_new(WINDOW_TOPLEVEL)
discard signal_connect(window, "destroy",
SIGNAL_FUNC(gtkex1.destroy), nil)
diff --git a/tests/rodfiles/gtkex2.nim b/tests/rodfiles/gtkex2.nim
index 70926bd50..0949e4872 100644
--- a/tests/rodfiles/gtkex2.nim
+++ b/tests/rodfiles/gtkex2.nim
@@ -2,17 +2,17 @@
import
glib2, gtk2
-proc destroy(widget: pWidget, data: pgpointer){.cdecl.} =
+proc destroy(widget: PWidget, data: Pgpointer){.cdecl.} =
main_quit()
var
window: PWidget
button: PWidget
-nimrod_init()
+nim_init()
window = window_new(WINDOW_TOPLEVEL)
button = button_new("Click me")
-set_border_width(PContainer(Window), 5)
+set_border_width(PContainer(window), 5)
add(PContainer(window), button)
discard signal_connect(window, "destroy",
SIGNAL_FUNC(gtkex2.destroy), nil)
diff --git a/tests/rodfiles/int2bool.nim b/tests/rodfiles/int2bool.nim
index 0f6fd14e6..bb0682844 100644
--- a/tests/rodfiles/int2bool.nim
+++ b/tests/rodfiles/int2bool.nim
@@ -2,7 +2,6 @@
{.overflowchecks: on.}
converter uglyToBool*(x: int): bool =
- {.Breakpoint.}
result = x != 0
diff --git a/tests/stdlib/thttpclient.nim b/tests/stdlib/thttpclient.nim
new file mode 100644
index 000000000..dd9a6139a
--- /dev/null
+++ b/tests/stdlib/thttpclient.nim
@@ -0,0 +1,113 @@
+discard """
+ cmd: "nim c -d:ssl $file"
+"""
+
+import strutils
+from net import TimeoutError
+
+import httpclient, asyncdispatch
+
+const manualTests = false
+
+proc asyncTest() {.async.} =
+ var client = newAsyncHttpClient()
+ var resp = await client.request("http://example.com/")
+ doAssert(resp.code.is2xx)
+ doAssert("Example Domain" in resp.body)
+
+ resp = await client.request("http://example.com/404")
+ doAssert(resp.code.is4xx)
+ doAssert(resp.code == Http404)
+ doAssert(resp.status == Http404)
+
+ resp = await client.request("https://google.com/")
+ doAssert(resp.code.is2xx or resp.code.is3xx)
+
+ # getContent
+ try:
+ discard await client.getContent("https://google.com/404")
+ doAssert(false, "HttpRequestError should have been raised")
+ except HttpRequestError:
+ discard
+ except:
+ doAssert(false, "HttpRequestError should have been raised")
+
+
+ # Multipart test.
+ var data = newMultipartData()
+ data["output"] = "soap12"
+ data["uploaded_file"] = ("test.html", "text/html",
+ "
This page lists the companies and individuals that are, very kindly, contributing a
-monthly amount to help sustain Nim's development. For more details take a
-look at the Bountysource campaign.
-
Last updated: ${getTime().getGMTime().format("dd/MM/yyyy")}
Last updated: ${getTime().getGMTime().format("dd/MM/yyyy")}
+
+${generateSponsors(activeSponsors)}
+
+#
+
Our Past Sponsors
+
This section lists the companies and individuals that have contributed
+money in the past to help sustain Nim's development. For more details take a
+look at the Bountysource campaign.
+
+${generateSponsors(inactiveSponsors)}
#
#end proc
diff --git a/web/assets/news/images/0.15.0/doc_search.gif b/web/assets/news/images/0.15.0/doc_search.gif
new file mode 100644
index 000000000..ac757b404
Binary files /dev/null and b/web/assets/news/images/0.15.0/doc_search.gif differ
diff --git a/web/assets/news/images/0.15.0/doc_sort.gif b/web/assets/news/images/0.15.0/doc_sort.gif
new file mode 100644
index 000000000..edd253c4a
Binary files /dev/null and b/web/assets/news/images/0.15.0/doc_sort.gif differ
diff --git a/web/assets/news/images/survey/10_needs.png b/web/assets/news/images/survey/10_needs.png
new file mode 100644
index 000000000..67d568552
Binary files /dev/null and b/web/assets/news/images/survey/10_needs.png differ
diff --git a/web/assets/news/images/survey/book.png b/web/assets/news/images/survey/book.png
new file mode 100644
index 000000000..5bb418e63
Binary files /dev/null and b/web/assets/news/images/survey/book.png differ
diff --git a/web/assets/news/images/survey/book_opinion.png b/web/assets/news/images/survey/book_opinion.png
new file mode 100644
index 000000000..4e56ab26e
Binary files /dev/null and b/web/assets/news/images/survey/book_opinion.png differ
diff --git a/web/assets/news/images/survey/breakage.png b/web/assets/news/images/survey/breakage.png
new file mode 100644
index 000000000..5eb4c5289
Binary files /dev/null and b/web/assets/news/images/survey/breakage.png differ
diff --git a/web/assets/news/images/survey/difficulty_fixing_breakage.png b/web/assets/news/images/survey/difficulty_fixing_breakage.png
new file mode 100644
index 000000000..022aa00ed
Binary files /dev/null and b/web/assets/news/images/survey/difficulty_fixing_breakage.png differ
diff --git a/web/assets/news/images/survey/domains.png b/web/assets/news/images/survey/domains.png
new file mode 100644
index 000000000..50b1ed7ff
Binary files /dev/null and b/web/assets/news/images/survey/domains.png differ
diff --git a/web/assets/news/images/survey/ex_nim.png b/web/assets/news/images/survey/ex_nim.png
new file mode 100644
index 000000000..50082ea8b
Binary files /dev/null and b/web/assets/news/images/survey/ex_nim.png differ
diff --git a/web/assets/news/images/survey/languages.png b/web/assets/news/images/survey/languages.png
new file mode 100644
index 000000000..db35f9bd4
Binary files /dev/null and b/web/assets/news/images/survey/languages.png differ
diff --git a/web/assets/news/images/survey/learning_resources.png b/web/assets/news/images/survey/learning_resources.png
new file mode 100644
index 000000000..39f533ad0
Binary files /dev/null and b/web/assets/news/images/survey/learning_resources.png differ
diff --git a/web/assets/news/images/survey/nim_appeal.png b/web/assets/news/images/survey/nim_appeal.png
new file mode 100644
index 000000000..4f53e1447
Binary files /dev/null and b/web/assets/news/images/survey/nim_appeal.png differ
diff --git a/web/assets/news/images/survey/nim_displeasing.png b/web/assets/news/images/survey/nim_displeasing.png
new file mode 100644
index 000000000..b7232df04
Binary files /dev/null and b/web/assets/news/images/survey/nim_displeasing.png differ
diff --git a/web/assets/news/images/survey/nim_domains.png b/web/assets/news/images/survey/nim_domains.png
new file mode 100644
index 000000000..2d8fc6652
Binary files /dev/null and b/web/assets/news/images/survey/nim_domains.png differ
diff --git a/web/assets/news/images/survey/nimble_opinion.png b/web/assets/news/images/survey/nimble_opinion.png
new file mode 100644
index 000000000..3fe76326e
Binary files /dev/null and b/web/assets/news/images/survey/nimble_opinion.png differ
diff --git a/web/assets/news/images/survey/non_user.png b/web/assets/news/images/survey/non_user.png
new file mode 100644
index 000000000..b5324b69c
Binary files /dev/null and b/web/assets/news/images/survey/non_user.png differ
diff --git a/web/assets/news/images/survey/planning_to_use_at_work.png b/web/assets/news/images/survey/planning_to_use_at_work.png
index be3a50467..b3e2001c3 100644
Binary files a/web/assets/news/images/survey/planning_to_use_at_work.png and b/web/assets/news/images/survey/planning_to_use_at_work.png differ
diff --git a/web/assets/news/images/survey/project_size_nim_rust.png b/web/assets/news/images/survey/project_size_nim_rust.png
index 41e3ec8b1..4bc0e6b47 100644
Binary files a/web/assets/news/images/survey/project_size_nim_rust.png and b/web/assets/news/images/survey/project_size_nim_rust.png differ
diff --git a/web/assets/news/images/survey/upgrades_broke_things.png b/web/assets/news/images/survey/upgrades_broke_things.png
deleted file mode 100644
index 28a8ee3f0..000000000
Binary files a/web/assets/news/images/survey/upgrades_broke_things.png and /dev/null differ
diff --git a/web/assets/style.css b/web/assets/style.css
index af32fad07..2e166530d 100644
--- a/web/assets/style.css
+++ b/web/assets/style.css
@@ -516,12 +516,11 @@ pre .end { background:url("images/tabEnd.png") no-repeat left bottom; }
padding:5px 30px;
margin-bottom:20px;
border:8px solid rgba(0,0,0,.8);
- border-right-width:16px;
+ border-right-width:0;
border-top-width:0;
border-bottom-width:0;
border-radius:3px;
- background:rgba(0,0,0,0.1);
- box-shadow:1px 3px 12px rgba(0,0,0,.4); }
+ background:rgba(0,0,0,0.1); }
.standout h2 { margin-bottom:10px; padding-bottom:10px; border-bottom:1px dashed rgba(0,0,0,.8); }
.standout li { margin:0 !important; padding-top:10px; border-top:1px dashed rgba(0,0,0,.2); }
.standout ul { padding-bottom:5px; }
@@ -611,6 +610,15 @@ p.lastUpdate {
color: #6d6d6d;
}
+/* quotes */
+
+blockquote {
+ padding: 0px 8px;
+ margin: 10px 0px;
+ border-left: 2px solid rgb(61, 61, 61);
+ color: rgb(109, 109, 109);
+}
+
/* News articles */
.metadata {
diff --git a/web/bountysource.nim b/web/bountysource.nim
index a4f76417e..1d47cea56 100644
--- a/web/bountysource.nim
+++ b/web/bountysource.nim
@@ -29,12 +29,6 @@ proc newBountySource(team, token: string): BountySource =
result.client.headers["Referer"] = "https://salt.bountysource.com/teams/nim/admin/supporters"
result.client.headers["Origin"] = "https://salt.bountysource.com/"
-proc getSupportLevels(self: BountySource): Future[JsonNode] {.async.} =
- let response = await self.client.get(apiUrl &
- "/support_levels?supporters_for_team=" & self.team)
- doAssert response.status.startsWith($Http200) # TODO: There should be a ==
- return parseJson(response.body)
-
proc getSupporters(self: BountySource): Future[JsonNode] {.async.} =
let response = await self.client.get(apiUrl &
"/supporters?order=monthly&per_page=200&team_slug=" & self.team)
@@ -47,26 +41,21 @@ proc getGithubUser(username: string): Future[JsonNode] {.async.} =
if response.status.startsWith($Http200):
return parseJson(response.body)
else:
+ echo("Could not get Github user: ", username, ". ", response.status)
return nil
-proc processLevels(supportLevels: JsonNode) =
- var before = supportLevels.elems.len
- supportLevels.elems.keepIf(
- item => item["status"].getStr == "active" and
- item["owner"]["display_name"].getStr != "Anonymous"
+proc processSupporters(supporters: JsonNode) =
+ var before = supporters.elems.len
+ supporters.elems.keepIf(
+ item => item["display_name"].getStr != "Anonymous"
)
- echo("Found ", before - supportLevels.elems.len, " sponsors that cancelled or didn't pay.")
- echo("Found ", supportLevels.elems.len, " active sponsors!")
+ echo("Discarded ", before - supporters.elems.len, " anonymous sponsors.")
+ echo("Found ", supporters.elems.len, " named sponsors.")
- supportLevels.elems.sort(
- (x, y) => cmp(y["amount"].getFNum, x["amount"].getFNum)
+ supporters.elems.sort(
+ (x, y) => cmp(y["alltime_amount"].getFNum, x["alltime_amount"].getFNum)
)
-proc getSupporter(supporters: JsonNode, displayName: string): JsonNode =
- for supporter in supporters:
- if supporter["display_name"].getStr == displayName:
- return supporter
- doAssert false
proc quote(text: string): string =
if {' ', ','} in text:
@@ -81,7 +70,7 @@ proc getLevel(amount: float): int =
if amount.int <= i:
result = i
-proc writeCsv(sponsors: seq[Sponsor]) =
+proc writeCsv(sponsors: seq[Sponsor], filename="sponsors.new.csv") =
var csv = ""
csv.add "logo, name, url, this_month, all_time, since, level\n"
for sponsor in sponsors:
@@ -91,7 +80,8 @@ proc writeCsv(sponsors: seq[Sponsor]) =
$sponsor.allTime.int, sponsor.since.format("MMM d, yyyy").quote,
$sponsor.amount.getLevel
]
- writeFile("sponsors.new.csv", csv)
+ writeFile(filename, csv)
+ echo("Written csv file to ", filename)
when isMainModule:
if paramCount() == 0:
@@ -105,14 +95,13 @@ when isMainModule:
echo("Getting sponsors...")
let supporters = waitFor bountysource.getSupporters()
-
- var supportLevels = waitFor bountysource.getSupportLevels()
- processLevels(supportLevels)
+ processSupporters(supporters)
echo("Generating sponsors list... (please be patient)")
- var sponsors: seq[Sponsor] = @[]
- for supportLevel in supportLevels:
- let name = supportLevel["owner"]["display_name"].getStr
+ var activeSponsors: seq[Sponsor] = @[]
+ var inactiveSponsors: seq[Sponsor] = @[]
+ for supporter in supporters:
+ let name = supporter["display_name"].getStr
var url = ""
let ghUser = waitFor getGithubUser(name)
if not ghUser.isNil:
@@ -124,21 +113,28 @@ when isMainModule:
if url.len > 0 and not url.startsWith("http"):
url = "http://" & url
- let amount = supportLevel["amount"].getFNum
+ let amount = supporter["monthly_amount"].getFNum()
# Only show URL when user donated at least $5.
if amount < 5:
url = ""
- let supporter = getSupporter(supporters, supportLevel["owner"]["display_name"].getStr)
+ #let supporter = getSupporter(supporters,
+ # supportLevel["owner"]["display_name"].getStr)
+ #if supporter.isNil: continue
var logo = ""
if amount >= 75:
discard # TODO
- sponsors.add(Sponsor(name: name, url: url, logo: logo, amount: amount,
- allTime: supporter["alltime_amount"].getFNum(),
- since: parse(supporter["created_at"].getStr, "yyyy-MM-dd'T'hh:mm:ss")
- ))
+ let sponsor = Sponsor(name: name, url: url, logo: logo, amount: amount,
+ allTime: supporter["alltime_amount"].getFNum(),
+ since: parse(supporter["created_at"].getStr, "yyyy-MM-dd'T'hh:mm:ss")
+ )
+ if supporter["monthly_amount"].getFNum > 0.0:
+ activeSponsors.add(sponsor)
+ else:
+ inactiveSponsors.add(sponsor)
- echo("Generated ", sponsors.len, " sponsors")
- writeCsv(sponsors)
- echo("Written csv file to sponsors.new.csv")
+ echo("Generated ", activeSponsors.len, " active sponsors")
+ echo("Generated ", inactiveSponsors.len, " inactive sponsors")
+ writeCsv(activeSponsors)
+ writeCsv(inactiveSponsors, "inactive_sponsors.new.csv")
diff --git a/web/download.rst b/web/download.rst
index 6593a928c..cf0841577 100644
--- a/web/download.rst
+++ b/web/download.rst
@@ -3,22 +3,17 @@ Download the compiler
You can download the latest version of the Nim compiler here.
-**Note:** The Nim compiler requires a C compiler to compile software. On
-Windows we recommend that you use
-`Mingw-w64 `_. GCC is recommended on Linux
-and Clang on Mac.
-
-
Binaries
--------
-Unfortunately, right now we only provide binaries for Windows. You can download
-an installer for both 32 bit and 64 bit versions of Windows below.
+Right now binaries are only provided for Windows. You can download
+an installer for both 32 bit and 64 bit versions of Windows below. These
+installers have everything you need to use Nim, including a C compiler.
-* | 32 bit: `nim-0.14.2_x32.exe `_
- | SHA-256 ca2de37759006d95db98732083a6fab20151bb9819186af2fa29d41884df78c9
-* | 64 bit: `nim-0.14.2_x64.exe `_
- | SHA-256 1fec054d3a5f54c0a67a40db615bb9ecb1d56413b19e324244110713bd4337d1
+* | 32 bit: `nim-0.15.0_x32.exe `_
+ | SHA-256 0ca8931e3369735bbafdf93de98a8fd0f425870f1173845e7601922a5e00c3c2
+* | 64 bit: `nim-0.15.0_x64.exe `_
+ | SHA-256 7bb9321cd9fb2860d36ee9b248e0202d7d4e36e2272a2f128fbce96fd4a9bfd6
These installers also include Aporia, Nimble and other useful Nim tools to get
you started with Nim development!
@@ -26,14 +21,18 @@ you started with Nim development!
Installation based on generated C code
--------------------------------------
-This installation method is the preferred way for Linux, Mac OS X, and other Unix
-like systems. Binary packages may be provided later.
+**Note:** The Nim compiler requires a C compiler to compile software. On
+Windows we recommend that you use
+`Mingw-w64 `_. GCC is recommended on Linux
+and Clang on Mac. The Windows installers above already includes a C compiler.
+This installation method is the preferred way for Linux, Mac OS X, and other Unix
+like systems.
Firstly, download this archive:
-* | `nim-0.14.2.tar.xz (4.5MB) `_
- | SHA-256 8f8d38d70ed57164795fc55e19de4c11488fcd31dbe42094e44a92a23e3f5e92
+* | `nim-0.15.0.tar.xz (4.5MB) `_
+ | SHA-256 c514535050b2b2156147bbe6e23aafe07cd996b2afa2c81fa9a09e1cd8c669fb
Extract the archive. Then copy the extracted files into your chosen installation
directory, ideally somewhere in your home directory.
@@ -45,6 +44,7 @@ Now open a terminal and follow these instructions:
``cd ~/programs/nim``.
* run ``sh build.sh``.
* Add ``$your_install_dir/bin`` to your PATH.
+* To build associated tools like ``nimble`` and ``nimsuggest`` run ``nim e install_tools.nims``.
After restarting your terminal, you should be able to run ``nim -v``
which should show you the version of Nim you just installed.
@@ -70,9 +70,9 @@ directory where you would like the download to take place.
The following commands can be used to download the current development branch
and then to build it::
- git clone git://github.com/nim-lang/Nim.git
+ git clone https://github.com/nim-lang/Nim.git
cd Nim
- git clone --depth 1 git://github.com/nim-lang/csources
+ git clone --depth 1 https://github.com/nim-lang/csources
cd csources && sh build.sh
cd ..
bin/nim c koch
@@ -86,7 +86,7 @@ Docker Hub
----------
The `official Docker images `_
-are published Docker Hub and include the compiler and Nimble. There are images
+are published on Docker Hub and include the compiler and Nimble. There are images
for standalone scripts as well as Nimble packages.
Get the latest stable image::
diff --git a/web/inactive_sponsors.csv b/web/inactive_sponsors.csv
new file mode 100644
index 000000000..d466f3f31
--- /dev/null
+++ b/web/inactive_sponsors.csv
@@ -0,0 +1,50 @@
+logo, name, url, this_month, all_time, since, level
+,bogen,,0,1010,"Jul 23, 2016",1
+,mikra,,0,400,"Apr 28, 2016",1
+,linkmonitor,,0,180,"Jan 28, 2016",1
+,avsej,,0,110,"Jun 10, 2016",1
+,WilRubin,,0,100,"Aug 11, 2015",1
+,"Benny Luypaert",,0,100,"Apr 10, 2016",1
+,"Chris Heller",,0,100,"May 19, 2016",1
+,PhilipWitte,,0,100,"Aug 5, 2016",1
+,Boxifier,,0,75,"Apr 12, 2016",1
+,iolloyd,,0,75,"Apr 29, 2016",1
+,rb01,,0,50,"May 4, 2016",1
+,TedSinger,,0,45,"Apr 9, 2016",1
+,martinbbjerregaard,,0,35,"Jun 9, 2016",1
+,RationalG,,0,30,"Jun 17, 2016",1
+,benbve,,0,30,"Jul 12, 2016",1
+,barcharcraz,,0,25,"Jun 2, 2016",1
+,"Landon Bass",,0,25,"Jun 7, 2016",1
+,jimrichards,,0,25,"Jun 8, 2016",1
+,jjzazuet,,0,25,"Jul 10, 2016",1
+,moigagoo,,0,20,"May 13, 2016",1
+,kteza1,,0,20,"Jun 10, 2016",1
+,tomkeus,,0,20,"Sep 4, 2016",1
+,mirek,,0,15,"Apr 9, 2016",1
+,DateinAsia,,0,15,"Jul 30, 2016",1
+,rickc,,0,15,"Jul 31, 2016",1
+,jpkx1984,,0,13,"Jul 11, 2016",1
+,vlkrav,,0,12,"Aug 9, 2015",1
+,tebanep,,0,12,"Aug 7, 2016",1
+,McSpiros,,0,10,"Apr 6, 2016",1
+,"Brandon Hunter",,0,10,"Apr 7, 2016",1
+,funny-falcon,,0,10,"Apr 7, 2016",1
+,teroz,,0,10,"Apr 8, 2016",1
+,iLikeLego,,0,10,"Apr 16, 2016",1
+,Angluca,,0,10,"May 3, 2016",1
+,calind,,0,10,"Jun 7, 2016",1
+,goldenreign,,0,10,"Jun 10, 2016",1
+,Blumenversand,,0,10,"Jul 21, 2016",1
+,cinnabardk,,0,10,"Aug 6, 2016",1
+,reddec,,0,10,"Aug 31, 2016",1
+,niv,,0,5,"Apr 6, 2016",1
+,goniz,,0,5,"Apr 7, 2016",1
+,genunix,,0,5,"Apr 12, 2016",1
+,CynepHy6,,0,5,"Apr 14, 2016",1
+,ivanflorentin,,0,5,"May 3, 2016",1
+,stevenyhw,,0,5,"May 20, 2016",1
+,"Sanjay Singh",,0,5,"Jun 6, 2016",1
+,yuttie,,0,5,"Jun 7, 2016",1
+,hron,,0,5,"Jun 11, 2016",1
+,laszlowaty,,0,5,"Jun 17, 2016",1
diff --git a/web/news.rst b/web/news.rst
index 95822a459..f819c384c 100644
--- a/web/news.rst
+++ b/web/news.rst
@@ -2,6 +2,12 @@
News
====
+`2016-09-30 Nim Version 0.15.0 released `_
+===================================
+
+`2016-09-03 Nim Community Survey results `_
+===================================
+
`2016-08-06 BountySource Update: The Road to v1.0 `_
===================================
diff --git a/web/news/2016_09_03_nim_community_survey_results.rst b/web/news/2016_09_03_nim_community_survey_results.rst
new file mode 100644
index 000000000..106dce0e4
--- /dev/null
+++ b/web/news/2016_09_03_nim_community_survey_results.rst
@@ -0,0 +1,699 @@
+Nim Community Survey Results
+============================
+
+.. container:: metadata
+
+ Posted by Dominik Picheta on 3 September 2016
+
+We have recently closed the 2016 Nim Community Survey. I am happy to
+say that we have received exactly 790 responses, huge thanks go to the people
+that took the time to respond. We're incredibly thankful for this very valuable
+feedback.
+
+This survey was inspired in part by the
+`2016 State of Rust `_
+survey. You will note that many of the questions were modelled after
+Rust's survey. One of the reasons for doing this was to allow us to easily
+compare our results against the results obtained in the Rust survey. In
+addition, we of course also liked many of their questions.
+
+Our survey ran from the 23rd of June 2016 until the 8th of August 2016. The
+response numbers are impressive considering Nim's community size; at 790 they
+make up just over 25% of the Rust survey's responses.
+
+The goal of this survey was to primarily determine how our community is using
+Nim, in order to better understand how we should be improving it. In particular,
+we wanted to know what people feel is missing from Nim in the lead up to
+version 1.0. We have also asked our respondents about how well the Nim tools
+worked, the challenges of adopting Nim, the resources that they used to learn
+Nim and more.
+
+It is my hope that we will be able to run a similar survey in a years time,
+doing so should give us an idea of whether we are improving.
+With these general facts in mind, let's begin looking at specific questions.
+
+How did you find out about Nim?
+-------------------------------
+
+The rationale for the first question was simple, we wanted to know where our
+respondents found out about Nim. This is an interesting question for us, as
+we do occassionally get users asking us why it took so long for them to hear
+about Nim. It allows us to see how effective each website is at spreading the
+word about Nim.
+
+.. raw::html
+
+
+
+
+
+The majority of our respondents found Nim via Reddit, HackerNews or a search
+engine such as Google. These results are not altogether surprising. There were
+also a lot of "Other" responses, some of which were a bit more
+interesting. These included multiple mentions of habrahabr.ru, Dr. Dobb's,
+and lobste.rs.
+
+Do you use Nim?
+---------------
+
+Just like the Rust survey creators, we wanted to ensure that our survey was
+open to both Nim users as well people who never used Nim. In addition to
+those two groups, we have also included a third group of people: ex-Nim
+users. All three are interesting, for many different reasons.
+Nim users can tell us how they are using Nim and also how Nim's
+tooling can improve. Ex-Nim users give us an
+idea of why they stopped using Nim. Finally, respondents who never used Nim
+can tell us the reasons for not adopting it.
+
+.. raw::html
+
+
+
+
+
+It's nice to see that we have such a good range of respondents. The Rust survey
+had a much larger number of Rust users amongst their respondents, with
+no distinction between users that never used Rust and users that stopped using
+Rust.
+
+Should we consider your answers to be invalid?
+----------------------------------------------
+
+This was something I thought would be interesting to have, after I saw it
+being used in another survey. While it does pinpoint possibly
+invalid respondents, I have opted against filtering those out. Mainly because
+that would require re-creating each of the charts generated by Google Forms
+manually.
+
+.. raw::html
+
+
+
+
+
+According to the responses to this question, around 94% of our responses
+can be considered reliable.
+
+Nim users
+---------
+
+The following questions were answered only by the 38.9% of our respondents
+who identified themselves as Nim users.
+
+How long have you been using Nim?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. raw::html
+
+
+
+
+
+A large proportion of our Nim users were new. This is good news as it means that
+our community is growing, with a large proportion of new Nim users that could
+become long-term Nimians. In total, more than 35% of Nim users can be considered
+new having used Nim for less than 3 months. With 18% of Nim users that can
+be considered very new having used Nim for less than a month.
+This could suggest that 18% of our users have only just found out about Nim in
+the last week or so and have not yet got the chance to use it extensively.
+
+The high percentages of long term Nim users are encouraging.
+They suggest
+that many users are continuing to use Nim after making it through the first
+few months. The sharp drop at 7-9 months is interesting, but may simply be
+due to the fact that there were fewer newcomers during that period, or it
+could be because our respondents are more likely to estimate that they have
+been using Nim for a year or half a year rather than the awkward 7-9 months.
+
+.. raw::html
+
+
+
+
+
+The results for Nim and Rust are actually remarkably similar. They both show a
+drop at 7-9 months, although Rust's isn't as dramatic. Nim on the other hand
+has a significantly higher percentage of new Nim users.
+
+Do you use Nim at work?
+~~~~~~~~~~~~~~~~~~~~~~~
+
+An important aspect of a language's adoption is whether it is being used for
+"real" work. We wanted to know how many people are using Nim in their day
+jobs and under what circumstances it is used.
+
+.. raw::html
+
+
+
+
+
+While a vast majority of our users are not using Nim at work, more than 25%
+of them are. It's encouraging to see such a high number already, even before
+we have released version 1.0. In fact, this percentage is likely close to 30%,
+because many of the "Other" responses mention using Nim for the likes of
+internal tools or small scripts to help with the respondent's work.
+
+.. raw::html
+
+
+
+
+
+Interestingly, a larger percentage of Nim users are using Nim at work than
+Rust users. The sample sizes are of course vastly different, but it's still an
+interesting result. Combined, nearly 1/5th of Rust users are using Rust
+commercially whereas more than a quarter of Nim users are using Nim
+commercially.
+
+Approximately how large are all the Nim projects that you work on?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Finding out how large the Nim projects worked on by Nim users are is also
+very valuable.
+
+.. raw::html
+
+
+
+
+
+This shows us that currently Nim is primarily being used for small scripts and
+applications, with nearly 60% of the projects consisting of less than 1,000
+lines of code. This makes sense as many of our users are not using Nim
+professionally, but are doing so in their spare time.
+
+.. raw::html
+
+
+
+
+
+The numbers for part-time and full-time work users of Nim tell a different
+story. Over 70% of the projects written by full-time users are between 10,001
+and 100,000 lines of code. Part-time users show a slightly different trend,
+with many more small projects, the majority being between 1,000 and
+10,000 lines of code.
+
+Overall it's good to see that there is a few large projects out there which are
+composed of more than 100,000 lines of code. We expect to see the amount of
+large projects to grow with time, especially with version 1.0 on the way.
+
+.. raw::html
+
+
+
+
+
+In comparison to Rust the proportion of project sizes for full-time users is
+vastly different. This is likely due to our small sample size. Project sizes for
+part-time users between Rust and Nim are somewhat similar, with differences of
+around 10% for each project size.
+
+Do you plan to try to use Nim at work?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. raw::html
+
+
+
+
+
+It's also encouraging to see that over 50% of Nim users are planning to use
+Nim at work! This is slightly more than Rust's 40% and should help Nim's
+adoption into even more areas.
+
+Nim and its tools
+~~~~~~~~~~~~~~~~~
+
+In this section of the survey, we wanted to find out the tools that Nim
+users are utilising when developing Nim applications.
+
+What editor(s) do you use when writing Nim?
+___________________________________________
+
+Programmers are very specific when it comes to their editor of choice, because
+of that it's good to know which editor is most popular among our community.
+
+.. raw::html
+
+
+
+
+
+Looks like Vim is the winner with almost 30%. Followed by Sublime Text and
+Emacs. Aporia, the Nim IDE, gets a respectable 15.5%. There was
+also more than
+17% of answers which included "Other" editors, such as: Notepad++, Geany, gedit,
+and Kate.
+
+What operating system(s) do you compile for and run your Nim projects on?
+_________________________________________________________________________
+
+This question gave us information about the most popular target operating
+systems, as well as some of the more obscure ones. We have asked this question
+to find out the platforms on which Nim applications run on most frequently.
+
+.. raw::html
+
+
+
+
+
+This question allowed multiple choices, so each percentage is out of the total
+number of respondents for this question. For example, 80.7% of the
+respondents selected "Linux" but only 26.6% selected OS X.
+
+This makes Linux by far the most popular target for Nim applications.
+Some "Other" targets included: BSD (OpenBSD, FreeBSD), iOS, Android, and
+JavaScript.
+It's great to see Nim being used on such a wide variety of platforms.
+
+What operating system(s) do you develop Nim projects on?
+________________________________________________________
+
+With this question, we wanted to know what operating systems are used for
+development.
+
+.. raw::html
+
+
+
+
+
+This question also allowed multiple choices and ended up with very similar
+results.
+
+You can see that Linux is also the most popular developmental
+platform for Nim. But it's more popular as a target platform.
+
+Which version(s) of Nim do you use for your applications?
+_________________________________________________________
+
+.. raw::html
+
+
+
+
+
+At the time of this survey, version 0.14.2 was the latest stable release.
+It's no wonder that it is the most commonly used release of Nim. It's good to
+see that the older versions are not used as often. The high use of ``Git HEAD (devel)``
+(nightly builds) isn't surprising, Nim is still evolving rapidly and our
+release schedule is not regular or frequent.
+
+Once we go past the 1.0 release, we expect to see much less use of the unstable
+``devel`` branch.
+
+Has upgrading to a new version of the Nim compiler broken your code?
+____________________________________________________________________
+
+.. raw::html
+
+
+
+
+
+Despite the unstable nature of Nim in the lead up to version 1.0, whenever
+we make breaking changes we do our best to deprecate things and ensure that
+old code continues to work for our users. Of course sometimes this is not
+possible and other times it is simply easier to add a breaking change.
+
+This question was asked to determine how much our user base is affected by
+breaking changes between Nim versions. We decided to have three possible
+answers for this question in order to give us an idea how frequent the
+breakage was.
+
+It's incredible to see that over 50% of our users have not experienced any
+breakage after upgrading. We expect this number to increase significantly
+after version 1.0 is released. Of the users that did experience breakage,
+over 80% of them said that it was a rare occurrence.
+
+In comparison to Rust, our results show that there was a higher percentage of
+users experiencing breakage as a result of an upgrade. This is to be expected,
+because Nim is still in its pre-1.0 period, whereas Rust 1.0 has been released
+over a year ago now.
+
+Unfortunately while we are still in this pre-1.0 period, releases will likely
+introduce breaking changes as we refine certain aspects of Nim such as its
+standard library, so the number of users experiencing breaking changes may
+increase.
+
+If so, how much work did it take to fix it?
+___________________________________________
+
+.. raw::html
+
+
+
+
+
+Thankfully most of the breakage experienced by Nim users was very easy to fix.
+
+
+If you used Nimble, do you like it?
+___________________________________
+
+.. raw::html
+
+
+
+
+
+Nimble is the Nim package manager, a tool that is very important in Nim's
+ecosystem as it allows developers to easily install dependencies for their
+software.
+
+The majority of respondents rated it as a 4, showing us that the majority does
+like Nimble. With over 55% rating it a 4 or 5. This percentage isn't as
+overwhelming as the 94.1% of users that rated Cargo a 4 or 5 in the Rust
+survey. Based on these results I think that we definitely need to do a
+better job with Nimble.
+
+In our next survey, it might be a good idea to ask more questions about Nimble
+to determine how exactly it can be improved.
+
+What aspects of Nim do you find most appealing?
+_______________________________________________
+
+.. raw::html
+
+
+
+
+
+We were interested to know the features of Nim that appeal most to our users.
+More than 80% of our respondents selected "Execution Speed" as one of the
+features that appeal to them. With "Development Speed" and "Readability"
+tying for second place and "Metaprogramming" at third place.
+
+The options given to our respondents are rather predictable,
+they do show us which of these features have the highest appeal though.
+What's more interesting are the "Other" answers.
+
+By far the most popular "Other" answer was related to Nim's compilation to C.
+Many users mentioned that they like how easy it is to interface with C
+libraries and the great portability that compiling to C offers.
+
+What aspects of Nim do you find most displeasing?
+_________________________________________________
+
+.. raw::html
+
+
+
+
+
+It was only natural to ask this question. The results are almost perfectly
+opposite to the previous question's answers, with almost 50% of respondents
+selecting "Debugging Tools"
+as the most displeasing aspect of Nim. With "Documentation" and "Testing Tools"
+in second and third place respectively. There is also a much larger number of
+"Other" answers to this question.
+
+The "Other" answers for this question vary a lot. Here is a selection of
+them, ordered by frequency:
+
+* Small community size.
+* Lack of in-depth tutorials.
+* Quality of error messages.
+* Forward declarations and no cyclic imports.
+* Bugs in the standard library.
+* No good IDE.
+* No REPL.
+* No major version.
+* Bugs in the compiler.
+* Lack of libraries.
+* Difficulty installing on Windows.
+* Non-intuitive semantics of various constructs.
+* Lack of immutable collections.
+* Async/await not being production ready.
+* Lack of shared collections for threads.
+* No Haxe target.
+* Memory safety.
+
+We hope that we can improve these things with time. Many of these issues are
+already being worked on, including the removal of the need for forward
+declarations. Some of these issues like our small community size are difficult
+to fix, but we will nonetheless do our best.
+
+
+Previous Nim users
+~~~~~~~~~~~~~~~~~~
+
+For users that have used Nim before but decided against using it, we asked just
+one specific question. The proportion of our respondents that answered it
+was 24%.
+
+Why did you stop using Nim?
+___________________________
+
+.. raw::html
+
+
+
+
+
+Again, this question got a lot of "Other" answers. Apart from that, the
+most popular reason for leaving Nim is that it is not stable. Followed by the
+a lack of needed libraries and packages and the instability of the
+standard library.
+
+* Lack of IDE support.
+* Style insensitive.
+* Documentation.
+* Dislike the syntax.
+* Community is too small.
+* Missing language features (for example RAII).
+* No opportunities to use it at work.
+* Messy standard library.
+
+The first item, "Lack of IDE support", was mentioned by multiple respondents.
+In the future we should look into ensuring that major IDEs have plugins which
+enable easy Nim development.
+
+Based on some of the "Other" answers, it seems that many of the respondents
+have not used Nim for very long, for example many respondents complained about
+installation issues which they would have run into before getting a chance to
+use Nim. Because of this I would consider them not
+ex-Nim users but developers that have not had a chance to try Nim fully.
+Next time we should also ask how long the respondent has used Nim for to get a
+better idea of whether they had a chance to use Nim for extended periods of
+time.
+
+Non-Nim users
+~~~~~~~~~~~~~
+
+We also wanted to know the reasons why developers decided against using Nim.
+
+Why do you not use Nim?
+_______________________
+
+.. raw::html
+
+
+
+
+
+The most common reason that people have for not using Nim is that it is
+not yet ready for production. Thankfully this will improve with time.
+IDE support is also a prominent factor just as we've seen in previous results.
+
+There is also a lot of "Other" answers, let's have a look at a selection of
+them. Some of the most prominent ones, in order of frequency, include:
+
+* No time to use/learn it
+* Syntax
+* Documentation is incomplete
+* Garbage Collection
+* Prefer functional paradigm
+* Small community
+* Style insensitivity/Case insensitivity
+
+One respondent made a very good suggestion: they said that the
+"Do you use Nim?" question should have included "No, but I intend to" as
+an answer. Definitely something we will do in the next survey. Indeed, many
+respondents mentioned that they were planning on trying out Nim but that they
+just have no time to do so, this is very encouraging!
+
+Learning Resources
+~~~~~~~~~~~~~~~~~~
+
+We wanted to get an idea of how Nim users are learning Nim. Every respondent
+answered this question, no matter what they answered for the "Do you use Nim?"
+question.
+
+Which learning resources, if any, did you use to learn Nim?
+___________________________________________________________
+
+.. raw::html
+
+
+
+
+
+The idea behind this question was to understand which learning resources
+were most popular among our user base. The
+`Nim tutorial `_ is by far the most
+popular. In previous questions, we saw respondents mentioning that the Nim
+tutorial does not go into enough detail about Nim. Thanks to this information
+we can come to the conclusion that the tutorial needs to be improved
+significantly to make sure that it gives our users the necessary information
+to use Nim effectively.
+
+Indeed, many users also use the
+`Nim manual `_ to learn Nim.
+This manual has been
+written as a specification and so is not ideal for teaching Nim. Many of
+the concepts in the Nim manual need to be explained in a lot more detail in
+the Nim tutorial.
+
+Of course, it's exciting to see our respondents using other materials to learn
+Nim. In particular I am excited to see that over 15% of the respondents have
+used
+`Nim in Action `_
+to learn Nim. I expect that more and more users will pick up the book after it
+is fully published.
+
+Nim in Action
+_____________
+
+As the author of
+`Nim in Action `_,
+I wanted to get some statistics surrounding
+my book. With this in mind, I have created some questions relating to it.
+
+Have you read Nim in Action?
+____________________________
+
+.. raw::html
+
+
+
+
+
+It's good to see that over 50% of respondents have read the book or are at least
+planning to read it. Keep in mind that this question was answered by all
+respondents, not just Nim users.
+
+.. container:: standout
+
+ Are you interested in purchasing a copy of
+ `Nim in Action `_?
+ If so, you can use code ``wm090416lt`` to get 50% off the printed book today only!
+ If you purchase it now you will get access to an early access copy of
+ Nim in Action in eBook form and will be able to take part in the development
+ of this book.
+
+Did you enjoy Nim in Action?
+____________________________
+
+.. raw::html
+
+
+
+
+
+Of the people that read Nim in Action it's nice to see that almost 70% have
+enjoyed it.
+
+Nim's future
+~~~~~~~~~~~~
+
+What improvements are needed before Nim v1.0 can be released?
+_____________________________________________________________
+
+We were interested to know what our users believe is needed before
+Nim version 1.0 can be released.
+
+.. raw::html
+
+
+
+
+
+It appears that the standard library is the biggest concern. With more than half
+of all respondents selecting "The standard library needs to reviewed and
+any problems with it fixed". This is in fact something we are already planning
+to address, so it's good to see that the majority agrees with us.
+
+A large proportion of users also believes that the language is great as-is
+and that we should focus on stabilising the compiler. This somewhat contradicts
+the majority. But perhaps most of them thought that "The language" excludes the
+standard library.
+
+For this question, we decided to give our respondents a dedicated place to
+give general feedback about what they feel is needed before v1.0 can be
+released. We received over 200 responses to that. Many of these responses
+reflect what we have already seen: that the documentation needs to improve,
+that we need a good Nim IDE, stability for experimental features such as
+concepts, the standard library needs to be cleaned up.
+
+Unfortunately many respondents used this question to say what needs to be fixed
+in Nim in general, not what is definitely necessary before 1.0 can be released.
+
+Community demographics
+~~~~~~~~~~~~~~~~~~~~~~
+
+What domain do you work in currently?
+_____________________________________
+
+.. raw::html
+
+
+
+
+
+
+Nim users are working in a wide variety of domains. It is encouraging to see
+people from so many different backgrounds taking part in this survey.
+
+What programming languages are you most comfortable with?
+_________________________________________________________
+
+
+.. raw::html
+
+
+
+
+
+Python and C are the top two programming languages that our respondents are
+most comfortable with. This is not altogether surprising.
+
+Last words
+~~~~~~~~~~
+
+At the end of the survey we gave our respondents a chance to speak their mind
+about anything they wish, with a simple question: "Anything else you'd like
+to tell us?"
+
+There was a lot of great feedback given in this question from people who
+obviously really care deeply about Nim. There is too much to outline here,
+but rest assurred that we will take it all into account and do our best to
+act on it.
+
+In addition to feedback, we were also overwhelmed by the amount of positive
+comments in the answers to this
+question. There was a lot of support from the community thanking us for our
+work and determination.
+
+I'll let some quotes speak for themselves:
+
+.. raw::html
+
+
You rock, seriously.
+
Nim rocks! Keep it up! Thank you very much!
+
You've made great progress on the language without any corporate backing, that is amazing. I wish Nim becomes one of the top used languages in a few years.
+
Nim is elegant and wonderful! Keep at it!
+
+Our community is truly brilliant. We thank each and every one of you for
+filling out this survey and hope that you will help us tackle some of the
+challenges that face Nim.
+
+This survey was a good place to give us feedback, but please don't wait for
+the next one. We are always looking to hear more from you and we hope that you
+will participate in discussions relating to this survey as well the future
+of Nim.
+
+Thanks for reading, and have a good day!
diff --git a/web/news/2016_09_30_version_0_15_0_released.rst b/web/news/2016_09_30_version_0_15_0_released.rst
new file mode 100644
index 000000000..47c02e9e4
--- /dev/null
+++ b/web/news/2016_09_30_version_0_15_0_released.rst
@@ -0,0 +1,517 @@
+Version 0.15.0 released
+=======================
+
+.. container:: metadata
+
+ Posted by Dominik Picheta and Andreas Rumpf on 30/09/2016
+
+We're happy to announce that the latest release of Nim, version 0.15.0, is now
+available!
+
+As always, you can grab the latest version from the
+`downloads page `_.
+
+This release includes almost 180 bug fixes and improvements. To see a full list
+of changes, take a look at the detailed changelog
+`below <#changelog>`_.
+
+Some of the most significant changes in this release include: improvements to
+the documentation, addition of a new ``multisync`` macro, and a new
+``HttpClient`` implementation.
+
+Documentation
+~~~~~~~~~~~~~
+
+All pages in the documentation now contain a search box and a drop down to
+select how procedures should be sorted. This allows you to search for
+procedures, types, macros and more from any documentation page.
+
+.. raw::html
+
+
+
+
+
+Sorting the procedures by type shows a more natural table of contents. This
+should also help you to find procedures and other identifiers.
+
+.. raw::html
+
+
+
+
+
+Multisync macro
+~~~~~~~~~~~~~~~
+
+The ``multisync`` macro was implemented to enable you to define both
+synchronous and asynchronous IO procedures without having to duplicate a
+lot of code.
+
+As an example, consider the ``recvTwice`` procedure below:
+
+.. code-block:: nim
+ proc recvTwice(socket: Socket | AsyncSocket): Future[string] {.multisync.} =
+ result = ""
+ result.add(await socket.recv(25))
+ result.add(await socket.recv(20))
+
+The ``multisync`` macro will transform this procedure into the following:
+
+.. code-block:: nim
+ proc recvTwice(socket: Socket): string =
+ result = ""
+ result.add(socket.recv(25))
+ result.add(socket.recv(20))
+
+ proc recvTwice(socket: AsyncSocket): Future[string] {.async.} =
+ result = ""
+ result.add(await socket.recv(25))
+ result.add(await socket.recv(20))
+
+Allowing you to use ``recvTwice`` with both synchronous and asynchronous sockets.
+
+HttpClient
+~~~~~~~~~~
+
+Many of the ``httpclient`` module's procedures have been deprecated in
+favour of a new implementation using the ``multisync`` macro. There are now
+two types: ``HttpClient`` and ``AsyncHttpClient``. Both of these implement the
+same procedures and functionality, the only difference is timeout support and
+whether they are blocking or not.
+
+See the `httpclient `_ module
+documentation for more information.
+
+Changelog
+~~~~~~~~~
+
+Changes affecting backwards compatibility
+-----------------------------------------
+
+- The ``json`` module now uses an ``OrderedTable`` rather than a ``Table``
+ for JSON objects.
+
+- The ``split`` `(doc) `_
+ procedure in the ``strutils`` module (with a delimiter of type
+ ``set[char]``) no longer strips and splits characters out of the target string
+ by the entire set of characters. Instead, it now behaves in a
+ similar fashion to ``split`` with ``string`` and ``char``
+ delimiters. Use ``splitWhitespace`` to get the old behaviour.
+
+- The command invocation syntax will soon apply to open brackets
+ and curlies too. This means that code like ``a [i]`` will be
+ interpreted as ``a([i])`` and not as ``a[i]`` anymore. Likewise
+ ``f (a, b)`` means that the tuple ``(a, b)`` is passed to ``f``.
+ The compiler produces a warning for ``a [i]``::
+
+ Warning: a [b] will be parsed as command syntax; spacing is deprecated
+
+ See `Issue #3898 `_ for the
+ relevant discussion.
+
+- Overloading the special operators ``.``, ``.()``, ``.=``, ``()`` now
+ needs to be enabled via the ``{.experimental.}`` pragma.
+
+- ``immediate`` templates and macros are now deprecated.
+ Use ``untyped`` `(doc) `_
+ parameters instead.
+
+- The metatype ``expr`` is deprecated. Use ``untyped``
+ `(doc) `_ instead.
+
+- The metatype ``stmt`` is deprecated. Use ``typed``
+ `(doc) `_ instead.
+
+- The compiler is now more picky when it comes to ``tuple`` types. The
+ following code used to compile, now it's rejected:
+
+.. code-block:: nim
+
+ import tables
+ var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64]]()
+ rocketaims["hi"] = {(-1.int8, 0.int8): 0.int64}.toTable()
+
+Instead be consistent in your tuple usage and use tuple names for named tuples:
+
+.. code-block:: nim
+
+ import tables
+ var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64]]()
+ rocketaims["hi"] = {(k: -1.int8, v: 0.int8): 0.int64}.toTable()
+
+- Now when you compile console applications for Windows, console output
+ encoding is automatically set to UTF-8.
+
+- Unhandled exceptions in JavaScript are now thrown regardless of whether
+ ``noUnhandledHandler`` is defined. But the stack traces should be much more
+ readable now.
+
+- In JavaScript, the ``system.alert`` procedure has been deprecated.
+ Use ``dom.alert`` instead.
+
+- De-deprecated ``re.nim`` because there is too much code using it
+ and it got the basic API right.
+
+- The type of ``headers`` field in the ``AsyncHttpClient`` type
+ `(doc) `_
+ has been changed
+ from a string table to the specialised ``HttpHeaders`` type.
+
+- The ``httpclient.request``
+ `(doc) `_
+ procedure which takes the ``httpMethod`` as a string
+ value no longer requires it to be prefixed with ``"http"``
+ (or similar).
+
+- Converting a ``HttpMethod``
+ `(doc) `_
+ value to a string using the ``$`` operator will
+ give string values without the ``"Http"`` prefix now.
+
+- The ``Request``
+ `(doc) `_
+ object defined in the ``asynchttpserver`` module now uses
+ the ``HttpMethod`` type for the request method.
+
+Library Additions
+-----------------
+
+- Added ``readHeaderRow`` and ``rowEntry`` to the ``parsecsv``
+ `(doc) `_ module
+ to provide
+ a lightweight alternative to python's ``csv.DictReader``.
+
+- Added ``setStdIoUnbuffered`` proc to the ``system`` module to enable
+ unbuffered I/O.
+
+- Added ``center`` and ``rsplit`` to the ``strutils``
+ `(doc) `_ module
+ to provide similar Python functionality for Nim's strings.
+
+- Added ``isTitle``, ``title``, ``swapCase``, ``isUpper``, ``toUpper``,
+ ``isLower``, ``toLower``, ``isAlpha``, ``isSpace``, and ``capitalize``
+ to the ``unicode.nim``
+ `(doc) `_ module
+ to provide unicode aware case manipulation and case
+ testing.
+
+- Added a new module ``strmisc``
+ `(doc) `_
+ to hold uncommon string
+ operations. Currently contains ``partition``, ``rpartition``
+ and ``expandTabs``.
+
+- Split out ``walkFiles`` in the ``os``
+ `(doc) `_ module to three separate procs
+ in order to make a clear distinction of functionality. ``walkPattern`` iterates
+ over both files and directories, while ``walkFiles`` now only iterates
+ over files and ``walkDirs`` only iterates over directories.
+
+- Added a synchronous ``HttpClient`` in the ``httpclient``
+ `(doc) `_
+ module. The old
+ ``get``, ``post`` and similar procedures are now deprecated in favour of it.
+
+- Added a new macro called ``multisync`` allowing you to write procedures for
+ synchronous and asynchronous sockets with no duplication.
+
+- The ``async`` macro will now complete ``FutureVar[T]`` parameters
+ automatically unless they have been completed already.
+
+Tool Additions
+--------------
+
+- The documentation is now searchable and sortable by type.
+- Pragmas are now hidden by default in the documentation to reduce noise.
+- Edit links are now present in the documentation.
+
+
+Compiler Additions
+------------------
+
+- The ``-d/--define`` flag can now optionally take a value to be used
+ by code at compile time.
+ `(doc) `_
+
+Nimscript Additions
+-------------------
+
+- It's possible to enable and disable specific hints and warnings in
+ Nimscript via the ``warning`` and ``hint`` procedures.
+
+- Nimscript exports a proc named ``patchFile`` which can be used to
+ patch modules or include files for different Nimble packages, including
+ the ``stdlib`` package.
+
+Language Additions
+------------------
+
+- Added ``{.intdefine.}`` and ``{.strdefine.}`` macros to make use of
+ (optional) compile time defines.
+ `(doc) `_
+
+- If the first statement is an ``import system`` statement then ``system``
+ is not imported implicitly anymore. This allows for code like
+ ``import system except echo`` or ``from system import nil``.
+
+Bugfixes
+--------
+
+The list below has been generated based on the commits in Nim's git
+repository. As such it lists only the issues which have been closed
+via a commit, for a full list see
+`this link on Github `_.
+
+- Fixed "RFC: should startsWith and endsWith work with characters?"
+ (`#4252 `_)
+
+- Fixed "Feature request: unbuffered I/O"
+ (`#2146 `_)
+- Fixed "clear() not implemented for CountTableRef"
+ (`#4325 `_)
+- Fixed "Cannot close file opened async"
+ (`#4334 `_)
+- Fixed "Feature Request: IDNA support"
+ (`#3045 `_)
+- Fixed "Async: wrong behavior of boolean operations on futures"
+ (`#4333 `_)
+- Fixed "os.walkFiles yields directories"
+ (`#4280 `_)
+- Fixed "Fix #4392 and progress on #4170"
+ (`#4393 `_)
+- Fixed "Await unable to wait futures from objects fields"
+ (`#4390 `_)
+- Fixed "TMP variable name generation should be more stable"
+ (`#4364 `_)
+- Fixed "nativesockets doesn't compile for Android 4.x (API v19 or older) because of gethostbyaddr"
+ (`#4376 `_)
+- Fixed "no generic parameters allowed for ref"
+ (`#4395 `_)
+- Fixed "split proc in strutils inconsistent for set[char]"
+ (`#4305 `_)
+- Fixed "Problem with sets in devel"
+ (`#4412 `_)
+- Fixed "Compiler crash when using seq[PNimrodNode] in macros"
+ (`#537 `_)
+- Fixed "ospaths should be marked for nimscript use only"
+ (`#4249 `_)
+- Fixed "Repeated deepCopy() on a recursive data structure eventually crashes"
+ (`#4340 `_)
+- Fixed "Analyzing destructor"
+ (`#4371 `_)
+- Fixed "getType does not work anymore on a typedesc"
+ (`#4462 `_)
+- Fixed "Error in rendering empty JSON array"
+ (`#4399 `_)
+- Fixed "Segmentation fault when using async pragma on generic procs"
+ (`#2377 `_)
+- Fixed "Forwarding does not work for generics, | produces an implicit generic"
+ (`#3055 `_)
+- Fixed "Inside a macro, the length of the `seq` data inside a `queue` does not increase and crashes"
+ (`#4422 `_)
+- Fixed "compiler sigsegv while processing varargs"
+ (`#4475 `_)
+- Fixed "JS codegen - strings are assigned by reference"
+ (`#4471 `_)
+- Fixed "when statement doesn't verify syntax"
+ (`#4301 `_)
+- Fixed ".this pragma doesn't work with .async procs"
+ (`#4358 `_)
+- Fixed "type foo = range(...) crashes compiler"
+ (`#4429 `_)
+- Fixed "Compiler crash"
+ (`#2730 `_)
+- Fixed "Crash in compiler with static[int]"
+ (`#3706 `_)
+- Fixed "Bad error message "could not resolve""
+ (`#3548 `_)
+- Fixed "Roof operator on string in template crashes compiler (Error: unhandled exception: sons is not accessible [FieldError])"
+ (`#3545 `_)
+- Fixed "SIGSEGV during compilation with parallel block"
+ (`#2758 `_)
+- Fixed "Codegen error with template and implicit dereference"
+ (`#4478 `_)
+- Fixed "@ in importcpp should work with no-argument functions"
+ (`#4496 `_)
+- Fixed "Regression: findExe raises"
+ (`#4497 `_)
+- Fixed "Linking error - repeated symbols when splitting into modules"
+ (`#4485 `_)
+- Fixed "Error: method is not a base"
+ (`#4428 `_)
+- Fixed "Casting from function returning a tuple fails"
+ (`#4345 `_)
+- Fixed "clang error with default nil parameter"
+ (`#4328 `_)
+- Fixed "internal compiler error: openArrayLoc"
+ (`#888 `_)
+- Fixed "Can't forward declare async procs"
+ (`#1970 `_)
+- Fixed "unittest.check and sequtils.allIt do not work together"
+ (`#4494 `_)
+- Fixed "httpclient package can't make SSL requests over an HTTP proxy"
+ (`#4520 `_)
+- Fixed "False positive warning "declared but not used" for enums."
+ (`#4510 `_)
+- Fixed "Explicit conversions not using converters"
+ (`#4432 `_)
+
+- Fixed "Unclear error message when importing"
+ (`#4541 `_)
+- Fixed "Change console encoding to UTF-8 by default"
+ (`#4417 `_)
+
+- Fixed "Typedesc ~= Generic notation does not work anymore!"
+ (`#4534 `_)
+- Fixed "unittest broken?"
+ (`#4555 `_)
+- Fixed "Operator "or" in converter types seems to crash the compiler."
+ (`#4537 `_)
+- Fixed "nimscript failed to compile/run -- Error: cannot 'importc' variable at compile time"
+ (`#4561 `_)
+- Fixed "Regression: identifier expected, but found ..."
+ (`#4564 `_)
+- Fixed "varargs with transformation that takes var argument creates invalid c code"
+ (`#4545 `_)
+- Fixed "Type mismatch when using empty tuple as generic parameter"
+ (`#4550 `_)
+- Fixed "strscans"
+ (`#4562 `_)
+- Fixed "getTypeImpl crashes (SIGSEGV) on variant types"
+ (`#4526 `_)
+- Fixed "Wrong result of sort in VM"
+ (`#4065 `_)
+- Fixed "I can't call the random[T](x: Slice[T]): T"
+ (`#4353 `_)
+- Fixed "invalid C code generated (function + block + empty tuple)"
+ (`#4505 `_)
+
+- Fixed "performance issue: const Table make a copy at runtime lookup."
+ (`#4354 `_)
+- Fixed "Compiler issue: libraries without absolute paths cannot be found correctly"
+ (`#4568 `_)
+- Fixed "Cannot use math.`^` with non-int types."
+ (`#4574 `_)
+- Fixed "C codegen fails when constructing an array using an object constructor."
+ (`#4582 `_)
+- Fixed "Visual Studio 10 unresolved external symbol _trunc(should we support VS2010?)"
+ (`#4532 `_)
+- Fixed "Cannot pass generic subtypes to proc for generic supertype"
+ (`#4528 `_)
+- Fixed "Lamda-lifting bug leading to crash."
+ (`#4551 `_)
+- Fixed "First-class iterators declared as inline are compiled at Nim side (no error message) and fail at C"
+ (`#2094 `_)
+- Fixed "VS2010-warning C4090 : 'function' : different 'const' qualifiers"
+ (`#4590 `_)
+- Fixed "Regression: type mismatch with generics"
+ (`#4589 `_)
+- Fixed "„can raise an unlisted exception“ when assigning nil as default value"
+ (`#4593 `_)
+- Fixed "upcoming asyncdispatch.closeSocket is not GC-safe"
+ (`#4606 `_)
+- Fixed "Visual Studio 10.0 compiler errors, 12.0 warning"
+ (`#4459 `_)
+- Fixed "Exception of net.newContext: result.extraInternalIndex == 0 [AssertionError]"
+ (`#4406 `_)
+- Fixed "error: redeclaration of 'result_115076' with no linkage"
+ (`#3221 `_)
+- Fixed "Compiler crashes on conversion from int to float at compile time"
+ (`#4619 `_)
+- Fixed "wrong number of arguments regression in devel"
+ (`#4600 `_)
+- Fixed "importc $ has broken error message (and is not documented)"
+ (`#4579 `_)
+- Fixed "Compiler segfaults on simple importcpp in js mode [regression]"
+ (`#4632 `_)
+- Fixed "Critical reference counting codegen problem"
+ (`#4653 `_)
+- Fixed "tables.nim needs lots of {.noSideEffect.}"
+ (`#4254 `_)
+- Fixed "Capture variable error when using ``=>`` macro"
+ (`#4658 `_)
+- Fixed "Enum from char: internal error getInt"
+ (`#3606 `_)
+- Fixed "Compiler crashes in debug mode (no error in release mode) with Natural discriminant in object variants"
+ (`#2865 `_)
+- Fixed "SIGSEGV when access field in const object variants"
+ (`#4253 `_)
+- Fixed "varargs cannot be used with template converter."
+ (`#4292 `_)
+- Fixed "Compiler crashes when borrowing $"
+ (`#3928 `_)
+- Fixed "internal error: genMagicExpr: mArrPut"
+ (`#4491 `_)
+- Fixed "Unhelpful error message on importc namespace collision"
+ (`#4580 `_)
+- Fixed "Problem with openarrays and slices"
+ (`#4179 `_)
+- Fixed "Removing lines from end of file then rebuilding does not rebuild [js only?]"
+ (`#4656 `_)
+- Fixed "getCurrentException and getCurrentExceptionMsg do not work with JS"
+ (`#4635 `_)
+- Fixed "generic proc parameter is not inferred if type parameter has specifier"
+ (`#4672 `_)
+- Fixed "Cannot instantiate generic parameter when it is parent type parameter"
+ (`#4673 `_)
+- Fixed "deepCopy doesn't work with inheritance after last commit"
+ (`#4693 `_)
+- Fixed "Multi-methods don't work when passing ref to a different thread"
+ (`#4689 `_)
+- Fixed "Infinite loop in effect analysis on generics"
+ (`#4677 `_)
+- Fixed "SIGSEGV when compiling NimYAML tests"
+ (`#4699 `_)
+
+- Fixed "Closing AsyncEvent now also unregisters it on non-Windows platforms"
+ (`#4694 `_)
+- Fixed "Don't update handle in upcoming/asyncdispatch poll() if it was closed"
+ (`#4697 `_)
+- Fixed "generated local variables declared outside block"
+ (`#4721 `_)
+- Fixed "Footer Documentation links, & Community link point to the wrong place under news entries"
+ (`#4529 `_)
+- Fixed "Jester's macro magic leads to incorrect C generation"
+ (`#4088 `_)
+- Fixed "cas bug in atomics.nim"
+ (`#3279 `_)
+- Fixed "nimgrep PEG not capturing the pattern 'A'"
+ (`#4751 `_)
+- Fixed "GC assert triggers when assigning TableRef threadvar"
+ (`#4640 `_)
+- Fixed ".this pragma conflicts with experimental ptr dereferencing when names conflict"
+ (`#4671 `_)
+- Fixed "Generic procs accepting var .importcpp type do not work [regression]"
+ (`#4625 `_)
+- Fixed "C Error on tuple assignment with array"
+ (`#4626 `_)
+- Fixed "module securehash not gcsafe"
+ (`#4760 `_)
+
+- Fixed "Nimble installation failed on Windows x86."
+ (`#4764 `_)
+- Fixed "Recent changes to marshal module break old marshalled data"
+ (`#4779 `_)
+- Fixed "tnewasyncudp.nim test loops forever"
+ (`#4777 `_)
+- Fixed "Wrong poll timeout behavior in asyncdispatch"
+ (`#4262 `_)
+- Fixed "Standalone await shouldn't read future"
+ (`#4170 `_)
+- Fixed "Regression: httpclient fails to compile without -d:ssl"
+ (`#4797 `_)
+- Fixed "C Error on declaring array of heritable objects with bitfields"
+ (`#3567 `_)
+- Fixed "Corruption when using Channels and Threads"
+ (`#4776 `_)
+- Fixed "Sometimes Channel tryRecv() erroneously reports no messages available on the first call on Windows"
+ (`#4746 `_)
+- Fixed "Improve error message of functions called without parenthesis"
+ (`#4813 `_)
+- Fixed "Docgen doesn't find doc comments in macro generated procs"
+ (`#4803 `_)
+- Fixed "asynchttpserver may consume unbounded memory reading headers"
+ (`#3847 `_)
+- Fixed "TLS connection to api.clashofclans.com hangs forever."
+ (`#4587 `_)
diff --git a/web/news/nim_community_survey_results.rst b/web/news/nim_community_survey_results.rst
deleted file mode 100644
index 49656f20a..000000000
--- a/web/news/nim_community_survey_results.rst
+++ /dev/null
@@ -1,317 +0,0 @@
-Nim Community Survey Results
-============================
-
-.. container:: metadata
-
- Posted by Dominik Picheta on 20/08/2016
-
-We have recently closed the 2016 Nim Community Survey. I am happy to
-say that we have received exactly 790 responses, huge thanks go to the people
-that took the time to respond. We're very thankful for this very valuable
-feedback.
-
-This survey was inspired in part by the
-`2016 State of Rust `_
-survey. You will note that many of the questions were modelled after
-Rust's survey. One of the reasons for doing this was to allow us to easily
-compare our results against the results obtained in the Rust survey. In
-addition, we of course also liked many of their questions.
-
-Our survey ran from the 23rd of June 2016 until the 8th of August 2016. The
-response numbers are impressive considering Nim's community size; at 790 they
-make up just over 25% of the Rust survey's responses.
-
-The goal of this survey was to primarily determine how our community is using
-Nim, in order to better understand how we should be improving it. In particular,
-we wanted to know what people feel is missing from Nim in the lead up to
-version 1.0. We have also asked our respondents about how well the Nim tools
-worked, the challenges of adopting Nim, the resources that they used to learn
-Nim and more.
-
-It is my hope that we will be able to run a similar survey in a years time,
-doing so should give us an idea of whether we are improving.
-With these general facts in mind, let's begin looking at specific questions.
-
-How did you find out about Nim?
--------------------------------
-
-The rationale for the first question was simple, we wanted to know where our
-respondents found out about Nim. This is an interesting question for us, as
-we do occassionally get users asking us why it took so long for them to hear
-about Nim. It allows us to see how effective each website is at spreading the
-word about Nim.
-
-.. raw::html
-
-
-
-
-
-The majority of our respondents found Nim via Reddit, HackerNews or a search
-engine such as Google. These results are not altogether surprising. There were
-also a lot of "Other" responses, some of which were a bit more
-interesting. These included multiple mentions of habrahabr.ru, Dr. Dobb's,
-and lobste.rs.
-
-Do you use Nim?
----------------
-
-Just like the Rust survey creators, we wanted to ensure that our survey was
-open to both Nim users as well people who never used Nim. In addition to
-those two groups, we have also included a third group of people: ex-Nim
-users. All three are interesting, for many different reasons.
-Nim users can tell us how they are using Nim and also how Nim's
-tooling can improve. Ex-Nim users give us an
-idea of why they stopped using Nim. Finally, respondents who never used Nim
-can tell us the reasons for not adopting it.
-
-.. raw::html
-
-
-
-
-
-It's nice to see that we have such a good range of respondents. The Rust survey
-had a much larger number of Rust users amongst their respondents, with
-no distinction between users that never used Rust and users that stopped using
-Rust.
-
-.. raw::html
-
-
-
-
-
-Should we consider your answers to be invalid?
-----------------------------------------------
-
-This was something I thought would be interesting to have, after I saw it
-being used in another survey. While it does pinpoint possibly
-invalid respondents, I have opted against filtering those out. Mainly because
-that would require re-creating each of the charts generated by Google Forms
-manually.
-
-.. raw::html
-
-
-
-
-
-According to the responses to this question, around 94% of our responses
-can be considered reliable.
-
-Nim users
----------
-
-The following questions were answered only by the 38.9% of our respondents
-who identified themselves as Nim users.
-
-How long have you been using Nim?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. raw::html
-
-
-
-
-
-A large proportion of our Nim users were new. This is good news as it means that
-our community is growing, with a large proportion of new Nim users that could
-become long-term Nimians. In total, more than 35% of Nim users can be considered
-new having used Nim for less than 3 months. With 18% of Nim users that can
-be considered very new having used Nim for less than a month.
-This could suggest that 18% of our users have only just found out about Nim in
-the last week or so and have not yet got the chance to use it extensively.
-
-The high percentages of long term Nim users are encouraging.
-They suggest
-that many users are continuing to use Nim after making it through the first
-few months. The sharp drop at 7-9 months is interesting, but may simply be
-due to the fact that there were fewer newcomers during that period, or it
-could be because our respondents are more likely to estimate that they have
-been using Nim for a year or half a year rather than the awkward 7-9 months.
-
-.. raw::html
-
-
-
-
-
-The results for Nim and Rust are actually remarkably similar. They both show a
-drop at 7-9 months, although Rust's isn't as dramatic. Nim on the other hand
-has a significantly higher percentage of new Nim users.
-
-Do you use Nim at work?
-~~~~~~~~~~~~~~~~~~~~~~~
-
-An important aspect of a language's adoption is whether it is being used for
-"real" work. We wanted to know how many people are using Nim in their day
-jobs and under what circumstances it is used.
-
-.. raw::html
-
-
-
-
-
-While a vast majority of our users are not using Nim at work, more than 25%
-of them are. It's encouraging to see such a high number already, even before
-we have released version 1.0. In fact, this percentage is likely close to 30%,
-because many of the "Other" responses mention using Nim for the likes of
-internal tools or small scripts to help with the respondent's work.
-
-.. raw::html
-
-
-
-
-
-Interestingly, a larger percentage of Nim users are using Nim at work than
-Rust users. The sample sizes are of course vastly different, but it's still an
-interesting result. Combined, nearly 1/5th of Rust users are using Rust
-commercially whereas more than a quarter of Nim users are using Nim
-commercially.
-
-Approximately how large are all the Nim projects that you work on?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Finding out how large the Nim projects worked on by Nim users are is also
-very valuable.
-
-.. raw::html
-
-
-
-
-
-This shows us that currently Nim is primarily being used for small scripts and
-applications, with nearly 60% of the projects consisting of less than 1,000
-lines of code. This makes sense as many of our users are not using Nim
-professionally, but are doing so in their spare time.
-
-.. raw::html
-
-
-
-
-
-The numbers for part-time and full-time work users of Nim tell a different
-story. Over 70% of the projects written by full-time users are between 10,001
-and 100,000 lines of code. Part-time users show a slightly different trend,
-with many more small projects, the majority being between 1,000 and
-10,000 lines of code.
-
-Overall it's good to see that there is a few large projects out there which are
-composed of more than 100,000 lines of code. We expect to see the amount of
-large projects to grow with time, especially with version 1.0 on the way.
-
-.. raw::html
-
-
-
-
-
-In comparison to Rust the proportion of project sizes for full-time users is
-vastly different. This is likely due to our small sample size. Project sizes for
-part-time users between Rust and Nim are somewhat similar, with differences of
-around 10% for each project size.
-
-Do you plan to try to use Nim at work?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. raw::html
-
-
-
-
-
-It's also encouraging to see that over 50% of Nim users are planning to use
-Nim at work! This is slightly more than Rust's 40% and should help Nim's
-adoption into even more areas.
-
-Nim and its tools
-~~~~~~~~~~~~~~~~~
-
-In this section of the survey, we wanted to find out the tools that Nim
-users are utilising when developing Nim applications.
-
-What editor(s) do you use when writing Nim?
-___________________________________________
-
-Programmers are very specific when it comes to their editor of choice, because
-of that it's good to know which editor is most popular among our community.
-
-.. raw::html
-
-
-
-
-
-Looks like Vim is the winner with almost 30%. Followed by Sublime Text and
-Visual Studio Code. Aporia, the Nim IDE, gets a respectable 15.5%. There was
-also more than
-17% of answers which included "Other" editors, such as: Notepad++, Geany, gedit,
-and Kate.
-
-What operating system(s) do you compile for and run your Nim projects on?
-_________________________________________________________________________
-
-This question gave us information about the most popular target operating
-systems, as well as some of the more obscure ones. We have asked this question
-to find out the platforms on which Nim applications run on most frequently.
-
-.. raw::html
-
-
-
-
-
-This question allowed multiple choices, so each percentage is out of the total
-number of respondents for this question. For example, 80.7% of the
-respondents selected "Linux" but only 26.6% selected OS X.
-
-This makes Linux by far the most popular target for Nim applications.
-Some "Other" targets included: BSD (OpenBSD, FreeBSD), iOS, Android, and
-JavaScript.
-It's great to see Nim being used on such a wide variety of platforms.
-
-What operating system(s) do you develop Nim projects on?
-________________________________________________________
-
-With this question, we wanted to know what operating systems are used for
-development.
-
-.. raw::html
-
-
-
-
-
-This question also allowed multiple choices and ended up with very similar
-results.
-
-You can see that Linux is also the most popular developmental
-platform for Nim. But it's more popular as a target platform.
-
-Which version(s) of Nim do you use for your applications?
-_________________________________________________________
-
-.. raw::html
-
-
-
-
-
-At the time of this survey, version 0.14.2 was the latest stable release.
-It's no wonder that it is the most commonly used release of Nim. It's good to
-see that the older versions are not used as often. The high use of ``Git HEAD (devel)``
-(nightly builds) isn't surprising, Nim is still evolving rapidly and our
-release schedule is not regular or frequent.
-
-Once we go past the 1.0 release, we expect to see much less use of the unstable
-``devel`` branch.
-
-
-
-
-
diff --git a/web/news/version_0_15_released.rst b/web/news/version_0_15_released.rst
deleted file mode 100644
index 1e35fb627..000000000
--- a/web/news/version_0_15_released.rst
+++ /dev/null
@@ -1,108 +0,0 @@
-Version 0.15.0 released
-=======================
-
-.. container:: metadata
-
- Posted by Dominik Picheta on 17/09/2016
-
-Some text here.
-
-Changes affecting backwards compatibility
------------------------------------------
-
-- De-deprecated ``re.nim`` because we have too much code using it
- and it got the basic API right.
-
-- ``split`` with ``set[char]`` as a delimiter in ``strutils.nim``
- no longer strips and splits characters out of the target string
- by the entire set of characters. Instead, it now behaves in a
- similar fashion to ``split`` with ``string`` and ``char``
- delimiters. Use ``splitWhitespace`` to get the old behaviour.
-- The command invocation syntax will soon apply to open brackets
- and curlies too. This means that code like ``a [i]`` will be
- interpreted as ``a([i])`` and not as ``a[i]`` anymore. Likewise
- ``f (a, b)`` means that the tuple ``(a, b)`` is passed to ``f``.
- The compiler produces a warning for ``a [i]``::
-
- Warning: a [b] will be parsed as command syntax; spacing is deprecated
-
- See ``_ for the relevant
- discussion.
-- Overloading the special operators ``.``, ``.()``, ``.=``, ``()`` now
- should be enabled via ``{.experimental.}``.
-- ``immediate`` templates and macros are now deprecated.
- Instead use ``untyped`` parameters.
-- The metatype ``expr`` is deprecated. Use ``untyped`` instead.
-- The metatype ``stmt`` is deprecated. Use ``typed`` instead.
-- The compiler is now more picky when it comes to ``tuple`` types. The
- following code used to compile, now it's rejected:
-
-.. code-block:: nim
-
- import tables
- var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64] ]()
- rocketaims["hi"] = {(-1.int8, 0.int8): 0.int64}.toTable()
-
-Instead be consistent in your tuple usage and use tuple names for tuples
-that have tuple name:
-
-.. code-block:: nim
-
- import tables
- var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64] ]()
- rocketaims["hi"] = {(k: -1.int8, v: 0.int8): 0.int64}.toTable()
-
-- Now when you compile console application for Windows, console output
- encoding is automatically set to UTF-8.
-
-Library Additions
------------------
-
-- Added ``readHeaderRow`` and ``rowEntry`` to ``parsecsv.nim`` to provide
- a lightweight alternative to python's ``csv.DictReader``.
-- Added ``setStdIoUnbuffered`` proc to ``system.nim`` to enable unbuffered I/O.
-
-- Added ``center`` and ``rsplit`` to ``strutils.nim`` to
- provide similar Python functionality for Nim's strings.
-
-- Added ``isTitle``, ``title``, ``swapCase``, ``isUpper``, ``toUpper``,
- ``isLower``, ``toLower``, ``isAlpha``, ``isSpace``, and ``capitalize``
- to ``unicode.nim`` to provide unicode aware case manipulation and case
- testing.
-
-- Added a new module ``lib/pure/strmisc.nim`` to hold uncommon string
- operations. Currently contains ``partition``, ``rpartition``
- and ``expandTabs``.
-
-- Split out ``walkFiles`` in ``os.nim`` to three separate procs in order
- to make a clear distinction of functionality. ``walkPattern`` iterates
- over both files and directories, while ``walkFiles`` now only iterates
- over files and ``walkDirs`` only iterates over directories.
-
-Compiler Additions
-------------------
-
-- The ``-d/--define`` flag can now optionally take a value to be used
- by code at compile time.
-
-Nimscript Additions
--------------------
-
-- Finally it's possible to dis/enable specific hints and warnings in
- Nimscript via the procs ``warning`` and ``hint``.
-- Nimscript exports a proc named ``patchFile`` which can be used to
- patch modules or include files for different Nimble packages, including
- the ``stdlib`` package.
-
-
-Language Additions
-------------------
-
-- Added ``{.intdefine.}`` and ``{.strdefine.}`` macros to make use of
- (optional) compile time defines.
-- If the first statement is an ``import system`` statement then ``system``
- is not imported implicitly anymore. This allows for code like
- ``import system except echo`` or ``from system import nil``.
-
-Bugfixes
---------
diff --git a/web/sponsors.csv b/web/sponsors.csv
index ac35b284c..0701575d5 100644
--- a/web/sponsors.csv
+++ b/web/sponsors.csv
@@ -1,31 +1,34 @@
logo, name, url, this_month, all_time, since, level
-assets/bountysource/secondspectrum.png,Second Spectrum,http://www.secondspectrum.com/,250,750,"May 5, 2016",250
-assets/bountysource/xored.svg,"Xored Software, Inc.",http://xored.com/,250,500,"Jun 20, 2016",250
-,avsej,http://avsej.net,75,85,"Jun 10, 2016",75
-,shkolnick-kun,https://github.com/shkolnick-kun,75,75,"Jul 6, 2016",75
-,flyx,http://flyx.org,35,140,"Apr 7, 2016",75
-,endragor,https://github.com/endragor,25,100,"Apr 7, 2016",25
-,euantorano,http://euantorano.co.uk,25,50,"Jun 7, 2016",25
-,FedericoCeratto,http://firelet.net,25,100,"Apr 7, 2016",25
-,"Adrian Veith",,25,100,"Apr 20, 2016",25
-,xxlabaza,https://github.com/xxlabaza,25,45,"Jun 17, 2016",25
-,"Yuriy Glukhov",,25,100,"Apr 6, 2016",25
-,"Jonathan Arnett",,10,30,"May 20, 2016",10
-,"Oskari Timperi",,10,20,"Jun 8, 2016",10
-,zachaysan,http://venn.lc,10,20,"Jun 7, 2016",10
-,"Matthew Baulch",,10,20,"Jun 7, 2016",10
-,RationalG,https://github.com/RationalG,10,20,"Jun 17, 2016",10
-,btbytes,https://www.btbytes.com/,10,40,"Apr 6, 2016",10
-,niebaopeng,https://github.com/niebaopeng,10,30,"Apr 15, 2016",10
-,moigagoo,http://sloth-ci.com,5,15,"May 13, 2016",5
-,calind,http://calindon.net,5,10,"Jun 7, 2016",5
-,swalf,https://github.com/swalf,5,35,"May 9, 2016",5
-,johnnovak,http://www.johnnovak.net/,5,20,"Apr 29, 2016",5
-,RyanMarcus,http://rmarcus.info,5,5,"Jul 19, 2016",5
-,Blumenversand,https://blumenversender.com/,5,5,"Jul 21, 2016",5
-,lenzenmi,https://github.com/lenzenmi,5,5,"Jul 28, 2016",5
-,"Handojo Goenadi",,5,20,"Apr 19, 2016",5
-,"Date in Asia",,5,5,"Jul 30, 2016",5
-,"Matthew Newton",,5,20,"Apr 20, 2016",5
-,"Michael D. Sklaroff",,1,4,"Apr 27, 2016",1
-,"Svend Knudsen",,1,4,"Apr 11, 2016",1
+assets/bountysource/secondspectrum.png,Second Spectrum,http://www.secondspectrum.com/,250,1250,"May 5, 2016",250
+assets/bountysource/xored.svg,"Xored Software, Inc.",http://xored.com/,250,1000,"Jun 20, 2016",250
+,shkolnick-kun,https://github.com/shkolnick-kun,75,225,"Jul 6, 2016",75
+,flyx,http://flyx.org,35,210,"Apr 7, 2016",75
+,"Yuriy Glukhov",,25,150,"Apr 6, 2016",25
+,endragor,https://github.com/endragor,25,150,"Apr 7, 2016",25
+,FedericoCeratto,http://firelet.net,25,150,"Apr 7, 2016",25
+,"Adrian Veith",,25,150,"Apr 20, 2016",25
+,skunkiferous,https://github.com/skunkiferous,100,100,"Oct 2, 2016",150
+,euantorano,http://euantorano.co.uk,25,100,"Jun 7, 2016",25
+,xxlabaza,https://github.com/xxlabaza,25,95,"Jun 17, 2016",25
+,btbytes,https://www.btbytes.com/,10,60,"Apr 6, 2016",10
+,niebaopeng,https://github.com/niebaopeng,10,50,"Apr 15, 2016",10
+,"Jonathan Arnett",,10,50,"May 20, 2016",10
+,swalf,https://github.com/swalf,5,45,"May 9, 2016",5
+,zolern,https://github.com/zolern,10,40,"Apr 15, 2016",10
+,"pyloor ",https://schwarz-weiss.cc/,10,40,"May 16, 2016",10
+,zachaysan,http://venn.lc,10,40,"Jun 7, 2016",10
+,"Matthew Baulch",,10,40,"Jun 7, 2016",10
+,"Oskari Timperi",,10,40,"Jun 8, 2016",10
+,"Handojo Goenadi",,5,35,"Apr 19, 2016",5
+,"Matthew Newton",,5,30,"Apr 20, 2016",5
+,johnnovak,http://www.johnnovak.net/,5,30,"Apr 29, 2016",5
+,RyanMarcus,http://rmarcus.info,5,15,"Jul 19, 2016",5
+,lenzenmi,https://github.com/lenzenmi,5,15,"Jul 28, 2016",5
+,cpunion,https://github.com/cpunion,10,10,"Sep 9, 2016",10
+,pandada8,https://github.com/pandada8,5,10,"Aug 12, 2016",5
+,abeaumont,http://alfredobeaumont.org/blog,5,10,"Aug 12, 2016",5
+,"Svend Knudsen",,1,6,"Apr 11, 2016",1
+,"Michael D. Sklaroff",,1,6,"Apr 27, 2016",1
+,csoriano89,https://github.com/csoriano89,5,5,"Sep 7, 2016",5
+,nicck,,1,2,"Aug 9, 2016",1
+,campbellr,,1,1,"Sep 4, 2016",1
diff --git a/web/ticker.html b/web/ticker.html
index ecbfb5e3f..a05ea8476 100644
--- a/web/ticker.html
+++ b/web/ticker.html
@@ -1,3 +1,13 @@
+
+