diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 443d845f63..4576a54b5a 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -263,33 +263,33 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = - # This is called by return and break stmts. - # When jumping out of try/except/finally stmts, - # we need to pop safe points from try statements, - # execute finally-stmts, and pop exceptions - # from except stmts + # Called by return and break stmts. + # Deals with issues faced when jumping out of try/except/finally stmts, - let L = p.nestedTryStmts.len - - # danger of endless recursion! we workaround this here by a temp stack var stack: seq[PNode] - newSeq(stack, howManyTrys) - for i in countup(1, howManyTrys): - stack[i-1] = p.nestedTryStmts[L-i] - setLen(p.nestedTryStmts, L-howManyTrys) + newSeq(stack, 0) var alreadyPoppedCnt = p.inExceptBlock - for tryStmt in items(stack): + for i in countup(1, howManyTrys): + if gCmd != cmdCompileToCpp: + # Pop safe points generated by try if alreadyPoppedCnt > 0: dec alreadyPoppedCnt else: linefmt(p, cpsStmts, "#popSafePoint();$n") - # Find finally-stmts for this try-stmt - # and generate a copy of the finally stmts here + + # Pop this try-stmt of the list of nested trys + # so we don't infinite recurse on it in the next step. + var tryStmt = p.nestedTryStmts.pop + stack.add(tryStmt) + + # Find finally-stmt for this try-stmt + # and generate a copy of its sons var finallyStmt = lastSon(tryStmt) if finallyStmt.kind == nkFinally: genStmts(p, finallyStmt.sons[0]) + # push old elements again: for i in countdown(howManyTrys-1, 0): p.nestedTryStmts.add(stack[i]) @@ -304,7 +304,14 @@ proc genReturnStmt(p: BProc, t: PNode) = p.beforeRetNeeded = true genLineDir(p, t) if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0]) - blockLeaveActions(p, min(1, p.nestedTryStmts.len), p.inExceptBlock) + blockLeaveActions(p, + howManyTrys = p.nestedTryStmts.len, + howManyExcepts = p.inExceptBlock) + if (p.finallySafePoints.len > 0): + # If we're in a finally block, and we came here by exception + # consume it before we return. + var safePoint = p.finallySafePoints[p.finallySafePoints.len-1] + linefmt(p, cpsStmts, "if ($1.status != 0) #popCurrentException();$n", safePoint) lineFF(p, cpsStmts, "goto BeforeRet;$n", "br label %BeforeRet$n", []) proc genComputedGoto(p: BProc; n: PNode) = @@ -843,7 +850,9 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = discard pop(p.nestedTryStmts) endBlock(p) # end of else block if i < length and t.sons[i].kind == nkFinally: + p.finallySafePoints.add(safePoint) exprBlock(p, t.sons[i].sons[0], d) + discard pop(p.finallySafePoints) linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", safePoint) proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): PRope = diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 5057ae558e..87ed23f368 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -962,8 +962,8 @@ proc genMainProc(m: BModule) = NimMainBody = "N_CDECL(void, NimMain)(void) {$N" & "\tPreMain();$N" & - "$1$N" & - "}$N" + "$1" & + "}$N$N" PosixNimMain = "int cmdCount;$N" & @@ -977,20 +977,20 @@ proc genMainProc(m: BModule) = "\tcmdCount = argc;$N" & "\tgEnv = env;$N" & MainProcsWithResult & - "}$N" + "}$N$N" StandaloneCMain = "int main(void) {$N" & MainProcs & "\treturn 0;$N" & - "}$N" + "}$N$N" WinNimMain = NimMainBody WinCMain = "N_STDCALL(int, WinMain)(HINSTANCE hCurInstance, $N" & " HINSTANCE hPrevInstance, $N" & " LPSTR lpCmdLine, int nCmdShow) {$N" & - MainProcsWithResult & "}$N" + MainProcsWithResult & "}$N$N" WinNimDllMain = "N_LIB_EXPORT " & NimMainBody @@ -998,14 +998,14 @@ proc genMainProc(m: BModule) = "BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, $N" & " LPVOID lpvReserved) {$N" & "\tif(fwdreason == DLL_PROCESS_ATTACH) {$N" & MainProcs & "}$N" & - "\treturn 1;$N}$N" + "\treturn 1;$N}$N$N" PosixNimDllMain = WinNimDllMain PosixCDllMain = "void NIM_POSIX_INIT NimMainInit(void) {$N" & MainProcs & - "}$N" + "}$N$N" var nimMain, otherMain: TFormatStr if platform.targetOS == osWindows and @@ -1034,7 +1034,7 @@ proc genMainProc(m: BModule) = platform.targetOS == osStandalone: "".toRope else: ropecg(m, "\t#initStackBottom();$N") inc(m.labels) - appcg(m, m.s[cfsProcs], "void PreMain() {$N" & PreMainBody & "}$N", [ + appcg(m, m.s[cfsProcs], "void PreMain() {$N" & PreMainBody & "}$N$N", [ mainDatInit, initStackBottomCall, gBreakpoints, otherModsInit]) appcg(m, m.s[cfsProcs], nimMain, [mainModInit, toRope(m.labels)]) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 71479abddd..0df7bb6dc8 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -65,11 +65,13 @@ type prc*: PSym # the Nimrod proc that this C proc belongs to beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed threadVarAccessed*: bool # true if the proc already accessed some threadvar - nestedTryStmts*: seq[PNode] # in how many nested try statements we are - # (the vars must be volatile then) + nestedTryStmts*: seq[PNode] # in how many nested try statements we are + # (the vars must be volatile then) inExceptBlock*: int # are we currently inside an except block? # leaving such scopes by raise or by return must # execute any applicable finally blocks + finallySafePoints*: seq[PRope] # For correctly cleaning up exceptions when + # using return in finally statements labels*: Natural # for generating unique labels in the C proc blocks*: seq[TBlock] # nested blocks breakIdx*: int # the block that will be exited @@ -142,6 +144,7 @@ proc newProc*(prc: PSym, module: BModule): BProc = else: result.options = gOptions newSeq(result.blocks, 1) result.nestedTryStmts = @[] + result.finallySafePoints = @[] iterator cgenModules*: var BModule = for i in 0..high(gModules): diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 2682053611..b44ca2ff01 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -94,6 +94,7 @@ type errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile, errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitely, errOnlyACallOpCanBeDelegator, errUsingNoSymbol, + errMacroBodyDependsOnGenericTypes, errDestructorNotGenericEnough, errXExpectsTwoArguments, @@ -104,6 +105,7 @@ type errXhasSideEffects, errIteratorExpected, errLetNeedsInit, errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errXCannotBeClosure, errXMustBeCompileTime, + errCannotInferTypeOfTheLiteral, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -325,6 +327,8 @@ const errInstantiateXExplicitely: "instantiate '$1' explicitely", errOnlyACallOpCanBeDelegator: "only a call operator can be a delegator", errUsingNoSymbol: "'$1' is not a variable, constant or a proc name", + errMacroBodyDependsOnGenericTypes: "the macro body cannot be compiled, " & + "because the parameter '$1' has a generic type", errDestructorNotGenericEnough: "Destructor signarue is too specific. " & "A destructor must be associated will all instantiations of a generic type", errXExpectsTwoArguments: "\'$1\' expects two arguments", @@ -348,6 +352,7 @@ const errIllegalCaptureX: "illegal capture '$1'", errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXMustBeCompileTime: "'$1' can only be used in compile-time context", + errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 602c70f4de..4304e5fb76 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -97,8 +97,25 @@ proc makeExternImport(s: PSym, extname: string) = incl(s.flags, sfImportc) excl(s.flags, sfForward) -proc makeExternExport(s: PSym, extname: string) = +const invalidIdentChars = AllChars - IdentChars + +proc validateExternCName(s: PSym, info: TLineInfo) = + ## Validates that the symbol name in s.loc.r is a valid C identifier. + ## + ## Valid identifiers are those alphanumeric including the underscore not + ## starting with a number. If the check fails, a generic error will be + ## displayed to the user. + let target = ropeToStr(s.loc.r) + if target.len < 1 or (not (target[0] in IdentStartChars)) or + (not target.allCharsInSet(IdentChars)): + localError(info, errGenerated, "invalid exported symbol") + +proc makeExternExport(s: PSym, extname: string, info: TLineInfo) = setExternName(s, extname) + case gCmd + of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC: + validateExternCName(s, info) + else: discard incl(s.flags, sfExportc) proc processImportCompilerProc(s: PSym, extname: string) = @@ -515,7 +532,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, if k in validPragmas: case k of wExportc: - makeExternExport(sym, getOptionalStr(c, it, "$1")) + makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info) incl(sym.flags, sfUsed) # avoid wrong hints of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1")) of wImportCompilerProc: @@ -603,7 +620,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, processDynLib(c, it, sym) of wCompilerproc: noVal(it) # compilerproc may not get a string! - makeExternExport(sym, "$1") + makeExternExport(sym, "$1", it.info) incl(sym.flags, sfCompilerProc) incl(sym.flags, sfUsed) # suppress all those stupid warnings registerCompilerProc(sym) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 503ea4bc1a..6c4e29f297 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -143,10 +143,11 @@ proc discardCheck(c: PContext, result: PNode) = while n.kind in skipForDiscardable: n = n.lastSon n.typ = nil - elif c.inTypeClass > 0 and result.typ.kind == tyBool: - let verdict = semConstExpr(c, result) - if verdict.intVal == 0: - localError(result.info, "type class predicate failed") + elif c.inTypeClass > 0: + if result.typ.kind == tyBool: + let verdict = semConstExpr(c, result) + if verdict.intVal == 0: + localError(result.info, "type class predicate failed") elif result.typ.kind != tyError and gCmd != cmdInteractive: if result.typ.kind == tyNil: fixNilType(result) @@ -349,7 +350,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = # BUGFIX: ``fitNode`` is needed here! # check type compability between def.typ and typ: if typ != nil: def = fitNode(c, typ, def) - else: typ = skipIntLit(def.typ) + else: + typ = skipIntLit(def.typ) + if typ.kind in {tySequence, tyArray, tySet} and + typ.lastSon.kind == tyEmpty: + localError(def.info, errCannotInferTypeOfTheLiteral, + ($typ.kind).substr(2).toLower) else: def = ast.emptyNode if symkind == skLet: localError(a.info, errLetNeedsInit) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 184aca4f80..809b804282 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -653,6 +653,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, var paramTypId = if not anon and paramType.sym != nil: paramType.sym.name else: nil + template maybeLift(typ: PType): expr = + let lifted = liftingWalk(typ) + (if lifted != nil: lifted else: typ) + template addImplicitGeneric(e: expr): expr = addImplicitGenericImpl(e, paramTypId) @@ -663,15 +667,19 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyStatic: # proc(a: expr{string}, b: expr{nkLambda}) # overload on compile time values and AST trees - result = addImplicitGeneric(c.newTypeWithSons(tyStatic, paramType.sons)) + let base = paramType.base.maybeLift + if base.isMetaType and procKind == skMacro: + localError(info, errMacroBodyDependsOnGenericTypes, paramName) + result = addImplicitGeneric(c.newTypeWithSons(tyStatic, @[base])) result.flags.incl tfHasStatic of tyTypeDesc: if tfUnresolved notin paramType.flags: # naked typedescs are not bindOnce types - if paramType.sonsLen == 0 and paramTypId != nil and + if paramType.base.kind == tyNone and paramTypId != nil and paramTypId.id == typedescId.id: paramTypId = nil - result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons)) + result = addImplicitGeneric( + c.newTypeWithSons(tyTypeDesc, @[paramType.base])) of tyDistinct: if paramType.sonsLen == 1: @@ -705,7 +713,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, # result.rawAddSon(copyType(paramType.sons[i], getCurrOwner(), true)) result = instGenericContainer(c, paramType.sym.info, result, allowMetaTypes = true) - result.lastSon.shouldHaveMeta result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result]) result = addImplicitGeneric(result) @@ -1011,7 +1018,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mOrdinal: result = semOrdinal(c, n, prev) of mSeq: result = semContainer(c, n, tySequence, "seq", prev) of mVarargs: result = semVarargs(c, n, prev) - of mExpr, mTypeDesc: + of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + of mExpr: result = semTypeNode(c, n.sons[0], nil) if result != nil: result = copyType(result, getCurrOwner(), false) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index f08214f1ed..22edc6e329 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -220,7 +220,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = # is difficult to handle: var body = t.sons[0] if body.kind != tyGenericBody: internalError(cl.info, "no generic body") - var header: PType = nil + var header: PType = t # search for some instantiation here: if cl.allowMetaTypes: result = PType(idTableGet(cl.localCache, t)) @@ -232,11 +232,13 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = if x.kind == tyGenericParam: x = lookupTypeVar(cl, x) if x != nil: - if header == nil: header = instCopyType(cl, t) + if header == t: header = instCopyType(cl, t) header.sons[i] = x propagateToOwner(header, x) + else: + propagateToOwner(header, x) - if header != nil: + if header != t: # search again after first pass: result = searchInstTypes(header) if result != nil: return @@ -244,6 +246,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = header = instCopyType(cl, t) result = newType(tyGenericInst, t.sons[0].owner) + result.flags = header.flags # be careful not to propagate unnecessary flags here (don't use rawAddSon) result.sons = @[header.sons[0]] # ugh need another pass for deeply recursive generic types (e.g. PActor) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 5fe474ef36..f8e3459df9 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -900,20 +900,27 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isNone of tyTypeDesc: - if a.kind != tyTypeDesc: return isNone - var prev = PType(idTableGet(c.bindings, f)) if prev == nil: + # proc foo(T: typedesc, x: T) + # when `f` is an unresolved typedesc, `a` could be any + # type, so we should not perform this check earlier + if a.kind != tyTypeDesc: return isNone + if f.base.kind == tyNone: result = isGeneric else: result = typeRel(c, f.base, a.base) + if result != isNone: put(c.bindings, f, a) else: - let toMatch = if tfUnresolved in f.flags: a - else: a.base - result = typeRel(c, prev.base, toMatch) + if tfUnresolved in f.flags: + result = typeRel(c, prev.base, a) + elif a.kind == tyTypeDesc: + result = typeRel(c, prev.base, a.base) + else: + result = isNone of tyStmt: result = isGeneric @@ -1022,6 +1029,28 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, r = typeRel(m, f, a) + if r != isNone and m.calleeSym != nil and + m.calleeSym.kind in {skMacro, skTemplate}: + # XXX: duplicating this is ugly, maybe we should move this + # directly into typeRel using return-like templates + case r + of isConvertible, isIntConv: inc(m.convMatches) + of isSubtype, isSubrange: inc(m.subtypeMatches) + of isGeneric, isInferred: inc(m.genericMatches) + of isInferredConvertible: inc(m.genericMatches); inc(m.convMatches) + of isFromIntLit: inc(m.intConvMatches, 256) + of isEqual: inc(m.exactMatches) + of isNone: discard + + if f.kind == tyStmt and argOrig.kind == nkDo: + return argOrig[bodyPos] + elif f.kind == tyTypeDesc: + return arg + elif f.kind == tyStatic: + return arg.typ.n + else: + return argOrig + case r of isConvertible: inc(m.convMatches) @@ -1039,31 +1068,23 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, #result = copyTree(arg) result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) of isInferred, isInferredConvertible: + inc(m.genericMatches) if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds: result = c.semInferredLambda(c, m.bindings, arg) else: let inferred = c.semGenerateInstance(c, arg.sym, m.bindings, arg.info) result = newSymNode(inferred, arg.info) if r == isInferredConvertible: + inc(m.convMatches) result = implicitConv(nkHiddenStdConv, f, result, m, c) of isGeneric: inc(m.genericMatches) - if m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: - if f.kind == tyStmt and argOrig.kind == nkDo: - result = argOrig[bodyPos] - elif f.kind == tyTypeDesc: - result = arg - elif f.kind == tyStatic: - result = arg.typ.n - else: - result = argOrig - else: - result = copyTree(arg) - result.typ = getInstantiatedType(c, arg, m, f) - # BUG: f may not be the right key! - if skipTypes(result.typ, abstractVar-{tyTypeDesc}).kind in {tyTuple}: - result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) - # BUGFIX: use ``result.typ`` and not `f` here + result = copyTree(arg) + result.typ = getInstantiatedType(c, arg, m, f) + # BUG: f may not be the right key! + if skipTypes(result.typ, abstractVar-{tyTypeDesc}).kind in {tyTuple}: + result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) + # BUGFIX: use ``result.typ`` and not `f` here of isFromIntLit: # too lazy to introduce another ``*matches`` field, so we conflate # ``isIntConv`` and ``isIntLit`` here: diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index d50394f604..3665215510 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +from posix import TSocketHandle + const EPOLLIN* = 0x00000001 EPOLLPRI* = 0x00000002 @@ -33,8 +35,8 @@ const type epoll_data* {.importc: "union epoll_data", header: "", pure, final.} = object # TODO: This is actually a union. - thePtr* {.importc: "ptr".}: pointer # \ - #fd*: cint + #thePtr* {.importc: "ptr".}: pointer + fd*: cint # \ #u32*: uint32 #u64*: uint64 @@ -54,7 +56,7 @@ proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1", ## Same as epoll_create but with an FLAGS parameter. The unused SIZE ## parameter has been dropped. -proc epoll_ctl*(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint {. +proc epoll_ctl*(epfd: cint; op: cint; fd: cint | TSocketHandle; event: ptr epoll_event): cint {. importc: "epoll_ctl", header: "".} ## Manipulate an epoll instance "epfd". Returns 0 in case of success, ## -1 in case of error ( the "errno" variable will contain the diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim new file mode 100644 index 0000000000..1ed1af3b6d --- /dev/null +++ b/lib/posix/linux.nim @@ -0,0 +1,25 @@ +import posix + +const + CSIGNAL* = 0x000000FF + CLONE_VM* = 0x00000100 + CLONE_FS* = 0x00000200 + CLONE_FILES* = 0x00000400 + CLONE_SIGHAND* = 0x00000800 + CLONE_PTRACE* = 0x00002000 + CLONE_VFORK* = 0x00004000 + CLONE_PARENT* = 0x00008000 + CLONE_THREAD* = 0x00010000 + CLONE_NEWNS* = 0x00020000 + CLONE_SYSVSEM* = 0x00040000 + CLONE_SETTLS* = 0x00080000 + CLONE_PARENT_SETTID* = 0x00100000 + CLONE_CHILD_CLEARTID* = 0x00200000 + CLONE_DETACHED* = 0x00400000 + CLONE_UNTRACED* = 0x00800000 + CLONE_CHILD_SETTID* = 0x01000000 + CLONE_STOPPED* = 0x02000000 + +# fn should be of type proc (a2: pointer): void {.cdecl.} +proc clone*(fn: pointer; child_stack: pointer; flags: cint; + arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "".} diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 41260b36fc..3865a6bff1 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -2066,6 +2066,7 @@ proc pthread_spin_unlock*(a1: ptr Tpthread_spinlock): cint {. proc pthread_testcancel*() {.importc, header: "".} +proc exitnow*(code: int): void {.importc: "_exit", header: "".} proc access*(a1: cstring, a2: cint): cint {.importc, header: "".} proc alarm*(a1: cint): cint {.importc, header: "".} proc chdir*(a1: cstring): cint {.importc, header: "".} @@ -2356,7 +2357,7 @@ proc FD_ZERO*(a1: var TFdSet) {.importc, header: "".} proc pselect*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimespec, a6: var Tsigset): cint {.importc, header: "".} -proc select*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {. +proc select*(a1: cint | TSocketHandle, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {. importc, header: "".} when hasSpawnH: diff --git a/lib/pure/asyncio2.nim b/lib/pure/asyncio2.nim index 8541b2ba74..12d4cb5a31 100644 --- a/lib/pure/asyncio2.nim +++ b/lib/pure/asyncio2.nim @@ -9,8 +9,6 @@ import os, oids, tables, strutils, macros -import winlean - import sockets2, net ## Asyncio2 @@ -93,7 +91,10 @@ proc failed*[T](future: PFuture[T]): bool = ## Determines whether ``future`` completed with an error. future.error != nil -when defined(windows): +# TODO: Get rid of register. Do it implicitly. + +when defined(windows) or defined(nimdoc): + import winlean type TCompletionKey = dword @@ -293,7 +294,10 @@ when defined(windows): proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, flags: int = 0): PFuture[string] = ## Reads ``size`` bytes from ``socket``. Returned future will complete once - ## all of the requested data is read. + ## all of the requested data is read. If socket is disconnected during the + ## recv operation then the future may complete with only a part of the + ## requested data read. If socket is disconnected and no data is available + ## to be read then the future will complete with a value of ``""``. var retFuture = newFuture[string]() @@ -448,24 +452,206 @@ when defined(windows): return retFuture - proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = - ## Accepts a new connection. Returns a future containing the client socket - ## corresponding to that connection. - ## The future will complete when the connection is successfully accepted. - var retFut = newFuture[TSocketHandle]() - var fut = p.acceptAddr(socket) - fut.callback = - proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = - assert future.finished - if future.failed: - retFut.fail(future.error) - else: - retFut.complete(future.read.client) - return retFut - initAll() else: - # TODO: Selectors. + import selectors + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK + type + TCallback = proc (sock: TSocketHandle): bool {.closure.} + + PData* = ref object of PObject + sock: TSocketHandle + readCBs: seq[TCallback] + writeCBs: seq[TCallback] + + PDispatcher* = ref object + selector: PSelector + + proc newDispatcher*(): PDispatcher = + new result + result.selector = newSelector() + + proc update(p: PDispatcher, sock: TSocketHandle, events: set[TEvent]) = + assert sock in p.selector + echo("Update: ", events) + if events == {}: + discard p.selector.unregister(sock) + else: + discard p.selector.update(sock, events) + + proc addRead(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[cb], writeCBs: @[]) + p.selector.register(sock, {EvRead}, data.PObject) + else: + p.selector[sock].data.PData.readCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvRead}) + + proc addWrite(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[], writeCBs: @[cb]) + p.selector.register(sock, {EvWrite}, data.PObject) + else: + p.selector[sock].data.PData.writeCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvWrite}) + + proc poll*(p: PDispatcher, timeout = 500) = + for info in p.selector.select(timeout): + let data = PData(info.key.data) + assert data.sock == info.key.fd + echo("R: ", data.readCBs.len, " W: ", data.writeCBs.len, ". ", info.events) + + if EvRead in info.events: + var newReadCBs: seq[TCallback] = @[] + for cb in data.readCBs: + if not cb(data.sock): + # Callback wants to be called again. + newReadCBs.add(cb) + data.readCBs = newReadCBs + + if EvWrite in info.events: + var newWriteCBs: seq[TCallback] = @[] + for cb in data.writeCBs: + if not cb(data.sock): + # Callback wants to be called again. + newWriteCBs.add(cb) + data.writeCBs = newWriteCBs + + var newEvents: set[TEvent] + if data.readCBs.len != 0: newEvents = {EvRead} + if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} + p.update(data.sock, newEvents) + + proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort, + af = AF_INET): PFuture[int] = + var retFuture = newFuture[int]() + + proc cb(sock: TSocketHandle): bool = + # We have connected. + retFuture.complete(0) + return true + + var aiList = getAddrInfo(address, port, af) + var success = false + var lastError: TOSErrorCode + var it = aiList + while it != nil: + var ret = connect(socket, it.ai_addr, it.ai_addrlen.TSocklen) + if ret == 0: + # Request to connect completed immediately. + success = true + retFuture.complete(0) + break + else: + lastError = osLastError() + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: + success = true + addWrite(p, socket, cb) + break + else: + success = false + it = it.ai_next + + dealloc(aiList) + if not success: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + return retFuture + + proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, + flags: int = 0): PFuture[string] = + var retFuture = newFuture[string]() + + var readBuffer = newString(size) + var sizeRead = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = size - sizeRead + let res = recv(sock, addr readBuffer[sizeRead], netSize, flags.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + elif res == 0: + # Disconnected + if sizeRead == 0: + retFuture.complete("") + else: + readBuffer.setLen(sizeRead) + retFuture.complete(readBuffer) + else: + sizeRead.inc(res) + if res != netSize: + result = false # We want to read all the data requested. + else: + retFuture.complete(readBuffer) + + addRead(p, socket, cb) + return retFuture + + proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = + var retFuture = newFuture[int]() + + var written = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = data.len-written + var d = data.cstring + let res = send(sock, addr d[written], netSize, 0.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + else: + written.inc(res) + if res != netSize: + result = false # We still have data to send. + else: + retFuture.complete(0) + addWrite(p, socket, cb) + return retFuture + + + proc acceptAddr*(p: PDispatcher, socket: TSocketHandle): + PFuture[tuple[address: string, client: TSocketHandle]] = + var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]() + proc cb(sock: TSocketHandle): bool = + result = true + var sockAddress: Tsockaddr_in + var addrLen = sizeof(sockAddress).TSocklen + var client = accept(sock, cast[ptr TSockAddr](addr(sockAddress)), + addr(addrLen)) + if client == osInvalidSocket: + let lastError = osLastError() + assert lastError.int32 notin {EWOULDBLOCK, EAGAIN} + if lastError.int32 == EINTR: + return false + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client)) + addRead(p, socket, cb) + return retFuture + +proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = + ## Accepts a new connection. Returns a future containing the client socket + ## corresponding to that connection. + ## The future will complete when the connection is successfully accepted. + var retFut = newFuture[TSocketHandle]() + var fut = p.acceptAddr(socket) + fut.callback = + proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = + assert future.finished + if future.failed: + retFut.fail(future.error) + else: + retFut.complete(future.read.client) + return retFut # -- Await Macro @@ -665,8 +851,7 @@ when isMainModule: var p = newDispatcher() var sock = socket() - #sock.setBlocking false - p.register(sock) + sock.setBlocking false when false: @@ -706,7 +891,7 @@ when isMainModule: var recvF = p.recv(sock, 10) recvF.callback = proc (future: PFuture[string]) = - echo("Read: ", future.read) + echo("Read ", future.read.len, ": ", future.read.repr) else: diff --git a/lib/pure/net.nim b/lib/pure/net.nim index bdcae677e8..0ec007009c 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -37,4 +37,19 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. if bindAddr(socket, aiList.ai_addr, aiList.ai_addrlen.TSocklen) < 0'i32: dealloc(aiList) osError(osLastError()) - dealloc(aiList) \ No newline at end of file + dealloc(aiList) + +proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} = + ## Sets blocking mode on socket + when defined(Windows): + var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking + if ioctlsocket(s, FIONBIO, addr(mode)) == -1: + osError(osLastError()) + else: # BSD sockets + var x: int = fcntl(s, F_GETFL, 0) + if x == -1: + osError(osLastError()) + else: + var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK + if fcntl(s, F_SETFL, mode) == -1: + osError(osLastError()) \ No newline at end of file diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 9a43c0a7da..2a685f3fb4 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -13,13 +13,16 @@ include "system/inclrtl" import - strutils, os, strtabs, streams, sequtils + strutils, os, strtabs, streams when defined(windows): import winlean else: import posix +when defined(linux): + import linux + type TProcess = object of TObject when defined(windows): @@ -44,7 +47,7 @@ type poStdErrToStdOut, ## merge stdout and stderr to the stdout stream poParentStreams ## use the parent's streams -template poUseShell*: TProcessOption {.deprecated.} = poUsePath +const poUseShell* {.deprecated.} = poUsePath ## Deprecated alias for poUsePath. proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = @@ -590,6 +593,23 @@ elif not defined(useNimRtl): copyMem(result[i], addr(x[0]), x.len+1) inc(i) + type TStartProcessData = object + sysCommand: cstring + sysArgs: cstringArray + sysEnv: cstringArray + workingDir: cstring + pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] + optionPoUsePath: bool + optionPoParentStreams: bool + optionPoStdErrToStdOut: bool + + proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + {.push stacktrace: off, profiler: off.} + proc startProcessAfterFork(data: ptr TStartProcessData) {. + tags: [FExecIO, FReadEnv], cdecl.} + {.pop.} + proc startProcess(command: string, workingDir: string = "", args: openArray[string] = [], @@ -604,100 +624,48 @@ elif not defined(useNimRtl): pipe(pStderr) != 0'i32: osError(osLastError()) - var sys_command: string - var sys_args_raw: seq[string] + var sysCommand: string + var sysArgsRaw: seq[string] if poEvalCommand in options: - sys_command = "/bin/sh" - sys_args_raw = @[sys_command, "-c", command] + sysCommand = "/bin/sh" + sysArgsRaw = @[sysCommand, "-c", command] assert args.len == 0 else: - sys_command = command - sys_args_raw = @[command] + sysCommand = command + sysArgsRaw = @[command] for arg in args.items: - sys_args_raw.add arg - - var sys_args = allocCStringArray(sys_args_raw) - finally: deallocCStringArray(sys_args) + sysArgsRaw.add arg var pid: TPid - when defined(posix_spawn) and not defined(useFork): - var attr: Tposix_spawnattr - var fops: Tposix_spawn_file_actions - template chck(e: expr) = - if e != 0'i32: osError(osLastError()) + var sysArgs = allocCStringArray(sysArgsRaw) + finally: deallocCStringArray(sysArgs) - chck posix_spawn_file_actions_init(fops) - chck posix_spawnattr_init(attr) - - var mask: Tsigset - chck sigemptyset(mask) - chck posix_spawnattr_setsigmask(attr, mask) - chck posix_spawnattr_setpgroup(attr, 0'i32) - - chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or - POSIX_SPAWN_SETSIGMASK or - POSIX_SPAWN_SETPGROUP) - - if poParentStreams notin options: - chck posix_spawn_file_actions_addclose(fops, pStdin[writeIdx]) - chck posix_spawn_file_actions_adddup2(fops, pStdin[readIdx], readIdx) - chck posix_spawn_file_actions_addclose(fops, pStdout[readIdx]) - chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], writeIdx) - chck posix_spawn_file_actions_addclose(fops, pStderr[readIdx]) - if poStdErrToStdOut in options: - chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], 2) - else: - chck posix_spawn_file_actions_adddup2(fops, p_stderr[writeIdx], 2) - - var sys_env = if env == nil: envToCStringArray() else: envToCStringArray(env) - var res: cint - # This is incorrect! - if workingDir.len > 0: os.setCurrentDir(workingDir) - if poUsePath in options: - res = posix_spawnp(pid, sys_command, fops, attr, sys_args, sys_env) + var sysEnv = if env == nil: + envToCStringArray() else: - res = posix_spawn(pid, sys_command, fops, attr, sys_args, sys_env) - deallocCStringArray(sys_env) - discard posix_spawn_file_actions_destroy(fops) - discard posix_spawnattr_destroy(attr) - chck res + envToCStringArray(env) + finally: deallocCStringArray(sysEnv) + + var data: TStartProcessData + data.sysCommand = sysCommand + data.sysArgs = sysArgs + data.sysEnv = sysEnv + data.pStdin = pStdin + data.pStdout = pStdout + data.pStderr = pStderr + data.optionPoParentStreams = poParentStreams in options + data.optionPoUsePath = poUsePath in options + data.optionPoStdErrToStdOut = poStdErrToStdOut in options + data.workingDir = workingDir + + + when defined(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux): + pid = startProcessAuxSpawn(data) else: - pid = fork() - if pid < 0: osError(osLastError()) - if pid == 0: - ## child process: + pid = startProcessAuxFork(data) - if poParentStreams notin options: - discard close(p_stdin[writeIdx]) - if dup2(p_stdin[readIdx], readIdx) < 0: osError(osLastError()) - discard close(p_stdout[readIdx]) - if dup2(p_stdout[writeIdx], writeIdx) < 0: osError(osLastError()) - discard close(p_stderr[readIdx]) - if poStdErrToStdOut in options: - if dup2(p_stdout[writeIdx], 2) < 0: osError(osLastError()) - else: - if dup2(p_stderr[writeIdx], 2) < 0: osError(osLastError()) - - # Create a new process group - if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno)) - - if workingDir.len > 0: os.setCurrentDir(workingDir) - - if env == nil: - if poUsePath in options: - discard execvp(sys_command, sys_args) - else: - discard execv(sys_command, sys_args) - else: - var c_env = envToCStringArray(env) - if poUsePath in options: - discard execvpe(sys_command, sys_args, c_env) - else: - discard execve(sys_command, sys_args, c_env) - # too risky to raise an exception here: - quit("execve call failed: " & $strerror(errno)) # Parent process. Copy process information. if poEchoCmd in options: echo(command, " ", join(args, " ")) @@ -723,6 +691,137 @@ elif not defined(useNimRtl): discard close(pStdin[readIdx]) discard close(pStdout[writeIdx]) + proc startProcessAuxSpawn(data: TStartProcessData): TPid = + var attr: Tposix_spawnattr + var fops: Tposix_spawn_file_actions + + template chck(e: expr) = + if e != 0'i32: osError(osLastError()) + + chck posix_spawn_file_actions_init(fops) + chck posix_spawnattr_init(attr) + + var mask: Tsigset + chck sigemptyset(mask) + chck posix_spawnattr_setsigmask(attr, mask) + chck posix_spawnattr_setpgroup(attr, 0'i32) + + chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or + POSIX_SPAWN_SETSIGMASK or + POSIX_SPAWN_SETPGROUP) + + if not data.optionPoParentStreams: + chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) + if data.optionPoStdErrToStdOut: + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) + else: + chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) + + var res: cint + # FIXME: chdir is global to process + if data.workingDir.len > 0: + setCurrentDir($data.workingDir) + var pid: TPid + + if data.optionPoUsePath: + res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + else: + res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + + discard posix_spawn_file_actions_destroy(fops) + discard posix_spawnattr_destroy(attr) + chck res + return pid + + proc startProcessAuxFork(data: TStartProcessData): TPid = + if pipe(data.pErrorPipe) != 0: + osError(osLastError()) + + finally: + discard close(data.pErrorPipe[readIdx]) + + var pid: TPid + var dataCopy = data + + when defined(useClone): + const stackSize = 65536 + let stackEnd = cast[clong](alloc(stackSize)) + let stack = cast[pointer](stackEnd + stackSize) + let fn: pointer = startProcessAfterFork + pid = clone(fn, stack, + cint(CLONE_VM or CLONE_VFORK or SIGCHLD), + pointer(addr dataCopy), nil, nil, nil) + discard close(data.pErrorPipe[writeIdx]) + dealloc(stack) + else: + pid = fork() + if pid == 0: + startProcessAfterFork(addr(dataCopy)) + exitnow(1) + + discard close(data.pErrorPipe[writeIdx]) + if pid < 0: osError(osLastError()) + + var error: cint + let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) + if sizeRead == sizeof(error): + osError($strerror(error)) + + return pid + + {.push stacktrace: off, profiler: off.} + proc startProcessFail(data: ptr TStartProcessData) = + var error: cint = errno + discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) + exitnow(1) + + when defined(macosx): + var environ {.importc.}: cstringArray + + proc startProcessAfterFork(data: ptr TStartProcessData) = + # Warning: no GC here! + # Or anythink that touches global structures - all called nimrod procs + # must be marked with noStackFrame. Inspect C code after making changes. + if not data.optionPoParentStreams: + discard close(data.pStdin[writeIdx]) + if dup2(data.pStdin[readIdx], readIdx) < 0: + startProcessFail(data) + discard close(data.pStdout[readIdx]) + if dup2(data.pStdout[writeIdx], writeIdx) < 0: + startProcessFail(data) + discard close(data.pStderr[readIdx]) + if data.optionPoStdErrToStdOut: + if dup2(data.pStdout[writeIdx], 2) < 0: + startProcessFail(data) + else: + if dup2(data.pStderr[writeIdx], 2) < 0: + startProcessFail(data) + + if data.workingDir.len > 0: + if chdir(data.workingDir) < 0: + startProcessFail(data) + + discard close(data.pErrorPipe[readIdx]) + discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) + + if data.optionPoUsePath: + when defined(macosx): + # MacOSX doesn't have execvpe, so we need workaround. + # On MacOSX we can arrive here only from fork, so this is safe: + environ = data.sysEnv + discard execvp(data.sysCommand, data.sysArgs) + else: + discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) + else: + discard execve(data.sysCommand, data.sysArgs, data.sysEnv) + + startProcessFail(data) + {.pop} + proc close(p: PProcess) = if p.inStream != nil: close(p.inStream) if p.outStream != nil: close(p.outStream) diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 70b617393a..68b1ab2237 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -836,9 +836,11 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string = while i < s.len: c.ml = 0 var L = rawMatch(s, pattern, i, c) - if L < 0: break - yield substr(s, i, i+L-1) - inc(i, L) + if L < 0: + inc(i, 1) + else: + yield substr(s, i, i+L-1) + inc(i, L) proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. nosideEffect, rtl, extern: "npegs$1".} = diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 83c158da14..6482a01a6b 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2013 Dominik Picheta +# (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,212 +9,211 @@ # TODO: Docs. -import tables, os, unsigned -when defined(windows): - import winlean -else: - import posix +import tables, os, unsigned, hashes + +when defined(linux): import posix, epoll +elif defined(windows): import winlean + +proc hash*(x: TSocketHandle): THash {.borrow.} type TEvent* = enum EvRead, EvWrite - TSelectorKey* = object - fd: cint - events: set[TEvent] - data: PObject + PSelectorKey* = ref object + fd*: TSocketHandle + events*: set[TEvent] ## The events which ``fd`` listens for. + data*: PObject ## User object. - TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]] + TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]] - PSelector* = ref object of PObject ## Selector interface. - fds*: TTable[cint, TSelectorKey] - registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].} - unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].} - selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].} - closeImpl*: proc (s: PSelector) {.nimcall.} - -template initSelector(r: expr) = - new r - r.fds = initTable[cint, TSelectorKey]() - -proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject): - TSelectorKey = - if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data) - -proc unregister*(s: PSelector, fd: cint): TSelectorKey = - ## - ## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey`` - ## will only have the ``fd`` field set. This is an optimisation and may - ## change in the future if a viable use case is presented. - if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd) - -proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] = - ## - ## The ``events`` field of the returned ``key`` contains the original events - ## for which the ``fd`` was bound. This is contrary to the ``events`` field - ## of the ``TReadyInfo`` tuple which determines which events are ready - ## on the ``fd``. - - if not s.selectImpl.isNil: result = s.selectImpl(s, timeout) - -proc close*(s: PSelector) = - if not s.closeImpl.isNil: s.closeImpl(s) - -# ---- Select() ---------------------------------------------------------------- - -type - PSelectSelector* = ref object of PSelector ## Implementation of select() - -proc ssRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - if s.fds.hasKey(fd): - raise newException(EInvalidValue, "FD already exists in selector.") - var sk = TSelectorKey(fd: fd, events: events, data: data) - s.fds[fd] = sk - result = sk - -proc ssUnregister(s: PSelector, fd: cint): TSelectorKey = - result = s.fds[fd] - s.fds.del(fd) - -proc ssClose(s: PSelector) = nil - -proc timeValFromMilliseconds(timeout: int): TTimeVal = - if timeout != -1: - var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 - -proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey], - m: var int) = - FD_ZERO(rd); FD_ZERO(wr) - for k, v in pairs(fds): - if EvRead in v.events: - m = max(m, int(k)) - FD_SET(k, rd) - if EvWrite in v.events: - m = max(m, int(k)) - FD_SET(k, wr) - -proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]): - seq[TReadyInfo] = - result = @[] - for k, v in pairs(fds): - var events: set[TEvent] = {} - if FD_ISSET(k, rd) != 0'i32: - events = events + {EvRead} - if FD_ISSET(k, wr) != 0'i32: - events = events + {EvWrite} - result.add((v, events)) - -proc select(fds: TTable[cint, TSelectorKey], timeout = 500): - seq[TReadyInfo] = - var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout) - - var rd, wr: TFdSet - var m = 0 - createFdSet(rd, wr, fds, m) - - var retCode = 0 - if timeout != -1: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) - else: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) - - if retCode < 0: - OSError(OSLastError()) - elif retCode == 0: - return @[] - else: - return getReadyFDs(rd, wr, fds) - -proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] = - result = select(s.fds, timeout) - -proc newSelectSelector*(): PSelectSelector = - initSelector(result) - result.registerImpl = ssRegister - result.unregisterImpl = ssUnregister - result.selectImpl = ssSelect - result.closeImpl = ssClose - -# ---- Epoll ------------------------------------------------------------------- - -when defined(linux): - import epoll +when defined(linux) or defined(nimdoc): type - PEpollSelector* = ref object of PSelector + PSelector* = ref object epollFD: cint events: array[64, ptr epoll_event] + fds: TTable[TSocketHandle, PSelectorKey] - TDataWrapper = object - fd: cint - boundEvents: set[TEvent] ## The events which ``fd`` listens for. - data: PObject ## User object. - - proc esRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - var es = PEpollSelector(s) - var event: epoll_event + proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event = if EvRead in events: - event.events = EPOLLIN + result.events = EPOLLIN if EvWrite in events: - event.events = event.events or EPOLLOUT - - var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd - dw.fd = fd - dw.boundEvents = events - dw.data = data - event.data.thePtr = dw - - if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: - OSError(OSLastError()) - - result = TSelectorKey(fd: fd, events: events, data: data) + result.events = result.events or EPOLLOUT + result.data.fd = fd.cint - proc esUnregister(s: PSelector, fd: cint): TSelectorKey = - # We cannot find out the information about this ``fd`` from the epoll - # context. As such I will simply return an almost empty TSelectorKey. - var es = PEpollSelector(s) - if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], + data: PObject): PSelectorKey {.discardable.} = + ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent + ## ``events``. + if s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor already exists.") + + var event = createEventStruct(events, fd) + + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) - # We could fill in the ``fds`` TTable to get the info, but that wouldn't - # be nice for our memory. - result = TSelectorKey(fd: fd, events: {}, data: nil) + + var key = PSelectorKey(fd: fd, events: events, data: data) + + s.fds[fd] = key + result = key + + proc update*(s: PSelector, fd: TSocketHandle, + events: set[TEvent]): PSelectorKey {.discardable.} = + ## Updates the events which ``fd`` wants notifications for. + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + var event = createEventStruct(events, fd) + + s.fds[fd].events = events + echo("About to update") + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: + OSError(OSLastError()) + echo("finished updating") + result = s.fds[fd] + + proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + OSError(OSLastError()) + result = s.fds[fd] + s.fds.del(fd) - proc esClose(s: PSelector) = - var es = PEpollSelector(s) - if es.epollFD.close() != 0: OSError(OSLastError()) - dealloc(addr es.events) # TODO: Test this + proc close*(s: PSelector) = + if s.epollFD.close() != 0: OSError(OSLastError()) + dealloc(addr s.events) # TODO: Test this - proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] = + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = + ## + ## The ``events`` field of the returned ``key`` contains the original events + ## for which the ``fd`` was bound. This is contrary to the ``events`` field + ## of the ``TReadyInfo`` tuple which determines which events are ready + ## on the ``fd``. result = @[] - var es = PEpollSelector(s) - let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint) + let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint) if evNum < 0: OSError(OSLastError()) if evNum == 0: return @[] for i in 0 .. 0: echo ready[0].events i.inc if i == 6: + assert selector.unregister(sock.getFD).fd == sock.getFD selector.close() break diff --git a/lib/pure/sockets2.nim b/lib/pure/sockets2.nim index 22624bbad5..031217b904 100644 --- a/lib/pure/sockets2.nim +++ b/lib/pure/sockets2.nim @@ -17,11 +17,13 @@ when hostos == "solaris": when defined(Windows): import winlean + export ioctlsocket else: import posix + export fcntl, F_GETFL, O_NONBLOCK, F_SETFL export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, - inet_ntoa + inet_ntoa, recv, `==`, connect, send, accept type @@ -63,10 +65,10 @@ type when defined(windows): let - OSInvalidSocket* = winlean.INVALID_SOCKET + osInvalidSocket* = winlean.INVALID_SOCKET else: let - OSInvalidSocket* = posix.INVALID_SOCKET + osInvalidSocket* = posix.INVALID_SOCKET proc `==`*(a, b: TPort): bool {.borrow.} ## ``==`` for ports. @@ -89,7 +91,7 @@ when defined(posix): of AF_UNIX: result = posix.AF_UNIX of AF_INET: result = posix.AF_INET of AF_INET6: result = posix.AF_INET6 - else: nil + else: discard proc toInt(typ: TType): cint = case typ @@ -97,7 +99,7 @@ when defined(posix): of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_RAW: result = posix.SOCK_RAW - else: nil + else: discard proc toInt(p: TProtocol): cint = case p @@ -107,7 +109,7 @@ when defined(posix): of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP - else: nil + else: discard else: proc toInt(domain: TDomain): cint = @@ -199,4 +201,4 @@ proc htons*(x: int16): int16 = when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) \ No newline at end of file + if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) diff --git a/lib/system.nim b/lib/system.nim index 27be9f8151..869658727e 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2014,8 +2014,10 @@ when not defined(JS): #and not defined(NimrodVM): ## Flushes `f`'s buffer. proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].} - ## Reads all data from the stream `file`. Raises an IO exception - ## in case of an error + ## Reads all data from the stream `file`. + ## + ## Raises an IO exception in case of an error. It is an error if the + ## current file position is not at the beginning of the file. proc readFile*(filename: string): TaintedString {.tags: [FReadIO].} ## Opens a file named `filename` for reading. Then calls `readAll` diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index a19c456f7f..1720804c4e 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -418,58 +418,58 @@ proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = return Math.floor(`a` % `b`); """ -proc NegInt(a: int): int {.compilerproc.} = +proc negInt(a: int): int {.compilerproc.} = result = a*(-1) -proc NegInt64(a: int64): int64 {.compilerproc.} = +proc negInt64(a: int64): int64 {.compilerproc.} = result = a*(-1) -proc AbsInt(a: int): int {.compilerproc.} = +proc absInt(a: int): int {.compilerproc.} = result = if a < 0: a*(-1) else: a -proc AbsInt64(a: int64): int64 {.compilerproc.} = +proc absInt64(a: int64): int64 {.compilerproc.} = result = if a < 0: a*(-1) else: a -proc LeU(a, b: int): bool {.compilerproc.} = +proc leU(a, b: int): bool {.compilerproc.} = result = abs(a) <= abs(b) -proc LtU(a, b: int): bool {.compilerproc.} = +proc ltU(a, b: int): bool {.compilerproc.} = result = abs(a) < abs(b) -proc LeU64(a, b: int64): bool {.compilerproc.} = +proc leU64(a, b: int64): bool {.compilerproc.} = result = abs(a) <= abs(b) - -proc LtU64(a, b: int64): bool {.compilerproc.} = +proc ltU64(a, b: int64): bool {.compilerproc.} = result = abs(a) < abs(b) -proc AddU(a, b: int): int {.compilerproc.} = +proc addU(a, b: int): int {.compilerproc.} = result = abs(a) + abs(b) -proc AddU64(a, b: int64): int64 {.compilerproc.} = +proc addU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) + abs(b) -proc SubU(a, b: int): int {.compilerproc.} = +proc subU(a, b: int): int {.compilerproc.} = result = abs(a) - abs(b) -proc SubU64(a, b: int64): int64 {.compilerproc.} = +proc subU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) - abs(b) -proc MulU(a, b: int): int {.compilerproc.} = +proc mulU(a, b: int): int {.compilerproc.} = result = abs(a) * abs(b) -proc MulU64(a, b: int64): int64 {.compilerproc.} = +proc mulU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) * abs(b) -proc DivU(a, b: int): int {.compilerproc.} = +proc divU(a, b: int): int {.compilerproc.} = result = abs(a) div abs(b) -proc DivU64(a, b: int64): int64 {.compilerproc.} = +proc divU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) div abs(b) -proc ModU(a, b: int): int {.compilerproc.} = +proc modU(a, b: int): int {.compilerproc.} = result = abs(a) mod abs(b) -proc ModU64(a, b: int64): int64 {.compilerproc.} = +proc modU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) mod abs(b) -proc Ze(a: int): int {.compilerproc.} = +proc ze*(a: int): int {.compilerproc.} = result = a -proc Ze64(a: int64): int64 {.compilerproc.} = + +proc ze64*(a: int64): int64 {.compilerproc.} = result = a proc ToU8(a: int): int8 {.asmNoStackFrame, compilerproc.} = @@ -487,7 +487,6 @@ proc ToU32(a: int): int32 {.asmNoStackFrame, compilerproc.} = return `a`; """ - proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b @@ -500,9 +499,9 @@ proc isFatPointer(ti: PNimType): bool = tyArray, tyArrayConstr, tyTuple, tyOpenArray, tySet, tyVar, tyRef, tyPtr} -proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} +proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} -proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = +proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = case n.kind of nkNone: sysAssert(false, "NimCopyAux") of nkSlot: @@ -518,7 +517,7 @@ proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = } """ -proc NimCopy(x: pointer, ti: PNimType): pointer = +proc nimCopy(x: pointer, ti: PNimType): pointer = case ti.kind of tyPtr, tyRef, tyVar, tyNil: if not isFatPointer(ti): diff --git a/tests/actiontable/tactiontable2.nim b/tests/actiontable/tactiontable2.nim index 00b4276032..878356321c 100644 --- a/tests/actiontable/tactiontable2.nim +++ b/tests/actiontable/tactiontable2.nim @@ -1,6 +1,6 @@ discard """ line: 21 - errormsg: "invalid type: 'TTable'" + errormsg: "invalid type: 'TTable[string, proc (string)]'" """ import tables diff --git a/tests/async/tasyncawait.nim b/tests/async/tasyncawait.nim index bcaffc2875..bde5bf8c8d 100644 --- a/tests/async/tasyncawait.nim +++ b/tests/async/tasyncawait.nim @@ -21,7 +21,7 @@ proc sendMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.asyn proc launchSwarm(disp: PDispatcher, port: TPort): PFuture[int] {.async.} = for i in 0 .. auto: a + b + +proc foreach[T](s: seq[T], body: proc(x: T)) = + for e in s: + body(e) + +foreach(@[1,2,3]) do (x): + echo x + diff --git a/tests/global/globalaux.nim b/tests/global/globalaux.nim new file mode 100644 index 0000000000..5f6f727210 --- /dev/null +++ b/tests/global/globalaux.nim @@ -0,0 +1,15 @@ +type + TObj*[T] = object + val*: T + +var + totalGlobals* = 0 + +proc makeObj[T](x: T): TObj[T] = + totalGlobals += 1 + result.val = x + +proc globalInstance*[T]: var TObj[T] = + var g {.global.} = when T is int: makeObj(10) else: makeObj("hello") + result = g + diff --git a/tests/global/globalaux2.nim b/tests/global/globalaux2.nim new file mode 100644 index 0000000000..6c77f1f485 --- /dev/null +++ b/tests/global/globalaux2.nim @@ -0,0 +1,4 @@ +import globalaux + +echo "in globalaux2: ", globalInstance[int]().val + diff --git a/tests/metatype/tbindtypedesc.nim b/tests/metatype/tbindtypedesc.nim index 5ea8cf063f..84527362f0 100644 --- a/tests/metatype/tbindtypedesc.nim +++ b/tests/metatype/tbindtypedesc.nim @@ -1,10 +1,10 @@ discard """ - msg: ''' -int -float -TFoo -TFoo -''' + msg: '''int int +float float +int int +TFoo TFoo +int float +TFoo TFoo''' """ import typetraits @@ -24,9 +24,8 @@ template reject(e: expr) = proc genericParamRepeated[T: typedesc](a: T, b: T) = static: - echo a.name - echo b.name - + echo a.name, " ", b.name + accept genericParamRepeated(int, int) accept genericParamRepeated(float, float) @@ -35,8 +34,7 @@ reject genericParamRepeated(int, float) proc genericParamOnce[T: typedesc](a, b: T) = static: - echo a.name - echo b.name + echo a.name, " ", b.name accept genericParamOnce(int, int) accept genericParamOnce(TFoo, TFoo) @@ -68,8 +66,7 @@ reject typePairs2(string, int, TBAR, TBAR) proc dontBind(a: typedesc, b: typedesc) = static: - echo a.name - echo b.name + echo a.name, " ", b.name accept dontBind(int, float) accept dontBind(TFoo, TFoo) diff --git a/tests/metatype/tusertypeclasses.nim b/tests/metatype/tusertypeclasses.nim index 4c8c0fc56a..5b04c490f2 100644 --- a/tests/metatype/tusertypeclasses.nim +++ b/tests/metatype/tusertypeclasses.nim @@ -31,9 +31,10 @@ proc intval(x: int) = discard # check real and virtual fields type TFoo = generic T - intval T.x + T.x + y(T) intval T.y - + proc y(x: TObj): int = 10 proc testFoo(x: TFoo) = discard diff --git a/tests/module/trecinca.nim b/tests/module/trecinca.nim index 73a0ec9376..62d37783ce 100644 --- a/tests/module/trecinca.nim +++ b/tests/module/trecinca.nim @@ -1,7 +1,7 @@ discard """ file: "tests/reject/trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/reject/trecincb.nim'" + errormsg: "recursive dependency: 'tests/module/trecincb.nim'" """ # Test recursive includes diff --git a/tests/module/trecincb.nim b/tests/module/trecincb.nim index 9dd7d51de4..a2934052f9 100644 --- a/tests/module/trecincb.nim +++ b/tests/module/trecincb.nim @@ -1,7 +1,7 @@ discard """ file: "trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/reject/trecincb.nim'" + errormsg: "recursive dependency: 'tests/module/trecincb.nim'" """ # Test recursive includes diff --git a/tests/stdlib/tircbot.nim b/tests/stdlib/tircbot.nim index 71ecb0b48e..f0417c7ac6 100644 --- a/tests/stdlib/tircbot.nim +++ b/tests/stdlib/tircbot.nim @@ -183,7 +183,7 @@ type channel: string timestamp: TTime case kind*: TSeenType - of PSeenJoin: discard + of PSeenJoin: nil of PSeenPart, PSeenQuit, PSeenMsg: msg: string of PSeenNick: diff --git a/tests/sunset.tmpl b/tests/template/sunset.tmpl similarity index 100% rename from tests/sunset.tmpl rename to tests/template/sunset.tmpl diff --git a/tests/threads/nimrod.cfg b/tests/threads/nimrod.cfg new file mode 100644 index 0000000000..b81c897213 --- /dev/null +++ b/tests/threads/nimrod.cfg @@ -0,0 +1 @@ +threads:on diff --git a/tests/typerel/tvoid.nim b/tests/typerel/tvoid.nim index bb569e7f87..d319362176 100644 --- a/tests/typerel/tvoid.nim +++ b/tests/typerel/tvoid.nim @@ -1,5 +1,9 @@ discard """ - output: "he, no return type;abc a string" + output: '''12 +empty +he, no return type; +abc a string +ha''' """ proc ReturnT[T](x: T): T =