Merge branch 'devel' into araq

This commit is contained in:
Andreas Rumpf 2017-05-04 16:02:50 +02:00
commit 764cc02177
42 changed files with 960 additions and 453 deletions

View file

@ -1324,8 +1324,11 @@ proc shouldRecompile(code: Rope, cfile: Cfile): bool =
if optForceFullMake notin gGlobalOptions: if optForceFullMake notin gGlobalOptions:
if not equalsFile(code, cfile.cname): if not equalsFile(code, cfile.cname):
if isDefined("nimdiff"): if isDefined("nimdiff"):
if fileExists(cfile.cname):
copyFile(cfile.cname, cfile.cname & ".backup") copyFile(cfile.cname, cfile.cname & ".backup")
echo "diff ", cfile.cname, ".backup ", cfile.cname echo "diff ", cfile.cname, ".backup ", cfile.cname
else:
echo "new file ", cfile.cname
writeRope(code, cfile.cname) writeRope(code, cfile.cname)
return return
if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname): if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname):

View file

@ -449,7 +449,8 @@ proc unsignedTrimmerPHP(size: BiggestInt): Rope =
template unsignedTrimmer(size: BiggestInt): Rope = template unsignedTrimmer(size: BiggestInt): Rope =
size.unsignedTrimmerJS | size.unsignedTrimmerPHP 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 var x, y: TCompRes
gen(p, n.sons[1], x) gen(p, n.sons[1], x)
gen(p, n.sons[2], y) gen(p, n.sons[2], y)
@ -1863,8 +1864,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) =
var var
a, b: TCompRes a, b: TCompRes
useMagic(p, "SetConstr") useMagic(p, "setConstr")
r.res = rope("SetConstr(") r.res = rope("setConstr(")
r.kind = resExpr r.kind = resExpr
for i in countup(0, sonsLen(n) - 1): for i in countup(0, sonsLen(n) - 1):
if i > 0: add(r.res, ", ") if i > 0: add(r.res, ", ")
@ -1877,6 +1878,12 @@ proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) =
gen(p, it, a) gen(p, it, a)
add(r.res, a.res) add(r.res, a.res)
add(r.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) = proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
var a: TCompRes var a: TCompRes
@ -2128,6 +2135,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
else: r.res = rope(f.toStrMaxPrecision) else: r.res = rope(f.toStrMaxPrecision)
r.kind = resExpr r.kind = resExpr
of nkCallKinds: of nkCallKinds:
if isEmptyType(n.typ): genLineDir(p, n)
if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone): if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone):
genMagic(p, n, r) genMagic(p, n, r)
elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and

View file

@ -74,9 +74,9 @@ proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode =
let value = n.lastSon let value = n.lastSon
result = newNodeI(nkStmtList, n.info) 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) 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) var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart.sons[0] = tempAsNode vpart.sons[0] = tempAsNode
@ -115,7 +115,7 @@ proc createObj*(owner: PSym, info: TLineInfo): PType =
incl result.flags, tfFinal incl result.flags, tfFinal
result.n = newNodeI(nkRecList, info) result.n = newNodeI(nkRecList, info)
when true: when true:
let s = newSym(skType, getIdent("Env_" & info.toFilename & "_" & $info.line), let s = newSym(skType, getIdent("Env_" & info.toFilename),
owner, info) owner, info)
incl s.flags, sfAnon incl s.flags, sfAnon
s.typ = result s.typ = result
@ -137,10 +137,32 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
addSon(result, newSymNode(field)) addSon(result, newSymNode(field))
result.typ = field.typ 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) = proc addField*(obj: PType; s: PSym) =
# because of 'gensym' support, we have to mangle the name with its ID. # 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. # 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) let t = skipIntLit(s.typ)
field.typ = t field.typ = t
assert t.kind != tyStmt assert t.kind != tyStmt
@ -148,9 +170,9 @@ proc addField*(obj: PType; s: PSym) =
addSon(obj.n, newSymNode(field)) addSon(obj.n, newSymNode(field))
proc addUniqueField*(obj: PType; s: PSym) = proc addUniqueField*(obj: PType; s: PSym) =
let fieldName = getIdent(s.name.s & $s.id) if lookupInRecord(obj.n, s.id) == nil:
if lookupInRecord(obj.n, fieldName) == nil: var field = newSym(skField, getIdent(s.name.s & $obj.n.len), s.owner, s.info)
var field = newSym(skField, fieldName, s.owner, s.info) field.id = -s.id
let t = skipIntLit(s.typ) let t = skipIntLit(s.typ)
field.typ = t field.typ = t
assert t.kind != tyStmt assert t.kind != tyStmt
@ -159,13 +181,36 @@ proc addUniqueField*(obj: PType; s: PSym) =
proc newDotExpr(obj, b: PSym): PNode = proc newDotExpr(obj, b: PSym): PNode =
result = newNodeI(nkDotExpr, obj.info) 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 assert field != nil, b.name.s
addSon(result, newSymNode(obj)) addSon(result, newSymNode(obj))
addSon(result, newSymNode(field)) addSon(result, newSymNode(field))
result.typ = field.typ 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 # returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info) var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst).sons[0] 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 = proc getFieldFromObj*(t: PType; v: PSym): PSym =
assert v.kind != skField assert v.kind != skField
let fieldName = getIdent(v.name.s & $v.id)
var t = t var t = t
while true: while true:
assert t.kind == tyObject assert t.kind == tyObject
result = getSymFromList(t.n, fieldName) result = lookupInRecord(t.n, v.id)
if result != nil: break if result != nil: break
t = t.sons[0] t = t.sons[0]
if t == nil: break if t == nil: break
@ -203,7 +247,7 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode = proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node # 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 = proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
result = indirectAccess(newSymNode(a), b, info) result = indirectAccess(newSymNode(a), b, info)

