Merge branch 'devel' of https://github.com/Araq/Nimrod into devel
This commit is contained in:
commit
1250db5075
71 changed files with 1533 additions and 692 deletions
|
|
@ -540,7 +540,7 @@ proc typeAtom(p: var TParser): PNode =
|
||||||
if p.tok.s == "unsigned":
|
if p.tok.s == "unsigned":
|
||||||
isUnsigned = true
|
isUnsigned = true
|
||||||
elif p.tok.s == "signed" or p.tok.s == "int":
|
elif p.tok.s == "signed" or p.tok.s == "int":
|
||||||
nil
|
discard
|
||||||
else:
|
else:
|
||||||
add(x, p.tok.s)
|
add(x, p.tok.s)
|
||||||
getTok(p, nil)
|
getTok(p, nil)
|
||||||
|
|
@ -746,7 +746,7 @@ proc directDeclarator(p: var TParser, a: PNode, ident: ptr PNode): PNode =
|
||||||
result = declarator(p, a, ident)
|
result = declarator(p, a, ident)
|
||||||
eat(p, pxParRi, result)
|
eat(p, pxParRi, result)
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
return parseTypeSuffix(p, a)
|
return parseTypeSuffix(p, a)
|
||||||
|
|
||||||
proc declarator(p: var TParser, a: PNode, ident: ptr PNode): PNode =
|
proc declarator(p: var TParser, a: PNode, ident: ptr PNode): PNode =
|
||||||
|
|
@ -1165,7 +1165,7 @@ proc enumSpecifier(p: var TParser): PNode =
|
||||||
|
|
||||||
proc setBaseFlags(n: PNode, base: TNumericalBase) =
|
proc setBaseFlags(n: PNode, base: TNumericalBase) =
|
||||||
case base
|
case base
|
||||||
of base10: nil
|
of base10: discard
|
||||||
of base2: incl(n.flags, nfBase2)
|
of base2: incl(n.flags, nfBase2)
|
||||||
of base8: incl(n.flags, nfBase8)
|
of base8: incl(n.flags, nfBase8)
|
||||||
of base16: incl(n.flags, nfBase16)
|
of base16: incl(n.flags, nfBase16)
|
||||||
|
|
@ -1686,7 +1686,7 @@ proc switchStatement(p: var TParser): PNode =
|
||||||
break
|
break
|
||||||
of "case", "default":
|
of "case", "default":
|
||||||
break
|
break
|
||||||
else: nil
|
else: discard
|
||||||
addSon(result, statement(p))
|
addSon(result, statement(p))
|
||||||
if sonsLen(result) == 0:
|
if sonsLen(result) == 0:
|
||||||
# translate empty statement list to Nimrod's ``nil`` statement
|
# translate empty statement list to Nimrod's ``nil`` statement
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ proc parseDefBody(p: var TParser, m: var TMacro, params: seq[string]) =
|
||||||
m.body.add(tok)
|
m.body.add(tok)
|
||||||
of pxDirConc:
|
of pxDirConc:
|
||||||
# just ignore this token: this implements token merging correctly
|
# just ignore this token: this implements token merging correctly
|
||||||
nil
|
discard
|
||||||
else:
|
else:
|
||||||
m.body.add(p.tok)
|
m.body.add(p.tok)
|
||||||
# we do not want macro expansion here:
|
# we do not want macro expansion here:
|
||||||
|
|
@ -166,7 +166,7 @@ proc parseStmtList(p: var TParser): PNode =
|
||||||
of pxDirectiveParLe, pxDirective:
|
of pxDirectiveParLe, pxDirective:
|
||||||
case p.tok.s
|
case p.tok.s
|
||||||
of "else", "endif", "elif": break
|
of "else", "endif", "elif": break
|
||||||
else: nil
|
else: discard
|
||||||
addSon(result, statement(p))
|
addSon(result, statement(p))
|
||||||
|
|
||||||
proc eatEndif(p: var TParser) =
|
proc eatEndif(p: var TParser) =
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ type
|
||||||
errInvalidIndexValueForTuple, errCommandExpectsFilename,
|
errInvalidIndexValueForTuple, errCommandExpectsFilename,
|
||||||
errMainModuleMustBeSpecified,
|
errMainModuleMustBeSpecified,
|
||||||
errXExpected,
|
errXExpected,
|
||||||
|
errTIsNotAConcreteType,
|
||||||
errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError,
|
errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError,
|
||||||
errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile,
|
errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile,
|
||||||
errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitely,
|
errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitely,
|
||||||
|
|
@ -103,6 +104,7 @@ type
|
||||||
errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
|
errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
|
||||||
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
|
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
|
||||||
errXCannotBeClosure, errXMustBeCompileTime,
|
errXCannotBeClosure, errXMustBeCompileTime,
|
||||||
|
errCannotInferTypeOfTheLiteral,
|
||||||
errUser,
|
errUser,
|
||||||
warnCannotOpenFile,
|
warnCannotOpenFile,
|
||||||
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
|
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
|
||||||
|
|
@ -312,6 +314,7 @@ const
|
||||||
errCommandExpectsFilename: "command expects a filename argument",
|
errCommandExpectsFilename: "command expects a filename argument",
|
||||||
errMainModuleMustBeSpecified: "please, specify a main module in the project configuration file",
|
errMainModuleMustBeSpecified: "please, specify a main module in the project configuration file",
|
||||||
errXExpected: "\'$1\' expected",
|
errXExpected: "\'$1\' expected",
|
||||||
|
errTIsNotAConcreteType: "\'$1\' is not a concrete type.",
|
||||||
errInvalidSectionStart: "invalid section start",
|
errInvalidSectionStart: "invalid section start",
|
||||||
errGridTableNotImplemented: "grid table is not implemented",
|
errGridTableNotImplemented: "grid table is not implemented",
|
||||||
errGeneralParseError: "general parse error",
|
errGeneralParseError: "general parse error",
|
||||||
|
|
@ -346,6 +349,7 @@ const
|
||||||
errIllegalCaptureX: "illegal capture '$1'",
|
errIllegalCaptureX: "illegal capture '$1'",
|
||||||
errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
|
errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
|
||||||
errXMustBeCompileTime: "'$1' can only be used in compile-time context",
|
errXMustBeCompileTime: "'$1' can only be used in compile-time context",
|
||||||
|
errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1",
|
||||||
errUser: "$1",
|
errUser: "$1",
|
||||||
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
|
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
|
||||||
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]",
|
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]",
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
|
||||||
h = h +% ord(c)
|
h = h +% ord(c)
|
||||||
h = h +% h shl 10
|
h = h +% h shl 10
|
||||||
h = h xor (h shr 6)
|
h = h xor (h shr 6)
|
||||||
of '_': nil
|
of '_': discard
|
||||||
else: break
|
else: break
|
||||||
inc(pos)
|
inc(pos)
|
||||||
h = h +% h shl 3
|
h = h +% h shl 3
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ proc exprColonEqExprList(p: var TParser, kind, elemKind: TNodeKind,
|
||||||
|
|
||||||
proc setBaseFlags(n: PNode, base: TNumericalBase) =
|
proc setBaseFlags(n: PNode, base: TNumericalBase) =
|
||||||
case base
|
case base
|
||||||
of base10: nil
|
of base10: discard
|
||||||
of base2: incl(n.flags, nfBase2)
|
of base2: incl(n.flags, nfBase2)
|
||||||
of base8: incl(n.flags, nfBase8)
|
of base8: incl(n.flags, nfBase8)
|
||||||
of base16: incl(n.flags, nfBase16)
|
of base16: incl(n.flags, nfBase16)
|
||||||
|
|
@ -466,7 +466,7 @@ proc lowestExprAux(p: var TParser, v: var PNode, limit: int): TTokKind =
|
||||||
eat(p, pxCurlyDirRi)
|
eat(p, pxCurlyDirRi)
|
||||||
opNode.ident = getIdent("&")
|
opNode.ident = getIdent("&")
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
of pxMinus:
|
of pxMinus:
|
||||||
if p.tok.xkind == pxPer:
|
if p.tok.xkind == pxPer:
|
||||||
getTok(p)
|
getTok(p)
|
||||||
|
|
@ -477,7 +477,7 @@ proc lowestExprAux(p: var TParser, v: var PNode, limit: int): TTokKind =
|
||||||
of pxNeq:
|
of pxNeq:
|
||||||
opNode.ident = getIdent("!=")
|
opNode.ident = getIdent("!=")
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
skipCom(p, opNode) # read sub-expression with higher priority
|
skipCom(p, opNode) # read sub-expression with higher priority
|
||||||
nextop = lowestExprAux(p, v2, opPred)
|
nextop = lowestExprAux(p, v2, opPred)
|
||||||
addSon(node, opNode)
|
addSon(node, opNode)
|
||||||
|
|
@ -505,7 +505,7 @@ proc fixExpr(n: PNode): PNode =
|
||||||
(n.sons[2].kind in {nkCharLit, nkStrLit}):
|
(n.sons[2].kind in {nkCharLit, nkStrLit}):
|
||||||
n.sons[0].ident = getIdent("&") # fix operator
|
n.sons[0].ident = getIdent("&") # fix operator
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
if not (n.kind in {nkEmpty..nkNilLit}):
|
if not (n.kind in {nkEmpty..nkNilLit}):
|
||||||
for i in countup(0, sonsLen(n) - 1): result.sons[i] = fixExpr(n.sons[i])
|
for i in countup(0, sonsLen(n) - 1): result.sons[i] = fixExpr(n.sons[i])
|
||||||
|
|
||||||
|
|
@ -603,7 +603,7 @@ proc parseStmtList(p: var TParser): PNode =
|
||||||
of pxCurlyDirLe, pxStarDirLe:
|
of pxCurlyDirLe, pxStarDirLe:
|
||||||
if not isHandledDirective(p): break
|
if not isHandledDirective(p): break
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
addSon(result, parseStmt(p))
|
addSon(result, parseStmt(p))
|
||||||
if sonsLen(result) == 1: result = result.sons[0]
|
if sonsLen(result) == 1: result = result.sons[0]
|
||||||
|
|
||||||
|
|
@ -732,7 +732,7 @@ proc parseRepeat(p: var TParser): PNode =
|
||||||
addSon(b, c)
|
addSon(b, c)
|
||||||
addSon(a, b)
|
addSon(a, b)
|
||||||
if b.sons[0].kind == nkIdent and b.sons[0].ident.id == getIdent("false").id:
|
if b.sons[0].kind == nkIdent and b.sons[0].ident.id == getIdent("false").id:
|
||||||
nil
|
discard
|
||||||
else:
|
else:
|
||||||
addSon(s, a)
|
addSon(s, a)
|
||||||
addSon(result, s)
|
addSon(result, s)
|
||||||
|
|
@ -840,7 +840,7 @@ proc parseParam(p: var TParser): PNode =
|
||||||
getTok(p)
|
getTok(p)
|
||||||
v = newNodeP(nkVarTy, p)
|
v = newNodeP(nkVarTy, p)
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
while true:
|
while true:
|
||||||
case p.tok.xkind
|
case p.tok.xkind
|
||||||
of pxSymbol: a = createIdentNodeP(p.tok.ident, p)
|
of pxSymbol: a = createIdentNodeP(p.tok.ident, p)
|
||||||
|
|
@ -1133,7 +1133,7 @@ proc parseRecordPart(p: var TParser): PNode =
|
||||||
proc exSymbol(n: var PNode) =
|
proc exSymbol(n: var PNode) =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkPostfix:
|
of nkPostfix:
|
||||||
nil
|
discard
|
||||||
of nkPragmaExpr:
|
of nkPragmaExpr:
|
||||||
exSymbol(n.sons[0])
|
exSymbol(n.sons[0])
|
||||||
of nkIdent, nkAccQuoted:
|
of nkIdent, nkAccQuoted:
|
||||||
|
|
@ -1154,7 +1154,7 @@ proc fixRecordDef(n: var PNode) =
|
||||||
for i in countup(0, sonsLen(n) - 1): fixRecordDef(n.sons[i])
|
for i in countup(0, sonsLen(n) - 1): fixRecordDef(n.sons[i])
|
||||||
of nkIdentDefs:
|
of nkIdentDefs:
|
||||||
for i in countup(0, sonsLen(n) - 3): exSymbol(n.sons[i])
|
for i in countup(0, sonsLen(n) - 3): exSymbol(n.sons[i])
|
||||||
of nkNilLit, nkEmpty: nil
|
of nkNilLit, nkEmpty: discard
|
||||||
else: internalError(n.info, "fixRecordDef(): " & $n.kind)
|
else: internalError(n.info, "fixRecordDef(): " & $n.kind)
|
||||||
|
|
||||||
proc addPragmaToIdent(ident: var PNode, pragma: PNode) =
|
proc addPragmaToIdent(ident: var PNode, pragma: PNode) =
|
||||||
|
|
@ -1191,7 +1191,7 @@ proc parseRecordBody(p: var TParser, result, definition: PNode) =
|
||||||
if definition != nil: addPragmaToIdent(definition.sons[0], parseCommand(p))
|
if definition != nil: addPragmaToIdent(definition.sons[0], parseCommand(p))
|
||||||
else: internalError(result.info, "anonymous record is not supported")
|
else: internalError(result.info, "anonymous record is not supported")
|
||||||
else:
|
else:
|
||||||
nil
|
discard
|
||||||
opt(p, pxSemicolon)
|
opt(p, pxSemicolon)
|
||||||
skipCom(p, result)
|
skipCom(p, result)
|
||||||
|
|
||||||
|
|
@ -1399,7 +1399,7 @@ proc fixVarSection(p: var TParser, counter: PNode) =
|
||||||
|
|
||||||
proc exSymbols(n: PNode) =
|
proc exSymbols(n: PNode) =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty..nkNilLit: nil
|
of nkEmpty..nkNilLit: discard
|
||||||
of nkProcDef..nkIteratorDef: exSymbol(n.sons[namePos])
|
of nkProcDef..nkIteratorDef: exSymbol(n.sons[namePos])
|
||||||
of nkWhenStmt, nkStmtList:
|
of nkWhenStmt, nkStmtList:
|
||||||
for i in countup(0, sonsLen(n) - 1): exSymbols(n.sons[i])
|
for i in countup(0, sonsLen(n) - 1): exSymbols(n.sons[i])
|
||||||
|
|
@ -1410,7 +1410,7 @@ proc exSymbols(n: PNode) =
|
||||||
exSymbol(n.sons[i].sons[0])
|
exSymbol(n.sons[i].sons[0])
|
||||||
if n.sons[i].sons[2].kind == nkObjectTy:
|
if n.sons[i].sons[2].kind == nkObjectTy:
|
||||||
fixRecordDef(n.sons[i].sons[2])
|
fixRecordDef(n.sons[i].sons[2])
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
proc parseBegin(p: var TParser, result: PNode) =
|
proc parseBegin(p: var TParser, result: PNode) =
|
||||||
getTok(p)
|
getTok(p)
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,25 @@ proc makeExternImport(s: PSym, extname: string) =
|
||||||
incl(s.flags, sfImportc)
|
incl(s.flags, sfImportc)
|
||||||
excl(s.flags, sfForward)
|
excl(s.flags, sfForward)
|
||||||
|
|
||||||
proc makeExternExport(s: PSym, extname: string) =
|
const invalidIdentChars = AllChars - IdentChars
|
||||||
|
|
||||||
|
proc validateExternCName(s: PSym, info: TLineInfo) =
|
||||||
|
## Validates that the symbol name in s.loc.r is a valid C identifier.
|
||||||
|
##
|
||||||
|
## Valid identifiers are those alphanumeric including the underscore not
|
||||||
|
## starting with a number. If the check fails, a generic error will be
|
||||||
|
## displayed to the user.
|
||||||
|
let target = ropeToStr(s.loc.r)
|
||||||
|
if target.len < 1 or (not (target[0] in IdentStartChars)) or
|
||||||
|
(not target.allCharsInSet(IdentChars)):
|
||||||
|
localError(info, errGenerated, "invalid exported symbol")
|
||||||
|
|
||||||
|
proc makeExternExport(s: PSym, extname: string, info: TLineInfo) =
|
||||||
setExternName(s, extname)
|
setExternName(s, extname)
|
||||||
|
case gCmd
|
||||||
|
of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC:
|
||||||
|
validateExternCName(s, info)
|
||||||
|
else: discard
|
||||||
incl(s.flags, sfExportc)
|
incl(s.flags, sfExportc)
|
||||||
|
|
||||||
proc processImportCompilerProc(s: PSym, extname: string) =
|
proc processImportCompilerProc(s: PSym, extname: string) =
|
||||||
|
|
@ -515,7 +532,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
|
||||||
if k in validPragmas:
|
if k in validPragmas:
|
||||||
case k
|
case k
|
||||||
of wExportc:
|
of wExportc:
|
||||||
makeExternExport(sym, getOptionalStr(c, it, "$1"))
|
makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info)
|
||||||
incl(sym.flags, sfUsed) # avoid wrong hints
|
incl(sym.flags, sfUsed) # avoid wrong hints
|
||||||
of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1"))
|
of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1"))
|
||||||
of wImportCompilerProc:
|
of wImportCompilerProc:
|
||||||
|
|
@ -601,7 +618,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
|
||||||
processDynLib(c, it, sym)
|
processDynLib(c, it, sym)
|
||||||
of wCompilerproc:
|
of wCompilerproc:
|
||||||
noVal(it) # compilerproc may not get a string!
|
noVal(it) # compilerproc may not get a string!
|
||||||
makeExternExport(sym, "$1")
|
makeExternExport(sym, "$1", it.info)
|
||||||
incl(sym.flags, sfCompilerProc)
|
incl(sym.flags, sfCompilerProc)
|
||||||
incl(sym.flags, sfUsed) # suppress all those stupid warnings
|
incl(sym.flags, sfUsed) # suppress all those stupid warnings
|
||||||
registerCompilerProc(sym)
|
registerCompilerProc(sym)
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,7 @@ proc myOpen(module: PSym): PPassContext =
|
||||||
c.semOperand = semOperand
|
c.semOperand = semOperand
|
||||||
c.semConstBoolExpr = semConstBoolExpr
|
c.semConstBoolExpr = semConstBoolExpr
|
||||||
c.semOverloadedCall = semOverloadedCall
|
c.semOverloadedCall = semOverloadedCall
|
||||||
|
c.semInferredLambda = semInferredLambda
|
||||||
c.semGenerateInstance = generateInstance
|
c.semGenerateInstance = generateInstance
|
||||||
c.semTypeNode = semTypeNode
|
c.semTypeNode = semTypeNode
|
||||||
pushProcCon(c, module)
|
pushProcCon(c, module)
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ type
|
||||||
semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
|
semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
|
||||||
filter: TSymKinds): PNode {.nimcall.}
|
filter: TSymKinds): PNode {.nimcall.}
|
||||||
semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
|
semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
|
||||||
|
semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode
|
||||||
semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable,
|
semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable,
|
||||||
info: TLineInfo): PSym
|
info: TLineInfo): PSym
|
||||||
includedFiles*: TIntSet # used to detect recursive include files
|
includedFiles*: TIntSet # used to detect recursive include files
|
||||||
|
|
|
||||||
|
|
@ -1829,8 +1829,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||||
result = symChoice(c, n, s, scClosed)
|
result = symChoice(c, n, s, scClosed)
|
||||||
if result.kind == nkSym:
|
if result.kind == nkSym:
|
||||||
markIndirect(c, result.sym)
|
markIndirect(c, result.sym)
|
||||||
if isGenericRoutine(result.sym):
|
# if isGenericRoutine(result.sym):
|
||||||
localError(n.info, errInstantiateXExplicitely, s.name.s)
|
# localError(n.info, errInstantiateXExplicitely, s.name.s)
|
||||||
of nkSym:
|
of nkSym:
|
||||||
# because of the changed symbol binding, this does not mean that we
|
# because of the changed symbol binding, this does not mean that we
|
||||||
# don't have to check the symbol for semantics here again!
|
# don't have to check the symbol for semantics here again!
|
||||||
|
|
|
||||||
|
|
@ -143,10 +143,11 @@ proc discardCheck(c: PContext, result: PNode) =
|
||||||
while n.kind in skipForDiscardable:
|
while n.kind in skipForDiscardable:
|
||||||
n = n.lastSon
|
n = n.lastSon
|
||||||
n.typ = nil
|
n.typ = nil
|
||||||
elif c.inTypeClass > 0 and result.typ.kind == tyBool:
|
elif c.inTypeClass > 0:
|
||||||
let verdict = semConstExpr(c, result)
|
if result.typ.kind == tyBool:
|
||||||
if verdict.intVal == 0:
|
let verdict = semConstExpr(c, result)
|
||||||
localError(result.info, "type class predicate failed")
|
if verdict.intVal == 0:
|
||||||
|
localError(result.info, "type class predicate failed")
|
||||||
elif result.typ.kind != tyError and gCmd != cmdInteractive:
|
elif result.typ.kind != tyError and gCmd != cmdInteractive:
|
||||||
if result.typ.kind == tyNil:
|
if result.typ.kind == tyNil:
|
||||||
fixNilType(result)
|
fixNilType(result)
|
||||||
|
|
@ -349,7 +350,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
# BUGFIX: ``fitNode`` is needed here!
|
# BUGFIX: ``fitNode`` is needed here!
|
||||||
# check type compability between def.typ and typ:
|
# check type compability between def.typ and typ:
|
||||||
if typ != nil: def = fitNode(c, typ, def)
|
if typ != nil: def = fitNode(c, typ, def)
|
||||||
else: typ = skipIntLit(def.typ)
|
else:
|
||||||
|
typ = skipIntLit(def.typ)
|
||||||
|
if typ.kind in {tySequence, tyArray, tySet} and
|
||||||
|
typ.lastSon.kind == tyEmpty:
|
||||||
|
localError(def.info, errCannotInferTypeOfTheLiteral,
|
||||||
|
($typ.kind).substr(2).toLower)
|
||||||
else:
|
else:
|
||||||
def = ast.emptyNode
|
def = ast.emptyNode
|
||||||
if symkind == skLet: localError(a.info, errLetNeedsInit)
|
if symkind == skLet: localError(a.info, errLetNeedsInit)
|
||||||
|
|
@ -764,6 +770,29 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
|
||||||
s.ast = a
|
s.ast = a
|
||||||
popOwner()
|
popOwner()
|
||||||
|
|
||||||
|
proc checkForMetaFields(n: PNode) =
|
||||||
|
template checkMeta(t) =
|
||||||
|
if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags:
|
||||||
|
localError(n.info, errTIsNotAConcreteType, t.typeToString)
|
||||||
|
|
||||||
|
case n.kind
|
||||||
|
of nkRecList, nkRecCase:
|
||||||
|
for s in n: checkForMetaFields(s)
|
||||||
|
of nkOfBranch, nkElse:
|
||||||
|
checkForMetaFields(n.lastSon)
|
||||||
|
of nkSym:
|
||||||
|
let t = n.sym.typ
|
||||||
|
case t.kind
|
||||||
|
of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyPtr, tyRef,
|
||||||
|
tyProc, tyGenericInvokation, tyGenericInst:
|
||||||
|
let start = ord(t.kind in {tyGenericInvokation, tyGenericInst})
|
||||||
|
for i in start .. <t.sons.len:
|
||||||
|
checkMeta(t.sons[i])
|
||||||
|
else:
|
||||||
|
checkMeta(t)
|
||||||
|
else:
|
||||||
|
internalAssert false
|
||||||
|
|
||||||
proc typeSectionFinalPass(c: PContext, n: PNode) =
|
proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
var a = n.sons[i]
|
var a = n.sons[i]
|
||||||
|
|
@ -780,6 +809,8 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||||
assignType(s.typ, t)
|
assignType(s.typ, t)
|
||||||
s.typ.id = t.id # same id
|
s.typ.id = t.id # same id
|
||||||
checkConstructedType(s.info, s.typ)
|
checkConstructedType(s.info, s.typ)
|
||||||
|
if s.typ.kind in {tyObject, tyTuple}:
|
||||||
|
checkForMetaFields(s.typ.n)
|
||||||
let aa = a.sons[2]
|
let aa = a.sons[2]
|
||||||
if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and
|
if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and
|
||||||
aa.sons[0].kind == nkObjectTy:
|
aa.sons[0].kind == nkObjectTy:
|
||||||
|
|
@ -883,12 +914,19 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
s = n[namePos].sym
|
s = n[namePos].sym
|
||||||
pushOwner(s)
|
pushOwner(s)
|
||||||
openScope(c)
|
openScope(c)
|
||||||
|
var gp: PNode
|
||||||
if n.sons[genericParamsPos].kind != nkEmpty:
|
if n.sons[genericParamsPos].kind != nkEmpty:
|
||||||
illFormedAst(n) # process parameters:
|
n.sons[genericParamsPos] = semGenericParamList(c, n.sons[genericParamsPos])
|
||||||
|
gp = n.sons[genericParamsPos]
|
||||||
|
else:
|
||||||
|
gp = newNodeI(nkGenericParams, n.info)
|
||||||
|
|
||||||
if n.sons[paramsPos].kind != nkEmpty:
|
if n.sons[paramsPos].kind != nkEmpty:
|
||||||
var gp = newNodeI(nkGenericParams, n.info)
|
|
||||||
semParamList(c, n.sons[paramsPos], gp, s)
|
semParamList(c, n.sons[paramsPos], gp, s)
|
||||||
paramsTypeCheck(c, s.typ)
|
# paramsTypeCheck(c, s.typ)
|
||||||
|
if sonsLen(gp) > 0 and n.sons[genericParamsPos].kind == nkEmpty:
|
||||||
|
# we have a list of implicit type parameters:
|
||||||
|
n.sons[genericParamsPos] = gp
|
||||||
else:
|
else:
|
||||||
s.typ = newTypeS(tyProc, c)
|
s.typ = newTypeS(tyProc, c)
|
||||||
rawAddSon(s.typ, nil)
|
rawAddSon(s.typ, nil)
|
||||||
|
|
@ -900,12 +938,13 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
|
localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
|
||||||
#if efDetermineType notin flags:
|
#if efDetermineType notin flags:
|
||||||
# XXX not good enough; see tnamedparamanonproc.nim
|
# XXX not good enough; see tnamedparamanonproc.nim
|
||||||
pushProcCon(c, s)
|
if n.sons[genericParamsPos].kind == nkEmpty:
|
||||||
addResult(c, s.typ.sons[0], n.info, skProc)
|
pushProcCon(c, s)
|
||||||
let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
|
addResult(c, s.typ.sons[0], n.info, skProc)
|
||||||
n.sons[bodyPos] = transformBody(c.module, semBody, s)
|
let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
|
||||||
addResultNode(c, n)
|
n.sons[bodyPos] = transformBody(c.module, semBody, s)
|
||||||
popProcCon(c)
|
addResultNode(c, n)
|
||||||
|
popProcCon(c)
|
||||||
sideEffectsCheck(c, s)
|
sideEffectsCheck(c, s)
|
||||||
else:
|
else:
|
||||||
localError(n.info, errImplOfXexpected, s.name.s)
|
localError(n.info, errImplOfXexpected, s.name.s)
|
||||||
|
|
@ -913,6 +952,34 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
popOwner()
|
popOwner()
|
||||||
result.typ = s.typ
|
result.typ = s.typ
|
||||||
|
|
||||||
|
proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
|
||||||
|
var n = n
|
||||||
|
|
||||||
|
n = replaceTypesInBody(c, pt, n)
|
||||||
|
result = n
|
||||||
|
|
||||||
|
n.sons[genericParamsPos] = emptyNode
|
||||||
|
n.sons[paramsPos] = n.typ.n
|
||||||
|
|
||||||
|
openScope(c)
|
||||||
|
var s = n.sons[namePos].sym
|
||||||
|
addParams(c, n.typ.n, skProc)
|
||||||
|
pushProcCon(c, s)
|
||||||
|
addResult(c, n.typ.sons[0], n.info, skProc)
|
||||||
|
let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
|
||||||
|
n.sons[bodyPos] = transformBody(c.module, semBody, n.sons[namePos].sym)
|
||||||
|
addResultNode(c, n)
|
||||||
|
popProcCon(c)
|
||||||
|
closeScope(c)
|
||||||
|
|
||||||
|
s.ast = result
|
||||||
|
|
||||||
|
# alternative variant (not quite working):
|
||||||
|
# var prc = arg[0].sym
|
||||||
|
# let inferred = c.semGenerateInstance(c, prc, m.bindings, arg.info)
|
||||||
|
# result = inferred.ast
|
||||||
|
# result.kind = arg.kind
|
||||||
|
|
||||||
proc activate(c: PContext, n: PNode) =
|
proc activate(c: PContext, n: PNode) =
|
||||||
# XXX: This proc is part of my plan for getting rid of
|
# XXX: This proc is part of my plan for getting rid of
|
||||||
# forward declarations. stay tuned.
|
# forward declarations. stay tuned.
|
||||||
|
|
@ -1263,8 +1330,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
let (outer, inner) = insertDestructors(c, n.sons[i])
|
let (outer, inner) = insertDestructors(c, n.sons[i])
|
||||||
if outer != nil:
|
if outer != nil:
|
||||||
n.sons[i] = outer
|
n.sons[i] = outer
|
||||||
for j in countup(i+1, length-1):
|
var rest = newNode(nkStmtList, n.info, n.sons[i+1 .. length-1])
|
||||||
inner.addSon(semStmt(c, n.sons[j]))
|
inner.addSon(semStmtList(c, rest, flags))
|
||||||
n.sons.setLen(i+1)
|
n.sons.setLen(i+1)
|
||||||
return
|
return
|
||||||
of LastBlockStmts:
|
of LastBlockStmts:
|
||||||
|
|
|
||||||
|
|
@ -669,7 +669,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||||
of tyTypeDesc:
|
of tyTypeDesc:
|
||||||
if tfUnresolved notin paramType.flags:
|
if tfUnresolved notin paramType.flags:
|
||||||
# naked typedescs are not bindOnce types
|
# naked typedescs are not bindOnce types
|
||||||
if paramType.sonsLen == 0 and paramTypId != nil and
|
if paramType.base.kind == tyNone and paramTypId != nil and
|
||||||
paramTypId.id == typedescId.id: paramTypId = nil
|
paramTypId.id == typedescId.id: paramTypId = nil
|
||||||
result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons))
|
result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons))
|
||||||
|
|
||||||
|
|
@ -705,7 +705,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||||
# result.rawAddSon(copyType(paramType.sons[i], getCurrOwner(), true))
|
# result.rawAddSon(copyType(paramType.sons[i], getCurrOwner(), true))
|
||||||
result = instGenericContainer(c, paramType.sym.info, result,
|
result = instGenericContainer(c, paramType.sym.info, result,
|
||||||
allowMetaTypes = true)
|
allowMetaTypes = true)
|
||||||
result.lastSon.shouldHaveMeta
|
|
||||||
result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result])
|
result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result])
|
||||||
result = addImplicitGeneric(result)
|
result = addImplicitGeneric(result)
|
||||||
|
|
||||||
|
|
@ -1011,7 +1010,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||||
of mOrdinal: result = semOrdinal(c, n, prev)
|
of mOrdinal: result = semOrdinal(c, n, prev)
|
||||||
of mSeq: result = semContainer(c, n, tySequence, "seq", prev)
|
of mSeq: result = semContainer(c, n, tySequence, "seq", prev)
|
||||||
of mVarargs: result = semVarargs(c, n, prev)
|
of mVarargs: result = semVarargs(c, n, prev)
|
||||||
of mExpr, mTypeDesc:
|
of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil))
|
||||||
|
of mExpr:
|
||||||
result = semTypeNode(c, n.sons[0], nil)
|
result = semTypeNode(c, n.sons[0], nil)
|
||||||
if result != nil:
|
if result != nil:
|
||||||
result = copyType(result, getCurrOwner(), false)
|
result = copyType(result, getCurrOwner(), false)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) =
|
||||||
localError(info, errVarVarTypeNotAllowed)
|
localError(info, errVarVarTypeNotAllowed)
|
||||||
elif computeSize(t) == szIllegalRecursion:
|
elif computeSize(t) == szIllegalRecursion:
|
||||||
localError(info, errIllegalRecursionInTypeX, typeToString(t))
|
localError(info, errIllegalRecursionInTypeX, typeToString(t))
|
||||||
|
|
||||||
when false:
|
when false:
|
||||||
if t.kind == tyObject and t.sons[0] != nil:
|
if t.kind == tyObject and t.sons[0] != nil:
|
||||||
if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags:
|
if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags:
|
||||||
|
|
@ -219,7 +220,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
|
||||||
# is difficult to handle:
|
# is difficult to handle:
|
||||||
var body = t.sons[0]
|
var body = t.sons[0]
|
||||||
if body.kind != tyGenericBody: internalError(cl.info, "no generic body")
|
if body.kind != tyGenericBody: internalError(cl.info, "no generic body")
|
||||||
var header: PType = nil
|
var header: PType = t
|
||||||
# search for some instantiation here:
|
# search for some instantiation here:
|
||||||
if cl.allowMetaTypes:
|
if cl.allowMetaTypes:
|
||||||
result = PType(idTableGet(cl.localCache, t))
|
result = PType(idTableGet(cl.localCache, t))
|
||||||
|
|
@ -231,11 +232,13 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
|
||||||
if x.kind == tyGenericParam:
|
if x.kind == tyGenericParam:
|
||||||
x = lookupTypeVar(cl, x)
|
x = lookupTypeVar(cl, x)
|
||||||
if x != nil:
|
if x != nil:
|
||||||
if header == nil: header = instCopyType(cl, t)
|
if header == t: header = instCopyType(cl, t)
|
||||||
header.sons[i] = x
|
header.sons[i] = x
|
||||||
propagateToOwner(header, x)
|
propagateToOwner(header, x)
|
||||||
|
else:
|
||||||
|
propagateToOwner(header, x)
|
||||||
|
|
||||||
if header != nil:
|
if header != t:
|
||||||
# search again after first pass:
|
# search again after first pass:
|
||||||
result = searchInstTypes(header)
|
result = searchInstTypes(header)
|
||||||
if result != nil: return
|
if result != nil: return
|
||||||
|
|
@ -243,6 +246,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
|
||||||
header = instCopyType(cl, t)
|
header = instCopyType(cl, t)
|
||||||
|
|
||||||
result = newType(tyGenericInst, t.sons[0].owner)
|
result = newType(tyGenericInst, t.sons[0].owner)
|
||||||
|
result.flags = header.flags
|
||||||
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
|
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
|
||||||
result.sons = @[header.sons[0]]
|
result.sons = @[header.sons[0]]
|
||||||
# ugh need another pass for deeply recursive generic types (e.g. PActor)
|
# ugh need another pass for deeply recursive generic types (e.g. PActor)
|
||||||
|
|
@ -409,14 +413,22 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
|
||||||
|
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
|
proc initTypeVars(p: PContext, pt: TIdTable, info: TLineInfo): TReplTypeVars =
|
||||||
|
initIdTable(result.symMap)
|
||||||
|
copyIdTable(result.typeMap, pt)
|
||||||
|
initIdTable(result.localCache)
|
||||||
|
result.info = info
|
||||||
|
result.c = p
|
||||||
|
|
||||||
|
proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode): PNode =
|
||||||
|
var cl = initTypeVars(p, pt, n.info)
|
||||||
|
pushInfoContext(n.info)
|
||||||
|
result = replaceTypeVarsN(cl, n)
|
||||||
|
popInfoContext()
|
||||||
|
|
||||||
proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo,
|
proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo,
|
||||||
t: PType): PType =
|
t: PType): PType =
|
||||||
var cl: TReplTypeVars
|
var cl = initTypeVars(p, pt, info)
|
||||||
initIdTable(cl.symMap)
|
|
||||||
copyIdTable(cl.typeMap, pt)
|
|
||||||
initIdTable(cl.localCache)
|
|
||||||
cl.info = info
|
|
||||||
cl.c = p
|
|
||||||
pushInfoContext(info)
|
pushInfoContext(info)
|
||||||
result = replaceTypeVarsT(cl, t)
|
result = replaceTypeVarsT(cl, t)
|
||||||
popInfoContext()
|
popInfoContext()
|
||||||
|
|
|
||||||
|
|
@ -343,53 +343,57 @@ proc allowsNil(f: PType): TTypeRelation {.inline.} =
|
||||||
proc inconsistentVarTypes(f, a: PType): bool {.inline.} =
|
proc inconsistentVarTypes(f, a: PType): bool {.inline.} =
|
||||||
result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar)
|
result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar)
|
||||||
|
|
||||||
proc procParamTypeRel(c: var TCandidate, f, a: PType,
|
proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
|
||||||
result: var TTypeRelation) =
|
var f = f
|
||||||
var
|
|
||||||
m: TTypeRelation
|
|
||||||
f = f
|
|
||||||
|
|
||||||
if a.isMetaType:
|
if a.isMetaType:
|
||||||
if f.isMetaType:
|
if f.isMetaType:
|
||||||
# we are matching a generic proc (as proc param)
|
# We are matching a generic proc (as proc param)
|
||||||
# to another generic type appearing in the proc
|
# to another generic type appearing in the proc
|
||||||
# sigunature. there is a change that the target
|
# signature. There is a change that the target
|
||||||
# type is already fully-determined, so we are
|
# type is already fully-determined, so we are
|
||||||
# going to try resolve it
|
# going to try resolve it
|
||||||
f = generateTypeInstance(c.c, c.bindings, c.call.info, f)
|
f = generateTypeInstance(c.c, c.bindings, c.call.info, f)
|
||||||
if f == nil or f.isMetaType:
|
if f == nil or f.isMetaType:
|
||||||
# no luck resolving the type, so the inference fails
|
# no luck resolving the type, so the inference fails
|
||||||
result = isNone
|
return isNone
|
||||||
return
|
|
||||||
let reverseRel = typeRel(c, a, f)
|
let reverseRel = typeRel(c, a, f)
|
||||||
if reverseRel == isGeneric:
|
if reverseRel == isGeneric:
|
||||||
m = isInferred
|
result = isInferred
|
||||||
|
inc c.genericMatches
|
||||||
else:
|
else:
|
||||||
m = typeRel(c, f, a)
|
result = typeRel(c, f, a)
|
||||||
|
|
||||||
if m <= isSubtype or inconsistentVarTypes(f, a):
|
if result <= isSubtype or inconsistentVarTypes(f, a):
|
||||||
result = isNone
|
result = isNone
|
||||||
return
|
|
||||||
else:
|
if result == isEqual:
|
||||||
result = minRel(m, result)
|
inc c.exactMatches
|
||||||
|
|
||||||
proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
|
proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
|
||||||
case a.kind
|
case a.kind
|
||||||
of tyProc:
|
of tyProc:
|
||||||
if sonsLen(f) != sonsLen(a): return
|
if sonsLen(f) != sonsLen(a): return
|
||||||
# Note: We have to do unification for the parameters before the
|
|
||||||
# return type!
|
|
||||||
result = isEqual # start with maximum; also correct for no
|
result = isEqual # start with maximum; also correct for no
|
||||||
# params at all
|
# params at all
|
||||||
for i in countup(1, sonsLen(f)-1):
|
|
||||||
procParamTypeRel(c, f.sons[i], a.sons[i], result)
|
template checkParam(f, a) =
|
||||||
|
result = minRel(result, procParamTypeRel(c, f, a))
|
||||||
|
if result == isNone: return
|
||||||
|
|
||||||
|
# Note: We have to do unification for the parameters before the
|
||||||
|
# return type!
|
||||||
|
for i in 1 .. <f.sonsLen:
|
||||||
|
checkParam(f.sons[i], a.sons[i])
|
||||||
|
|
||||||
if f.sons[0] != nil:
|
if f.sons[0] != nil:
|
||||||
if a.sons[0] != nil:
|
if a.sons[0] != nil:
|
||||||
procParamTypeRel(c, f.sons[0], a.sons[0], result)
|
checkParam(f.sons[0], a.sons[0])
|
||||||
else:
|
else:
|
||||||
return isNone
|
return isNone
|
||||||
elif a.sons[0] != nil:
|
elif a.sons[0] != nil:
|
||||||
return isNone
|
return isNone
|
||||||
|
|
||||||
if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
|
if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
|
||||||
return isNone
|
return isNone
|
||||||
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
|
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
|
||||||
|
|
@ -896,20 +900,27 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
|
||||||
result = isNone
|
result = isNone
|
||||||
|
|
||||||
of tyTypeDesc:
|
of tyTypeDesc:
|
||||||
if a.kind != tyTypeDesc: return isNone
|
|
||||||
|
|
||||||
var prev = PType(idTableGet(c.bindings, f))
|
var prev = PType(idTableGet(c.bindings, f))
|
||||||
if prev == nil:
|
if prev == nil:
|
||||||
|
# proc foo(T: typedesc, x: T)
|
||||||
|
# when `f` is an unresolved typedesc, `a` could be any
|
||||||
|
# type, so we should not perform this check earlier
|
||||||
|
if a.kind != tyTypeDesc: return isNone
|
||||||
|
|
||||||
if f.base.kind == tyNone:
|
if f.base.kind == tyNone:
|
||||||
result = isGeneric
|
result = isGeneric
|
||||||
else:
|
else:
|
||||||
result = typeRel(c, f.base, a.base)
|
result = typeRel(c, f.base, a.base)
|
||||||
|
|
||||||
if result != isNone:
|
if result != isNone:
|
||||||
put(c.bindings, f, a)
|
put(c.bindings, f, a)
|
||||||
else:
|
else:
|
||||||
let toMatch = if tfUnresolved in f.flags: a
|
if tfUnresolved in f.flags:
|
||||||
else: a.base
|
result = typeRel(c, prev.base, a)
|
||||||
result = typeRel(c, prev.base, toMatch)
|
elif a.kind == tyTypeDesc:
|
||||||
|
result = typeRel(c, prev.base, a.base)
|
||||||
|
else:
|
||||||
|
result = isNone
|
||||||
|
|
||||||
of tyStmt:
|
of tyStmt:
|
||||||
result = isGeneric
|
result = isGeneric
|
||||||
|
|
@ -1035,10 +1046,11 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
|
||||||
#result = copyTree(arg)
|
#result = copyTree(arg)
|
||||||
result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c)
|
result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c)
|
||||||
of isInferred, isInferredConvertible:
|
of isInferred, isInferredConvertible:
|
||||||
var prc = if arg.kind in nkLambdaKinds: arg[0].sym
|
if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds:
|
||||||
else: arg.sym
|
result = c.semInferredLambda(c, m.bindings, arg)
|
||||||
let inferred = c.semGenerateInstance(c, prc, m.bindings, arg.info)
|
else:
|
||||||
result = newSymNode(inferred, arg.info)
|
let inferred = c.semGenerateInstance(c, arg.sym, m.bindings, arg.info)
|
||||||
|
result = newSymNode(inferred, arg.info)
|
||||||
if r == isInferredConvertible:
|
if r == isInferredConvertible:
|
||||||
result = implicitConv(nkHiddenStdConv, f, result, m, c)
|
result = implicitConv(nkHiddenStdConv, f, result, m, c)
|
||||||
of isGeneric:
|
of isGeneric:
|
||||||
|
|
@ -1101,6 +1113,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
|
||||||
# incorrect to simply use the first fitting match. However, to implement
|
# incorrect to simply use the first fitting match. However, to implement
|
||||||
# this correctly is inefficient. We have to copy `m` here to be able to
|
# this correctly is inefficient. We have to copy `m` here to be able to
|
||||||
# roll back the side effects of the unification algorithm.
|
# roll back the side effects of the unification algorithm.
|
||||||
|
|
||||||
let c = m.c
|
let c = m.c
|
||||||
var x, y, z: TCandidate
|
var x, y, z: TCandidate
|
||||||
initCandidate(c, x, m.callee)
|
initCandidate(c, x, m.callee)
|
||||||
|
|
|
||||||
|
|
@ -1042,7 +1042,8 @@ proc typeAllowedAux(marker: var TIntSet, typ: PType, kind: TSymKind,
|
||||||
of tyEmpty:
|
of tyEmpty:
|
||||||
result = taField in flags
|
result = taField in flags
|
||||||
of tyTypeClasses:
|
of tyTypeClasses:
|
||||||
result = true
|
result = tfGenericTypeParam in t.flags or
|
||||||
|
taField notin flags
|
||||||
of tyGenericBody, tyGenericParam, tyGenericInvokation,
|
of tyGenericBody, tyGenericParam, tyGenericInvokation,
|
||||||
tyNone, tyForward, tyFromExpr, tyFieldAccessor:
|
tyNone, tyForward, tyFromExpr, tyFieldAccessor:
|
||||||
result = false
|
result = false
|
||||||
|
|
|
||||||
|
|
@ -1025,7 +1025,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): PNode =
|
||||||
regs[ra].sons[0].flags.incl nfIsRef
|
regs[ra].sons[0].flags.incl nfIsRef
|
||||||
of opcNCopyNimNode:
|
of opcNCopyNimNode:
|
||||||
decodeB(nkMetaNode)
|
decodeB(nkMetaNode)
|
||||||
setMeta(regs[ra], copyNode(regs[rb]))
|
setMeta(regs[ra], copyNode(regs[rb].skipMeta))
|
||||||
of opcNCopyNimTree:
|
of opcNCopyNimTree:
|
||||||
decodeB(nkMetaNode)
|
decodeB(nkMetaNode)
|
||||||
setMeta(regs[ra], copyTree(regs[rb]))
|
setMeta(regs[ra], copyTree(regs[rb]))
|
||||||
|
|
|
||||||
|
|
@ -321,9 +321,18 @@ proc genAndOr(c: PCtx; n: PNode; opc: TOpcode; dest: var TDest) =
|
||||||
c.gen(n.sons[2], dest)
|
c.gen(n.sons[2], dest)
|
||||||
c.patch(L1)
|
c.patch(L1)
|
||||||
|
|
||||||
|
proc nilLiteral(n: PNode): PNode =
|
||||||
|
if n.kind == nkNilLit and n.typ.sym != nil and
|
||||||
|
n.typ.sym.magic == mPNimrodNode:
|
||||||
|
let nilo = newNodeIT(nkNilLit, n.info, n.typ)
|
||||||
|
result = newNodeIT(nkMetaNode, n.info, n.typ)
|
||||||
|
result.add nilo
|
||||||
|
else:
|
||||||
|
result = n
|
||||||
|
|
||||||
proc rawGenLiteral(c: PCtx; n: PNode): int =
|
proc rawGenLiteral(c: PCtx; n: PNode): int =
|
||||||
result = c.constants.len
|
result = c.constants.len
|
||||||
c.constants.add n
|
c.constants.add n.nilLiteral
|
||||||
internalAssert result < 0x7fff
|
internalAssert result < 0x7fff
|
||||||
|
|
||||||
proc sameConstant*(a, b: PNode): bool =
|
proc sameConstant*(a, b: PNode): bool =
|
||||||
|
|
@ -907,17 +916,20 @@ proc requiresCopy(n: PNode): bool =
|
||||||
proc unneededIndirection(n: PNode): bool =
|
proc unneededIndirection(n: PNode): bool =
|
||||||
n.typ.skipTypes(abstractInst-{tyTypeDesc}).kind == tyRef
|
n.typ.skipTypes(abstractInst-{tyTypeDesc}).kind == tyRef
|
||||||
|
|
||||||
proc skipDeref(n: PNode): PNode =
|
|
||||||
if n.kind in {nkDerefExpr, nkHiddenDeref} and unneededIndirection(n.sons[0]):
|
|
||||||
result = n.sons[0]
|
|
||||||
else:
|
|
||||||
result = n
|
|
||||||
|
|
||||||
proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
|
proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
|
||||||
flags: TGenFlags) =
|
flags: TGenFlags) =
|
||||||
# a nop for certain types
|
# a nop for certain types
|
||||||
let flags = if opc == opcAddr: flags+{gfAddrOf} else: flags
|
let flags = if opc == opcAddr: flags+{gfAddrOf} else: flags
|
||||||
if unneededIndirection(n.sons[0]):
|
# consider:
|
||||||
|
# proc foo(f: var ref int) =
|
||||||
|
# f = new(int)
|
||||||
|
# proc blah() =
|
||||||
|
# var x: ref int
|
||||||
|
# foo x
|
||||||
|
#
|
||||||
|
# The type of 'f' is 'var ref int' and of 'x' is 'ref int'. Hence for
|
||||||
|
# nkAddr we must not use 'unneededIndirection', but for deref we use it.
|
||||||
|
if opc != opcAddr and unneededIndirection(n.sons[0]):
|
||||||
gen(c, n.sons[0], dest, flags)
|
gen(c, n.sons[0], dest, flags)
|
||||||
else:
|
else:
|
||||||
let tmp = c.genx(n.sons[0], flags)
|
let tmp = c.genx(n.sons[0], flags)
|
||||||
|
|
@ -1109,7 +1121,12 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode =
|
||||||
result = newNodeIT(nkFloatLit, info, t)
|
result = newNodeIT(nkFloatLit, info, t)
|
||||||
of tyVar, tyPointer, tyPtr, tyCString, tySequence, tyString, tyExpr,
|
of tyVar, tyPointer, tyPtr, tyCString, tySequence, tyString, tyExpr,
|
||||||
tyStmt, tyTypeDesc, tyStatic, tyRef:
|
tyStmt, tyTypeDesc, tyStatic, tyRef:
|
||||||
result = newNodeIT(nkNilLit, info, t)
|
if t.sym != nil and t.sym.magic == mPNimrodNode:
|
||||||
|
let nilo = newNodeIT(nkNilLit, info, t)
|
||||||
|
result = newNodeIT(nkMetaNode, info, t)
|
||||||
|
result.add nilo
|
||||||
|
else:
|
||||||
|
result = newNodeIT(nkNilLit, info, t)
|
||||||
of tyProc:
|
of tyProc:
|
||||||
if t.callConv != ccClosure:
|
if t.callConv != ccClosure:
|
||||||
result = newNodeIT(nkNilLit, info, t)
|
result = newNodeIT(nkNilLit, info, t)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ block mainLoop:
|
||||||
case x.kind
|
case x.kind
|
||||||
of xmlEof: break mainLoop
|
of xmlEof: break mainLoop
|
||||||
of xmlElementClose: break
|
of xmlElementClose: break
|
||||||
else: nil
|
else: discard
|
||||||
x.next() # skip ``xmlElementClose``
|
x.next() # skip ``xmlElementClose``
|
||||||
# now we have the description for the ``a`` element
|
# now we have the description for the ``a`` element
|
||||||
var desc = ""
|
var desc = ""
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from posix import TSocketHandle
|
||||||
|
|
||||||
const
|
const
|
||||||
EPOLLIN* = 0x00000001
|
EPOLLIN* = 0x00000001
|
||||||
EPOLLPRI* = 0x00000002
|
EPOLLPRI* = 0x00000002
|
||||||
|
|
@ -33,8 +35,8 @@ const
|
||||||
type
|
type
|
||||||
epoll_data* {.importc: "union epoll_data",
|
epoll_data* {.importc: "union epoll_data",
|
||||||
header: "<sys/epoll.h>", pure, final.} = object # TODO: This is actually a union.
|
header: "<sys/epoll.h>", pure, final.} = object # TODO: This is actually a union.
|
||||||
thePtr* {.importc: "ptr".}: pointer # \
|
#thePtr* {.importc: "ptr".}: pointer
|
||||||
#fd*: cint
|
fd*: cint # \
|
||||||
#u32*: uint32
|
#u32*: uint32
|
||||||
#u64*: uint64
|
#u64*: uint64
|
||||||
|
|
||||||
|
|
@ -54,7 +56,7 @@ proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1",
|
||||||
## Same as epoll_create but with an FLAGS parameter. The unused SIZE
|
## Same as epoll_create but with an FLAGS parameter. The unused SIZE
|
||||||
## parameter has been dropped.
|
## parameter has been dropped.
|
||||||
|
|
||||||
proc epoll_ctl*(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint {.
|
proc epoll_ctl*(epfd: cint; op: cint; fd: cint | TSocketHandle; event: ptr epoll_event): cint {.
|
||||||
importc: "epoll_ctl", header: "<sys/epoll.h>".}
|
importc: "epoll_ctl", header: "<sys/epoll.h>".}
|
||||||
## Manipulate an epoll instance "epfd". Returns 0 in case of success,
|
## Manipulate an epoll instance "epfd". Returns 0 in case of success,
|
||||||
## -1 in case of error ( the "errno" variable will contain the
|
## -1 in case of error ( the "errno" variable will contain the
|
||||||
|
|
|
||||||
|
|
@ -2356,7 +2356,7 @@ proc FD_ZERO*(a1: var TFdSet) {.importc, header: "<sys/select.h>".}
|
||||||
|
|
||||||
proc pselect*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimespec,
|
proc pselect*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimespec,
|
||||||
a6: var Tsigset): cint {.importc, header: "<sys/select.h>".}
|
a6: var Tsigset): cint {.importc, header: "<sys/select.h>".}
|
||||||
proc select*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {.
|
proc select*(a1: cint | TSocketHandle, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {.
|
||||||
importc, header: "<sys/select.h>".}
|
importc, header: "<sys/select.h>".}
|
||||||
|
|
||||||
when hasSpawnH:
|
when hasSpawnH:
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
import os, oids, tables, strutils
|
import os, oids, tables, strutils, macros
|
||||||
|
|
||||||
import winlean
|
|
||||||
|
|
||||||
import sockets2, net
|
import sockets2, net
|
||||||
|
|
||||||
|
|
@ -23,14 +21,13 @@ import sockets2, net
|
||||||
# -- Futures
|
# -- Futures
|
||||||
|
|
||||||
type
|
type
|
||||||
PFutureVoid* = ref object of PObject
|
PFutureBase* = ref object of PObject
|
||||||
cbVoid: proc () {.closure.}
|
cb: proc () {.closure.}
|
||||||
finished: bool
|
finished: bool
|
||||||
|
|
||||||
PFuture*[T] = ref object of PFutureVoid
|
PFuture*[T] = ref object of PFutureBase
|
||||||
value: T
|
value: T
|
||||||
error: ref EBase
|
error: ref EBase
|
||||||
cb: proc (future: PFuture[T]) {.closure.}
|
|
||||||
|
|
||||||
proc newFuture*[T](): PFuture[T] =
|
proc newFuture*[T](): PFuture[T] =
|
||||||
## Creates a new future.
|
## Creates a new future.
|
||||||
|
|
@ -39,42 +36,38 @@ proc newFuture*[T](): PFuture[T] =
|
||||||
|
|
||||||
proc complete*[T](future: PFuture[T], val: T) =
|
proc complete*[T](future: PFuture[T], val: T) =
|
||||||
## Completes ``future`` with value ``val``.
|
## Completes ``future`` with value ``val``.
|
||||||
assert(not future.finished)
|
assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||||
assert(future.error == nil)
|
assert(future.error == nil)
|
||||||
future.value = val
|
future.value = val
|
||||||
future.finished = true
|
future.finished = true
|
||||||
if future.cb != nil:
|
if future.cb != nil:
|
||||||
future.cb(future)
|
future.cb()
|
||||||
if future.cbVoid != nil:
|
|
||||||
future.cbVoid()
|
|
||||||
|
|
||||||
proc fail*[T](future: PFuture[T], error: ref EBase) =
|
proc fail*[T](future: PFuture[T], error: ref EBase) =
|
||||||
## Completes ``future`` with ``error``.
|
## Completes ``future`` with ``error``.
|
||||||
assert(not future.finished)
|
assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||||
future.finished = true
|
future.finished = true
|
||||||
future.error = error
|
future.error = error
|
||||||
if future.cb != nil:
|
if future.cb != nil:
|
||||||
future.cb(future)
|
future.cb()
|
||||||
|
|
||||||
|
proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) =
|
||||||
|
## Sets the callback proc to be called when the future completes.
|
||||||
|
##
|
||||||
|
## If future has already completed then ``cb`` will be called immediately.
|
||||||
|
##
|
||||||
|
## **Note**: You most likely want the other ``callback`` setter which
|
||||||
|
## passes ``future`` as a param to the callback.
|
||||||
|
future.cb = cb
|
||||||
|
if future.finished:
|
||||||
|
future.cb()
|
||||||
|
|
||||||
proc `callback=`*[T](future: PFuture[T],
|
proc `callback=`*[T](future: PFuture[T],
|
||||||
cb: proc (future: PFuture[T]) {.closure.}) =
|
cb: proc (future: PFuture[T]) {.closure.}) =
|
||||||
## Sets the callback proc to be called when the future completes.
|
## Sets the callback proc to be called when the future completes.
|
||||||
##
|
##
|
||||||
## If future has already completed then ``cb`` will be called immediately.
|
## If future has already completed then ``cb`` will be called immediately.
|
||||||
future.cb = cb
|
future.callback = proc () = cb(future)
|
||||||
if future.finished:
|
|
||||||
future.cb(future)
|
|
||||||
|
|
||||||
proc `callbackVoid=`*(future: PFutureVoid, cb: proc () {.closure.}) =
|
|
||||||
## Sets the **void** callback proc to be called when the future completes.
|
|
||||||
##
|
|
||||||
## If future has already completed then ``cb`` will be called immediately.
|
|
||||||
##
|
|
||||||
## **Note**: This is used for the ``await`` functionality, you most likely
|
|
||||||
## want to use ``callback``.
|
|
||||||
future.cbVoid = cb
|
|
||||||
if future.finished:
|
|
||||||
future.cbVoid()
|
|
||||||
|
|
||||||
proc read*[T](future: PFuture[T]): T =
|
proc read*[T](future: PFuture[T]): T =
|
||||||
## Retrieves the value of ``future``. Future must be finished otherwise
|
## Retrieves the value of ``future``. Future must be finished otherwise
|
||||||
|
|
@ -98,16 +91,21 @@ proc failed*[T](future: PFuture[T]): bool =
|
||||||
## Determines whether ``future`` completed with an error.
|
## Determines whether ``future`` completed with an error.
|
||||||
future.error != nil
|
future.error != nil
|
||||||
|
|
||||||
when defined(windows):
|
# TODO: Get rid of register. Do it implicitly.
|
||||||
|
|
||||||
|
when defined(windows) or defined(nimdoc):
|
||||||
|
import winlean
|
||||||
type
|
type
|
||||||
TCompletionKey = dword
|
TCompletionKey = dword
|
||||||
|
|
||||||
TCompletionData* = object
|
TCompletionData* = object
|
||||||
sock: TSocketHandle
|
sock: TSocketHandle
|
||||||
cb: proc (sock: TSocketHandle, errcode: TOSErrorCode) {.closure.}
|
cb: proc (sock: TSocketHandle, bytesTransferred: DWORD,
|
||||||
|
errcode: TOSErrorCode) {.closure.}
|
||||||
|
|
||||||
PDispatcher* = ref object
|
PDispatcher* = ref object
|
||||||
ioPort: THandle
|
ioPort: THandle
|
||||||
|
hasHandles: bool
|
||||||
|
|
||||||
TCustomOverlapped = object
|
TCustomOverlapped = object
|
||||||
Internal*: DWORD
|
Internal*: DWORD
|
||||||
|
|
@ -129,9 +127,13 @@ when defined(windows):
|
||||||
if CreateIOCompletionPort(sock.THandle, p.ioPort,
|
if CreateIOCompletionPort(sock.THandle, p.ioPort,
|
||||||
cast[TCompletionKey](sock), 1) == 0:
|
cast[TCompletionKey](sock), 1) == 0:
|
||||||
OSError(OSLastError())
|
OSError(OSLastError())
|
||||||
|
p.hasHandles = true
|
||||||
|
|
||||||
proc poll*(p: PDispatcher, timeout = 500) =
|
proc poll*(p: PDispatcher, timeout = 500) =
|
||||||
## Waits for completion events and processes them.
|
## Waits for completion events and processes them.
|
||||||
|
if not p.hasHandles:
|
||||||
|
raise newException(EInvalidValue, "No handles registered in dispatcher.")
|
||||||
|
|
||||||
let llTimeout =
|
let llTimeout =
|
||||||
if timeout == -1: winlean.INFINITE
|
if timeout == -1: winlean.INFINITE
|
||||||
else: timeout.int32
|
else: timeout.int32
|
||||||
|
|
@ -145,16 +147,19 @@ when defined(windows):
|
||||||
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
|
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
|
||||||
var customOverlapped = cast[PCustomOverlapped](lpOverlapped)
|
var customOverlapped = cast[PCustomOverlapped](lpOverlapped)
|
||||||
if res:
|
if res:
|
||||||
|
# This is useful for ensuring the reliability of the overlapped struct.
|
||||||
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
||||||
|
|
||||||
customOverlapped.data.cb(customOverlapped.data.sock, TOSErrorCode(-1))
|
customOverlapped.data.cb(customOverlapped.data.sock,
|
||||||
|
lpNumberOfBytesTransferred, TOSErrorCode(-1))
|
||||||
dealloc(customOverlapped)
|
dealloc(customOverlapped)
|
||||||
else:
|
else:
|
||||||
let errCode = OSLastError()
|
let errCode = OSLastError()
|
||||||
if lpOverlapped != nil:
|
if lpOverlapped != nil:
|
||||||
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
||||||
|
customOverlapped.data.cb(customOverlapped.data.sock,
|
||||||
|
lpNumberOfBytesTransferred, errCode)
|
||||||
dealloc(customOverlapped)
|
dealloc(customOverlapped)
|
||||||
customOverlapped.data.cb(customOverlapped.data.sock, errCode)
|
|
||||||
else:
|
else:
|
||||||
if errCode.int32 == WAIT_TIMEOUT:
|
if errCode.int32 == WAIT_TIMEOUT:
|
||||||
# Timed out
|
# Timed out
|
||||||
|
|
@ -252,11 +257,12 @@ when defined(windows):
|
||||||
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
|
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
|
||||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||||
ol.data = TCompletionData(sock: socket, cb:
|
ol.data = TCompletionData(sock: socket, cb:
|
||||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||||
if errcode == TOSErrorCode(-1):
|
if not retFuture.finished:
|
||||||
retFuture.complete(0)
|
if errcode == TOSErrorCode(-1):
|
||||||
else:
|
retFuture.complete(0)
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
else:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||||
)
|
)
|
||||||
|
|
||||||
var ret = connectEx(socket, it.ai_addr, sizeof(TSockAddrIn).cint,
|
var ret = connectEx(socket, it.ai_addr, sizeof(TSockAddrIn).cint,
|
||||||
|
|
@ -265,7 +271,9 @@ when defined(windows):
|
||||||
# Request to connect completed immediately.
|
# Request to connect completed immediately.
|
||||||
success = true
|
success = true
|
||||||
retFuture.complete(0)
|
retFuture.complete(0)
|
||||||
dealloc(ol)
|
# 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
|
break
|
||||||
else:
|
else:
|
||||||
lastError = OSLastError()
|
lastError = OSLastError()
|
||||||
|
|
@ -283,9 +291,13 @@ when defined(windows):
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc recv*(p: PDispatcher, socket: TSocketHandle, size: int): PFuture[string] =
|
proc recv*(p: PDispatcher, socket: TSocketHandle, size: int,
|
||||||
|
flags: int = 0): PFuture[string] =
|
||||||
## Reads ``size`` bytes from ``socket``. Returned future will complete once
|
## Reads ``size`` bytes from ``socket``. Returned future will complete once
|
||||||
## all of the requested data is read.
|
## all of the requested data is read. If socket is disconnected during the
|
||||||
|
## recv operation then the future may complete with only a part of the
|
||||||
|
## requested data read. If socket is disconnected and no data is available
|
||||||
|
## to be read then the future will complete with a value of ``""``.
|
||||||
|
|
||||||
var retFuture = newFuture[string]()
|
var retFuture = newFuture[string]()
|
||||||
|
|
||||||
|
|
@ -293,31 +305,50 @@ when defined(windows):
|
||||||
dataBuf.buf = newString(size)
|
dataBuf.buf = newString(size)
|
||||||
dataBuf.len = size
|
dataBuf.len = size
|
||||||
|
|
||||||
var bytesReceived, flags: DWord
|
var bytesReceived: DWord
|
||||||
|
var flagsio = flags.dword
|
||||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||||
ol.data = TCompletionData(sock: socket, cb:
|
ol.data = TCompletionData(sock: socket, cb:
|
||||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||||
if errcode == TOSErrorCode(-1):
|
if not retFuture.finished:
|
||||||
var data = newString(size)
|
if errcode == TOSErrorCode(-1):
|
||||||
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
if bytesCount == 0 and dataBuf.buf[0] == '\0':
|
||||||
retFuture.complete($data)
|
retFuture.complete("")
|
||||||
else:
|
else:
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
var data = newString(size)
|
||||||
|
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
||||||
|
retFuture.complete($data)
|
||||||
|
else:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||||
)
|
)
|
||||||
|
|
||||||
let ret = WSARecv(socket, addr dataBuf, 1, addr bytesReceived,
|
let ret = WSARecv(socket, addr dataBuf, 1, addr bytesReceived,
|
||||||
addr flags, cast[POverlapped](ol), nil)
|
addr flagsio, cast[POverlapped](ol), nil)
|
||||||
if ret == -1:
|
if ret == -1:
|
||||||
let err = OSLastError()
|
let err = OSLastError()
|
||||||
if err.int32 != ERROR_IO_PENDING:
|
if err.int32 != ERROR_IO_PENDING:
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||||
dealloc(ol)
|
dealloc(ol)
|
||||||
|
elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0':
|
||||||
|
# We have to ensure that the buffer is empty because WSARecv will tell
|
||||||
|
# us immediatelly when it was disconnected, even when there is still
|
||||||
|
# data in the buffer.
|
||||||
|
# We want to give the user as much data as we can. So we only return
|
||||||
|
# the empty string (which signals a disconnection) when there is
|
||||||
|
# nothing left to read.
|
||||||
|
retFuture.complete("")
|
||||||
|
# TODO: "For message-oriented sockets, where a zero byte message is often
|
||||||
|
# allowable, a failure with an error code of WSAEDISCON is used to
|
||||||
|
# indicate graceful closure."
|
||||||
|
# ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx
|
||||||
else:
|
else:
|
||||||
# Request to read completed immediately.
|
# Request to read completed immediately.
|
||||||
var data = newString(size)
|
var data = newString(size)
|
||||||
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
||||||
retFuture.complete($data)
|
retFuture.complete($data)
|
||||||
dealloc(ol)
|
# 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``.
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] =
|
proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] =
|
||||||
|
|
@ -332,11 +363,12 @@ when defined(windows):
|
||||||
var bytesReceived, flags: DWord
|
var bytesReceived, flags: DWord
|
||||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||||
ol.data = TCompletionData(sock: socket, cb:
|
ol.data = TCompletionData(sock: socket, cb:
|
||||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||||
if errcode == TOSErrorCode(-1):
|
if not retFuture.finished:
|
||||||
retFuture.complete(0)
|
if errcode == TOSErrorCode(-1):
|
||||||
else:
|
retFuture.complete(0)
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
else:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||||
)
|
)
|
||||||
|
|
||||||
let ret = WSASend(socket, addr dataBuf, 1, addr bytesReceived,
|
let ret = WSASend(socket, addr dataBuf, 1, addr bytesReceived,
|
||||||
|
|
@ -348,7 +380,9 @@ when defined(windows):
|
||||||
dealloc(ol)
|
dealloc(ol)
|
||||||
else:
|
else:
|
||||||
retFuture.complete(0)
|
retFuture.complete(0)
|
||||||
dealloc(ol)
|
# 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``.
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc acceptAddr*(p: PDispatcher, socket: TSocketHandle):
|
proc acceptAddr*(p: PDispatcher, socket: TSocketHandle):
|
||||||
|
|
@ -390,11 +424,12 @@ when defined(windows):
|
||||||
|
|
||||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||||
ol.data = TCompletionData(sock: socket, cb:
|
ol.data = TCompletionData(sock: socket, cb:
|
||||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||||
if errcode == TOSErrorCode(-1):
|
if not retFuture.finished:
|
||||||
completeAccept()
|
if errcode == TOSErrorCode(-1):
|
||||||
else:
|
completeAccept()
|
||||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
else:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||||
)
|
)
|
||||||
|
|
||||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
|
||||||
|
|
@ -411,70 +446,472 @@ when defined(windows):
|
||||||
dealloc(ol)
|
dealloc(ol)
|
||||||
else:
|
else:
|
||||||
completeAccept()
|
completeAccept()
|
||||||
dealloc(ol)
|
# 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``.
|
||||||
|
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] =
|
|
||||||
## Accepts a new connection. Returns a future containing the client socket
|
|
||||||
## corresponding to that connection.
|
|
||||||
## The future will complete when the connection is successfully accepted.
|
|
||||||
var retFut = newFuture[TSocketHandle]()
|
|
||||||
var fut = p.acceptAddr(socket)
|
|
||||||
fut.callback =
|
|
||||||
proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) =
|
|
||||||
assert future.finished
|
|
||||||
if future.failed:
|
|
||||||
retFut.fail(future.error)
|
|
||||||
else:
|
|
||||||
retFut.complete(future.read.client)
|
|
||||||
return retFut
|
|
||||||
|
|
||||||
initAll()
|
initAll()
|
||||||
else:
|
else:
|
||||||
# TODO: Selectors.
|
import selectors
|
||||||
|
from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK
|
||||||
|
type
|
||||||
|
TCallback = proc (sock: TSocketHandle): bool {.closure.}
|
||||||
|
|
||||||
|
PData* = ref object of PObject
|
||||||
|
sock: TSocketHandle
|
||||||
|
readCBs: seq[TCallback]
|
||||||
|
writeCBs: seq[TCallback]
|
||||||
|
|
||||||
|
PDispatcher* = ref object
|
||||||
|
selector: PSelector
|
||||||
|
|
||||||
|
proc newDispatcher*(): PDispatcher =
|
||||||
|
new result
|
||||||
|
result.selector = newSelector()
|
||||||
|
|
||||||
|
proc update(p: PDispatcher, sock: TSocketHandle, events: set[TEvent]) =
|
||||||
|
assert sock in p.selector
|
||||||
|
echo("Update: ", events)
|
||||||
|
if events == {}:
|
||||||
|
discard p.selector.unregister(sock)
|
||||||
|
else:
|
||||||
|
discard p.selector.update(sock, events)
|
||||||
|
|
||||||
|
proc addRead(p: PDispatcher, sock: TSocketHandle, cb: TCallback) =
|
||||||
|
if sock notin p.selector:
|
||||||
|
var data = PData(sock: sock, readCBs: @[cb], writeCBs: @[])
|
||||||
|
p.selector.register(sock, {EvRead}, data.PObject)
|
||||||
|
else:
|
||||||
|
p.selector[sock].data.PData.readCBs.add(cb)
|
||||||
|
p.update(sock, p.selector[sock].events + {EvRead})
|
||||||
|
|
||||||
|
proc addWrite(p: PDispatcher, sock: TSocketHandle, cb: TCallback) =
|
||||||
|
if sock notin p.selector:
|
||||||
|
var data = PData(sock: sock, readCBs: @[], writeCBs: @[cb])
|
||||||
|
p.selector.register(sock, {EvWrite}, data.PObject)
|
||||||
|
else:
|
||||||
|
p.selector[sock].data.PData.writeCBs.add(cb)
|
||||||
|
p.update(sock, p.selector[sock].events + {EvWrite})
|
||||||
|
|
||||||
|
proc poll*(p: PDispatcher, timeout = 500) =
|
||||||
|
for info in p.selector.select(timeout):
|
||||||
|
let data = PData(info.key.data)
|
||||||
|
assert data.sock == info.key.fd
|
||||||
|
echo("R: ", data.readCBs.len, " W: ", data.writeCBs.len, ". ", info.events)
|
||||||
|
|
||||||
|
if EvRead in info.events:
|
||||||
|
var newReadCBs: seq[TCallback] = @[]
|
||||||
|
for cb in data.readCBs:
|
||||||
|
if not cb(data.sock):
|
||||||
|
# Callback wants to be called again.
|
||||||
|
newReadCBs.add(cb)
|
||||||
|
data.readCBs = newReadCBs
|
||||||
|
|
||||||
|
if EvWrite in info.events:
|
||||||
|
var newWriteCBs: seq[TCallback] = @[]
|
||||||
|
for cb in data.writeCBs:
|
||||||
|
if not cb(data.sock):
|
||||||
|
# Callback wants to be called again.
|
||||||
|
newWriteCBs.add(cb)
|
||||||
|
data.writeCBs = newWriteCBs
|
||||||
|
|
||||||
|
var newEvents: set[TEvent]
|
||||||
|
if data.readCBs.len != 0: newEvents = {EvRead}
|
||||||
|
if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite}
|
||||||
|
p.update(data.sock, newEvents)
|
||||||
|
|
||||||
|
proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort,
|
||||||
|
af = AF_INET): PFuture[int] =
|
||||||
|
var retFuture = newFuture[int]()
|
||||||
|
|
||||||
|
proc cb(sock: TSocketHandle): bool =
|
||||||
|
# We have connected.
|
||||||
|
retFuture.complete(0)
|
||||||
|
return true
|
||||||
|
|
||||||
|
var aiList = getAddrInfo(address, port, af)
|
||||||
|
var success = false
|
||||||
|
var lastError: TOSErrorCode
|
||||||
|
var it = aiList
|
||||||
|
while it != nil:
|
||||||
|
var ret = connect(socket, it.ai_addr, it.ai_addrlen.TSocklen)
|
||||||
|
if ret == 0:
|
||||||
|
# Request to connect completed immediately.
|
||||||
|
success = true
|
||||||
|
retFuture.complete(0)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
lastError = osLastError()
|
||||||
|
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
|
||||||
|
success = true
|
||||||
|
addWrite(p, socket, cb)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
success = false
|
||||||
|
it = it.ai_next
|
||||||
|
|
||||||
|
dealloc(aiList)
|
||||||
|
if not success:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||||
|
return retFuture
|
||||||
|
|
||||||
|
proc recv*(p: PDispatcher, socket: TSocketHandle, size: int,
|
||||||
|
flags: int = 0): PFuture[string] =
|
||||||
|
var retFuture = newFuture[string]()
|
||||||
|
|
||||||
|
var readBuffer = newString(size)
|
||||||
|
var sizeRead = 0
|
||||||
|
|
||||||
|
proc cb(sock: TSocketHandle): bool =
|
||||||
|
result = true
|
||||||
|
let netSize = size - sizeRead
|
||||||
|
let res = recv(sock, addr readBuffer[sizeRead], netSize, flags.cint)
|
||||||
|
if res < 0:
|
||||||
|
let lastError = osLastError()
|
||||||
|
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||||
|
else:
|
||||||
|
result = false # We still want this callback to be called.
|
||||||
|
elif res == 0:
|
||||||
|
# Disconnected
|
||||||
|
if sizeRead == 0:
|
||||||
|
retFuture.complete("")
|
||||||
|
else:
|
||||||
|
readBuffer.setLen(sizeRead)
|
||||||
|
retFuture.complete(readBuffer)
|
||||||
|
else:
|
||||||
|
sizeRead.inc(res)
|
||||||
|
if res != netSize:
|
||||||
|
result = false # We want to read all the data requested.
|
||||||
|
else:
|
||||||
|
retFuture.complete(readBuffer)
|
||||||
|
|
||||||
|
addRead(p, socket, cb)
|
||||||
|
return retFuture
|
||||||
|
|
||||||
|
proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] =
|
||||||
|
var retFuture = newFuture[int]()
|
||||||
|
|
||||||
|
var written = 0
|
||||||
|
|
||||||
|
proc cb(sock: TSocketHandle): bool =
|
||||||
|
result = true
|
||||||
|
let netSize = data.len-written
|
||||||
|
var d = data.cstring
|
||||||
|
let res = send(sock, addr d[written], netSize, 0.cint)
|
||||||
|
if res < 0:
|
||||||
|
let lastError = osLastError()
|
||||||
|
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||||
|
else:
|
||||||
|
result = false # We still want this callback to be called.
|
||||||
|
else:
|
||||||
|
written.inc(res)
|
||||||
|
if res != netSize:
|
||||||
|
result = false # We still have data to send.
|
||||||
|
else:
|
||||||
|
retFuture.complete(0)
|
||||||
|
addWrite(p, socket, cb)
|
||||||
|
return retFuture
|
||||||
|
|
||||||
|
|
||||||
|
proc acceptAddr*(p: PDispatcher, socket: TSocketHandle):
|
||||||
|
PFuture[tuple[address: string, client: TSocketHandle]] =
|
||||||
|
var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]()
|
||||||
|
proc cb(sock: TSocketHandle): bool =
|
||||||
|
result = true
|
||||||
|
var sockAddress: Tsockaddr_in
|
||||||
|
var addrLen = sizeof(sockAddress).TSocklen
|
||||||
|
var client = accept(sock, cast[ptr TSockAddr](addr(sockAddress)),
|
||||||
|
addr(addrLen))
|
||||||
|
if client == osInvalidSocket:
|
||||||
|
let lastError = osLastError()
|
||||||
|
assert lastError.int32 notin {EWOULDBLOCK, EAGAIN}
|
||||||
|
if lastError.int32 == EINTR:
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||||
|
else:
|
||||||
|
retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client))
|
||||||
|
addRead(p, socket, cb)
|
||||||
|
return retFuture
|
||||||
|
|
||||||
|
proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] =
|
||||||
|
## Accepts a new connection. Returns a future containing the client socket
|
||||||
|
## corresponding to that connection.
|
||||||
|
## The future will complete when the connection is successfully accepted.
|
||||||
|
var retFut = newFuture[TSocketHandle]()
|
||||||
|
var fut = p.acceptAddr(socket)
|
||||||
|
fut.callback =
|
||||||
|
proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) =
|
||||||
|
assert future.finished
|
||||||
|
if future.failed:
|
||||||
|
retFut.fail(future.error)
|
||||||
|
else:
|
||||||
|
retFut.complete(future.read.client)
|
||||||
|
return retFut
|
||||||
|
|
||||||
|
# -- Await Macro
|
||||||
|
|
||||||
|
template createCb*(cbName, varNameIterSym, retFutureSym: expr): stmt {.immediate, dirty.} =
|
||||||
|
proc cbName {.closure.} =
|
||||||
|
if not varNameIterSym.finished:
|
||||||
|
var next = varNameIterSym()
|
||||||
|
if next == nil:
|
||||||
|
assert retFutureSym.finished, "Async procedure's return Future was not finished."
|
||||||
|
else:
|
||||||
|
next.callback = cbName
|
||||||
|
|
||||||
|
template createVar(futSymName: string, asyncProc: PNimrodNode,
|
||||||
|
valueReceiver: expr) {.immediate, dirty.} =
|
||||||
|
# TODO: Used template here due to bug #926
|
||||||
|
result = newNimNode(nnkStmtList)
|
||||||
|
var futSym = newIdentNode(futSymName) #genSym(nskVar, "future")
|
||||||
|
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
|
||||||
|
result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x>
|
||||||
|
valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read
|
||||||
|
|
||||||
|
proc processBody(node, retFutureSym: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
result = node
|
||||||
|
case node.kind
|
||||||
|
of nnkReturnStmt:
|
||||||
|
result = newNimNode(nnkStmtList)
|
||||||
|
result.add newCall(newIdentNode("complete"), retFutureSym,
|
||||||
|
if node[0].kind == nnkEmpty: newIdentNode("result") else: node[0])
|
||||||
|
result.add newNimNode(nnkYieldStmt).add(newNilLit())
|
||||||
|
of nnkCommand:
|
||||||
|
if node[0].ident == !"await":
|
||||||
|
case node[1].kind
|
||||||
|
of nnkIdent:
|
||||||
|
# await x
|
||||||
|
result = newNimNode(nnkYieldStmt).add(node[1]) # -> yield x
|
||||||
|
of nnkCall:
|
||||||
|
# await foo(p, x)
|
||||||
|
var futureValue: PNimrodNode
|
||||||
|
createVar("future" & $node[1][0].toStrLit, node[1], futureValue)
|
||||||
|
result.add futureValue
|
||||||
|
else:
|
||||||
|
error("Invalid node kind in 'await', got: " & $node[1].kind)
|
||||||
|
elif node[1].kind == nnkCommand and node[1][0].kind == nnkIdent and
|
||||||
|
node[1][0].ident == !"await":
|
||||||
|
# foo await x
|
||||||
|
var newCommand = node
|
||||||
|
createVar("future" & $node[0].ident, node[1][0], newCommand[1])
|
||||||
|
result.add newCommand
|
||||||
|
|
||||||
|
of nnkVarSection, nnkLetSection:
|
||||||
|
case node[0][2].kind
|
||||||
|
of nnkCommand:
|
||||||
|
if node[0][2][0].ident == !"await":
|
||||||
|
# var x = await y
|
||||||
|
var newVarSection = node # TODO: Should this use copyNimNode?
|
||||||
|
createVar("future" & $node[0][0].ident, node[0][2][1],
|
||||||
|
newVarSection[0][2])
|
||||||
|
result.add newVarSection
|
||||||
|
else: discard
|
||||||
|
of nnkAsgn:
|
||||||
|
case node[1].kind
|
||||||
|
of nnkCommand:
|
||||||
|
if node[1][0].ident == !"await":
|
||||||
|
# x = await y
|
||||||
|
var newAsgn = node
|
||||||
|
createVar("future" & $node[0].ident, node[1][1], newAsgn[1])
|
||||||
|
result.add newAsgn
|
||||||
|
else: discard
|
||||||
|
of nnkDiscardStmt:
|
||||||
|
# discard await x
|
||||||
|
if node[0][0].ident == !"await":
|
||||||
|
var dummy = newNimNode(nnkStmtList)
|
||||||
|
createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], dummy)
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
for i in 0 .. <result.len:
|
||||||
|
result[i] = processBody(result[i], retFutureSym)
|
||||||
|
#echo(treeRepr(result))
|
||||||
|
|
||||||
|
proc getName(node: PNimrodNode): string {.compileTime.} =
|
||||||
|
case node.kind
|
||||||
|
of nnkPostfix:
|
||||||
|
return $node[1].ident
|
||||||
|
of nnkIdent:
|
||||||
|
return $node.ident
|
||||||
|
else:
|
||||||
|
assert false
|
||||||
|
|
||||||
|
macro async*(prc: stmt): stmt {.immediate.} =
|
||||||
|
expectKind(prc, nnkProcDef)
|
||||||
|
|
||||||
|
hint("Processing " & prc[0].getName & " as an async proc.")
|
||||||
|
|
||||||
|
# Verify that the return type is a PFuture[T]
|
||||||
|
if prc[3][0].kind == nnkIdent:
|
||||||
|
error("Expected return type of 'PFuture' got '" & $prc[3][0] & "'")
|
||||||
|
elif prc[3][0].kind == nnkBracketExpr:
|
||||||
|
if $prc[3][0][0] != "PFuture":
|
||||||
|
error("Expected return type of 'PFuture' got '" & $prc[3][0][0] & "'")
|
||||||
|
|
||||||
|
# TODO: Why can't I use genSym? I get illegal capture errors for Syms.
|
||||||
|
# TODO: It seems genSym is broken. Change all usages back to genSym when fixed
|
||||||
|
|
||||||
|
var outerProcBody = newNimNode(nnkStmtList)
|
||||||
|
|
||||||
|
# -> var retFuture = newFuture[T]()
|
||||||
|
var retFutureSym = newIdentNode("retFuture") #genSym(nskVar, "retFuture")
|
||||||
|
outerProcBody.add(
|
||||||
|
newVarStmt(retFutureSym,
|
||||||
|
newCall(
|
||||||
|
newNimNode(nnkBracketExpr).add(
|
||||||
|
newIdentNode("newFuture"),
|
||||||
|
prc[3][0][1])))) # Get type from return type of this proc.
|
||||||
|
|
||||||
|
# -> iterator nameIter(): PFutureBase {.closure.} =
|
||||||
|
# -> var result: T
|
||||||
|
# -> <proc_body>
|
||||||
|
# -> complete(retFuture, result)
|
||||||
|
var iteratorNameSym = newIdentNode($prc[0].getName & "Iter") #genSym(nskIterator, $prc[0].ident & "Iter")
|
||||||
|
var procBody = prc[6].processBody(retFutureSym)
|
||||||
|
procBody.insert(0, newNimNode(nnkVarSection).add(
|
||||||
|
newIdentDefs(newIdentNode("result"), prc[3][0][1]))) # -> var result: T
|
||||||
|
procBody.add(
|
||||||
|
newCall(newIdentNode("complete"),
|
||||||
|
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
|
||||||
|
|
||||||
|
var closureIterator = newProc(iteratorNameSym, [newIdentNode("PFutureBase")],
|
||||||
|
procBody, nnkIteratorDef)
|
||||||
|
closureIterator[4] = newNimNode(nnkPragma).add(newIdentNode("closure"))
|
||||||
|
outerProcBody.add(closureIterator)
|
||||||
|
|
||||||
|
# -> var nameIterVar = nameIter
|
||||||
|
# -> var first = nameIterVar()
|
||||||
|
var varNameIterSym = newIdentNode($prc[0].getName & "IterVar") #genSym(nskVar, $prc[0].ident & "IterVar")
|
||||||
|
var varNameIter = newVarStmt(varNameIterSym, iteratorNameSym)
|
||||||
|
outerProcBody.add varNameIter
|
||||||
|
var varFirstSym = genSym(nskVar, "first")
|
||||||
|
var varFirst = newVarStmt(varFirstSym, newCall(varNameIterSym))
|
||||||
|
outerProcBody.add varFirst
|
||||||
|
|
||||||
|
# -> createCb(cb, nameIter, retFuture)
|
||||||
|
var cbName = newIdentNode("cb")
|
||||||
|
var procCb = newCall("createCb", cbName, varNameIterSym, retFutureSym)
|
||||||
|
outerProcBody.add procCb
|
||||||
|
|
||||||
|
# -> first.callback = cb
|
||||||
|
outerProcBody.add newAssignment(
|
||||||
|
newDotExpr(varFirstSym, newIdentNode("callback")),
|
||||||
|
cbName)
|
||||||
|
|
||||||
|
# -> return retFuture
|
||||||
|
outerProcBody.add newNimNode(nnkReturnStmt).add(retFutureSym)
|
||||||
|
|
||||||
|
result = prc
|
||||||
|
|
||||||
|
# Remove the 'async' pragma.
|
||||||
|
for i in 0 .. <result[4].len:
|
||||||
|
if result[4][i].ident == !"async":
|
||||||
|
result[4].del(i)
|
||||||
|
|
||||||
|
result[6] = outerProcBody
|
||||||
|
|
||||||
|
echo(toStrLit(result))
|
||||||
|
|
||||||
|
proc recvLine*(p: PDispatcher, socket: TSocketHandle): PFuture[string] {.async.} =
|
||||||
|
## Reads a line of data from ``socket``. Returned future will complete once
|
||||||
|
## a full line is read or an error occurs.
|
||||||
|
##
|
||||||
|
## If a full line is read ``\r\L`` is not
|
||||||
|
## added to ``line``, however if solely ``\r\L`` is read then ``line``
|
||||||
|
## will be set to it.
|
||||||
|
##
|
||||||
|
## If the socket is disconnected, ``line`` will be set to ``""``.
|
||||||
|
|
||||||
|
template addNLIfEmpty(): stmt =
|
||||||
|
if result.len == 0:
|
||||||
|
result.add("\c\L")
|
||||||
|
|
||||||
|
result = ""
|
||||||
|
var c = ""
|
||||||
|
while true:
|
||||||
|
c = await p.recv(socket, 1)
|
||||||
|
if c.len == 0:
|
||||||
|
return
|
||||||
|
if c == "\r":
|
||||||
|
c = await p.recv(socket, 1, MSG_PEEK)
|
||||||
|
if c.len > 0 and c == "\L":
|
||||||
|
discard await p.recv(socket, 1)
|
||||||
|
addNLIfEmpty()
|
||||||
|
return
|
||||||
|
elif c == "\L":
|
||||||
|
addNLIfEmpty()
|
||||||
|
return
|
||||||
|
add(result.string, c)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
|
||||||
var p = newDispatcher()
|
var p = newDispatcher()
|
||||||
var sock = socket()
|
var sock = socket()
|
||||||
#sock.setBlocking false
|
sock.setBlocking false
|
||||||
p.register(sock)
|
|
||||||
|
|
||||||
when true:
|
|
||||||
|
|
||||||
var f = p.connect(sock, "irc.freenode.org", TPort(6667))
|
when false:
|
||||||
f.callback =
|
# Await tests
|
||||||
proc (future: PFuture[int]) =
|
proc main(p: PDispatcher): PFuture[int] {.async.} =
|
||||||
echo("Connected in future!")
|
discard await p.connect(sock, "irc.freenode.net", TPort(6667))
|
||||||
echo(future.read)
|
while true:
|
||||||
for i in 0 .. 50:
|
var line = await p.recvLine(sock)
|
||||||
var recvF = p.recv(sock, 10)
|
echo("Line is: ", line.repr)
|
||||||
recvF.callback =
|
if line == "":
|
||||||
proc (future: PFuture[string]) =
|
echo "Disconnected"
|
||||||
echo("Read: ", future.read)
|
break
|
||||||
|
|
||||||
|
proc peekTest(p: PDispatcher): PFuture[int] {.async.} =
|
||||||
|
discard await p.connect(sock, "localhost", TPort(6667))
|
||||||
|
while true:
|
||||||
|
var line = await p.recv(sock, 1, MSG_PEEK)
|
||||||
|
var line2 = await p.recv(sock, 1)
|
||||||
|
echo(line.repr)
|
||||||
|
echo(line2.repr)
|
||||||
|
echo("---")
|
||||||
|
if line2 == "": break
|
||||||
|
sleep(500)
|
||||||
|
|
||||||
|
var f = main(p)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
when false:
|
||||||
|
|
||||||
sock.bindAddr(TPort(6667))
|
var f = p.connect(sock, "irc.freenode.org", TPort(6667))
|
||||||
sock.listen()
|
f.callback =
|
||||||
proc onAccept(future: PFuture[TSocketHandle]) =
|
|
||||||
echo "Accepted"
|
|
||||||
var t = p.send(future.read, "test\c\L")
|
|
||||||
t.callback =
|
|
||||||
proc (future: PFuture[int]) =
|
proc (future: PFuture[int]) =
|
||||||
|
echo("Connected in future!")
|
||||||
echo(future.read)
|
echo(future.read)
|
||||||
|
for i in 0 .. 50:
|
||||||
|
var recvF = p.recv(sock, 10)
|
||||||
|
recvF.callback =
|
||||||
|
proc (future: PFuture[string]) =
|
||||||
|
echo("Read ", future.read.len, ": ", future.read.repr)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
sock.bindAddr(TPort(6667))
|
||||||
|
sock.listen()
|
||||||
|
proc onAccept(future: PFuture[TSocketHandle]) =
|
||||||
|
echo "Accepted"
|
||||||
|
var t = p.send(future.read, "test\c\L")
|
||||||
|
t.callback =
|
||||||
|
proc (future: PFuture[int]) =
|
||||||
|
echo(future.read)
|
||||||
|
|
||||||
|
var f = p.accept(sock)
|
||||||
|
f.callback = onAccept
|
||||||
|
|
||||||
var f = p.accept(sock)
|
var f = p.accept(sock)
|
||||||
f.callback = onAccept
|
f.callback = onAccept
|
||||||
|
|
||||||
var f = p.accept(sock)
|
|
||||||
f.callback = onAccept
|
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
p.poll()
|
p.poll()
|
||||||
echo "polled"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
type
|
type
|
||||||
TLibHandle* = pointer ## a handle to a dynamically loaded library
|
TLibHandle* = pointer ## a handle to a dynamically loaded library
|
||||||
|
|
||||||
proc loadLib*(path: string): TLibHandle
|
proc loadLib*(path: string, global_symbols=false): TLibHandle
|
||||||
## loads a library from `path`. Returns nil if the library could not
|
## loads a library from `path`. Returns nil if the library could not
|
||||||
## be loaded.
|
## be loaded.
|
||||||
|
|
||||||
|
|
@ -53,6 +53,7 @@ when defined(posix):
|
||||||
#
|
#
|
||||||
var
|
var
|
||||||
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
|
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
|
||||||
|
RTLD_GLOBAL {.importc: "RTLD_GLOBAL", header: "<dlfcn.h>".}: int
|
||||||
|
|
||||||
proc dlclose(lib: TLibHandle) {.importc, header: "<dlfcn.h>".}
|
proc dlclose(lib: TLibHandle) {.importc, header: "<dlfcn.h>".}
|
||||||
proc dlopen(path: CString, mode: int): TLibHandle {.
|
proc dlopen(path: CString, mode: int): TLibHandle {.
|
||||||
|
|
@ -60,7 +61,10 @@ when defined(posix):
|
||||||
proc dlsym(lib: TLibHandle, name: cstring): pointer {.
|
proc dlsym(lib: TLibHandle, name: cstring): pointer {.
|
||||||
importc, header: "<dlfcn.h>".}
|
importc, header: "<dlfcn.h>".}
|
||||||
|
|
||||||
proc loadLib(path: string): TLibHandle = return dlopen(path, RTLD_NOW)
|
proc loadLib(path: string, global_symbols=false): TLibHandle =
|
||||||
|
var flags = RTLD_NOW
|
||||||
|
if global_symbols: flags = flags or RTLD_GLOBAL
|
||||||
|
return dlopen(path, flags)
|
||||||
proc loadLib(): TLibHandle = return dlopen(nil, RTLD_NOW)
|
proc loadLib(): TLibHandle = return dlopen(nil, RTLD_NOW)
|
||||||
proc unloadLib(lib: TLibHandle) = dlclose(lib)
|
proc unloadLib(lib: TLibHandle) = dlclose(lib)
|
||||||
proc symAddr(lib: TLibHandle, name: cstring): pointer =
|
proc symAddr(lib: TLibHandle, name: cstring): pointer =
|
||||||
|
|
@ -81,7 +85,7 @@ elif defined(windows) or defined(dos):
|
||||||
proc getProcAddress(lib: THINSTANCE, name: cstring): pointer {.
|
proc getProcAddress(lib: THINSTANCE, name: cstring): pointer {.
|
||||||
importc: "GetProcAddress", header: "<windows.h>", stdcall.}
|
importc: "GetProcAddress", header: "<windows.h>", stdcall.}
|
||||||
|
|
||||||
proc loadLib(path: string): TLibHandle =
|
proc loadLib(path: string, global_symbols=false): TLibHandle =
|
||||||
result = cast[TLibHandle](winLoadLibrary(path))
|
result = cast[TLibHandle](winLoadLibrary(path))
|
||||||
proc loadLib(): TLibHandle =
|
proc loadLib(): TLibHandle =
|
||||||
result = cast[TLibHandle](winLoadLibrary(nil))
|
result = cast[TLibHandle](winLoadLibrary(nil))
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,18 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {.
|
||||||
dealloc(aiList)
|
dealloc(aiList)
|
||||||
osError(osLastError())
|
osError(osLastError())
|
||||||
dealloc(aiList)
|
dealloc(aiList)
|
||||||
|
|
||||||
|
proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} =
|
||||||
|
## Sets blocking mode on socket
|
||||||
|
when defined(Windows):
|
||||||
|
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
|
||||||
|
if ioctlsocket(s, FIONBIO, addr(mode)) == -1:
|
||||||
|
osError(osLastError())
|
||||||
|
else: # BSD sockets
|
||||||
|
var x: int = fcntl(s, F_GETFL, 0)
|
||||||
|
if x == -1:
|
||||||
|
osError(osLastError())
|
||||||
|
else:
|
||||||
|
var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
|
||||||
|
if fcntl(s, F_SETFL, mode) == -1:
|
||||||
|
osError(osLastError())
|
||||||
|
|
@ -836,9 +836,11 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string =
|
||||||
while i < s.len:
|
while i < s.len:
|
||||||
c.ml = 0
|
c.ml = 0
|
||||||
var L = rawMatch(s, pattern, i, c)
|
var L = rawMatch(s, pattern, i, c)
|
||||||
if L < 0: break
|
if L < 0:
|
||||||
yield substr(s, i, i+L-1)
|
inc(i, 1)
|
||||||
inc(i, L)
|
else:
|
||||||
|
yield substr(s, i, i+L-1)
|
||||||
|
inc(i, L)
|
||||||
|
|
||||||
proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {.
|
proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {.
|
||||||
nosideEffect, rtl, extern: "npegs$1".} =
|
nosideEffect, rtl, extern: "npegs$1".} =
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Nimrod's Runtime Library
|
# Nimrod's Runtime Library
|
||||||
# (c) Copyright 2013 Dominik Picheta
|
# (c) Copyright 2014 Dominik Picheta
|
||||||
#
|
#
|
||||||
# See the file "copying.txt", included in this
|
# See the file "copying.txt", included in this
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
|
|
@ -9,212 +9,211 @@
|
||||||
|
|
||||||
# TODO: Docs.
|
# TODO: Docs.
|
||||||
|
|
||||||
import tables, os, unsigned
|
import tables, os, unsigned, hashes
|
||||||
when defined(windows):
|
|
||||||
import winlean
|
when defined(linux): import posix, epoll
|
||||||
else:
|
elif defined(windows): import winlean
|
||||||
import posix
|
|
||||||
|
proc hash*(x: TSocketHandle): THash {.borrow.}
|
||||||
|
|
||||||
type
|
type
|
||||||
TEvent* = enum
|
TEvent* = enum
|
||||||
EvRead, EvWrite
|
EvRead, EvWrite
|
||||||
|
|
||||||
TSelectorKey* = object
|
PSelectorKey* = ref object
|
||||||
fd: cint
|
fd*: TSocketHandle
|
||||||
events: set[TEvent]
|
events*: set[TEvent] ## The events which ``fd`` listens for.
|
||||||
data: PObject
|
data*: PObject ## User object.
|
||||||
|
|
||||||
TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]]
|
TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]]
|
||||||
|
|
||||||
PSelector* = ref object of PObject ## Selector interface.
|
when defined(linux) or defined(nimdoc):
|
||||||
fds*: TTable[cint, TSelectorKey]
|
|
||||||
registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent],
|
|
||||||
data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].}
|
|
||||||
unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].}
|
|
||||||
selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].}
|
|
||||||
closeImpl*: proc (s: PSelector) {.nimcall.}
|
|
||||||
|
|
||||||
template initSelector(r: expr) =
|
|
||||||
new r
|
|
||||||
r.fds = initTable[cint, TSelectorKey]()
|
|
||||||
|
|
||||||
proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject):
|
|
||||||
TSelectorKey =
|
|
||||||
if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data)
|
|
||||||
|
|
||||||
proc unregister*(s: PSelector, fd: cint): TSelectorKey =
|
|
||||||
##
|
|
||||||
## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey``
|
|
||||||
## will only have the ``fd`` field set. This is an optimisation and may
|
|
||||||
## change in the future if a viable use case is presented.
|
|
||||||
if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd)
|
|
||||||
|
|
||||||
proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] =
|
|
||||||
##
|
|
||||||
## The ``events`` field of the returned ``key`` contains the original events
|
|
||||||
## for which the ``fd`` was bound. This is contrary to the ``events`` field
|
|
||||||
## of the ``TReadyInfo`` tuple which determines which events are ready
|
|
||||||
## on the ``fd``.
|
|
||||||
|
|
||||||
if not s.selectImpl.isNil: result = s.selectImpl(s, timeout)
|
|
||||||
|
|
||||||
proc close*(s: PSelector) =
|
|
||||||
if not s.closeImpl.isNil: s.closeImpl(s)
|
|
||||||
|
|
||||||
# ---- Select() ----------------------------------------------------------------
|
|
||||||
|
|
||||||
type
|
|
||||||
PSelectSelector* = ref object of PSelector ## Implementation of select()
|
|
||||||
|
|
||||||
proc ssRegister(s: PSelector, fd: cint, events: set[TEvent],
|
|
||||||
data: PObject): TSelectorKey =
|
|
||||||
if s.fds.hasKey(fd):
|
|
||||||
raise newException(EInvalidValue, "FD already exists in selector.")
|
|
||||||
var sk = TSelectorKey(fd: fd, events: events, data: data)
|
|
||||||
s.fds[fd] = sk
|
|
||||||
result = sk
|
|
||||||
|
|
||||||
proc ssUnregister(s: PSelector, fd: cint): TSelectorKey =
|
|
||||||
result = s.fds[fd]
|
|
||||||
s.fds.del(fd)
|
|
||||||
|
|
||||||
proc ssClose(s: PSelector) = nil
|
|
||||||
|
|
||||||
proc timeValFromMilliseconds(timeout: int): TTimeVal =
|
|
||||||
if timeout != -1:
|
|
||||||
var seconds = timeout div 1000
|
|
||||||
result.tv_sec = seconds.int32
|
|
||||||
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
|
||||||
|
|
||||||
proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey],
|
|
||||||
m: var int) =
|
|
||||||
FD_ZERO(rd); FD_ZERO(wr)
|
|
||||||
for k, v in pairs(fds):
|
|
||||||
if EvRead in v.events:
|
|
||||||
m = max(m, int(k))
|
|
||||||
FD_SET(k, rd)
|
|
||||||
if EvWrite in v.events:
|
|
||||||
m = max(m, int(k))
|
|
||||||
FD_SET(k, wr)
|
|
||||||
|
|
||||||
proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]):
|
|
||||||
seq[TReadyInfo] =
|
|
||||||
result = @[]
|
|
||||||
for k, v in pairs(fds):
|
|
||||||
var events: set[TEvent] = {}
|
|
||||||
if FD_ISSET(k, rd) != 0'i32:
|
|
||||||
events = events + {EvRead}
|
|
||||||
if FD_ISSET(k, wr) != 0'i32:
|
|
||||||
events = events + {EvWrite}
|
|
||||||
result.add((v, events))
|
|
||||||
|
|
||||||
proc select(fds: TTable[cint, TSelectorKey], timeout = 500):
|
|
||||||
seq[TReadyInfo] =
|
|
||||||
var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
|
|
||||||
|
|
||||||
var rd, wr: TFdSet
|
|
||||||
var m = 0
|
|
||||||
createFdSet(rd, wr, fds, m)
|
|
||||||
|
|
||||||
var retCode = 0
|
|
||||||
if timeout != -1:
|
|
||||||
retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv)))
|
|
||||||
else:
|
|
||||||
retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil))
|
|
||||||
|
|
||||||
if retCode < 0:
|
|
||||||
OSError(OSLastError())
|
|
||||||
elif retCode == 0:
|
|
||||||
return @[]
|
|
||||||
else:
|
|
||||||
return getReadyFDs(rd, wr, fds)
|
|
||||||
|
|
||||||
proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
|
|
||||||
result = select(s.fds, timeout)
|
|
||||||
|
|
||||||
proc newSelectSelector*(): PSelectSelector =
|
|
||||||
initSelector(result)
|
|
||||||
result.registerImpl = ssRegister
|
|
||||||
result.unregisterImpl = ssUnregister
|
|
||||||
result.selectImpl = ssSelect
|
|
||||||
result.closeImpl = ssClose
|
|
||||||
|
|
||||||
# ---- Epoll -------------------------------------------------------------------
|
|
||||||
|
|
||||||
when defined(linux):
|
|
||||||
import epoll
|
|
||||||
type
|
type
|
||||||
PEpollSelector* = ref object of PSelector
|
PSelector* = ref object
|
||||||
epollFD: cint
|
epollFD: cint
|
||||||
events: array[64, ptr epoll_event]
|
events: array[64, ptr epoll_event]
|
||||||
|
fds: TTable[TSocketHandle, PSelectorKey]
|
||||||
|
|
||||||
TDataWrapper = object
|
proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event =
|
||||||
fd: cint
|
|
||||||
boundEvents: set[TEvent] ## The events which ``fd`` listens for.
|
|
||||||
data: PObject ## User object.
|
|
||||||
|
|
||||||
proc esRegister(s: PSelector, fd: cint, events: set[TEvent],
|
|
||||||
data: PObject): TSelectorKey =
|
|
||||||
var es = PEpollSelector(s)
|
|
||||||
var event: epoll_event
|
|
||||||
if EvRead in events:
|
if EvRead in events:
|
||||||
event.events = EPOLLIN
|
result.events = EPOLLIN
|
||||||
if EvWrite in events:
|
if EvWrite in events:
|
||||||
event.events = event.events or EPOLLOUT
|
result.events = result.events or EPOLLOUT
|
||||||
|
result.data.fd = fd.cint
|
||||||
|
|
||||||
var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd
|
proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent],
|
||||||
dw.fd = fd
|
data: PObject): PSelectorKey {.discardable.} =
|
||||||
dw.boundEvents = events
|
## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent
|
||||||
dw.data = data
|
## ``events``.
|
||||||
event.data.thePtr = dw
|
if s.fds.hasKey(fd):
|
||||||
|
raise newException(EInvalidValue, "File descriptor already exists.")
|
||||||
|
|
||||||
if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
|
var event = createEventStruct(events, fd)
|
||||||
|
|
||||||
|
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
|
||||||
OSError(OSLastError())
|
OSError(OSLastError())
|
||||||
|
|
||||||
result = TSelectorKey(fd: fd, events: events, data: data)
|
var key = PSelectorKey(fd: fd, events: events, data: data)
|
||||||
|
|
||||||
proc esUnregister(s: PSelector, fd: cint): TSelectorKey =
|
s.fds[fd] = key
|
||||||
# We cannot find out the information about this ``fd`` from the epoll
|
result = key
|
||||||
# context. As such I will simply return an almost empty TSelectorKey.
|
|
||||||
var es = PEpollSelector(s)
|
proc update*(s: PSelector, fd: TSocketHandle,
|
||||||
if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
|
events: set[TEvent]): PSelectorKey {.discardable.} =
|
||||||
|
## Updates the events which ``fd`` wants notifications for.
|
||||||
|
if not s.fds.hasKey(fd):
|
||||||
|
raise newException(EInvalidValue, "File descriptor not found.")
|
||||||
|
var event = createEventStruct(events, fd)
|
||||||
|
|
||||||
|
s.fds[fd].events = events
|
||||||
|
echo("About to update")
|
||||||
|
if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0:
|
||||||
OSError(OSLastError())
|
OSError(OSLastError())
|
||||||
# We could fill in the ``fds`` TTable to get the info, but that wouldn't
|
echo("finished updating")
|
||||||
# be nice for our memory.
|
result = s.fds[fd]
|
||||||
result = TSelectorKey(fd: fd, events: {}, data: nil)
|
|
||||||
|
|
||||||
proc esClose(s: PSelector) =
|
proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} =
|
||||||
var es = PEpollSelector(s)
|
if not s.fds.hasKey(fd):
|
||||||
if es.epollFD.close() != 0: OSError(OSLastError())
|
raise newException(EInvalidValue, "File descriptor not found.")
|
||||||
dealloc(addr es.events) # TODO: Test this
|
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
|
||||||
|
OSError(OSLastError())
|
||||||
|
result = s.fds[fd]
|
||||||
|
s.fds.del(fd)
|
||||||
|
|
||||||
proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
|
proc close*(s: PSelector) =
|
||||||
|
if s.epollFD.close() != 0: OSError(OSLastError())
|
||||||
|
dealloc(addr s.events) # TODO: Test this
|
||||||
|
|
||||||
|
proc select*(s: PSelector, timeout: int): seq[TReadyInfo] =
|
||||||
|
##
|
||||||
|
## The ``events`` field of the returned ``key`` contains the original events
|
||||||
|
## for which the ``fd`` was bound. This is contrary to the ``events`` field
|
||||||
|
## of the ``TReadyInfo`` tuple which determines which events are ready
|
||||||
|
## on the ``fd``.
|
||||||
result = @[]
|
result = @[]
|
||||||
var es = PEpollSelector(s)
|
|
||||||
|
|
||||||
let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint)
|
let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint)
|
||||||
if evNum < 0: OSError(OSLastError())
|
if evNum < 0: OSError(OSLastError())
|
||||||
if evNum == 0: return @[]
|
if evNum == 0: return @[]
|
||||||
for i in 0 .. <evNum:
|
for i in 0 .. <evNum:
|
||||||
var evSet: set[TEvent] = {}
|
var evSet: set[TEvent] = {}
|
||||||
if (es.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
|
if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
|
||||||
if (es.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
|
if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
|
||||||
let dw = cast[ptr TDataWrapper](es.events[i].data.thePtr)
|
|
||||||
|
|
||||||
let selectorKey = TSelectorKey(fd: dw.fd, events: dw.boundEvents,
|
let selectorKey = s.fds[s.events[i].data.fd.TSocketHandle]
|
||||||
data: dw.data)
|
|
||||||
result.add((selectorKey, evSet))
|
result.add((selectorKey, evSet))
|
||||||
|
|
||||||
proc newEpollSelector*(): PEpollSelector =
|
proc newSelector*(): PSelector =
|
||||||
new result
|
new result
|
||||||
result.epollFD = epoll_create(64)
|
result.epollFD = epoll_create(64)
|
||||||
result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64))
|
result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64))
|
||||||
|
result.fds = initTable[TSocketHandle, PSelectorKey]()
|
||||||
if result.epollFD < 0:
|
if result.epollFD < 0:
|
||||||
OSError(OSLastError())
|
OSError(OSLastError())
|
||||||
result.registerImpl = esRegister
|
|
||||||
result.unregisterImpl = esUnregister
|
proc contains*(s: PSelector, fd: TSocketHandle): bool =
|
||||||
result.closeImpl = esClose
|
## Determines whether selector contains a file descriptor.
|
||||||
result.selectImpl = esSelect
|
return s.fds.hasKey(fd)
|
||||||
|
|
||||||
|
proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey =
|
||||||
|
## Retrieves the selector key for ``fd``.
|
||||||
|
return s.fds[fd]
|
||||||
|
|
||||||
|
elif defined(windows):
|
||||||
|
type
|
||||||
|
PSelector* = ref object
|
||||||
|
fds: TTable[TSocketHandle, PSelectorKey]
|
||||||
|
|
||||||
|
proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent],
|
||||||
|
data: PObject): PSelectorKey {.discardable.} =
|
||||||
|
if s.fds.hasKey(fd):
|
||||||
|
raise newException(EInvalidValue, "File descriptor already exists.")
|
||||||
|
var sk = PSelectorKey(fd: fd, events: events, data: data)
|
||||||
|
s.fds[fd] = sk
|
||||||
|
result = sk
|
||||||
|
|
||||||
|
proc update*(s: PSelector, fd: TSocketHandle,
|
||||||
|
events: set[TEvent]): PSelectorKey {.discardable.} =
|
||||||
|
## Updates the events which ``fd`` wants notifications for.
|
||||||
|
if not s.fds.hasKey(fd):
|
||||||
|
raise newException(EInvalidValue, "File descriptor not found.")
|
||||||
|
|
||||||
|
s.fds[fd].events = events
|
||||||
|
result = s.fds[fd]
|
||||||
|
|
||||||
|
proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} =
|
||||||
|
result = s.fds[fd]
|
||||||
|
s.fds.del(fd)
|
||||||
|
|
||||||
|
proc close*(s: PSelector) = nil
|
||||||
|
|
||||||
|
proc timeValFromMilliseconds(timeout: int): TTimeVal =
|
||||||
|
if timeout != -1:
|
||||||
|
var seconds = timeout div 1000
|
||||||
|
result.tv_sec = seconds.int32
|
||||||
|
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
||||||
|
|
||||||
|
proc createFdSet(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey],
|
||||||
|
m: var int) =
|
||||||
|
FD_ZERO(rd); FD_ZERO(wr)
|
||||||
|
for k, v in pairs(fds):
|
||||||
|
if EvRead in v.events:
|
||||||
|
m = max(m, int(k))
|
||||||
|
FD_SET(k, rd)
|
||||||
|
if EvWrite in v.events:
|
||||||
|
m = max(m, int(k))
|
||||||
|
FD_SET(k, wr)
|
||||||
|
|
||||||
|
proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey]):
|
||||||
|
seq[TReadyInfo] =
|
||||||
|
result = @[]
|
||||||
|
for k, v in pairs(fds):
|
||||||
|
var events: set[TEvent] = {}
|
||||||
|
if FD_ISSET(k, rd) != 0'i32:
|
||||||
|
events = events + {EvRead}
|
||||||
|
if FD_ISSET(k, wr) != 0'i32:
|
||||||
|
events = events + {EvWrite}
|
||||||
|
result.add((v, events))
|
||||||
|
|
||||||
|
proc select(fds: TTable[TSocketHandle, PSelectorKey], timeout = 500):
|
||||||
|
seq[TReadyInfo] =
|
||||||
|
var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
|
||||||
|
|
||||||
|
var rd, wr: TFdSet
|
||||||
|
var m = 0
|
||||||
|
createFdSet(rd, wr, fds, m)
|
||||||
|
|
||||||
|
var retCode = 0
|
||||||
|
if timeout != -1:
|
||||||
|
retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, addr(tv)))
|
||||||
|
else:
|
||||||
|
retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, nil))
|
||||||
|
|
||||||
|
if retCode < 0:
|
||||||
|
OSError(OSLastError())
|
||||||
|
elif retCode == 0:
|
||||||
|
return @[]
|
||||||
|
else:
|
||||||
|
return getReadyFDs(rd, wr, fds)
|
||||||
|
|
||||||
|
proc select*(s: PSelector, timeout: int): seq[TReadyInfo] =
|
||||||
|
result = select(s.fds, timeout)
|
||||||
|
|
||||||
|
proc newSelector*(): PSelector =
|
||||||
|
new result
|
||||||
|
result.fds = initTable[TSocketHandle, PSelectorKey]()
|
||||||
|
|
||||||
|
proc contains*(s: PSelector, fd: TSocketHandle): bool =
|
||||||
|
return s.fds.hasKey(fd)
|
||||||
|
|
||||||
|
proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey =
|
||||||
|
return s.fds[fd]
|
||||||
|
|
||||||
|
elif defined(bsd) or defined(macosx):
|
||||||
|
# TODO: kqueue
|
||||||
|
{.error: "Sorry your platform is not supported yet.".}
|
||||||
|
else:
|
||||||
|
{.error: "Sorry your platform is not supported.".}
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
# Select()
|
# Select()
|
||||||
|
|
@ -224,11 +223,12 @@ when isMainModule:
|
||||||
sock: TSocket
|
sock: TSocket
|
||||||
|
|
||||||
var sock = socket()
|
var sock = socket()
|
||||||
|
sock.setBlocking(false)
|
||||||
sock.connect("irc.freenode.net", TPort(6667))
|
sock.connect("irc.freenode.net", TPort(6667))
|
||||||
|
|
||||||
var selector = newEpollSelector()
|
var selector = newSelector()
|
||||||
var data = PSockWrapper(sock: sock)
|
var data = PSockWrapper(sock: sock)
|
||||||
let key = selector.register(sock.getFD.cint, {EvRead}, data)
|
let key = selector.register(sock.getFD, {EvWrite}, data)
|
||||||
var i = 0
|
var i = 0
|
||||||
while true:
|
while true:
|
||||||
let ready = selector.select(1000)
|
let ready = selector.select(1000)
|
||||||
|
|
@ -236,6 +236,7 @@ when isMainModule:
|
||||||
if ready.len > 0: echo ready[0].events
|
if ready.len > 0: echo ready[0].events
|
||||||
i.inc
|
i.inc
|
||||||
if i == 6:
|
if i == 6:
|
||||||
|
assert selector.unregister(sock.getFD).fd == sock.getFD
|
||||||
selector.close()
|
selector.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ when hostos == "solaris":
|
||||||
|
|
||||||
when defined(Windows):
|
when defined(Windows):
|
||||||
import winlean
|
import winlean
|
||||||
|
export ioctlsocket
|
||||||
else:
|
else:
|
||||||
import posix
|
import posix
|
||||||
|
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL
|
||||||
|
|
||||||
export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen,
|
export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen,
|
||||||
inet_ntoa
|
inet_ntoa, recv, `==`, connect, send, accept
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
|
|
@ -63,10 +65,10 @@ type
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
let
|
let
|
||||||
OSInvalidSocket* = winlean.INVALID_SOCKET
|
osInvalidSocket* = winlean.INVALID_SOCKET
|
||||||
else:
|
else:
|
||||||
let
|
let
|
||||||
OSInvalidSocket* = posix.INVALID_SOCKET
|
osInvalidSocket* = posix.INVALID_SOCKET
|
||||||
|
|
||||||
proc `==`*(a, b: TPort): bool {.borrow.}
|
proc `==`*(a, b: TPort): bool {.borrow.}
|
||||||
## ``==`` for ports.
|
## ``==`` for ports.
|
||||||
|
|
@ -89,7 +91,7 @@ when defined(posix):
|
||||||
of AF_UNIX: result = posix.AF_UNIX
|
of AF_UNIX: result = posix.AF_UNIX
|
||||||
of AF_INET: result = posix.AF_INET
|
of AF_INET: result = posix.AF_INET
|
||||||
of AF_INET6: result = posix.AF_INET6
|
of AF_INET6: result = posix.AF_INET6
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
proc toInt(typ: TType): cint =
|
proc toInt(typ: TType): cint =
|
||||||
case typ
|
case typ
|
||||||
|
|
@ -97,7 +99,7 @@ when defined(posix):
|
||||||
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: nil
|
else: discard
|
||||||
|
|
||||||
proc toInt(p: TProtocol): cint =
|
proc toInt(p: TProtocol): cint =
|
||||||
case p
|
case p
|
||||||
|
|
@ -107,7 +109,7 @@ when defined(posix):
|
||||||
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: nil
|
else: discard
|
||||||
|
|
||||||
else:
|
else:
|
||||||
proc toInt(domain: TDomain): cint =
|
proc toInt(domain: TDomain): cint =
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,119 @@ proc `$`*(m: TMonth): string =
|
||||||
"November", "December"]
|
"November", "December"]
|
||||||
return lookup[m]
|
return lookup[m]
|
||||||
|
|
||||||
|
proc format_token(info: TTimeInfo, token: string, buf: var string) =
|
||||||
|
## Helper of the format proc to parse individual tokens.
|
||||||
|
##
|
||||||
|
## Pass the found token in the user input string, and the buffer where the
|
||||||
|
## final string is being built. This has to be a var value because certain
|
||||||
|
## formatting tokens require modifying the previous characters.
|
||||||
|
case token
|
||||||
|
of "d":
|
||||||
|
buf.add($info.monthday)
|
||||||
|
of "dd":
|
||||||
|
if info.monthday < 10:
|
||||||
|
buf.add("0")
|
||||||
|
buf.add($info.monthday)
|
||||||
|
of "ddd":
|
||||||
|
buf.add(($info.weekday)[0 .. 2])
|
||||||
|
of "dddd":
|
||||||
|
buf.add($info.weekday)
|
||||||
|
of "h":
|
||||||
|
buf.add($(if info.hour > 12: info.hour - 12 else: info.hour))
|
||||||
|
of "hh":
|
||||||
|
let amerHour = if info.hour > 12: info.hour - 12 else: info.hour
|
||||||
|
if amerHour < 10:
|
||||||
|
buf.add('0')
|
||||||
|
buf.add($amerHour)
|
||||||
|
of "H":
|
||||||
|
buf.add($info.hour)
|
||||||
|
of "HH":
|
||||||
|
if info.hour < 10:
|
||||||
|
buf.add('0')
|
||||||
|
buf.add($info.hour)
|
||||||
|
of "m":
|
||||||
|
buf.add($info.minute)
|
||||||
|
of "mm":
|
||||||
|
if info.minute < 10:
|
||||||
|
buf.add('0')
|
||||||
|
buf.add($info.minute)
|
||||||
|
of "M":
|
||||||
|
buf.add($(int(info.month)+1))
|
||||||
|
of "MM":
|
||||||
|
if info.month < mOct:
|
||||||
|
buf.add('0')
|
||||||
|
buf.add($(int(info.month)+1))
|
||||||
|
of "MMM":
|
||||||
|
buf.add(($info.month)[0..2])
|
||||||
|
of "MMMM":
|
||||||
|
buf.add($info.month)
|
||||||
|
of "s":
|
||||||
|
buf.add($info.second)
|
||||||
|
of "ss":
|
||||||
|
if info.second < 10:
|
||||||
|
buf.add('0')
|
||||||
|
buf.add($info.second)
|
||||||
|
of "t":
|
||||||
|
if info.hour >= 12:
|
||||||
|
buf.add('P')
|
||||||
|
else: buf.add('A')
|
||||||
|
of "tt":
|
||||||
|
if info.hour >= 12:
|
||||||
|
buf.add("PM")
|
||||||
|
else: buf.add("AM")
|
||||||
|
of "y":
|
||||||
|
var fr = ($info.year).len()-1
|
||||||
|
if fr < 0: fr = 0
|
||||||
|
buf.add(($info.year)[fr .. ($info.year).len()-1])
|
||||||
|
of "yy":
|
||||||
|
var fr = ($info.year).len()-2
|
||||||
|
if fr < 0: fr = 0
|
||||||
|
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
||||||
|
if fyear.len != 2: fyear = repeatChar(2-fyear.len(), '0') & fyear
|
||||||
|
buf.add(fyear)
|
||||||
|
of "yyy":
|
||||||
|
var fr = ($info.year).len()-3
|
||||||
|
if fr < 0: fr = 0
|
||||||
|
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
||||||
|
if fyear.len != 3: fyear = repeatChar(3-fyear.len(), '0') & fyear
|
||||||
|
buf.add(fyear)
|
||||||
|
of "yyyy":
|
||||||
|
var fr = ($info.year).len()-4
|
||||||
|
if fr < 0: fr = 0
|
||||||
|
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
||||||
|
if fyear.len != 4: fyear = repeatChar(4-fyear.len(), '0') & fyear
|
||||||
|
buf.add(fyear)
|
||||||
|
of "yyyyy":
|
||||||
|
var fr = ($info.year).len()-5
|
||||||
|
if fr < 0: fr = 0
|
||||||
|
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
||||||
|
if fyear.len != 5: fyear = repeatChar(5-fyear.len(), '0') & fyear
|
||||||
|
buf.add(fyear)
|
||||||
|
of "z":
|
||||||
|
let hrs = (info.timezone div 60) div 60
|
||||||
|
buf.add($hrs)
|
||||||
|
of "zz":
|
||||||
|
let hrs = (info.timezone div 60) div 60
|
||||||
|
|
||||||
|
buf.add($hrs)
|
||||||
|
if hrs.abs < 10:
|
||||||
|
var atIndex = buf.len-(($hrs).len-(if hrs < 0: 1 else: 0))
|
||||||
|
buf.insert("0", atIndex)
|
||||||
|
of "zzz":
|
||||||
|
let hrs = (info.timezone div 60) div 60
|
||||||
|
|
||||||
|
buf.add($hrs & ":00")
|
||||||
|
if hrs.abs < 10:
|
||||||
|
var atIndex = buf.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0))
|
||||||
|
buf.insert("0", atIndex)
|
||||||
|
of "ZZZ":
|
||||||
|
buf.add(info.tzname)
|
||||||
|
of "":
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
raise newException(EInvalidValue, "Invalid format string: " & token)
|
||||||
|
|
||||||
|
|
||||||
proc format*(info: TTimeInfo, f: string): string =
|
proc format*(info: TTimeInfo, f: string): string =
|
||||||
## This function formats `info` as specified by `f`. The following format
|
## This function formats `info` as specified by `f`. The following format
|
||||||
## specifiers are available:
|
## specifiers are available:
|
||||||
|
|
@ -591,8 +704,11 @@ proc format*(info: TTimeInfo, f: string): string =
|
||||||
## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST``
|
## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST``
|
||||||
## ========== ================================================================================= ================================================
|
## ========== ================================================================================= ================================================
|
||||||
##
|
##
|
||||||
## Other strings can be inserted by putting them in ``''``. For example ``hh'->'mm`` will give ``01->56``.
|
## Other strings can be inserted by putting them in ``''``. For example
|
||||||
## The following characters can be inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` ``,``
|
## ``hh'->'mm`` will give ``01->56``. The following characters can be
|
||||||
|
## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]``
|
||||||
|
## ``,``. However you don't need to necessarily separate format specifiers, a
|
||||||
|
## unambiguous format string like ``yyyyMMddhhmmss`` is valid too.
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
@ -600,111 +716,7 @@ proc format*(info: TTimeInfo, f: string): string =
|
||||||
while true:
|
while true:
|
||||||
case f[i]
|
case f[i]
|
||||||
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
|
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
|
||||||
case currentF
|
format_token(info, currentF, result)
|
||||||
of "d":
|
|
||||||
result.add($info.monthday)
|
|
||||||
of "dd":
|
|
||||||
if info.monthday < 10:
|
|
||||||
result.add("0")
|
|
||||||
result.add($info.monthday)
|
|
||||||
of "ddd":
|
|
||||||
result.add(($info.weekday)[0 .. 2])
|
|
||||||
of "dddd":
|
|
||||||
result.add($info.weekday)
|
|
||||||
of "h":
|
|
||||||
result.add($(if info.hour > 12: info.hour - 12 else: info.hour))
|
|
||||||
of "hh":
|
|
||||||
let amerHour = if info.hour > 12: info.hour - 12 else: info.hour
|
|
||||||
if amerHour < 10:
|
|
||||||
result.add('0')
|
|
||||||
result.add($amerHour)
|
|
||||||
of "H":
|
|
||||||
result.add($info.hour)
|
|
||||||
of "HH":
|
|
||||||
if info.hour < 10:
|
|
||||||
result.add('0')
|
|
||||||
result.add($info.hour)
|
|
||||||
of "m":
|
|
||||||
result.add($info.minute)
|
|
||||||
of "mm":
|
|
||||||
if info.minute < 10:
|
|
||||||
result.add('0')
|
|
||||||
result.add($info.minute)
|
|
||||||
of "M":
|
|
||||||
result.add($(int(info.month)+1))
|
|
||||||
of "MM":
|
|
||||||
if info.month < mOct:
|
|
||||||
result.add('0')
|
|
||||||
result.add($(int(info.month)+1))
|
|
||||||
of "MMM":
|
|
||||||
result.add(($info.month)[0..2])
|
|
||||||
of "MMMM":
|
|
||||||
result.add($info.month)
|
|
||||||
of "s":
|
|
||||||
result.add($info.second)
|
|
||||||
of "ss":
|
|
||||||
if info.second < 10:
|
|
||||||
result.add('0')
|
|
||||||
result.add($info.second)
|
|
||||||
of "t":
|
|
||||||
if info.hour >= 12:
|
|
||||||
result.add('P')
|
|
||||||
else: result.add('A')
|
|
||||||
of "tt":
|
|
||||||
if info.hour >= 12:
|
|
||||||
result.add("PM")
|
|
||||||
else: result.add("AM")
|
|
||||||
of "y":
|
|
||||||
var fr = ($info.year).len()-1
|
|
||||||
if fr < 0: fr = 0
|
|
||||||
result.add(($info.year)[fr .. ($info.year).len()-1])
|
|
||||||
of "yy":
|
|
||||||
var fr = ($info.year).len()-2
|
|
||||||
if fr < 0: fr = 0
|
|
||||||
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
|
||||||
if fyear.len != 2: fyear = repeatChar(2-fyear.len(), '0') & fyear
|
|
||||||
result.add(fyear)
|
|
||||||
of "yyy":
|
|
||||||
var fr = ($info.year).len()-3
|
|
||||||
if fr < 0: fr = 0
|
|
||||||
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
|
||||||
if fyear.len != 3: fyear = repeatChar(3-fyear.len(), '0') & fyear
|
|
||||||
result.add(fyear)
|
|
||||||
of "yyyy":
|
|
||||||
var fr = ($info.year).len()-4
|
|
||||||
if fr < 0: fr = 0
|
|
||||||
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
|
||||||
if fyear.len != 4: fyear = repeatChar(4-fyear.len(), '0') & fyear
|
|
||||||
result.add(fyear)
|
|
||||||
of "yyyyy":
|
|
||||||
var fr = ($info.year).len()-5
|
|
||||||
if fr < 0: fr = 0
|
|
||||||
var fyear = ($info.year)[fr .. ($info.year).len()-1]
|
|
||||||
if fyear.len != 5: fyear = repeatChar(5-fyear.len(), '0') & fyear
|
|
||||||
result.add(fyear)
|
|
||||||
of "z":
|
|
||||||
let hrs = (info.timezone div 60) div 60
|
|
||||||
result.add($hrs)
|
|
||||||
of "zz":
|
|
||||||
let hrs = (info.timezone div 60) div 60
|
|
||||||
|
|
||||||
result.add($hrs)
|
|
||||||
if hrs.abs < 10:
|
|
||||||
var atIndex = result.len-(($hrs).len-(if hrs < 0: 1 else: 0))
|
|
||||||
result.insert("0", atIndex)
|
|
||||||
of "zzz":
|
|
||||||
let hrs = (info.timezone div 60) div 60
|
|
||||||
|
|
||||||
result.add($hrs & ":00")
|
|
||||||
if hrs.abs < 10:
|
|
||||||
var atIndex = result.len-(($hrs & ":00").len-(if hrs < 0: 1 else: 0))
|
|
||||||
result.insert("0", atIndex)
|
|
||||||
of "ZZZ":
|
|
||||||
result.add(info.tzname)
|
|
||||||
of "":
|
|
||||||
discard
|
|
||||||
else:
|
|
||||||
raise newException(EInvalidValue, "Invalid format string: " & currentF)
|
|
||||||
|
|
||||||
currentF = ""
|
currentF = ""
|
||||||
if f[i] == '\0': break
|
if f[i] == '\0': break
|
||||||
|
|
@ -716,7 +728,15 @@ proc format*(info: TTimeInfo, f: string): string =
|
||||||
inc(i)
|
inc(i)
|
||||||
else: result.add(f[i])
|
else: result.add(f[i])
|
||||||
|
|
||||||
else: currentF.add(f[i])
|
else:
|
||||||
|
# Check if the letter being added matches previous accumulated buffer.
|
||||||
|
if currentF.len < 1 or currentF[high(currentF)] == f[i]:
|
||||||
|
currentF.add(f[i])
|
||||||
|
else:
|
||||||
|
format_token(info, currentF, result)
|
||||||
|
dec(i) # Move position back to re-process the character separately.
|
||||||
|
currentF = ""
|
||||||
|
|
||||||
inc(i)
|
inc(i)
|
||||||
|
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
@ -727,12 +747,16 @@ when isMainModule:
|
||||||
|
|
||||||
var t = getGMTime(fromSeconds(2147483647))
|
var t = getGMTime(fromSeconds(2147483647))
|
||||||
echo t.format("ddd dd MMM hh:mm:ss ZZZ yyyy")
|
echo t.format("ddd dd MMM hh:mm:ss ZZZ yyyy")
|
||||||
|
echo t.format("ddd ddMMMhhmmssZZZyyyy")
|
||||||
assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038"
|
assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038"
|
||||||
|
assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038"
|
||||||
|
|
||||||
assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
||||||
" ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") ==
|
" ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") ==
|
||||||
"19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC"
|
"19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC"
|
||||||
|
|
||||||
|
assert t.format("yyyyMMddhhmmss") == "20380119031407"
|
||||||
|
|
||||||
var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975
|
var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975
|
||||||
assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
||||||
" ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") ==
|
" ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") ==
|
||||||
|
|
|
||||||
|
|
@ -418,76 +418,75 @@ proc modInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||||
return Math.floor(`a` % `b`);
|
return Math.floor(`a` % `b`);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc NegInt(a: int): int {.compilerproc.} =
|
proc negInt(a: int): int {.compilerproc.} =
|
||||||
result = a*(-1)
|
result = a*(-1)
|
||||||
|
|
||||||
proc NegInt64(a: int64): int64 {.compilerproc.} =
|
proc negInt64(a: int64): int64 {.compilerproc.} =
|
||||||
result = a*(-1)
|
result = a*(-1)
|
||||||
|
|
||||||
proc AbsInt(a: int): int {.compilerproc.} =
|
proc absInt(a: int): int {.compilerproc.} =
|
||||||
result = if a < 0: a*(-1) else: a
|
result = if a < 0: a*(-1) else: a
|
||||||
|
|
||||||
proc AbsInt64(a: int64): int64 {.compilerproc.} =
|
proc absInt64(a: int64): int64 {.compilerproc.} =
|
||||||
result = if a < 0: a*(-1) else: a
|
result = if a < 0: a*(-1) else: a
|
||||||
|
|
||||||
proc LeU(a, b: int): bool {.compilerproc.} =
|
proc leU(a, b: int): bool {.compilerproc.} =
|
||||||
result = abs(a) <= abs(b)
|
result = abs(a) <= abs(b)
|
||||||
|
|
||||||
proc LtU(a, b: int): bool {.compilerproc.} =
|
proc ltU(a, b: int): bool {.compilerproc.} =
|
||||||
result = abs(a) < abs(b)
|
result = abs(a) < abs(b)
|
||||||
|
|
||||||
proc LeU64(a, b: int64): bool {.compilerproc.} =
|
proc leU64(a, b: int64): bool {.compilerproc.} =
|
||||||
result = abs(a) <= abs(b)
|
result = abs(a) <= abs(b)
|
||||||
|
proc ltU64(a, b: int64): bool {.compilerproc.} =
|
||||||
proc LtU64(a, b: int64): bool {.compilerproc.} =
|
|
||||||
result = abs(a) < abs(b)
|
result = abs(a) < abs(b)
|
||||||
|
|
||||||
proc AddU(a, b: int): int {.compilerproc.} =
|
proc addU(a, b: int): int {.compilerproc.} =
|
||||||
result = abs(a) + abs(b)
|
result = abs(a) + abs(b)
|
||||||
proc AddU64(a, b: int64): int64 {.compilerproc.} =
|
proc addU64(a, b: int64): int64 {.compilerproc.} =
|
||||||
result = abs(a) + abs(b)
|
result = abs(a) + abs(b)
|
||||||
|
|
||||||
proc SubU(a, b: int): int {.compilerproc.} =
|
proc subU(a, b: int): int {.compilerproc.} =
|
||||||
result = abs(a) - abs(b)
|
result = abs(a) - abs(b)
|
||||||
proc SubU64(a, b: int64): int64 {.compilerproc.} =
|
proc subU64(a, b: int64): int64 {.compilerproc.} =
|
||||||
result = abs(a) - abs(b)
|
result = abs(a) - abs(b)
|
||||||
|
|
||||||
proc MulU(a, b: int): int {.compilerproc.} =
|
proc mulU(a, b: int): int {.compilerproc.} =
|
||||||
result = abs(a) * abs(b)
|
result = abs(a) * abs(b)
|
||||||
proc MulU64(a, b: int64): int64 {.compilerproc.} =
|
proc mulU64(a, b: int64): int64 {.compilerproc.} =
|
||||||
result = abs(a) * abs(b)
|
result = abs(a) * abs(b)
|
||||||
|
|
||||||
proc DivU(a, b: int): int {.compilerproc.} =
|
proc divU(a, b: int): int {.compilerproc.} =
|
||||||
result = abs(a) div abs(b)
|
result = abs(a) div abs(b)
|
||||||
proc DivU64(a, b: int64): int64 {.compilerproc.} =
|
proc divU64(a, b: int64): int64 {.compilerproc.} =
|
||||||
result = abs(a) div abs(b)
|
result = abs(a) div abs(b)
|
||||||
|
|
||||||
proc ModU(a, b: int): int {.compilerproc.} =
|
proc modU(a, b: int): int {.compilerproc.} =
|
||||||
result = abs(a) mod abs(b)
|
result = abs(a) mod abs(b)
|
||||||
proc ModU64(a, b: int64): int64 {.compilerproc.} =
|
proc modU64(a, b: int64): int64 {.compilerproc.} =
|
||||||
result = abs(a) mod abs(b)
|
result = abs(a) mod abs(b)
|
||||||
|
|
||||||
proc Ze(a: int): int {.compilerproc.} =
|
proc ze*(a: int): int {.compilerproc.} =
|
||||||
result = a
|
|
||||||
proc Ze64(a: int64): int64 {.compilerproc.} =
|
|
||||||
result = a
|
result = a
|
||||||
|
|
||||||
proc ToU8(a: int): int8 {.noStackFrame, compilerproc.} =
|
proc ze64*(a: int64): int64 {.compilerproc.} =
|
||||||
|
result = a
|
||||||
|
|
||||||
|
proc toU8*(a: int): int8 {.noStackFrame, compilerproc.} =
|
||||||
asm """
|
asm """
|
||||||
return `a`;
|
return `a`;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc ToU16(a: int): int16 {.noStackFrame, compilerproc.} =
|
proc toU16*(a: int): int16 {.noStackFrame, compilerproc.} =
|
||||||
asm """
|
asm """
|
||||||
return `a`;
|
return `a`;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc ToU32(a: int): int32 {.noStackFrame, compilerproc.} =
|
proc toU32*(a: int64): int32 {.noStackFrame, compilerproc.} =
|
||||||
asm """
|
asm """
|
||||||
return `a`;
|
return `a`;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
|
proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
|
||||||
proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
|
proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
|
||||||
|
|
||||||
|
|
@ -500,9 +499,9 @@ proc isFatPointer(ti: PNimType): bool =
|
||||||
tyArray, tyArrayConstr, tyTuple,
|
tyArray, tyArrayConstr, tyTuple,
|
||||||
tyOpenArray, tySet, tyVar, tyRef, tyPtr}
|
tyOpenArray, tySet, tyVar, tyRef, tyPtr}
|
||||||
|
|
||||||
proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
|
proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
|
||||||
|
|
||||||
proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkNone: sysAssert(false, "NimCopyAux")
|
of nkNone: sysAssert(false, "NimCopyAux")
|
||||||
of nkSlot:
|
of nkSlot:
|
||||||
|
|
@ -518,7 +517,7 @@ proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc NimCopy(x: pointer, ti: PNimType): pointer =
|
proc nimCopy(x: pointer, ti: PNimType): pointer =
|
||||||
case ti.kind
|
case ti.kind
|
||||||
of tyPtr, tyRef, tyVar, tyNil:
|
of tyPtr, tyRef, tyVar, tyNil:
|
||||||
if not isFatPointer(ti):
|
if not isFatPointer(ti):
|
||||||
|
|
@ -585,7 +584,7 @@ proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} =
|
||||||
else:
|
else:
|
||||||
result = nil
|
result = nil
|
||||||
|
|
||||||
proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {.
|
proc arrayConstr(len: int, value: pointer, typ: PNimType): pointer {.
|
||||||
noStackFrame, compilerproc.} =
|
noStackFrame, compilerproc.} =
|
||||||
# types are fake
|
# types are fake
|
||||||
asm """
|
asm """
|
||||||
|
|
|
||||||
|
|
@ -199,14 +199,14 @@ else:
|
||||||
importc: "GetCurrentDirectoryA", dynlib: "kernel32", stdcall.}
|
importc: "GetCurrentDirectoryA", dynlib: "kernel32", stdcall.}
|
||||||
proc setCurrentDirectoryA*(lpPathName: cstring): int32 {.
|
proc setCurrentDirectoryA*(lpPathName: cstring): int32 {.
|
||||||
importc: "SetCurrentDirectoryA", dynlib: "kernel32", stdcall.}
|
importc: "SetCurrentDirectoryA", dynlib: "kernel32", stdcall.}
|
||||||
proc createDirectoryA*(pathName: cstring, security: pointer=nil): int32 {.
|
proc createDirectoryA*(pathName: cstring, security: Pointer=nil): int32 {.
|
||||||
importc: "CreateDirectoryA", dynlib: "kernel32", stdcall.}
|
importc: "CreateDirectoryA", dynlib: "kernel32", stdcall.}
|
||||||
proc removeDirectoryA*(lpPathName: cstring): int32 {.
|
proc removeDirectoryA*(lpPathName: cstring): int32 {.
|
||||||
importc: "RemoveDirectoryA", dynlib: "kernel32", stdcall.}
|
importc: "RemoveDirectoryA", dynlib: "kernel32", stdcall.}
|
||||||
proc setEnvironmentVariableA*(lpName, lpValue: cstring): int32 {.
|
proc setEnvironmentVariableA*(lpName, lpValue: cstring): int32 {.
|
||||||
stdcall, dynlib: "kernel32", importc: "SetEnvironmentVariableA".}
|
stdcall, dynlib: "kernel32", importc: "SetEnvironmentVariableA".}
|
||||||
|
|
||||||
proc getModuleFileNameA*(handle: THandle, buf: cstring, size: int32): int32 {.
|
proc getModuleFileNameA*(handle: THandle, buf: CString, size: int32): int32 {.
|
||||||
importc: "GetModuleFileNameA", dynlib: "kernel32", stdcall.}
|
importc: "GetModuleFileNameA", dynlib: "kernel32", stdcall.}
|
||||||
|
|
||||||
when useWinUnicode:
|
when useWinUnicode:
|
||||||
|
|
@ -304,7 +304,7 @@ else:
|
||||||
dwFileAttributes: int32): WINBOOL {.
|
dwFileAttributes: int32): WINBOOL {.
|
||||||
stdcall, dynlib: "kernel32", importc: "SetFileAttributesA".}
|
stdcall, dynlib: "kernel32", importc: "SetFileAttributesA".}
|
||||||
|
|
||||||
proc copyFileA*(lpExistingFileName, lpNewFileName: cstring,
|
proc copyFileA*(lpExistingFileName, lpNewFileName: CString,
|
||||||
bFailIfExists: cint): cint {.
|
bFailIfExists: cint): cint {.
|
||||||
importc: "CopyFileA", stdcall, dynlib: "kernel32".}
|
importc: "CopyFileA", stdcall, dynlib: "kernel32".}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
discard """
|
discard """
|
||||||
line: 21
|
line: 21
|
||||||
errormsg: "invalid type: 'TTable'"
|
errormsg: "invalid type: 'TTable[string, proc (string)]'"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tables
|
import tables
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ type
|
||||||
proc ha() =
|
proc ha() =
|
||||||
var
|
var
|
||||||
x: TExport # no error
|
x: TExport # no error
|
||||||
nil
|
discard
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ type
|
||||||
TExport* = enum x, y, z
|
TExport* = enum x, y, z
|
||||||
|
|
||||||
proc foo*(x: int) =
|
proc foo*(x: int) =
|
||||||
nil
|
discard
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
type
|
type
|
||||||
TExport* = enum x, y, z # exactly the same type!
|
TExport* = enum x, y, z # exactly the same type!
|
||||||
|
|
||||||
proc foo*(x: int) = nil
|
proc foo*(x: int) = discard
|
||||||
|
|
|
||||||
64
tests/async/tasyncawait.nim
Normal file
64
tests/async/tasyncawait.nim
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
discard """
|
||||||
|
file: "tasyncawait.nim"
|
||||||
|
cmd: "nimrod cc --hints:on $# $#"
|
||||||
|
output: "5000"
|
||||||
|
"""
|
||||||
|
import asyncio2, sockets2, net, strutils
|
||||||
|
|
||||||
|
var disp = newDispatcher()
|
||||||
|
var msgCount = 0
|
||||||
|
|
||||||
|
const
|
||||||
|
swarmSize = 50
|
||||||
|
messagesToSend = 100
|
||||||
|
|
||||||
|
var clientCount = 0
|
||||||
|
|
||||||
|
proc sendMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.async.} =
|
||||||
|
for i in 0 .. <messagesToSend:
|
||||||
|
discard await disp.send(client, "Message " & $i & "\c\L")
|
||||||
|
|
||||||
|
proc launchSwarm(disp: PDispatcher, port: TPort): PFuture[int] {.async.} =
|
||||||
|
for i in 0 .. <swarmSize:
|
||||||
|
var sock = socket()
|
||||||
|
#disp.register(sock)
|
||||||
|
discard await disp.connect(sock, "localhost", port)
|
||||||
|
when true:
|
||||||
|
discard await sendMessages(disp, sock)
|
||||||
|
sock.close()
|
||||||
|
else:
|
||||||
|
# Issue #932: https://github.com/Araq/Nimrod/issues/932
|
||||||
|
var msgFut = sendMessages(disp, sock)
|
||||||
|
msgFut.callback =
|
||||||
|
proc () =
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
proc readMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.async.} =
|
||||||
|
while true:
|
||||||
|
var line = await disp.recvLine(client)
|
||||||
|
if line == "":
|
||||||
|
client.close()
|
||||||
|
clientCount.inc
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if line.startswith("Message "):
|
||||||
|
msgCount.inc
|
||||||
|
else:
|
||||||
|
doAssert false
|
||||||
|
|
||||||
|
proc createServer(disp: PDispatcher, port: TPort): PFuture[int] {.async.} =
|
||||||
|
var server = socket()
|
||||||
|
#disp.register(server)
|
||||||
|
server.bindAddr(port)
|
||||||
|
server.listen()
|
||||||
|
while true:
|
||||||
|
discard readMessages(disp, await disp.accept(server))
|
||||||
|
|
||||||
|
discard disp.createServer(TPort(10335))
|
||||||
|
discard disp.launchSwarm(TPort(10335))
|
||||||
|
while true:
|
||||||
|
disp.poll()
|
||||||
|
if clientCount == swarmSize: break
|
||||||
|
|
||||||
|
assert msgCount == swarmSize * messagesToSend
|
||||||
|
echo msgCount
|
||||||
|
|
@ -19,8 +19,8 @@ of eB, eC: write(stdout, "b or c")
|
||||||
case x
|
case x
|
||||||
of "Andreas", "Rumpf": write(stdout, "Hallo Meister!")
|
of "Andreas", "Rumpf": write(stdout, "Hallo Meister!")
|
||||||
of "aa", "bb": write(stdout, "Du bist nicht mein Meister")
|
of "aa", "bb": write(stdout, "Du bist nicht mein Meister")
|
||||||
of "cc", "hash", "when": nil
|
of "cc", "hash", "when": discard
|
||||||
of "will", "it", "finally", "be", "generated": nil
|
of "will", "it", "finally", "be", "generated": discard
|
||||||
|
|
||||||
var z = case i
|
var z = case i
|
||||||
of 1..5, 8, 9: "aa"
|
of 1..5, 8, 9: "aa"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ var
|
||||||
thr: array [0..5, TThread[tuple[a, b: int]]]
|
thr: array [0..5, TThread[tuple[a, b: int]]]
|
||||||
L, M, N: TLock
|
L, M, N: TLock
|
||||||
|
|
||||||
proc doNothing() = nil
|
proc doNothing() = discard
|
||||||
|
|
||||||
proc threadFunc(interval: tuple[a, b: int]) {.thread.} =
|
proc threadFunc(interval: tuple[a, b: int]) {.thread.} =
|
||||||
doNothing()
|
doNothing()
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,13 @@ proc mygeneric1() =
|
||||||
echo "mygeneric1 constructed"
|
echo "mygeneric1 constructed"
|
||||||
|
|
||||||
proc mygeneric2[T](val: T) =
|
proc mygeneric2[T](val: T) =
|
||||||
var
|
var a = open()
|
||||||
a = open()
|
|
||||||
b = TMyGeneric2[int, T](x: 10, y: val)
|
|
||||||
c = TMyGeneric3[int, int, string](x: 10, y: 20, z: "test")
|
|
||||||
|
|
||||||
|
var b = TMyGeneric2[int, T](x: 10, y: val)
|
||||||
echo "mygeneric2 constructed"
|
echo "mygeneric2 constructed"
|
||||||
|
|
||||||
|
var c = TMyGeneric3[int, int, string](x: 10, y: 20, z: "test")
|
||||||
|
|
||||||
proc mygeneric3 =
|
proc mygeneric3 =
|
||||||
var x = TMyGeneric3[int, string, TMyGeneric1[int]](
|
var x = TMyGeneric3[int, string, TMyGeneric1[int]](
|
||||||
x: 10, y: "test", z: TMyGeneric1[int](x: 10))
|
x: 10, y: "test", z: TMyGeneric1[int](x: 10))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ type
|
||||||
PDict[TK, TV] = ref TDict[TK, TV]
|
PDict[TK, TV] = ref TDict[TK, TV]
|
||||||
|
|
||||||
proc fakeNew[T](x: var ref T, destroy: proc (a: ref T) {.nimcall.}) =
|
proc fakeNew[T](x: var ref T, destroy: proc (a: ref T) {.nimcall.}) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
proc destroyDict[TK, TV](a: PDict[TK, TV]) =
|
proc destroyDict[TK, TV](a: PDict[TK, TV]) =
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const
|
||||||
proc len[T,D] (n:PNode[T,D]): Int {.inline.} =
|
proc len[T,D] (n:PNode[T,D]): Int {.inline.} =
|
||||||
return n.Count
|
return n.Count
|
||||||
|
|
||||||
proc clean[T: TOrdinal|TNumber](o: var T) {.inline.} = nil
|
proc clean[T: TOrdinal|TNumber](o: var T) {.inline.} = discard
|
||||||
|
|
||||||
proc clean[T: string|seq](o: var T) {.inline.} =
|
proc clean[T: string|seq](o: var T) {.inline.} =
|
||||||
o = nil
|
o = nil
|
||||||
|
|
@ -98,7 +98,7 @@ proc DeleteItem[T,D] (n: PNode[T,D], x: Int): PNode[T,D] {.inline.} =
|
||||||
of cLen3 : setLen(n.slots, cLen3)
|
of cLen3 : setLen(n.slots, cLen3)
|
||||||
of cLenCenter : setLen(n.slots, cLenCenter)
|
of cLenCenter : setLen(n.slots, cLenCenter)
|
||||||
of cLen4 : setLen(n.slots, cLen4)
|
of cLen4 : setLen(n.slots, cLen4)
|
||||||
else: nil
|
else: discard
|
||||||
Result = n
|
Result = n
|
||||||
|
|
||||||
else :
|
else :
|
||||||
|
|
@ -232,7 +232,7 @@ proc InsertItem[T,D](APath: RPath[T,D], ANode:PNode[T,D], AKey: T, AValue: D) =
|
||||||
of cLen3: setLen(APath.Nd.slots, cLenCenter)
|
of cLen3: setLen(APath.Nd.slots, cLenCenter)
|
||||||
of cLenCenter: setLen(APath.Nd.slots, cLen4)
|
of cLenCenter: setLen(APath.Nd.slots, cLen4)
|
||||||
of cLen4: setLen(APath.Nd.slots, cLenMax)
|
of cLen4: setLen(APath.Nd.slots, cLenMax)
|
||||||
else: nil
|
else: discard
|
||||||
for i in countdown(APath.Nd.Count.int - 1, x + 1): shallowCopy(APath.Nd.slots[i], APath.Nd.slots[i - 1])
|
for i in countdown(APath.Nd.Count.int - 1, x + 1): shallowCopy(APath.Nd.slots[i], APath.Nd.slots[i - 1])
|
||||||
APath.Nd.slots[x] = setItem(AKey, AValue, ANode)
|
APath.Nd.slots[x] = setItem(AKey, AValue, ANode)
|
||||||
|
|
||||||
|
|
|
||||||
18
tests/generics/tgenericlambda.nim
Normal file
18
tests/generics/tgenericlambda.nim
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
discard """
|
||||||
|
output: "10\n10\n1\n2\n3"
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc test(x: proc (a, b: int): int) =
|
||||||
|
echo x(5, 5)
|
||||||
|
|
||||||
|
test(proc (a, b): auto = a + b)
|
||||||
|
|
||||||
|
test do (a, b) -> auto: a + b
|
||||||
|
|
||||||
|
proc foreach[T](s: seq[T], body: proc(x: T)) =
|
||||||
|
for e in s:
|
||||||
|
body(e)
|
||||||
|
|
||||||
|
foreach(@[1,2,3]) do (x):
|
||||||
|
echo x
|
||||||
|
|
||||||
18
tests/generics/tmetafield.nim
Normal file
18
tests/generics/tmetafield.nim
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
discard """
|
||||||
|
cmd: "nimrod check $# $#"
|
||||||
|
msg: "'proc' is not a concrete type"
|
||||||
|
msg: "'Foo' is not a concrete type."
|
||||||
|
msg: "invalid type: 'TBaseMed'"
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
Foo[T] = object
|
||||||
|
x: T
|
||||||
|
|
||||||
|
TBaseMed = object
|
||||||
|
doSmth: proc
|
||||||
|
data: seq[Foo]
|
||||||
|
|
||||||
|
var a: TBaseMed
|
||||||
|
|
||||||
|
# issue 188
|
||||||
15
tests/global/globalaux.nim
Normal file
15
tests/global/globalaux.nim
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
type
|
||||||
|
TObj*[T] = object
|
||||||
|
val*: T
|
||||||
|
|
||||||
|
var
|
||||||
|
totalGlobals* = 0
|
||||||
|
|
||||||
|
proc makeObj[T](x: T): TObj[T] =
|
||||||
|
totalGlobals += 1
|
||||||
|
result.val = x
|
||||||
|
|
||||||
|
proc globalInstance*[T]: var TObj[T] =
|
||||||
|
var g {.global.} = when T is int: makeObj(10) else: makeObj("hello")
|
||||||
|
result = g
|
||||||
|
|
||||||
4
tests/global/globalaux2.nim
Normal file
4
tests/global/globalaux2.nim
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import globalaux
|
||||||
|
|
||||||
|
echo "in globalaux2: ", globalInstance[int]().val
|
||||||
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
template foo(a: int, b: string) = nil
|
template foo(a: int, b: string) = discard
|
||||||
foo(1, "test")
|
foo(1, "test")
|
||||||
|
|
||||||
proc bar(a: int, b: string) = nil
|
proc bar(a: int, b: string) = discard
|
||||||
bar(1, "test")
|
bar(1, "test")
|
||||||
|
|
||||||
template foo(a: int, b: string) = bar(a, b)
|
template foo(a: int, b: string) = bar(a, b)
|
||||||
foo(1, "test")
|
foo(1, "test")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
proc bar(a: int, b: string) = nil
|
proc bar(a: int, b: string) = discard
|
||||||
template foo(a: int, b: string) = nil
|
template foo(a: int, b: string) = discard
|
||||||
foo(1, "test")
|
foo(1, "test")
|
||||||
bar(1, "test")
|
bar(1, "test")
|
||||||
|
|
||||||
proc baz =
|
proc baz =
|
||||||
proc foo(a: int, b: string) = nil
|
proc foo(a: int, b: string) = discard
|
||||||
proc foo(b: string) =
|
proc foo(b: string) =
|
||||||
template bar(a: int, b: string) = nil
|
template bar(a: int, b: string) = discard
|
||||||
bar(1, "test")
|
bar(1, "test")
|
||||||
|
|
||||||
foo("test")
|
foo("test")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
proc foo(b: string) = nil
|
proc foo(b: string) = discard
|
||||||
foo("test")
|
foo("test")
|
||||||
foo(1, "test")
|
foo(1, "test")
|
||||||
|
|
||||||
|
|
|
||||||
19
tests/macros/tvarnimnode.nim
Normal file
19
tests/macros/tvarnimnode.nim
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
discard """
|
||||||
|
output: 10
|
||||||
|
"""
|
||||||
|
|
||||||
|
#bug #926
|
||||||
|
|
||||||
|
import macros
|
||||||
|
|
||||||
|
proc test(f: var PNimrodNode) {.compileTime.} =
|
||||||
|
f = newNimNode(nnkStmtList)
|
||||||
|
f.add newCall(newIdentNode("echo"), newLit(10))
|
||||||
|
|
||||||
|
macro blah(prc: stmt): stmt =
|
||||||
|
result = prc
|
||||||
|
|
||||||
|
test(result)
|
||||||
|
|
||||||
|
proc test() {.blah.} =
|
||||||
|
echo 5
|
||||||
|
|
@ -178,14 +178,14 @@ template new_parsed_parameter*(tkind: Tparam_kind, expr): Tparsed_parameter =
|
||||||
## #parsed_param3 = new_parsed_parameter(PK_INT, "231")
|
## #parsed_param3 = new_parsed_parameter(PK_INT, "231")
|
||||||
var result {.gensym.}: Tparsed_parameter
|
var result {.gensym.}: Tparsed_parameter
|
||||||
result.kind = tkind
|
result.kind = tkind
|
||||||
when tkind == PK_EMPTY: nil
|
when tkind == PK_EMPTY: discard
|
||||||
elif tkind == PK_INT: result.int_val = expr
|
elif tkind == PK_INT: result.int_val = expr
|
||||||
elif tkind == PK_BIGGEST_INT: result.big_int_val = expr
|
elif tkind == PK_BIGGEST_INT: result.big_int_val = expr
|
||||||
elif tkind == PK_FLOAT: result.float_val = expr
|
elif tkind == PK_FLOAT: result.float_val = expr
|
||||||
elif tkind == PK_BIGGEST_FLOAT: result.big_float_val = expr
|
elif tkind == PK_BIGGEST_FLOAT: result.big_float_val = expr
|
||||||
elif tkind == PK_STRING: result.str_val = expr
|
elif tkind == PK_STRING: result.str_val = expr
|
||||||
elif tkind == PK_BOOL: result.bool_val = expr
|
elif tkind == PK_BOOL: result.bool_val = expr
|
||||||
elif tkind == PK_HELP: nil
|
elif tkind == PK_HELP: discard
|
||||||
else: {.error: "unknown kind".}
|
else: {.error: "unknown kind".}
|
||||||
result
|
result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
discard """
|
discard """
|
||||||
msg: '''
|
msg: '''int int
|
||||||
int
|
float float
|
||||||
float
|
int int
|
||||||
TFoo
|
TFoo TFoo
|
||||||
TFoo
|
int float
|
||||||
'''
|
TFoo TFoo'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import typetraits
|
import typetraits
|
||||||
|
|
@ -24,8 +24,7 @@ template reject(e: expr) =
|
||||||
|
|
||||||
proc genericParamRepeated[T: typedesc](a: T, b: T) =
|
proc genericParamRepeated[T: typedesc](a: T, b: T) =
|
||||||
static:
|
static:
|
||||||
echo a.name
|
echo a.name, " ", b.name
|
||||||
echo b.name
|
|
||||||
|
|
||||||
accept genericParamRepeated(int, int)
|
accept genericParamRepeated(int, int)
|
||||||
accept genericParamRepeated(float, float)
|
accept genericParamRepeated(float, float)
|
||||||
|
|
@ -35,8 +34,7 @@ reject genericParamRepeated(int, float)
|
||||||
|
|
||||||
proc genericParamOnce[T: typedesc](a, b: T) =
|
proc genericParamOnce[T: typedesc](a, b: T) =
|
||||||
static:
|
static:
|
||||||
echo a.name
|
echo a.name, " ", b.name
|
||||||
echo b.name
|
|
||||||
|
|
||||||
accept genericParamOnce(int, int)
|
accept genericParamOnce(int, int)
|
||||||
accept genericParamOnce(TFoo, TFoo)
|
accept genericParamOnce(TFoo, TFoo)
|
||||||
|
|
@ -68,8 +66,7 @@ reject typePairs2(string, int, TBAR, TBAR)
|
||||||
|
|
||||||
proc dontBind(a: typedesc, b: typedesc) =
|
proc dontBind(a: typedesc, b: typedesc) =
|
||||||
static:
|
static:
|
||||||
echo a.name
|
echo a.name, " ", b.name
|
||||||
echo b.name
|
|
||||||
|
|
||||||
accept dontBind(int, float)
|
accept dontBind(int, float)
|
||||||
accept dontBind(TFoo, TFoo)
|
accept dontBind(TFoo, TFoo)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ proc intval(x: int) = discard
|
||||||
# check real and virtual fields
|
# check real and virtual fields
|
||||||
type
|
type
|
||||||
TFoo = generic T
|
TFoo = generic T
|
||||||
intval T.x
|
T.x
|
||||||
|
y(T)
|
||||||
intval T.y
|
intval T.y
|
||||||
|
|
||||||
proc y(x: TObj): int = 10
|
proc y(x: TObj): int = 10
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ type
|
||||||
TSomethingElse = object
|
TSomethingElse = object
|
||||||
PSomethingElse = ref TSomethingElse
|
PSomethingElse = ref TSomethingElse
|
||||||
|
|
||||||
method foo(a: PNode, b: PSomethingElse) = nil
|
method foo(a: PNode, b: PSomethingElse) = discard
|
||||||
method foo(a: PNodeFoo, b: PSomethingElse) = nil
|
method foo(a: PNodeFoo, b: PSomethingElse) = discard
|
||||||
|
|
||||||
var o: TObject
|
var o: TObject
|
||||||
o.somethin()
|
o.somethin()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
discard """
|
discard """
|
||||||
file: "tests/reject/trecincb.nim"
|
file: "tests/reject/trecincb.nim"
|
||||||
line: 9
|
line: 9
|
||||||
errormsg: "recursive dependency: 'tests/reject/trecincb.nim'"
|
errormsg: "recursive dependency: 'tests/module/trecincb.nim'"
|
||||||
"""
|
"""
|
||||||
# Test recursive includes
|
# Test recursive includes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
discard """
|
discard """
|
||||||
file: "trecincb.nim"
|
file: "trecincb.nim"
|
||||||
line: 9
|
line: 9
|
||||||
errormsg: "recursive dependency: 'tests/reject/trecincb.nim'"
|
errormsg: "recursive dependency: 'tests/module/trecincb.nim'"
|
||||||
"""
|
"""
|
||||||
# Test recursive includes
|
# Test recursive includes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ discard """
|
||||||
# Test the overloading resolution in connection with a qualifier
|
# Test the overloading resolution in connection with a qualifier
|
||||||
|
|
||||||
proc write(t: TFile, s: string) =
|
proc write(t: TFile, s: string) =
|
||||||
nil # a nop
|
discard # a nop
|
||||||
|
|
||||||
system.write(stdout, "hello")
|
system.write(stdout, "hello")
|
||||||
#OUT hello
|
#OUT hello
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ discard """
|
||||||
output: "12false3ha"
|
output: "12false3ha"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc f(x: varargs[string, `$`]) = nil
|
proc f(x: varargs[string, `$`]) = discard
|
||||||
template optF{f(X)}(x: varargs[expr]) =
|
template optF{f(X)}(x: varargs[expr]) =
|
||||||
writeln(stdout, x)
|
writeln(stdout, x)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ type
|
||||||
TRange = range[0..40]
|
TRange = range[0..40]
|
||||||
|
|
||||||
proc p(r: TRange) =
|
proc p(r: TRange) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
var
|
var
|
||||||
r: TRange
|
r: TRange
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ type
|
||||||
TRange = range[0..40]
|
TRange = range[0..40]
|
||||||
|
|
||||||
proc p(r: TRange) =
|
proc p(r: TRange) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
var
|
var
|
||||||
r: TRange
|
r: TRange
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,12 @@ else: write(stdout, "BUG: F ist nicht in s!\n")
|
||||||
a = {} #{'a'..'z'}
|
a = {} #{'a'..'z'}
|
||||||
for x in low(TAZ) .. high(TAZ):
|
for x in low(TAZ) .. high(TAZ):
|
||||||
incl(a, x)
|
incl(a, x)
|
||||||
if x in a: nil
|
if x in a: discard
|
||||||
else: write(stdout, "BUG: something not in a!\n")
|
else: write(stdout, "BUG: something not in a!\n")
|
||||||
|
|
||||||
for x in low(TTokTypeRange) .. high(TTokTypeRange):
|
for x in low(TTokTypeRange) .. high(TTokTypeRange):
|
||||||
if x in tokTypes:
|
if x in tokTypes:
|
||||||
nil
|
discard
|
||||||
#writeln(stdout, "the token '$1' is in the set" % repr(x))
|
#writeln(stdout, "the token '$1' is in the set" % repr(x))
|
||||||
|
|
||||||
#OUT Ha ein F ist in s!
|
#OUT Ha ein F ist in s!
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ proc setSeen(d: TDb, s: TSeen) =
|
||||||
var hashToSet = @[("type", $s.kind.int), ("channel", s.channel),
|
var hashToSet = @[("type", $s.kind.int), ("channel", s.channel),
|
||||||
("timestamp", $s.timestamp.int)]
|
("timestamp", $s.timestamp.int)]
|
||||||
case s.kind
|
case s.kind
|
||||||
of PSeenJoin: nil
|
of PSeenJoin: discard
|
||||||
of PSeenPart, PSeenMsg, PSeenQuit:
|
of PSeenPart, PSeenMsg, PSeenQuit:
|
||||||
hashToSet.add(("msg", s.msg))
|
hashToSet.add(("msg", s.msg))
|
||||||
of PSeenNick:
|
of PSeenNick:
|
||||||
|
|
@ -338,7 +338,7 @@ proc hubConnect(state: PState) =
|
||||||
|
|
||||||
proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) =
|
proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) =
|
||||||
case event.typ
|
case event.typ
|
||||||
of EvConnected: nil
|
of EvConnected: discard
|
||||||
of EvDisconnected:
|
of EvDisconnected:
|
||||||
while not state.ircClient.isConnected:
|
while not state.ircClient.isConnected:
|
||||||
try:
|
try:
|
||||||
|
|
@ -424,7 +424,7 @@ proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) =
|
||||||
seenNick.newNick = event.params[0]
|
seenNick.newNick = event.params[0]
|
||||||
state.database.setSeen(seenNick)
|
state.database.setSeen(seenNick)
|
||||||
else:
|
else:
|
||||||
nil # TODO: ?
|
discard # TODO: ?
|
||||||
|
|
||||||
proc open(port: TPort = TPort(5123)): PState =
|
proc open(port: TPort = TPort(5123)): PState =
|
||||||
var res: PState
|
var res: PState
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# tests for the interpreter
|
# tests for the interpreter
|
||||||
|
|
||||||
proc loops(a: var int) =
|
proc loops(a: var int) =
|
||||||
nil
|
discard
|
||||||
#var
|
#var
|
||||||
# b: int
|
# b: int
|
||||||
#b = glob
|
#b = glob
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ proc walkDirTree(root: string) =
|
||||||
case k
|
case k
|
||||||
of pcFile, pcLinkToFile: echo(f)
|
of pcFile, pcLinkToFile: echo(f)
|
||||||
of pcDir: walkDirTree(f)
|
of pcDir: walkDirTree(f)
|
||||||
of pcLinkToDir: nil
|
of pcLinkToDir: discard
|
||||||
|
|
||||||
walkDirTree(".")
|
walkDirTree(".")
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ type
|
||||||
rule: TNode ## the rule that the symbol refers to
|
rule: TNode ## the rule that the symbol refers to
|
||||||
TNode {.final, shallow.} = object
|
TNode {.final, shallow.} = object
|
||||||
case kind: TPegKind
|
case kind: TPegKind
|
||||||
of pkEmpty..pkWhitespace: nil
|
of pkEmpty..pkWhitespace: discard
|
||||||
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string
|
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string
|
||||||
of pkChar, pkGreedyRepChar: ch: char
|
of pkChar, pkGreedyRepChar: ch: char
|
||||||
of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char]
|
of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char]
|
||||||
|
|
@ -123,7 +123,7 @@ proc add(d: var TPeg, s: TPeg) {.inline.} = add(d.sons, s)
|
||||||
proc copyPeg(a: TPeg): TPeg =
|
proc copyPeg(a: TPeg): TPeg =
|
||||||
result.kind = a.kind
|
result.kind = a.kind
|
||||||
case a.kind
|
case a.kind
|
||||||
of pkEmpty..pkWhitespace: nil
|
of pkEmpty..pkWhitespace: discard
|
||||||
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle:
|
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle:
|
||||||
result.term = a.term
|
result.term = a.term
|
||||||
of pkChar, pkGreedyRepChar:
|
of pkChar, pkGreedyRepChar:
|
||||||
|
|
@ -229,7 +229,7 @@ when false:
|
||||||
case a.kind
|
case a.kind
|
||||||
of pkEmpty, pkAny, pkAnyRune, pkGreedyAny, pkNewLine, pkTerminal,
|
of pkEmpty, pkAny, pkAnyRune, pkGreedyAny, pkNewLine, pkTerminal,
|
||||||
pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, pkGreedyRepChar,
|
pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, pkGreedyRepChar,
|
||||||
pkCharChoice, pkGreedyRepSet: nil
|
pkCharChoice, pkGreedyRepSet: discard
|
||||||
of pkNonTerminal: return true
|
of pkNonTerminal: return true
|
||||||
else:
|
else:
|
||||||
for i in 0..a.sons.len-1:
|
for i in 0..a.sons.len-1:
|
||||||
|
|
@ -318,7 +318,7 @@ proc backrefIgnoreStyle*(index: range[1..MaxSubPatterns]): TPeg {.
|
||||||
|
|
||||||
proc spaceCost(n: TPeg): int =
|
proc spaceCost(n: TPeg): int =
|
||||||
case n.kind
|
case n.kind
|
||||||
of pkEmpty: nil
|
of pkEmpty: discard
|
||||||
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar,
|
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar,
|
||||||
pkGreedyRepChar, pkCharChoice, pkGreedyRepSet,
|
pkGreedyRepChar, pkCharChoice, pkGreedyRepSet,
|
||||||
pkAny..pkWhitespace, pkGreedyAny:
|
pkAny..pkWhitespace, pkGreedyAny:
|
||||||
|
|
@ -1111,7 +1111,7 @@ proc handleHexChar(c: var TPegLexer, xi: var int) =
|
||||||
of 'A'..'F':
|
of 'A'..'F':
|
||||||
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10)
|
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10)
|
||||||
inc(c.bufpos)
|
inc(c.bufpos)
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
proc getEscapedChar(c: var TPegLexer, tok: var TToken) =
|
proc getEscapedChar(c: var TPegLexer, tok: var TToken) =
|
||||||
inc(c.bufpos)
|
inc(c.bufpos)
|
||||||
|
|
@ -1341,7 +1341,7 @@ proc getTok(c: var TPegLexer, tok: var TToken) =
|
||||||
of "i": tok.modifier = modIgnoreCase
|
of "i": tok.modifier = modIgnoreCase
|
||||||
of "y": tok.modifier = modIgnoreStyle
|
of "y": tok.modifier = modIgnoreStyle
|
||||||
of "v": tok.modifier = modVerbatim
|
of "v": tok.modifier = modVerbatim
|
||||||
else: nil
|
else: discard
|
||||||
setLen(tok.literal, 0)
|
setLen(tok.literal, 0)
|
||||||
if c.buf[c.bufpos] == '$':
|
if c.buf[c.bufpos] == '$':
|
||||||
getDollar(c, tok)
|
getDollar(c, tok)
|
||||||
|
|
@ -1488,7 +1488,7 @@ proc primary(p: var TPegParser): TPeg =
|
||||||
of tkCurlyAt:
|
of tkCurlyAt:
|
||||||
getTok(p)
|
getTok(p)
|
||||||
return !*\primary(p).token(p)
|
return !*\primary(p).token(p)
|
||||||
else: nil
|
else: discard
|
||||||
case p.tok.kind
|
case p.tok.kind
|
||||||
of tkIdentifier:
|
of tkIdentifier:
|
||||||
if p.identIsVerbatim:
|
if p.identIsVerbatim:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Test if the new table constructor syntax works:
|
# Test if the new table constructor syntax works:
|
||||||
|
|
||||||
template ignoreExpr(e: expr): stmt {.immediate.} =
|
template ignoreExpr(e: expr): stmt {.immediate.} =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
# test first class '..' syntactical citizen:
|
# test first class '..' syntactical citizen:
|
||||||
ignoreExpr x <> 2..4
|
ignoreExpr x <> 2..4
|
||||||
|
|
|
||||||
1
tests/threads/nimrod.cfg
Normal file
1
tests/threads/nimrod.cfg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
threads:on
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
discard """
|
discard """
|
||||||
output: "he, no return type;abc a string"
|
output: '''12
|
||||||
|
empty
|
||||||
|
he, no return type;
|
||||||
|
abc a string
|
||||||
|
ha'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc ReturnT[T](x: T): T =
|
proc ReturnT[T](x: T): T =
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ proc init: TYourObj =
|
||||||
result.y = -1
|
result.y = -1
|
||||||
|
|
||||||
proc f(x: var TYourObj) =
|
proc f(x: var TYourObj) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
var m: TMyObj = init()
|
var m: TMyObj = init()
|
||||||
f(m)
|
f(m)
|
||||||
|
|
|
||||||
4
todo.txt
4
todo.txt
|
|
@ -1,8 +1,10 @@
|
||||||
version 0.9.4
|
version 0.9.4
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
- fix macros\tstringinterp.nim:
|
||||||
|
- problem: needs another level of indirection for 'seq'
|
||||||
|
- problem: deref is not correct
|
||||||
- fix GC issues
|
- fix GC issues
|
||||||
- fix macros\tstringinterp.nim
|
|
||||||
- test and fix showoff
|
- test and fix showoff
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ proc walker(dir: string) =
|
||||||
of pcDir:
|
of pcDir:
|
||||||
if optRecursive in options:
|
if optRecursive in options:
|
||||||
walker(path)
|
walker(path)
|
||||||
else: nil
|
else: discard
|
||||||
if existsFile(dir): processFile(dir)
|
if existsFile(dir): processFile(dir)
|
||||||
|
|
||||||
proc writeHelp() =
|
proc writeHelp() =
|
||||||
|
|
|
||||||
|
|
@ -74,5 +74,15 @@
|
||||||
<div id="legal">Copyright © 2013 - Andreas Rumpf & Contributors - All rights reserved - <a href="http://reign-studios.com/philipwitte/">Design by Philip Witte</a></div>
|
<div id="legal">Copyright © 2013 - Andreas Rumpf & Contributors - All rights reserved - <a href="http://reign-studios.com/philipwitte/">Design by Philip Witte</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-48159761-1', 'nimrod-lang.org');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ html, body {
|
||||||
|
|
||||||
#page { position:relative; float:left; padding:20px 30px 50px 50px; width:620px; color:#343739; }
|
#page { position:relative; float:left; padding:20px 30px 50px 50px; width:620px; color:#343739; }
|
||||||
|
|
||||||
#page h1 { margin-top:40px; }
|
#page h1 { margin-top:40px; line-height: 28px; }
|
||||||
#page h2 { margin-top:40px; }
|
#page h2 { margin-top:40px; }
|
||||||
|
|
||||||
#page p { text-align:justify; }
|
#page p { text-align:justify; }
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,55 @@
|
||||||
Discuss Nimrod in our `forum <http://forum.nimrod-code.org/>`_.
|
Forum
|
||||||
|
=====
|
||||||
|
|
||||||
Visit our project page at GitHub: http://github.com/Araq/Nimrod.
|
The `Nimrod forum <http://forum.nimrod-code.org/>`_ is the place where most
|
||||||
|
discussions related to the language happen. It not only includes discussions
|
||||||
|
relating to the design of Nimrod but also allows for beginners to ask questions
|
||||||
|
relating to Nimrod.
|
||||||
|
|
||||||
Wiki: http://github.com/Araq/Nimrod/wiki.
|
IRC
|
||||||
|
====
|
||||||
|
|
||||||
Bug reports: http://github.com/Araq/Nimrod/issues.
|
Many Nimrod developers are a part of the
|
||||||
|
`#nimrod IRC channel <http://webchat.freenode.net/?channels=nimrod>`_ on
|
||||||
|
Freenode. That is the place where the rest of the discussion relating to Nimrod
|
||||||
|
occurs. Be sure to join us there if you wish to discuss Nimrod in real-time.
|
||||||
|
IRC is the perfect place for people just starting to learn Nimrod and we
|
||||||
|
welcome any questions that you may have!
|
||||||
|
|
||||||
For quickest feedback, join our IRC channel: irc://irc.freenode.net/nimrod
|
You may also be interested in reading the
|
||||||
(logs at `<http://build.nimrod-code.org/irclogs/>`_).
|
`IRC logs <http://build.nimrod-code.org/irclogs/>`_ which are an archive of all
|
||||||
|
of the previous discussions that took place in the IRC channel.
|
||||||
|
|
||||||
Check out our Twitter account for latest news and announcements: `@nimrodlang <http://twitter.com/nimrodlang>`_.
|
Github
|
||||||
|
======
|
||||||
|
|
||||||
|
Nimrod's `source code <http://github.com/Araq/Nimrod>`_ is hosted on Github.
|
||||||
|
Together with the `wiki <http://github.com/Araq/Nimrod/wiki>`_ and
|
||||||
|
`issue tracker <http://github.com/Araq/Nimrod/issues>`_.
|
||||||
|
|
||||||
|
Github also hosts other projects relating to Nimrod. These projects are a part
|
||||||
|
of the `nimrod-code organisation <http://github.com/nimrod-code>`_.
|
||||||
|
This includes the `Babel package manager <http://github.com/nimrod-code/babel>`_
|
||||||
|
and its `package repository <http://github.com/nimrod-code/packages>`_.
|
||||||
|
|
||||||
|
Twitter
|
||||||
|
=======
|
||||||
|
|
||||||
|
Follow us `@nimrodlang <http://twitter.com/nimrodlang>`_ for latest news about
|
||||||
|
Nimrod.
|
||||||
|
|
||||||
|
Reddit
|
||||||
|
======
|
||||||
|
|
||||||
|
Subscribe to `/r/nimrod <http://reddit.com/r/nimrod>`_ for latest news about
|
||||||
|
Nimrod.
|
||||||
|
|
||||||
|
StackOverflow
|
||||||
|
=============
|
||||||
|
|
||||||
|
When asking a question relating to Nimrod, be sure to use the
|
||||||
|
`Nimrod <http://stackoverflow.com/questions/tagged/nimrod>`_ tag in your
|
||||||
|
question.
|
||||||
|
|
||||||
How to help
|
How to help
|
||||||
===========
|
===========
|
||||||
|
|
@ -26,7 +66,9 @@ can't find anything you fancy doing, you can always ask for inspiration on IRC
|
||||||
Donations
|
Donations
|
||||||
---------
|
---------
|
||||||
|
|
||||||
If you love what we do and are feeling generous then you can always donate:
|
If you love what we do and are feeling generous then you can always donate.
|
||||||
|
Contributions of any quantity are greatly appreciated and will contribute to
|
||||||
|
making Nimrod even better!
|
||||||
|
|
||||||
Gittip
|
Gittip
|
||||||
``````
|
``````
|
||||||
|
|
|
||||||
143
web/news.txt
143
web/news.txt
|
|
@ -3,86 +3,103 @@ News
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
||||||
2014-XX-XX Version 0.9.4 released
|
..
|
||||||
=================================
|
2014-XX-XX Version 0.9.4 released
|
||||||
|
=================================
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
||||||
Library Additions
|
Library Additions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
- Added ``macros.genSym`` builtin for AST generation.
|
- Added ``macros.genSym`` builtin for AST generation.
|
||||||
- Added ``macros.newLit`` procs for easier AST generation.
|
- Added ``macros.newLit`` procs for easier AST generation.
|
||||||
|
|
||||||
|
|
||||||
Changes affecting backwards compatibility
|
Changes affecting backwards compatibility
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
- The scoping rules for the ``if`` statement changed for better interaction
|
- The scoping rules for the ``if`` statement changed for better interaction
|
||||||
with the new syntactic construct ``(;)``.
|
with the new syntactic construct ``(;)``.
|
||||||
- ``OSError`` family of procedures has been deprecated. Procedures with the same
|
- ``OSError`` family of procedures has been deprecated. Procedures with the same
|
||||||
name but which take different parameters have been introduced. These procs now
|
name but which take different parameters have been introduced. These procs now
|
||||||
require an error code to be passed to them. This error code can be retrieved
|
require an error code to be passed to them. This error code can be retrieved
|
||||||
using the new ``OSLastError`` proc.
|
using the new ``OSLastError`` proc.
|
||||||
- ``os.parentDir`` now returns "" if there is no parent dir.
|
- ``os.parentDir`` now returns "" if there is no parent dir.
|
||||||
- In CGI scripts stacktraces are shown to the user only
|
- In CGI scripts stacktraces are shown to the user only
|
||||||
if ``cgi.setStackTraceStdout`` is used.
|
if ``cgi.setStackTraceStdout`` is used.
|
||||||
- The symbol binding rules for clean templates changed: ``bind`` for any
|
- The symbol binding rules for clean templates changed: ``bind`` for any
|
||||||
symbol that's not a parameter is now the default. ``mixin`` can be used
|
symbol that's not a parameter is now the default. ``mixin`` can be used
|
||||||
to require instantiation scope for a symbol.
|
to require instantiation scope for a symbol.
|
||||||
- ``quoteIfContainsWhite`` now escapes argument in such way that it can be safely
|
- ``quoteIfContainsWhite`` now escapes argument in such way that it can be safely
|
||||||
passed to shell, instead of just adding double quotes.
|
passed to shell, instead of just adding double quotes.
|
||||||
- ``macros.dumpTree`` and ``macros.dumpLisp`` have been made ``immediate``,
|
- ``macros.dumpTree`` and ``macros.dumpLisp`` have been made ``immediate``,
|
||||||
``dumpTreeImm`` and ``dumpLispImm`` are now deprecated.
|
``dumpTreeImm`` and ``dumpLispImm`` are now deprecated.
|
||||||
- The ``nil`` statement has been deprecated, use an empty ``discard`` instead.
|
- The ``nil`` statement has been deprecated, use an empty ``discard`` instead.
|
||||||
- ``sockets.select`` now prunes sockets that are **not** ready from the list
|
- ``sockets.select`` now prunes sockets that are **not** ready from the list
|
||||||
of sockets given to it.
|
of sockets given to it.
|
||||||
|
|
||||||
|
|
||||||
Compiler Additions
|
Compiler Additions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- The compiler can now warn about "uninitialized" variables. (There are no
|
- The compiler can now warn about "uninitialized" variables. (There are no
|
||||||
real uninitialized variables in Nimrod as they are initialized to binary
|
real uninitialized variables in Nimrod as they are initialized to binary
|
||||||
zero). Activate via ``{.warning[Uninit]:on.}``.
|
zero). Activate via ``{.warning[Uninit]:on.}``.
|
||||||
- The compiler now enforces the ``not nil`` constraint.
|
- The compiler now enforces the ``not nil`` constraint.
|
||||||
- The compiler now supports a ``codegenDecl`` pragma for even more control
|
- The compiler now supports a ``codegenDecl`` pragma for even more control
|
||||||
over the generated code.
|
over the generated code.
|
||||||
- The compiler now supports a ``computedGoto`` pragma to support very fast
|
- The compiler now supports a ``computedGoto`` pragma to support very fast
|
||||||
dispatching for interpreters and the like.
|
dispatching for interpreters and the like.
|
||||||
- The old evaluation engine has been replaced by a proper register based
|
- The old evaluation engine has been replaced by a proper register based
|
||||||
virtual machine. This fixes numerous bugs for ``nimrod i`` and for macro
|
virtual machine. This fixes numerous bugs for ``nimrod i`` and for macro
|
||||||
evaluation.
|
evaluation.
|
||||||
- ``--gc:none`` produces warnings when code uses the GC.
|
- ``--gc:none`` produces warnings when code uses the GC.
|
||||||
|
|
||||||
|
|
||||||
Language Additions
|
Language Additions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Arrays can now be declared with a single integer literal ``N`` instead of a
|
- Arrays can now be declared with a single integer literal ``N`` instead of a
|
||||||
range; the range is then ``0..N-1``.
|
range; the range is then ``0..N-1``.
|
||||||
- Added ``requiresInit`` pragma to enforce explicit initialization.
|
- Added ``requiresInit`` pragma to enforce explicit initialization.
|
||||||
- Exported templates are allowed to access hidden fields.
|
- Exported templates are allowed to access hidden fields.
|
||||||
- The ``using statement`` enables you to more easily author domain-specific
|
- The ``using statement`` enables you to more easily author domain-specific
|
||||||
languages and libraries providing OOP-like syntactic sugar.
|
languages and libraries providing OOP-like syntactic sugar.
|
||||||
- Added the possibility to override various dot operators in order to handle
|
- Added the possibility to override various dot operators in order to handle
|
||||||
calls to missing procs and reads from undeclared fields at compile-time.
|
calls to missing procs and reads from undeclared fields at compile-time.
|
||||||
- The overload resolution now supports ``static[T]`` params that must be
|
- The overload resolution now supports ``static[T]`` params that must be
|
||||||
evaluable at compile-time.
|
evaluable at compile-time.
|
||||||
- Support for user-defined type classes has been added.
|
- Support for user-defined type classes has been added.
|
||||||
- The *command syntax* is supported in a lot more contexts.
|
- The *command syntax* is supported in a lot more contexts.
|
||||||
- Anonymous iterators are now supported and iterators can capture variables
|
- Anonymous iterators are now supported and iterators can capture variables
|
||||||
of an outer proc.
|
of an outer proc.
|
||||||
|
|
||||||
|
|
||||||
Tools improvements
|
Tools improvements
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- c2nim can deal with a subset of C++. Use the ``--cpp`` command line option
|
- c2nim can deal with a subset of C++. Use the ``--cpp`` command line option
|
||||||
to activate.
|
to activate.
|
||||||
|
|
||||||
|
|
||||||
|
2014-02-11 Nimrod Featured in Dr. Dobb's Journal
|
||||||
|
================================================
|
||||||
|
|
||||||
|
Nimrod has been `featured<http://www.drdobbs.com/open-source/nimrod-a-new-systems-programming-languag/240165321>`_
|
||||||
|
as the cover story in the February 2014 issue of Dr. Dobb's Journal.
|
||||||
|
|
||||||
|
|
||||||
|
2014-01-15 Andreas Rumpf's talk on Nimrod at Strange Loop 2013 is now online
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Andreas Rumpf presented *Nimrod: A New Approach to Metaprogramming* at
|
||||||
|
`Strange Loop 2013<https://thestrangeloop.com/sessions/nimrod-a-new-approach-to-meta-programming>`_.
|
||||||
|
The `video and slides<http://www.infoq.com/presentations/nimrod>`_
|
||||||
|
of the talk are now available.
|
||||||
|
|
||||||
|
|
||||||
2013-05-20 New website design!
|
2013-05-20 New website design!
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
<a class="news" href="news.html#new-website-design">
|
<a class="news" href="news.html#Z2014-02-11-nimrod-featured-in-dr-dobb-s-journal">
|
||||||
|
<h3>Feb 11, 2014</h3>
|
||||||
|
<p>Nimrod featured in Dr. Dobb's Journal</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="news" href="news.html#Z2014-01-15-andreas-rumpf-s-talk-on-nimrod-at-strange-loop-2013-is-now-online">
|
||||||
|
<h3>Jan 15, 2014</h3>
|
||||||
|
<p>Andreas Rumpf's talk on Nimrod at Strange Loop 2013 is now online.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="news" href="news.html#Z2013-05-20-new-website-design">
|
||||||
<h3>May 20, 2013</h3>
|
<h3>May 20, 2013</h3>
|
||||||
<p>New website design!</p>
|
<p>New website design!</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue