diff options
45 files changed, 1548 insertions, 1307 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 347060248..131c2a38f 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) @@ -925,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 diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index e1ee6f1de..0ba775b25 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2084,11 +2084,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 1b21e641a..294235ae9 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -359,6 +359,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]) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 4a3113edf..7333c77c3 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -616,6 +616,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) @@ -627,11 +645,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) @@ -791,12 +815,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") % diff --git a/compiler/semcall.nim b/compiler/semcall.nim index b3fc020b1..5f7e5084b 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -34,55 +34,85 @@ 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, filter: TSymKinds, best, alt: var TCandidate, errors: var CandidateErrors) = - while true: - block pickAttempt: - var o: TOverloadIter - var sym = initOverloadIter(o, c, headSymbol) - # 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 o: TOverloadIter + 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 - while sym != nil: - if sym.kind in filter: - initCandidate(c, best, sym, initialBinding, o.lastOverloadScope) - initCandidate(c, alt, sym, initialBinding, o.lastOverloadScope) - best.state = csNoMatch - break - else: - sym = nextOverloadIter(o, c, headSymbol) - var z: TCandidate - while sym != nil: - if sym.kind notin filter: - sym = nextOverloadIter(o, c, headSymbol) - continue - determineType(c, sym) - initCandidate(c, z, sym, initialBinding, o.lastOverloadScope) - if c.currentScope.symbols.counter != counterInitial: break pickAttempt - matches(c, n, orig, z) - if errors != nil: - errors.safeAdd((sym, int z.mutabilityProblem)) - if z.errors != nil: - for err in z.errors: - errors.add(err) - if z.state == csMatch: - # little hack so that iterators are preferred over everything else: - if sym.kind == skIterator: inc(z.exactMatches, 200) - case best.state - of csEmpty, csNoMatch: best = z - of csMatch: - var cmp = cmpCandidates(best, z) - if cmp < 0: best = z # x is better than the best so far - elif cmp == 0: alt = z # x is as good as the best so far - else: discard - sym = nextOverloadIter(o, c, headSymbol) - break # pick attempt was successful + initCandidate(c, best, sym, initialBinding, scope) + initCandidate(c, alt, sym, initialBinding, scope) + best.state = csNoMatch + break + else: + sym = nextOverloadIter(o, c, headSymbol) + scope = o.lastOverloadScope + var z: TCandidate + while sym != nil: + if sym.kind notin filter: + sym = nextOverloadIter(o, c, headSymbol) + scope = o.lastOverloadScope + continue + determineType(c, sym) + initCandidate(c, z, sym, initialBinding, scope) + if c.currentScope.symbols.counter == counterInitial or syms != nil: + matches(c, n, orig, z) + if errors != nil: + errors.safeAdd((sym, int z.mutabilityProblem)) + if z.errors != nil: + for err in z.errors: + errors.add(err) + if z.state == csMatch: + # little hack so that iterators are preferred over everything else: + if sym.kind == skIterator: inc(z.exactMatches, 200) + case best.state + of csEmpty, csNoMatch: best = z + of csMatch: + var cmp = cmpCandidates(best, z) + if cmp < 0: best = z # x is better than the best so far + elif cmp == 0: alt = z # x is as good as the best so far + else: + # Symbol table has been modified. Restart and pre-calculate all syms + # before any further candidate init and compare. SLOW, but rare case. + syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o) + if syms == nil: + sym = nextOverloadIter(o, c, headSymbol) + scope = o.lastOverloadScope + elif nextSymIndex < syms.len: + # rare case: retrieve the next pre-calculated symbol + sym = syms[nextSymIndex].s + scope = syms[nextSymIndex].scope + nextSymIndex += 1 + else: + break proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 1cb726053..42fa60781 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -514,7 +514,8 @@ proc foldConv*(n, a: PNode; check = false): PNode = else: result = a result.typ = n.typ - if check: rangeCheck(n, result.intVal) + if check and result.kind in {nkCharLit..nkUInt64Lit}: + rangeCheck(n, result.intVal) of tyFloat..tyFloat64: case skipTypes(a.typ, abstractRange).kind of tyInt..tyInt64, tyEnum, tyBool, tyChar: 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/suggest.nim b/compiler/suggest.nim index f331fce69..52f00550b 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -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/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 79bc1b96d..6bea8e817 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 @@ -827,14 +827,17 @@ when defined(windows) or defined(nimdoc): var retFuture = newFuture[void]("send") var dataBuf: TWSABuf - dataBuf.buf = data # since this is not used in a callback, this is fine + dataBuf.buf = data dataBuf.len = data.len.ULONG + GC_ref(data) # we need to protect data until send operation is completed + # or failed. 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() @@ -851,6 +854,8 @@ when defined(windows) or defined(nimdoc): 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: @@ -1577,365 +1582,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: 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 .. <tryStmt.len: - let exceptBranch = tryStmt[i] - if exceptBranch[0].kind == nnkStmtList: - exceptionChecks.add((newIdentNode("true"), exceptBranch[0])) - else: - var exceptIdentCount = 0 - var ifCond: NimNode - for i in 0 .. <exceptBranch.len: - let child = exceptBranch[i] - if child.kind == nnkIdent: - let cond = infix(errorNode, "of", child) - if exceptIdentCount == 0: - ifCond = cond - else: - ifCond = infix(ifCond, "or", cond) - else: - break - exceptIdentCount.inc - - expectKind(exceptBranch[exceptIdentCount], nnkStmtList) - exceptionChecks.add((ifCond, exceptBranch[exceptIdentCount])) - # -> -> 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<x> - result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode) - # -> future<x>.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<x> = 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 .. <result.len: - result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil) - -proc getName(node: NimNode): string {.compileTime.} = - case node.kind - of nnkPostfix: - return $node[1].ident - of nnkIdent: - return $node.ident - of nnkEmpty: - return "anonymous" - else: - error("Unknown name.") - -proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = - ## This macro transforms a single procedure into a closure iterator. - ## The ``async`` macro supports a stmtList holding multiple async procedures. - if prc.kind notin {nnkProcDef, nnkLambda}: - error("Cannot transform this node kind into an async proc." & - " Proc definition or lambda node expected.") - - hint("Processing " & prc[0].getName & " as an async proc.") - - let returnType = prc[3][0] - var baseType: NimNode - # Verify that the return type is a Future[T] - if returnType.kind == nnkBracketExpr: - let fut = repr(returnType[0]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") - baseType = returnType[1] - elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": - let fut = repr(returnType[1]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") - baseType = returnType[2] - elif returnType.kind == nnkEmpty: - baseType = returnType - else: - error("Expected return type of 'Future' got '" & repr(returnType) & "'") - - let subtypeIsVoid = returnType.kind == nnkEmpty or - (baseType.kind == nnkIdent and returnType[1].ident == !"void") - - var outerProcBody = newNimNode(nnkStmtList, prc[6]) - - # -> 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.} - # -> <proc_body> - # -> 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 .. <result[4].len: - if result[4][i].kind == nnkIdent and result[4][i].ident == !"async": - result[4].del(i) - result[4] = newEmptyNode() - if subtypeIsVoid: - # Add discardable pragma. - if returnType.kind == nnkEmpty: - # Add Future[void] - result[3][0] = parseExpr("Future[void]") - if procBody.kind != nnkEmpty: - result[6] = outerProcBody - #echo(treeRepr(result)) - #if prc[0].getName == "testInfix": - # echo(toStrLit(result)) - -macro async*(prc: untyped): untyped = - ## Macro which processes async procedures into the appropriate - ## iterators and yield statements. - if prc.kind == nnkStmtList: - for oneProc in prc: - result = newStmtList() - result.add asyncSingleProc(oneProc) - else: - result = asyncSingleProc(prc) - when defined(nimDumpAsync): - echo repr result +include asyncmacro proc recvLine*(socket: AsyncFD): Future[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim new file mode 100644 index 000000000..28e3e2a16 --- /dev/null +++ b/lib/pure/asyncmacro.nim @@ -0,0 +1,373 @@ +# +# +# 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 + +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 .. <tryStmt.len: + let exceptBranch = tryStmt[i] + if exceptBranch[0].kind == nnkStmtList: + exceptionChecks.add((newIdentNode("true"), exceptBranch[0])) + else: + var exceptIdentCount = 0 + var ifCond: NimNode + for i in 0 .. <exceptBranch.len: + let child = exceptBranch[i] + if child.kind == nnkIdent: + let cond = infix(errorNode, "of", child) + if exceptIdentCount == 0: + ifCond = cond + else: + ifCond = infix(ifCond, "or", cond) + else: + break + exceptIdentCount.inc + + expectKind(exceptBranch[exceptIdentCount], nnkStmtList) + exceptionChecks.add((ifCond, exceptBranch[exceptIdentCount])) + # -> -> 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<x> + result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode) + # -> future<x>.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<x> = 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 .. <result.len: + result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil) + +proc getName(node: NimNode): string {.compileTime.} = + case node.kind + of nnkPostfix: + return $node[1].ident + of nnkIdent: + return $node.ident + of nnkEmpty: + return "anonymous" + else: + error("Unknown name.") + +proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = + ## This macro transforms a single procedure into a closure iterator. + ## The ``async`` macro supports a stmtList holding multiple async procedures. + if prc.kind notin {nnkProcDef, nnkLambda}: + error("Cannot transform this node kind into an async proc." & + " Proc definition or lambda node expected.") + + hint("Processing " & prc[0].getName & " as an async proc.") + + let returnType = prc[3][0] + var baseType: NimNode + # Verify that the return type is a Future[T] + if returnType.kind == nnkBracketExpr: + let fut = repr(returnType[0]) + if fut != "Future": + error("Expected return type of 'Future' got '" & fut & "'") + baseType = returnType[1] + elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": + let fut = repr(returnType[1]) + if fut != "Future": + error("Expected return type of 'Future' got '" & fut & "'") + baseType = returnType[2] + elif returnType.kind == nnkEmpty: + baseType = returnType + else: + error("Expected return type of 'Future' got '" & repr(returnType) & "'") + + let subtypeIsVoid = returnType.kind == nnkEmpty or + (baseType.kind == nnkIdent and returnType[1].ident == !"void") + + var outerProcBody = newNimNode(nnkStmtList, prc[6]) + + # -> 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.} + # -> <proc_body> + # -> 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 .. <result[4].len: + if result[4][i].kind == nnkIdent and result[4][i].ident == !"async": + result[4].del(i) + result[4] = newEmptyNode() + if subtypeIsVoid: + # Add discardable pragma. + if returnType.kind == nnkEmpty: + # Add Future[void] + result[3][0] = parseExpr("Future[void]") + if procBody.kind != nnkEmpty: + result[6] = outerProcBody + #echo(treeRepr(result)) + #if prc[0].getName == "testInfix": + # echo(toStrLit(result)) + +macro async*(prc: untyped): untyped = + ## Macro which processes async procedures into the appropriate + ## iterators and yield statements. + if prc.kind == nnkStmtList: + for oneProc in prc: + result = newStmtList() + result.add asyncSingleProc(oneProc) + else: + result = asyncSingleProc(prc) + when defined(nimDumpAsync): + echo repr result 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 - -# ------ - -proc countLogLines(logger: RollingFileLogger): int = - result = 0 - for line in logger.file.lines(): - result.inc() - -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 - - result.logFiles = countFiles(filename) - - if mode == fmAppend: - # We need to get a line count because we will be appending to the file. - result.curLine = countLogLines(result) - -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 +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 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 countLogLines(logger: RollingFileLogger): int = + result = 0 + for line in logger.file.lines(): + result.inc() + + 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 + + result.logFiles = countFiles(filename) + + if mode == fmAppend: + # We need to get a line count because we will be appending to the file. + result.curLine = countLogLines(result) + + 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/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index 3c11e082f..ce44e8a6a 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 @@ -796,14 +796,17 @@ when defined(windows) or defined(nimdoc): var retFuture = newFuture[void]("send") var dataBuf: TWSABuf - dataBuf.buf = data # since this is not used in a callback, this is fine + dataBuf.buf = data dataBuf.len = data.len.ULONG + GC_ref(data) # we need to protect data until send operation is completed + # or failed. 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() @@ -820,6 +823,8 @@ when defined(windows) or defined(nimdoc): 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: @@ -952,7 +957,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 +977,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 = @@ -1748,360 +1753,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 .. <tryStmt.len: - let exceptBranch = tryStmt[i] - if exceptBranch[0].kind == nnkStmtList: - exceptionChecks.add((newIdentNode("true"), exceptBranch[0])) - else: - var exceptIdentCount = 0 - var ifCond: NimNode - for i in 0 .. <exceptBranch.len: - let child = exceptBranch[i] - if child.kind == nnkIdent: - let cond = infix(errorNode, "of", child) - if exceptIdentCount == 0: - ifCond = cond - else: - ifCond = infix(ifCond, "or", cond) - else: - break - exceptIdentCount.inc - - expectKind(exceptBranch[exceptIdentCount], nnkStmtList) - exceptionChecks.add((ifCond, exceptBranch[exceptIdentCount])) - # -> -> 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<x> - result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode) - # -> future<x>.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<x> = 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 .. <result.len: - result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil) - -proc getName(node: NimNode): string {.compileTime.} = - case node.kind - of nnkPostfix: - return $node[1].ident - of nnkIdent: - return $node.ident - of nnkEmpty: - return "anonymous" - else: - error("Unknown name.") - -proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = - ## This macro transforms a single procedure into a closure iterator. - ## The ``async`` macro supports a stmtList holding multiple async procedures. - if prc.kind notin {nnkProcDef, nnkLambda}: - error("Cannot transform this node kind into an async proc." & - " Proc definition or lambda node expected.") - - hint("Processing " & prc[0].getName & " as an async proc.") - - let returnType = prc[3][0] - var baseType: NimNode - # Verify that the return type is a Future[T] - if returnType.kind == nnkBracketExpr: - let fut = repr(returnType[0]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") - baseType = returnType[1] - elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": - let fut = repr(returnType[1]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") - baseType = returnType[2] - elif returnType.kind == nnkEmpty: - baseType = returnType - else: - error("Expected return type of 'Future' got '" & repr(returnType) & "'") - - let subtypeIsVoid = returnType.kind == nnkEmpty or - (baseType.kind == nnkIdent and returnType[1].ident == !"void") - - var outerProcBody = newNimNode(nnkStmtList, prc[6]) - - # -> 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.} - # -> <proc_body> - # -> 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 .. <result[4].len: - if result[4][i].kind == nnkIdent and result[4][i].ident == !"async": - result[4].del(i) - result[4] = newEmptyNode() - if subtypeIsVoid: - # Add discardable pragma. - if returnType.kind == nnkEmpty: - # Add Future[void] - result[3][0] = parseExpr("Future[void]") - - result[6] = outerProcBody - - #echo(treeRepr(result)) - #if prc[0].getName == "testInfix": - # echo(toStrLit(result)) - -macro async*(prc: stmt): stmt {.immediate.} = - ## Macro which processes async procedures into the appropriate - ## iterators and yield statements. - if prc.kind == nnkStmtList: - for oneProc in prc: - result = newStmtList() - result.add asyncSingleProc(oneProc) - else: - result = asyncSingleProc(prc) +include asyncmacro proc recvLine*(socket: AsyncFD): Future[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once diff --git a/readme.md b/readme.md index f56b054d6..204b51906 100644 --- a/readme.md +++ b/readme.md @@ -68,13 +68,16 @@ problem. ## Community [![Join the Chat at irc.freenode.net#nim](https://img.shields.io/badge/IRC-join_chat_in_%23nim-blue.svg)](https://webchat.freenode.net/?channels=nim) +[![Join the Gitter channel](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/nim-lang/Nim) [![Get help](https://img.shields.io/badge/Forum-get%20help-4eb899.svg)](http://forum.nim-lang.org) [![Stackoverflow](https://img.shields.io/badge/stackoverflow-use_%23nim_tag-yellow.svg)](http://stackoverflow.com/questions/tagged/nim?sort=newest&pageSize=15) [![Follow @nim_lang!](https://img.shields.io/twitter/follow/nim_lang.svg?style=social)](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/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/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/testament/categories.nim b/tests/testament/categories.nim index 29b6d9aba..3ed2f2196 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -202,6 +202,14 @@ proc ioTests(r: var TResults, cat: Category, options: string) = testSpec c, makeTest("tests/system/helpers/readall_echo", options, cat) testSpec r, makeTest("tests/system/io", options, cat) +# ------------------------- async tests --------------------------------------- +proc asyncTests(r: var TResults, cat: Category, options: string) = + template test(filename: untyped) = + testSpec r, makeTest(filename, options, cat) + testSpec r, makeTest(filename, options & " -d:upcoming", cat) + for t in os.walkFiles("tests/async/t*.nim"): + test(t) + # ------------------------- debugger tests ------------------------------------ proc debuggerTests(r: var TResults, cat: Category, options: string) = @@ -228,8 +236,8 @@ proc jsTests(r: var TResults, cat: Category, options: string) = "varres/tvartup", "misc/tints", "misc/tunsignedinc"]: test "tests/" & testfile & ".nim" - for testfile in ["pure/strutils", "pure/json", "pure/random", "pure/times"]: - test "lib/" & testfile & ".nim" + for testfile in ["strutils", "json", "random", "times", "logging"]: + test "lib/pure/" & testfile & ".nim" # ------------------------- manyloc ------------------------------------------- #proc runSpecialTests(r: var TResults, options: string) = @@ -390,6 +398,8 @@ proc processCategory(r: var TResults, cat: Category, options: string, fileGlob: threadTests r, cat, options & " --threads:on" of "io": ioTests r, cat, options + of "async": + asyncTests r, cat, options of "lib": testStdlib(r, "lib/pure/*.nim", options, cat) testStdlib(r, "lib/packages/docutils/highlite", options, cat) diff --git a/tools/nimweb.nim b/tools/nimweb.nim index 4cf7020c2..cef4df1c6 100644 --- a/tools/nimweb.nim +++ b/tools/nimweb.nim @@ -94,7 +94,8 @@ Compile_options: rYearMonthDay = r"(\d{4})_(\d{2})_(\d{2})" rssUrl = "http://nim-lang.org/news.xml" rssNewsUrl = "http://nim-lang.org/news.html" - sponsors = "web/sponsors.csv" + activeSponsors = "web/sponsors.csv" + inactiveSponsors = "web/inactive_sponsors.csv" validAnchorCharacters = Letters + Digits @@ -446,8 +447,9 @@ proc readSponsors(sponsorsFile: string): seq[Sponsor] = since: parser.row[5], level: parser.row[6].parseInt)) parser.close() -proc buildSponsors(c: var TConfigData, sponsorsFile: string, outputDir: string) = - let sponsors = generateSponsors(readSponsors(sponsorsFile)) +proc buildSponsors(c: var TConfigData, outputDir: string) = + let sponsors = generateSponsorsPage(readSponsors(activeSponsors), + readSponsors(inactiveSponsors)) let outFile = outputDir / "sponsors.html" var f: File if open(f, outFile, fmWrite): @@ -500,7 +502,7 @@ proc buildWebsite(c: var TConfigData) = buildPage(c, file, if file == "question": "FAQ" else: file, rss) copyDir("web/assets", "web/upload/assets") buildNewsRss(c, "web/upload") - buildSponsors(c, sponsors, "web/upload") + buildSponsors(c, "web/upload") buildNews(c, "web/news", "web/upload/news") proc main(c: var TConfigData) = diff --git a/tools/website.tmpl b/tools/website.tmpl index cf2c72a60..87e1b151c 100644 --- a/tools/website.tmpl +++ b/tools/website.tmpl @@ -66,7 +66,7 @@ </a> </div> <div id="slide1" class="niaslide"> - <a href="news.html#Z2016-01-27-nim-in-action-is-now-available"> + <a href="news/2016_01_27_nim_in_action_is_now_available.html"> <img src="${rootDir}assets/niminaction/banner.jpg" alt="New in Manning Early Access Program: Nim in Action!"/> </a> </div> @@ -218,14 +218,9 @@ runForever() </html> #end proc # +# #proc generateSponsors(sponsors: seq[Sponsor]): string = #result = "" -<h1 id="our-current-sponsors">Our Current Sponsors</h1> -<p>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 <a href="https://salt.bountysource.com/teams/nim">Bountysource campaign</a>.</p> -<p class="lastUpdate">Last updated: ${getTime().getGMTime().format("dd/MM/yyyy")}</p> -<dl> #for sponsor in sponsors: <dt class="level-${sponsor.level}"> #if sponsor.url.len > 0: @@ -248,6 +243,24 @@ look at the <a href="https://salt.bountysource.com/teams/nim">Bountysource campa Donated $$${sponsor.allTime} in total since ${sponsor.since} </dd> #end for +#end proc +#proc generateSponsorsPage(activeSponsors, inactiveSponsors: seq[Sponsor]): string = +#result = "" +<h1 id="our-current-sponsors">Our Current Sponsors</h1> +<p>This section 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 <a href="https://salt.bountysource.com/teams/nim">Bountysource campaign</a>.</p> +<p class="lastUpdate">Last updated: ${getTime().getGMTime().format("dd/MM/yyyy")}</p> +<dl> +${generateSponsors(activeSponsors)} +</dl> +# +<h1 id="our-past-sponsors">Our Past Sponsors</h1> +<p>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 <a href="https://salt.bountysource.com/teams/nim">Bountysource campaign</a>.</p> +<dl> +${generateSponsors(inactiveSponsors)} </dl> # #end proc 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 --- /dev/null +++ b/web/assets/news/images/survey/10_needs.png Binary files differdiff --git a/web/assets/news/images/survey/book.png b/web/assets/news/images/survey/book.png new file mode 100644 index 000000000..5bb418e63 --- /dev/null +++ b/web/assets/news/images/survey/book.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/book_opinion.png Binary files differdiff --git a/web/assets/news/images/survey/breakage.png b/web/assets/news/images/survey/breakage.png new file mode 100644 index 000000000..5eb4c5289 --- /dev/null +++ b/web/assets/news/images/survey/breakage.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/difficulty_fixing_breakage.png Binary files differdiff --git a/web/assets/news/images/survey/domains.png b/web/assets/news/images/survey/domains.png new file mode 100644 index 000000000..50b1ed7ff --- /dev/null +++ b/web/assets/news/images/survey/domains.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/ex_nim.png Binary files differdiff --git a/web/assets/news/images/survey/languages.png b/web/assets/news/images/survey/languages.png new file mode 100644 index 000000000..db35f9bd4 --- /dev/null +++ b/web/assets/news/images/survey/languages.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/learning_resources.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/nim_appeal.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/nim_displeasing.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/nim_domains.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/nimble_opinion.png Binary files differdiff --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 --- /dev/null +++ b/web/assets/news/images/survey/non_user.png Binary files differdiff --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 --- a/web/assets/news/images/survey/planning_to_use_at_work.png +++ b/web/assets/news/images/survey/planning_to_use_at_work.png Binary files differdiff --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 --- a/web/assets/news/images/survey/project_size_nim_rust.png +++ b/web/assets/news/images/survey/project_size_nim_rust.png Binary files differdiff --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 --- a/web/assets/news/images/survey/upgrades_broke_things.png +++ /dev/null Binary files differdiff --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") - )) - - echo("Generated ", sponsors.len, " sponsors") - writeCsv(sponsors) - echo("Written csv file to sponsors.new.csv") + 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 ", activeSponsors.len, " active sponsors") + echo("Generated ", inactiveSponsors.len, " inactive sponsors") + writeCsv(activeSponsors) + writeCsv(inactiveSponsors, "inactive_sponsors.new.csv") diff --git a/web/inactive_sponsors.csv b/web/inactive_sponsors.csv new file mode 100644 index 000000000..929882ff4 --- /dev/null +++ b/web/inactive_sponsors.csv @@ -0,0 +1,44 @@ +logo, name, url, this_month, all_time, since, level +,mikra,,0,400,"Apr 28, 2016",1 +,linkmonitor,,0,180,"Jan 28, 2016",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 +,WilRubin,,0,50,"Aug 11, 2015",1 +,rb01,,0,50,"May 4, 2016",1 +,TedSinger,,0,45,"Apr 9, 2016",1 +,martinbbjerregaard,,0,35,"Jun 9, 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 +,zolern,,0,20,"Apr 15, 2016",1 +,mirek,,0,15,"Apr 9, 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 +,kteza1,,0,10,"Jun 10, 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..2b43034cc 100644 --- a/web/news.rst +++ b/web/news.rst @@ -2,6 +2,9 @@ News ==== +`2016-09-03 Nim Community Survey results <news/2016_09_03_nim_community_survey_results.html>`_ +=================================== + `2016-08-06 BountySource Update: The Road to v1.0 <news/2016_08_06_bountysource_update_the_road_to_v10.html>`_ =================================== 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 <https://blog.rust-lang.org/2016/06/30/State-of-Rust-Survey-2016.html>`_ +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 + + <a href="../assets/news/images/survey/nim_found.png"> + <img src="../assets/news/images/survey/nim_found.png" alt="How did you find out about Nim?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/do_you_use_nim.png"> + <img src="../assets/news/images/survey/do_you_use_nim.png" alt="Do you use Nim?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/reliability.png"> + <img src="../assets/news/images/survey/reliability.png" alt="Should we consider your answers to be invalid?" style="width:100%"/> + </a> + +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 href="../assets/news/images/survey/nim_time.png"> + <img src="../assets/news/images/survey/nim_time.png" alt="How long have you been using Nim?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/nim_time_rust.png"> + <img src="../assets/news/images/survey/nim_time_rust.png" alt="Time using Nim and Rust" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/nim_at_work.png"> + <img src="../assets/news/images/survey/nim_at_work.png" alt="Do you use Nim at work?" style="width:100%"/> + </a> + +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 + + <a href="https://blog.rust-lang.org/images/2016-06-Survey/rust_at_work.png"> + <img src="https://blog.rust-lang.org/images/2016-06-Survey/rust_at_work.png" alt="Do you use Rust at work?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/project_size.png"> + <img src="../assets/news/images/survey/project_size.png" alt="Nim project size for all users" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/project_size_work.png"> + <img src="../assets/news/images/survey/project_size_work.png" alt="Nim project size for work users" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/project_size_nim_rust.png"> + <img src="../assets/news/images/survey/project_size_nim_rust.png" alt="Nim project size for work users (Nim vs. Rust)" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/planning_to_use_at_work.png"> + <img src="../assets/news/images/survey/planning_to_use_at_work.png" alt="Planning to use Nim at work?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/editors.png"> + <img src="../assets/news/images/survey/editors.png" alt="Editors used by Nim users" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/target_os.png"> + <img src="../assets/news/images/survey/target_os.png" alt="Target operating systems" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/dev_os.png"> + <img src="../assets/news/images/survey/dev_os.png" alt="Development operating systems" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/nim_versions.png"> + <img src="../assets/news/images/survey/nim_versions.png" alt="Version use" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/breakage.png"> + <img src="../assets/news/images/survey/breakage.png" alt="Breakage" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/difficulty_fixing_breakage.png"> + <img src="../assets/news/images/survey/difficulty_fixing_breakage.png" alt="difficulty fixing breakage" style="width:100%"/> + </a> + +Thankfully most of the breakage experienced by Nim users was very easy to fix. + + +If you used Nimble, do you like it? +___________________________________ + +.. raw::html + + <a href="../assets/news/images/survey/nimble_opinion.png"> + <img src="../assets/news/images/survey/nimble_opinion.png" alt="Do you like Nimble?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/nim_appeal.png"> + <img src="../assets/news/images/survey/nim_appeal.png" alt="What aspects of Nim do you find most appealing?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/nim_displeasing.png"> + <img src="../assets/news/images/survey/nim_displeasing.png" alt="What aspects of Nim do you find most displeasing?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/ex_nim.png"> + <img src="../assets/news/images/survey/ex_nim.png" alt="I stopped using Nim because..." style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/non_user.png"> + <img src="../assets/news/images/survey/non_user.png" alt="I don't use Nim because..." style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/learning_resources.png"> + <img src="../assets/news/images/survey/learning_resources.png" alt="learning resources" style="width:100%"/> + </a> + +The idea behind this question was to understand which learning resources +were most popular among our user base. The +`Nim tutorial <http://nim-lang.org/docs/tut1.html>`_ 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 <http://nim-lang.org/docs/manual.html>`_ 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 <https://manning.com/books/nim-in-action?a_aid=niminaction&a_bid=78a27e81>`_ +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 <https://manning.com/books/nim-in-action?a_aid=niminaction&a_bid=78a27e81>`_, +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 + + <a href="../assets/news/images/survey/book.png"> + <img src="../assets/news/images/survey/book.png" alt="Have you read Nim in Action?" style="width:100%"/> + </a> + +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 <https://manning.com/books/nim-in-action?a_aid=niminaction&a_bid=78a27e81>`_? + 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 + + <a href="../assets/news/images/survey/book_opinion.png"> + <img src="../assets/news/images/survey/book_opinion.png" alt="Did you enjoy Nim in Action?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/10_needs.png"> + <img src="../assets/news/images/survey/10_needs.png" alt="What is needed before 1.0 can be released?" style="width:100%"/> + </a> + +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 + + <a href="../assets/news/images/survey/domains.png"> + <img src="../assets/news/images/survey/domains.png" alt="Work domains" style="width:100%"/> + </a> + + +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 + + <a href="../assets/news/images/survey/languages.png"> + <img src="../assets/news/images/survey/languages.png" alt="Programming languages" style="width:100%"/> + </a> + +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 + + <blockquote>You rock, seriously.</blockquote> + <blockquote>Nim rocks! Keep it up! Thank you very much!</blockquote> + <blockquote>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.</blockquote> + <blockquote>Nim is elegant and wonderful! Keep at it!</blockquote> + +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/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 <https://blog.rust-lang.org/2016/06/30/State-of-Rust-Survey-2016.html>`_ -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 - - <a href="../assets/news/images/survey/nim_found.png"> - <img src="../assets/news/images/survey/nim_found.png" alt="How did you find out about Nim?" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/do_you_use_nim.png"> - <img src="../assets/news/images/survey/do_you_use_nim.png" alt="Do you use Nim?" style="width:100%"/> - </a> - -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 - - <a href="https://blog.rust-lang.org/images/2016-06-Survey/do_you_use_rust.png"> - <img src="https://blog.rust-lang.org/images/2016-06-Survey/do_you_use_rust.png" alt="Do you use Rust?" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/reliability.png"> - <img src="../assets/news/images/survey/reliability.png" alt="Should we consider your answers to be invalid?" style="width:100%"/> - </a> - -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 href="../assets/news/images/survey/nim_time.png"> - <img src="../assets/news/images/survey/nim_time.png" alt="How long have you been using Nim?" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/nim_time_rust.png"> - <img src="../assets/news/images/survey/nim_time_rust.png" alt="Time using Nim and Rust" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/nim_at_work.png"> - <img src="../assets/news/images/survey/nim_at_work.png" alt="Do you use Nim at work?" style="width:100%"/> - </a> - -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 - - <a href="https://blog.rust-lang.org/images/2016-06-Survey/rust_at_work.png"> - <img src="https://blog.rust-lang.org/images/2016-06-Survey/rust_at_work.png" alt="Do you use Rust at work?" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/project_size.png"> - <img src="../assets/news/images/survey/project_size.png" alt="Nim project size for all users" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/project_size_work.png"> - <img src="../assets/news/images/survey/project_size_work.png" alt="Nim project size for work users" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/project_size_nim_rust.png"> - <img src="../assets/news/images/survey/project_size_nim_rust.png" alt="Nim project size for work users (Nim vs. Rust)" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/planning_to_use_at_work.png"> - <img src="../assets/news/images/survey/planning_to_use_at_work.png" alt="Planning to use Nim at work?" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/editors.png"> - <img src="../assets/news/images/survey/editors.png" alt="Editors used by Nim users" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/target_os.png"> - <img src="../assets/news/images/survey/target_os.png" alt="Target operating systems" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/dev_os.png"> - <img src="../assets/news/images/survey/dev_os.png" alt="Development operating systems" style="width:100%"/> - </a> - -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 - - <a href="../assets/news/images/survey/nim_versions.png"> - <img src="../assets/news/images/survey/nim_versions.png" alt="Version use" style="width:100%"/> - </a> - -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/sponsors.csv b/web/sponsors.csv index ac35b284c..fe0261d17 100644 --- a/web/sponsors.csv +++ b/web/sponsors.csv @@ -1,31 +1,36 @@ 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 +,bogen,https://github.com/bogen,250,1010,"Jul 23, 2016",250 +assets/bountysource/secondspectrum.png,Second Spectrum,http://www.secondspectrum.com/,250,1000,"May 5, 2016",250 +assets/bountysource/xored.svg,"Xored Software, Inc.",http://xored.com/,250,750,"Jun 20, 2016",250 +,flyx,http://flyx.org,35,175,"Apr 7, 2016",75 +,shkolnick-kun,https://github.com/shkolnick-kun,75,150,"Jul 6, 2016",75 +,"Yuriy Glukhov",,25,125,"Apr 6, 2016",25 +,endragor,https://github.com/endragor,25,125,"Apr 7, 2016",25 +,FedericoCeratto,http://firelet.net,25,125,"Apr 7, 2016",25 +,"Adrian Veith",,25,125,"Apr 20, 2016",25 +,avsej,http://avsej.net,25,110,"Jun 10, 2016",25 +,euantorano,http://euantorano.co.uk,25,75,"Jun 7, 2016",25 +,xxlabaza,https://github.com/xxlabaza,25,70,"Jun 17, 2016",25 +,btbytes,https://www.btbytes.com/,10,50,"Apr 6, 2016",10 +,niebaopeng,https://github.com/niebaopeng,10,40,"Apr 15, 2016",10 +,"pyloor ",https://schwarz-weiss.cc/,10,40,"May 16, 2016",10 +,"Jonathan Arnett",,10,40,"May 20, 2016",10 +,swalf,https://github.com/swalf,5,40,"May 9, 2016",5 +,zachaysan,http://venn.lc,10,30,"Jun 7, 2016",10 +,"Matthew Baulch",,10,30,"Jun 7, 2016",10 +,"Oskari Timperi",,10,30,"Jun 8, 2016",10 +,RationalG,https://github.com/RationalG,10,30,"Jun 17, 2016",10 +,"Handojo Goenadi",,5,25,"Apr 19, 2016",5 +,"Matthew Newton",,5,25,"Apr 20, 2016",5 +,johnnovak,http://www.johnnovak.net/,5,25,"Apr 29, 2016",5 +,moigagoo,http://sloth-ci.com,5,20,"May 13, 2016",5 +,RyanMarcus,http://rmarcus.info,5,10,"Jul 19, 2016",5 +,Blumenversand,https://github.com/blumenversand,5,10,"Jul 21, 2016",5 +,lenzenmi,https://github.com/lenzenmi,5,10,"Jul 28, 2016",5 +,DateinAsia,,5,10,"Jul 30, 2016",5 +,pandada8,https://github.com/pandada8,5,5,"Aug 12, 2016",5 +,abeaumont,http://alfredobeaumont.org/blog,5,5,"Aug 12, 2016",5 +,"Svend Knudsen",,1,5,"Apr 11, 2016",1 +,"Michael D. Sklaroff",,1,5,"Apr 27, 2016",1 +,nicck,,1,1,"Aug 9, 2016",1 + diff --git a/web/ticker.html b/web/ticker.html index ecbfb5e3f..7a1d620d3 100644 --- a/web/ticker.html +++ b/web/ticker.html @@ -1,3 +1,8 @@ +<a class="news" href="$1news/2016_09_03_nim_community_survey_results.html"> + <h4>September 3, 2016</h4> + <p>Nim Community Survey results</p> +</a> + <a class="news" href="$1news/2016_08_06_bountysource_update_the_road_to_v10.html"> <h4>August 6, 2016</h4> <p>BountySource Update: The Road to v1.0</p> @@ -17,9 +22,4 @@ <h4>June 04, 2016</h4> <p>Meet our BountySource sponsors</p> </a> - -<a class="news" href="$1news/2016_01_27_nim_in_action_is_now_available.html"> - <h4>January 27, 2016</h4> - <p>Nim in Action is now available!</p> -</a> <a href="$1news.html" class="blue">See All News...</a> |