View file

@ -17,15 +17,17 @@ proc addPath*(path: string, info: TLineInfo) =
proc versionSplitPos(s: string): int = proc versionSplitPos(s: string): int =
result = s.len-2 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 if s[result] != '-': result = s.len
const const
latest = "head" latest = ""
proc `<.`(a, b: string): bool = proc `<.`(a, b: string): bool =
# wether a has a smaller version than b: # 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 i = 0
var j = 0 var j = 0
var verA = 0 var verA = 0
@ -33,8 +35,13 @@ proc `<.`(a, b: string): bool =
while true: while true:
let ii = parseInt(a, verA, i) let ii = parseInt(a, verA, i)
let jj = parseInt(b, verB, j) let jj = parseInt(b, verB, j)
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 # 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 return jj > 0
if verA < verB: return true if verA < verB: return true
elif verA > verB: return false elif verA > verB: return false
# else: same version number; continue: # else: same version number; continue:
@ -46,12 +53,9 @@ proc `<.`(a, b: string): bool =
proc addPackage(packages: StringTableRef, p: string) = proc addPackage(packages: StringTableRef, p: string) =
let x = versionSplitPos(p) let x = versionSplitPos(p)
let name = p.substr(0, x-1) let name = p.substr(0, x-1)
if x < p.len: let version = if x < p.len: p.substr(x+1) else: ""
let version = p.substr(x+1)
if packages.getOrDefault(name) <. version: if packages.getOrDefault(name) <. version:
packages[name] = version packages[name] = version
else:
packages[name] = latest
iterator chosen(packages: StringTableRef): string = iterator chosen(packages: StringTableRef): string =
for key, val in pairs(packages): for key, val in pairs(packages):
@ -76,3 +80,18 @@ proc addPathRec(dir: string, info: TLineInfo) =
proc nimblePath*(path: string, info: TLineInfo) = proc nimblePath*(path: string, info: TLineInfo) =
addPathRec(path, info) addPathRec(path, info)
addNimblePath(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

View file

@ -1238,9 +1238,6 @@ proc parseExprStmt(p: var TParser): PNode =
addSon(result, e) addSon(result, e)
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break
elif p.tok.indent < 0 and isExprStart(p): 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: while true:
var e = parseExpr(p) var e = parseExpr(p)

View file

@ -1266,7 +1266,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
result.typ = makeTypeDesc(c, semTypeNode(c, n, nil)) result.typ = makeTypeDesc(c, semTypeNode(c, n, nil))
#result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) #result = symNodeFromType(c, semTypeNode(c, n, nil), n.info)
of tyTuple: of tyTuple:
checkSonsLen(n, 2) if n.len != 2: return nil
n.sons[0] = makeDeref(n.sons[0]) n.sons[0] = makeDeref(n.sons[0])
c.p.bracketExpr = n.sons[0] c.p.bracketExpr = n.sons[0]
# [] operator for tuples requires constant expression: # [] 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]) var idx = getOrdValue(n.sons[1])
if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)]
else: localError(n.info, errInvalidIndexValueForTuple) else: localError(n.info, errInvalidIndexValueForTuple)
else:
localError(n.info, errIndexTypesDoNotMatch)
result = n result = n
else:
result = nil
else: else:
let s = if n.sons[0].kind == nkSym: n.sons[0].sym let s = if n.sons[0].kind == nkSym: n.sons[0].sym
elif n[0].kind in nkSymChoices: n.sons[0][0].sym elif n[0].kind in nkSymChoices: n.sons[0][0].sym

View file

@ -517,7 +517,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
proc procVarcheck(n: PNode) = proc procVarcheck(n: PNode) =
if n.kind in nkSymChoices: if n.kind in nkSymChoices:
for x in n: procVarCheck(x) 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) localError(n.info, errXCannotBePassedToProcVar, n.sym.name.s)
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =

View file

@ -96,7 +96,7 @@ proc isDeepConstExpr*(n: PNode): bool =
result = true result = true
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv: of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
result = isDeepConstExpr(n.sons[1]) 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: for i in ord(n.kind == nkObjConstr) .. <n.len:
if not isDeepConstExpr(n.sons[i]): return false if not isDeepConstExpr(n.sons[i]): return false
if n.typ.isNil: result = true if n.typ.isNil: result = true

View file

