diff options
-rw-r--r-- | compiler/hlo.nim | 3 | ||||
-rw-r--r-- | compiler/nimrod.nimrod.cfg | 1 | ||||
-rw-r--r-- | compiler/sem.nim | 19 | ||||
-rw-r--r-- | compiler/semdata.nim | 2 | ||||
-rw-r--r-- | compiler/semexprs.nim | 17 | ||||
-rw-r--r-- | compiler/semgnrc.nim | 8 | ||||
-rw-r--r-- | compiler/semtempl.nim | 4 | ||||
-rw-r--r-- | compiler/semtypes.nim | 10 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 2 | ||||
-rw-r--r-- | compiler/transf.nim | 1 | ||||
-rw-r--r-- | compiler/vm.nim | 12 | ||||
-rw-r--r-- | compiler/vmgen.nim | 2 | ||||
-rw-r--r-- | doc/lib.txt | 28 | ||||
-rw-r--r-- | lib/nimbase.h | 2 | ||||
-rw-r--r-- | lib/pure/asyncdispatch.nim | 68 | ||||
-rw-r--r-- | lib/pure/asynchttpserver.nim | 179 | ||||
-rw-r--r-- | lib/pure/asyncnet.nim | 38 | ||||
-rw-r--r-- | lib/pure/httpclient.nim | 44 | ||||
-rw-r--r-- | lib/pure/irc.nim | 1 | ||||
-rw-r--r-- | lib/pure/math.nim | 26 | ||||
-rw-r--r-- | lib/pure/net.nim | 11 | ||||
-rw-r--r-- | lib/pure/parseurl.nim | 5 | ||||
-rw-r--r-- | lib/pure/rawsockets.nim | 34 | ||||
-rw-r--r-- | lib/pure/redis.nim | 54 | ||||
-rw-r--r-- | lib/pure/selectors.nim | 39 | ||||
-rw-r--r-- | lib/pure/sockets.nim | 14 | ||||
-rw-r--r-- | lib/pure/uri.nim | 8 | ||||
-rw-r--r-- | lib/system/sysio.nim | 5 | ||||
-rw-r--r-- | lib/windows/winlean.nim | 7 | ||||
-rw-r--r-- | tests/assert/tfailedassert.nim | 2 | ||||
-rw-r--r-- | tests/trmacros/targlist.nim (renamed from tests/patterns/targlist.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tcse.nim (renamed from tests/patterns/tcse.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/thoist.nim (renamed from tests/patterns/thoist.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tmatrix.nim (renamed from tests/patterns/tmatrix.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tnoalias.nim (renamed from tests/patterns/tnoalias.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tnoendlessrec.nim (renamed from tests/patterns/tnoendlessrec.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tor.nim (renamed from tests/patterns/tor.nim) | 9 | ||||
-rw-r--r-- | tests/trmacros/tpartial.nim (renamed from tests/patterns/tpartial.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tpatterns.nim (renamed from tests/patterns/tpatterns.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tstar.nim (renamed from tests/patterns/tstar.nim) | 0 | ||||
-rw-r--r-- | tests/trmacros/tstmtlist.nim (renamed from tests/patterns/tstmtlist.nim) | 0 | ||||
-rw-r--r-- | tests/typerel/texplicitcmp.nim | 32 | ||||
-rw-r--r-- | tests/typerel/tvarargsexpr.nim | 18 | ||||
-rw-r--r-- | tests/vm/tarrayboundeval.nim | 23 | ||||
-rw-r--r-- | web/news.txt | 238 | ||||
-rw-r--r-- | web/nimrod.ini | 3 |
46 files changed, 732 insertions, 237 deletions
diff --git a/compiler/hlo.nim b/compiler/hlo.nim index 7982d4aa1..c75d6519f 100644 --- a/compiler/hlo.nim +++ b/compiler/hlo.nim @@ -68,7 +68,8 @@ proc hlo(c: PContext, n: PNode): PNode = result = n else: if n.kind in {nkFastAsgn, nkAsgn, nkIdentDefs, nkVarTuple} and - n.sons[0].kind == nkSym and sfGlobal in n.sons[0].sym.flags: + n.sons[0].kind == nkSym and + {sfGlobal, sfPure} * n.sons[0].sym.flags == {sfGlobal, sfPure}: # do not optimize 'var g {.global} = re(...)' again! return n result = applyPatterns(c, n) diff --git a/compiler/nimrod.nimrod.cfg b/compiler/nimrod.nimrod.cfg index cc27d9f36..2c6e6f249 100644 --- a/compiler/nimrod.nimrod.cfg +++ b/compiler/nimrod.nimrod.cfg @@ -20,3 +20,4 @@ import:testability define:useStdoutAsStdmsg cs:partial +#define:useNodeIds diff --git a/compiler/sem.nim b/compiler/sem.nim index c35cff027..b34972219 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -161,12 +161,13 @@ proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} = localError(typ.n.info, errXisNoType, typeToString(typ)) proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym -proc semTemplateExpr(c: PContext, n: PNode, s: PSym, semCheck = true): PNode proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode proc isOpImpl(c: PContext, n: PNode): PNode +proc semTemplateExpr(c: PContext, n: PNode, s: PSym, + flags: TExprFlags = {}): PNode proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, - semCheck: bool = true): PNode + flags: TExprFlags = {}): PNode proc symFromType(t: PType, info: TLineInfo): PSym = if t.sym != nil: return t.sym @@ -265,7 +266,8 @@ proc semConstExpr(c: PContext, n: PNode): PNode = include hlo, seminst, semcall -proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = +proc semAfterMacroCall(c: PContext, n: PNode, s: PSym, + flags: TExprFlags): PNode = inc(evalTemplateCounter) if evalTemplateCounter > 100: globalError(s.info, errTemplateInstantiationTooNested) @@ -281,7 +283,7 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = # BUGFIX: we cannot expect a type here, because module aliases would not # work then (see the ``tmodulealias`` test) # semExprWithType(c, result) - result = semExpr(c, result) + result = semExpr(c, result, flags) of tyStmt: result = semStmt(c, result) of tyTypeDesc: @@ -290,14 +292,14 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = result.typ = makeTypeDesc(c, typ) #result = symNodeFromType(c, typ, n.info) else: - result = semExpr(c, result) + result = semExpr(c, result, flags) result = fitNode(c, s.typ.sons[0], result) #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0])) dec(evalTemplateCounter) c.friendModule = oldFriend -proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, - semCheck: bool = true): PNode = +proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, + flags: TExprFlags = {}): PNode = markUsed(n, sym) if sym == c.p.owner: globalError(n.info, errRecursiveDependencyX, sym.name.s) @@ -306,7 +308,8 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, # c.evalContext = c.createEvalContext(emStatic) result = evalMacroCall(c.module, n, nOrig, sym) - if semCheck: result = semAfterMacroCall(c, result, sym) + if efNoSemCheck notin flags: + result = semAfterMacroCall(c, result, sym, flags) proc forceBool(c: PContext, n: PNode): PNode = result = fitNode(c, getSysType(tyBool), n) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index b46d83a92..4cb5ad38c 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -42,7 +42,7 @@ type TExprFlag* = enum efLValue, efWantIterator, efInTypeof, efWantStmt, efDetermineType, - efAllowDestructor, efWantValue, efOperand + efAllowDestructor, efWantValue, efOperand, efNoSemCheck TExprFlags* = set[TExprFlag] PContext* = ref TContext diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 6743768a2..9b3b2d73e 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -10,11 +10,12 @@ # this module does the semantic checking for expressions # included from sem.nim -proc semTemplateExpr(c: PContext, n: PNode, s: PSym, semCheck = true): PNode = +proc semTemplateExpr(c: PContext, n: PNode, s: PSym, + flags: TExprFlags = {}): PNode = markUsed(n, s) pushInfoContext(n.info) result = evalTemplate(n, s, getCurrOwner()) - if semCheck: result = semAfterMacroCall(c, result, s) + if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags) popInfoContext() proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode @@ -95,8 +96,8 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = else: result = newSymNode(s, n.info) else: result = newSymNode(s, n.info) - of skMacro: result = semMacroExpr(c, n, n, s) - of skTemplate: result = semTemplateExpr(c, n, s) + of skMacro: result = semMacroExpr(c, n, n, s, flags) + of skTemplate: result = semTemplateExpr(c, n, s, flags) of skVar, skLet, skResult, skParam, skForVar: markUsed(n, s) # if a proc accesses a global variable, it is not side effect free: @@ -793,8 +794,8 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = result = n let callee = result.sons[0].sym case callee.kind - of skMacro: result = semMacroExpr(c, result, orig, callee) - of skTemplate: result = semTemplateExpr(c, result, callee) + of skMacro: result = semMacroExpr(c, result, orig, callee, flags) + of skTemplate: result = semTemplateExpr(c, result, callee, flags) else: semFinishOperands(c, result) activate(c, result) @@ -1966,13 +1967,13 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = semDirectOp(c, n, flags) else: var p = fixImmediateParams(n) - result = semMacroExpr(c, p, p, s) + result = semMacroExpr(c, p, p, s, flags) of skTemplate: if sfImmediate notin s.flags: result = semDirectOp(c, n, flags) else: var p = fixImmediateParams(n) - result = semTemplateExpr(c, p, s) + result = semTemplateExpr(c, p, s, flags) of skType: # XXX think about this more (``set`` procs) if n.len == 2: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index ffc1a43b2..da4a2a132 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -47,12 +47,12 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym): PNode = of skTemplate: if macroToExpand(s): let n = fixImmediateParams(n) - result = semTemplateExpr(c, n, s, false) + result = semTemplateExpr(c, n, s, {efNoSemCheck}) else: result = symChoice(c, n, s, scOpen) of skMacro: if macroToExpand(s): - result = semMacroExpr(c, n, n, s, false) + result = semMacroExpr(c, n, n, s, {efNoSemCheck}) else: result = symChoice(c, n, s, scOpen) of skGenericParam: @@ -126,14 +126,14 @@ proc semGenericStmt(c: PContext, n: PNode, case s.kind of skMacro: if macroToExpand(s): - result = semMacroExpr(c, n, n, s, false) + result = semMacroExpr(c, n, n, s, {efNoSemCheck}) else: n.sons[0] = symChoice(c, n.sons[0], s, scOption) result = n of skTemplate: if macroToExpand(s): let n = fixImmediateParams(n) - result = semTemplateExpr(c, n, s, false) + result = semTemplateExpr(c, n, s, {efNoSemCheck}) else: n.sons[0] = symChoice(c, n.sons[0], s, scOption) result = n diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 1432b76f0..ad34eea65 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -539,7 +539,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = elif contains(c.toBind, s.id): result = symChoice(c.c, n, s, scClosed) elif templToExpand(s): - result = semPatternBody(c, semTemplateExpr(c.c, n, s, false)) + result = semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck})) else: discard # we keep the ident unbound for matching instantiated symbols and @@ -584,7 +584,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = if s.owner == c.owner and s.kind == skParam: discard elif contains(c.toBind, s.id): discard elif templToExpand(s): - return semPatternBody(c, semTemplateExpr(c.c, n, s, false)) + return semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck})) if n.kind == nkInfix and n.sons[0].kind == nkIdent: # we interpret `*` and `|` only as pattern operators if they occur in diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index c53dc0f7d..a563cf06c 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -225,8 +225,16 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = # properly filled-out in semtypinst (see how tyStaticExpr # is handled there). indx = makeRangeWithStaticExpr(c, e) - else: + elif e.kind == nkIdent: indx = e.typ.skipTypes({tyTypeDesc}) + else: + let x = semConstExpr(c, e) + if x.kind in {nkIntLit..nkUInt64Lit}: + indx = makeRangeType(c, 0, x.intVal-1, n.info, + x.typ.skipTypes({tyTypeDesc})) + else: + indx = x.typ.skipTypes({tyTypeDesc}) + #localError(n[1].info, errConstExprExpected) addSonSkipIntLit(result, indx) if indx.kind == tyGenericInst: indx = lastSon(indx) if indx.kind notin {tyGenericParam, tyStatic, tyFromExpr}: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index b9fb0c957..b83c27d22 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -668,6 +668,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isNone else: discard of tyOpenArray, tyVarargs: + # varargs[expr] is special + if f.kind == tyVarargs and f.sons[0].kind == tyExpr: return case a.kind of tyOpenArray, tyVarargs: result = typeRel(c, base(f), base(a)) diff --git a/compiler/transf.nim b/compiler/transf.nim index 7922acbe9..fb5e321b6 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -166,6 +166,7 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = idNodeTablePut(c.transCon.mapping, it.sons[j].sym, newSymNode(newVar)) defs[j] = newSymNode(newVar).PTransNode assert(it.sons[L-2].kind == nkEmpty) + defs[L-2] = ast.emptyNode.PTransNode defs[L-1] = transform(c, it.sons[L-1]) result[i] = defs diff --git a/compiler/vm.nim b/compiler/vm.nim index b365dba9a..fb8749250 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -48,9 +48,17 @@ type # XXX 'break' should perform cleanup actions # What does the C backend do for it? -proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int) = +proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = if x != nil: - stackTraceAux(c, x.next, x.comesFrom) + if recursionLimit == 0: + var calls = 0 + var x = x + while x != nil: + inc calls + x = x.next + msgWriteln($calls & " calls omitted\n") + return + stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] # we now use the same format as in system/except.nim var s = toFilename(info) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 434cb0932..7c0c3d4f5 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1501,7 +1501,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = else: localError(n.info, errGenerated, "VM is not allowed to 'cast'") else: - internalError n.info, "too implement " & $n.kind + internalError n.info, "cannot generate VM code for " & n.renderTree proc removeLastEof(c: PCtx) = let last = c.code.len-1 diff --git a/doc/lib.txt b/doc/lib.txt index ca2539631..62efe6a5d 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -223,6 +223,34 @@ Internet Protocols and Support * `uri <uri.html>`_ This module provides functions for working with URIs. +* `asyncdispatch <asyncdispatch.html>`_ + This module implements an asynchronous dispatcher for IO operations. + + **Note:** This module is still largely experimental. + +* `asyncnet <asyncnet.html>`_ + This module implements asynchronous sockets based on the ``asyncdispatch`` + module. + + **Note:** This module is still largely experimental. + +* `asynchttpserver <asynchttpserver.html>`_ + This module implements an asynchronous HTTP server using the ``asyncnet`` + module. + + **Note:** This module is still largely experimental. + +* `net <net.html>`_ + This module implements a high-level sockets API. It will replace the + ``sockets`` module in the future. + +* `rawsockets <rawsockets.html>`_ + This module implements a low-level sockets API. + +* `selectors <selectors.html>`_ + This module implements a selector API with backends specific to each OS. + Currently epoll on Linux and select on other operating systems. + Parsers ------- diff --git a/lib/nimbase.h b/lib/nimbase.h index b16b27b2c..cfd33dca1 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -376,7 +376,7 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } # define GC_GUARD #endif -/* Test to see if nimrod and the C compiler agrees on the size of a pointer. +/* Test to see if nimrod and the C compiler agree on the size of a pointer. On disagreement, your C compiler will say something like: "error: 'assert_numbits' declared as an array with a negative size" */ typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1]; diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 84b0ebbf8..880458ee5 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -10,13 +10,22 @@ import os, oids, tables, strutils, macros import rawsockets +export TPort ## AsyncDispatch -## -------- +## ------------- ## ## This module implements a brand new dispatcher based on Futures. -## On Windows IOCP is used and on other operating systems the selectors module -## is used instead. +## On Windows IOCP is used and on other operating systems the ``selectors`` +## module is used instead. +## +## **Note:** This module is still largely experimental. + + +# TODO: Discarded void PFutures need to checked for exception. +# TODO: Exceptions are currently uncatchable due to the limitation that +# you cannot have yield in a try stmt. Perhaps I can get the macro to put +# a user's try except around ``future.read``. # -- Futures @@ -151,7 +160,7 @@ when defined(windows) or defined(nimdoc): let p = getGlobalDispatcher() if CreateIOCompletionPort(sock.THandle, p.ioPort, cast[TCompletionKey](sock), 1) == 0: - OSError(OSLastError()) + osError(osLastError()) p.handles.incl(sock) proc verifyPresence(sock: TAsyncFD) = @@ -188,7 +197,7 @@ when defined(windows) or defined(nimdoc): lpNumberOfBytesTransferred, TOSErrorCode(-1)) dealloc(customOverlapped) else: - let errCode = OSLastError() + let errCode = osLastError() if lpOverlapped != nil: assert customOverlapped.data.sock == lpCompletionKey.TAsyncFD customOverlapped.data.cb(customOverlapped.data.sock, @@ -198,7 +207,7 @@ when defined(windows) or defined(nimdoc): if errCode.int32 == WAIT_TIMEOUT: # Timed out discard - else: OSError(errCode) + else: osError(errCode) var connectExPtr: pointer = nil var acceptExPtr: pointer = nil @@ -215,11 +224,11 @@ when defined(windows) or defined(nimdoc): proc initAll() = let dummySock = newRawSocket() if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX): - OSError(OSLastError()) + osError(osLastError()) if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX): - OSError(OSLastError()) + osError(osLastError()) if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS): - OSError(OSLastError()) + osError(osLastError()) proc connectEx(s: TSocketHandle, name: ptr TSockAddr, namelen: cint, lpSendBuffer: pointer, dwSendDataLength: dword, @@ -280,7 +289,7 @@ when defined(windows) or defined(nimdoc): saddr.sin_addr.s_addr = INADDR_ANY if bindAddr(socket.TSocketHandle, cast[ptr TSockAddr](addr(saddr)), sizeof(saddr).TSockLen) < 0'i32: - OSError(OSLastError()) + osError(osLastError()) var aiList = getAddrInfo(address, port, af) var success = false @@ -311,7 +320,7 @@ when defined(windows) or defined(nimdoc): # free ``ol``. break else: - lastError = OSLastError() + lastError = osLastError() if lastError.int32 == ERROR_IO_PENDING: # In this case ``ol`` will be deallocated in ``poll``. success = true @@ -331,7 +340,7 @@ when defined(windows) or defined(nimdoc): ## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## complete once all the data requested is read, a part of the data has been ## read, or the socket has disconnected in which case the future will - ## complete with a value of ``""`. + ## complete with a value of ``""``. # Things to note: @@ -367,7 +376,7 @@ when defined(windows) or defined(nimdoc): let ret = WSARecv(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, addr flagsio, cast[POverlapped](ol), nil) if ret == -1: - let err = OSLastError() + let err = osLastError() if err.int32 != ERROR_IO_PENDING: retFuture.fail(newException(EOS, osErrorMsg(err))) dealloc(ol) @@ -391,7 +400,6 @@ when defined(windows) or defined(nimdoc): size else: bytesReceived - assert dataBuf.buf[0] != '\0' var data = newString(realSize) copyMem(addr data[0], addr dataBuf.buf[0], realSize) retFuture.complete($data) @@ -446,7 +454,7 @@ when defined(windows) or defined(nimdoc): var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]() var clientSock = newRawSocket() - if clientSock == OSInvalidSocket: osError(osLastError()) + if clientSock == osInvalidSocket: osError(osLastError()) const lpOutputLen = 1024 var lpOutputBuf = newString(lpOutputLen) @@ -656,8 +664,7 @@ else: var retFuture = newFuture[string]() var readBuffer = newString(size) - var sizeRead = 0 - + proc cb(sock: TAsyncFD): bool = result = true let res = recv(sock.TSocketHandle, addr readBuffer[0], size, @@ -670,12 +677,11 @@ else: else: result = false # We still want this callback to be called. elif res == 0: - #echo("Disconnected recv: ", sizeRead) # Disconnected retFuture.complete("") else: + readBuffer.setLen(res) retFuture.complete(readBuffer) - #echo("Recv cb result: ", result) addRead(socket, cb) return retFuture @@ -764,14 +770,24 @@ template createVar(futSymName: string, asyncProc: PNimrodNode, result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x> valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read -proc processBody(node, retFutureSym: PNimrodNode): PNimrodNode {.compileTime.} = +proc processBody(node, retFutureSym: PNimrodNode, + subtypeName: string): PNimrodNode {.compileTime.} = result = node case node.kind of nnkReturnStmt: result = newNimNode(nnkStmtList) - result.add newCall(newIdentNode("complete"), retFutureSym, - if node[0].kind == nnkEmpty: newIdentNode("result") else: node[0]) - result.add newNimNode(nnkYieldStmt).add(newNilLit()) + if node[0].kind == nnkEmpty: + if subtypeName != "void": + 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, subtypeName)) + + result.add newNimNode(nnkReturnStmt).add(newNilLit()) + return # Don't process the children of this return stmt of nnkCommand: if node[0].kind == nnkIdent and node[0].ident == !"await": case node[1].kind @@ -819,7 +835,7 @@ proc processBody(node, retFutureSym: PNimrodNode): PNimrodNode {.compileTime.} = else: discard for i in 0 .. <result.len: - result[i] = processBody(result[i], retFutureSym) + result[i] = processBody(result[i], retFutureSym, subtypeName) #echo(treeRepr(result)) proc getName(node: PNimrodNode): string {.compileTime.} = @@ -867,7 +883,7 @@ macro async*(prc: stmt): stmt {.immediate.} = # -> <proc_body> # -> complete(retFuture, result) var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") - var procBody = prc[6].processBody(retFutureSym) + var procBody = prc[6].processBody(retFutureSym, subtypeName) if subtypeName != "void": procBody.insert(0, newNimNode(nnkVarSection).add( newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T @@ -906,7 +922,7 @@ macro async*(prc: stmt): stmt {.immediate.} = result[6] = outerProcBody - echo(toStrLit(result)) + #echo(toStrLit(result)) proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim new file mode 100644 index 000000000..74b044e05 --- /dev/null +++ b/lib/pure/asynchttpserver.nim @@ -0,0 +1,179 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a high performance asynchronous HTTP server. +## +## **Note:** This module is still largely experimental. + +import strtabs, asyncnet, asyncdispatch, parseutils, parseurl, strutils +type + TRequest* = object + client: PAsyncSocket # TODO: Separate this into a Response object? + reqMethod*: string + headers*: PStringTable + protocol*: tuple[orig: string, major, minor: int] + url*: TURL + hostname*: string ## The hostname of the client that made the request. + + PAsyncHttpServer* = ref object + socket: PAsyncSocket + + THttpCode* = enum + Http200 = "200 OK", + Http303 = "303 Moved", + Http400 = "400 Bad Request", + Http404 = "404 Not Found", + Http500 = "500 Internal Server Error", + Http502 = "502 Bad Gateway" + + THttpVersion* = enum + HttpVer11, + HttpVer10 + +proc `==`*(protocol: tuple[orig: string, major, minor: int], + ver: THttpVersion): bool = + let major = + case ver + of HttpVer11, HttpVer10: 1 + let minor = + case ver + of HttpVer11: 1 + of HttpVer10: 0 + result = protocol.major == major and protocol.minor == minor + +proc newAsyncHttpServer*(): PAsyncHttpServer = + new result + +proc sendHeaders*(req: TRequest, headers: PStringTable) {.async.} = + ## Sends the specified headers to the requesting client. + for k, v in headers: + await req.client.send(k & ": " & v & "\c\L") + +proc respond*(req: TRequest, code: THttpCode, + content: string, headers: PStringTable = newStringTable()) {.async.} = + ## Responds to the request with the specified ``HttpCode``, headers and + ## content. + ## + ## This procedure will **not** close the client socket. + var customHeaders = headers + customHeaders["Content-Length"] = $content.len + await req.client.send("HTTP/1.1 " & $code & "\c\L") + await sendHeaders(req, headers) + await req.client.send("\c\L" & content) + +proc newRequest(): TRequest = + result.headers = newStringTable(modeCaseInsensitive) + +proc parseHeader(line: string): tuple[key, value: string] = + var i = 0 + i = line.parseUntil(result.key, ':') + inc(i) # skip : + i += line.skipWhiteSpace(i) + i += line.parseUntil(result.value, {'\c', '\L'}, i) + +proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = + var i = protocol.skipIgnoreCase("HTTP/") + if i != 5: + raise newException(EInvalidValue, "Invalid request protocol. Got: " & + protocol) + result.orig = protocol + i.inc protocol.parseInt(result.major, i) + i.inc # Skip . + i.inc protocol.parseInt(result.minor, i) + +proc processClient(client: PAsyncSocket, address: string, + callback: proc (request: TRequest): PFuture[void]) {.async.} = + # GET /path HTTP/1.1 + # Header: val + # \n + + var request = newRequest() + # First line - GET /path HTTP/1.1 + let line = await client.recvLine() # TODO: Timeouts. + if line == "": + client.close() + return + let lineParts = line.split(' ') + if lineParts.len != 3: + request.respond(Http400, "Invalid request. Got: " & line) + + let reqMethod = lineParts[0] + let path = lineParts[1] + let protocol = lineParts[2] + + # Headers + var i = 0 + while true: + i = 0 + let headerLine = await client.recvLine() + if headerLine == "": + client.close(); return + if headerLine == "\c\L": break + # TODO: Compiler crash + #let (key, value) = parseHeader(headerLine) + let kv = parseHeader(headerLine) + request.headers[kv.key] = kv.value + + request.reqMethod = reqMethod + request.url = parseUrl(path) + try: + request.protocol = protocol.parseProtocol() + except EInvalidValue: + request.respond(Http400, "Invalid request protocol. Got: " & protocol) + return + request.hostname = address + request.client = client + + case reqMethod.normalize + of "get": + await callback(request) + else: + echo(reqMethod.repr) + echo(line.repr) + request.respond(Http400, "Invalid request method. Got: " & reqMethod) + + # Persistent connections + if (request.protocol == HttpVer11 and + request.headers["connection"].normalize != "close") or + (request.protocol == HttpVer10 and + request.headers["connection"].normalize == "keep-alive"): + # In HTTP 1.1 we assume that connection is persistent. Unless connection + # header states otherwise. + # In HTTP 1.0 we assume that the connection should not be persistent. + # Unless the connection header states otherwise. + await processClient(client, address, callback) + else: + request.client.close() + +proc serve*(server: PAsyncHttpServer, port: TPort, + callback: proc (request: TRequest): PFuture[void], + address = "") {.async.} = + ## Starts the process of listening for incoming HTTP connections on the + ## specified address and port. + ## + ## When a request is made by a client the specified callback will be called. + server.socket = newAsyncSocket() + server.socket.bindAddr(port, address) + server.socket.listen() + + while true: + # TODO: Causes compiler crash. + #var (address, client) = await server.socket.acceptAddr() + var fut = await server.socket.acceptAddr() + processClient(fut.client, fut.address, callback) + +when isMainModule: + var server = newAsyncHttpServer() + proc cb(req: TRequest) {.async.} = + #echo(req.reqMethod, " ", req.url) + #echo(req.headers) + await req.respond(Http200, "Hello World") + + server.serve(TPort(5555), cb) + runForever() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 748566aaa..daa6c8839 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -6,6 +6,44 @@ # See the file "copying.txt", included in this # distribution, for details about the copyright. # + +## This module implements a high-level asynchronous sockets API based on the +## asynchronous dispatcher defined in the ``asyncdispatch`` module. +## +## Example +## ======= +## +## The following example demonstrates a simple chat server. +## +## .. code-block::nimrod +## +## import asyncnet, asyncdispatch +## +## var clients: seq[PAsyncSocket] = @[] +## +## proc processClient(client: PAsyncSocket) {.async.} = +## while true: +## let line = await client.recvLine() +## for c in clients: +## await c.send(line & "\c\L") +## +## proc serve() {.async.} = +## var server = newAsyncSocket() +## server.bindAddr(TPort(12345)) +## server.listen() +## +## while true: +## let client = await server.accept() +## clients.add client +## +## processClient(client) +## +## serve() +## runForever() +## +## +## **Note:** This module is still largely experimental. + import asyncdispatch import rawsockets import net diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 68adf5f49..2a145eb89 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -430,20 +430,30 @@ proc generateHeaders(r: TURL, httpMethod: THttpMethod, add(result, "\c\L") type - PAsyncHttpClient = ref object + PAsyncHttpClient* = ref object socket: PAsyncSocket connected: bool currentURL: TURL ## Where we are currently connected. headers: PStringTable + maxRedirects: int userAgent: string -proc newAsyncHttpClient*(): PAsyncHttpClient = +proc newAsyncHttpClient*(userAgent = defUserAgent, + maxRedirects = 5): PAsyncHttpClient = + ## Creates a new PAsyncHttpClient instance. + ## + ## ``userAgent`` specifies the user agent that will be used when making + ## requests. + ## + ## ``maxRedirects`` specifies the maximum amount of redirects to follow, + ## default is 5. new result result.headers = newStringTable(modeCaseInsensitive) result.userAgent = defUserAgent + result.maxRedirects = maxRedirects proc close*(client: PAsyncHttpClient) = - ## Closes any connections held by the HttpClient. + ## Closes any connections held by the HTTP client. if client.connected: client.socket.close() client.connected = false @@ -453,7 +463,9 @@ proc recvFull(socket: PAsyncSocket, size: int): PFuture[string] {.async.} = result = "" while true: if size == result.len: break - result.add await socket.recv(size - result.len) + let data = await socket.recv(size - result.len) + if data == "": break # We've been disconnected. + result.add data proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} = result = "" @@ -534,9 +546,6 @@ proc parseResponse(client: PAsyncHttpClient, # Parse HTTP version info and status code. var le = skipIgnoreCase(line, "HTTP/", linei) if le <= 0: - while true: - let nl = await client.socket.recvLine() - echo("Got another line: ", nl) httpError("invalid http version, " & line.repr) inc(linei, le) le = skipIgnoreCase(line, "1.1", linei) @@ -586,6 +595,14 @@ proc newConnection(client: PAsyncHttpClient, url: TURL) {.async.} = proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET, body = ""): PFuture[TResponse] {.async.} = + ## Connects to the hostname specified by the URL and performs a request + ## using the method specified. + ## + ## Connection will kept alive. Further requests on the same ``client`` to + ## the same hostname will not require a new connection to be made. The + ## connection can be closed by using the ``close`` procedure. + ## + ## The returned future will complete once the request is completed. let r = parseUrl(url) await newConnection(client, r) @@ -600,6 +617,19 @@ proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET, result = await parseResponse(client, httpMethod != httpHEAD) +proc get*(client: PAsyncHttpClient, url: string): PFuture[TResponse] {.async.} = + ## Connects to the hostname specified by the URL and performs a GET request. + ## + ## This procedure will follow redirects up to a maximum number of redirects + ## specified in ``newAsyncHttpClient``. + result = await client.request(url, httpGET) + var lastURL = url + for i in 1..client.maxRedirects: + if result.status.redirection(): + let redirectTo = getNewLocation(lastURL, result.headers) + result = await client.request(redirectTo, httpGET) + lastUrl = redirectTo + when isMainModule: when true: # Async diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim index c1b519b0b..83fb231f6 100644 --- a/lib/pure/irc.nim +++ b/lib/pure/irc.nim @@ -346,6 +346,7 @@ proc poll*(irc: PIRC, ev: var TIRCEvent, var line = TaintedString"" var socks = @[irc.sock] var ret = socks.select(timeout) + if ret == -1: osError(osLastError()) if socks.len() != 0 and ret != 0: irc.sock.readLine(line) ev = irc.processLine(line.string) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 94570fc68..d258e9a7c 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -75,28 +75,30 @@ proc binom*(n, k: int): int {.noSideEffect.} = result = (result * (n + 1 - i)) div i proc fac*(n: int): int {.noSideEffect.} = - ## computes the faculty function + ## computes the faculty/factorial function. result = 1 for i in countup(2, n): result = result * i proc isPowerOfTwo*(x: int): bool {.noSideEffect.} = - ## returns true, if x is a power of two, false otherwise. - ## Negative numbers are not a power of two. - return (x and -x) == x - -proc nextPowerOfTwo*(x: int): int = - ## returns the nearest power of two, so that - ## result**2 >= x > (result-1)**2. - result = x - 1 + ## returns true, if `x` is a power of two, false otherwise. + ## Zero and negative numbers are not a power of two. + return (x != 0) and ((x and (x - 1)) == 0) + +proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = + ## returns `x` rounded up to the nearest power of two. + ## Zero and negative numbers get rounded up to 1. + result = x - 1 when defined(cpu64): result = result or (result shr 32) - result = result or (result shr 16) - result = result or (result shr 8) + when sizeof(int) > 16: + result = result or (result shr 16) + when sizeof(int) > 8: + result = result or (result shr 8) result = result or (result shr 4) result = result or (result shr 2) result = result or (result shr 1) - inc(result) + result += 1 + ord(x<=0) proc countBits32*(n: int32): int {.noSideEffect.} = ## counts the set bits in `n`. diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 4afb5c6ab..74739630b 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -12,6 +12,9 @@ {.deadCodeElim: on.} import rawsockets, os, strutils, unsigned, parseutils, times export TPort + +const useWinVersion = defined(Windows) or defined(nimdoc) + type IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address IPv6, ## IPv6 address @@ -499,7 +502,7 @@ proc socketError*(socket: PSocket, err: int = -1, async = false) = if err == -1 and not (when defined(ssl): socket.isSSL else: false): let lastError = osLastError() if async: - when defined(windows): + when useWinVersion: if lastError.int32 == WSAEWOULDBLOCK: return else: osError(lastError) @@ -525,7 +528,7 @@ proc bindAddr*(socket: PSocket, port = TPort(0), address = "") {. if address == "": var name: TSockaddr_in - when defined(windows): + when useWinVersion: name.sin_family = toInt(AF_INET).int16 else: name.sin_family = toInt(AF_INET) @@ -1009,7 +1012,7 @@ proc send*(socket: PSocket, data: pointer, size: int): int {. if socket.isSSL: return SSLWrite(socket.sslHandle, cast[cstring](data), size) - when defined(windows) or defined(macosx): + when useWinVersion or defined(macosx): result = send(socket.fd, data, size.cint, 0'i32) else: when defined(solaris): @@ -1088,7 +1091,7 @@ proc connectAsync(socket: PSocket, name: string, port = TPort(0), break else: lastError = osLastError() - when defined(windows): + when useWinVersion: # Windows EINTR doesn't behave same as POSIX. if lastError.int32 == WSAEWOULDBLOCK: success = true diff --git a/lib/pure/parseurl.nim b/lib/pure/parseurl.nim index 937f26f6f..357d1df0f 100644 --- a/lib/pure/parseurl.nim +++ b/lib/pure/parseurl.nim @@ -1,13 +1,16 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2010 Dominik Picheta +# (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Parses & constructs URLs. +## +## **Note**: This module will be deprecated in the future and merged into a +## new ``url`` module. import strutils diff --git a/lib/pure/rawsockets.nim b/lib/pure/rawsockets.nim index aeaa7f3b5..07b647b68 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/rawsockets.nim @@ -17,7 +17,9 @@ import unsigned, os when hostos == "solaris": {.passl: "-lsocket -lnsl".} -when defined(Windows): +const useWinVersion = defined(Windows) or defined(nimdoc) + +when useWinVersion: import winlean export WSAEWOULDBLOCK else: @@ -74,7 +76,7 @@ type length*: int addrList*: seq[string] -when defined(windows): +when useWinVersion: let osInvalidSocket* = winlean.INVALID_SOCKET @@ -106,7 +108,7 @@ proc toInt*(typ: TType): cint proc toInt*(p: TProtocol): cint ## Converts the TProtocol enum to a platform-dependent ``cint``. -when defined(posix): +when not useWinVersion: proc toInt(domain: TDomain): cint = case domain of AF_UNIX: result = posix.AF_UNIX @@ -150,7 +152,7 @@ proc newRawSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, proc close*(socket: TSocketHandle) = ## closes a socket. - when defined(windows): + when useWinVersion: discard winlean.closeSocket(socket) else: discard posix.close(socket) @@ -164,7 +166,7 @@ proc listen*(socket: TSocketHandle, backlog = SOMAXCONN): cint {.tags: [FReadIO] ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. - when defined(windows): + when useWinVersion: result = winlean.listen(socket, cint(backlog)) else: result = posix.listen(socket, cint(backlog)) @@ -181,8 +183,8 @@ proc getAddrInfo*(address: string, port: TPort, af: TDomain = AF_INET, typ: TTyp hints.ai_protocol = toInt(prot) var gaiResult = getAddrInfo(address, $port, addr(hints), result) if gaiResult != 0'i32: - when defined(windows): - OSError(OSLastError()) + when useWinVersion: + osError(osLastError()) else: raise newException(EOS, $gai_strerror(gaiResult)) @@ -224,7 +226,7 @@ proc getServByName*(name, proto: string): TServent {.tags: [FReadIO].} = ## and the protocol name specified by ``proto`` matches the s_proto member. ## ## On posix this will search through the ``/etc/services`` file. - when defined(Windows): + when useWinVersion: var s = winlean.getservbyname(name, proto) else: var s = posix.getservbyname(name, proto) @@ -240,7 +242,7 @@ proc getServByPort*(port: TPort, proto: string): TServent {.tags: [FReadIO].} = ## protocol name specified by ``proto`` matches the s_proto member. ## ## On posix this will search through the ``/etc/services`` file. - when defined(Windows): + when useWinVersion: var s = winlean.getservbyport(ze(int16(port)).cint, proto) else: var s = posix.getservbyport(ze(int16(port)).cint, proto) @@ -255,7 +257,7 @@ proc getHostByAddr*(ip: string): Thostent {.tags: [FReadIO].} = var myaddr: TInAddr myaddr.s_addr = inet_addr(ip) - when defined(windows): + when useWinVersion: var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, cint(rawsockets.AF_INET)) if s == nil: osError(osLastError()) @@ -267,7 +269,7 @@ proc getHostByAddr*(ip: string): Thostent {.tags: [FReadIO].} = result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) - when defined(windows): + when useWinVersion: result.addrtype = TDomain(s.h_addrtype) else: if s.h_addrtype == posix.AF_INET: @@ -281,14 +283,14 @@ proc getHostByAddr*(ip: string): Thostent {.tags: [FReadIO].} = proc getHostByName*(name: string): Thostent {.tags: [FReadIO].} = ## This function will lookup the IP address of a hostname. - when defined(Windows): + when useWinVersion: var s = winlean.gethostbyname(name) else: var s = posix.gethostbyname(name) if s == nil: osError(osLastError()) result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) - when defined(windows): + when useWinVersion: result.addrtype = TDomain(s.h_addrtype) else: if s.h_addrtype == posix.AF_INET: @@ -303,7 +305,7 @@ proc getHostByName*(name: string): Thostent {.tags: [FReadIO].} = proc getSockName*(socket: TSocketHandle): TPort = ## returns the socket's associated port number. var name: Tsockaddr_in - when defined(Windows): + when useWinVersion: name.sin_family = int16(ord(AF_INET)) else: name.sin_family = posix.AF_INET @@ -337,7 +339,7 @@ proc setBlocking*(s: TSocketHandle, blocking: bool) = ## Sets blocking mode on socket. ## ## Raises EOS on error. - when defined(Windows): + when useWinVersion: var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking if ioctlsocket(s, FIONBIO, addr(mode)) == -1: osError(osLastError()) @@ -418,4 +420,4 @@ proc selectWrite*(writefds: var seq[TSocketHandle], when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) + if WSAStartup(0x0101'i16, addr wsa) != 0: osError(osLastError()) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 8171dc12b..f4c45b99c 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -45,7 +45,7 @@ proc raiseInvalidReply(expected, got: char) = [$expected, $got]) proc raiseNoOK(status: string) = - if status != "OK": + if status != "QUEUED" and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) proc parseStatus(r: TRedis): TRedisStatus = @@ -64,6 +64,10 @@ proc parseStatus(r: TRedis): TRedisStatus = proc parseInteger(r: TRedis): TRedisInteger = var line = "" r.socket.readLine(line) + + if line == "+QUEUED": # inside of multi + return -1 + if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -81,10 +85,7 @@ proc recv(sock: TSocket, size: int): TaintedString = if sock.recv(cstring(result), size) != size: raise newException(EInvalidReply, "recv failed") -proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = - var line = "" - r.socket.readLine(line.TaintedString) - +proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = # Error. if line[0] == '-': raise newException(ERedis, strip(line)) @@ -94,6 +95,9 @@ proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = if line == "*-1": return RedisNil + if line == "+QUEUED" or line == "+OK" : # inside of a transaction (multi) + return nil + if line[0] != '$': raiseInvalidReply('$', line[0]) @@ -104,18 +108,41 @@ proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = var s = r.socket.recv(numBytes+2) result = strip(s.string) +proc parseMultiLines(r: TRedis, countLine:string): TRedisList = + if countLine.string[0] != '*': + raiseInvalidReply('*', countLine.string[0]) + + var numElems = parseInt(countLine.string.substr(1)) + if numElems == -1: return nil + result = @[] + for i in 1..numElems: + var line = "" + r.socket.readLine(line.TaintedString) + if line[0] == '*': # after exec() may contain more multi-bulk replies + var parsed = r.parseMultiLines(line) + for item in parsed: + result.add(item) + else: + result.add(r.parseSingle(line)) + +proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = + var line = "" + r.socket.readLine(line.TaintedString) + + if line == "+QUEUED" or line == "+OK": # inside of a transaction (multi) + return nil + + return r.parseSingle(line, allowMBNil) + proc parseMultiBulk(r: TRedis): TRedisList = var line = TaintedString"" r.socket.readLine(line) + + if line == "+QUEUED": # inside of a transaction (multi) + return nil - if line.string[0] != '*': - raiseInvalidReply('*', line.string[0]) - - var numElems = parseInt(line.string.substr(1)) - if numElems == -1: return nil - result = @[] - for i in 1..numElems: - result.add(r.parseBulk()) + return r.parseMultiLines(line) + proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = var request = "*" & $(1 + args.len()) & "\c\L" @@ -722,6 +749,7 @@ proc discardMulti*(r: TRedis) = proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI r.sendCommand("EXEC") + return r.parseMultiBulk() proc multi*(r: TRedis) = diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 085344e3e..a4a7b5afd 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -13,6 +13,7 @@ import tables, os, unsigned, hashes when defined(linux): import posix, epoll elif defined(windows): import winlean +else: import posix proc hash*(x: TSocketHandle): THash {.borrow.} proc `$`*(x: TSocketHandle): string {.borrow.} @@ -126,19 +127,12 @@ when defined(linux) or defined(nimdoc): else: return false - proc contains*(s: PSelector, key: PSelectorKey): bool = - ## Determines whether selector contains this selector key. More accurate - ## than checking if the file descriptor is in the selector because it - ## ensures that the keys are equal. File descriptors may not always be - ## unique especially when an fd is closed and then a new one is opened, - ## the new one may have the same value. - return key.fd in s and s.fds[key.fd] == key - proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = ## Retrieves the selector key for ``fd``. return s.fds[fd] -elif defined(windows): +else: + # TODO: kqueue for bsd/mac os x. type PSelector* = ref object fds: TTable[TSocketHandle, PSelectorKey] @@ -204,9 +198,9 @@ elif defined(windows): var retCode = 0 if timeout != -1: - retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, addr(tv))) + retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) else: - retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, nil)) + retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) if retCode < 0: OSError(OSLastError()) @@ -228,11 +222,13 @@ elif defined(windows): proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = return s.fds[fd] -elif defined(bsd) or defined(macosx): - # TODO: kqueue - {.error: "Sorry your platform is not supported yet.".} -else: - {.error: "Sorry your platform is not supported.".} +proc contains*(s: PSelector, key: PSelectorKey): bool = + ## Determines whether selector contains this selector key. More accurate + ## than checking if the file descriptor is in the selector because it + ## ensures that the keys are equal. File descriptors may not always be + ## unique especially when an fd is closed and then a new one is opened, + ## the new one may have the same value. + return key.fd in s and s.fds[key.fd] == key when isMainModule: # Select() @@ -242,7 +238,7 @@ when isMainModule: sock: TSocket var sock = socket() - sock.setBlocking(false) + #sock.setBlocking(false) sock.connect("irc.freenode.net", TPort(6667)) var selector = newSelector() @@ -258,12 +254,3 @@ when isMainModule: assert selector.unregister(sock.getFD).fd == sock.getFD selector.close() break - - - - - - - - - diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 76d37879b..b6acc329f 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -925,11 +925,12 @@ proc createFdSet(fd: var TFdSet, s: seq[TSocket], m: var int) = m = max(m, int(i.fd)) FD_SET(i.fd, fd) -proc pruneSocketSet(s: var seq[TSocket], fd: var TFdSet) = +proc pruneSocketSet(s: var seq[TSocket], fd: var TFdSet) = var i = 0 var L = s.len while i < L: if FD_ISSET(s[i].fd, fd) == 0'i32: + # not set. s[i] = s[L-1] dec(L) else: @@ -954,9 +955,9 @@ proc checkBuffer(readfds: var seq[TSocket]): int = for s in readfds: if hasDataBuffered(s): inc(result) - else: res.add(s) - readfds = res + if result > 0: + readfds = res proc select*(readfds, writefds, exceptfds: var seq[TSocket], timeout = 500): int {.tags: [FReadIO].} = @@ -965,8 +966,9 @@ proc select*(readfds, writefds, exceptfds: var seq[TSocket], ## If there are none; 0 is returned. ## ``Timeout`` is in miliseconds and -1 can be specified for no timeout. ## - ## A socket is removed from the specific ``seq`` when it has data waiting to - ## be read/written to or has errors (``exceptfds``). + ## Sockets which are **not** ready for reading, writing or which don't have + ## errors waiting on them are removed from the ``readfds``, ``writefds``, + ## ``exceptfds`` sequences respectively. let buffersFilled = checkBuffer(readfds) if buffersFilled > 0: return buffersFilled @@ -1013,7 +1015,7 @@ proc selectWrite*(writefds: var seq[TSocket], timeout = 500): int {.tags: [FReadIO].} = ## When a socket in ``writefds`` is ready to be written to then a non-zero ## value will be returned specifying the count of the sockets which can be - ## written to. The sockets which can be written to will also be removed + ## written to. The sockets which **cannot** be written to will also be removed ## from ``writefds``. ## ## ``timeout`` is specified in miliseconds and ``-1`` can be specified for diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 18afd4af6..ee1226a35 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -1,11 +1,15 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Dominik Picheta +# (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # + +## **Note**: This module will be deprecated in the future and merged into a +## new ``url`` module. + import strutils type TUrl* = distinct string @@ -30,4 +34,4 @@ proc add*(url: var TUrl, a: TUrl) = url = url / a when isMainModule: - assert($("http://".TUrl / "localhost:5000".TUrl) == "http://localhost:5000") \ No newline at end of file + assert($("http://".TUrl / "localhost:5000".TUrl) == "http://localhost:5000") diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 56e6a9e5f..02c17b92b 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -176,9 +176,10 @@ proc rawEchoNL() {.inline, compilerproc.} = write(stdout, "\n") # interface to the C procs: -when defined(windows) and not defined(useWinAnsi): +when (defined(windows) and not defined(useWinAnsi)) or defined(nimdoc): include "system/widestrs" - + +when defined(windows) and not defined(useWinAnsi): proc wfopen(filename, mode: WideCString): pointer {. importc: "_wfopen", nodecl.} proc wfreopen(filename, mode: WideCString, stream: TFile): TFile {. diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 4d87cf4b2..69a3c5c81 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -657,7 +657,12 @@ type D4*: array [0..7, int8] const - ERROR_IO_PENDING* = 997 + ERROR_IO_PENDING* = 997 # a.k.a WSA_IO_PENDING + WSAECONNABORTED* = 10053 + WSAECONNRESET* = 10054 + WSAEDISCON* = 10101 + WSAENETRESET* = 10052 + WSAETIMEDOUT* = 10060 proc CreateIoCompletionPort*(FileHandle: THANDLE, ExistingCompletionPort: THANDLE, CompletionKey: DWORD, diff --git a/tests/assert/tfailedassert.nim b/tests/assert/tfailedassert.nim index 263893767..4994e13c8 100644 --- a/tests/assert/tfailedassert.nim +++ b/tests/assert/tfailedassert.nim @@ -3,7 +3,7 @@ discard """ WARNING: false first asseertion from bar ERROR: false second assertion from bar -1 -tests/assert/tfailedassert.nim:27 false assertion from foo +tfailedassert.nim:27 false assertion from foo ''' """ diff --git a/tests/patterns/targlist.nim b/tests/trmacros/targlist.nim index e416edf0a..e416edf0a 100644 --- a/tests/patterns/targlist.nim +++ b/tests/trmacros/targlist.nim diff --git a/tests/patterns/tcse.nim b/tests/trmacros/tcse.nim index ff04f7d83..ff04f7d83 100644 --- a/tests/patterns/tcse.nim +++ b/tests/trmacros/tcse.nim diff --git a/tests/patterns/thoist.nim b/tests/trmacros/thoist.nim index 7d14c0abf..7d14c0abf 100644 --- a/tests/patterns/thoist.nim +++ b/tests/trmacros/thoist.nim diff --git a/tests/patterns/tmatrix.nim b/tests/trmacros/tmatrix.nim index c825a7792..c825a7792 100644 --- a/tests/patterns/tmatrix.nim +++ b/tests/trmacros/tmatrix.nim diff --git a/tests/patterns/tnoalias.nim b/tests/trmacros/tnoalias.nim index 1d5671362..1d5671362 100644 --- a/tests/patterns/tnoalias.nim +++ b/tests/trmacros/tnoalias.nim diff --git a/tests/patterns/tnoendlessrec.nim b/tests/trmacros/tnoendlessrec.nim index 53891bcc0..53891bcc0 100644 --- a/tests/patterns/tnoendlessrec.nim +++ b/tests/trmacros/tnoendlessrec.nim diff --git a/tests/patterns/tor.nim b/tests/trmacros/tor.nim index 833418919..dc72a96cd 100644 --- a/tests/patterns/tor.nim +++ b/tests/trmacros/tor.nim @@ -1,6 +1,7 @@ discard """ output: '''3060 -true''' +true +3''' """ template arithOps: expr = (`+` | `-` | `*`) @@ -19,3 +20,9 @@ var c = false a = b and a echo a + +# bug #798 +template t012{(0|1|2){x}}(x: expr): expr = x+1 +let z = 1 +# outputs 3 thanks to fixpoint iteration: +echo z diff --git a/tests/patterns/tpartial.nim b/tests/trmacros/tpartial.nim index fdaa3414a..fdaa3414a 100644 --- a/tests/patterns/tpartial.nim +++ b/tests/trmacros/tpartial.nim diff --git a/tests/patterns/tpatterns.nim b/tests/trmacros/tpatterns.nim index 6bc8772e3..6bc8772e3 100644 --- a/tests/patterns/tpatterns.nim +++ b/tests/trmacros/tpatterns.nim diff --git a/tests/patterns/tstar.nim b/tests/trmacros/tstar.nim index 8443268f4..8443268f4 100644 --- a/tests/patterns/tstar.nim +++ b/tests/trmacros/tstar.nim diff --git a/tests/patterns/tstmtlist.nim b/tests/trmacros/tstmtlist.nim index 20cb5d688..20cb5d688 100644 --- a/tests/patterns/tstmtlist.nim +++ b/tests/trmacros/tstmtlist.nim diff --git a/tests/typerel/texplicitcmp.nim b/tests/typerel/texplicitcmp.nim new file mode 100644 index 000000000..8aec9885a --- /dev/null +++ b/tests/typerel/texplicitcmp.nim @@ -0,0 +1,32 @@ +discard """ + output: '''[1 2 3 ] +[1 2 3 ] +[1 2 3 ]''' +""" + +# bug #297 + +import json, tables, algorithm + +proc outp(a: openarray[int]) = + stdout.write "[" + for i in a: stdout.write($i & " ") + stdout.write "]\n" + +proc works() = + var f = @[3, 2, 1] + sort(f, system.cmp[int]) + outp(f) + +proc weird(json_params: TTable) = + var f = @[3, 2, 1] + # The following line doesn't compile: type mismatch. Why? + sort(f, system.cmp[int]) + outp(f) + +when isMainModule: + var t = @[3, 2, 1] + sort(t, system.cmp[int]) + outp(t) + works() + weird(initTable[string, TJsonNode]()) diff --git a/tests/typerel/tvarargsexpr.nim b/tests/typerel/tvarargsexpr.nim new file mode 100644 index 000000000..fcb49af61 --- /dev/null +++ b/tests/typerel/tvarargsexpr.nim @@ -0,0 +1,18 @@ +discard """ + output: '''success''' +""" + +#bug #913 + +import macros + +macro thirteen(args: varargs[expr]): expr = + result = newIntLitNode(13) + +doAssert(13==thirteen([1,2])) # works +doAssert(13==thirteen(1,2)) # works + +doAssert(13==thirteen(1,[2])) # does not work +doAssert(13==thirteen([1], 2)) # does not work + +echo "success" diff --git a/tests/vm/tarrayboundeval.nim b/tests/vm/tarrayboundeval.nim new file mode 100644 index 000000000..9b33a2415 --- /dev/null +++ b/tests/vm/tarrayboundeval.nim @@ -0,0 +1,23 @@ +discard """ + output: '''7 +8 8''' +""" + +#bug 1063 + +const + KeyMax = 227 + myconst = int((KeyMax + 31) / 32) + +type + FU = array[int((KeyMax + 31) / 32), cuint] + +echo FU.high + +type + PKeyboard* = ptr object + TKeyboardState* = object + display*: pointer + internal: array[int((KeyMax + 31)/32), cuint] + +echo myconst, " ", int((KeyMax + 31) / 32) diff --git a/web/news.txt b/web/news.txt index 9c84b5490..4aaf10992 100644 --- a/web/news.txt +++ b/web/news.txt @@ -3,95 +3,155 @@ News ==== -.. - 2014-XX-XX Version 0.9.4 released - ================================= - - - Bugfixes - -------- - - - Library Additions - ----------------- - - - Added ``macros.genSym`` builtin for AST generation. - - Added ``macros.newLit`` procs for easier AST generation. - - - Changes affecting backwards compatibility - ----------------------------------------- - - - The scoping rules for the ``if`` statement changed for better interaction - with the new syntactic construct ``(;)``. - - ``OSError`` family of procedures has been deprecated. Procedures with the same - name but which take different parameters have been introduced. These procs now - require an error code to be passed to them. This error code can be retrieved - using the new ``OSLastError`` proc. - - ``os.parentDir`` now returns "" if there is no parent dir. - - In CGI scripts stacktraces are shown to the user only - if ``cgi.setStackTraceStdout`` is used. - - The symbol binding rules for clean templates changed: ``bind`` for any - symbol that's not a parameter is now the default. ``mixin`` can be used - to require instantiation scope for a symbol. - - ``quoteIfContainsWhite`` now escapes argument in such way that it can be safely - passed to shell, instead of just adding double quotes. - - ``macros.dumpTree`` and ``macros.dumpLisp`` have been made ``immediate``, - ``dumpTreeImm`` and ``dumpLispImm`` are now deprecated. - - The ``nil`` statement has been deprecated, use an empty ``discard`` instead. - - ``sockets.select`` now prunes sockets that are **not** ready from the list - of sockets given to it. - - The ``noStackFrame`` pragma has been renamed to ``asmNoStackFrame`` to - ensure you only use it when you know what you're doing. - - - Compiler Additions - ------------------ - - - The compiler can now warn about "uninitialized" variables. (There are no - real uninitialized variables in Nimrod as they are initialized to binary - zero). Activate via ``{.warning[Uninit]:on.}``. - - The compiler now enforces the ``not nil`` constraint. - - The compiler now supports a ``codegenDecl`` pragma for even more control - over the generated code. - - The compiler now supports a ``computedGoto`` pragma to support very fast - dispatching for interpreters and the like. - - The old evaluation engine has been replaced by a proper register based - virtual machine. This fixes numerous bugs for ``nimrod i`` and for macro - evaluation. - - ``--gc:none`` produces warnings when code uses the GC. - - A ``union`` pragma for better C interoperability is now supported. - - A ``packed`` pragma to control the memory packing/alignment of fields in - an object. - - Arrays can be annotated to be ``unchecked`` for easier low level - manipulations of memory. - - - Language Additions - ------------------ - - - Arrays can now be declared with a single integer literal ``N`` instead of a - range; the range is then ``0..N-1``. - - Added ``requiresInit`` pragma to enforce explicit initialization. - - Exported templates are allowed to access hidden fields. - - The ``using statement`` enables you to more easily author domain-specific - languages and libraries providing OOP-like syntactic sugar. - - Added the possibility to override various dot operators in order to handle - calls to missing procs and reads from undeclared fields at compile-time. - - The overload resolution now supports ``static[T]`` params that must be - evaluable at compile-time. - - Support for user-defined type classes has been added. - - The *command syntax* is supported in a lot more contexts. - - Anonymous iterators are now supported and iterators can capture variables - of an outer proc. - - The experimental ``strongSpaces`` parsing mode has been implemented. - - - Tools improvements - ------------------ - - - c2nim can deal with a subset of C++. Use the ``--cpp`` command line option - to activate. + +2014-XX-XX Version 0.9.4 released +================================= + +The Nimrod development community is proud to announce the release of version +0.9.4 of the Nimrod compiler and tools. + +This release includes about 1300 changes in total including various bug +fixes, new languages features and standard library additions and improvements. +This release brings with it support for user-defined type classes, a brand +new VM for executing Nimrod code at compile-time and new symbol binding +rules for clean templates. +It also introduces support for the brand new +`Babel package manager <https://github.com/nimrod-code/babel>`_ which +has itself seen its first release recently. Many of the wrappers that were +present in the standard library have been moved to separate repositories +and should now be installed using Babel. + +Apart from that a new **experimental** Asynchronous IO API has been added via +the ``asyncdispatch`` and ``asyncnet`` modules. The ``net`` and ``rawsockets`` +modules have also been added and they will likely replace the sockets +module in the next release. The Asynchronous IO API has been designed to +take advantage of Linux's epoll and Windows' IOCP APIs, support for BSD's +kqueue has not been implemented yet but will be in the future. +The Asynchronous IO API provides both +a callback interface and an interface which allows you to write code as you +would if you were writing synchronous code. The latter is done through +the use of an ``await`` macro which behaves similar to C#'s await. The +following is a very simple chat server demonstrating Nimrod's new async +capabilities. + +.. code-block::nimrod + import asyncnet, asyncdispatch + + var clients: seq[PAsyncSocket] = @[] + + proc processClient(client: PAsyncSocket) {.async.} = + while true: + let line = await client.recvLine() + for c in clients: + await c.send(line & "\c\L") + + proc serve() {.async.} = + var server = newAsyncSocket() + server.bindAddr(TPort(12345)) + server.listen() + + while true: + let client = await server.accept() + clients.add client + + processClient(client) + + serve() + runForever() + + +Note that this feature has been implemented with Nimrod's macro system and so +``await`` and ``async`` are no keywords. + + +Library Additions +----------------- + +- Added ``macros.genSym`` builtin for AST generation. +- Added ``macros.newLit`` procs for easier AST generation. +- Added module ``logging``. +- Added module ``asyncdispatch``. +- Added module ``asyncnet``. +- Added module ``net``. +- Added module ``rawsockets``. +- Added module ``selectors``. +- Added module ``asynchttpserver``. +- Added support for the new asynchronous IO in the ``httpclient`` module. + +Changes affecting backwards compatibility +----------------------------------------- + +- The scoping rules for the ``if`` statement changed for better interaction + with the new syntactic construct ``(;)``. +- ``OSError`` family of procedures has been deprecated. Procedures with the same + name but which take different parameters have been introduced. These procs now + require an error code to be passed to them. This error code can be retrieved + using the new ``OSLastError`` proc. +- ``os.parentDir`` now returns "" if there is no parent dir. +- In CGI scripts stacktraces are shown to the user only + if ``cgi.setStackTraceStdout`` is used. +- The symbol binding rules for clean templates changed: ``bind`` for any + symbol that's not a parameter is now the default. ``mixin`` can be used + to require instantiation scope for a symbol. +- ``quoteIfContainsWhite`` now escapes argument in such way that it can be safely + passed to shell, instead of just adding double quotes. +- ``macros.dumpTree`` and ``macros.dumpLisp`` have been made ``immediate``, + ``dumpTreeImm`` and ``dumpLispImm`` are now deprecated. +- The ``nil`` statement has been deprecated, use an empty ``discard`` instead. +- ``sockets.select`` now prunes sockets that are **not** ready from the list + of sockets given to it. +- The ``noStackFrame`` pragma has been renamed to ``asmNoStackFrame`` to + ensure you only use it when you know what you're doing. + + +Compiler Additions +------------------ + +- The compiler can now warn about "uninitialized" variables. (There are no + real uninitialized variables in Nimrod as they are initialized to binary + zero). Activate via ``{.warning[Uninit]:on.}``. +- The compiler now enforces the ``not nil`` constraint. +- The compiler now supports a ``codegenDecl`` pragma for even more control + over the generated code. +- The compiler now supports a ``computedGoto`` pragma to support very fast + dispatching for interpreters and the like. +- The old evaluation engine has been replaced by a proper register based + virtual machine. This fixes numerous bugs for ``nimrod i`` and for macro + evaluation. +- ``--gc:none`` produces warnings when code uses the GC. +- A ``union`` pragma for better C interoperability is now supported. +- A ``packed`` pragma to control the memory packing/alignment of fields in + an object. +- Arrays can be annotated to be ``unchecked`` for easier low level + manipulations of memory. +- Support for the new Babel package manager. + + +Language Additions +------------------ + +- Arrays can now be declared with a single integer literal ``N`` instead of a + range; the range is then ``0..N-1``. +- Added ``requiresInit`` pragma to enforce explicit initialization. +- Exported templates are allowed to access hidden fields. +- The ``using statement`` enables you to more easily author domain-specific + languages and libraries providing OOP-like syntactic sugar. +- Added the possibility to override various dot operators in order to handle + calls to missing procs and reads from undeclared fields at compile-time. +- The overload resolution now supports ``static[T]`` params that must be + evaluable at compile-time. +- Support for user-defined type classes has been added. +- The *command syntax* is supported in a lot more contexts. +- Anonymous iterators are now supported and iterators can capture variables + of an outer proc. +- The experimental ``strongSpaces`` parsing mode has been implemented. + + +Tools improvements +------------------ + +- c2nim can deal with a subset of C++. Use the ``--cpp`` command line option + to activate. 2014-02-11 Nimrod Featured in Dr. Dobb's Journal diff --git a/web/nimrod.ini b/web/nimrod.ini index 0ebc4b089..b29bcff30 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -62,7 +62,8 @@ srcdoc2: "pure/ftpclient;pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" -srcdoc2: "packages/docutils/rstgen;pure/logging" +srcdoc2: "packages/docutils/rstgen;pure/logging;pure/asyncdispatch;pure/asyncnet" +srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" |