diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2017-05-04 16:02:50 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2017-05-04 16:02:50 +0200 |
commit | 764cc0217735454c166cb7dd490db58f5fa6fe26 (patch) | |
tree | 63633b39ae24815e8b677ccbc9bac2cb01c39d3b | |
parent | afa80092d378a6dbc116c0aa3ed3964fd8c599d6 (diff) | |
parent | c1aa973758a60d7ef0e698c94861b74132612de5 (diff) | |
download | Nim-764cc0217735454c166cb7dd490db58f5fa6fe26.tar.gz |
Merge branch 'devel' into araq
42 files changed, 960 insertions, 453 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim index bad346298..c9b047a49 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1324,8 +1324,11 @@ proc shouldRecompile(code: Rope, cfile: Cfile): bool = if optForceFullMake notin gGlobalOptions: if not equalsFile(code, cfile.cname): if isDefined("nimdiff"): - copyFile(cfile.cname, cfile.cname & ".backup") - echo "diff ", cfile.cname, ".backup ", cfile.cname + if fileExists(cfile.cname): + copyFile(cfile.cname, cfile.cname & ".backup") + echo "diff ", cfile.cname, ".backup ", cfile.cname + else: + echo "new file ", cfile.cname writeRope(code, cfile.cname) return if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname): diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 0016a8492..eb3fb9f47 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -434,22 +434,23 @@ proc binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = proc unsignedTrimmerJS(size: BiggestInt): Rope = case size - of 1: rope"& 0xff" - of 2: rope"& 0xffff" - of 4: rope">>> 0" - else: rope"" + of 1: rope"& 0xff" + of 2: rope"& 0xffff" + of 4: rope">>> 0" + else: rope"" proc unsignedTrimmerPHP(size: BiggestInt): Rope = case size - of 1: rope"& 0xff" - of 2: rope"& 0xffff" - of 4: rope"& 0xffffffff" - else: rope"" + of 1: rope"& 0xff" + of 2: rope"& 0xffff" + of 4: rope"& 0xffffffff" + else: rope"" template unsignedTrimmer(size: BiggestInt): Rope = size.unsignedTrimmerJS | size.unsignedTrimmerPHP -proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, reassign: bool = false) = +proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, + reassign = false) = var x, y: TCompRes gen(p, n.sons[1], x) gen(p, n.sons[2], y) @@ -1633,11 +1634,11 @@ proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = gen(p, n.sons[1], a) if magic == "reprAny": - # the pointer argument in reprAny is expandend to + # the pointer argument in reprAny is expandend to # (pointedto, pointer), so we need to fill it if a.address.isNil: add(r.res, a.res) - add(r.res, ", null") + add(r.res, ", null") else: add(r.res, "$1, $2" % [a.address, a.res]) else: @@ -1670,7 +1671,7 @@ proc genRepr(p: PProc, n: PNode, r: var TCompRes) = genReprAux(p, n, r, "reprSet", genTypeInfo(p, t)) of tyEmpty, tyVoid: localError(n.info, "'repr' doesn't support 'void' type") - of tyPointer: + of tyPointer: genReprAux(p, n, r, "reprPointer") of tyOpenArray, tyVarargs: genReprAux(p, n, r, "reprJSONStringify") @@ -1863,8 +1864,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = var a, b: TCompRes - useMagic(p, "SetConstr") - r.res = rope("SetConstr(") + useMagic(p, "setConstr") + r.res = rope("setConstr(") r.kind = resExpr for i in countup(0, sonsLen(n) - 1): if i > 0: add(r.res, ", ") @@ -1877,6 +1878,12 @@ proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = gen(p, it, a) add(r.res, a.res) add(r.res, ")") + # emit better code for constant sets: + if p.target == targetJS and isDeepConstExpr(n): + inc(p.g.unique) + let tmp = rope("ConstSet") & rope(p.g.unique) + addf(p.g.constants, "var $1 = $2;$n", [tmp, r.res]) + r.res = tmp proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes @@ -2128,6 +2135,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = else: r.res = rope(f.toStrMaxPrecision) r.kind = resExpr of nkCallKinds: + if isEmptyType(n.typ): genLineDir(p, n) if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone): genMagic(p, n, r) elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 245cc5f61..4bd54603d 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -74,9 +74,9 @@ proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode = let value = n.lastSon result = newNodeI(nkStmtList, n.info) - var temp = newSym(skTemp, getIdent(genPrefix), owner, value.info) + var temp = newSym(skLet, getIdent("_"), owner, value.info) var v = newNodeI(nkLetSection, value.info) - let tempAsNode = newIdentNode(getIdent(genPrefix & $temp.id), value.info) + let tempAsNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info) var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3) vpart.sons[0] = tempAsNode @@ -115,7 +115,7 @@ proc createObj*(owner: PSym, info: TLineInfo): PType = incl result.flags, tfFinal result.n = newNodeI(nkRecList, info) when true: - let s = newSym(skType, getIdent("Env_" & info.toFilename & "_" & $info.line), + let s = newSym(skType, getIdent("Env_" & info.toFilename), owner, info) incl s.flags, sfAnon s.typ = result @@ -137,10 +137,32 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode = addSon(result, newSymNode(field)) result.typ = field.typ +proc lookupInRecord(n: PNode, id: int): PSym = + result = nil + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + result = lookupInRecord(n.sons[i], id) + if result != nil: return + of nkRecCase: + if n.sons[0].kind != nkSym: return + result = lookupInRecord(n.sons[0], id) + if result != nil: return + for i in countup(1, sonsLen(n) - 1): + case n.sons[i].kind + of nkOfBranch, nkElse: + result = lookupInRecord(lastSon(n.sons[i]), id) + if result != nil: return + else: discard + of nkSym: + if n.sym.id == -abs(id): result = n.sym + else: discard + proc addField*(obj: PType; s: PSym) = # because of 'gensym' support, we have to mangle the name with its ID. # This is hacky but the clean solution is much more complex than it looks. - var field = newSym(skField, getIdent(s.name.s & $s.id), s.owner, s.info) + var field = newSym(skField, getIdent(s.name.s & $obj.n.len), s.owner, s.info) + field.id = -s.id let t = skipIntLit(s.typ) field.typ = t assert t.kind != tyStmt @@ -148,9 +170,9 @@ proc addField*(obj: PType; s: PSym) = addSon(obj.n, newSymNode(field)) proc addUniqueField*(obj: PType; s: PSym) = - let fieldName = getIdent(s.name.s & $s.id) - if lookupInRecord(obj.n, fieldName) == nil: - var field = newSym(skField, fieldName, s.owner, s.info) + if lookupInRecord(obj.n, s.id) == nil: + var field = newSym(skField, getIdent(s.name.s & $obj.n.len), s.owner, s.info) + field.id = -s.id let t = skipIntLit(s.typ) field.typ = t assert t.kind != tyStmt @@ -159,13 +181,36 @@ proc addUniqueField*(obj: PType; s: PSym) = proc newDotExpr(obj, b: PSym): PNode = result = newNodeI(nkDotExpr, obj.info) - let field = getSymFromList(obj.typ.n, getIdent(b.name.s & $b.id)) + let field = lookupInRecord(obj.typ.n, b.id) assert field != nil, b.name.s addSon(result, newSymNode(obj)) addSon(result, newSymNode(field)) result.typ = field.typ -proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode = +proc indirectAccess*(a: PNode, b: int, info: TLineInfo): PNode = + # returns a[].b as a node + var deref = newNodeI(nkHiddenDeref, info) + deref.typ = a.typ.skipTypes(abstractInst).sons[0] + var t = deref.typ.skipTypes(abstractInst) + var field: PSym + while true: + assert t.kind == tyObject + field = lookupInRecord(t.n, b) + if field != nil: break + t = t.sons[0] + if t == nil: break + t = t.skipTypes(skipPtrs) + #if field == nil: + # echo "FIELD ", b + # debug deref.typ + internalAssert field != nil + addSon(deref, a) + result = newNodeI(nkDotExpr, info) + addSon(result, deref) + addSon(result, newSymNode(field)) + result.typ = field.typ + +proc indirectAccess(a: PNode, b: string, info: TLineInfo): PNode = # returns a[].b as a node var deref = newNodeI(nkHiddenDeref, info) deref.typ = a.typ.skipTypes(abstractInst).sons[0] @@ -191,11 +236,10 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode = proc getFieldFromObj*(t: PType; v: PSym): PSym = assert v.kind != skField - let fieldName = getIdent(v.name.s & $v.id) var t = t while true: assert t.kind == tyObject - result = getSymFromList(t.n, fieldName) + result = lookupInRecord(t.n, v.id) if result != nil: break t = t.sons[0] if t == nil: break @@ -203,7 +247,7 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym = proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode = # returns a[].b as a node - result = indirectAccess(a, b.name.s & $b.id, info) + result = indirectAccess(a, b.id, info) proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode = result = indirectAccess(newSymNode(a), b, info) diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index e6466fc24..5e6d843de 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -17,15 +17,17 @@ proc addPath*(path: string, info: TLineInfo) = proc versionSplitPos(s: string): int = result = s.len-2 - while result > 1 and s[result] in {'0'..'9', '.'}: dec result + #while result > 1 and s[result] in {'0'..'9', '.'}: dec result + while result > 1 and s[result] != '-': dec result if s[result] != '-': result = s.len const - latest = "head" + latest = "" proc `<.`(a, b: string): bool = # wether a has a smaller version than b: - if a == latest: return false + if a == latest: return true + elif b == latest: return false var i = 0 var j = 0 var verA = 0 @@ -33,8 +35,13 @@ proc `<.`(a, b: string): bool = while true: let ii = parseInt(a, verA, i) let jj = parseInt(b, verB, j) - # if A has no number left, but B has, B is preferred: 0.8 vs 0.8.3 - if ii <= 0 or jj <= 0: return jj > 0 + if ii <= 0 or jj <= 0: + # if A has no number and B has but A has no number whatsoever ("#head"), + # A is preferred: + if ii > 0 and jj <= 0 and j == 0: return true + if ii <= 0 and jj > 0 and i == 0: return false + # if A has no number left, but B has, B is preferred: 0.8 vs 0.8.3 + return jj > 0 if verA < verB: return true elif verA > verB: return false # else: same version number; continue: @@ -46,12 +53,9 @@ proc `<.`(a, b: string): bool = proc addPackage(packages: StringTableRef, p: string) = let x = versionSplitPos(p) let name = p.substr(0, x-1) - if x < p.len: - let version = p.substr(x+1) - if packages.getOrDefault(name) <. version: - packages[name] = version - else: - packages[name] = latest + let version = if x < p.len: p.substr(x+1) else: "" + if packages.getOrDefault(name) <. version: + packages[name] = version iterator chosen(packages: StringTableRef): string = for key, val in pairs(packages): @@ -76,3 +80,18 @@ proc addPathRec(dir: string, info: TLineInfo) = proc nimblePath*(path: string, info: TLineInfo) = addPathRec(path, info) addNimblePath(path, info) + +when isMainModule: + var rr = newStringTable() + addPackage rr, "irc-#head" + addPackage rr, "irc-0.1.0" + addPackage rr, "irc" + addPackage rr, "another" + addPackage rr, "another-0.1" + + addPackage rr, "ab-0.1.3" + addPackage rr, "ab-0.1" + addPackage rr, "justone" + + for p in rr.chosen: + echo p diff --git a/compiler/parser.nim b/compiler/parser.nim index e9dff25ac..fabe4bcc8 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1238,10 +1238,7 @@ proc parseExprStmt(p: var TParser): PNode = addSon(result, e) if p.tok.tokType != tkComma: break elif p.tok.indent < 0 and isExprStart(p): - if a.kind == nkCommand: - result = a - else: - result = newNode(nkCommand, a.info, @[a]) + result = newNode(nkCommand, a.info, @[a]) while true: var e = parseExpr(p) addSon(result, e) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 316cf55c8..25f62983d 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1266,7 +1266,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = result.typ = makeTypeDesc(c, semTypeNode(c, n, nil)) #result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) of tyTuple: - checkSonsLen(n, 2) + if n.len != 2: return nil n.sons[0] = makeDeref(n.sons[0]) c.p.bracketExpr = n.sons[0] # [] operator for tuples requires constant expression: @@ -1276,9 +1276,9 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = var idx = getOrdValue(n.sons[1]) if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] else: localError(n.info, errInvalidIndexValueForTuple) + result = n else: - localError(n.info, errIndexTypesDoNotMatch) - result = n + result = nil else: let s = if n.sons[0].kind == nkSym: n.sons[0].sym elif n[0].kind in nkSymChoices: n.sons[0][0].sym diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index ce7474966..96bdc6cba 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -517,7 +517,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = proc procVarcheck(n: PNode) = if n.kind in nkSymChoices: for x in n: procVarCheck(x) - elif n.kind == nkSym and n.sym.magic != mNone: + elif n.kind == nkSym and n.sym.magic != mNone and n.sym.kind in routineKinds: localError(n.info, errXCannotBePassedToProcVar, n.sym.name.s) proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = diff --git a/compiler/trees.nim b/compiler/trees.nim index 424fba14c..8f0af89d3 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -96,7 +96,7 @@ proc isDeepConstExpr*(n: PNode): bool = result = true of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv: result = isDeepConstExpr(n.sons[1]) - of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure: + of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure, nkRange: for i in ord(n.kind == nkObjConstr) .. <n.len: if not isDeepConstExpr(n.sons[i]): return false if n.typ.isNil: result = true diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index fdc3d2ba0..3e656cb8f 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -129,6 +129,8 @@ doc.file = """<?xml version="1.0" encoding="utf-8" ?> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> + <!-- Favicon --> <link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAUAAAAF////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAIAAABbAAAAlQAAAKIAAACbAAAAmwAAAKIAAACVAAAAWwAAAAL///8A////AP///wD///8A////AAAAABQAAADAAAAAYwAAAA3///8A////AP///wD///8AAAAADQAAAGMAAADAAAAAFP///wD///8A////AP///wAAAACdAAAAOv///wD///8A////AP///wD///8A////AP///wD///8AAAAAOgAAAJ3///8A////AP///wAAAAAnAAAAcP///wAAAAAoAAAASv///wD///8A////AP///wAAAABKAAAAKP///wAAAABwAAAAJ////wD///8AAAAAgQAAABwAAACIAAAAkAAAAJMAAACtAAAAFQAAABUAAACtAAAAkwAAAJAAAACIAAAAHAAAAIH///8A////AAAAAKQAAACrAAAAaP///wD///8AAAAARQAAANIAAADSAAAARf///wD///8AAAAAaAAAAKsAAACk////AAAAADMAAACcAAAAnQAAABj///8A////AP///wAAAAAYAAAAGP///wD///8A////AAAAABgAAACdAAAAnAAAADMAAAB1AAAAwwAAAP8AAADpAAAAsQAAAE4AAAAb////AP///wAAAAAbAAAATgAAALEAAADpAAAA/wAAAMMAAAB1AAAAtwAAAOkAAAD/AAAA/wAAAP8AAADvAAAA3gAAAN4AAADeAAAA3gAAAO8AAAD/AAAA/wAAAP8AAADpAAAAtwAAAGUAAAA/AAAA3wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADfAAAAPwAAAGX///8A////AAAAAEgAAADtAAAAvwAAAL0AAADGAAAA7wAAAO8AAADGAAAAvQAAAL8AAADtAAAASP///wD///8A////AP///wD///8AAAAAO////wD///8A////AAAAAIcAAACH////AP///wD///8AAAAAO////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD4HwAA7/cAAN/7AAD//wAAoYUAAJ55AACf+QAAh+EAAAAAAADAAwAA4AcAAP5/AAD//wAA//8AAA=="/> diff --git a/doc/intern.txt b/doc/intern.txt index d0aaa283a..dadb0eb05 100644 --- a/doc/intern.txt +++ b/doc/intern.txt @@ -157,7 +157,7 @@ compilation fails. This exit code tells ``git bisect`` to skip the current commit.:: git bisect start bad-commit good-commit - git bisect ./koch -r c test-source.nim + git bisect run ./koch temp -r c test-source.nim The compiler's architecture =========================== diff --git a/koch.nim b/koch.nim index e24367dd5..da99ea264 100644 --- a/koch.nim +++ b/koch.nim @@ -178,7 +178,7 @@ proc bundleNimbleExe() = bundleNimbleSrc() # now compile Nimble and copy it to $nim/bin for the installer.ini # to pick it up: - nimexec("c dist/nimble/src/nimble.nim") + nimexec("c -d:release dist/nimble/src/nimble.nim") copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe) proc buildNimble(latest: bool) = @@ -205,7 +205,7 @@ proc buildNimble(latest: bool) = else: exec("git checkout -f stable") exec("git pull") - nimexec("c --noNimblePath -p:compiler " & installDir / "src/nimble.nim") + nimexec("c --noNimblePath -p:compiler -d:release " & installDir / "src/nimble.nim") copyExe(installDir / "src/nimble".exe, "bin/nimble".exe) proc bundleNimsuggest(buildExe: bool) = diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index ab608efc6..02312a4d5 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -97,15 +97,6 @@ when not defined(macosx): ## Second-granularity time of last status change. result = s.st_ctim.tv_sec -proc WIFCONTINUED*(s:cint) : bool {.importc, header: "<sys/wait.h>".} - ## True if child has been continued. -proc WIFEXITED*(s:cint) : bool {.importc, header: "<sys/wait.h>".} - ## True if child exited normally. -proc WIFSIGNALED*(s:cint) : bool {.importc, header: "<sys/wait.h>".} - ## True if child exited due to uncaught signal. -proc WIFSTOPPED*(s:cint) : bool {.importc, header: "<sys/wait.h>".} - ## True if child is currently stopped. - when hasAioH: proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".} proc aio_error*(a1: ptr Taiocb): cint {.importc, header: "<aio.h>".} diff --git a/lib/posix/posix_linux_amd64.nim b/lib/posix/posix_linux_amd64.nim index 4948642c1..70f7e710f 100644 --- a/lib/posix/posix_linux_amd64.nim +++ b/lib/posix/posix_linux_amd64.nim @@ -602,3 +602,12 @@ var include posix_linux_amd64_consts const POSIX_SPAWN_USEVFORK* = cint(0x40) # needs _GNU_SOURCE! + +# <sys/wait.h> +proc WEXITSTATUS*(s: cint): cint = (s and 0xff00) shr 8 +proc WTERMSIG*(s:cint): cint = s and 0x7f +proc WSTOPSIG*(s:cint): cint = WEXITSTATUS(s) +proc WIFEXITED*(s:cint) : bool = WTERMSIG(s) == 0 +proc WIFSIGNALED*(s:cint) : bool = (cast[int8]((s and 0x7f) + 1) shr 1) > 0 +proc WIFSTOPPED*(s:cint) : bool = (s and 0xff) == 0x7f +proc WIFCONTINUED*(s:cint) : bool = s == W_CONTINUED diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim index 6e1c33605..b1cc244b7 100644 --- a/lib/posix/posix_other.nim +++ b/lib/posix/posix_other.nim @@ -611,3 +611,18 @@ when hasSpawnH: # OR'ing of flags: const POSIX_SPAWN_USEVFORK* = cint(0) +# <sys/wait.h> +proc WEXITSTATUS*(s: cint): cint {.importc, header: "<sys/wait.h>".} + ## Exit code, iff WIFEXITED(s) +proc WTERMSIG*(s: cint): cint {.importc, header: "<sys/wait.h>".} + ## Termination signal, iff WIFSIGNALED(s) +proc WSTOPSIG*(s: cint): cint {.importc, header: "<sys/wait.h>".} + ## Stop signal, iff WIFSTOPPED(s) +proc WIFEXITED*(s: cint): bool {.importc, header: "<sys/wait.h>".} + ## True if child exited normally. +proc WIFSIGNALED*(s: cint): bool {.importc, header: "<sys/wait.h>".} + ## True if child exited due to uncaught signal. +proc WIFSTOPPED*(s: cint): bool {.importc, header: "<sys/wait.h>".} + ## True if child is currently stopped. +proc WIFCONTINUED*(s: cint): bool {.importc, header: "<sys/wait.h>".} + ## True if child has been continued. diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 1696c4ed9..6a877be30 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, times, heapqueue +import os, oids, tables, strutils, times, heapqueue, options import nativesockets, net, deques @@ -385,68 +385,6 @@ when defined(windows) or defined(nimdoc): dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, RemoteSockaddr, RemoteSockaddrLength) - proc connect*(socket: AsyncFD, address: string, port: Port, - domain = nativesockets.AF_INET): Future[void] = - ## Connects ``socket`` to server at ``address:port``. - ## - ## Returns a ``Future`` which will complete when the connection succeeds - ## or an error occurs. - verifyPresence(socket) - var retFuture = newFuture[void]("connect") - # Apparently ``ConnectEx`` expects the socket to be initially bound: - var saddr: Sockaddr_in - saddr.sin_family = int16(toInt(domain)) - saddr.sin_port = 0 - saddr.sin_addr.s_addr = INADDR_ANY - if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)), - sizeof(saddr).SockLen) < 0'i32: - raiseOSError(osLastError()) - - var aiList = getAddrInfo(address, port, domain) - var success = false - var lastError: OSErrorCode - var it = aiList - while it != nil: - # "the OVERLAPPED structure must remain valid until the I/O completes" - # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx - var ol = PCustomOverlapped() - GC_ref(ol) - ol.data = CompletionData(fd: socket, cb: - proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = - if not retFuture.finished: - if errcode == OSErrorCode(-1): - retFuture.complete() - else: - retFuture.fail(newException(OSError, osErrorMsg(errcode))) - ) - - var ret = connectEx(socket.SocketHandle, it.ai_addr, - sizeof(Sockaddr_in).cint, nil, 0, nil, - cast[POVERLAPPED](ol)) - if ret: - # Request to connect completed immediately. - success = true - retFuture.complete() - # We don't deallocate ``ol`` here because even though this completed - # immediately poll will still be notified about its completion and it will - # free ``ol``. - break - else: - lastError = osLastError() - if lastError.int32 == ERROR_IO_PENDING: - # In this case ``ol`` will be deallocated in ``poll``. - success = true - break - else: - GC_unref(ol) - success = false - it = it.ai_next - - freeAddrInfo(aiList) - if not success: - retFuture.fail(newException(OSError, osErrorMsg(lastError))) - return retFuture - proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = ## Reads **up to** ``size`` bytes from ``socket``. Returned future will @@ -754,8 +692,8 @@ when defined(windows) or defined(nimdoc): var lpOutputBuf = newString(lpOutputLen) var dwBytesReceived: Dword let dwReceiveDataLength = 0.Dword # We don't want any data to be read. - let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) - let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) + let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16) + let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16) template failAccept(errcode) = if flags.isDisconnectionError(errcode): @@ -785,12 +723,14 @@ when defined(windows) or defined(nimdoc): dwLocalAddressLength, dwRemoteAddressLength, addr localSockaddr, addr localLen, addr remoteSockaddr, addr remoteLen) - register(clientSock.AsyncFD) - # TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 - retFuture.complete( - (address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), - client: clientSock.AsyncFD) - ) + try: + let address = getAddrString(remoteSockAddr) + register(clientSock.AsyncFD) + retFuture.complete((address: address, client: clientSock.AsyncFD)) + except: + # getAddrString may raise + clientSock.close() + retFuture.fail(getCurrentException()) var ol = PCustomOverlapped() GC_ref(ol) @@ -823,20 +763,6 @@ when defined(windows) or defined(nimdoc): return retFuture - proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD = - ## Creates a new socket and registers it with the dispatcher implicitly. - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - register(result) - - proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = - ## Creates a new socket and registers it with the dispatcher implicitly. - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - register(result) - proc closeSocket*(socket: AsyncFD) = ## Closes a socket and ensures that it is unregistered. socket.SocketHandle.close() @@ -1015,23 +941,6 @@ else: var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) p.selector.register(fd.SocketHandle, {}, data.RootRef) - proc newAsyncNativeSocket*(domain: cint, sockType: cint, - protocol: cint): AsyncFD = - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - when defined(macosx): - result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) - register(result) - - proc newAsyncNativeSocket*(domain: Domain = AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - when defined(macosx): - result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) - register(result) - proc closeSocket*(sock: AsyncFD) = let disp = getGlobalDispatcher() disp.selector.unregister(sock.SocketHandle) @@ -1115,50 +1024,6 @@ else: # Callback queue processing processPendingCallbacks(p) - proc connect*(socket: AsyncFD, address: string, port: Port, - domain = AF_INET): Future[void] = - var retFuture = newFuture[void]("connect") - - proc cb(fd: AsyncFD): bool = - var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR)) - if ret == 0: - # We have connected. - retFuture.complete() - return true - elif ret == EINTR: - # interrupted, keep waiting - return false - else: - retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret)))) - return true - - assert getSockDomain(socket.SocketHandle) == domain - var aiList = getAddrInfo(address, port, domain) - var success = false - var lastError: OSErrorCode - var it = aiList - while it != nil: - var ret = connect(socket.SocketHandle, it.ai_addr, it.ai_addrlen.Socklen) - if ret == 0: - # Request to connect completed immediately. - success = true - retFuture.complete() - break - else: - lastError = osLastError() - if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: - success = true - addWrite(socket, cb) - break - else: - success = false - it = it.ai_next - - freeAddrInfo(aiList) - if not success: - retFuture.fail(newException(OSError, osErrorMsg(lastError))) - return retFuture - proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = var retFuture = newFuture[string]("recv") @@ -1320,11 +1185,20 @@ else: else: retFuture.fail(newException(OSError, osErrorMsg(lastError))) else: - register(client.AsyncFD) - retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), client.AsyncFD)) + try: + let address = getAddrString(cast[ptr SockAddr](addr sockAddress)) + register(client.AsyncFD) + retFuture.complete((address, client.AsyncFD)) + except: + # getAddrString may raise + client.close() + retFuture.fail(getCurrentException()) addRead(socket, cb) return retFuture +# Common procedures between current and upcoming asyncdispatch +include includes.asynccommon + proc sleepAsync*(ms: int): Future[void] = ## Suspends the execution of the current async procedure for the next ## ``ms`` milliseconds. diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 1ec751a64..11b7998b2 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -244,6 +244,17 @@ when defineSsl: else: raiseSSLError("Socket has been disconnected") +proc dial*(address: string, port: Port, protocol = IPPROTO_TCP, + buffered = true): Future[AsyncSocket] {.async.} = + ## Establishes connection to the specified ``address``:``port`` pair via the + ## specified protocol. The procedure iterates through possible + ## resolutions of the ``address`` until it succeeds, meaning that it + ## seamlessly works with both IPv4 and IPv6. + ## Returns AsyncSocket ready to send or receive data. + let asyncFd = await asyncdispatch.dial(address, port, protocol) + let sockType = protocol.toSockType() + let domain = getSockDomain(asyncFd.SocketHandle) + result = newAsyncSocket(asyncFd, domain, sockType, protocol, buffered) proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} = ## Connects ``socket`` to server at ``address:port``. diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index eee03d7ae..4b0d08292 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -44,21 +44,23 @@ const cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediate.} = +template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = ## encodes `s` into base64 representation. After `lineLen` characters, a ## `newline` is added. var total = ((len(s) + 2) div 3) * 4 - var numLines = (total + lineLen - 1) div lineLen + let numLines = (total + lineLen - 1) div lineLen if numLines > 0: inc(total, (numLines - 1) * newLine.len) result = newString(total) - var i = 0 - var r = 0 - var currLine = 0 + var + i = 0 + r = 0 + currLine = 0 while i < s.len - 2: - var a = ord(s[i]) - var b = ord(s[i+1]) - var c = ord(s[i+2]) + let + a = ord(s[i]) + b = ord(s[i+1]) + c = ord(s[i+2]) result[r] = cb64[a shr 2] result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] result[r+2] = cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)] @@ -74,8 +76,9 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat currLine = 0 if i < s.len-1: - var a = ord(s[i]) - var b = ord(s[i+1]) + let + a = ord(s[i]) + b = ord(s[i+1]) result[r] = cb64[a shr 2] result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] result[r+2] = cb64[((b and 0x0F) shl 2)] @@ -83,7 +86,7 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat if r+4 != result.len: setLen(result, r+4) elif i < s.len: - var a = ord(s[i]) + let a = ord(s[i]) result[r] = cb64[a shr 2] result[r+1] = cb64[(a and 3) shl 4] result[r+2] = '=' @@ -127,15 +130,17 @@ proc decode*(s: string): string = # total is an upper bound, as we will skip arbitrary whitespace: result = newString(total) - var i = 0 - var r = 0 + var + i = 0 + r = 0 while true: while s[i] in Whitespace: inc(i) if i < s.len-3: - var a = s[i].decodeByte - var b = s[i+1].decodeByte - var c = s[i+2].decodeByte - var d = s[i+3].decodeByte + let + a = s[i].decodeByte + b = s[i+1].decodeByte + c = s[i+2].decodeByte + d = s[i+3].decodeByte result[r] = chr((a shl 2) and 0xff or ((b shr 4) and 0x03)) result[r+1] = chr((b shl 4) and 0xff or ((c shr 2) and 0x0F)) @@ -169,4 +174,4 @@ when isMainModule: for t in items(tests): assert decode(encode(t)) == t assert decode(encode(t, lineLen=40)) == t - assert decode(encode(t, lineLen=76)) == t \ No newline at end of file + assert decode(encode(t, lineLen=76)) == t diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index b6c00966f..969802cfc 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -785,7 +785,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B], t[].sort(cmp) proc del*[A, B](t: var OrderedTable[A, B], key: A) = - ## deletes `key` from ordered hash table `t`. O(n) comlexity. + ## deletes `key` from ordered hash table `t`. O(n) complexity. var n: OrderedKeyValuePairSeq[A, B] newSeq(n, len(t.data)) var h = t.first @@ -804,7 +804,7 @@ proc del*[A, B](t: var OrderedTable[A, B], key: A) = h = nxt proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = - ## deletes `key` from ordered hash table `t`. O(n) comlexity. + ## deletes `key` from ordered hash table `t`. O(n) complexity. t[].del(key) # ------------------------------ count tables ------------------------------- diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index 8090cd49d..7d850798c 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -39,7 +39,7 @@ proc setCookie*(key, value: string, domain = "", path = "", if domain != "": result.add("; Domain=" & domain) if path != "": result.add("; Path=" & path) if expires != "": result.add("; Expires=" & expires) - if secure: result.add("; secure") + if secure: result.add("; Secure") if httpOnly: result.add("; HttpOnly") proc setCookie*(key, value: string, expires: TimeInfo, @@ -50,7 +50,7 @@ proc setCookie*(key, value: string, expires: TimeInfo, ## ## **Note:** UTC is assumed as the timezone for ``expires``. return setCookie(key, value, domain, path, - format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"), + format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'GMT'"), noname, secure, httpOnly) when isMainModule: diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 62c7e2067..1b8a20b65 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -434,7 +434,7 @@ proc `[]=`*(p: var MultipartData, name: string, ## "<html><head></head><body><p>test</p></body></html>") p.add(name, file.content, file.name, file.contentType) -proc format(p: MultipartData): tuple[header, body: string] = +proc format(p: MultipartData): tuple[contentType, body: string] = if p == nil or p.content == nil or p.content.len == 0: return ("", "") @@ -449,7 +449,7 @@ proc format(p: MultipartData): tuple[header, body: string] = if not found: break - result.header = "Content-Type: multipart/form-data; boundary=" & bound & "\c\L" + result.contentType = "multipart/form-data; boundary=" & bound result.body = "" for s in p.content: result.body.add("--" & bound & "\c\L" & s) @@ -640,7 +640,7 @@ proc post*(url: string, extraHeaders = "", body = "", ## ``multipart/form-data`` POSTs comfortably. ## ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead. - let (mpHeaders, mpBody) = format(multipart) + let (mpContentType, mpBody) = format(multipart) template withNewLine(x): untyped = if x.len > 0 and not x.endsWith("\c\L"): @@ -650,9 +650,12 @@ proc post*(url: string, extraHeaders = "", body = "", var xb = mpBody.withNewLine() & body - var xh = extraHeaders.withNewLine() & mpHeaders.withNewLine() & + var xh = extraHeaders.withNewLine() & withNewLine("Content-Length: " & $len(xb)) + if not multipart.isNil: + xh.add(withNewLine("Content-Type: " & mpContentType)) + result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent, proxy) var lastURL = url @@ -1030,32 +1033,38 @@ proc newConnection(client: HttpClient | AsyncHttpClient, if client.currentURL.hostname != url.hostname or client.currentURL.scheme != url.scheme or client.currentURL.port != url.port: + let isSsl = url.scheme.toLowerAscii() == "https" + + if isSsl and not defined(ssl): + raise newException(HttpRequestError, + "SSL support is not available. Cannot connect over SSL.") + if client.connected: client.close() - when client is HttpClient: - client.socket = newSocket() - elif client is AsyncHttpClient: - client.socket = newAsyncSocket() - else: {.fatal: "Unsupported client type".} - # TODO: I should be able to write 'net.Port' here... let port = if url.port == "": - if url.scheme.toLower() == "https": + if isSsl: nativesockets.Port(443) else: nativesockets.Port(80) else: nativesockets.Port(url.port.parseInt) - if url.scheme.toLower() == "https": - when defined(ssl): - client.sslContext.wrapSocket(client.socket) - else: - raise newException(HttpRequestError, - "SSL support is not available. Cannot connect over SSL.") + when client is HttpClient: + client.socket = await net.dial(url.hostname, port) + elif client is AsyncHttpClient: + client.socket = await asyncnet.dial(url.hostname, port) + else: {.fatal: "Unsupported client type".} + + when defined(ssl): + if isSsl: + try: + client.sslContext.wrapConnectedSocket(client.socket, handshakeAsClient) + except: + client.socket.close() + raise getCurrentException() - await client.socket.connect(url.hostname, port) client.currentURL = url client.connected = true @@ -1188,7 +1197,7 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", ## ## This procedure will follow redirects up to a maximum number of redirects ## specified in ``client.maxRedirects``. - let (mpHeader, mpBody) = format(multipart) + let (mpContentType, mpBody) = format(multipart) # TODO: Support FutureStream for `body` parameter. template withNewLine(x): untyped = if x.len > 0 and not x.endsWith("\c\L"): @@ -1199,7 +1208,7 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", var headers = newHttpHeaders() if multipart != nil: - headers["Content-Type"] = mpHeader.split(": ")[1] + headers["Content-Type"] = mpContentType headers["Content-Length"] = $len(xb) result = await client.requestAux(url, $HttpPOST, xb, headers) diff --git a/lib/pure/includes/asynccommon.nim b/lib/pure/includes/asynccommon.nim new file mode 100644 index 000000000..a7d2f803f --- /dev/null +++ b/lib/pure/includes/asynccommon.nim @@ -0,0 +1,201 @@ +template newAsyncNativeSocketImpl(domain, sockType, protocol) = + let handle = newNativeSocket(domain, sockType, protocol) + if handle == osInvalidSocket: + raiseOSError(osLastError()) + handle.setBlocking(false) + when defined(macosx): + handle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) + result = handle.AsyncFD + register(result) + +proc newAsyncNativeSocket*(domain: cint, sockType: cint, + protocol: cint): AsyncFD = + newAsyncNativeSocketImpl(domain, sockType, protocol) + +proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET, + sockType: SockType = SOCK_STREAM, + protocol: Protocol = IPPROTO_TCP): AsyncFD = + newAsyncNativeSocketImpl(domain, sockType, protocol) + +when defined(windows) or defined(nimdoc): + proc bindToDomain(handle: SocketHandle, domain: Domain) = + # Extracted into a separate proc, because connect() on Windows requires + # the socket to be initially bound. + template doBind(saddr) = + if bindAddr(handle, cast[ptr SockAddr](addr(saddr)), + sizeof(saddr).SockLen) < 0'i32: + raiseOSError(osLastError()) + + if domain == Domain.AF_INET6: + var saddr: Sockaddr_in6 + saddr.sin6_family = int16(toInt(domain)) + doBind(saddr) + else: + var saddr: Sockaddr_in + saddr.sin_family = int16(toInt(domain)) + doBind(saddr) + + proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] = + let retFuture = newFuture[void]("doConnect") + result = retFuture + + var ol = PCustomOverlapped() + GC_ref(ol) + ol.data = CompletionData(fd: socket, cb: + proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = + if not retFuture.finished: + if errcode == OSErrorCode(-1): + retFuture.complete() + else: + retFuture.fail(newException(OSError, osErrorMsg(errcode))) + ) + + let ret = connectEx(socket.SocketHandle, addrInfo.ai_addr, + cint(addrInfo.ai_addrlen), nil, 0, nil, + cast[POVERLAPPED](ol)) + if ret: + # Request to connect completed immediately. + retFuture.complete() + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it + # will free ``ol``. + else: + let lastError = osLastError() + if lastError.int32 != ERROR_IO_PENDING: + # With ERROR_IO_PENDING ``ol`` will be deallocated in ``poll``, + # and the future will be completed/failed there, too. + GC_unref(ol) + retFuture.fail(newException(OSError, osErrorMsg(lastError))) +else: + proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] = + let retFuture = newFuture[void]("doConnect") + result = retFuture + + proc cb(fd: AsyncFD): bool = + let ret = SocketHandle(fd).getSockOptInt( + cint(SOL_SOCKET), cint(SO_ERROR)) + if ret == 0: + # We have connected. + retFuture.complete() + return true + elif ret == EINTR: + # interrupted, keep waiting + return false + else: + retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret)))) + return true + + let ret = connect(socket.SocketHandle, + addrInfo.ai_addr, + addrInfo.ai_addrlen.Socklen) + if ret == 0: + # Request to connect completed immediately. + retFuture.complete() + else: + let lastError = osLastError() + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: + addWrite(socket, cb) + else: + retFuture.fail(newException(OSError, osErrorMsg(lastError))) + +template asyncAddrInfoLoop(addrInfo: ptr AddrInfo, fd: untyped, + protocol: Protocol = IPPROTO_RAW) = + ## Iterates through the AddrInfo linked list asynchronously + ## until the connection can be established. + const shouldCreateFd = not declared(fd) + + when shouldCreateFd: + let sockType = protocol.toSockType() + + var fdPerDomain: array[low(Domain).ord..high(Domain).ord, AsyncFD] + for i in low(fdPerDomain)..high(fdPerDomain): + fdPerDomain[i] = osInvalidSocket.AsyncFD + template closeUnusedFds(domainToKeep = -1) {.dirty.} = + for i, fd in fdPerDomain: + if fd != osInvalidSocket.AsyncFD and i != domainToKeep: + fd.closeSocket() + + var lastException: ref Exception + var curAddrInfo = addrInfo + var domain: Domain + when shouldCreateFd: + var curFd: AsyncFD + else: + var curFd = fd + proc tryNextAddrInfo(fut: Future[void]) {.gcsafe.} = + if fut == nil or fut.failed: + if fut != nil: + lastException = fut.readError() + + while curAddrInfo != nil: + let domainOpt = curAddrInfo.ai_family.toKnownDomain() + if domainOpt.isSome: + domain = domainOpt.unsafeGet() + break + curAddrInfo = curAddrInfo.ai_next + + if curAddrInfo == nil: + freeAddrInfo(addrInfo) + when shouldCreateFd: + closeUnusedFds() + if lastException != nil: + retFuture.fail(lastException) + else: + retFuture.fail(newException( + IOError, "Couldn't resolve address: " & address)) + return + + when shouldCreateFd: + curFd = fdPerDomain[ord(domain)] + if curFd == osInvalidSocket.AsyncFD: + try: + curFd = newAsyncNativeSocket(domain, sockType, protocol) + except: + freeAddrInfo(addrInfo) + closeUnusedFds() + raise getCurrentException() + when defined(windows): + curFd.SocketHandle.bindToDomain(domain) + fdPerDomain[ord(domain)] = curFd + + doConnect(curFd, curAddrInfo).callback = tryNextAddrInfo + curAddrInfo = curAddrInfo.ai_next + else: + freeAddrInfo(addrInfo) + when shouldCreateFd: + closeUnusedFds(ord(domain)) + retFuture.complete(curFd) + else: + retFuture.complete() + + tryNextAddrInfo(nil) + +proc dial*(address: string, port: Port, + protocol: Protocol = IPPROTO_TCP): Future[AsyncFD] = + ## Establishes connection to the specified ``address``:``port`` pair via the + ## specified protocol. The procedure iterates through possible + ## resolutions of the ``address`` until it succeeds, meaning that it + ## seamlessly works with both IPv4 and IPv6. + ## Returns the async file descriptor, registered in the dispatcher of + ## the current thread, ready to send or receive data. + let retFuture = newFuture[AsyncFD]("dial") + result = retFuture + let sockType = protocol.toSockType() + + let aiList = getAddrInfo(address, port, Domain.AF_UNSPEC, sockType, protocol) + asyncAddrInfoLoop(aiList, noFD, protocol) + +proc connect*(socket: AsyncFD, address: string, port: Port, + domain = Domain.AF_INET): Future[void] = + let retFuture = newFuture[void]("connect") + result = retFuture + + when defined(windows): + verifyPresence(socket) + else: + assert getSockDomain(socket.SocketHandle) == domain + + let aiList = getAddrInfo(address, port, domain) + when defined(windows): + socket.SocketHandle.bindToDomain(domain) + asyncAddrInfoLoop(aiList, socket) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index b3fc47749..564f952d3 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -1213,22 +1213,22 @@ when not defined(js): proc parseJson*(s: Stream, filename: string): JsonNode = ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## for nice error messages. - ## If `s` contains extra data, it will raising `JsonParsingError`. + ## If `s` contains extra data, it will raise `JsonParsingError`. var p: JsonParser p.open(s, filename) defer: p.close() discard getTok(p) # read first token result = p.parseJson() - eat(p, tkEof) # check there are no exstra data + eat(p, tkEof) # check if there is no extra data proc parseJson*(buffer: string): JsonNode = ## Parses JSON from `buffer`. - ## If `buffer` contains extra data, it will raising `JsonParsingError`. + ## If `buffer` contains extra data, it will raise `JsonParsingError`. result = parseJson(newStringStream(buffer), "input") proc parseFile*(filename: string): JsonNode = ## Parses `file` into a `JsonNode`. - ## If `file` contains extra data, it will raising `JsonParsingError`. + ## If `file` contains extra data, it will raise `JsonParsingError`. var stream = newFileStream(filename, fmRead) if stream == nil: raise newException(IOError, "cannot read from file: " & filename) @@ -1502,7 +1502,7 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = doAssert result.len > 0 proc processType(typeName: NimNode, obj: NimNode, - jsonNode: NimNode): NimNode {.compileTime.} = + jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} = ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``. ## ## Sample ``ObjectTy``: @@ -1524,6 +1524,20 @@ proc processType(typeName: NimNode, obj: NimNode, for field in obj[2]: let nodes = processObjField(field, jsonNode) result.add(nodes) + + # Object might be null. So we need to check for that. + if isRef: + result = quote do: + verifyJsonKind(`jsonNode`, {JObject, JNull}, astToStr(`jsonNode`)) + if `jsonNode`.kind == JNull: + nil + else: + `result` + else: + result = quote do: + verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`)); + `result` + of nnkEnumTy: let instType = toIdentNode(getTypeInst(typeName)) let getEnumCall = createGetEnumCall(jsonNode, instType) @@ -1536,8 +1550,8 @@ proc processType(typeName: NimNode, obj: NimNode, of "float": result = quote do: ( - verifyJsonKind(`jsonNode`, {JFloat}, astToStr(`jsonNode`)); - `jsonNode`.fnum + verifyJsonKind(`jsonNode`, {JFloat, JInt}, astToStr(`jsonNode`)); + if `jsonNode`.kind == JFloat: `jsonNode`.fnum else: `jsonNode`.num.float ) of "string": result = quote do: @@ -1551,6 +1565,12 @@ proc processType(typeName: NimNode, obj: NimNode, verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); `jsonNode`.num.int ) + of "biggestint": + result = quote do: + ( + verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); + `jsonNode`.num + ) of "bool": result = quote do: ( @@ -1585,7 +1605,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = typeName = typeName[0 .. ^12] let obj = getType(typeSym[1]) - result = processType(newIdentNode(typeName), obj, jsonNode) + result = processType(newIdentNode(typeName), obj, jsonNode, true) of "seq": let seqT = typeSym[1] let forLoopI = newIdentNode("i") @@ -1605,17 +1625,21 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = else: # Generic type. let obj = getType(typeSym) - result = processType(typeSym, obj, jsonNode) + result = processType(typeSym, obj, jsonNode, false) of nnkSym: let obj = getType(typeSym) - result = processType(typeSym, obj, jsonNode) + if obj.kind == nnkBracketExpr: + # When `Sym "Foo"` turns out to be a `ref object`. + result = createConstructor(obj, jsonNode) + else: + result = processType(typeSym, obj, jsonNode, false) else: doAssert false, "Unable to create constructor for: " & $typeSym.kind doAssert(not result.isNil(), "Constructor not initialised.") proc postProcess(node: NimNode): NimNode -proc postProcessValue(value: NimNode, depth=0): NimNode = +proc postProcessValue(value: NimNode): NimNode = ## Looks for object constructors and calls the ``postProcess`` procedure ## on them. Otherwise it just returns the node as-is. case value.kind @@ -1736,9 +1760,10 @@ macro to*(node: JsonNode, T: typedesc): untyped = doAssert(($typeNode[0]).normalize == "typedesc") result = createConstructor(typeNode[1], node) - result = postProcess(result) + # TODO: Rename postProcessValue and move it (?) + result = postProcessValue(result) - #echo(toStrLit(result)) + # echo(toStrLit(result)) when false: import os diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index fa3fa4aea..7568408a6 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -12,7 +12,7 @@ # TODO: Clean up the exports a bit and everything else in general. -import os +import os, options when hostOS == "solaris": {.passl: "-lsocket -lnsl".} @@ -52,9 +52,11 @@ type Domain* = enum ## domain, which specifies the protocol family of the ## created socket. Other domains than those that are listed ## here are unsupported. - AF_UNIX, ## for local socket (using a file). Unsupported on Windows. + AF_UNSPEC = 0, ## unspecified domain (can be detected automatically by + ## some procedures, such as getaddrinfo) + AF_UNIX = 1, ## for local socket (using a file). Unsupported on Windows. AF_INET = 2, ## for network protocol IPv4 or - AF_INET6 = 23 ## for network protocol IPv6. + AF_INET6 = when defined(macosx): 30 else: 23 ## for network protocol IPv6. SockType* = enum ## second argument to `socket` proc SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets @@ -125,10 +127,19 @@ proc toInt*(p: Protocol): cint when not useWinVersion: proc toInt(domain: Domain): cshort = case domain + of AF_UNSPEC: result = posix.AF_UNSPEC.cshort of AF_UNIX: result = posix.AF_UNIX.cshort of AF_INET: result = posix.AF_INET.cshort of AF_INET6: result = posix.AF_INET6.cshort - else: discard + + proc toKnownDomain*(family: cint): Option[Domain] = + ## Converts the platform-dependent ``cint`` to the Domain or none(), + ## if the ``cint`` is not known. + result = if family == posix.AF_UNSPEC: some(Domain.AF_UNSPEC) + elif family == posix.AF_UNIX: some(Domain.AF_UNIX) + elif family == posix.AF_INET: some(Domain.AF_INET) + elif family == posix.AF_INET6: some(Domain.AF_INET6) + else: none(Domain) proc toInt(typ: SockType): cint = case typ @@ -136,7 +147,6 @@ when not useWinVersion: of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_RAW: result = posix.SOCK_RAW - else: discard proc toInt(p: Protocol): cint = case p @@ -146,18 +156,33 @@ when not useWinVersion: of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP - else: discard else: proc toInt(domain: Domain): cshort = result = toU16(ord(domain)) + proc toKnownDomain*(family: cint): Option[Domain] = + ## Converts the platform-dependent ``cint`` to the Domain or none(), + ## if the ``cint`` is not known. + result = if family == winlean.AF_UNSPEC: some(Domain.AF_UNSPEC) + elif family == winlean.AF_INET: some(Domain.AF_INET) + elif family == winlean.AF_INET6: some(Domain.AF_INET6) + else: none(Domain) + proc toInt(typ: SockType): cint = result = cint(ord(typ)) proc toInt(p: Protocol): cint = result = cint(ord(p)) +proc toSockType*(protocol: Protocol): SockType = + result = case protocol + of IPPROTO_TCP: + SOCK_STREAM + of IPPROTO_UDP: + SOCK_DGRAM + of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP: + SOCK_RAW proc newNativeSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, @@ -392,14 +417,14 @@ proc getHostname*(): string {.tags: [ReadIOEffect].} = proc getSockDomain*(socket: SocketHandle): Domain = ## returns the socket's domain (AF_INET or AF_INET6). - var name: SockAddr + var name: Sockaddr_in6 var namelen = sizeof(name).SockLen if getsockname(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) - if name.sa_family == nativeAfInet: + if name.sin6_family == nativeAfInet: result = AF_INET - elif name.sa_family == nativeAfInet6: + elif name.sin6_family == nativeAfInet6: result = AF_INET6 else: raiseOSError(osLastError(), "unknown socket family in getSockFamily") @@ -410,17 +435,23 @@ proc getAddrString*(sockAddr: ptr SockAddr): string = if sockAddr.sa_family == nativeAfInet: result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr) elif sockAddr.sa_family == nativeAfInet6: + let addrLen = when not useWinVersion: posix.INET6_ADDRSTRLEN + else: 46 # it's actually 46 in both cases + result = newString(addrLen) + let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr when not useWinVersion: - # TODO: Windows - result = newString(posix.INET6_ADDRSTRLEN) - let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr - discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring, - result.len.int32) + if posix.inet_ntop(posix.AF_INET6, addr6, addr result[0], + result.len.int32) == nil: + raiseOSError(osLastError()) if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0: result = result.substr("::ffff:".len) + else: + if winlean.inet_ntop(winlean.AF_INET6, addr6, addr result[0], + result.len.int32) == nil: + raiseOSError(osLastError()) + setLen(result, len(cstring(result))) else: - raiseOSError(osLastError(), "unknown socket family in getAddrString") - + raise newException(IOError, "Unknown socket family in getAddrString") proc getSockName*(socket: SocketHandle): Port = ## returns the socket's associated port number. diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 56f8b9399..d175bd537 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -66,7 +66,7 @@ ## {.deadCodeElim: on.} -import nativesockets, os, strutils, parseutils, times, sets +import nativesockets, os, strutils, parseutils, times, sets, options export Port, `$`, `==` export Domain, SockType, Protocol @@ -669,7 +669,7 @@ proc close*(socket: Socket) = ## Closes a socket. try: when defineSsl: - if socket.isSSL: + if socket.isSSL and socket.sslHandle != nil: ErrClearError() # As we are closing the underlying socket immediately afterwards, # it is valid, under the TLS standard, to perform a unidirectional @@ -1477,6 +1477,63 @@ proc isIpAddress*(address_str: string): bool {.tags: [].} = return false return true +proc dial*(address: string, port: Port, + protocol = IPPROTO_TCP, buffered = true): Socket + {.tags: [ReadIOEffect, WriteIOEffect].} = + ## Establishes connection to the specified ``address``:``port`` pair via the + ## specified protocol. The procedure iterates through possible + ## resolutions of the ``address`` until it succeeds, meaning that it + ## seamlessly works with both IPv4 and IPv6. + ## Returns Socket ready to send or receive data. + let sockType = protocol.toSockType() + + let aiList = getAddrInfo(address, port, AF_UNSPEC, sockType, protocol) + + var fdPerDomain: array[low(Domain).ord..high(Domain).ord, SocketHandle] + for i in low(fdPerDomain)..high(fdPerDomain): + fdPerDomain[i] = osInvalidSocket + template closeUnusedFds(domainToKeep = -1) {.dirty.} = + for i, fd in fdPerDomain: + if fd != osInvalidSocket and i != domainToKeep: + fd.close() + + var success = false + var lastError: OSErrorCode + var it = aiList + var domain: Domain + var lastFd: SocketHandle + while it != nil: + let domainOpt = it.ai_family.toKnownDomain() + if domainOpt.isNone: + it = it.ai_next + continue + domain = domainOpt.unsafeGet() + lastFd = fdPerDomain[ord(domain)] + if lastFd == osInvalidSocket: + lastFd = newNativeSocket(domain, sockType, protocol) + if lastFd == osInvalidSocket: + # we always raise if socket creation failed, because it means a + # network system problem (e.g. not enough FDs), and not an unreachable + # address. + let err = osLastError() + freeAddrInfo(aiList) + closeUnusedFds() + raiseOSError(err) + fdPerDomain[ord(domain)] = lastFd + if connect(lastFd, it.ai_addr, it.ai_addrlen.SockLen) == 0'i32: + success = true + break + lastError = osLastError() + it = it.ai_next + freeAddrInfo(aiList) + closeUnusedFds(ord(domain)) + + if success: + result = newSocket(lastFd, domain, sockType, protocol) + elif lastError != 0.OSErrorCode: + raiseOSError(lastError) + else: + raise newException(IOError, "Couldn't resolve address: " & address) proc connect*(socket: Socket, address: string, port = Port(0)) {.tags: [ReadIOEffect].} = diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index e4c97b260..60b53dbe0 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -69,10 +69,9 @@ var proc genOid*(): Oid = ## generates a new OID. proc rand(): cint {.importc: "rand", header: "<stdlib.h>", nodecl.} - proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".} proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.} - var t = gettime(nil) + var t = getTime().int32 var i = int32(atomicInc(incr)) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 71991e35a..7720fb2a6 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -569,7 +569,7 @@ when declared(getEnv) or defined(nimscript): ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``. proc findExe*(exe: string, followSymlinks: bool = true; - extensions=ExeExts): string {. + extensions: openarray[string]=ExeExts): string {. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = ## Searches for `exe` in the current working directory and then ## in directories listed in the ``PATH`` environment variable. diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 0f37f8fe0..82a0c0c65 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -209,9 +209,16 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl, ## ## **Warning**: Be careful when using waitForExit for processes created without ## poParentStreams because they may fill output buffers, causing deadlock. + ## + ## On posix, if the process has exited because of a signal, 128 + signal + ## number will be returned. + proc peekExitCode*(p: Process): int {.tags: [].} ## return -1 if the process is still running. Otherwise the process' exit code + ## + ## On posix, if the process has exited because of a signal, 128 + signal + ## number will be returned. proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} ## returns ``p``'s input stream for writing to. @@ -679,6 +686,16 @@ elif not defined(useNimRtl): readIdx = 0 writeIdx = 1 + proc isExitStatus(status: cint): bool = + WIFEXITED(status) or WIFSIGNALED(status) + + proc exitStatus(status: cint): cint = + if WIFSIGNALED(status): + # like the shell! + 128 + WTERMSIG(status) + else: + WEXITSTATUS(status) + proc envToCStringArray(t: StringTableRef): cstringArray = result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) var i = 0 @@ -967,7 +984,7 @@ elif not defined(useNimRtl): var status : cint = 1 ret = waitpid(p.id, status, WNOHANG) if ret == int(p.id): - if WIFEXITED(status): + if isExitStatus(status): p.exitStatus = status return false else: @@ -990,7 +1007,9 @@ elif not defined(useNimRtl): import kqueue, times proc waitForExit(p: Process, timeout: int = -1): int = - if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) + if p.exitStatus != -3: + return exitStatus(p.exitStatus) + if timeout == -1: var status : cint = 1 if waitpid(p.id, status, 0) < 0: @@ -1041,7 +1060,7 @@ elif not defined(useNimRtl): finally: discard posix.close(kqFD) - result = ((p.exitStatus and 0xFF00) shr 8) + result = exitStatus(p.exitStatus) else: import times @@ -1077,7 +1096,9 @@ elif not defined(useNimRtl): # ``waitPid`` fails if the process is not running anymore. But then # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is # initialized with -3, wrong success exit codes are prevented. - if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) + if p.exitStatus != -3: + return exitStatus(p.exitStatus) + if timeout == -1: var status : cint = 1 if waitpid(p.id, status, 0) < 0: @@ -1151,17 +1172,19 @@ elif not defined(useNimRtl): if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: raiseOSError(osLastError()) - result = ((p.exitStatus and 0xFF00) shr 8) + result = exitStatus(p.exitStatus) proc peekExitCode(p: Process): int = var status = cint(0) result = -1 - if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) + if p.exitStatus != -3: + return exitStatus(p.exitStatus) + var ret = waitpid(p.id, status, WNOHANG) if ret > 0: - if WIFEXITED(status): + if isExitStatus(status): p.exitStatus = status - result = (status and 0xFF00) shr 8 + result = exitStatus(status) proc createStream(stream: var Stream, handle: var FileHandle, fileMode: FileMode) = @@ -1189,7 +1212,8 @@ elif not defined(useNimRtl): proc execCmd(command: string): int = when defined(linux): - result = csystem(command) shr 8 + let tmp = csystem(command) + result = if tmp == -1: tmp else: exitStatus(tmp) else: result = csystem(command) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index ba745cfd3..c7e0ed1da 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -50,6 +50,7 @@ proc add*(url: var Url, a: Url) {.deprecated.} = proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false + var inIPv6 = false while true: case authority[i] of '@': @@ -59,7 +60,14 @@ proc parseAuthority(authority: string, result: var Uri) = result.hostname.setLen(0) inPort = false of ':': - inPort = true + if inIPv6: + result.hostname.add(authority[i]) + else: + inPort = true + of '[': + inIPv6 = true + of ']': + inIPv6 = false of '\0': break else: if inPort: @@ -346,6 +354,17 @@ when isMainModule: doAssert($test == str) block: + # IPv6 address + let str = "foo://[::1]:1234/bar?baz=true&qux#quux" + let uri = parseUri(str) + doAssert uri.scheme == "foo" + doAssert uri.hostname == "::1" + doAssert uri.port == "1234" + doAssert uri.path == "/bar" + doAssert uri.query == "baz=true&qux" + doAssert uri.anchor == "quux" + + block: let str = "urn:example:animal:ferret:nose" let test = parseUri(str) doAssert test.scheme == "urn" diff --git a/lib/system.nim b/lib/system.nim index 82d3bb7f7..9b41253cc 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2016,6 +2016,12 @@ proc min*(x, y: float): float {.magic: "MinF64", noSideEffect.} = if x <= y: x else: y proc max*(x, y: float): float {.magic: "MaxF64", noSideEffect.} = if y <= x: x else: y + +proc min*[T](x, y: T): T = + if x <= y: x else: y + +proc max*[T](x, y: T): T = + if y <= x: x else: y {.pop.} proc clamp*[T](x, a, b: T): T = diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 9ef4a02f2..8a81a550a 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -172,7 +172,7 @@ proc raiseIndexError() {.compilerproc, noreturn.} = proc raiseFieldError(f: string) {.compilerproc, noreturn.} = raise newException(FieldError, f & " is not accessible") -proc SetConstr() {.varargs, asmNoStackFrame, compilerproc.} = +proc setConstr() {.varargs, asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ $args = func_get_args(); diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index feee87bae..7c0497cc6 100644 --- a/lib/upcoming/asyncdispatch.nim +++ b/lib/upcoming/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, times, heapqueue, lists +import os, oids, tables, strutils, times, heapqueue, lists, options import nativesockets, net, deques @@ -325,68 +325,6 @@ when defined(windows) or defined(nimdoc): getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun) close(dummySock) - proc connect*(socket: AsyncFD, address: string, port: Port, - domain = nativesockets.AF_INET): Future[void] = - ## Connects ``socket`` to server at ``address:port``. - ## - ## Returns a ``Future`` which will complete when the connection succeeds - ## or an error occurs. - verifyPresence(socket) - var retFuture = newFuture[void]("connect") - # Apparently ``ConnectEx`` expects the socket to be initially bound: - var saddr: Sockaddr_in - saddr.sin_family = int16(toInt(domain)) - saddr.sin_port = 0 - saddr.sin_addr.s_addr = INADDR_ANY - if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)), - sizeof(saddr).SockLen) < 0'i32: - raiseOSError(osLastError()) - - var aiList = getAddrInfo(address, port, domain) - var success = false - var lastError: OSErrorCode - var it = aiList - while it != nil: - # "the OVERLAPPED structure must remain valid until the I/O completes" - # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx - var ol = PCustomOverlapped() - GC_ref(ol) - ol.data = CompletionData(fd: socket, cb: - proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = - if not retFuture.finished: - if errcode == OSErrorCode(-1): - retFuture.complete() - else: - retFuture.fail(newException(OSError, osErrorMsg(errcode))) - ) - - var ret = connectEx(socket.SocketHandle, it.ai_addr, - sizeof(Sockaddr_in).cint, nil, 0, nil, - cast[POVERLAPPED](ol)) - if ret: - # Request to connect completed immediately. - success = true - retFuture.complete() - # We don't deallocate ``ol`` here because even though this completed - # immediately poll will still be notified about its completion and it will - # free ``ol``. - break - else: - lastError = osLastError() - if lastError.int32 == ERROR_IO_PENDING: - # In this case ``ol`` will be deallocated in ``poll``. - success = true - break - else: - GC_unref(ol) - success = false - it = it.ai_next - - freeAddrInfo(aiList) - if not success: - retFuture.fail(newException(OSError, osErrorMsg(lastError))) - return retFuture - proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = ## Reads **up to** ``size`` bytes from ``socket``. Returned future will @@ -739,8 +677,8 @@ when defined(windows) or defined(nimdoc): var lpOutputBuf = newString(lpOutputLen) var dwBytesReceived: Dword let dwReceiveDataLength = 0.Dword # We don't want any data to be read. - let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) - let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) + let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16) + let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16) template failAccept(errcode) = if flags.isDisconnectionError(errcode): @@ -770,12 +708,14 @@ when defined(windows) or defined(nimdoc): dwLocalAddressLength, dwRemoteAddressLength, addr localSockaddr, addr localLen, addr remoteSockaddr, addr remoteLen) - register(clientSock.AsyncFD) - # TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 - retFuture.complete( - (address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), - client: clientSock.AsyncFD) - ) + try: + let address = getAddrString(remoteSockAddr) + register(clientSock.AsyncFD) + retFuture.complete((address: address, client: clientSock.AsyncFD)) + except: + # getAddrString may raise + clientSock.close() + retFuture.fail(getCurrentException()) var ol = PCustomOverlapped() GC_ref(ol) @@ -808,20 +748,6 @@ when defined(windows) or defined(nimdoc): return retFuture - proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD = - ## Creates a new socket and registers it with the dispatcher implicitly. - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - register(result) - - proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = - ## Creates a new socket and registers it with the dispatcher implicitly. - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - register(result) - proc closeSocket*(socket: AsyncFD) = ## Closes a socket and ensures that it is unregistered. socket.SocketHandle.close() @@ -1159,23 +1085,6 @@ else: var data = newAsyncData() p.selector.registerHandle(fd.SocketHandle, {}, data) - proc newAsyncNativeSocket*(domain: cint, sockType: cint, - protocol: cint): AsyncFD = - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - when defined(macosx): - result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) - register(result) - - proc newAsyncNativeSocket*(domain: Domain = AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = - result = newNativeSocket(domain, sockType, protocol).AsyncFD - result.SocketHandle.setBlocking(false) - when defined(macosx): - result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) - register(result) - proc closeSocket*(sock: AsyncFD) = let disp = getGlobalDispatcher() disp.selector.unregister(sock.SocketHandle) @@ -1331,50 +1240,6 @@ else: # Callback queue processing processPendingCallbacks(p) - proc connect*(socket: AsyncFD, address: string, port: Port, - domain = AF_INET): Future[void] = - var retFuture = newFuture[void]("connect") - - proc cb(fd: AsyncFD): bool = - var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR)) - if ret == 0: - # We have connected. - retFuture.complete() - return true - elif ret == EINTR: - # interrupted, keep waiting - return false - else: - retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret)))) - return true - - assert getSockDomain(socket.SocketHandle) == domain - var aiList = getAddrInfo(address, port, domain) - var success = false - var lastError: OSErrorCode - var it = aiList - while it != nil: - var ret = connect(socket.SocketHandle, it.ai_addr, it.ai_addrlen.Socklen) - if ret == 0: - # Request to connect completed immediately. - success = true - retFuture.complete() - break - else: - lastError = osLastError() - if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: - success = true - addWrite(socket, cb) - break - else: - success = false - it = it.ai_next - - freeAddrInfo(aiList) - if not success: - retFuture.fail(newException(OSError, osErrorMsg(lastError))) - return retFuture - proc recv*(socket: AsyncFD, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] = var retFuture = newFuture[string]("recv") @@ -1568,9 +1433,14 @@ else: else: retFuture.fail(newException(OSError, osErrorMsg(lastError))) else: - register(client.AsyncFD) - retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), - client.AsyncFD)) + try: + let address = getAddrString(cast[ptr SockAddr](addr sockAddress)) + register(client.AsyncFD) + retFuture.complete((address, client.AsyncFD)) + except: + # getAddrString may raise + client.close() + retFuture.fail(getCurrentException()) addRead(socket, cb) return retFuture @@ -1623,6 +1493,9 @@ else: data.readList.add(cb) p.selector.registerEvent(SelectEvent(ev), data) +# Common procedures between current and upcoming asyncdispatch +include includes.asynccommon + proc sleepAsync*(ms: int): Future[void] = ## Suspends the execution of the current async procedure for the next ## ``ms`` milliseconds. diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 1f8dc9ad6..164499543 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -495,7 +495,7 @@ type ai_family*: cint ## Address family of socket. ai_socktype*: cint ## Socket type. ai_protocol*: cint ## Protocol of socket. - ai_addrlen*: int ## Length of socket address. + ai_addrlen*: csize ## Length of socket address. ai_canonname*: cstring ## Canonical name of service location. ai_addr*: ptr SockAddr ## Socket address of socket. ai_next*: ptr AddrInfo ## Pointer to next in list. @@ -803,6 +803,7 @@ const SIO_GET_EXTENSION_FUNCTION_POINTER* = WSAIORW(IOC_WS2,6).DWORD SO_UPDATE_ACCEPT_CONTEXT* = 0x700B AI_V4MAPPED* = 0x0008 + AF_UNSPEC* = 0 AF_INET* = 2 AF_INET6* = 23 diff --git a/readme.md b/readme.md index 34208eb5e..30cc14079 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# ![Logo][image-nim-logo] Nim [![Build Status][badge-nim-travisci]][nim-travisci] +# <img src="https://raw.githubusercontent.com/nim-lang/assets/master/Art/logo-crown.png" height="28px"/> Nim [![Build Status][badge-nim-travisci]][nim-travisci] This repository contains the Nim compiler, Nim's stdlib, tools and documentation. For more information about Nim, including downloads and documentation for @@ -171,5 +171,4 @@ Copyright © 2006-2017 Andreas Rumpf, all rights reserved. [badge-nim-gratipay]: https://img.shields.io/gratipay/team/nim.svg?style=flat-square [badge-nim-bountysource]: https://img.shields.io/bountysource/team/nim/activity.svg?style=flat-square [badge-nim-bitcoin]: https://img.shields.io/badge/bitcoin-1BXfuKM2uvoD6mbx4g5xM3eQhLzkCK77tJ-D69134.svg?style=flat-square -[image-nim-logo]: https://images1-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://raw.githubusercontent.com/nim-lang/assets/master/Art/logo-crown.png&container=focus&resize_w=36&refresh=21600 [pull-request-instructions]: https://help.github.com/articles/using-pull-requests/ diff --git a/tests/async/tasyncdial.nim b/tests/async/tasyncdial.nim new file mode 100644 index 000000000..d70e14020 --- /dev/null +++ b/tests/async/tasyncdial.nim @@ -0,0 +1,53 @@ +discard """ + file: "tasyncdial.nim" + output: ''' +OK AF_INET +OK AF_INET6 +''' +""" + +import + nativesockets, os, asyncdispatch + +proc setupServerSocket(hostname: string, port: Port, domain: Domain): AsyncFD = + ## Creates a socket, binds it to the specified address, and starts listening for connecitons. + ## Registers the descriptor with the dispatcher of the current thread + ## Raises OSError in case of an error. + let fd = newNativeSocket(domain) + setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1) + var aiList = getAddrInfo(hostname, port, domain) + if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32: + freeAddrInfo(aiList) + raiseOSError(osLastError()) + freeAddrInfo(aiList) + if listen(fd) != 0: + raiseOSError(osLastError()) + setBlocking(fd, false) + result = fd.AsyncFD + register(result) + +proc doTest(domain: static[Domain]) {.async.} = + const + testHost = when domain == Domain.AF_INET6: "::1" else: "127.0.0.1" + testPort = Port(17384) + let serverFd = setupServerSocket(testHost, testPort, domain) + let acceptFut = serverFd.accept() + let clientFdFut = dial(testHost, testPort) + + let serverClientFd = await acceptFut + serverFd.closeSocket() + + let clientFd = await clientFdFut + + let recvFut = serverClientFd.recv(2) + await clientFd.send("Hi") + let msg = await recvFut + + serverClientFd.closeSocket() + clientFd.closeSocket() + + if msg == "Hi": + echo "OK ", domain + +waitFor(doTest(Domain.AF_INET)) +waitFor(doTest(Domain.AF_INET6)) diff --git a/tests/osproc/texitsignal.nim b/tests/osproc/texitsignal.nim new file mode 100644 index 000000000..c0bed70ee --- /dev/null +++ b/tests/osproc/texitsignal.nim @@ -0,0 +1,36 @@ +discard """ + output: '''true +true''' + targets: "c" +""" + +import os, osproc +when not defined(windows): + import posix + +# Checks that the environment is passed correctly in startProcess +# To do that launches a copy of itself with a new environment. + +if paramCount() == 0: + # Parent process + + let p = startProcess( + getAppFilename(), + args = @["child"], + options = {poStdErrToStdOut, poUsePath, poParentStreams} + ) + + echo p.running() + + p.kill() + + when defined(windows): + # windows kill happens using TerminateProcess(h, 0), so we should get a + # 0 here + echo p.waitForExit() == 0 + else: + # on posix (non-windows), kill sends SIGKILL + echo p.waitForExit() == 128 + SIGKILL + +else: + sleep(5000) # should get killed before this \ No newline at end of file diff --git a/tests/parser/twrongcmdsyntax.nim b/tests/parser/twrongcmdsyntax.nim new file mode 100644 index 000000000..affe72c34 --- /dev/null +++ b/tests/parser/twrongcmdsyntax.nim @@ -0,0 +1,6 @@ +discard """ + errormsg: '''identifier expected, but found 'echo 4''' + line: 6 +""" + +echo 4 +2 diff --git a/tests/stdlib/thttpclient.nim b/tests/stdlib/thttpclient.nim index 62c1ebee7..40324d92a 100644 --- a/tests/stdlib/thttpclient.nim +++ b/tests/stdlib/thttpclient.nim @@ -1,11 +1,13 @@ discard """ cmd: "nim c --threads:on -d:ssl $file" + exitcode: 0 + output: "OK" """ import strutils from net import TimeoutError -import httpclient, asyncdispatch +import nativesockets, os, httpclient, asyncdispatch const manualTests = false @@ -112,6 +114,40 @@ proc syncTest() = except: doAssert false, "TimeoutError should have been raised." -syncTest() +proc makeIPv6HttpServer(hostname: string, port: Port): AsyncFD = + let fd = newNativeSocket(AF_INET6) + setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1) + var aiList = getAddrInfo(hostname, port, AF_INET6) + if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32: + freeAddrInfo(aiList) + raiseOSError(osLastError()) + freeAddrInfo(aiList) + if listen(fd) != 0: + raiseOSError(osLastError()) + setBlocking(fd, false) + + var serverFd = fd.AsyncFD + register(serverFd) + result = serverFd + + proc onAccept(fut: Future[AsyncFD]) {.gcsafe.} = + if not fut.failed: + let clientFd = fut.read() + clientFd.send("HTTP/1.1 200 OK\r\LContent-Length: 0\r\LConnection: Closed\r\L\r\L").callback = proc() = + clientFd.closeSocket() + serverFd.accept().callback = onAccept + serverFd.accept().callback = onAccept + +proc ipv6Test() = + var client = newAsyncHttpClient() + let serverFd = makeIPv6HttpServer("::1", Port(18473)) + var resp = waitFor client.request("http://[::1]:18473/") + doAssert(resp.status == "200 OK") + serverFd.closeSocket() + client.close() +syncTest() waitFor(asyncTest()) +ipv6Test() + +echo "OK" diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim index 74b0d4dc3..323b3e1ee 100644 --- a/tests/stdlib/tjsonmacro.nim +++ b/tests/stdlib/tjsonmacro.nim @@ -159,11 +159,11 @@ when isMainModule: name: string age: int - Data = object + Data1 = object # TODO: Codegen bug when changed to ``Data``. person: Person list: seq[int] - var data = to(jsonNode, Data) + var data = to(jsonNode, Data1) doAssert data.person.name == "Nimmer" doAssert data.person.age == 21 doAssert data.list == @[1, 2, 3, 4] @@ -182,4 +182,48 @@ when isMainModule: } var result = to(node, TestEnum) - doAssert result.field == Bar \ No newline at end of file + doAssert result.field == Bar + + # Test ref type in field. + block: + var jsonNode = parseJson(""" + { + "person": { + "name": "Nimmer", + "age": 21 + }, + "list": [1, 2, 3, 4] + } + """) + + type + Person = ref object + name: string + age: int + + Data = object + person: Person + list: seq[int] + + var data = to(jsonNode, Data) + doAssert data.person.name == "Nimmer" + doAssert data.person.age == 21 + doAssert data.list == @[1, 2, 3, 4] + + jsonNode = parseJson(""" + { + "person": null, + "list": [1, 2, 3, 4] + } + """) + data = to(jsonNode, Data) + doAssert data.person.isNil + + block: + type + FooBar = object + field: float + + let x = parseJson("""{ "field": 5}""") + let data = to(x, FooBar) + doAssert data.field == 5.0 \ No newline at end of file diff --git a/tests/stdlib/tnetdial.nim b/tests/stdlib/tnetdial.nim new file mode 100644 index 000000000..da6088d70 --- /dev/null +++ b/tests/stdlib/tnetdial.nim @@ -0,0 +1,60 @@ +discard """ + cmd: "nim c --threads:on $file" + exitcode: 0 + output: "OK" +""" + +import os, net, nativesockets, asyncdispatch + +## Test for net.dial + +const port = Port(28431) + +proc initIPv6Server(hostname: string, port: Port): AsyncFD = + let fd = newNativeSocket(AF_INET6) + setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1) + var aiList = getAddrInfo(hostname, port, AF_INET6) + if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32: + freeAddrInfo(aiList) + raiseOSError(osLastError()) + freeAddrInfo(aiList) + if listen(fd) != 0: + raiseOSError(osLastError()) + setBlocking(fd, false) + + var serverFd = fd.AsyncFD + register(serverFd) + result = serverFd + +# Since net.dial is synchronous, we use main thread to setup server, +# and dial to it from another thread. + +proc testThread() {.thread.} = + let fd = net.dial("::1", port) + var s = newString(5) + doAssert fd.recv(addr s[0], 5) == 5 + if s == "Hello": + echo "OK" + fd.close() + +proc test() = + var t: Thread[void] + createThread(t, testThread) + + let serverFd = initIPv6Server("::1", port) + var done = false + + serverFd.accept().callback = proc(fut: Future[AsyncFD]) = + serverFd.closeSocket() + if not fut.failed: + let fd = fut.read() + fd.send("Hello").callback = proc() = + fd.closeSocket() + done = true + + while not done: + poll() + + joinThread(t) + +test() diff --git a/tests/tuples/tunpack_asgn.nim b/tests/tuples/tunpack_asgn.nim index a48fcff5d..1dc7ff074 100644 --- a/tests/tuples/tunpack_asgn.nim +++ b/tests/tuples/tunpack_asgn.nim @@ -21,6 +21,8 @@ proc pg[T](x, y: var T) = # test as a top level statement: var x, y, a, b: int +# test for regression: +(x, y) = (1, 2) (x, y) = fooBar() echo x, " ", y diff --git a/tests/tuples/tuple_subscript.nim b/tests/tuples/tuple_subscript.nim new file mode 100644 index 000000000..021793dc3 --- /dev/null +++ b/tests/tuples/tuple_subscript.nim @@ -0,0 +1,40 @@ +discard """ + output: '''5 +5 +str2 +str2 +4''' +""" + +proc`[]` (t: tuple, key: string): string = + for name, field in fieldPairs(t): + if name == key: + return $field + return "" + + +proc`[]` [A,B](t: tuple, key: string, op: (proc(x: A): B)): B = + for name, field in fieldPairs(t): + when field is A: + if name == key: + return op(field) + +proc`[]=`[T](t: var tuple, key: string, val: T) = + for name, field in fieldPairs(t): + when field is T: + if name == key: + field = val + +var tt = (a: 1, b: "str1") + +# test built in operator +tt[0] = 5 +echo tt[0] +echo `[]`(tt, 0) + + +# test overloaded operator +tt["b"] = "str2" +echo tt["b"] +echo `[]`(tt, "b") +echo tt["b", proc(s: string) : int = s.len] \ No newline at end of file diff --git a/web/news/e031_version_0_16_2.rst b/web/news/e031_version_0_16_2.rst index fd12bb822..63884700f 100644 --- a/web/news/e031_version_0_16_2.rst +++ b/web/news/e031_version_0_16_2.rst @@ -13,6 +13,9 @@ Changelog Changes affecting backwards compatibility ----------------------------------------- +- There are now two different HTTP response types, ``Response`` and + ``AsyncResponse``. ``AsyncResponse``'s ``body`` accessor returns a + ``Future[string]``! - ``httpclient.request`` now respects ``maxRedirects`` option. Previously redirects were handled only by ``get`` and ``post`` procs. - The IO routines now raise ``EOFError`` for the "end of file" condition. @@ -56,12 +59,17 @@ Changes affecting backwards compatibility checks. When fields within case objects are initialiazed, the compiler will now demand that the respective discriminator field has a matching known compile-time value. +- On posix, the results of `waitForExit`, `peekExitCode`, `execCmd` will return + 128 + signal number if the application terminates via signal. Library Additions ----------------- - Added ``system.onThreadDestruction``. - +- Added ``dial`` procedure to networking modules: ``net``, ``asyncdispatch``, + ``asyncnet``. It merges socket creation, address resolution, and connection + into single step. When using ``dial``, you don't have to worry about + IPv4 vs IPv6 problem. ``httpclient`` now supports IPv6. Tool Additions -------------- |