@ -129,6 +129,8 @@ doc.file = """<?xml version="1.0" encoding="utf-8" ?>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Favicon --> <!-- 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=="/> <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=="/>

View file

@ -157,7 +157,7 @@ compilation fails. This exit code tells ``git bisect`` to skip the
current commit.:: current commit.::
git bisect start bad-commit good-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 The compiler's architecture
=========================== ===========================

View file

@ -178,7 +178,7 @@ proc bundleNimbleExe() =
bundleNimbleSrc() bundleNimbleSrc()
# now compile Nimble and copy it to $nim/bin for the installer.ini # now compile Nimble and copy it to $nim/bin for the installer.ini
# to pick it up: # 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) copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe)
proc buildNimble(latest: bool) = proc buildNimble(latest: bool) =
@ -205,7 +205,7 @@ proc buildNimble(latest: bool) =
else: else:
exec("git checkout -f stable") exec("git checkout -f stable")
exec("git pull") 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) copyExe(installDir / "src/nimble".exe, "bin/nimble".exe)
proc bundleNimsuggest(buildExe: bool) = proc bundleNimsuggest(buildExe: bool) =

View file

@ -97,15 +97,6 @@ when not defined(macosx):
## Second-granularity time of last status change. ## Second-granularity time of last status change.
result = s.st_ctim.tv_sec 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: when hasAioH:
proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".} proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".}
proc aio_error*(a1: ptr Taiocb): cint {.importc, header: "<aio.h>".} proc aio_error*(a1: ptr Taiocb): cint {.importc, header: "<aio.h>".}

View file

@ -602,3 +602,12 @@ var
include posix_linux_amd64_consts include posix_linux_amd64_consts
const POSIX_SPAWN_USEVFORK* = cint(0x40) # needs _GNU_SOURCE! 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

View file

@ -611,3 +611,18 @@ when hasSpawnH:
# OR'ing of flags: # OR'ing of flags:
const POSIX_SPAWN_USEVFORK* = cint(0) 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.

View file

@ -9,7 +9,7 @@
include "system/inclrtl" include "system/inclrtl"
import os, oids, tables, strutils, times, heapqueue import os, oids, tables, strutils, times, heapqueue, options
import nativesockets, net, deques import nativesockets, net, deques
@ -385,68 +385,6 @@ when defined(windows) or defined(nimdoc):
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
RemoteSockaddr, RemoteSockaddrLength) 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, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## 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 lpOutputBuf = newString(lpOutputLen)
var dwBytesReceived: Dword var dwBytesReceived: Dword
let dwReceiveDataLength = 0.Dword # We don't want any data to be read. let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
template failAccept(errcode) = template failAccept(errcode) =
if flags.isDisconnectionError(errcode): if flags.isDisconnectionError(errcode):
@ -785,12 +723,14 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength, dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen, addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen) addr remoteSockaddr, addr remoteLen)
try:
let address = getAddrString(remoteSockAddr)
register(clientSock.AsyncFD) register(clientSock.AsyncFD)
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 retFuture.complete((address: address, client: clientSock.AsyncFD))
retFuture.complete( except:
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), # getAddrString may raise
client: clientSock.AsyncFD) clientSock.close()
) retFuture.fail(getCurrentException())
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
@ -823,20 +763,6 @@ when defined(windows) or defined(nimdoc):
return retFuture 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) = proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered. ## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close() socket.SocketHandle.close()
@ -1015,23 +941,6 @@ else:
var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef) 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) = proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher() let disp = getGlobalDispatcher()
disp.selector.unregister(sock.SocketHandle) disp.selector.unregister(sock.SocketHandle)
@ -1115,50 +1024,6 @@ else:
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) 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, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv") var retFuture = newFuture[string]("recv")
@ -1320,11 +1185,20 @@ else:
else: else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else: else:
try:
let address = getAddrString(cast[ptr SockAddr](addr sockAddress))
register(client.AsyncFD) register(client.AsyncFD)
retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), client.AsyncFD)) retFuture.complete((address, client.AsyncFD))
except:
# getAddrString may raise
client.close()
retFuture.fail(getCurrentException())
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
# Common procedures between current and upcoming asyncdispatch
include includes.asynccommon
proc sleepAsync*(ms: int): Future[void] = proc sleepAsync*(ms: int): Future[void] =
## Suspends the execution of the current async procedure for the next ## Suspends the execution of the current async procedure for the next
## ``ms`` milliseconds. ## ``ms`` milliseconds.

View file

@ -244,6 +244,17 @@ when defineSsl:
else: else:
raiseSSLError("Socket has been disconnected") 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.} = proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.

View file

@ -44,21 +44,23 @@
const const
cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 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 ## encodes `s` into base64 representation. After `lineLen` characters, a
## `newline` is added. ## `newline` is added.
var total = ((len(s) + 2) div 3) * 4 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) if numLines > 0: inc(total, (numLines - 1) * newLine.len)
result = newString(total) result = newString(total)
var i = 0 var
var r = 0 i = 0
var currLine = 0 r = 0
currLine = 0
while i < s.len - 2: while i < s.len - 2:
var a = ord(s[i]) let
var b = ord(s[i+1]) a = ord(s[i])
var c = ord(s[i+2]) b = ord(s[i+1])
c = ord(s[i+2])
result[r] = cb64[a shr 2] result[r] = cb64[a shr 2]
result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] 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)] 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 currLine = 0
if i < s.len-1: if i < s.len-1:
var a = ord(s[i]) let
var b = ord(s[i+1]) a = ord(s[i])
b = ord(s[i+1])
result[r] = cb64[a shr 2] result[r] = cb64[a shr 2]
result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] result[r+1] = cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
result[r+2] = cb64[((b and 0x0F) shl 2)] 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: if r+4 != result.len:
setLen(result, r+4) setLen(result, r+4)
elif i < s.len: elif i < s.len:
var a = ord(s[i]) let a = ord(s[i])
result[r] = cb64[a shr 2] result[r] = cb64[a shr 2]
result[r+1] = cb64[(a and 3) shl 4] result[r+1] = cb64[(a and 3) shl 4]
result[r+2] = '=' result[r+2] = '='
@ -127,15 +130,17 @@ proc decode*(s: string): string =
# total is an upper bound, as we will skip arbitrary whitespace: # total is an upper bound, as we will skip arbitrary whitespace:
result = newString(total) result = newString(total)
var i = 0 var
var r = 0 i = 0
r = 0
while true: while true:
while s[i] in Whitespace: inc(i) while s[i] in Whitespace: inc(i)
if i < s.len-3: if i < s.len-3:
var a = s[i].decodeByte let
var b = s[i+1].decodeByte a = s[i].decodeByte
var c = s[i+2].decodeByte b = s[i+1].decodeByte
var d = s[i+3].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] = 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)) result[r+1] = chr((b shl 4) and 0xff or ((c shr 2) and 0x0F))

View file

@ -785,7 +785,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B],
t[].sort(cmp) t[].sort(cmp)
proc del*[A, B](t: var OrderedTable[A, B], key: A) = 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] var n: OrderedKeyValuePairSeq[A, B]
newSeq(n, len(t.data)) newSeq(n, len(t.data))
var h = t.first var h = t.first
@ -804,7 +804,7 @@ proc del*[A, B](t: var OrderedTable[A, B], key: A) =
h = nxt h = nxt
proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = 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) t[].del(key)
# ------------------------------ count tables ------------------------------- # ------------------------------ count tables -------------------------------

View file

@ -39,7 +39,7 @@ proc setCookie*(key, value: string, domain = "", path = "",
if domain != "": result.add("; Domain=" & domain) if domain != "": result.add("; Domain=" & domain)
if path != "": result.add("; Path=" & path) if path != "": result.add("; Path=" & path)
if expires != "": result.add("; Expires=" & expires) if expires != "": result.add("; Expires=" & expires)
if secure: result.add("; secure") if secure: result.add("; Secure")
if httpOnly: result.add("; HttpOnly") if httpOnly: result.add("; HttpOnly")
proc setCookie*(key, value: string, expires: TimeInfo, 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``. ## **Note:** UTC is assumed as the timezone for ``expires``.
return setCookie(key, value, domain, path, 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) noname, secure, httpOnly)
when isMainModule: when isMainModule:

View file

@ -434,7 +434,7 @@ proc `[]=`*(p: var MultipartData, name: string,
## "<html><head></head><body><p>test</p></body></html>") ## "<html><head></head><body><p>test</p></body></html>")
p.add(name, file.content, file.name, file.contentType) 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: if p == nil or p.content == nil or p.content.len == 0:
return ("", "") return ("", "")
@ -449,7 +449,7 @@ proc format(p: MultipartData): tuple[header, body: string] =
if not found: if not found:
break break
result.header = "Content-Type: multipart/form-data; boundary=" & bound & "\c\L" result.contentType = "multipart/form-data; boundary=" & bound
result.body = "" result.body = ""
for s in p.content: for s in p.content:
result.body.add("--" & bound & "\c\L" & s) result.body.add("--" & bound & "\c\L" & s)
@ -640,7 +640,7 @@ proc post*(url: string, extraHeaders = "", body = "",
## ``multipart/form-data`` POSTs comfortably. ## ``multipart/form-data`` POSTs comfortably.
## ##
## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead. ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead.
let (mpHeaders, mpBody) = format(multipart) let (mpContentType, mpBody) = format(multipart)
template withNewLine(x): untyped = template withNewLine(x): untyped =
if x.len > 0 and not x.endsWith("\c\L"): 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 xb = mpBody.withNewLine() & body
var xh = extraHeaders.withNewLine() & mpHeaders.withNewLine() & var xh = extraHeaders.withNewLine() &
withNewLine("Content-Length: " & $len(xb)) withNewLine("Content-Length: " & $len(xb))
if not multipart.isNil:
xh.add(withNewLine("Content-Type: " & mpContentType))
result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent, result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent,
proxy) proxy)
var lastURL = url var lastURL = url
@ -1030,32 +1033,38 @@ proc newConnection(client: HttpClient | AsyncHttpClient,
if client.currentURL.hostname != url.hostname or if client.currentURL.hostname != url.hostname or
client.currentURL.scheme != url.scheme or client.currentURL.scheme != url.scheme or
client.currentURL.port != url.port: 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: if client.connected:
client.close() 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... # TODO: I should be able to write 'net.Port' here...
let port = let port =
if url.port == "": if url.port == "":
if url.scheme.toLower() == "https": if isSsl:
nativesockets.Port(443) nativesockets.Port(443)
else: else:
nativesockets.Port(80) nativesockets.Port(80)
else: nativesockets.Port(url.port.parseInt) else: nativesockets.Port(url.port.parseInt)
if url.scheme.toLower() == "https": when client is HttpClient:
when defined(ssl): client.socket = await net.dial(url.hostname, port)
client.sslContext.wrapSocket(client.socket) elif client is AsyncHttpClient:
else: client.socket = await asyncnet.dial(url.hostname, port)
raise newException(HttpRequestError, else: {.fatal: "Unsupported client type".}
"SSL support is not available. Cannot connect over SSL.")
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.currentURL = url
client.connected = true 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 ## This procedure will follow redirects up to a maximum number of redirects
## specified in ``client.maxRedirects``. ## specified in ``client.maxRedirects``.
let (mpHeader, mpBody) = format(multipart) let (mpContentType, mpBody) = format(multipart)
# TODO: Support FutureStream for `body` parameter. # TODO: Support FutureStream for `body` parameter.
template withNewLine(x): untyped = template withNewLine(x): untyped =
if x.len > 0 and not x.endsWith("\c\L"): 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() var headers = newHttpHeaders()
if multipart != nil: if multipart != nil:
headers["Content-Type"] = mpHeader.split(": ")[1] headers["Content-Type"] = mpContentType
headers["Content-Length"] = $len(xb) headers["Content-Length"] = $len(xb)
result = await client.requestAux(url, $HttpPOST, xb, headers) result = await client.requestAux(url, $HttpPOST, xb, headers)

View file

@ -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)

View file

@ -1213,22 +1213,22 @@ when not defined(js):
proc parseJson*(s: Stream, filename: string): JsonNode = proc parseJson*(s: Stream, filename: string): JsonNode =
## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed
## for nice error messages. ## 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 var p: JsonParser
p.open(s, filename) p.open(s, filename)
defer: p.close() defer: p.close()
discard getTok(p) # read first token discard getTok(p) # read first token
result = p.parseJson() 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 = proc parseJson*(buffer: string): JsonNode =
## Parses JSON from `buffer`. ## 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") result = parseJson(newStringStream(buffer), "input")
proc parseFile*(filename: string): JsonNode = proc parseFile*(filename: string): JsonNode =
## Parses `file` into a `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) var stream = newFileStream(filename, fmRead)
if stream == nil: if stream == nil:
raise newException(IOError, "cannot read from file: " & filename) raise newException(IOError, "cannot read from file: " & filename)
@ -1502,7 +1502,7 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
doAssert result.len > 0 doAssert result.len > 0
proc processType(typeName: NimNode, obj: NimNode, 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 ...``. ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``.
## ##
## Sample ``ObjectTy``: ## Sample ``ObjectTy``:
@ -1524,6 +1524,20 @@ proc processType(typeName: NimNode, obj: NimNode,
for field in obj[2]: for field in obj[2]:
let nodes = processObjField(field, jsonNode) let nodes = processObjField(field, jsonNode)
result.add(nodes) 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: of nnkEnumTy:
let instType = toIdentNode(getTypeInst(typeName)) let instType = toIdentNode(getTypeInst(typeName))
let getEnumCall = createGetEnumCall(jsonNode, instType) let getEnumCall = createGetEnumCall(jsonNode, instType)
@ -1536,8 +1550,8 @@ proc processType(typeName: NimNode, obj: NimNode,
of "float": of "float":
result = quote do: result = quote do:
( (
verifyJsonKind(`jsonNode`, {JFloat}, astToStr(`jsonNode`)); verifyJsonKind(`jsonNode`, {JFloat, JInt}, astToStr(`jsonNode`));
`jsonNode`.fnum if `jsonNode`.kind == JFloat: `jsonNode`.fnum else: `jsonNode`.num.float
) )
of "string": of "string":
result = quote do: result = quote do:
@ -1551,6 +1565,12 @@ proc processType(typeName: NimNode, obj: NimNode,
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
`jsonNode`.num.int `jsonNode`.num.int
) )
of "biggestint":
result = quote do:
(
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
`jsonNode`.num
)
of "bool": of "bool":
result = quote do: result = quote do:
( (
@ -1585,7 +1605,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
typeName = typeName[0 .. ^12] typeName = typeName[0 .. ^12]
let obj = getType(typeSym[1]) let obj = getType(typeSym[1])
result = processType(newIdentNode(typeName), obj, jsonNode) result = processType(newIdentNode(typeName), obj, jsonNode, true)
of "seq": of "seq":
let seqT = typeSym[1] let seqT = typeSym[1]
let forLoopI = newIdentNode("i") let forLoopI = newIdentNode("i")
@ -1605,17 +1625,21 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
else: else:
# Generic type. # Generic type.
let obj = getType(typeSym) let obj = getType(typeSym)
result = processType(typeSym, obj, jsonNode) result = processType(typeSym, obj, jsonNode, false)
of nnkSym: of nnkSym:
let obj = getType(typeSym) 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: else:
doAssert false, "Unable to create constructor for: " & $typeSym.kind doAssert false, "Unable to create constructor for: " & $typeSym.kind
doAssert(not result.isNil(), "Constructor not initialised.") doAssert(not result.isNil(), "Constructor not initialised.")
proc postProcess(node: NimNode): NimNode 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 ## Looks for object constructors and calls the ``postProcess`` procedure
## on them. Otherwise it just returns the node as-is. ## on them. Otherwise it just returns the node as-is.
case value.kind case value.kind
@ -1736,9 +1760,10 @@ macro to*(node: JsonNode, T: typedesc): untyped =
doAssert(($typeNode[0]).normalize == "typedesc") doAssert(($typeNode[0]).normalize == "typedesc")
result = createConstructor(typeNode[1], node) 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: when false:
import os import os

View file

@ -12,7 +12,7 @@
# TODO: Clean up the exports a bit and everything else in general. # TODO: Clean up the exports a bit and everything else in general.
import os import os, options
when hostOS == "solaris": when hostOS == "solaris":
{.passl: "-lsocket -lnsl".} {.passl: "-lsocket -lnsl".}
@ -52,9 +52,11 @@ type
Domain* = enum ## domain, which specifies the protocol family of the Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed ## created socket. Other domains than those that are listed
## here are unsupported. ## 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_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 SockType* = enum ## second argument to `socket` proc
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
@ -125,10 +127,19 @@ proc toInt*(p: Protocol): cint
when not useWinVersion: when not useWinVersion:
proc toInt(domain: Domain): cshort = proc toInt(domain: Domain): cshort =
case domain case domain
of AF_UNSPEC: result = posix.AF_UNSPEC.cshort
of AF_UNIX: result = posix.AF_UNIX.cshort of AF_UNIX: result = posix.AF_UNIX.cshort
of AF_INET: result = posix.AF_INET.cshort of AF_INET: result = posix.AF_INET.cshort
of AF_INET6: result = posix.AF_INET6.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 = proc toInt(typ: SockType): cint =
case typ case typ
@ -136,7 +147,6 @@ when not useWinVersion:
of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_DGRAM: result = posix.SOCK_DGRAM
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
of SOCK_RAW: result = posix.SOCK_RAW of SOCK_RAW: result = posix.SOCK_RAW
else: discard
proc toInt(p: Protocol): cint = proc toInt(p: Protocol): cint =
case p case p
@ -146,18 +156,33 @@ when not useWinVersion:
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_RAW: result = posix.IPPROTO_RAW
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
else: discard
else: else:
proc toInt(domain: Domain): cshort = proc toInt(domain: Domain): cshort =
result = toU16(ord(domain)) 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 = proc toInt(typ: SockType): cint =
result = cint(ord(typ)) result = cint(ord(typ))
proc toInt(p: Protocol): cint = proc toInt(p: Protocol): cint =
result = cint(ord(p)) 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, proc newNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM, sockType: SockType = SOCK_STREAM,
@ -392,14 +417,14 @@ proc getHostname*(): string {.tags: [ReadIOEffect].} =
proc getSockDomain*(socket: SocketHandle): Domain = proc getSockDomain*(socket: SocketHandle): Domain =
## returns the socket's domain (AF_INET or AF_INET6). ## returns the socket's domain (AF_INET or AF_INET6).
var name: SockAddr var name: Sockaddr_in6
var namelen = sizeof(name).SockLen var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)), if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32: addr(namelen)) == -1'i32:
raiseOSError(osLastError()) raiseOSError(osLastError())
if name.sa_family == nativeAfInet: if name.sin6_family == nativeAfInet:
result = AF_INET result = AF_INET
elif name.sa_family == nativeAfInet6: elif name.sin6_family == nativeAfInet6:
result = AF_INET6 result = AF_INET6
else: else:
raiseOSError(osLastError(), "unknown socket family in getSockFamily") raiseOSError(osLastError(), "unknown socket family in getSockFamily")
@ -410,17 +435,23 @@ proc getAddrString*(sockAddr: ptr SockAddr): string =
if sockAddr.sa_family == nativeAfInet: if sockAddr.sa_family == nativeAfInet:
result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr) result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr)
elif sockAddr.sa_family == nativeAfInet6: elif sockAddr.sa_family == nativeAfInet6:
when not useWinVersion: let addrLen = when not useWinVersion: posix.INET6_ADDRSTRLEN
# TODO: Windows else: 46 # it's actually 46 in both cases
result = newString(posix.INET6_ADDRSTRLEN) result = newString(addrLen)
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr
discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring, when not useWinVersion:
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: if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0:
result = result.substr("::ffff:".len) result = result.substr("::ffff:".len)
else: else:
raiseOSError(osLastError(), "unknown socket family in getAddrString") if winlean.inet_ntop(winlean.AF_INET6, addr6, addr result[0],
result.len.int32) == nil:
raiseOSError(osLastError())
setLen(result, len(cstring(result)))
else:
raise newException(IOError, "Unknown socket family in getAddrString")
proc getSockName*(socket: SocketHandle): Port = proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number. ## returns the socket's associated port number.

View file

@ -66,7 +66,7 @@
## ##
{.deadCodeElim: on.} {.deadCodeElim: on.}
import nativesockets, os, strutils, parseutils, times, sets import nativesockets, os, strutils, parseutils, times, sets, options
export Port, `$`, `==` export Port, `$`, `==`
export Domain, SockType, Protocol export Domain, SockType, Protocol
@ -669,7 +669,7 @@ proc close*(socket: Socket) =
## Closes a socket. ## Closes a socket.
try: try:
when defineSsl: when defineSsl:
if socket.isSSL: if socket.isSSL and socket.sslHandle != nil:
ErrClearError() ErrClearError()
# As we are closing the underlying socket immediately afterwards, # As we are closing the underlying socket immediately afterwards,
# it is valid, under the TLS standard, to perform a unidirectional # 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 false
return true 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, proc connect*(socket: Socket, address: string,
port = Port(0)) {.tags: [ReadIOEffect].} = port = Port(0)) {.tags: [ReadIOEffect].} =

View file

@ -69,10 +69,9 @@ var
proc genOid*(): Oid = proc genOid*(): Oid =
## generates a new OID. ## generates a new OID.
proc rand(): cint {.importc: "rand", header: "<stdlib.h>", nodecl.} 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.} proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.}
var t = gettime(nil) var t = getTime().int32
var i = int32(atomicInc(incr)) var i = int32(atomicInc(incr))

View file

@ -569,7 +569,7 @@ when declared(getEnv) or defined(nimscript):
## ``["exe", "cmd", "bat"]``, on Posix ``[""]``. ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
proc findExe*(exe: string, followSymlinks: bool = true; proc findExe*(exe: string, followSymlinks: bool = true;
extensions=ExeExts): string {. extensions: openarray[string]=ExeExts): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then ## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable. ## in directories listed in the ``PATH`` environment variable.

View file

@ -209,9 +209,16 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl,
## ##
## **Warning**: Be careful when using waitForExit for processes created without ## **Warning**: Be careful when using waitForExit for processes created without
## poParentStreams because they may fill output buffers, causing deadlock. ## 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: [].} proc peekExitCode*(p: Process): int {.tags: [].}
## return -1 if the process is still running. Otherwise the process' exit code ## 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: [].} proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].}
## returns ``p``'s input stream for writing to. ## returns ``p``'s input stream for writing to.
@ -679,6 +686,16 @@ elif not defined(useNimRtl):
readIdx = 0 readIdx = 0
writeIdx = 1 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 = proc envToCStringArray(t: StringTableRef): cstringArray =
result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
var i = 0 var i = 0
@ -967,7 +984,7 @@ elif not defined(useNimRtl):
var status : cint = 1 var status : cint = 1
ret = waitpid(p.id, status, WNOHANG) ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id): if ret == int(p.id):
if WIFEXITED(status): if isExitStatus(status):
p.exitStatus = status p.exitStatus = status
return false return false
else: else:
@ -990,7 +1007,9 @@ elif not defined(useNimRtl):
import kqueue, times import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int = 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: if timeout == -1:
var status : cint = 1 var status : cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
@ -1041,7 +1060,7 @@ elif not defined(useNimRtl):
finally: finally:
discard posix.close(kqFD) discard posix.close(kqFD)
result = ((p.exitStatus and 0xFF00) shr 8) result = exitStatus(p.exitStatus)
else: else:
import times import times
@ -1077,7 +1096,9 @@ elif not defined(useNimRtl):
# ``waitPid`` fails if the process is not running anymore. But then # ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is
# initialized with -3, wrong success exit codes are prevented. # 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: if timeout == -1:
var status : cint = 1 var status : cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
@ -1151,17 +1172,19 @@ elif not defined(useNimRtl):
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError()) raiseOSError(osLastError())
result = ((p.exitStatus and 0xFF00) shr 8) result = exitStatus(p.exitStatus)
proc peekExitCode(p: Process): int = proc peekExitCode(p: Process): int =
var status = cint(0) var status = cint(0)
result = -1 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) var ret = waitpid(p.id, status, WNOHANG)
if ret > 0: if ret > 0:
if WIFEXITED(status): if isExitStatus(status):
p.exitStatus = status p.exitStatus = status
result = (status and 0xFF00) shr 8 result = exitStatus(status)
proc createStream(stream: var Stream, handle: var FileHandle, proc createStream(stream: var Stream, handle: var FileHandle,
fileMode: FileMode) = fileMode: FileMode) =
@ -1189,7 +1212,8 @@ elif not defined(useNimRtl):
proc execCmd(command: string): int = proc execCmd(command: string): int =
when defined(linux): when defined(linux):
result = csystem(command) shr 8 let tmp = csystem(command)
result = if tmp == -1: tmp else: exitStatus(tmp)
else: else:
result = csystem(command) result = csystem(command)

View file

@ -50,6 +50,7 @@ proc add*(url: var Url, a: Url) {.deprecated.} =
proc parseAuthority(authority: string, result: var Uri) = proc parseAuthority(authority: string, result: var Uri) =
var i = 0 var i = 0
var inPort = false var inPort = false
var inIPv6 = false
while true: while true:
case authority[i] case authority[i]
of '@': of '@':
@ -59,7 +60,14 @@ proc parseAuthority(authority: string, result: var Uri) =
result.hostname.setLen(0) result.hostname.setLen(0)
inPort = false inPort = false
of ':': of ':':
if inIPv6:
result.hostname.add(authority[i])
else:
inPort = true inPort = true
of '[':
inIPv6 = true
of ']':
inIPv6 = false
of '\0': break of '\0': break
else: else:
if inPort: if inPort:
@ -345,6 +353,17 @@ when isMainModule:
doAssert test.anchor == "nose" doAssert test.anchor == "nose"
doAssert($test == str) 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: block:
let str = "urn:example:animal:ferret:nose" let str = "urn:example:animal:ferret:nose"
let test = parseUri(str) let test = parseUri(str)

View file

@ -2016,6 +2016,12 @@ proc min*(x, y: float): float {.magic: "MinF64", noSideEffect.} =
if x <= y: x else: y if x <= y: x else: y
proc max*(x, y: float): float {.magic: "MaxF64", noSideEffect.} = proc max*(x, y: float): float {.magic: "MaxF64", noSideEffect.} =
if y <= x: x else: y 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.} {.pop.}
proc clamp*[T](x, a, b: T): T = proc clamp*[T](x, a, b: T): T =

View file

@ -172,7 +172,7 @@ proc raiseIndexError() {.compilerproc, noreturn.} =
proc raiseFieldError(f: string) {.compilerproc, noreturn.} = proc raiseFieldError(f: string) {.compilerproc, noreturn.} =
raise newException(FieldError, f & " is not accessible") raise newException(FieldError, f & " is not accessible")
proc SetConstr() {.varargs, asmNoStackFrame, compilerproc.} = proc setConstr() {.varargs, asmNoStackFrame, compilerproc.} =
when defined(nimphp): when defined(nimphp):
asm """ asm """
$args = func_get_args(); $args = func_get_args();

View file

@ -9,7 +9,7 @@
include "system/inclrtl" include "system/inclrtl"
import os, oids, tables, strutils, times, heapqueue, lists import os, oids, tables, strutils, times, heapqueue, lists, options
import nativesockets, net, deques import nativesockets, net, deques
@ -325,68 +325,6 @@ when defined(windows) or defined(nimdoc):
getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun) getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun)
close(dummySock) 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, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## 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 lpOutputBuf = newString(lpOutputLen)
var dwBytesReceived: Dword var dwBytesReceived: Dword
let dwReceiveDataLength = 0.Dword # We don't want any data to be read. let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
template failAccept(errcode) = template failAccept(errcode) =
if flags.isDisconnectionError(errcode): if flags.isDisconnectionError(errcode):
@ -770,12 +708,14 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength, dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen, addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen) addr remoteSockaddr, addr remoteLen)
try:
let address = getAddrString(remoteSockAddr)
register(clientSock.AsyncFD) register(clientSock.AsyncFD)
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 retFuture.complete((address: address, client: clientSock.AsyncFD))
retFuture.complete( except:
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), # getAddrString may raise
client: clientSock.AsyncFD) clientSock.close()
) retFuture.fail(getCurrentException())
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
@ -808,20 +748,6 @@ when defined(windows) or defined(nimdoc):
return retFuture 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) = proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered. ## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close() socket.SocketHandle.close()
@ -1159,23 +1085,6 @@ else:
var data = newAsyncData() var data = newAsyncData()
p.selector.registerHandle(fd.SocketHandle, {}, data) 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) = proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher() let disp = getGlobalDispatcher()
disp.selector.unregister(sock.SocketHandle) disp.selector.unregister(sock.SocketHandle)
@ -1331,50 +1240,6 @@ else:
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) 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, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv") var retFuture = newFuture[string]("recv")
@ -1568,9 +1433,14 @@ else:
else: else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else: else:
try:
let address = getAddrString(cast[ptr SockAddr](addr sockAddress))
register(client.AsyncFD) register(client.AsyncFD)
retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), retFuture.complete((address, client.AsyncFD))
client.AsyncFD)) except:
# getAddrString may raise
client.close()
retFuture.fail(getCurrentException())
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
@ -1623,6 +1493,9 @@ else:
data.readList.add(cb) data.readList.add(cb)
p.selector.registerEvent(SelectEvent(ev), data) p.selector.registerEvent(SelectEvent(ev), data)
# Common procedures between current and upcoming asyncdispatch
include includes.asynccommon
proc sleepAsync*(ms: int): Future[void] = proc sleepAsync*(ms: int): Future[void] =
## Suspends the execution of the current async procedure for the next ## Suspends the execution of the current async procedure for the next
## ``ms`` milliseconds. ## ``ms`` milliseconds.

View file

@ -495,7 +495,7 @@ type
ai_family*: cint ## Address family of socket. ai_family*: cint ## Address family of socket.
ai_socktype*: cint ## Socket type. ai_socktype*: cint ## Socket type.
ai_protocol*: cint ## Protocol of socket. 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_canonname*: cstring ## Canonical name of service location.
ai_addr*: ptr SockAddr ## Socket address of socket. ai_addr*: ptr SockAddr ## Socket address of socket.
ai_next*: ptr AddrInfo ## Pointer to next in list. ai_next*: ptr AddrInfo ## Pointer to next in list.
@ -803,6 +803,7 @@ const
SIO_GET_EXTENSION_FUNCTION_POINTER* = WSAIORW(IOC_WS2,6).DWORD SIO_GET_EXTENSION_FUNCTION_POINTER* = WSAIORW(IOC_WS2,6).DWORD
SO_UPDATE_ACCEPT_CONTEXT* = 0x700B SO_UPDATE_ACCEPT_CONTEXT* = 0x700B
AI_V4MAPPED* = 0x0008 AI_V4MAPPED* = 0x0008
AF_UNSPEC* = 0
AF_INET* = 2 AF_INET* = 2
AF_INET6* = 23 AF_INET6* = 23

View file

@ -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. This repository contains the Nim compiler, Nim's stdlib, tools and documentation.
For more information about Nim, including downloads and documentation for 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-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-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 [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/ [pull-request-instructions]: https://help.github.com/articles/using-pull-requests/

View file

@ -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))

View file

@ -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

View file

@ -0,0 +1,6 @@
discard """
errormsg: '''identifier expected, but found 'echo 4'''
line: 6
"""
echo 4 +2

View file

@ -1,11 +1,13 @@
discard """ discard """
cmd: "nim c --threads:on -d:ssl $file" cmd: "nim c --threads:on -d:ssl $file"
exitcode: 0
output: "OK"
""" """
import strutils import strutils
from net import TimeoutError from net import TimeoutError
import httpclient, asyncdispatch import nativesockets, os, httpclient, asyncdispatch
const manualTests = false const manualTests = false
@ -112,6 +114,40 @@ proc syncTest() =
except: except:
doAssert false, "TimeoutError should have been raised." 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()) waitFor(asyncTest())
ipv6Test()
echo "OK"

View file

@ -159,11 +159,11 @@ when isMainModule:
name: string name: string
age: int age: int
Data = object Data1 = object # TODO: Codegen bug when changed to ``Data``.
person: Person person: Person
list: seq[int] list: seq[int]
var data = to(jsonNode, Data) var data = to(jsonNode, Data1)
doAssert data.person.name == "Nimmer" doAssert data.person.name == "Nimmer"
doAssert data.person.age == 21 doAssert data.person.age == 21
doAssert data.list == @[1, 2, 3, 4] doAssert data.list == @[1, 2, 3, 4]
@ -183,3 +183,47 @@ when isMainModule:
var result = to(node, TestEnum) var result = to(node, TestEnum)
doAssert result.field == Bar 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

60
tests/stdlib/tnetdial.nim Normal file
View file

@ -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()

View file

@ -21,6 +21,8 @@ proc pg[T](x, y: var T) =
# test as a top level statement: # test as a top level statement:
var x, y, a, b: int var x, y, a, b: int
# test for regression:
(x, y) = (1, 2)
(x, y) = fooBar() (x, y) = fooBar()
echo x, " ", y echo x, " ", y

View file

@ -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]

View file

@ -13,6 +13,9 @@ Changelog
Changes affecting backwards compatibility 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 - ``httpclient.request`` now respects ``maxRedirects`` option. Previously
redirects were handled only by ``get`` and ``post`` procs. redirects were handled only by ``get`` and ``post`` procs.
- The IO routines now raise ``EOFError`` for the "end of file" condition. - 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 checks. When fields within case objects are initialiazed, the compiler will
now demand that the respective discriminator field has a matching known now demand that the respective discriminator field has a matching known
compile-time value. compile-time value.
- On posix, the results of `waitForExit`, `peekExitCode`, `execCmd` will return
128 + signal number if the application terminates via signal.
Library Additions Library Additions
----------------- -----------------
- Added ``system.onThreadDestruction``. - 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 Tool Additions
-------------- --------------