Merge branch 'devel' of github.com:Araq/Nimrod into seq_toString
This commit is contained in:
commit
ffb36db5a6
215 changed files with 9066 additions and 4504 deletions
|
|
@ -48,17 +48,17 @@ type
|
|||
nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt,
|
||||
nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt,
|
||||
nnkDiscardStmt, nnkStmtList,
|
||||
|
||||
nnkImportStmt,
|
||||
nnkImportExceptStmt,
|
||||
nnkExportStmt,
|
||||
nnkExportExceptStmt,
|
||||
nnkFromStmt,
|
||||
nnkIncludeStmt,
|
||||
|
||||
nnkBindStmt, nnkMixinStmt, nnkUsingStmt,
|
||||
nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr,
|
||||
nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy,
|
||||
nnkStmtListType, nnkBlockType,
|
||||
nnkWith, nnkWithout,
|
||||
nnkTypeOfExpr, nnkObjectTy,
|
||||
nnkTupleTy, nnkTypeClassTy, nnkStaticTy,
|
||||
nnkRecList, nnkRecCase, nnkRecWhen,
|
||||
nnkRefTy, nnkPtrTy, nnkVarTy,
|
||||
|
|
@ -88,7 +88,7 @@ type
|
|||
nskUnknown, nskConditional, nskDynLib, nskParam,
|
||||
nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet,
|
||||
nskConst, nskResult,
|
||||
nskProc, nskMethod, nskIterator,
|
||||
nskProc, nskMethod, nskIterator, nskClosureIterator,
|
||||
nskConverter, nskMacro, nskTemplate, nskField,
|
||||
nskEnumField, nskForVar, nskLabel,
|
||||
nskStub
|
||||
|
|
@ -516,7 +516,7 @@ proc last*(node: PNimrodNode): PNimrodNode {.compileTime.} = node[node.high]
|
|||
|
||||
|
||||
const
|
||||
RoutineNodes* = {nnkProcDef, nnkMethodDef, nnkDo, nnkLambda}
|
||||
RoutineNodes* = {nnkProcDef, nnkMethodDef, nnkDo, nnkLambda, nnkIteratorDef}
|
||||
AtomicNodes* = {nnkNone..nnkNilLit}
|
||||
CallNodes* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
|
||||
nnkCallStrLit, nnkHiddenCallConv}
|
||||
|
|
|
|||
|
|
@ -1,227 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2012 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module implements a higher level wrapper for `mongodb`:idx:. Example:
|
||||
##
|
||||
## .. code-block:: nimrod
|
||||
##
|
||||
## import mongo, db_mongo, oids, json
|
||||
##
|
||||
## var conn = db_mongo.open()
|
||||
##
|
||||
## # construct JSON data:
|
||||
## var data = %{"a": %13, "b": %"my string value",
|
||||
## "inner": %{"i": %71} }
|
||||
##
|
||||
## var id = insertID(conn, "test.test", data)
|
||||
##
|
||||
## for v in find(conn, "test.test", "this.a == 13"):
|
||||
## print v
|
||||
##
|
||||
## delete(conn, "test.test", id)
|
||||
## close(conn)
|
||||
|
||||
import mongo, oids, json
|
||||
|
||||
type
|
||||
EDb* = object of EIO ## exception that is raised if a database error occurs
|
||||
TDbConn* = TMongo ## a database connection; alias for ``TMongo``
|
||||
|
||||
FDb* = object of FIO ## effect that denotes a database operation
|
||||
FReadDb* = object of FDB ## effect that denotes a read operation
|
||||
FWriteDb* = object of FDB ## effect that denotes a write operation
|
||||
|
||||
proc dbError*(db: TDbConn, msg: string) {.noreturn.} =
|
||||
## raises an EDb exception with message `msg`.
|
||||
var e: ref EDb
|
||||
new(e)
|
||||
if db.errstr[0] != '\0':
|
||||
e.msg = $db.errstr
|
||||
else:
|
||||
e.msg = $db.err & " " & msg
|
||||
raise e
|
||||
|
||||
proc close*(db: var TDbConn) {.tags: [FDB].} =
|
||||
## closes the database connection.
|
||||
disconnect(db)
|
||||
destroy(db)
|
||||
|
||||
proc open*(host: string = defaultHost, port: int = defaultPort): TDbConn {.
|
||||
tags: [FDB].} =
|
||||
## opens a database connection. Raises `EDb` if the connection could not
|
||||
## be established.
|
||||
init(result)
|
||||
|
||||
let x = client(result, host, port.cint)
|
||||
if x != 0'i32:
|
||||
dbError(result, "cannot open: " & host)
|
||||
|
||||
proc jsonToBSon(b: var TBSon, key: string, j: PJsonNode) =
|
||||
case j.kind
|
||||
of JString:
|
||||
add(b, key, j.str)
|
||||
of JInt:
|
||||
add(b, key, j.num)
|
||||
of JFloat:
|
||||
add(b, key, j.fnum)
|
||||
of JBool:
|
||||
addBool(b, key, ord(j.bval))
|
||||
of JNull:
|
||||
addNull(b, key)
|
||||
of JObject:
|
||||
addStartObject(b, key)
|
||||
for k, v in items(j.fields):
|
||||
jsonToBSon(b, k, v)
|
||||
addFinishObject(b)
|
||||
of JArray:
|
||||
addStartArray(b, key)
|
||||
for i, e in pairs(j.elems):
|
||||
jsonToBSon(b, $i, e)
|
||||
addFinishArray(b)
|
||||
|
||||
proc jsonToBSon*(j: PJsonNode, oid: TOid): TBSon =
|
||||
## converts a JSON value into the BSON format. The result must be
|
||||
## ``destroyed`` explicitely!
|
||||
init(result)
|
||||
assert j.kind == JObject
|
||||
add(result, "_id", oid)
|
||||
for key, val in items(j.fields):
|
||||
jsonToBSon(result, key, val)
|
||||
finish(result)
|
||||
|
||||
proc `[]`*(obj: var TBSon, fieldname: cstring): TBSon =
|
||||
## retrieves the value belonging to `fieldname`. Raises `EInvalidKey` if
|
||||
## the attribute does not exist.
|
||||
var it = initIter(obj)
|
||||
let res = find(it, result, fieldname)
|
||||
if res == bkEOO:
|
||||
raise newException(EInvalidIndex, "key not in object")
|
||||
|
||||
proc getId*(obj: var TBSon): TOid =
|
||||
## retrieves the ``_id`` attribute of `obj`.
|
||||
var it = initIter(obj)
|
||||
var b: TBSon
|
||||
let res = find(it, b, "_id")
|
||||
if res == bkOID:
|
||||
result = oidVal(it)[]
|
||||
else:
|
||||
raise newException(EInvalidIndex, "_id not in object")
|
||||
|
||||
proc insertId*(db: var TDbConn, namespace: string, data: PJsonNode): TOid {.
|
||||
tags: [FWriteDb].} =
|
||||
## converts `data` to BSON format and inserts it in `namespace`. Returns
|
||||
## the generated OID for the ``_id`` field.
|
||||
result = genOid()
|
||||
var x = jsonToBSon(data, result)
|
||||
insert(db, namespace, x, nil)
|
||||
destroy(x)
|
||||
|
||||
proc insert*(db: var TDbConn, namespace: string, data: PJsonNode) {.
|
||||
tags: [FWriteDb].} =
|
||||
## converts `data` to BSON format and inserts it in `namespace`.
|
||||
discard InsertID(db, namespace, data)
|
||||
|
||||
proc update*(db: var TDbConn, namespace: string, obj: var TBSon) {.
|
||||
tags: [FReadDB, FWriteDb].} =
|
||||
## updates `obj` in `namespace`.
|
||||
var cond: TBson
|
||||
init(cond)
|
||||
cond.add("_id", getId(obj))
|
||||
finish(cond)
|
||||
update(db, namespace, cond, obj, ord(UPDATE_UPSERT))
|
||||
destroy(cond)
|
||||
|
||||
proc update*(db: var TDbConn, namespace: string, oid: TOid, obj: PJsonNode) {.
|
||||
tags: [FReadDB, FWriteDb].} =
|
||||
## updates the data with `oid` to have the new data `obj`.
|
||||
var a = jsonToBSon(obj, oid)
|
||||
Update(db, namespace, a)
|
||||
destroy(a)
|
||||
|
||||
proc delete*(db: var TDbConn, namespace: string, oid: TOid) {.
|
||||
tags: [FWriteDb].} =
|
||||
## Deletes the object belonging to `oid`.
|
||||
var cond: TBson
|
||||
init(cond)
|
||||
cond.add("_id", oid)
|
||||
finish(cond)
|
||||
discard remove(db, namespace, cond)
|
||||
destroy(cond)
|
||||
|
||||
proc delete*(db: var TDbConn, namespace: string, obj: var TBSon) {.
|
||||
tags: [FWriteDb].} =
|
||||
## Deletes the object `obj`.
|
||||
delete(db, namespace, getId(obj))
|
||||
|
||||
iterator find*(db: var TDbConn, namespace: string): var TBSon {.
|
||||
tags: [FReadDB].} =
|
||||
## iterates over any object in `namespace`.
|
||||
var cursor: TCursor
|
||||
init(cursor, db, namespace)
|
||||
while next(cursor) == mongo.OK:
|
||||
yield bson(cursor)[]
|
||||
destroy(cursor)
|
||||
|
||||
iterator find*(db: var TDbConn, namespace: string,
|
||||
query, fields: var TBSon): var TBSon {.tags: [FReadDB].} =
|
||||
## yields the `fields` of any document that suffices `query`.
|
||||
var cursor = find(db, namespace, query, fields, 0'i32, 0'i32, 0'i32)
|
||||
if cursor != nil:
|
||||
while next(cursor[]) == mongo.OK:
|
||||
yield bson(cursor[])[]
|
||||
destroy(cursor[])
|
||||
|
||||
proc setupFieldnames(fields: varargs[string]): TBSon =
|
||||
init(result)
|
||||
for x in fields: add(result, x, 1'i32)
|
||||
finish(result)
|
||||
|
||||
iterator find*(db: var TDbConn, namespace: string,
|
||||
query: var TBSon, fields: varargs[string]): var TBSon {.
|
||||
tags: [FReadDB].} =
|
||||
## yields the `fields` of any document that suffices `query`. If `fields`
|
||||
## is ``[]`` the whole document is yielded.
|
||||
var f = setupFieldnames(fields)
|
||||
var cursor = find(db, namespace, query, f, 0'i32, 0'i32, 0'i32)
|
||||
if cursor != nil:
|
||||
while next(cursor[]) == mongo.OK:
|
||||
yield bson(cursor[])[]
|
||||
destroy(cursor[])
|
||||
destroy(f)
|
||||
|
||||
proc setupQuery(query: string): TBSon =
|
||||
init(result)
|
||||
add(result, "$where", query)
|
||||
finish(result)
|
||||
|
||||
iterator find*(db: var TDbConn, namespace: string,
|
||||
query: string, fields: varargs[string]): var TBSon {.
|
||||
tags: [FReadDB].} =
|
||||
## yields the `fields` of any document that suffices `query`. If `fields`
|
||||
## is ``[]`` the whole document is yielded.
|
||||
var f = setupFieldnames(fields)
|
||||
var q = setupQuery(query)
|
||||
var cursor = find(db, namespace, q, f, 0'i32, 0'i32, 0'i32)
|
||||
if cursor != nil:
|
||||
while next(cursor[]) == mongo.OK:
|
||||
yield bson(cursor[])[]
|
||||
destroy(cursor[])
|
||||
destroy(q)
|
||||
destroy(f)
|
||||
|
||||
when false:
|
||||
# this doesn't work this way; would require low level hacking
|
||||
iterator fieldPairs*(obj: var TBSon): tuple[key: cstring, value: TBSon] =
|
||||
## iterates over `obj` and yields all (key, value)-Pairs.
|
||||
var it = initIter(obj)
|
||||
var v: TBSon
|
||||
while next(it) != bkEOO:
|
||||
let key = key(it)
|
||||
discard init(v, value(it))
|
||||
yield (key, v)
|
||||
|
|
@ -87,7 +87,7 @@ proc newRow(L: int): TRow =
|
|||
|
||||
proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
|
||||
if row != nil:
|
||||
while mysql.FetchRow(sqlres) != nil: nil
|
||||
while mysql.FetchRow(sqlres) != nil: discard
|
||||
mysql.FreeResult(sqlres)
|
||||
|
||||
iterator fastRows*(db: TDbConn, query: TSqlQuery,
|
||||
|
|
@ -195,8 +195,14 @@ proc open*(connection, user, password, database: string): TDbConn {.
|
|||
## be established.
|
||||
result = mysql.Init(nil)
|
||||
if result == nil: dbError("could not open database connection")
|
||||
if mysql.RealConnect(result, "", user, password, database,
|
||||
0'i32, nil, 0) == nil:
|
||||
let
|
||||
colonPos = connection.find(':')
|
||||
host = if colonPos < 0: connection
|
||||
else: substr(connection, 0, colonPos-1)
|
||||
port: int32 = if colonPos < 0: 0'i32
|
||||
else: substr(connection, colonPos+1).parseInt.int32
|
||||
if mysql.RealConnect(result, host, user, password, database,
|
||||
port, nil, 0) == nil:
|
||||
var errmsg = $mysql.error(result)
|
||||
db_mysql.Close(result)
|
||||
dbError(errmsg)
|
||||
|
|
|
|||
|
|
@ -285,8 +285,8 @@ static N_INLINE(NI32, float32ToInt32)(float x) {
|
|||
|
||||
typedef struct TStringDesc* string;
|
||||
|
||||
/* declared size of a sequence: */
|
||||
#if defined(__GNUC__)
|
||||
/* declared size of a sequence/variable length array: */
|
||||
#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
|
||||
# define SEQ_DECL_SIZE /* empty is correct! */
|
||||
#else
|
||||
# define SEQ_DECL_SIZE 1000000
|
||||
|
|
@ -314,6 +314,9 @@ static unsigned long nimNaN[2]={0xffffffff, 0x7fffffff};
|
|||
# define INF INFINITY
|
||||
# elif defined(HUGE_VAL)
|
||||
# define INF HUGE_VAL
|
||||
# elif defined(_MSC_VER)
|
||||
# include <float.h>
|
||||
# define INF (DBL_MAX+DBL_MAX)
|
||||
# else
|
||||
# define INF (1.0 / 0.0)
|
||||
# endif
|
||||
|
|
@ -373,5 +376,8 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); }
|
|||
# define GC_GUARD
|
||||
#endif
|
||||
|
||||
/* Test to see if nimrod and the C compiler agrees on the size of a pointer.
|
||||
On disagreement, your C compiler will say something like:
|
||||
"error: 'assert_numbits' declared as an array with a negative size" */
|
||||
typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
|
||||
#endif
|
||||
|
|
|
|||
6
lib/packages/docutils/docutils.babel
Normal file
6
lib/packages/docutils/docutils.babel
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[Package]
|
||||
name = "docutils"
|
||||
version = "0.9.0"
|
||||
author = "Andreas Rumpf"
|
||||
description = "Nimrod's reStructuredText processor."
|
||||
license = "MIT"
|
||||
|
|
@ -61,9 +61,8 @@ proc getSourceLanguage*(name: string): TSourceLanguage =
|
|||
if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0:
|
||||
return i
|
||||
result = langNone
|
||||
|
||||
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
|
||||
g.buf = cstring(buf)
|
||||
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) =
|
||||
g.buf = buf
|
||||
g.kind = low(TTokenClass)
|
||||
g.start = 0
|
||||
g.length = 0
|
||||
|
|
@ -71,6 +70,8 @@ proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
|
|||
var pos = 0 # skip initial whitespace:
|
||||
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
|
||||
g.pos = pos
|
||||
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
|
||||
initGeneralTokenizer(g, cstring(buf))
|
||||
|
||||
proc deinitGeneralTokenizer*(g: var TGeneralTokenizer) =
|
||||
discard
|
||||
|
|
|
|||
|
|
@ -1543,7 +1543,7 @@ proc dirRaw(p: var TRstParser): PRstNode =
|
|||
elif cmpIgnoreCase(result.sons[0].sons[0].text, "latex") == 0:
|
||||
dirRawAux(p, result, rnRawLatex, parseLiteralBlock)
|
||||
else:
|
||||
rstMessage(p, meInvalidDirective, result.sons[0].text)
|
||||
rstMessage(p, meInvalidDirective, result.sons[0].sons[0].text)
|
||||
else:
|
||||
dirRawAux(p, result, rnRaw, parseSectionWrapper)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
from posix import TSocketHandle
|
||||
|
||||
const
|
||||
EPOLLIN* = 0x00000001
|
||||
EPOLLPRI* = 0x00000002
|
||||
|
|
@ -33,8 +35,8 @@ const
|
|||
type
|
||||
epoll_data* {.importc: "union epoll_data",
|
||||
header: "<sys/epoll.h>", pure, final.} = object # TODO: This is actually a union.
|
||||
thePtr* {.importc: "ptr".}: pointer # \
|
||||
#fd*: cint
|
||||
#thePtr* {.importc: "ptr".}: pointer
|
||||
fd* {.importc: "fd".}: cint # \
|
||||
#u32*: uint32
|
||||
#u64*: uint64
|
||||
|
||||
|
|
@ -54,7 +56,7 @@ proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1",
|
|||
## Same as epoll_create but with an FLAGS parameter. The unused SIZE
|
||||
## parameter has been dropped.
|
||||
|
||||
proc epoll_ctl*(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint {.
|
||||
proc epoll_ctl*(epfd: cint; op: cint; fd: cint | TSocketHandle; event: ptr epoll_event): cint {.
|
||||
importc: "epoll_ctl", header: "<sys/epoll.h>".}
|
||||
## Manipulate an epoll instance "epfd". Returns 0 in case of success,
|
||||
## -1 in case of error ( the "errno" variable will contain the
|
||||
|
|
|
|||
25
lib/posix/linux.nim
Normal file
25
lib/posix/linux.nim
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import posix
|
||||
|
||||
const
|
||||
CSIGNAL* = 0x000000FF
|
||||
CLONE_VM* = 0x00000100
|
||||
CLONE_FS* = 0x00000200
|
||||
CLONE_FILES* = 0x00000400
|
||||
CLONE_SIGHAND* = 0x00000800
|
||||
CLONE_PTRACE* = 0x00002000
|
||||
CLONE_VFORK* = 0x00004000
|
||||
CLONE_PARENT* = 0x00008000
|
||||
CLONE_THREAD* = 0x00010000
|
||||
CLONE_NEWNS* = 0x00020000
|
||||
CLONE_SYSVSEM* = 0x00040000
|
||||
CLONE_SETTLS* = 0x00080000
|
||||
CLONE_PARENT_SETTID* = 0x00100000
|
||||
CLONE_CHILD_CLEARTID* = 0x00200000
|
||||
CLONE_DETACHED* = 0x00400000
|
||||
CLONE_UNTRACED* = 0x00800000
|
||||
CLONE_CHILD_SETTID* = 0x01000000
|
||||
CLONE_STOPPED* = 0x02000000
|
||||
|
||||
# fn should be of type proc (a2: pointer): void {.cdecl.}
|
||||
proc clone*(fn: pointer; child_stack: pointer; flags: cint;
|
||||
arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "<sched.h>".}
|
||||
|
|
@ -2066,6 +2066,7 @@ proc pthread_spin_unlock*(a1: ptr Tpthread_spinlock): cint {.
|
|||
proc pthread_testcancel*() {.importc, header: "<pthread.h>".}
|
||||
|
||||
|
||||
proc exitnow*(code: int): void {.importc: "_exit", header: "<unistd.h>".}
|
||||
proc access*(a1: cstring, a2: cint): cint {.importc, header: "<unistd.h>".}
|
||||
proc alarm*(a1: cint): cint {.importc, header: "<unistd.h>".}
|
||||
proc chdir*(a1: cstring): cint {.importc, header: "<unistd.h>".}
|
||||
|
|
@ -2265,6 +2266,7 @@ proc gmtime_r*(a1: var TTime, a2: var Ttm): ptr Ttm {.importc, header: "<time.h>
|
|||
proc localtime*(a1: var TTime): ptr Ttm {.importc, header: "<time.h>".}
|
||||
proc localtime_r*(a1: var TTime, a2: var Ttm): ptr Ttm {.importc, header: "<time.h>".}
|
||||
proc mktime*(a1: var Ttm): TTime {.importc, header: "<time.h>".}
|
||||
proc timegm*(a1: var Ttm): TTime {.importc, header: "<time.h>".}
|
||||
proc nanosleep*(a1, a2: var Ttimespec): cint {.importc, header: "<time.h>".}
|
||||
proc strftime*(a1: cstring, a2: int, a3: cstring,
|
||||
a4: var Ttm): int {.importc, header: "<time.h>".}
|
||||
|
|
@ -2356,7 +2358,7 @@ proc FD_ZERO*(a1: var TFdSet) {.importc, header: "<sys/select.h>".}
|
|||
|
||||
proc pselect*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimespec,
|
||||
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>".}
|
||||
|
||||
when hasSpawnH:
|
||||
|
|
|
|||
|
|
@ -55,6 +55,36 @@ proc smartBinarySearch*[T](a: openArray[T], key: T): int =
|
|||
const
|
||||
onlySafeCode = true
|
||||
|
||||
proc lowerBound*[T](a: openarray[T], key: T, cmp: proc(x,y: T): int {.closure.}): int =
|
||||
## same as binarySearch except that if key is not in `a` then this
|
||||
## returns the location where `key` would be if it were. In other
|
||||
## words if you have a sorted sequence and you call insert(thing, elm, lowerBound(thing, elm))
|
||||
## the sequence will still be sorted
|
||||
##
|
||||
## `cmp` is the comparator function to use, the expected return values are the same as
|
||||
## that of system.cmp
|
||||
##
|
||||
## example::
|
||||
##
|
||||
## var arr = @[1,2,3,5,6,7,8,9]
|
||||
## arr.insert(4, arr.lowerBound(4))
|
||||
## `after running the above arr is `[1,2,3,4,5,6,7,8,9]`
|
||||
result = a.low
|
||||
var pos = result
|
||||
var count, step: int
|
||||
count = a.high - a.low + 1
|
||||
while count != 0:
|
||||
pos = result
|
||||
step = count div 2
|
||||
pos += step
|
||||
if cmp(a[pos], key) < 0:
|
||||
pos.inc
|
||||
result = pos
|
||||
count -= step + 1
|
||||
else:
|
||||
count = step
|
||||
|
||||
proc lowerBound*[T](a: openarray[T], key: T): int = lowerBound(a, key, cmp[T])
|
||||
proc merge[T](a, b: var openArray[T], lo, m, hi: int,
|
||||
cmp: proc (x, y: T): int {.closure.}, order: TSortOrder) =
|
||||
template `<-` (a, b: expr) =
|
||||
|
|
|
|||
956
lib/pure/asyncdispatch.nim
Normal file
956
lib/pure/asyncdispatch.nim
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import os, oids, tables, strutils, macros
|
||||
|
||||
import rawsockets
|
||||
|
||||
## AsyncDispatch
|
||||
## --------
|
||||
##
|
||||
## This module implements a brand new dispatcher based on Futures.
|
||||
## On Windows IOCP is used and on other operating systems the selectors module
|
||||
## is used instead.
|
||||
|
||||
# -- Futures
|
||||
|
||||
type
|
||||
PFutureBase* = ref object of PObject
|
||||
cb: proc () {.closure.}
|
||||
finished: bool
|
||||
|
||||
PFuture*[T] = ref object of PFutureBase
|
||||
value: T
|
||||
error*: ref EBase # TODO: This shouldn't be necessary, generics bug?
|
||||
|
||||
proc newFuture*[T](): PFuture[T] =
|
||||
## Creates a new future.
|
||||
new(result)
|
||||
result.finished = false
|
||||
|
||||
proc complete*[T](future: PFuture[T], val: T) =
|
||||
## Completes ``future`` with value ``val``.
|
||||
assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||
assert(future.error == nil)
|
||||
future.value = val
|
||||
future.finished = true
|
||||
if future.cb != nil:
|
||||
future.cb()
|
||||
|
||||
proc complete*(future: PFuture[void]) =
|
||||
## Completes a void ``future``.
|
||||
assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||
assert(future.error == nil)
|
||||
future.finished = true
|
||||
if future.cb != nil:
|
||||
future.cb()
|
||||
|
||||
proc fail*[T](future: PFuture[T], error: ref EBase) =
|
||||
## Completes ``future`` with ``error``.
|
||||
assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||
future.finished = true
|
||||
future.error = error
|
||||
if future.cb != nil:
|
||||
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],
|
||||
cb: proc (future: PFuture[T]) {.closure.}) =
|
||||
## Sets the callback proc to be called when the future completes.
|
||||
##
|
||||
## If future has already completed then ``cb`` will be called immediately.
|
||||
future.callback = proc () = cb(future)
|
||||
|
||||
proc read*[T](future: PFuture[T]): T =
|
||||
## Retrieves the value of ``future``. Future must be finished otherwise
|
||||
## this function will fail with a ``EInvalidValue`` exception.
|
||||
##
|
||||
## If the result of the future is an error then that error will be raised.
|
||||
if future.finished:
|
||||
if future.error != nil: raise future.error
|
||||
when T isnot void:
|
||||
return future.value
|
||||
else:
|
||||
# TODO: Make a custom exception type for this?
|
||||
raise newException(EInvalidValue, "Future still in progress.")
|
||||
|
||||
proc readError*[T](future: PFuture[T]): ref EBase =
|
||||
if future.error != nil: return future.error
|
||||
else:
|
||||
raise newException(EInvalidValue, "No error in future.")
|
||||
|
||||
proc finished*[T](future: PFuture[T]): bool =
|
||||
## Determines whether ``future`` has completed.
|
||||
##
|
||||
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
|
||||
future.finished
|
||||
|
||||
proc failed*[T](future: PFuture[T]): bool =
|
||||
## Determines whether ``future`` completed with an error.
|
||||
future.error != nil
|
||||
|
||||
when defined(windows) or defined(nimdoc):
|
||||
import winlean, sets, hashes
|
||||
type
|
||||
TCompletionKey = dword
|
||||
|
||||
TCompletionData* = object
|
||||
sock: TAsyncFD
|
||||
cb: proc (sock: TAsyncFD, bytesTransferred: DWORD,
|
||||
errcode: TOSErrorCode) {.closure.}
|
||||
|
||||
PDispatcher* = ref object
|
||||
ioPort: THandle
|
||||
handles: TSet[TAsyncFD]
|
||||
|
||||
TCustomOverlapped = object
|
||||
Internal*: DWORD
|
||||
InternalHigh*: DWORD
|
||||
Offset*: DWORD
|
||||
OffsetHigh*: DWORD
|
||||
hEvent*: THANDLE
|
||||
data*: TCompletionData
|
||||
|
||||
PCustomOverlapped = ptr TCustomOverlapped
|
||||
|
||||
TAsyncFD* = distinct int
|
||||
|
||||
proc hash(x: TAsyncFD): THash {.borrow.}
|
||||
proc `==`*(x: TAsyncFD, y: TAsyncFD): bool {.borrow.}
|
||||
|
||||
proc newDispatcher*(): PDispatcher =
|
||||
## Creates a new Dispatcher instance.
|
||||
new result
|
||||
result.ioPort = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
|
||||
result.handles = initSet[TAsyncFD]()
|
||||
|
||||
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
|
||||
proc getGlobalDispatcher*(): PDispatcher =
|
||||
## Retrieves the global thread-local dispatcher.
|
||||
if gDisp.isNil: gDisp = newDispatcher()
|
||||
result = gDisp
|
||||
|
||||
proc register*(sock: TAsyncFD) =
|
||||
## Registers ``sock`` with the dispatcher.
|
||||
let p = getGlobalDispatcher()
|
||||
if CreateIOCompletionPort(sock.THandle, p.ioPort,
|
||||
cast[TCompletionKey](sock), 1) == 0:
|
||||
OSError(OSLastError())
|
||||
p.handles.incl(sock)
|
||||
|
||||
proc verifyPresence(sock: TAsyncFD) =
|
||||
## Ensures that socket has been registered with the dispatcher.
|
||||
let p = getGlobalDispatcher()
|
||||
if sock notin p.handles:
|
||||
raise newException(EInvalidValue,
|
||||
"Operation performed on a socket which has not been registered with" &
|
||||
" the dispatcher yet.")
|
||||
|
||||
proc poll*(timeout = 500) =
|
||||
## Waits for completion events and processes them.
|
||||
let p = getGlobalDispatcher()
|
||||
if p.handles.len == 0:
|
||||
raise newException(EInvalidValue, "No handles registered in dispatcher.")
|
||||
|
||||
let llTimeout =
|
||||
if timeout == -1: winlean.INFINITE
|
||||
else: timeout.int32
|
||||
var lpNumberOfBytesTransferred: DWORD
|
||||
var lpCompletionKey: ULONG
|
||||
var lpOverlapped: POverlapped
|
||||
let res = GetQueuedCompletionStatus(p.ioPort, addr lpNumberOfBytesTransferred,
|
||||
addr lpCompletionKey, addr lpOverlapped, llTimeout).bool
|
||||
|
||||
# http://stackoverflow.com/a/12277264/492186
|
||||
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
|
||||
var customOverlapped = cast[PCustomOverlapped](lpOverlapped)
|
||||
if res:
|
||||
# This is useful for ensuring the reliability of the overlapped struct.
|
||||
assert customOverlapped.data.sock == lpCompletionKey.TAsyncFD
|
||||
|
||||
customOverlapped.data.cb(customOverlapped.data.sock,
|
||||
lpNumberOfBytesTransferred, TOSErrorCode(-1))
|
||||
dealloc(customOverlapped)
|
||||
else:
|
||||
let errCode = OSLastError()
|
||||
if lpOverlapped != nil:
|
||||
assert customOverlapped.data.sock == lpCompletionKey.TAsyncFD
|
||||
customOverlapped.data.cb(customOverlapped.data.sock,
|
||||
lpNumberOfBytesTransferred, errCode)
|
||||
dealloc(customOverlapped)
|
||||
else:
|
||||
if errCode.int32 == WAIT_TIMEOUT:
|
||||
# Timed out
|
||||
discard
|
||||
else: OSError(errCode)
|
||||
|
||||
var connectExPtr: pointer = nil
|
||||
var acceptExPtr: pointer = nil
|
||||
var getAcceptExSockAddrsPtr: pointer = nil
|
||||
|
||||
proc initPointer(s: TSocketHandle, func: var pointer, guid: var TGUID): bool =
|
||||
# Ref: https://github.com/powdahound/twisted/blob/master/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c
|
||||
var bytesRet: DWord
|
||||
func = nil
|
||||
result = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, addr guid,
|
||||
sizeof(TGUID).dword, addr func, sizeof(pointer).DWORD,
|
||||
addr bytesRet, nil, nil) == 0
|
||||
|
||||
proc initAll() =
|
||||
let dummySock = newRawSocket()
|
||||
if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX):
|
||||
OSError(OSLastError())
|
||||
if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX):
|
||||
OSError(OSLastError())
|
||||
if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS):
|
||||
OSError(OSLastError())
|
||||
|
||||
proc connectEx(s: TSocketHandle, name: ptr TSockAddr, namelen: cint,
|
||||
lpSendBuffer: pointer, dwSendDataLength: dword,
|
||||
lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool =
|
||||
if connectExPtr.isNil: raise newException(EInvalidValue, "Need to initialise ConnectEx().")
|
||||
let func =
|
||||
cast[proc (s: TSocketHandle, name: ptr TSockAddr, namelen: cint,
|
||||
lpSendBuffer: pointer, dwSendDataLength: dword,
|
||||
lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall.}](connectExPtr)
|
||||
|
||||
result = func(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent,
|
||||
lpOverlapped)
|
||||
|
||||
proc acceptEx(listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD,
|
||||
lpOverlapped: POverlapped): bool =
|
||||
if acceptExPtr.isNil: raise newException(EInvalidValue, "Need to initialise AcceptEx().")
|
||||
let func =
|
||||
cast[proc (listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD,
|
||||
lpOverlapped: POverlapped): bool {.stdcall.}](acceptExPtr)
|
||||
result = func(listenSock, acceptSock, lpOutputBuffer, dwReceiveDataLength,
|
||||
dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived,
|
||||
lpOverlapped)
|
||||
|
||||
proc getAcceptExSockaddrs(lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD,
|
||||
LocalSockaddr: ptr ptr TSockAddr, LocalSockaddrLength: lpint,
|
||||
RemoteSockaddr: ptr ptr TSockAddr, RemoteSockaddrLength: lpint) =
|
||||
if getAcceptExSockAddrsPtr.isNil:
|
||||
raise newException(EInvalidValue, "Need to initialise getAcceptExSockAddrs().")
|
||||
|
||||
let func =
|
||||
cast[proc (lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, LocalSockaddr: ptr ptr TSockAddr,
|
||||
LocalSockaddrLength: lpint, RemoteSockaddr: ptr ptr TSockAddr,
|
||||
RemoteSockaddrLength: lpint) {.stdcall.}](getAcceptExSockAddrsPtr)
|
||||
|
||||
func(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
|
||||
RemoteSockaddr, RemoteSockaddrLength)
|
||||
|
||||
proc connect*(socket: TAsyncFD, address: string, port: TPort,
|
||||
af = AF_INET): PFuture[void] =
|
||||
## Connects ``socket`` to server at ``address:port``.
|
||||
##
|
||||
## Returns a ``PFuture`` which will complete when the connection succeeds
|
||||
## or an error occurs.
|
||||
verifyPresence(socket)
|
||||
var retFuture = newFuture[void]()
|
||||
# Apparently ``ConnectEx`` expects the socket to be initially bound:
|
||||
var saddr: Tsockaddr_in
|
||||
saddr.sin_family = int16(toInt(af))
|
||||
saddr.sin_port = 0
|
||||
saddr.sin_addr.s_addr = INADDR_ANY
|
||||
if bindAddr(socket.TSocketHandle, cast[ptr TSockAddr](addr(saddr)),
|
||||
sizeof(saddr).TSockLen) < 0'i32:
|
||||
OSError(OSLastError())
|
||||
|
||||
var aiList = getAddrInfo(address, port, af)
|
||||
var success = false
|
||||
var lastError: TOSErrorCode
|
||||
var it = aiList
|
||||
while it != nil:
|
||||
# "the OVERLAPPED structure must remain valid until the I/O completes"
|
||||
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == TOSErrorCode(-1):
|
||||
retFuture.complete()
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
var ret = connectEx(socket.TSocketHandle, it.ai_addr,
|
||||
sizeof(TSockAddrIn).cint, nil, 0, nil,
|
||||
cast[POverlapped](ol))
|
||||
if ret:
|
||||
# Request to connect completed immediately.
|
||||
success = true
|
||||
retFuture.complete()
|
||||
# We don't deallocate ``ol`` here because even though this completed
|
||||
# immediately poll will still be notified about its completion and it will
|
||||
# free ``ol``.
|
||||
break
|
||||
else:
|
||||
lastError = OSLastError()
|
||||
if lastError.int32 == ERROR_IO_PENDING:
|
||||
# In this case ``ol`` will be deallocated in ``poll``.
|
||||
success = true
|
||||
break
|
||||
else:
|
||||
dealloc(ol)
|
||||
success = false
|
||||
it = it.ai_next
|
||||
|
||||
dealloc(aiList)
|
||||
if not success:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||
return retFuture
|
||||
|
||||
proc recv*(socket: TAsyncFD, size: int,
|
||||
flags: int = 0): PFuture[string] =
|
||||
## Reads ``size`` bytes from ``socket``. Returned future will complete once
|
||||
## 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 ``""``.
|
||||
verifyPresence(socket)
|
||||
var retFuture = newFuture[string]()
|
||||
|
||||
var dataBuf: TWSABuf
|
||||
dataBuf.buf = newString(size)
|
||||
dataBuf.len = size
|
||||
|
||||
var bytesReceived: DWord
|
||||
var flagsio = flags.dword
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == TOSErrorCode(-1):
|
||||
if bytesCount == 0 and dataBuf.buf[0] == '\0':
|
||||
retFuture.complete("")
|
||||
else:
|
||||
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.TSocketHandle, addr dataBuf, 1, addr bytesReceived,
|
||||
addr flagsio, cast[POverlapped](ol), nil)
|
||||
if ret == -1:
|
||||
let err = OSLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
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:
|
||||
# Request to read completed immediately.
|
||||
var data = newString(size)
|
||||
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
||||
retFuture.complete($data)
|
||||
# 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
|
||||
|
||||
proc send*(socket: TAsyncFD, data: string): PFuture[void] =
|
||||
## Sends ``data`` to ``socket``. The returned future will complete once all
|
||||
## data has been sent.
|
||||
verifyPresence(socket)
|
||||
var retFuture = newFuture[void]()
|
||||
|
||||
var dataBuf: TWSABuf
|
||||
dataBuf.buf = data
|
||||
dataBuf.len = data.len
|
||||
|
||||
var bytesReceived, flags: DWord
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == TOSErrorCode(-1):
|
||||
retFuture.complete()
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
let ret = WSASend(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived,
|
||||
flags, cast[POverlapped](ol), nil)
|
||||
if ret == -1:
|
||||
let err = osLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
dealloc(ol)
|
||||
else:
|
||||
retFuture.complete()
|
||||
# We don't deallocate ``ol`` here because even though this completed
|
||||
# immediately poll will still be notified about its completion and it will
|
||||
# free ``ol``.
|
||||
return retFuture
|
||||
|
||||
proc acceptAddr*(socket: TAsyncFD):
|
||||
PFuture[tuple[address: string, client: TAsyncFD]] =
|
||||
## Accepts a new connection. Returns a future containing the client socket
|
||||
## corresponding to that connection and the remote address of the client.
|
||||
## The future will complete when the connection is successfully accepted.
|
||||
##
|
||||
## The resulting client socket is automatically registered to dispatcher.
|
||||
verifyPresence(socket)
|
||||
var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]()
|
||||
|
||||
var clientSock = newRawSocket()
|
||||
if clientSock == OSInvalidSocket: osError(osLastError())
|
||||
|
||||
const lpOutputLen = 1024
|
||||
var lpOutputBuf = newString(lpOutputLen)
|
||||
var dwBytesReceived: DWORD
|
||||
let dwReceiveDataLength = 0.DWORD # We don't want any data to be read.
|
||||
let dwLocalAddressLength = DWORD(sizeof (TSockaddr_in) + 16)
|
||||
let dwRemoteAddressLength = DWORD(sizeof(TSockaddr_in) + 16)
|
||||
|
||||
template completeAccept(): stmt {.immediate, dirty.} =
|
||||
var listenSock = socket
|
||||
let setoptRet = setsockopt(clientSock, SOL_SOCKET,
|
||||
SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
|
||||
sizeof(listenSock).TSockLen)
|
||||
if setoptRet != 0: osError(osLastError())
|
||||
|
||||
var LocalSockaddr, RemoteSockaddr: ptr TSockAddr
|
||||
var localLen, remoteLen: int32
|
||||
getAcceptExSockaddrs(addr lpOutputBuf[0], dwReceiveDataLength,
|
||||
dwLocalAddressLength, dwRemoteAddressLength,
|
||||
addr LocalSockaddr, addr localLen,
|
||||
addr RemoteSockaddr, addr remoteLen)
|
||||
register(clientSock.TAsyncFD)
|
||||
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
|
||||
retFuture.complete(
|
||||
(address: $inet_ntoa(cast[ptr Tsockaddr_in](remoteSockAddr).sin_addr),
|
||||
client: clientSock.TAsyncFD)
|
||||
)
|
||||
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == TOSErrorCode(-1):
|
||||
completeAccept()
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
|
||||
let ret = acceptEx(socket.TSocketHandle, clientSock, addr lpOutputBuf[0],
|
||||
dwReceiveDataLength,
|
||||
dwLocalAddressLength,
|
||||
dwRemoteAddressLength,
|
||||
addr dwBytesReceived, cast[POverlapped](ol))
|
||||
|
||||
if not ret:
|
||||
let err = osLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
dealloc(ol)
|
||||
else:
|
||||
completeAccept()
|
||||
# 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
|
||||
|
||||
proc newAsyncRawSocket*(domain: TDomain = AF_INET,
|
||||
typ: TType = SOCK_STREAM,
|
||||
protocol: TProtocol = IPPROTO_TCP): TAsyncFD =
|
||||
## Creates a new socket and registers it with the dispatcher implicitly.
|
||||
result = newRawSocket(domain, typ, protocol).TAsyncFD
|
||||
result.TSocketHandle.setBlocking(false)
|
||||
register(result)
|
||||
|
||||
proc close*(socket: TAsyncFD) =
|
||||
## Closes a socket and ensures that it is unregistered.
|
||||
socket.TSocketHandle.close()
|
||||
getGlobalDispatcher().handles.excl(socket)
|
||||
|
||||
initAll()
|
||||
else:
|
||||
import selectors
|
||||
from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK
|
||||
type
|
||||
TAsyncFD* = distinct cint
|
||||
TCallback = proc (sock: TAsyncFD): bool {.closure.}
|
||||
|
||||
PData* = ref object of PObject
|
||||
sock: TAsyncFD
|
||||
readCBs: seq[TCallback]
|
||||
writeCBs: seq[TCallback]
|
||||
|
||||
PDispatcher* = ref object
|
||||
selector: PSelector
|
||||
|
||||
proc `==`*(x, y: TAsyncFD): bool {.borrow.}
|
||||
|
||||
proc newDispatcher*(): PDispatcher =
|
||||
new result
|
||||
result.selector = newSelector()
|
||||
|
||||
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
|
||||
proc getGlobalDispatcher*(): PDispatcher =
|
||||
if gDisp.isNil: gDisp = newDispatcher()
|
||||
result = gDisp
|
||||
|
||||
proc update(sock: TAsyncFD, events: set[TEvent]) =
|
||||
let p = getGlobalDispatcher()
|
||||
assert sock.TSocketHandle in p.selector
|
||||
discard p.selector.update(sock.TSocketHandle, events)
|
||||
|
||||
proc register(sock: TAsyncFD) =
|
||||
let p = getGlobalDispatcher()
|
||||
var data = PData(sock: sock, readCBs: @[], writeCBs: @[])
|
||||
p.selector.register(sock.TSocketHandle, {}, data.PObject)
|
||||
|
||||
proc newAsyncRawSocket*(domain: TDomain = AF_INET,
|
||||
typ: TType = SOCK_STREAM,
|
||||
protocol: TProtocol = IPPROTO_TCP): TAsyncFD =
|
||||
result = newRawSocket(domain, typ, protocol).TAsyncFD
|
||||
result.TSocketHandle.setBlocking(false)
|
||||
register(result)
|
||||
|
||||
proc close*(sock: TAsyncFD) =
|
||||
let disp = getGlobalDispatcher()
|
||||
sock.TSocketHandle.close()
|
||||
disp.selector.unregister(sock.TSocketHandle)
|
||||
|
||||
proc addRead(sock: TAsyncFD, cb: TCallback) =
|
||||
let p = getGlobalDispatcher()
|
||||
if sock.TSocketHandle notin p.selector:
|
||||
raise newException(EInvalidValue, "File descriptor not registered.")
|
||||
p.selector[sock.TSocketHandle].data.PData.readCBs.add(cb)
|
||||
update(sock, p.selector[sock.TSocketHandle].events + {EvRead})
|
||||
|
||||
proc addWrite(sock: TAsyncFD, cb: TCallback) =
|
||||
let p = getGlobalDispatcher()
|
||||
if sock.TSocketHandle notin p.selector:
|
||||
raise newException(EInvalidValue, "File descriptor not registered.")
|
||||
p.selector[sock.TSocketHandle].data.PData.writeCBs.add(cb)
|
||||
update(sock, p.selector[sock.TSocketHandle].events + {EvWrite})
|
||||
|
||||
proc poll*(timeout = 500) =
|
||||
let p = getGlobalDispatcher()
|
||||
for info in p.selector.select(timeout):
|
||||
let data = PData(info.key.data)
|
||||
assert data.sock == info.key.fd.TAsyncFD
|
||||
#echo("In poll ", data.sock.cint)
|
||||
if EvRead in info.events:
|
||||
# Callback may add items to ``data.readCBs`` which causes issues if
|
||||
# we are iterating over ``data.readCBs`` at the same time. We therefore
|
||||
# make a copy to iterate over.
|
||||
let currentCBs = data.readCBs
|
||||
data.readCBs = @[]
|
||||
for cb in currentCBs:
|
||||
if not cb(data.sock):
|
||||
# Callback wants to be called again.
|
||||
data.readCBs.add(cb)
|
||||
|
||||
if EvWrite in info.events:
|
||||
let currentCBs = data.writeCBs
|
||||
data.writeCBs = @[]
|
||||
for cb in currentCBs:
|
||||
if not cb(data.sock):
|
||||
# Callback wants to be called again.
|
||||
data.writeCBs.add(cb)
|
||||
|
||||
if info.key in p.selector:
|
||||
var newEvents: set[TEvent]
|
||||
if data.readCBs.len != 0: newEvents = {EvRead}
|
||||
if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite}
|
||||
if newEvents != info.key.events:
|
||||
update(data.sock, newEvents)
|
||||
else:
|
||||
# FD no longer a part of the selector. Likely been closed
|
||||
# (e.g. socket disconnected).
|
||||
|
||||
proc connect*(socket: TAsyncFD, address: string, port: TPort,
|
||||
af = AF_INET): PFuture[void] =
|
||||
var retFuture = newFuture[void]()
|
||||
|
||||
proc cb(sock: TAsyncFD): bool =
|
||||
# We have connected.
|
||||
retFuture.complete()
|
||||
return true
|
||||
|
||||
var aiList = getAddrInfo(address, port, af)
|
||||
var success = false
|
||||
var lastError: TOSErrorCode
|
||||
var it = aiList
|
||||
while it != nil:
|
||||
var ret = connect(socket.TSocketHandle, it.ai_addr, it.ai_addrlen.TSocklen)
|
||||
if ret == 0:
|
||||
# Request to connect completed immediately.
|
||||
success = true
|
||||
retFuture.complete()
|
||||
break
|
||||
else:
|
||||
lastError = osLastError()
|
||||
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
|
||||
success = true
|
||||
addWrite(socket, cb)
|
||||
break
|
||||
else:
|
||||
success = false
|
||||
it = it.ai_next
|
||||
|
||||
dealloc(aiList)
|
||||
if not success:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
|
||||
return retFuture
|
||||
|
||||
proc recv*(socket: TAsyncFD, size: int,
|
||||
flags: int = 0): PFuture[string] =
|
||||
var retFuture = newFuture[string]()
|
||||
|
||||
var readBuffer = newString(size)
|
||||
var sizeRead = 0
|
||||
|
||||
proc cb(sock: TAsyncFD): bool =
|
||||
result = true
|
||||
let netSize = size - sizeRead
|
||||
let res = recv(sock.TSocketHandle, addr readBuffer[sizeRead], netSize,
|
||||
flags.cint)
|
||||
#echo("recv cb res: ", res)
|
||||
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:
|
||||
#echo("Disconnected recv: ", sizeRead)
|
||||
# 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)
|
||||
#echo("Recv cb result: ", result)
|
||||
|
||||
addRead(socket, cb)
|
||||
return retFuture
|
||||
|
||||
proc send*(socket: TAsyncFD, data: string): PFuture[void] =
|
||||
var retFuture = newFuture[void]()
|
||||
|
||||
var written = 0
|
||||
|
||||
proc cb(sock: TAsyncFD): bool =
|
||||
result = true
|
||||
let netSize = data.len-written
|
||||
var d = data.cstring
|
||||
let res = send(sock.TSocketHandle, 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()
|
||||
addWrite(socket, cb)
|
||||
return retFuture
|
||||
|
||||
proc acceptAddr*(socket: TAsyncFD):
|
||||
PFuture[tuple[address: string, client: TAsyncFD]] =
|
||||
var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]()
|
||||
proc cb(sock: TAsyncFD): bool =
|
||||
result = true
|
||||
var sockAddress: Tsockaddr_in
|
||||
var addrLen = sizeof(sockAddress).TSocklen
|
||||
var client = accept(sock.TSocketHandle,
|
||||
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:
|
||||
register(client.TAsyncFD)
|
||||
retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client.TAsyncFD))
|
||||
addRead(socket, cb)
|
||||
return retFuture
|
||||
|
||||
proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] =
|
||||
## 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[TAsyncFD]()
|
||||
var fut = acceptAddr(socket)
|
||||
fut.callback =
|
||||
proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) =
|
||||
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 = 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].kind == nnkIdent and 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].toStrLit, node[1][1], 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].toStrLit, node[1][1], newAsgn[1])
|
||||
result.add newAsgn
|
||||
else: discard
|
||||
of nnkDiscardStmt:
|
||||
# discard await x
|
||||
if node[0][0].kind == nnkIdent and 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.} =
|
||||
## Macro which processes async procedures into the appropriate
|
||||
## iterators and yield statements.
|
||||
|
||||
expectKind(prc, nnkProcDef)
|
||||
|
||||
hint("Processing " & prc[0].getName & " as an async proc.")
|
||||
|
||||
let returnType = prc[3][0]
|
||||
var subtypeName = ""
|
||||
# Verify that the return type is a PFuture[T]
|
||||
if returnType.kind == nnkIdent:
|
||||
error("Expected return type of 'PFuture' got '" & $returnType & "'")
|
||||
elif returnType.kind == nnkBracketExpr:
|
||||
if $returnType[0] != "PFuture":
|
||||
error("Expected return type of 'PFuture' got '" & $returnType[0] & "'")
|
||||
subtypeName = $returnType[1].ident
|
||||
elif returnType.kind == nnkEmpty:
|
||||
subtypeName = "void"
|
||||
|
||||
var outerProcBody = newNimNode(nnkStmtList)
|
||||
|
||||
# -> var retFuture = newFuture[T]()
|
||||
var retFutureSym = genSym(nskVar, "retFuture")
|
||||
outerProcBody.add(
|
||||
newVarStmt(retFutureSym,
|
||||
newCall(
|
||||
newNimNode(nnkBracketExpr).add(
|
||||
newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
|
||||
newIdentNode(subtypeName))))) # Get type from return type of this proc
|
||||
|
||||
# -> iterator nameIter(): PFutureBase {.closure.} =
|
||||
# -> var result: T
|
||||
# -> <proc_body>
|
||||
# -> complete(retFuture, result)
|
||||
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
|
||||
var procBody = prc[6].processBody(retFutureSym)
|
||||
if subtypeName != "void":
|
||||
procBody.insert(0, newNimNode(nnkVarSection).add(
|
||||
newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T
|
||||
procBody.add(
|
||||
newCall(newIdentNode("complete"),
|
||||
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
|
||||
else:
|
||||
# -> complete(retFuture)
|
||||
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
|
||||
|
||||
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 = genSym(nskVar, $prc[0].getName & "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)
|
||||
if subtypeName == "void":
|
||||
# Add discardable pragma.
|
||||
result[4].add(newIdentNode("discardable"))
|
||||
if returnType.kind == nnkEmpty:
|
||||
# Add PFuture[void]
|
||||
result[3][0] = parseExpr("PFuture[void]")
|
||||
|
||||
result[6] = outerProcBody
|
||||
|
||||
echo(toStrLit(result))
|
||||
|
||||
proc recvLine*(socket: TAsyncFD): 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 ``""``.
|
||||
##
|
||||
## If the socket is disconnected in the middle of a line (before ``\r\L``
|
||||
## is read) then line will be set to ``""``.
|
||||
## The partial line **will be lost**.
|
||||
|
||||
template addNLIfEmpty(): stmt =
|
||||
if result.len == 0:
|
||||
result.add("\c\L")
|
||||
|
||||
result = ""
|
||||
var c = ""
|
||||
while true:
|
||||
c = await recv(socket, 1)
|
||||
if c.len == 0:
|
||||
return ""
|
||||
if c == "\r":
|
||||
c = await recv(socket, 1, MSG_PEEK)
|
||||
if c.len > 0 and c == "\L":
|
||||
discard await recv(socket, 1)
|
||||
addNLIfEmpty()
|
||||
return
|
||||
elif c == "\L":
|
||||
addNLIfEmpty()
|
||||
return
|
||||
add(result, c)
|
||||
|
||||
proc runForever*() =
|
||||
## Begins a never ending global dispatcher poll loop.
|
||||
while true:
|
||||
poll()
|
||||
3
lib/pure/asyncdispatch.nimrod.cfg
Normal file
3
lib/pure/asyncdispatch.nimrod.cfg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@if nimdoc:
|
||||
--os:linux
|
||||
@end
|
||||
|
|
@ -167,7 +167,7 @@ proc asyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
|||
result = newAsyncSocket()
|
||||
result.socket = socket(domain, typ, protocol, buffered)
|
||||
result.proto = protocol
|
||||
if result.socket == InvalidSocket: OSError(OSLastError())
|
||||
if result.socket == invalidSocket: osError(osLastError())
|
||||
result.socket.setBlocking(false)
|
||||
|
||||
proc toAsyncSocket*(sock: TSocket, state: TInfo = SockConnected): PAsyncSocket =
|
||||
|
|
@ -357,7 +357,7 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
|||
client.sslNeedAccept = false
|
||||
client.info = SockConnected
|
||||
|
||||
if c == InvalidSocket: SocketError(server.socket)
|
||||
if c == invalidSocket: socketError(server.socket)
|
||||
c.setBlocking(false) # TODO: Needs to be tested.
|
||||
|
||||
# deleg.open is set in ``toDelegate``.
|
||||
|
|
@ -481,7 +481,7 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool {.deprecated.} =
|
|||
of RecvDisconnected:
|
||||
result = true
|
||||
of RecvFail:
|
||||
s.SocketError(async = true)
|
||||
s.socketError(async = true)
|
||||
result = false
|
||||
{.pop.}
|
||||
|
||||
|
|
@ -615,11 +615,11 @@ proc poll*(d: PDispatcher, timeout: int = 500): bool =
|
|||
if d.hasDataBuffered(d.deleVal):
|
||||
hasDataBufferedCount.inc()
|
||||
d.handleRead(d.deleVal)
|
||||
if hasDataBufferedCount > 0: return True
|
||||
if hasDataBufferedCount > 0: return true
|
||||
|
||||
if readDg.len() == 0 and writeDg.len() == 0:
|
||||
## TODO: Perhaps this shouldn't return if errorDg has something?
|
||||
return False
|
||||
return false
|
||||
|
||||
if select(readDg, writeDg, errorDg, timeout) != 0:
|
||||
for i in 0..len(d.delegates)-1:
|
||||
|
|
|
|||
|
|
@ -1,485 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import os, oids, tables, strutils
|
||||
|
||||
import winlean
|
||||
|
||||
import sockets2, net
|
||||
|
||||
## Asyncio2
|
||||
## --------
|
||||
##
|
||||
## This module implements a brand new asyncio module based on Futures.
|
||||
## IOCP is used under the hood on Windows and the selectors module is used for
|
||||
## other operating systems.
|
||||
|
||||
# -- Futures
|
||||
|
||||
type
|
||||
PFutureVoid* = ref object of PObject
|
||||
cbVoid: proc () {.closure.}
|
||||
finished: bool
|
||||
|
||||
PFuture*[T] = ref object of PFutureVoid
|
||||
value: T
|
||||
error: ref EBase
|
||||
cb: proc (future: PFuture[T]) {.closure.}
|
||||
|
||||
proc newFuture*[T](): PFuture[T] =
|
||||
## Creates a new future.
|
||||
new(result)
|
||||
result.finished = false
|
||||
|
||||
proc complete*[T](future: PFuture[T], val: T) =
|
||||
## Completes ``future`` with value ``val``.
|
||||
assert(not future.finished)
|
||||
assert(future.error == nil)
|
||||
future.value = val
|
||||
future.finished = true
|
||||
if future.cb != nil:
|
||||
future.cb(future)
|
||||
if future.cbVoid != nil:
|
||||
future.cbVoid()
|
||||
|
||||
proc fail*[T](future: PFuture[T], error: ref EBase) =
|
||||
## Completes ``future`` with ``error``.
|
||||
assert(not future.finished)
|
||||
future.finished = true
|
||||
future.error = error
|
||||
if future.cb != nil:
|
||||
future.cb(future)
|
||||
|
||||
proc `callback=`*[T](future: PFuture[T],
|
||||
cb: proc (future: PFuture[T]) {.closure.}) =
|
||||
## Sets the callback proc to be called when the future completes.
|
||||
##
|
||||
## If future has already completed then ``cb`` will be called immediately.
|
||||
future.cb = cb
|
||||
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 =
|
||||
## Retrieves the value of ``future``. Future must be finished otherwise
|
||||
## this function will fail with a ``EInvalidValue`` exception.
|
||||
##
|
||||
## If the result of the future is an error then that error will be raised.
|
||||
if future.finished:
|
||||
if future.error != nil: raise future.error
|
||||
return future.value
|
||||
else:
|
||||
# TODO: Make a custom exception type for this?
|
||||
raise newException(EInvalidValue, "Future still in progress.")
|
||||
|
||||
proc finished*[T](future: PFuture[T]): bool =
|
||||
## Determines whether ``future`` has completed.
|
||||
##
|
||||
## ``True`` may indicate an error or a value. Use ``hasError`` to distinguish.
|
||||
future.finished
|
||||
|
||||
proc failed*[T](future: PFuture[T]): bool =
|
||||
## Determines whether ``future`` completed with an error.
|
||||
future.error != nil
|
||||
|
||||
when defined(windows):
|
||||
type
|
||||
TCompletionKey = dword
|
||||
|
||||
TCompletionData* = object
|
||||
sock: TSocketHandle
|
||||
cb: proc (sock: TSocketHandle, errcode: TOSErrorCode) {.closure.}
|
||||
|
||||
PDispatcher* = ref object
|
||||
ioPort: THandle
|
||||
|
||||
TCustomOverlapped = object
|
||||
Internal*: DWORD
|
||||
InternalHigh*: DWORD
|
||||
Offset*: DWORD
|
||||
OffsetHigh*: DWORD
|
||||
hEvent*: THANDLE
|
||||
data*: TCompletionData
|
||||
|
||||
PCustomOverlapped = ptr TCustomOverlapped
|
||||
|
||||
proc newDispatcher*(): PDispatcher =
|
||||
## Creates a new Dispatcher instance.
|
||||
new result
|
||||
result.ioPort = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
|
||||
|
||||
proc register*(p: PDispatcher, sock: TSocketHandle) =
|
||||
## Registers ``sock`` with the dispatcher ``p``.
|
||||
if CreateIOCompletionPort(sock.THandle, p.ioPort,
|
||||
cast[TCompletionKey](sock), 1) == 0:
|
||||
OSError(OSLastError())
|
||||
|
||||
proc poll*(p: PDispatcher, timeout = 500) =
|
||||
## Waits for completion events and processes them.
|
||||
let llTimeout =
|
||||
if timeout == -1: winlean.INFINITE
|
||||
else: timeout.int32
|
||||
var lpNumberOfBytesTransferred: DWORD
|
||||
var lpCompletionKey: ULONG
|
||||
var lpOverlapped: POverlapped
|
||||
let res = GetQueuedCompletionStatus(p.ioPort, addr lpNumberOfBytesTransferred,
|
||||
addr lpCompletionKey, addr lpOverlapped, llTimeout).bool
|
||||
|
||||
# http://stackoverflow.com/a/12277264/492186
|
||||
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
|
||||
var customOverlapped = cast[PCustomOverlapped](lpOverlapped)
|
||||
if res:
|
||||
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
||||
|
||||
customOverlapped.data.cb(customOverlapped.data.sock, TOSErrorCode(-1))
|
||||
dealloc(customOverlapped)
|
||||
else:
|
||||
let errCode = OSLastError()
|
||||
if lpOverlapped != nil:
|
||||
assert customOverlapped.data.sock == lpCompletionKey.TSocketHandle
|
||||
dealloc(customOverlapped)
|
||||
customOverlapped.data.cb(customOverlapped.data.sock, errCode)
|
||||
else:
|
||||
if errCode.int32 == WAIT_TIMEOUT:
|
||||
# Timed out
|
||||
discard
|
||||
else: OSError(errCode)
|
||||
|
||||
var connectExPtr: pointer = nil
|
||||
var acceptExPtr: pointer = nil
|
||||
var getAcceptExSockAddrsPtr: pointer = nil
|
||||
|
||||
proc initPointer(s: TSocketHandle, func: var pointer, guid: var TGUID): bool =
|
||||
# Ref: https://github.com/powdahound/twisted/blob/master/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c
|
||||
var bytesRet: DWord
|
||||
func = nil
|
||||
result = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, addr guid,
|
||||
sizeof(TGUID).dword, addr func, sizeof(pointer).DWORD,
|
||||
addr bytesRet, nil, nil) == 0
|
||||
|
||||
proc initAll() =
|
||||
let dummySock = socket()
|
||||
if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX):
|
||||
OSError(OSLastError())
|
||||
if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX):
|
||||
OSError(OSLastError())
|
||||
if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS):
|
||||
OSError(OSLastError())
|
||||
|
||||
proc connectEx(s: TSocketHandle, name: ptr TSockAddr, namelen: cint,
|
||||
lpSendBuffer: pointer, dwSendDataLength: dword,
|
||||
lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool =
|
||||
if connectExPtr.isNil: raise newException(EInvalidValue, "Need to initialise ConnectEx().")
|
||||
let func =
|
||||
cast[proc (s: TSocketHandle, name: ptr TSockAddr, namelen: cint,
|
||||
lpSendBuffer: pointer, dwSendDataLength: dword,
|
||||
lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall.}](connectExPtr)
|
||||
|
||||
result = func(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent,
|
||||
lpOverlapped)
|
||||
|
||||
proc acceptEx(listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD,
|
||||
lpOverlapped: POverlapped): bool =
|
||||
if acceptExPtr.isNil: raise newException(EInvalidValue, "Need to initialise AcceptEx().")
|
||||
let func =
|
||||
cast[proc (listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD,
|
||||
lpOverlapped: POverlapped): bool {.stdcall.}](acceptExPtr)
|
||||
result = func(listenSock, acceptSock, lpOutputBuffer, dwReceiveDataLength,
|
||||
dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived,
|
||||
lpOverlapped)
|
||||
|
||||
proc getAcceptExSockaddrs(lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD,
|
||||
LocalSockaddr: ptr ptr TSockAddr, LocalSockaddrLength: lpint,
|
||||
RemoteSockaddr: ptr ptr TSockAddr, RemoteSockaddrLength: lpint) =
|
||||
if getAcceptExSockAddrsPtr.isNil:
|
||||
raise newException(EInvalidValue, "Need to initialise getAcceptExSockAddrs().")
|
||||
|
||||
let func =
|
||||
cast[proc (lpOutputBuffer: pointer,
|
||||
dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength: DWORD, LocalSockaddr: ptr ptr TSockAddr,
|
||||
LocalSockaddrLength: lpint, RemoteSockaddr: ptr ptr TSockAddr,
|
||||
RemoteSockaddrLength: lpint) {.stdcall.}](getAcceptExSockAddrsPtr)
|
||||
|
||||
func(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,
|
||||
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
|
||||
RemoteSockaddr, RemoteSockaddrLength)
|
||||
|
||||
proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort,
|
||||
af = AF_INET): PFuture[int] =
|
||||
## Connects ``socket`` to server at ``address:port``.
|
||||
##
|
||||
## Returns a ``PFuture`` which will complete when the connection succeeds
|
||||
## or an error occurs.
|
||||
|
||||
var retFuture = newFuture[int]()# TODO: Change to void when that regression is fixed.
|
||||
# Apparently ``ConnectEx`` expects the socket to be initially bound:
|
||||
var saddr: Tsockaddr_in
|
||||
saddr.sin_family = int16(toInt(af))
|
||||
saddr.sin_port = 0
|
||||
saddr.sin_addr.s_addr = INADDR_ANY
|
||||
if bindAddr(socket, cast[ptr TSockAddr](addr(saddr)),
|
||||
sizeof(saddr).TSockLen) < 0'i32:
|
||||
OSError(OSLastError())
|
||||
|
||||
var aiList = getAddrInfo(address, port, af)
|
||||
var success = false
|
||||
var lastError: TOSErrorCode
|
||||
var it = aiList
|
||||
while it != nil:
|
||||
# "the OVERLAPPED structure must remain valid until the I/O completes"
|
||||
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
||||
if errcode == TOSErrorCode(-1):
|
||||
retFuture.complete(0)
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
var ret = connectEx(socket, it.ai_addr, sizeof(TSockAddrIn).cint,
|
||||
nil, 0, nil, cast[POverlapped](ol))
|
||||
if ret:
|
||||
# Request to connect completed immediately.
|
||||
success = true
|
||||
retFuture.complete(0)
|
||||
dealloc(ol)
|
||||
break
|
||||
else:
|
||||
lastError = OSLastError()
|
||||
if lastError.int32 == ERROR_IO_PENDING:
|
||||
# In this case ``ol`` will be deallocated in ``poll``.
|
||||
success = true
|
||||
break
|
||||
else:
|
||||
dealloc(ol)
|
||||
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): PFuture[string] =
|
||||
## Reads ``size`` bytes from ``socket``. Returned future will complete once
|
||||
## all of the requested data is read.
|
||||
|
||||
var retFuture = newFuture[string]()
|
||||
|
||||
var dataBuf: TWSABuf
|
||||
dataBuf.buf = newString(size)
|
||||
dataBuf.len = size
|
||||
|
||||
var bytesReceived, flags: DWord
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
||||
if errcode == TOSErrorCode(-1):
|
||||
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,
|
||||
addr flags, cast[POverlapped](ol), nil)
|
||||
if ret == -1:
|
||||
let err = OSLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
dealloc(ol)
|
||||
else:
|
||||
# Request to read completed immediately.
|
||||
var data = newString(size)
|
||||
copyMem(addr data[0], addr dataBuf.buf[0], size)
|
||||
retFuture.complete($data)
|
||||
dealloc(ol)
|
||||
return retFuture
|
||||
|
||||
proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] =
|
||||
## Sends ``data`` to ``socket``. The returned future will complete once all
|
||||
## data has been sent.
|
||||
var retFuture = newFuture[int]()
|
||||
|
||||
var dataBuf: TWSABuf
|
||||
dataBuf.buf = data
|
||||
dataBuf.len = data.len
|
||||
|
||||
var bytesReceived, flags: DWord
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
||||
if errcode == TOSErrorCode(-1):
|
||||
retFuture.complete(0)
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
let ret = WSASend(socket, addr dataBuf, 1, addr bytesReceived,
|
||||
flags, cast[POverlapped](ol), nil)
|
||||
if ret == -1:
|
||||
let err = osLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
dealloc(ol)
|
||||
else:
|
||||
retFuture.complete(0)
|
||||
dealloc(ol)
|
||||
return retFuture
|
||||
|
||||
proc acceptAddr*(p: PDispatcher, socket: TSocketHandle):
|
||||
PFuture[tuple[address: string, client: TSocketHandle]] =
|
||||
## Accepts a new connection. Returns a future containing the client socket
|
||||
## corresponding to that connection and the remote address of the client.
|
||||
## The future will complete when the connection is successfully accepted.
|
||||
|
||||
var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]()
|
||||
|
||||
var clientSock = socket()
|
||||
if clientSock == OSInvalidSocket: osError(osLastError())
|
||||
|
||||
const lpOutputLen = 1024
|
||||
var lpOutputBuf = newString(lpOutputLen)
|
||||
var dwBytesReceived: DWORD
|
||||
let dwReceiveDataLength = 0.DWORD # We don't want any data to be read.
|
||||
let dwLocalAddressLength = DWORD(sizeof (TSockaddr_in) + 16)
|
||||
let dwRemoteAddressLength = DWORD(sizeof(TSockaddr_in) + 16)
|
||||
|
||||
template completeAccept(): stmt {.immediate, dirty.} =
|
||||
var listenSock = socket
|
||||
let setoptRet = setsockopt(clientSock, SOL_SOCKET,
|
||||
SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
|
||||
sizeof(listenSock).TSockLen)
|
||||
if setoptRet != 0: osError(osLastError())
|
||||
|
||||
var LocalSockaddr, RemoteSockaddr: ptr TSockAddr
|
||||
var localLen, remoteLen: int32
|
||||
getAcceptExSockaddrs(addr lpOutputBuf[0], dwReceiveDataLength,
|
||||
dwLocalAddressLength, dwRemoteAddressLength,
|
||||
addr LocalSockaddr, addr localLen,
|
||||
addr RemoteSockaddr, addr remoteLen)
|
||||
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
|
||||
retFuture.complete(
|
||||
(address: $inet_ntoa(cast[ptr Tsockaddr_in](remoteSockAddr).sin_addr),
|
||||
client: clientSock)
|
||||
)
|
||||
|
||||
var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped)))
|
||||
ol.data = TCompletionData(sock: socket, cb:
|
||||
proc (sock: TSocketHandle, errcode: TOSErrorCode) =
|
||||
if errcode == TOSErrorCode(-1):
|
||||
completeAccept()
|
||||
else:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
|
||||
let ret = acceptEx(socket, clientSock, addr lpOutputBuf[0],
|
||||
dwReceiveDataLength,
|
||||
dwLocalAddressLength,
|
||||
dwRemoteAddressLength,
|
||||
addr dwBytesReceived, cast[POverlapped](ol))
|
||||
|
||||
if not ret:
|
||||
let err = osLastError()
|
||||
if err.int32 != ERROR_IO_PENDING:
|
||||
retFuture.fail(newException(EOS, osErrorMsg(err)))
|
||||
dealloc(ol)
|
||||
else:
|
||||
completeAccept()
|
||||
dealloc(ol)
|
||||
|
||||
return retFuture
|
||||
|
||||
proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] =
|
||||
## Accepts a new connection. Returns a future containing the client socket
|
||||
## corresponding to that connection.
|
||||
## The future will complete when the connection is successfully accepted.
|
||||
var retFut = newFuture[TSocketHandle]()
|
||||
var fut = p.acceptAddr(socket)
|
||||
fut.callback =
|
||||
proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) =
|
||||
assert future.finished
|
||||
if future.failed:
|
||||
retFut.fail(future.error)
|
||||
else:
|
||||
retFut.complete(future.read.client)
|
||||
return retFut
|
||||
|
||||
initAll()
|
||||
else:
|
||||
# TODO: Selectors.
|
||||
|
||||
|
||||
when isMainModule:
|
||||
|
||||
var p = newDispatcher()
|
||||
var sock = socket()
|
||||
#sock.setBlocking false
|
||||
p.register(sock)
|
||||
|
||||
when true:
|
||||
|
||||
var f = p.connect(sock, "irc.freenode.org", TPort(6667))
|
||||
f.callback =
|
||||
proc (future: PFuture[int]) =
|
||||
echo("Connected in future!")
|
||||
echo(future.read)
|
||||
for i in 0 .. 50:
|
||||
var recvF = p.recv(sock, 10)
|
||||
recvF.callback =
|
||||
proc (future: PFuture[string]) =
|
||||
echo("Read: ", future.read)
|
||||
|
||||
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)
|
||||
f.callback = onAccept
|
||||
|
||||
while true:
|
||||
p.poll()
|
||||
echo "polled"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
195
lib/pure/asyncnet.nim
Normal file
195
lib/pure/asyncnet.nim
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
import asyncdispatch
|
||||
import rawsockets
|
||||
import net
|
||||
|
||||
when defined(ssl):
|
||||
import openssl
|
||||
|
||||
type
|
||||
# TODO: I would prefer to just do:
|
||||
# PAsyncSocket* {.borrow: `.`.} = distinct PSocket. But that doesn't work.
|
||||
TAsyncSocket {.borrow: `.`.} = distinct TSocketImpl
|
||||
PAsyncSocket* = ref TAsyncSocket
|
||||
|
||||
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect.
|
||||
|
||||
proc newSocket(fd: TAsyncFD, isBuff: bool): PAsyncSocket =
|
||||
assert fd != osInvalidSocket.TAsyncFD
|
||||
new(result.PSocket)
|
||||
result.fd = fd.TSocketHandle
|
||||
result.isBuffered = isBuff
|
||||
if isBuff:
|
||||
result.currPos = 0
|
||||
|
||||
proc newAsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||
protocol: TProtocol = IPPROTO_TCP, buffered = true): PAsyncSocket =
|
||||
## Creates a new asynchronous socket.
|
||||
result = newSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
|
||||
|
||||
proc connect*(socket: PAsyncSocket, address: string, port: TPort,
|
||||
af = AF_INET): PFuture[void] =
|
||||
## Connects ``socket`` to server at ``address:port``.
|
||||
##
|
||||
## Returns a ``PFuture`` which will complete when the connection succeeds
|
||||
## or an error occurs.
|
||||
result = connect(socket.fd.TAsyncFD, address, port, af)
|
||||
|
||||
proc recv*(socket: PAsyncSocket, size: int,
|
||||
flags: int = 0): PFuture[string] =
|
||||
## Reads ``size`` bytes from ``socket``. Returned future will complete once
|
||||
## 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 ``""``.
|
||||
result = recv(socket.fd.TAsyncFD, size, flags)
|
||||
|
||||
proc send*(socket: PAsyncSocket, data: string): PFuture[void] =
|
||||
## Sends ``data`` to ``socket``. The returned future will complete once all
|
||||
## data has been sent.
|
||||
result = send(socket.fd.TAsyncFD, data)
|
||||
|
||||
proc acceptAddr*(socket: PAsyncSocket):
|
||||
PFuture[tuple[address: string, client: PAsyncSocket]] =
|
||||
## Accepts a new connection. Returns a future containing the client socket
|
||||
## corresponding to that connection and the remote address of the client.
|
||||
## The future will complete when the connection is successfully accepted.
|
||||
var retFuture = newFuture[tuple[address: string, client: PAsyncSocket]]()
|
||||
var fut = acceptAddr(socket.fd.TAsyncFD)
|
||||
fut.callback =
|
||||
proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) =
|
||||
assert future.finished
|
||||
if future.failed:
|
||||
retFuture.fail(future.readError)
|
||||
else:
|
||||
let resultTup = (future.read.address,
|
||||
newSocket(future.read.client, socket.isBuffered))
|
||||
retFuture.complete(resultTup)
|
||||
return retFuture
|
||||
|
||||
proc accept*(socket: PAsyncSocket): PFuture[PAsyncSocket] =
|
||||
## 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[PAsyncSocket]()
|
||||
var fut = acceptAddr(socket)
|
||||
fut.callback =
|
||||
proc (future: PFuture[tuple[address: string, client: PAsyncSocket]]) =
|
||||
assert future.finished
|
||||
if future.failed:
|
||||
retFut.fail(future.readError)
|
||||
else:
|
||||
retFut.complete(future.read.client)
|
||||
return retFut
|
||||
|
||||
proc recvLine*(socket: PAsyncSocket): 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 ``""``.
|
||||
##
|
||||
## If the socket is disconnected in the middle of a line (before ``\r\L``
|
||||
## is read) then line will be set to ``""``.
|
||||
## The partial line **will be lost**.
|
||||
|
||||
template addNLIfEmpty(): stmt =
|
||||
if result.len == 0:
|
||||
result.add("\c\L")
|
||||
|
||||
result = ""
|
||||
var c = ""
|
||||
while true:
|
||||
c = await recv(socket, 1)
|
||||
if c.len == 0:
|
||||
return ""
|
||||
if c == "\r":
|
||||
c = await recv(socket, 1, MSG_PEEK)
|
||||
if c.len > 0 and c == "\L":
|
||||
discard await recv(socket, 1)
|
||||
addNLIfEmpty()
|
||||
return
|
||||
elif c == "\L":
|
||||
addNLIfEmpty()
|
||||
return
|
||||
add(result.string, c)
|
||||
|
||||
proc bindAddr*(socket: PAsyncSocket, port = TPort(0), address = "") =
|
||||
## Binds ``address``:``port`` to the socket.
|
||||
##
|
||||
## If ``address`` is "" then ADDR_ANY will be bound.
|
||||
socket.PSocket.bindAddr(port, address)
|
||||
|
||||
proc listen*(socket: PAsyncSocket, backlog = SOMAXCONN) =
|
||||
## Marks ``socket`` as accepting connections.
|
||||
## ``Backlog`` specifies the maximum length of the
|
||||
## queue of pending connections.
|
||||
##
|
||||
## Raises an EOS error upon failure.
|
||||
socket.PSocket.listen(backlog)
|
||||
|
||||
proc close*(socket: PAsyncSocket) =
|
||||
## Closes the socket.
|
||||
socket.fd.TAsyncFD.close()
|
||||
# TODO SSL
|
||||
|
||||
when isMainModule:
|
||||
type
|
||||
TestCases = enum
|
||||
HighClient, LowClient, LowServer
|
||||
|
||||
const test = LowServer
|
||||
|
||||
when test == HighClient:
|
||||
proc main() {.async.} =
|
||||
var sock = newAsyncSocket()
|
||||
await sock.connect("irc.freenode.net", TPort(6667))
|
||||
while true:
|
||||
let line = await sock.recvLine()
|
||||
if line == "":
|
||||
echo("Disconnected")
|
||||
break
|
||||
else:
|
||||
echo("Got line: ", line)
|
||||
main()
|
||||
elif test == LowClient:
|
||||
var sock = newAsyncSocket()
|
||||
var f = connect(sock, "irc.freenode.net", TPort(6667))
|
||||
f.callback =
|
||||
proc (future: PFuture[void]) =
|
||||
echo("Connected in future!")
|
||||
for i in 0 .. 50:
|
||||
var recvF = recv(sock, 10)
|
||||
recvF.callback =
|
||||
proc (future: PFuture[string]) =
|
||||
echo("Read ", future.read.len, ": ", future.read.repr)
|
||||
elif test == LowServer:
|
||||
var sock = newAsyncSocket()
|
||||
sock.bindAddr(TPort(6667))
|
||||
sock.listen()
|
||||
proc onAccept(future: PFuture[PAsyncSocket]) =
|
||||
let client = future.read
|
||||
echo "Accepted ", client.fd.cint
|
||||
var t = send(client, "test\c\L")
|
||||
t.callback =
|
||||
proc (future: PFuture[void]) =
|
||||
echo("Send")
|
||||
client.close()
|
||||
|
||||
var f = accept(sock)
|
||||
f.callback = onAccept
|
||||
|
||||
var f = accept(sock)
|
||||
f.callback = onAccept
|
||||
runForever()
|
||||
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
type
|
||||
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
|
||||
## be loaded.
|
||||
|
||||
|
|
@ -53,6 +53,7 @@ when defined(posix):
|
|||
#
|
||||
var
|
||||
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 dlopen(path: CString, mode: int): TLibHandle {.
|
||||
|
|
@ -60,7 +61,10 @@ when defined(posix):
|
|||
proc dlsym(lib: TLibHandle, name: cstring): pointer {.
|
||||
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 unloadLib(lib: TLibHandle) = dlclose(lib)
|
||||
proc symAddr(lib: TLibHandle, name: cstring): pointer =
|
||||
|
|
@ -81,14 +85,14 @@ elif defined(windows) or defined(dos):
|
|||
proc getProcAddress(lib: THINSTANCE, name: cstring): pointer {.
|
||||
importc: "GetProcAddress", header: "<windows.h>", stdcall.}
|
||||
|
||||
proc loadLib(path: string): TLibHandle =
|
||||
proc loadLib(path: string, global_symbols=false): TLibHandle =
|
||||
result = cast[TLibHandle](winLoadLibrary(path))
|
||||
proc loadLib(): TLibHandle =
|
||||
result = cast[TLibHandle](winLoadLibrary(nil))
|
||||
proc unloadLib(lib: TLibHandle) = FreeLibrary(cast[THINSTANCE](lib))
|
||||
|
||||
proc symAddr(lib: TLibHandle, name: cstring): pointer =
|
||||
result = GetProcAddress(cast[THINSTANCE](lib), name)
|
||||
result = getProcAddress(cast[THINSTANCE](lib), name)
|
||||
|
||||
else:
|
||||
{.error: "no implementation for dynlib".}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2012 Andreas Rumpf
|
||||
# (c) Copyright 2014 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## **Warning**: This module uses ``immediate`` macros which are known to
|
||||
## cause problems. Do yourself a favor and import the module
|
||||
## as ``from htmlgen import nil`` and then fully qualify the macros.
|
||||
##
|
||||
##
|
||||
## This module implements a simple `XML`:idx: and `HTML`:idx: code
|
||||
## generator. Each commonly used HTML tag has a corresponding macro
|
||||
## that generates a string with its HTML representation.
|
||||
|
|
@ -15,11 +20,11 @@
|
|||
##
|
||||
## .. code-block:: nimrod
|
||||
## var nim = "Nimrod"
|
||||
## echo h1(a(href="http://nimrod-code.org", nim))
|
||||
## echo h1(a(href="http://nimrod-lang.org", nim))
|
||||
##
|
||||
## Writes the string::
|
||||
##
|
||||
## <h1><a href="http://nimrod-code.org">Nimrod</a></h1>
|
||||
## <h1><a href="http://nimrod-lang.org">Nimrod</a></h1>
|
||||
##
|
||||
|
||||
import
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@
|
|||
## currently only basic authentication is supported.
|
||||
|
||||
import sockets, strutils, parseurl, parseutils, strtabs, base64
|
||||
import asyncnet, asyncdispatch
|
||||
import rawsockets
|
||||
|
||||
type
|
||||
TResponse* = tuple[
|
||||
|
|
@ -286,16 +288,16 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
|
|||
add(headers, "\c\L")
|
||||
|
||||
var s = socket()
|
||||
var port = TPort(80)
|
||||
var port = sockets.TPort(80)
|
||||
if r.scheme == "https":
|
||||
when defined(ssl):
|
||||
sslContext.wrapSocket(s)
|
||||
port = TPort(443)
|
||||
port = sockets.TPort(443)
|
||||
else:
|
||||
raise newException(EHttpRequestErr,
|
||||
"SSL support is not available. Cannot connect over SSL.")
|
||||
if r.port != "":
|
||||
port = TPort(r.port.parseInt)
|
||||
port = sockets.TPort(r.port.parseInt)
|
||||
|
||||
if timeout == -1:
|
||||
s.connect(r.hostname, port)
|
||||
|
|
@ -413,28 +415,217 @@ proc downloadFile*(url: string, outputFilename: string,
|
|||
else:
|
||||
fileError("Unable to open file")
|
||||
|
||||
proc generateHeaders(r: TURL, httpMethod: THttpMethod,
|
||||
headers: PStringTable): string =
|
||||
result = substr($httpMethod, len("http"))
|
||||
# TODO: Proxies
|
||||
result.add(" /" & r.path & r.query)
|
||||
result.add(" HTTP/1.1\c\L")
|
||||
|
||||
add(result, "Host: " & r.hostname & "\c\L")
|
||||
add(result, "Connection: Keep-Alive\c\L")
|
||||
for key, val in headers:
|
||||
add(result, key & ": " & val & "\c\L")
|
||||
|
||||
add(result, "\c\L")
|
||||
|
||||
type
|
||||
PAsyncHttpClient = ref object
|
||||
socket: PAsyncSocket
|
||||
connected: bool
|
||||
currentURL: TURL ## Where we are currently connected.
|
||||
headers: PStringTable
|
||||
userAgent: string
|
||||
|
||||
proc newAsyncHttpClient*(): PAsyncHttpClient =
|
||||
new result
|
||||
result.socket = newAsyncSocket()
|
||||
result.headers = newStringTable(modeCaseInsensitive)
|
||||
result.userAgent = defUserAgent
|
||||
|
||||
proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} =
|
||||
result = ""
|
||||
var ri = 0
|
||||
while true:
|
||||
var chunkSize = 0
|
||||
var chunkSizeStr = await client.socket.recvLine()
|
||||
var i = 0
|
||||
if chunkSizeStr == "":
|
||||
httpError("Server terminated connection prematurely")
|
||||
while true:
|
||||
case chunkSizeStr[i]
|
||||
of '0'..'9':
|
||||
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
|
||||
of 'a'..'f':
|
||||
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
|
||||
of 'A'..'F':
|
||||
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
|
||||
of '\0':
|
||||
break
|
||||
of ';':
|
||||
# http://tools.ietf.org/html/rfc2616#section-3.6.1
|
||||
# We don't care about chunk-extensions.
|
||||
break
|
||||
else:
|
||||
httpError("Invalid chunk size: " & chunkSizeStr)
|
||||
inc(i)
|
||||
if chunkSize <= 0: break
|
||||
result.add await recv(client.socket, chunkSize)
|
||||
discard await recv(client.socket, 2) # Skip \c\L
|
||||
# Trailer headers will only be sent if the request specifies that we want
|
||||
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
|
||||
|
||||
proc parseBody(client: PAsyncHttpClient,
|
||||
headers: PStringTable): PFuture[string] {.async.} =
|
||||
result = ""
|
||||
if headers["Transfer-Encoding"] == "chunked":
|
||||
result = await parseChunks(client)
|
||||
else:
|
||||
# -REGION- Content-Length
|
||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
|
||||
var contentLengthHeader = headers["Content-Length"]
|
||||
if contentLengthHeader != "":
|
||||
var length = contentLengthHeader.parseint()
|
||||
result = await client.socket.recv(length)
|
||||
if result == "":
|
||||
httpError("Got disconnected while trying to recv body.")
|
||||
else:
|
||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO
|
||||
|
||||
# -REGION- Connection: Close
|
||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
|
||||
if headers["Connection"] == "close":
|
||||
var buf = ""
|
||||
while True:
|
||||
buf = await client.socket.recv(4000)
|
||||
if buf == "": break
|
||||
result.add(buf)
|
||||
|
||||
proc parseResponse(client: PAsyncHttpClient,
|
||||
getBody: bool): PFuture[TResponse] {.async.} =
|
||||
var parsedStatus = false
|
||||
var linei = 0
|
||||
var fullyRead = false
|
||||
var line = ""
|
||||
result.headers = newStringTable(modeCaseInsensitive)
|
||||
while True:
|
||||
linei = 0
|
||||
line = await client.socket.recvLine()
|
||||
if line == "": break # We've been disconnected.
|
||||
if line == "\c\L":
|
||||
fullyRead = true
|
||||
break
|
||||
if not parsedStatus:
|
||||
# Parse HTTP version info and status code.
|
||||
var le = skipIgnoreCase(line, "HTTP/", linei)
|
||||
if le <= 0: httpError("invalid http version")
|
||||
inc(linei, le)
|
||||
le = skipIgnoreCase(line, "1.1", linei)
|
||||
if le > 0: result.version = "1.1"
|
||||
else:
|
||||
le = skipIgnoreCase(line, "1.0", linei)
|
||||
if le <= 0: httpError("unsupported http version")
|
||||
result.version = "1.0"
|
||||
inc(linei, le)
|
||||
# Status code
|
||||
linei.inc skipWhitespace(line, linei)
|
||||
result.status = line[linei .. -1]
|
||||
parsedStatus = true
|
||||
else:
|
||||
# Parse headers
|
||||
var name = ""
|
||||
var le = parseUntil(line, name, ':', linei)
|
||||
if le <= 0: httpError("invalid headers")
|
||||
inc(linei, le)
|
||||
if line[linei] != ':': httpError("invalid headers")
|
||||
inc(linei) # Skip :
|
||||
|
||||
result.headers[name] = line[linei.. -1].strip()
|
||||
if not fullyRead:
|
||||
httpError("Connection was closed before full request has been made")
|
||||
if getBody:
|
||||
result.body = await parseBody(client, result.headers)
|
||||
else:
|
||||
result.body = ""
|
||||
|
||||
proc close*(client: PAsyncHttpClient) =
|
||||
## Closes any connections held by the HttpClient.
|
||||
if client.connected:
|
||||
client.socket.close()
|
||||
client.connected = false
|
||||
#client.socket = newAsyncSocket()
|
||||
|
||||
proc newConnection(client: PAsyncHttpClient, url: TURL) {.async.} =
|
||||
if not client.connected or client.currentURL.hostname != url.hostname or
|
||||
client.currentURL.scheme != url.scheme:
|
||||
if client.connected: client.close()
|
||||
if url.scheme == "https":
|
||||
assert false, "TODO SSL"
|
||||
|
||||
# TODO: I should be able to write 'net.TPort' here...
|
||||
let port =
|
||||
if url.port == "": rawsockets.TPort(80)
|
||||
else: rawsockets.TPort(url.port.parseInt)
|
||||
|
||||
await client.socket.connect(url.hostname, port)
|
||||
client.currentURL = url
|
||||
|
||||
proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET,
|
||||
body = ""): PFuture[TResponse] {.async.} =
|
||||
let r = parseUrl(url)
|
||||
await newConnection(client, r)
|
||||
|
||||
if not client.headers.hasKey("user-agent") and client.userAgent != "":
|
||||
client.headers["User-Agent"] = client.userAgent
|
||||
|
||||
var headers = generateHeaders(r, httpMethod, client.headers)
|
||||
|
||||
await client.socket.send(headers)
|
||||
if body != "":
|
||||
await client.socket.send(body)
|
||||
|
||||
result = await parseResponse(client, httpMethod != httpHEAD)
|
||||
|
||||
when isMainModule:
|
||||
#downloadFile("http://force7.de/nimrod/index.html", "nimrodindex.html")
|
||||
#downloadFile("http://www.httpwatch.com/", "ChunkTest.html")
|
||||
#downloadFile("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com",
|
||||
# "validator.html")
|
||||
when true:
|
||||
# Async
|
||||
proc main() {.async.} =
|
||||
var client = newAsyncHttpClient()
|
||||
var resp = await client.request("http://picheta.me")
|
||||
|
||||
echo("Got response: ", resp.status)
|
||||
echo("Body:\n")
|
||||
echo(resp.body)
|
||||
|
||||
#var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
|
||||
# charset=%28detect+automatically%29&doctype=Inline&group=0")
|
||||
|
||||
var headers: string = "Content-Type: multipart/form-data; boundary=xyz\c\L"
|
||||
var body: string = "--xyz\c\L"
|
||||
# soap 1.2 output
|
||||
body.add("Content-Disposition: form-data; name=\"output\"\c\L")
|
||||
body.add("\c\Lsoap12\c\L")
|
||||
|
||||
# html
|
||||
body.add("--xyz\c\L")
|
||||
body.add("Content-Disposition: form-data; name=\"uploaded_file\";" &
|
||||
" filename=\"test.html\"\c\L")
|
||||
body.add("Content-Type: text/html\c\L")
|
||||
body.add("\c\L<html><head></head><body><p>test</p></body></html>\c\L")
|
||||
body.add("--xyz--")
|
||||
#var resp1 = await client.request("http://freenode.net")
|
||||
#echo("Got response: ", resp1.status)
|
||||
|
||||
echo(postContent("http://validator.w3.org/check", headers, body))
|
||||
var resp2 = await client.request("http://picheta.me/aasfasgf.html")
|
||||
echo("Got response: ", resp2.status)
|
||||
main()
|
||||
runForever()
|
||||
|
||||
else:
|
||||
#downloadFile("http://force7.de/nimrod/index.html", "nimrodindex.html")
|
||||
#downloadFile("http://www.httpwatch.com/", "ChunkTest.html")
|
||||
#downloadFile("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com",
|
||||
# "validator.html")
|
||||
|
||||
#var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
|
||||
# charset=%28detect+automatically%29&doctype=Inline&group=0")
|
||||
|
||||
var headers: string = "Content-Type: multipart/form-data; boundary=xyz\c\L"
|
||||
var body: string = "--xyz\c\L"
|
||||
# soap 1.2 output
|
||||
body.add("Content-Disposition: form-data; name=\"output\"\c\L")
|
||||
body.add("\c\Lsoap12\c\L")
|
||||
|
||||
# html
|
||||
body.add("--xyz\c\L")
|
||||
body.add("Content-Disposition: form-data; name=\"uploaded_file\";" &
|
||||
" filename=\"test.html\"\c\L")
|
||||
body.add("Content-Type: text/html\c\L")
|
||||
body.add("\c\L<html><head></head><body><p>test</p></body></html>\c\L")
|
||||
body.add("--xyz--")
|
||||
|
||||
echo(postContent("http://validator.w3.org/check", headers, body))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
when defined(Posix) and not defined(haiku):
|
||||
{.passl: "-lm".}
|
||||
|
||||
import times
|
||||
|
||||
const
|
||||
PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number)
|
||||
E* = 2.71828182845904523536028747 ## Euler's number
|
||||
|
|
@ -201,7 +203,7 @@ when not defined(JS):
|
|||
result = drand48() * max
|
||||
|
||||
proc randomize() =
|
||||
randomize(gettime(nil))
|
||||
randomize(cast[int](epochTime()))
|
||||
|
||||
proc randomize(seed: int) =
|
||||
srand(cint(seed))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2012 Nimrod Contributors
|
||||
# (c) Copyright 2014 Nimrod Contributors
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
|
|
@ -34,6 +34,43 @@ type
|
|||
else:
|
||||
handle: cint
|
||||
|
||||
|
||||
proc mapMem*(m: var TMemFile, mode: TFileMode = fmRead,
|
||||
mappedSize = -1, offset = 0): pointer =
|
||||
var readonly = mode == fmRead
|
||||
when defined(windows):
|
||||
result = mapViewOfFileEx(
|
||||
m.mapHandle,
|
||||
if readonly: FILE_MAP_READ else: FILE_MAP_WRITE,
|
||||
int32(offset shr 32),
|
||||
int32(offset and 0xffffffff),
|
||||
if mappedSize == -1: 0 else: mappedSize,
|
||||
nil)
|
||||
if result == nil:
|
||||
osError(osLastError())
|
||||
else:
|
||||
assert mappedSize > 0
|
||||
result = mmap(
|
||||
nil,
|
||||
mappedSize,
|
||||
if readonly: PROT_READ else: PROT_READ or PROT_WRITE,
|
||||
if readonly: MAP_PRIVATE else: MAP_SHARED,
|
||||
m.handle, offset)
|
||||
if result == cast[pointer](MAP_FAILED):
|
||||
osError(osLastError())
|
||||
|
||||
|
||||
proc unmapMem*(f: var TMemFile, p: pointer, size: int) =
|
||||
## unmaps the memory region ``(p, <p+size)`` of the mapped file `f`.
|
||||
## All changes are written back to the file system, if `f` was opened
|
||||
## with write access. ``size`` must be of exactly the size that was requested
|
||||
## via ``mapMem``.
|
||||
when defined(windows):
|
||||
if unmapViewOfFile(p) == 0: osError(osLastError())
|
||||
else:
|
||||
if munmap(p, size) != 0: osError(osLastError())
|
||||
|
||||
|
||||
proc open*(filename: string, mode: TFileMode = fmRead,
|
||||
mappedSize = -1, offset = 0, newFileSize = -1): TMemFile =
|
||||
## opens a memory mapped file. If this fails, ``EOS`` is raised.
|
||||
|
|
@ -71,7 +108,7 @@ proc open*(filename: string, mode: TFileMode = fmRead,
|
|||
when useWinUnicode:
|
||||
result.fHandle = callCreateFile(createFileW, newWideCString(filename))
|
||||
else:
|
||||
result.fHandle = callCreateFile(CreateFileA, filename)
|
||||
result.fHandle = callCreateFile(createFileA, filename)
|
||||
|
||||
if result.fHandle == INVALID_HANDLE_VALUE:
|
||||
fail(osLastError(), "error opening file")
|
||||
|
|
@ -170,14 +207,14 @@ proc close*(f: var TMemFile) =
|
|||
|
||||
when defined(windows):
|
||||
if f.fHandle != INVALID_HANDLE_VALUE:
|
||||
lastErr = osLastError()
|
||||
error = unmapViewOfFile(f.mem) == 0
|
||||
lastErr = osLastError()
|
||||
error = (closeHandle(f.mapHandle) == 0) or error
|
||||
error = (closeHandle(f.fHandle) == 0) or error
|
||||
else:
|
||||
if f.handle != 0:
|
||||
lastErr = osLastError()
|
||||
error = munmap(f.mem, f.size) != 0
|
||||
lastErr = osLastError()
|
||||
error = (close(f.handle) != 0) or error
|
||||
|
||||
f.size = 0
|
||||
|
|
|
|||
1109
lib/pure/net.nim
1109
lib/pure/net.nim
File diff suppressed because it is too large
Load diff
|
|
@ -67,7 +67,7 @@ when withThreads:
|
|||
|
||||
proc hookAux(st: TStackTrace, costs: int) =
|
||||
# this is quite performance sensitive!
|
||||
when withThreads: Acquire profilingLock
|
||||
when withThreads: acquire profilingLock
|
||||
inc totalCalls
|
||||
var last = high(st)
|
||||
while last > 0 and isNil(st[last]): dec last
|
||||
|
|
@ -106,7 +106,7 @@ proc hookAux(st: TStackTrace, costs: int) =
|
|||
h = ((5 * h) + 1) and high(profileData)
|
||||
inc chain
|
||||
maxChainLen = max(maxChainLen, chain)
|
||||
when withThreads: Release profilingLock
|
||||
when withThreads: release profilingLock
|
||||
|
||||
when defined(memProfiler):
|
||||
const
|
||||
|
|
|
|||
|
|
@ -260,11 +260,12 @@ proc osError*(errorCode: TOSErrorCode) =
|
|||
##
|
||||
## If the error code is ``0`` or an error message could not be retrieved,
|
||||
## the message ``unknown OS error`` will be used.
|
||||
let msg = osErrorMsg(errorCode)
|
||||
if msg == "":
|
||||
raise newException(EOS, "unknown OS error")
|
||||
else:
|
||||
raise newException(EOS, msg)
|
||||
var e: ref EOS; new(e)
|
||||
e.errorCode = errorCode.int32
|
||||
e.msg = osErrorMsg(errorCode)
|
||||
if e.msg == "":
|
||||
e.msg = "unknown OS error"
|
||||
raise e
|
||||
|
||||
{.push stackTrace:off.}
|
||||
proc osLastError*(): TOSErrorCode =
|
||||
|
|
@ -1037,7 +1038,10 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
|
|||
## the process has finished. To execute a program without having a
|
||||
## shell involved, use the `execProcess` proc of the `osproc`
|
||||
## module.
|
||||
result = c_system(command) shr 8
|
||||
when defined(linux):
|
||||
result = c_system(command) shr 8
|
||||
else:
|
||||
result = c_system(command)
|
||||
|
||||
# Environment handling cannot be put into RTL, because the ``envPairs``
|
||||
# iterator depends on ``environment``.
|
||||
|
|
@ -1189,7 +1193,8 @@ iterator walkFiles*(pattern: string): string {.tags: [FReadDir].} =
|
|||
res = findFirstFile(pattern, f)
|
||||
if res != -1:
|
||||
while true:
|
||||
if not skipFindData(f):
|
||||
if not skipFindData(f) and
|
||||
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) == 0'i32:
|
||||
yield splitFile(pattern).dir / extractFilename(getFilename(f))
|
||||
if findNextFile(res, f) == 0'i32: break
|
||||
findClose(res)
|
||||
|
|
|
|||
|
|
@ -13,13 +13,16 @@
|
|||
include "system/inclrtl"
|
||||
|
||||
import
|
||||
strutils, os, strtabs, streams, sequtils
|
||||
strutils, os, strtabs, streams
|
||||
|
||||
when defined(windows):
|
||||
import winlean
|
||||
else:
|
||||
import posix
|
||||
|
||||
when defined(linux):
|
||||
import linux
|
||||
|
||||
type
|
||||
TProcess = object of TObject
|
||||
when defined(windows):
|
||||
|
|
@ -44,7 +47,7 @@ type
|
|||
poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
|
||||
poParentStreams ## use the parent's streams
|
||||
|
||||
template poUseShell*: TProcessOption {.deprecated.} = poUsePath
|
||||
const poUseShell* {.deprecated.} = poUsePath
|
||||
## Deprecated alias for poUsePath.
|
||||
|
||||
proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
|
||||
|
|
@ -165,6 +168,9 @@ proc processID*(p: PProcess): int {.rtl, extern: "nosp$1".} =
|
|||
proc waitForExit*(p: PProcess, timeout: int = -1): int {.rtl,
|
||||
extern: "nosp$1", tags: [].}
|
||||
## waits for the process to finish and returns `p`'s error code.
|
||||
##
|
||||
## **Warning**: Be careful when using waitForExit for processes created without
|
||||
## poParentStreams because they may fill output buffers, causing deadlock.
|
||||
|
||||
proc peekExitCode*(p: PProcess): int {.tags: [].}
|
||||
## return -1 if the process is still running. Otherwise the process' exit code
|
||||
|
|
@ -590,6 +596,24 @@ elif not defined(useNimRtl):
|
|||
copyMem(result[i], addr(x[0]), x.len+1)
|
||||
inc(i)
|
||||
|
||||
type TStartProcessData = object
|
||||
sysCommand: cstring
|
||||
sysArgs: cstringArray
|
||||
sysEnv: cstringArray
|
||||
workingDir: cstring
|
||||
pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint]
|
||||
optionPoUsePath: bool
|
||||
optionPoParentStreams: bool
|
||||
optionPoStdErrToStdOut: bool
|
||||
|
||||
when not defined(useFork):
|
||||
proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
|
||||
proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
|
||||
{.push stacktrace: off, profiler: off.}
|
||||
proc startProcessAfterFork(data: ptr TStartProcessData) {.
|
||||
tags: [FExecIO, FReadEnv], cdecl.}
|
||||
{.pop.}
|
||||
|
||||
proc startProcess(command: string,
|
||||
workingDir: string = "",
|
||||
args: openArray[string] = [],
|
||||
|
|
@ -604,100 +628,49 @@ elif not defined(useNimRtl):
|
|||
pipe(pStderr) != 0'i32:
|
||||
osError(osLastError())
|
||||
|
||||
var sys_command: string
|
||||
var sys_args_raw: seq[string]
|
||||
var sysCommand: string
|
||||
var sysArgsRaw: seq[string]
|
||||
if poEvalCommand in options:
|
||||
sys_command = "/bin/sh"
|
||||
sys_args_raw = @[sys_command, "-c", command]
|
||||
sysCommand = "/bin/sh"
|
||||
sysArgsRaw = @[sysCommand, "-c", command]
|
||||
assert args.len == 0
|
||||
else:
|
||||
sys_command = command
|
||||
sys_args_raw = @[command]
|
||||
sysCommand = command
|
||||
sysArgsRaw = @[command]
|
||||
for arg in args.items:
|
||||
sys_args_raw.add arg
|
||||
|
||||
var sys_args = allocCStringArray(sys_args_raw)
|
||||
finally: deallocCStringArray(sys_args)
|
||||
sysArgsRaw.add arg
|
||||
|
||||
var pid: TPid
|
||||
when defined(posix_spawn) and not defined(useFork):
|
||||
var attr: Tposix_spawnattr
|
||||
var fops: Tposix_spawn_file_actions
|
||||
|
||||
template chck(e: expr) =
|
||||
if e != 0'i32: osError(osLastError())
|
||||
var sysArgs = allocCStringArray(sysArgsRaw)
|
||||
finally: deallocCStringArray(sysArgs)
|
||||
|
||||
chck posix_spawn_file_actions_init(fops)
|
||||
chck posix_spawnattr_init(attr)
|
||||
|
||||
var mask: Tsigset
|
||||
chck sigemptyset(mask)
|
||||
chck posix_spawnattr_setsigmask(attr, mask)
|
||||
chck posix_spawnattr_setpgroup(attr, 0'i32)
|
||||
|
||||
chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or
|
||||
POSIX_SPAWN_SETSIGMASK or
|
||||
POSIX_SPAWN_SETPGROUP)
|
||||
|
||||
if poParentStreams notin options:
|
||||
chck posix_spawn_file_actions_addclose(fops, pStdin[writeIdx])
|
||||
chck posix_spawn_file_actions_adddup2(fops, pStdin[readIdx], readIdx)
|
||||
chck posix_spawn_file_actions_addclose(fops, pStdout[readIdx])
|
||||
chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], writeIdx)
|
||||
chck posix_spawn_file_actions_addclose(fops, pStderr[readIdx])
|
||||
if poStdErrToStdOut in options:
|
||||
chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], 2)
|
||||
else:
|
||||
chck posix_spawn_file_actions_adddup2(fops, p_stderr[writeIdx], 2)
|
||||
|
||||
var sys_env = if env == nil: envToCStringArray() else: envToCStringArray(env)
|
||||
var res: cint
|
||||
# This is incorrect!
|
||||
if workingDir.len > 0: os.setCurrentDir(workingDir)
|
||||
if poUsePath in options:
|
||||
res = posix_spawnp(pid, sys_command, fops, attr, sys_args, sys_env)
|
||||
var sysEnv = if env == nil:
|
||||
envToCStringArray()
|
||||
else:
|
||||
res = posix_spawn(pid, sys_command, fops, attr, sys_args, sys_env)
|
||||
deallocCStringArray(sys_env)
|
||||
discard posix_spawn_file_actions_destroy(fops)
|
||||
discard posix_spawnattr_destroy(attr)
|
||||
chck res
|
||||
envToCStringArray(env)
|
||||
|
||||
finally: deallocCStringArray(sysEnv)
|
||||
|
||||
var data: TStartProcessData
|
||||
data.sysCommand = sysCommand
|
||||
data.sysArgs = sysArgs
|
||||
data.sysEnv = sysEnv
|
||||
data.pStdin = pStdin
|
||||
data.pStdout = pStdout
|
||||
data.pStderr = pStderr
|
||||
data.optionPoParentStreams = poParentStreams in options
|
||||
data.optionPoUsePath = poUsePath in options
|
||||
data.optionPoStdErrToStdOut = poStdErrToStdOut in options
|
||||
data.workingDir = workingDir
|
||||
|
||||
|
||||
when defined(posix_spawn) and not defined(useFork) and
|
||||
not defined(useClone) and not defined(linux):
|
||||
pid = startProcessAuxSpawn(data)
|
||||
else:
|
||||
pid = fork()
|
||||
if pid < 0: osError(osLastError())
|
||||
if pid == 0:
|
||||
## child process:
|
||||
pid = startProcessAuxFork(data)
|
||||
|
||||
if poParentStreams notin options:
|
||||
discard close(p_stdin[writeIdx])
|
||||
if dup2(p_stdin[readIdx], readIdx) < 0: osError(osLastError())
|
||||
discard close(p_stdout[readIdx])
|
||||
if dup2(p_stdout[writeIdx], writeIdx) < 0: osError(osLastError())
|
||||
discard close(p_stderr[readIdx])
|
||||
if poStdErrToStdOut in options:
|
||||
if dup2(p_stdout[writeIdx], 2) < 0: osError(osLastError())
|
||||
else:
|
||||
if dup2(p_stderr[writeIdx], 2) < 0: osError(osLastError())
|
||||
|
||||
# Create a new process group
|
||||
if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno))
|
||||
|
||||
if workingDir.len > 0: os.setCurrentDir(workingDir)
|
||||
|
||||
if env == nil:
|
||||
if poUsePath in options:
|
||||
discard execvp(sys_command, sys_args)
|
||||
else:
|
||||
discard execv(sys_command, sys_args)
|
||||
else:
|
||||
var c_env = envToCStringArray(env)
|
||||
if poUsePath in options:
|
||||
discard execvpe(sys_command, sys_args, c_env)
|
||||
else:
|
||||
discard execve(sys_command, sys_args, c_env)
|
||||
# too risky to raise an exception here:
|
||||
quit("execve call failed: " & $strerror(errno))
|
||||
# Parent process. Copy process information.
|
||||
if poEchoCmd in options:
|
||||
echo(command, " ", join(args, " "))
|
||||
|
|
@ -723,6 +696,138 @@ elif not defined(useNimRtl):
|
|||
discard close(pStdin[readIdx])
|
||||
discard close(pStdout[writeIdx])
|
||||
|
||||
when not defined(useFork):
|
||||
proc startProcessAuxSpawn(data: TStartProcessData): TPid =
|
||||
var attr: Tposix_spawnattr
|
||||
var fops: Tposix_spawn_file_actions
|
||||
|
||||
template chck(e: expr) =
|
||||
if e != 0'i32: osError(osLastError())
|
||||
|
||||
chck posix_spawn_file_actions_init(fops)
|
||||
chck posix_spawnattr_init(attr)
|
||||
|
||||
var mask: Tsigset
|
||||
chck sigemptyset(mask)
|
||||
chck posix_spawnattr_setsigmask(attr, mask)
|
||||
chck posix_spawnattr_setpgroup(attr, 0'i32)
|
||||
|
||||
chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or
|
||||
POSIX_SPAWN_SETSIGMASK or
|
||||
POSIX_SPAWN_SETPGROUP)
|
||||
|
||||
if not data.optionPoParentStreams:
|
||||
chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx])
|
||||
chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx)
|
||||
chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx])
|
||||
chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx)
|
||||
chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx])
|
||||
if data.optionPoStdErrToStdOut:
|
||||
chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2)
|
||||
else:
|
||||
chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2)
|
||||
|
||||
var res: cint
|
||||
# FIXME: chdir is global to process
|
||||
if data.workingDir.len > 0:
|
||||
setCurrentDir($data.workingDir)
|
||||
var pid: TPid
|
||||
|
||||
if data.optionPoUsePath:
|
||||
res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
|
||||
else:
|
||||
res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
|
||||
|
||||
discard posix_spawn_file_actions_destroy(fops)
|
||||
discard posix_spawnattr_destroy(attr)
|
||||
chck res
|
||||
return pid
|
||||
|
||||
proc startProcessAuxFork(data: TStartProcessData): TPid =
|
||||
if pipe(data.pErrorPipe) != 0:
|
||||
osError(osLastError())
|
||||
|
||||
finally:
|
||||
discard close(data.pErrorPipe[readIdx])
|
||||
|
||||
var pid: TPid
|
||||
var dataCopy = data
|
||||
|
||||
when defined(useClone):
|
||||
const stackSize = 65536
|
||||
let stackEnd = cast[clong](alloc(stackSize))
|
||||
let stack = cast[pointer](stackEnd + stackSize)
|
||||
let fn: pointer = startProcessAfterFork
|
||||
pid = clone(fn, stack,
|
||||
cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
|
||||
pointer(addr dataCopy), nil, nil, nil)
|
||||
discard close(data.pErrorPipe[writeIdx])
|
||||
dealloc(stack)
|
||||
else:
|
||||
pid = fork()
|
||||
if pid == 0:
|
||||
startProcessAfterFork(addr(dataCopy))
|
||||
exitnow(1)
|
||||
|
||||
discard close(data.pErrorPipe[writeIdx])
|
||||
if pid < 0: osError(osLastError())
|
||||
|
||||
var error: cint
|
||||
let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
|
||||
if sizeRead == sizeof(error):
|
||||
osError($strerror(error))
|
||||
|
||||
return pid
|
||||
|
||||
{.push stacktrace: off, profiler: off.}
|
||||
proc startProcessFail(data: ptr TStartProcessData) =
|
||||
var error: cint = errno
|
||||
discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
|
||||
exitnow(1)
|
||||
|
||||
when defined(macosx):
|
||||
var environ {.importc.}: cstringArray
|
||||
|
||||
proc startProcessAfterFork(data: ptr TStartProcessData) =
|
||||
# Warning: no GC here!
|
||||
# Or anythink that touches global structures - all called nimrod procs
|
||||
# must be marked with noStackFrame. Inspect C code after making changes.
|
||||
if not data.optionPoParentStreams:
|
||||
discard close(data.pStdin[writeIdx])
|
||||
if dup2(data.pStdin[readIdx], readIdx) < 0:
|
||||
startProcessFail(data)
|
||||
discard close(data.pStdout[readIdx])
|
||||
if dup2(data.pStdout[writeIdx], writeIdx) < 0:
|
||||
startProcessFail(data)
|
||||
discard close(data.pStderr[readIdx])
|
||||
if data.optionPoStdErrToStdOut:
|
||||
if dup2(data.pStdout[writeIdx], 2) < 0:
|
||||
startProcessFail(data)
|
||||
else:
|
||||
if dup2(data.pStderr[writeIdx], 2) < 0:
|
||||
startProcessFail(data)
|
||||
|
||||
if data.workingDir.len > 0:
|
||||
if chdir(data.workingDir) < 0:
|
||||
startProcessFail(data)
|
||||
|
||||
discard close(data.pErrorPipe[readIdx])
|
||||
discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
|
||||
|
||||
if data.optionPoUsePath:
|
||||
when defined(macosx):
|
||||
# MacOSX doesn't have execvpe, so we need workaround.
|
||||
# On MacOSX we can arrive here only from fork, so this is safe:
|
||||
environ = data.sysEnv
|
||||
discard execvp(data.sysCommand, data.sysArgs)
|
||||
else:
|
||||
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
|
||||
else:
|
||||
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
|
||||
|
||||
startProcessFail(data)
|
||||
{.pop}
|
||||
|
||||
proc close(p: PProcess) =
|
||||
if p.inStream != nil: close(p.inStream)
|
||||
if p.outStream != nil: close(p.outStream)
|
||||
|
|
@ -791,7 +896,10 @@ elif not defined(useNimRtl):
|
|||
proc csystem(cmd: cstring): cint {.nodecl, importc: "system".}
|
||||
|
||||
proc execCmd(command: string): int =
|
||||
result = csystem(command) shr 8
|
||||
when defined(linux):
|
||||
result = csystem(command) shr 8
|
||||
else:
|
||||
result = csystem(command)
|
||||
|
||||
proc createFdSet(fd: var TFdSet, s: seq[PProcess], m: var int) =
|
||||
FD_ZERO(fd)
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ proc getSymbol(c: var TSqlLexer, tok: var TToken) =
|
|||
while true:
|
||||
add(tok.literal, buf[pos])
|
||||
Inc(pos)
|
||||
if not (buf[pos] in {'a'..'z','A'..'Z','0'..'9','_','$', '\128'..'\255'}):
|
||||
if buf[pos] notin {'a'..'z','A'..'Z','0'..'9','_','$', '\128'..'\255'}:
|
||||
break
|
||||
c.bufpos = pos
|
||||
tok.kind = tkIdentifier
|
||||
|
|
|
|||
|
|
@ -836,9 +836,11 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string =
|
|||
while i < s.len:
|
||||
c.ml = 0
|
||||
var L = rawMatch(s, pattern, i, c)
|
||||
if L < 0: break
|
||||
yield substr(s, i, i+L-1)
|
||||
inc(i, L)
|
||||
if L < 0:
|
||||
inc(i, 1)
|
||||
else:
|
||||
yield substr(s, i, i+L-1)
|
||||
inc(i, L)
|
||||
|
||||
proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {.
|
||||
nosideEffect, rtl, extern: "npegs$1".} =
|
||||
|
|
|
|||
421
lib/pure/rawsockets.nim
Normal file
421
lib/pure/rawsockets.nim
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module implements a low-level cross-platform sockets interface. Look
|
||||
## at the ``net`` module for the higher-level version.
|
||||
|
||||
# TODO: Clean up the exports a bit and everything else in general.
|
||||
|
||||
import unsigned, os
|
||||
|
||||
when hostos == "solaris":
|
||||
{.passl: "-lsocket -lnsl".}
|
||||
|
||||
when defined(Windows):
|
||||
import winlean
|
||||
export WSAEWOULDBLOCK
|
||||
else:
|
||||
import posix
|
||||
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
|
||||
EINTR, EINPROGRESS
|
||||
|
||||
export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen,
|
||||
inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto
|
||||
|
||||
export
|
||||
SO_ERROR,
|
||||
SOL_SOCKET,
|
||||
SOMAXCONN,
|
||||
SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE,
|
||||
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
|
||||
MSG_PEEK
|
||||
|
||||
type
|
||||
|
||||
TPort* = distinct uint16 ## port type
|
||||
|
||||
TDomain* = enum ## domain, which specifies the protocol family of the
|
||||
## created socket. Other domains than those that are listed
|
||||
## here are unsupported.
|
||||
AF_UNIX, ## for local socket (using a file). Unsupported on Windows.
|
||||
AF_INET = 2, ## for network protocol IPv4 or
|
||||
AF_INET6 = 23 ## for network protocol IPv6.
|
||||
|
||||
TType* = enum ## second argument to `socket` proc
|
||||
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
|
||||
SOCK_DGRAM = 2, ## datagram service or Datagram Sockets
|
||||
SOCK_RAW = 3, ## raw protocols atop the network layer.
|
||||
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
|
||||
|
||||
TProtocol* = enum ## third argument to `socket` proc
|
||||
IPPROTO_TCP = 6, ## Transmission control protocol.
|
||||
IPPROTO_UDP = 17, ## User datagram protocol.
|
||||
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
|
||||
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
|
||||
IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows.
|
||||
IPPROTO_ICMP ## Control message protocol. Unsupported on Windows.
|
||||
|
||||
TServent* {.pure, final.} = object ## information about a service
|
||||
name*: string
|
||||
aliases*: seq[string]
|
||||
port*: TPort
|
||||
proto*: string
|
||||
|
||||
Thostent* {.pure, final.} = object ## information about a given host
|
||||
name*: string
|
||||
aliases*: seq[string]
|
||||
addrtype*: TDomain
|
||||
length*: int
|
||||
addrList*: seq[string]
|
||||
|
||||
when defined(windows):
|
||||
let
|
||||
osInvalidSocket* = winlean.INVALID_SOCKET
|
||||
|
||||
const
|
||||
IOCPARM_MASK* = 127
|
||||
IOC_IN* = int(-2147483648)
|
||||
FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
|
||||
(102 shl 8) or 126
|
||||
|
||||
proc ioctlsocket*(s: TSocketHandle, cmd: clong,
|
||||
argptr: ptr clong): cint {.
|
||||
stdcall, importc: "ioctlsocket", dynlib: "ws2_32.dll".}
|
||||
else:
|
||||
let
|
||||
osInvalidSocket* = posix.INVALID_SOCKET
|
||||
|
||||
proc `==`*(a, b: TPort): bool {.borrow.}
|
||||
## ``==`` for ports.
|
||||
|
||||
proc `$`*(p: TPort): string {.borrow.}
|
||||
## returns the port number as a string
|
||||
|
||||
proc toInt*(domain: TDomain): cint
|
||||
## Converts the TDomain enum to a platform-dependent ``cint``.
|
||||
|
||||
proc toInt*(typ: TType): cint
|
||||
## Converts the TType enum to a platform-dependent ``cint``.
|
||||
|
||||
proc toInt*(p: TProtocol): cint
|
||||
## Converts the TProtocol enum to a platform-dependent ``cint``.
|
||||
|
||||
when defined(posix):
|
||||
proc toInt(domain: TDomain): cint =
|
||||
case domain
|
||||
of AF_UNIX: result = posix.AF_UNIX
|
||||
of AF_INET: result = posix.AF_INET
|
||||
of AF_INET6: result = posix.AF_INET6
|
||||
else: discard
|
||||
|
||||
proc toInt(typ: TType): cint =
|
||||
case typ
|
||||
of SOCK_STREAM: result = posix.SOCK_STREAM
|
||||
of SOCK_DGRAM: result = posix.SOCK_DGRAM
|
||||
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
|
||||
of SOCK_RAW: result = posix.SOCK_RAW
|
||||
else: discard
|
||||
|
||||
proc toInt(p: TProtocol): cint =
|
||||
case p
|
||||
of IPPROTO_TCP: result = posix.IPPROTO_TCP
|
||||
of IPPROTO_UDP: result = posix.IPPROTO_UDP
|
||||
of IPPROTO_IP: result = posix.IPPROTO_IP
|
||||
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
|
||||
of IPPROTO_RAW: result = posix.IPPROTO_RAW
|
||||
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
|
||||
else: discard
|
||||
|
||||
else:
|
||||
proc toInt(domain: TDomain): cint =
|
||||
result = toU16(ord(domain))
|
||||
|
||||
proc toInt(typ: TType): cint =
|
||||
result = cint(ord(typ))
|
||||
|
||||
proc toInt(p: TProtocol): cint =
|
||||
result = cint(ord(p))
|
||||
|
||||
|
||||
proc newRawSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||
protocol: TProtocol = IPPROTO_TCP): TSocketHandle =
|
||||
## Creates a new socket; returns `InvalidSocket` if an error occurs.
|
||||
socket(toInt(domain), toInt(typ), toInt(protocol))
|
||||
|
||||
proc close*(socket: TSocketHandle) =
|
||||
## closes a socket.
|
||||
when defined(windows):
|
||||
discard winlean.closeSocket(socket)
|
||||
else:
|
||||
discard posix.close(socket)
|
||||
# TODO: These values should not be discarded. An EOS should be raised.
|
||||
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
|
||||
|
||||
proc bindAddr*(socket: TSocketHandle, name: ptr TSockAddr, namelen: TSockLen): cint =
|
||||
result = bindSocket(socket, name, namelen)
|
||||
|
||||
proc listen*(socket: TSocketHandle, backlog = SOMAXCONN): cint {.tags: [FReadIO].} =
|
||||
## Marks ``socket`` as accepting connections.
|
||||
## ``Backlog`` specifies the maximum length of the
|
||||
## queue of pending connections.
|
||||
when defined(windows):
|
||||
result = winlean.listen(socket, cint(backlog))
|
||||
else:
|
||||
result = posix.listen(socket, cint(backlog))
|
||||
|
||||
proc getAddrInfo*(address: string, port: TPort, af: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||
prot: TProtocol = IPPROTO_TCP): ptr TAddrInfo =
|
||||
##
|
||||
##
|
||||
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
|
||||
var hints: TAddrInfo
|
||||
result = nil
|
||||
hints.ai_family = toInt(af)
|
||||
hints.ai_socktype = toInt(typ)
|
||||
hints.ai_protocol = toInt(prot)
|
||||
var gaiResult = getAddrInfo(address, $port, addr(hints), result)
|
||||
if gaiResult != 0'i32:
|
||||
when defined(windows):
|
||||
OSError(OSLastError())
|
||||
else:
|
||||
raise newException(EOS, $gai_strerror(gaiResult))
|
||||
|
||||
proc dealloc*(ai: ptr TAddrInfo) =
|
||||
freeaddrinfo(ai)
|
||||
|
||||
proc ntohl*(x: int32): int32 =
|
||||
## Converts 32-bit integers from network to host byte order.
|
||||
## On machines where the host byte order is the same as network byte order,
|
||||
## this is a no-op; otherwise, it performs a 4-byte swap operation.
|
||||
when cpuEndian == bigEndian: result = x
|
||||
else: result = (x shr 24'i32) or
|
||||
(x shr 8'i32 and 0xff00'i32) or
|
||||
(x shl 8'i32 and 0xff0000'i32) or
|
||||
(x shl 24'i32)
|
||||
|
||||
proc ntohs*(x: int16): int16 =
|
||||
## Converts 16-bit integers from network to host byte order. On machines
|
||||
## where the host byte order is the same as network byte order, this is
|
||||
## a no-op; otherwise, it performs a 2-byte swap operation.
|
||||
when cpuEndian == bigEndian: result = x
|
||||
else: result = (x shr 8'i16) or (x shl 8'i16)
|
||||
|
||||
proc htonl*(x: int32): int32 =
|
||||
## Converts 32-bit integers from host to network byte order. On machines
|
||||
## where the host byte order is the same as network byte order, this is
|
||||
## a no-op; otherwise, it performs a 4-byte swap operation.
|
||||
result = rawsockets.ntohl(x)
|
||||
|
||||
proc htons*(x: int16): int16 =
|
||||
## Converts 16-bit positive integers from host to network byte order.
|
||||
## On machines where the host byte order is the same as network byte
|
||||
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
|
||||
result = rawsockets.ntohs(x)
|
||||
|
||||
proc getServByName*(name, proto: string): TServent {.tags: [FReadIO].} =
|
||||
## Searches the database from the beginning and finds the first entry for
|
||||
## which the service name specified by ``name`` matches the s_name member
|
||||
## and the protocol name specified by ``proto`` matches the s_proto member.
|
||||
##
|
||||
## On posix this will search through the ``/etc/services`` file.
|
||||
when defined(Windows):
|
||||
var s = winlean.getservbyname(name, proto)
|
||||
else:
|
||||
var s = posix.getservbyname(name, proto)
|
||||
if s == nil: raise newException(EOS, "Service not found.")
|
||||
result.name = $s.s_name
|
||||
result.aliases = cstringArrayToSeq(s.s_aliases)
|
||||
result.port = TPort(s.s_port)
|
||||
result.proto = $s.s_proto
|
||||
|
||||
proc getServByPort*(port: TPort, proto: string): TServent {.tags: [FReadIO].} =
|
||||
## Searches the database from the beginning and finds the first entry for
|
||||
## which the port specified by ``port`` matches the s_port member and the
|
||||
## protocol name specified by ``proto`` matches the s_proto member.
|
||||
##
|
||||
## On posix this will search through the ``/etc/services`` file.
|
||||
when defined(Windows):
|
||||
var s = winlean.getservbyport(ze(int16(port)).cint, proto)
|
||||
else:
|
||||
var s = posix.getservbyport(ze(int16(port)).cint, proto)
|
||||
if s == nil: raise newException(EOS, "Service not found.")
|
||||
result.name = $s.s_name
|
||||
result.aliases = cstringArrayToSeq(s.s_aliases)
|
||||
result.port = TPort(s.s_port)
|
||||
result.proto = $s.s_proto
|
||||
|
||||
proc getHostByAddr*(ip: string): Thostent {.tags: [FReadIO].} =
|
||||
## This function will lookup the hostname of an IP Address.
|
||||
var myaddr: TInAddr
|
||||
myaddr.s_addr = inet_addr(ip)
|
||||
|
||||
when defined(windows):
|
||||
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
|
||||
cint(rawsockets.AF_INET))
|
||||
if s == nil: osError(osLastError())
|
||||
else:
|
||||
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).TSocklen,
|
||||
cint(posix.AF_INET))
|
||||
if s == nil:
|
||||
raise newException(EOS, $hstrerror(h_errno))
|
||||
|
||||
result.name = $s.h_name
|
||||
result.aliases = cstringArrayToSeq(s.h_aliases)
|
||||
when defined(windows):
|
||||
result.addrtype = TDomain(s.h_addrtype)
|
||||
else:
|
||||
if s.h_addrtype == posix.AF_INET:
|
||||
result.addrtype = AF_INET
|
||||
elif s.h_addrtype == posix.AF_INET6:
|
||||
result.addrtype = AF_INET6
|
||||
else:
|
||||
raise newException(EOS, "unknown h_addrtype")
|
||||
result.addrList = cstringArrayToSeq(s.h_addr_list)
|
||||
result.length = int(s.h_length)
|
||||
|
||||
proc getHostByName*(name: string): Thostent {.tags: [FReadIO].} =
|
||||
## This function will lookup the IP address of a hostname.
|
||||
when defined(Windows):
|
||||
var s = winlean.gethostbyname(name)
|
||||
else:
|
||||
var s = posix.gethostbyname(name)
|
||||
if s == nil: osError(osLastError())
|
||||
result.name = $s.h_name
|
||||
result.aliases = cstringArrayToSeq(s.h_aliases)
|
||||
when defined(windows):
|
||||
result.addrtype = TDomain(s.h_addrtype)
|
||||
else:
|
||||
if s.h_addrtype == posix.AF_INET:
|
||||
result.addrtype = AF_INET
|
||||
elif s.h_addrtype == posix.AF_INET6:
|
||||
result.addrtype = AF_INET6
|
||||
else:
|
||||
raise newException(EOS, "unknown h_addrtype")
|
||||
result.addrList = cstringArrayToSeq(s.h_addr_list)
|
||||
result.length = int(s.h_length)
|
||||
|
||||
proc getSockName*(socket: TSocketHandle): TPort =
|
||||
## returns the socket's associated port number.
|
||||
var name: Tsockaddr_in
|
||||
when defined(Windows):
|
||||
name.sin_family = int16(ord(AF_INET))
|
||||
else:
|
||||
name.sin_family = posix.AF_INET
|
||||
#name.sin_port = htons(cint16(port))
|
||||
#name.sin_addr.s_addr = htonl(INADDR_ANY)
|
||||
var namelen = sizeof(name).TSocklen
|
||||
if getsockname(socket, cast[ptr TSockAddr](addr(name)),
|
||||
addr(namelen)) == -1'i32:
|
||||
osError(osLastError())
|
||||
result = TPort(rawsockets.ntohs(name.sin_port))
|
||||
|
||||
proc getSockOptInt*(socket: TSocketHandle, level, optname: int): int {.
|
||||
tags: [FReadIO].} =
|
||||
## getsockopt for integer options.
|
||||
var res: cint
|
||||
var size = sizeof(res).TSocklen
|
||||
if getsockopt(socket, cint(level), cint(optname),
|
||||
addr(res), addr(size)) < 0'i32:
|
||||
osError(osLastError())
|
||||
result = int(res)
|
||||
|
||||
proc setSockOptInt*(socket: TSocketHandle, level, optname, optval: int) {.
|
||||
tags: [FWriteIO].} =
|
||||
## setsockopt for integer options.
|
||||
var value = cint(optval)
|
||||
if setsockopt(socket, cint(level), cint(optname), addr(value),
|
||||
sizeof(value).TSocklen) < 0'i32:
|
||||
osError(osLastError())
|
||||
|
||||
proc setBlocking*(s: TSocketHandle, blocking: bool) =
|
||||
## Sets blocking mode on socket.
|
||||
##
|
||||
## Raises EOS on error.
|
||||
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())
|
||||
|
||||
proc timeValFromMilliseconds(timeout = 500): Ttimeval =
|
||||
if timeout != -1:
|
||||
var seconds = timeout div 1000
|
||||
result.tv_sec = seconds.int32
|
||||
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
||||
|
||||
proc createFdSet(fd: var TFdSet, s: seq[TSocketHandle], m: var int) =
|
||||
FD_ZERO(fd)
|
||||
for i in items(s):
|
||||
m = max(m, int(i))
|
||||
FD_SET(i, fd)
|
||||
|
||||
proc pruneSocketSet(s: var seq[TSocketHandle], fd: var TFdSet) =
|
||||
var i = 0
|
||||
var L = s.len
|
||||
while i < L:
|
||||
if FD_ISSET(s[i], fd) == 0'i32:
|
||||
s[i] = s[L-1]
|
||||
dec(L)
|
||||
else:
|
||||
inc(i)
|
||||
setLen(s, L)
|
||||
|
||||
proc select*(readfds: var seq[TSocketHandle], timeout = 500): int =
|
||||
## Traditional select function. This function will return the number of
|
||||
## sockets that are ready to be read from, written to, or which have errors.
|
||||
## If there are none; 0 is returned.
|
||||
## ``Timeout`` is in miliseconds and -1 can be specified for no timeout.
|
||||
##
|
||||
## A socket is removed from the specific ``seq`` when it has data waiting to
|
||||
## be read/written to or has errors (``exceptfds``).
|
||||
var tv {.noInit.}: Ttimeval = timeValFromMilliseconds(timeout)
|
||||
|
||||
var rd: TFdSet
|
||||
var m = 0
|
||||
createFdSet((rd), readfds, m)
|
||||
|
||||
if timeout != -1:
|
||||
result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
|
||||
else:
|
||||
result = int(select(cint(m+1), addr(rd), nil, nil, nil))
|
||||
|
||||
pruneSocketSet(readfds, (rd))
|
||||
|
||||
proc selectWrite*(writefds: var seq[TSocketHandle],
|
||||
timeout = 500): int {.tags: [FReadIO].} =
|
||||
## When a socket in ``writefds`` is ready to be written to then a non-zero
|
||||
## value will be returned specifying the count of the sockets which can be
|
||||
## written to. The sockets which can be written to will also be removed
|
||||
## from ``writefds``.
|
||||
##
|
||||
## ``timeout`` is specified in miliseconds and ``-1`` can be specified for
|
||||
## an unlimited time.
|
||||
var tv {.noInit.}: Ttimeval = timeValFromMilliseconds(timeout)
|
||||
|
||||
var wr: TFdSet
|
||||
var m = 0
|
||||
createFdSet((wr), writefds, m)
|
||||
|
||||
if timeout != -1:
|
||||
result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv)))
|
||||
else:
|
||||
result = int(select(cint(m+1), nil, addr(wr), nil, nil))
|
||||
|
||||
pruneSocketSet(writefds, (wr))
|
||||
|
||||
when defined(Windows):
|
||||
var wsa: TWSADATA
|
||||
if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError())
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2013 Dominik Picheta
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
|
|
@ -9,212 +9,230 @@
|
|||
|
||||
# TODO: Docs.
|
||||
|
||||
import tables, os, unsigned
|
||||
when defined(windows):
|
||||
import winlean
|
||||
else:
|
||||
import posix
|
||||
import tables, os, unsigned, hashes
|
||||
|
||||
when defined(linux): import posix, epoll
|
||||
elif defined(windows): import winlean
|
||||
|
||||
proc hash*(x: TSocketHandle): THash {.borrow.}
|
||||
proc `$`*(x: TSocketHandle): string {.borrow.}
|
||||
|
||||
type
|
||||
TEvent* = enum
|
||||
EvRead, EvWrite
|
||||
|
||||
TSelectorKey* = object
|
||||
fd: cint
|
||||
events: set[TEvent]
|
||||
data: PObject
|
||||
PSelectorKey* = ref object
|
||||
fd*: TSocketHandle
|
||||
events*: set[TEvent] ## The events which ``fd`` listens for.
|
||||
data*: PObject ## User object.
|
||||
|
||||
TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]]
|
||||
TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]]
|
||||
|
||||
PSelector* = ref object of PObject ## Selector interface.
|
||||
fds*: TTable[cint, TSelectorKey]
|
||||
registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent],
|
||||
data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].}
|
||||
unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].}
|
||||
selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].}
|
||||
closeImpl*: proc (s: PSelector) {.nimcall.}
|
||||
|
||||
template initSelector(r: expr) =
|
||||
new r
|
||||
r.fds = initTable[cint, TSelectorKey]()
|
||||
|
||||
proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject):
|
||||
TSelectorKey =
|
||||
if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data)
|
||||
|
||||
proc unregister*(s: PSelector, fd: cint): TSelectorKey =
|
||||
##
|
||||
## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey``
|
||||
## will only have the ``fd`` field set. This is an optimisation and may
|
||||
## change in the future if a viable use case is presented.
|
||||
if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd)
|
||||
|
||||
proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] =
|
||||
##
|
||||
## The ``events`` field of the returned ``key`` contains the original events
|
||||
## for which the ``fd`` was bound. This is contrary to the ``events`` field
|
||||
## of the ``TReadyInfo`` tuple which determines which events are ready
|
||||
## on the ``fd``.
|
||||
|
||||
if not s.selectImpl.isNil: result = s.selectImpl(s, timeout)
|
||||
|
||||
proc close*(s: PSelector) =
|
||||
if not s.closeImpl.isNil: s.closeImpl(s)
|
||||
|
||||
# ---- Select() ----------------------------------------------------------------
|
||||
|
||||
type
|
||||
PSelectSelector* = ref object of PSelector ## Implementation of select()
|
||||
|
||||
proc ssRegister(s: PSelector, fd: cint, events: set[TEvent],
|
||||
data: PObject): TSelectorKey =
|
||||
if s.fds.hasKey(fd):
|
||||
raise newException(EInvalidValue, "FD already exists in selector.")
|
||||
var sk = TSelectorKey(fd: fd, events: events, data: data)
|
||||
s.fds[fd] = sk
|
||||
result = sk
|
||||
|
||||
proc ssUnregister(s: PSelector, fd: cint): TSelectorKey =
|
||||
result = s.fds[fd]
|
||||
s.fds.del(fd)
|
||||
|
||||
proc ssClose(s: PSelector) = nil
|
||||
|
||||
proc timeValFromMilliseconds(timeout: int): TTimeVal =
|
||||
if timeout != -1:
|
||||
var seconds = timeout div 1000
|
||||
result.tv_sec = seconds.int32
|
||||
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
|
||||
|
||||
proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey],
|
||||
m: var int) =
|
||||
FD_ZERO(rd); FD_ZERO(wr)
|
||||
for k, v in pairs(fds):
|
||||
if EvRead in v.events:
|
||||
m = max(m, int(k))
|
||||
FD_SET(k, rd)
|
||||
if EvWrite in v.events:
|
||||
m = max(m, int(k))
|
||||
FD_SET(k, wr)
|
||||
|
||||
proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]):
|
||||
seq[TReadyInfo] =
|
||||
result = @[]
|
||||
for k, v in pairs(fds):
|
||||
var events: set[TEvent] = {}
|
||||
if FD_ISSET(k, rd) != 0'i32:
|
||||
events = events + {EvRead}
|
||||
if FD_ISSET(k, wr) != 0'i32:
|
||||
events = events + {EvWrite}
|
||||
result.add((v, events))
|
||||
|
||||
proc select(fds: TTable[cint, TSelectorKey], timeout = 500):
|
||||
seq[TReadyInfo] =
|
||||
var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
|
||||
|
||||
var rd, wr: TFdSet
|
||||
var m = 0
|
||||
createFdSet(rd, wr, fds, m)
|
||||
|
||||
var retCode = 0
|
||||
if timeout != -1:
|
||||
retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv)))
|
||||
else:
|
||||
retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil))
|
||||
|
||||
if retCode < 0:
|
||||
OSError(OSLastError())
|
||||
elif retCode == 0:
|
||||
return @[]
|
||||
else:
|
||||
return getReadyFDs(rd, wr, fds)
|
||||
|
||||
proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
|
||||
result = select(s.fds, timeout)
|
||||
|
||||
proc newSelectSelector*(): PSelectSelector =
|
||||
initSelector(result)
|
||||
result.registerImpl = ssRegister
|
||||
result.unregisterImpl = ssUnregister
|
||||
result.selectImpl = ssSelect
|
||||
result.closeImpl = ssClose
|
||||
|
||||
# ---- Epoll -------------------------------------------------------------------
|
||||
|
||||
when defined(linux):
|
||||
import epoll
|
||||
when defined(linux) or defined(nimdoc):
|
||||
type
|
||||
PEpollSelector* = ref object of PSelector
|
||||
PSelector* = ref object
|
||||
epollFD: cint
|
||||
events: array[64, ptr epoll_event]
|
||||
events: array[64, epoll_event]
|
||||
fds: TTable[TSocketHandle, PSelectorKey]
|
||||
|
||||
TDataWrapper = object
|
||||
fd: cint
|
||||
boundEvents: set[TEvent] ## The events which ``fd`` listens for.
|
||||
data: PObject ## User object.
|
||||
|
||||
proc esRegister(s: PSelector, fd: cint, events: set[TEvent],
|
||||
data: PObject): TSelectorKey =
|
||||
var es = PEpollSelector(s)
|
||||
var event: epoll_event
|
||||
proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event =
|
||||
if EvRead in events:
|
||||
event.events = EPOLLIN
|
||||
result.events = EPOLLIN
|
||||
if EvWrite in events:
|
||||
event.events = event.events or EPOLLOUT
|
||||
|
||||
var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd
|
||||
dw.fd = fd
|
||||
dw.boundEvents = events
|
||||
dw.data = data
|
||||
event.data.thePtr = dw
|
||||
|
||||
if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
|
||||
OSError(OSLastError())
|
||||
|
||||
result = TSelectorKey(fd: fd, events: events, data: data)
|
||||
result.events = result.events or EPOLLOUT
|
||||
result.events = result.events or EPOLLRDHUP
|
||||
result.data.fd = fd.cint
|
||||
|
||||
proc esUnregister(s: PSelector, fd: cint): TSelectorKey =
|
||||
# We cannot find out the information about this ``fd`` from the epoll
|
||||
# context. As such I will simply return an almost empty TSelectorKey.
|
||||
var es = PEpollSelector(s)
|
||||
if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
|
||||
proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent],
|
||||
data: PObject): PSelectorKey {.discardable.} =
|
||||
## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent
|
||||
## ``events``.
|
||||
var event = createEventStruct(events, fd)
|
||||
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
|
||||
OSError(OSLastError())
|
||||
# We could fill in the ``fds`` TTable to get the info, but that wouldn't
|
||||
# be nice for our memory.
|
||||
result = TSelectorKey(fd: fd, events: {}, data: nil)
|
||||
|
||||
var key = PSelectorKey(fd: fd, events: events, data: data)
|
||||
|
||||
s.fds[fd] = key
|
||||
result = key
|
||||
|
||||
proc update*(s: PSelector, fd: TSocketHandle,
|
||||
events: set[TEvent]): PSelectorKey {.discardable.} =
|
||||
## Updates the events which ``fd`` wants notifications for.
|
||||
if s.fds[fd].events != events:
|
||||
var event = createEventStruct(events, fd)
|
||||
|
||||
s.fds[fd].events = events
|
||||
if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0:
|
||||
OSError(OSLastError())
|
||||
result = s.fds[fd]
|
||||
|
||||
proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} =
|
||||
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
|
||||
let err = OSLastError()
|
||||
if err.cint notin {ENOENT, EBADF}: # TODO: Why do we sometimes get an EBADF? Is this normal?
|
||||
OSError(err)
|
||||
result = s.fds[fd]
|
||||
s.fds.del(fd)
|
||||
|
||||
proc esClose(s: PSelector) =
|
||||
var es = PEpollSelector(s)
|
||||
if es.epollFD.close() != 0: OSError(OSLastError())
|
||||
dealloc(addr es.events) # TODO: Test this
|
||||
proc close*(s: PSelector) =
|
||||
if s.epollFD.close() != 0: OSError(OSLastError())
|
||||
dealloc(addr s.events) # TODO: Test this
|
||||
|
||||
proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
|
||||
proc epollHasFd(s: PSelector, fd: TSocketHandle): bool =
|
||||
result = true
|
||||
var event = createEventStruct(s.fds[fd].events, fd)
|
||||
if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0:
|
||||
let err = osLastError()
|
||||
if err.cint in {ENOENT, EBADF}:
|
||||
return false
|
||||
OSError(OSLastError())
|
||||
|
||||
proc select*(s: PSelector, timeout: int): seq[TReadyInfo] =
|
||||
##
|
||||
## The ``events`` field of the returned ``key`` contains the original events
|
||||
## for which the ``fd`` was bound. This is contrary to the ``events`` field
|
||||
## of the ``TReadyInfo`` tuple which determines which events are ready
|
||||
## on the ``fd``.
|
||||
result = @[]
|
||||
var es = PEpollSelector(s)
|
||||
|
||||
let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint)
|
||||
let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint)
|
||||
if evNum < 0: OSError(OSLastError())
|
||||
if evNum == 0: return @[]
|
||||
for i in 0 .. <evNum:
|
||||
let fd = s.events[i].data.fd.TSocketHandle
|
||||
|
||||
var evSet: set[TEvent] = {}
|
||||
if (es.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
|
||||
if (es.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,
|
||||
data: dw.data)
|
||||
if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
|
||||
if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
|
||||
let selectorKey = s.fds[fd]
|
||||
assert selectorKey != nil
|
||||
result.add((selectorKey, evSet))
|
||||
|
||||
#echo("Epoll: ", result[i].key.fd, " ", result[i].events, " ", result[i].key.events)
|
||||
|
||||
proc newEpollSelector*(): PEpollSelector =
|
||||
proc newSelector*(): PSelector =
|
||||
new result
|
||||
result.epollFD = epoll_create(64)
|
||||
result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64))
|
||||
result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64))
|
||||
result.fds = initTable[TSocketHandle, PSelectorKey]()
|
||||
if result.epollFD < 0:
|
||||
OSError(OSLastError())
|
||||
result.registerImpl = esRegister
|
||||
result.unregisterImpl = esUnregister
|
||||
result.closeImpl = esClose
|
||||
result.selectImpl = esSelect
|
||||
|
||||
proc contains*(s: PSelector, fd: TSocketHandle): bool =
|
||||
## Determines whether selector contains a file descriptor.
|
||||
if s.fds.hasKey(fd):
|
||||
# Ensure the underlying epoll instance still contains this fd.
|
||||
result = epollHasFd(s, fd)
|
||||
else:
|
||||
return false
|
||||
|
||||
proc contains*(s: PSelector, key: PSelectorKey): bool =
|
||||
## Determines whether selector contains this selector key. More accurate
|
||||
## than checking if the file descriptor is in the selector because it
|
||||
## ensures that the keys are equal. File descriptors may not always be
|
||||
## unique especially when an fd is closed and then a new one is opened,
|
||||
## the new one may have the same value.
|
||||
return key.fd in s and s.fds[key.fd] == key
|
||||
|
||||
proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey =
|
||||
## Retrieves the selector key for ``fd``.
|
||||
return s.fds[fd]
|
||||
|
||||
elif defined(windows):
|
||||
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:
|
||||
# Select()
|
||||
|
|
@ -224,11 +242,12 @@ when isMainModule:
|
|||
sock: TSocket
|
||||
|
||||
var sock = socket()
|
||||
sock.setBlocking(false)
|
||||
sock.connect("irc.freenode.net", TPort(6667))
|
||||
|
||||
var selector = newEpollSelector()
|
||||
var selector = newSelector()
|
||||
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
|
||||
while true:
|
||||
let ready = selector.select(1000)
|
||||
|
|
@ -236,6 +255,7 @@ when isMainModule:
|
|||
if ready.len > 0: echo ready[0].events
|
||||
i.inc
|
||||
if i == 6:
|
||||
assert selector.unregister(sock.getFD).fd == sock.getFD
|
||||
selector.close()
|
||||
break
|
||||
|
||||
|
|
@ -246,4 +266,4 @@ when isMainModule:
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,202 +0,0 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2014 Dominik Picheta
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module implements a low-level cross-platform sockets interface. Look
|
||||
## at the ``net`` module for the higher-level version.
|
||||
|
||||
import unsigned, os
|
||||
|
||||
when hostos == "solaris":
|
||||
{.passl: "-lsocket -lnsl".}
|
||||
|
||||
when defined(Windows):
|
||||
import winlean
|
||||
else:
|
||||
import posix
|
||||
|
||||
export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen,
|
||||
inet_ntoa
|
||||
|
||||
type
|
||||
|
||||
TPort* = distinct uint16 ## port type
|
||||
|
||||
TDomain* = enum ## domain, which specifies the protocol family of the
|
||||
## created socket. Other domains than those that are listed
|
||||
## here are unsupported.
|
||||
AF_UNIX, ## for local socket (using a file). Unsupported on Windows.
|
||||
AF_INET = 2, ## for network protocol IPv4 or
|
||||
AF_INET6 = 23 ## for network protocol IPv6.
|
||||
|
||||
TType* = enum ## second argument to `socket` proc
|
||||
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
|
||||
SOCK_DGRAM = 2, ## datagram service or Datagram Sockets
|
||||
SOCK_RAW = 3, ## raw protocols atop the network layer.
|
||||
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
|
||||
|
||||
TProtocol* = enum ## third argument to `socket` proc
|
||||
IPPROTO_TCP = 6, ## Transmission control protocol.
|
||||
IPPROTO_UDP = 17, ## User datagram protocol.
|
||||
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
|
||||
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
|
||||
IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows.
|
||||
IPPROTO_ICMP ## Control message protocol. Unsupported on Windows.
|
||||
|
||||
TServent* {.pure, final.} = object ## information about a service
|
||||
name*: string
|
||||
aliases*: seq[string]
|
||||
port*: TPort
|
||||
proto*: string
|
||||
|
||||
Thostent* {.pure, final.} = object ## information about a given host
|
||||
name*: string
|
||||
aliases*: seq[string]
|
||||
addrtype*: TDomain
|
||||
length*: int
|
||||
addrList*: seq[string]
|
||||
|
||||
when defined(windows):
|
||||
let
|
||||
OSInvalidSocket* = winlean.INVALID_SOCKET
|
||||
else:
|
||||
let
|
||||
OSInvalidSocket* = posix.INVALID_SOCKET
|
||||
|
||||
proc `==`*(a, b: TPort): bool {.borrow.}
|
||||
## ``==`` for ports.
|
||||
|
||||
proc `$`*(p: TPort): string {.borrow.}
|
||||
## returns the port number as a string
|
||||
|
||||
proc toInt*(domain: TDomain): cint
|
||||
## Converts the TDomain enum to a platform-dependent ``cint``.
|
||||
|
||||
proc toInt*(typ: TType): cint
|
||||
## Converts the TType enum to a platform-dependent ``cint``.
|
||||
|
||||
proc toInt*(p: TProtocol): cint
|
||||
## Converts the TProtocol enum to a platform-dependent ``cint``.
|
||||
|
||||
when defined(posix):
|
||||
proc toInt(domain: TDomain): cint =
|
||||
case domain
|
||||
of AF_UNIX: result = posix.AF_UNIX
|
||||
of AF_INET: result = posix.AF_INET
|
||||
of AF_INET6: result = posix.AF_INET6
|
||||
else: nil
|
||||
|
||||
proc toInt(typ: TType): cint =
|
||||
case typ
|
||||
of SOCK_STREAM: result = posix.SOCK_STREAM
|
||||
of SOCK_DGRAM: result = posix.SOCK_DGRAM
|
||||
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
|
||||
of SOCK_RAW: result = posix.SOCK_RAW
|
||||
else: nil
|
||||
|
||||
proc toInt(p: TProtocol): cint =
|
||||
case p
|
||||
of IPPROTO_TCP: result = posix.IPPROTO_TCP
|
||||
of IPPROTO_UDP: result = posix.IPPROTO_UDP
|
||||
of IPPROTO_IP: result = posix.IPPROTO_IP
|
||||
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
|
||||
of IPPROTO_RAW: result = posix.IPPROTO_RAW
|
||||
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
|
||||
else: nil
|
||||
|
||||
else:
|
||||
proc toInt(domain: TDomain): cint =
|
||||
result = toU16(ord(domain))
|
||||
|
||||
proc toInt(typ: TType): cint =
|
||||
result = cint(ord(typ))
|
||||
|
||||
proc toInt(p: TProtocol): cint =
|
||||
result = cint(ord(p))
|
||||
|
||||
|
||||
proc socket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||
protocol: TProtocol = IPPROTO_TCP): TSocketHandle =
|
||||
## Creates a new socket; returns `InvalidSocket` if an error occurs.
|
||||
|
||||
# TODO: The function which will use this will raise EOS.
|
||||
socket(toInt(domain), toInt(typ), toInt(protocol))
|
||||
|
||||
proc close*(socket: TSocketHandle) =
|
||||
## closes a socket.
|
||||
when defined(windows):
|
||||
discard winlean.closeSocket(socket)
|
||||
else:
|
||||
discard posix.close(socket)
|
||||
# TODO: These values should not be discarded. An EOS should be raised.
|
||||
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
|
||||
|
||||
proc bindAddr*(socket: TSocketHandle, name: ptr TSockAddr, namelen: TSockLen): cint =
|
||||
result = bindSocket(socket, name, namelen)
|
||||
|
||||
proc listen*(socket: TSocketHandle, backlog = SOMAXCONN) {.tags: [FReadIO].} =
|
||||
## Marks ``socket`` as accepting connections.
|
||||
## ``Backlog`` specifies the maximum length of the
|
||||
## queue of pending connections.
|
||||
when defined(windows):
|
||||
if winlean.listen(socket, cint(backlog)) < 0'i32: osError(osLastError())
|
||||
else:
|
||||
if posix.listen(socket, cint(backlog)) < 0'i32: osError(osLastError())
|
||||
|
||||
proc getAddrInfo*(address: string, port: TPort, af: TDomain = AF_INET, typ: TType = SOCK_STREAM,
|
||||
prot: TProtocol = IPPROTO_TCP): ptr TAddrInfo =
|
||||
##
|
||||
##
|
||||
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
|
||||
var hints: TAddrInfo
|
||||
result = nil
|
||||
hints.ai_family = toInt(af)
|
||||
hints.ai_socktype = toInt(typ)
|
||||
hints.ai_protocol = toInt(prot)
|
||||
var gaiResult = getAddrInfo(address, $port, addr(hints), result)
|
||||
if gaiResult != 0'i32:
|
||||
when defined(windows):
|
||||
OSError(OSLastError())
|
||||
else:
|
||||
raise newException(EOS, $gai_strerror(gaiResult))
|
||||
|
||||
proc dealloc*(ai: ptr TAddrInfo) =
|
||||
freeaddrinfo(ai)
|
||||
|
||||
proc ntohl*(x: int32): int32 =
|
||||
## Converts 32-bit integers from network to host byte order.
|
||||
## On machines where the host byte order is the same as network byte order,
|
||||
## this is a no-op; otherwise, it performs a 4-byte swap operation.
|
||||
when cpuEndian == bigEndian: result = x
|
||||
else: result = (x shr 24'i32) or
|
||||
(x shr 8'i32 and 0xff00'i32) or
|
||||
(x shl 8'i32 and 0xff0000'i32) or
|
||||
(x shl 24'i32)
|
||||
|
||||
proc ntohs*(x: int16): int16 =
|
||||
## Converts 16-bit integers from network to host byte order. On machines
|
||||
## where the host byte order is the same as network byte order, this is
|
||||
## a no-op; otherwise, it performs a 2-byte swap operation.
|
||||
when cpuEndian == bigEndian: result = x
|
||||
else: result = (x shr 8'i16) or (x shl 8'i16)
|
||||
|
||||
proc htonl*(x: int32): int32 =
|
||||
## Converts 32-bit integers from host to network byte order. On machines
|
||||
## where the host byte order is the same as network byte order, this is
|
||||
## a no-op; otherwise, it performs a 4-byte swap operation.
|
||||
result = sockets2.ntohl(x)
|
||||
|
||||
proc htons*(x: int16): int16 =
|
||||
## Converts 16-bit positive integers from host to network byte order.
|
||||
## On machines where the host byte order is the same as network byte
|
||||
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
|
||||
result = sockets2.ntohs(x)
|
||||
|
||||
when defined(Windows):
|
||||
var wsa: TWSADATA
|
||||
if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError())
|
||||
|
|
@ -557,6 +557,119 @@ proc `$`*(m: TMonth): string =
|
|||
"November", "December"]
|
||||
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 =
|
||||
## This function formats `info` as specified by `f`. The following format
|
||||
## 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``
|
||||
## ========== ================================================================================= ================================================
|
||||
##
|
||||
## Other strings can be inserted by putting them in ``''``. For example ``hh'->'mm`` will give ``01->56``.
|
||||
## The following characters can be inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` ``,``
|
||||
## Other strings can be inserted by putting them in ``''``. For example
|
||||
## ``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 = ""
|
||||
var i = 0
|
||||
|
|
@ -600,112 +716,8 @@ proc format*(info: TTimeInfo, f: string): string =
|
|||
while true:
|
||||
case f[i]
|
||||
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
|
||||
case currentF
|
||||
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)
|
||||
|
||||
format_token(info, currentF, result)
|
||||
|
||||
currentF = ""
|
||||
if f[i] == '\0': break
|
||||
|
||||
|
|
@ -716,7 +728,15 @@ proc format*(info: TTimeInfo, f: string): string =
|
|||
inc(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)
|
||||
|
||||
{.pop.}
|
||||
|
|
@ -727,11 +747,15 @@ when isMainModule:
|
|||
|
||||
var t = getGMTime(fromSeconds(2147483647))
|
||||
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 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" &
|
||||
" 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"
|
||||
|
||||
assert t.format("yyyyMMddhhmmss") == "20380119031407"
|
||||
|
||||
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" &
|
||||
|
|
|
|||
6
lib/stdlib.babel
Normal file
6
lib/stdlib.babel
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[Package]
|
||||
name = "stdlib"
|
||||
version = "0.9.0"
|
||||
author = "Dominik Picheta"
|
||||
description = "Nimrod's standard library."
|
||||
license = "MIT"
|
||||
142
lib/system.nim
142
lib/system.nim
|
|
@ -185,6 +185,8 @@ proc `..`*[T](b: T): TSlice[T] {.noSideEffect, inline.} =
|
|||
|
||||
when not defined(niminheritable):
|
||||
{.pragma: inheritable.}
|
||||
when not defined(nimunion):
|
||||
{.pragma: unchecked.}
|
||||
|
||||
const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \
|
||||
## "fake variables" like 'var EBADF {.importc.}: cint'.
|
||||
|
|
@ -194,9 +196,10 @@ when not defined(JS):
|
|||
TGenericSeq {.compilerproc, pure, inheritable.} = object
|
||||
len, reserved: int
|
||||
PGenericSeq {.exportc.} = ptr TGenericSeq
|
||||
UncheckedCharArray {.unchecked.} = array[0..100_000_000, char]
|
||||
# len and space without counting the terminating zero:
|
||||
NimStringDesc {.compilerproc, final.} = object of TGenericSeq
|
||||
data: array[0..100_000_000, char]
|
||||
data: UncheckedCharArray
|
||||
NimString = ptr NimStringDesc
|
||||
|
||||
when not defined(JS) and not defined(NimrodVM):
|
||||
|
|
@ -257,6 +260,7 @@ type
|
|||
## system raises.
|
||||
EIO* = object of ESystem ## raised if an IO error occured.
|
||||
EOS* = object of ESystem ## raised if an operating system service failed.
|
||||
errorCode*: int32 ## OS-defined error code describing this error.
|
||||
EInvalidLibrary* = object of EOS ## raised if a dynamic library
|
||||
## could not be loaded.
|
||||
EResourceExhausted* = object of ESystem ## raised if a resource request
|
||||
|
|
@ -1161,6 +1165,14 @@ when not defined(nimrodVM):
|
|||
## from it before writing to it is undefined behaviour!
|
||||
## The allocated memory belongs to its allocating thread!
|
||||
## Use `allocShared` to allocate from a shared heap.
|
||||
proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||
## allocates a new memory block with at least ``T.sizeof * size``
|
||||
## bytes. The block has to be freed with ``resize(block, 0)`` or
|
||||
## ``free(block)``. The block is not initialized, so reading
|
||||
## from it before writing to it is undefined behaviour!
|
||||
## The allocated memory belongs to its allocating thread!
|
||||
## Use `createSharedU` to allocate from a shared heap.
|
||||
cast[ptr T](alloc(T.sizeof * size))
|
||||
proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].}
|
||||
## allocates a new memory block with at least ``size`` bytes. The
|
||||
## block has to be freed with ``realloc(block, 0)`` or
|
||||
|
|
@ -1168,14 +1180,31 @@ when not defined(nimrodVM):
|
|||
## containing zero, so it is somewhat safer than ``alloc``.
|
||||
## The allocated memory belongs to its allocating thread!
|
||||
## Use `allocShared0` to allocate from a shared heap.
|
||||
proc realloc*(p: pointer, newsize: int): pointer {.noconv, rtl, tags: [].}
|
||||
proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||
## allocates a new memory block with at least ``T.sizeof * size``
|
||||
## bytes. The block has to be freed with ``resize(block, 0)`` or
|
||||
## ``free(block)``. The block is initialized with all bytes
|
||||
## containing zero, so it is somewhat safer than ``createU``.
|
||||
## The allocated memory belongs to its allocating thread!
|
||||
## Use `createShared` to allocate from a shared heap.
|
||||
cast[ptr T](alloc0(T.sizeof * size))
|
||||
proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].}
|
||||
## grows or shrinks a given memory block. If p is **nil** then a new
|
||||
## memory block is returned. In either way the block has at least
|
||||
## ``newsize`` bytes. If ``newsize == 0`` and p is not **nil**
|
||||
## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
|
||||
## ``realloc`` calls ``dealloc(p)``. In other cases the block has to
|
||||
## be freed with ``dealloc``.
|
||||
## The allocated memory belongs to its allocating thread!
|
||||
## Use `reallocShared` to reallocate from a shared heap.
|
||||
proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} =
|
||||
## grows or shrinks a given memory block. If p is **nil** then a new
|
||||
## memory block is returned. In either way the block has at least
|
||||
## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not
|
||||
## **nil** ``resize`` calls ``free(p)``. In other cases the block
|
||||
## has to be freed with ``free``. The allocated memory belongs to
|
||||
## its allocating thread!
|
||||
## Use `resizeShared` to reallocate from a shared heap.
|
||||
cast[ptr T](realloc(p, T.sizeof * newSize))
|
||||
proc dealloc*(p: pointer) {.noconv, rtl, tags: [].}
|
||||
## frees the memory allocated with ``alloc``, ``alloc0`` or
|
||||
## ``realloc``. This procedure is dangerous! If one forgets to
|
||||
|
|
@ -1184,31 +1213,60 @@ when not defined(nimrodVM):
|
|||
## or other memory may be corrupted.
|
||||
## The freed memory must belong to its allocating thread!
|
||||
## Use `deallocShared` to deallocate from a shared heap.
|
||||
|
||||
proc free*[T](p: ptr T) {.inline.} =
|
||||
dealloc(p)
|
||||
proc allocShared*(size: int): pointer {.noconv, rtl.}
|
||||
## allocates a new memory block on the shared heap with at
|
||||
## least ``size`` bytes. The block has to be freed with
|
||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
|
||||
## is not initialized, so reading from it before writing to it is
|
||||
## undefined behaviour!
|
||||
proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||
## allocates a new memory block on the shared heap with at
|
||||
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
||||
## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block
|
||||
## is not initialized, so reading from it before writing to it is
|
||||
## undefined behaviour!
|
||||
cast[ptr T](allocShared(T.sizeof * size))
|
||||
proc allocShared0*(size: int): pointer {.noconv, rtl.}
|
||||
## allocates a new memory block on the shared heap with at
|
||||
## least ``size`` bytes. The block has to be freed with
|
||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
|
||||
## The block is initialized with all bytes
|
||||
## containing zero, so it is somewhat safer than ``allocShared``.
|
||||
proc reallocShared*(p: pointer, newsize: int): pointer {.noconv, rtl.}
|
||||
proc createShared*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||
## allocates a new memory block on the shared heap with at
|
||||
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
||||
## ``resizeShared(block, 0)`` or ``freeShared(block)``.
|
||||
## The block is initialized with all bytes
|
||||
## containing zero, so it is somewhat safer than ``createSharedU``.
|
||||
cast[ptr T](allocShared0(T.sizeof * size))
|
||||
proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.}
|
||||
## grows or shrinks a given memory block on the heap. If p is **nil**
|
||||
## then a new memory block is returned. In either way the block has at least
|
||||
## ``newsize`` bytes. If ``newsize == 0`` and p is not **nil**
|
||||
## then a new memory block is returned. In either way the block has at
|
||||
## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
|
||||
## ``reallocShared`` calls ``deallocShared(p)``. In other cases the
|
||||
## block has to be freed with ``deallocShared``.
|
||||
proc resizeShared*[T](p: ptr T, newSize: Natural): ptr T {.inline.} =
|
||||
## grows or shrinks a given memory block on the heap. If p is **nil**
|
||||
## then a new memory block is returned. In either way the block has at
|
||||
## least ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is
|
||||
## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other
|
||||
## cases the block has to be freed with ``freeShared``.
|
||||
cast[ptr T](reallocShared(p, T.sizeof * newSize))
|
||||
proc deallocShared*(p: pointer) {.noconv, rtl.}
|
||||
## frees the memory allocated with ``allocShared``, ``allocShared0`` or
|
||||
## ``reallocShared``. This procedure is dangerous! If one forgets to
|
||||
## free the memory a leak occurs; if one tries to access freed
|
||||
## memory (or just freeing it twice!) a core dump may happen
|
||||
## or other memory may be corrupted.
|
||||
proc freeShared*[T](p: ptr T) {.inline.} =
|
||||
## frees the memory allocated with ``createShared``, ``createSharedU`` or
|
||||
## ``resizeShared``. This procedure is dangerous! If one forgets to
|
||||
## free the memory a leak occurs; if one tries to access freed
|
||||
## memory (or just freeing it twice!) a core dump may happen
|
||||
## or other memory may be corrupted.
|
||||
deallocShared(p)
|
||||
|
||||
proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.}
|
||||
## swaps the values `a` and `b`. This is often more efficient than
|
||||
|
|
@ -1399,20 +1457,6 @@ iterator items*[IX, T](a: array[IX, T]): T {.inline.} =
|
|||
if i >= high(IX): break
|
||||
inc(i)
|
||||
|
||||
iterator items*[T](a: seq[T]): T {.inline.} =
|
||||
## iterates over each item of `a`.
|
||||
var i = 0
|
||||
while i < len(a):
|
||||
yield a[i]
|
||||
inc(i)
|
||||
|
||||
iterator items*(a: string): char {.inline.} =
|
||||
## iterates over each item of `a`.
|
||||
var i = 0
|
||||
while i < len(a):
|
||||
yield a[i]
|
||||
inc(i)
|
||||
|
||||
iterator items*[T](a: set[T]): T {.inline.} =
|
||||
## iterates over each element of `a`. `items` iterates only over the
|
||||
## elements that are really in the set (and not over the ones the set is
|
||||
|
|
@ -1514,7 +1558,7 @@ when not defined(NimrodVM):
|
|||
proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} =
|
||||
result = cast[pointer](x)
|
||||
else:
|
||||
proc seqToPtr[T](x: seq[T]): pointer {.noStackFrame, nosideeffect.} =
|
||||
proc seqToPtr[T](x: seq[T]): pointer {.asmNoStackFrame, nosideeffect.} =
|
||||
asm """return `x`"""
|
||||
|
||||
proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} =
|
||||
|
|
@ -1802,7 +1846,7 @@ type
|
|||
len*: int ## length of the inspectable slots
|
||||
|
||||
when defined(JS):
|
||||
proc add*(x: var string, y: cstring) {.noStackFrame.} =
|
||||
proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =
|
||||
asm """
|
||||
var len = `x`[0].length-1;
|
||||
for (var i = 0; i < `y`.length; ++i) {
|
||||
|
|
@ -2019,8 +2063,10 @@ when not defined(JS): #and not defined(NimrodVM):
|
|||
## Flushes `f`'s buffer.
|
||||
|
||||
proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].}
|
||||
## Reads all data from the stream `file`. Raises an IO exception
|
||||
## in case of an error
|
||||
## Reads all data from the stream `file`.
|
||||
##
|
||||
## Raises an IO exception in case of an error. It is an error if the
|
||||
## current file position is not at the beginning of the file.
|
||||
|
||||
proc readFile*(filename: string): TaintedString {.tags: [FReadIO].}
|
||||
## Opens a file named `filename` for reading. Then calls `readAll`
|
||||
|
|
@ -2309,12 +2355,32 @@ when not defined(JS): #and not defined(NimrodVM):
|
|||
|
||||
when not defined(NimrodVM):
|
||||
proc likely*(val: bool): bool {.importc: "likely", nodecl, nosideeffect.}
|
||||
## can be used to mark a condition to be likely. This is a hint for the
|
||||
## optimizer.
|
||||
## Hints the optimizer that `val` is likely going to be true.
|
||||
##
|
||||
## You can use this proc to decorate a branch condition. On certain
|
||||
## platforms this can help the processor predict better which branch is
|
||||
## going to be run. Example:
|
||||
##
|
||||
## .. code-block:: nimrod
|
||||
## for value in inputValues:
|
||||
## if likely(value <= 100):
|
||||
## process(value)
|
||||
## else:
|
||||
## echo "Value too big!"
|
||||
|
||||
proc unlikely*(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.}
|
||||
## can be used to mark a condition to be unlikely. This is a hint for the
|
||||
## optimizer.
|
||||
## Hints the optimizer that `val` is likely going to be false.
|
||||
##
|
||||
## You can use this proc to decorate a branch condition. On certain
|
||||
## platforms this can help the processor predict better which branch is
|
||||
## going to be run. Example:
|
||||
##
|
||||
## .. code-block:: nimrod
|
||||
## for value in inputValues:
|
||||
## if unlikely(value > 100):
|
||||
## echo "Value too big!"
|
||||
## else:
|
||||
## process(value)
|
||||
|
||||
proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} =
|
||||
## retrieves the raw proc pointer of the closure `x`. This is
|
||||
|
|
@ -2612,6 +2678,24 @@ template doAssert*(cond: bool, msg = "") =
|
|||
if not cond:
|
||||
raiseAssert(astToStr(cond) & ' ' & msg)
|
||||
|
||||
iterator items*[T](a: seq[T]): T {.inline.} =
|
||||
## iterates over each item of `a`.
|
||||
var i = 0
|
||||
let L = len(a)
|
||||
while i < L:
|
||||
yield a[i]
|
||||
inc(i)
|
||||
assert(len(a) == L, "seq modified while iterating over it")
|
||||
|
||||
iterator items*(a: string): char {.inline.} =
|
||||
## iterates over each item of `a`.
|
||||
var i = 0
|
||||
let L = len(a)
|
||||
while i < L:
|
||||
yield a[i]
|
||||
inc(i)
|
||||
assert(len(a) == L, "string modified while iterating over it")
|
||||
|
||||
when not defined(nimhygiene):
|
||||
{.pragma: inject.}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const
|
|||
when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
||||
# assembler optimized versions for compilers that
|
||||
# have an intel syntax assembler:
|
||||
proc addInt(a, b: int): int {.compilerProc, noStackFrame.} =
|
||||
proc addInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
# a in eax, and b in edx
|
||||
asm """
|
||||
mov eax, `a`
|
||||
|
|
@ -121,7 +121,7 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
|||
theEnd:
|
||||
"""
|
||||
|
||||
proc subInt(a, b: int): int {.compilerProc, noStackFrame.} =
|
||||
proc subInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
sub eax, `b`
|
||||
|
|
@ -130,7 +130,7 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
|||
theEnd:
|
||||
"""
|
||||
|
||||
proc negInt(a: int): int {.compilerProc, noStackFrame.} =
|
||||
proc negInt(a: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
neg eax
|
||||
|
|
@ -139,7 +139,7 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
|||
theEnd:
|
||||
"""
|
||||
|
||||
proc divInt(a, b: int): int {.compilerProc, noStackFrame.} =
|
||||
proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
|
|
@ -150,7 +150,7 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
|||
theEnd:
|
||||
"""
|
||||
|
||||
proc modInt(a, b: int): int {.compilerProc, noStackFrame.} =
|
||||
proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
|
|
@ -162,7 +162,7 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
|||
mov eax, edx
|
||||
"""
|
||||
|
||||
proc mulInt(a, b: int): int {.compilerProc, noStackFrame.} =
|
||||
proc mulInt(a, b: int): int {.compilerProc, asmNoStackFrame.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ proc scanAndAppendWord(src: cstring, a: var TStaticStr, start: int): int =
|
|||
while True:
|
||||
case src[result]
|
||||
of 'a'..'z', '0'..'9': add(a, src[result])
|
||||
of '_': nil # just skip it
|
||||
of '_': discard # just skip it
|
||||
of 'A'..'Z': add(a, chr(ord(src[result]) - ord('A') + ord('a')))
|
||||
else: break
|
||||
inc(result)
|
||||
|
|
@ -203,7 +203,7 @@ proc scanNumber(src: cstring, a: var int, start: int): int =
|
|||
while true:
|
||||
case src[result]
|
||||
of '0'..'9': a = a * 10 + ord(src[result]) - ord('0')
|
||||
of '_': nil # skip underscores (nice for long line numbers)
|
||||
of '_': discard # skip underscores (nice for long line numbers)
|
||||
else: break
|
||||
inc(result)
|
||||
|
||||
|
|
@ -524,7 +524,7 @@ proc lineHookImpl() {.nimcall.} =
|
|||
of dbBreakpoints:
|
||||
# debugger is only interested in breakpoints
|
||||
checkForBreakpoint()
|
||||
else: nil
|
||||
else: discard
|
||||
|
||||
proc watchpointHookImpl(name: cstring) {.nimcall.} =
|
||||
dbgWriteStackTrace(framePtr)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ type
|
|||
PCallFrame = ptr TCallFrame
|
||||
TCallFrame {.importc, nodecl, final.} = object
|
||||
prev: PCallFrame
|
||||
procname: CString
|
||||
procname: cstring
|
||||
line: int # current line number
|
||||
filename: CString
|
||||
filename: cstring
|
||||
|
||||
var
|
||||
framePtr {.importc, nodecl, volatile.}: PCallFrame
|
||||
|
|
@ -48,7 +48,7 @@ proc getCurrentExceptionMsg*(): string =
|
|||
|
||||
proc auxWriteStackTrace(f: PCallFrame): string =
|
||||
type
|
||||
TTempFrame = tuple[procname: CString, line: int]
|
||||
TTempFrame = tuple[procname: cstring, line: int]
|
||||
var
|
||||
it = f
|
||||
i = 0
|
||||
|
|
@ -84,7 +84,7 @@ proc rawWriteStackTrace(): string =
|
|||
framePtr = nil
|
||||
|
||||
proc raiseException(e: ref E_Base, ename: cstring) {.
|
||||
compilerproc, noStackFrame.} =
|
||||
compilerproc, asmNoStackFrame.} =
|
||||
e.name = ename
|
||||
if excHandler != nil:
|
||||
excHandler.exc = e
|
||||
|
|
@ -104,7 +104,7 @@ proc raiseException(e: ref E_Base, ename: cstring) {.
|
|||
alert(buf)
|
||||
asm """throw `e`;"""
|
||||
|
||||
proc reraiseException() {.compilerproc, noStackFrame.} =
|
||||
proc reraiseException() {.compilerproc, asmNoStackFrame.} =
|
||||
if excHandler == nil:
|
||||
raise newException(ENoExceptionToReraise, "no exception to reraise")
|
||||
else:
|
||||
|
|
@ -125,7 +125,7 @@ proc raiseIndexError() {.compilerproc, noreturn.} =
|
|||
proc raiseFieldError(f: string) {.compilerproc, noreturn.} =
|
||||
raise newException(EInvalidField, f & " is not accessible")
|
||||
|
||||
proc SetConstr() {.varargs, noStackFrame, compilerproc.} =
|
||||
proc SetConstr() {.varargs, asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
|
|
@ -141,7 +141,7 @@ proc SetConstr() {.varargs, noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc cstrToNimstr(c: cstring): string {.noStackFrame, compilerproc.} =
|
||||
proc cstrToNimstr(c: cstring): string {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = [];
|
||||
for (var i = 0; i < `c`.length; ++i) {
|
||||
|
|
@ -151,7 +151,7 @@ proc cstrToNimstr(c: cstring): string {.noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc toJSStr(s: string): cstring {.noStackFrame, compilerproc.} =
|
||||
proc toJSStr(s: string): cstring {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var len = `s`.length-1;
|
||||
var result = new Array(len);
|
||||
|
|
@ -162,7 +162,7 @@ proc toJSStr(s: string): cstring {.noStackFrame, compilerproc.} =
|
|||
return result.join("");
|
||||
"""
|
||||
|
||||
proc mnewString(len: int): string {.noStackFrame, compilerproc.} =
|
||||
proc mnewString(len: int): string {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = new Array(`len`+1);
|
||||
result[0] = 0;
|
||||
|
|
@ -170,7 +170,7 @@ proc mnewString(len: int): string {.noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc SetCard(a: int): int {.compilerproc, noStackFrame.} =
|
||||
proc SetCard(a: int): int {.compilerproc, asmNoStackFrame.} =
|
||||
# argument type is a fake
|
||||
asm """
|
||||
var result = 0;
|
||||
|
|
@ -178,14 +178,14 @@ proc SetCard(a: int): int {.compilerproc, noStackFrame.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc SetEq(a, b: int): bool {.compilerproc, noStackFrame.} =
|
||||
proc SetEq(a, b: int): bool {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
for (var elem in `a`) { if (!`b`[elem]) return false; }
|
||||
for (var elem in `b`) { if (!`a`[elem]) return false; }
|
||||
return true;
|
||||
"""
|
||||
|
||||
proc SetLe(a, b: int): bool {.compilerproc, noStackFrame.} =
|
||||
proc SetLe(a, b: int): bool {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
for (var elem in `a`) { if (!`b`[elem]) return false; }
|
||||
return true;
|
||||
|
|
@ -194,7 +194,7 @@ proc SetLe(a, b: int): bool {.compilerproc, noStackFrame.} =
|
|||
proc SetLt(a, b: int): bool {.compilerproc.} =
|
||||
result = SetLe(a, b) and not SetEq(a, b)
|
||||
|
||||
proc SetMul(a, b: int): int {.compilerproc, noStackFrame.} =
|
||||
proc SetMul(a, b: int): int {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) {
|
||||
|
|
@ -203,7 +203,7 @@ proc SetMul(a, b: int): int {.compilerproc, noStackFrame.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc SetPlus(a, b: int): int {.compilerproc, noStackFrame.} =
|
||||
proc SetPlus(a, b: int): int {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) { result[elem] = true; }
|
||||
|
|
@ -211,7 +211,7 @@ proc SetPlus(a, b: int): int {.compilerproc, noStackFrame.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc SetMinus(a, b: int): int {.compilerproc, noStackFrame.} =
|
||||
proc SetMinus(a, b: int): int {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) {
|
||||
|
|
@ -220,7 +220,7 @@ proc SetMinus(a, b: int): int {.compilerproc, noStackFrame.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc cmpStrings(a, b: string): int {.noStackFrame, compilerProc.} =
|
||||
proc cmpStrings(a, b: string): int {.asmNoStackFrame, compilerProc.} =
|
||||
asm """
|
||||
if (`a` == `b`) return 0;
|
||||
if (!`a`) return -1;
|
||||
|
|
@ -234,7 +234,7 @@ proc cmpStrings(a, b: string): int {.noStackFrame, compilerProc.} =
|
|||
|
||||
proc cmp(x, y: string): int = return cmpStrings(x, y)
|
||||
|
||||
proc eqStrings(a, b: string): bool {.noStackFrame, compilerProc.} =
|
||||
proc eqStrings(a, b: string): bool {.asmNoStackFrame, compilerProc.} =
|
||||
asm """
|
||||
if (`a` == `b`) return true;
|
||||
if ((!`a`) || (!`b`)) return false;
|
||||
|
|
@ -300,7 +300,7 @@ type
|
|||
setAttributeNode*: proc (attr: ref TNode) {.nimcall.}
|
||||
|
||||
when defined(kwin):
|
||||
proc rawEcho {.compilerproc, nostackframe.} =
|
||||
proc rawEcho {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
var buf = "";
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
|
|
@ -312,7 +312,7 @@ when defined(kwin):
|
|||
elif defined(nodejs):
|
||||
proc ewriteln(x: cstring) = log(x)
|
||||
|
||||
proc rawEcho {.compilerproc, nostackframe.} =
|
||||
proc rawEcho {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
var buf = "";
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
|
|
@ -345,42 +345,42 @@ else:
|
|||
node.appendChild(document.createElement("br"))
|
||||
|
||||
# Arithmetic:
|
||||
proc addInt(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc addInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` + `b`;
|
||||
if (result > 2147483647 || result < -2147483648) `raiseOverflow`();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc subInt(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc subInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` - `b`;
|
||||
if (result > 2147483647 || result < -2147483648) `raiseOverflow`();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc mulInt(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc mulInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` * `b`;
|
||||
if (result > 2147483647 || result < -2147483648) `raiseOverflow`();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc divInt(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc divInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) `raiseDivByZero`();
|
||||
if (`b` == -1 && `a` == 2147483647) `raiseOverflow`();
|
||||
return Math.floor(`a` / `b`);
|
||||
"""
|
||||
|
||||
proc modInt(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) `raiseDivByZero`();
|
||||
if (`b` == -1 && `a` == 2147483647) `raiseOverflow`();
|
||||
return Math.floor(`a` % `b`);
|
||||
"""
|
||||
|
||||
proc addInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc addInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` + `b`;
|
||||
if (result > 9223372036854775807
|
||||
|
|
@ -388,7 +388,7 @@ proc addInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc subInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc subInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` - `b`;
|
||||
if (result > 9223372036854775807
|
||||
|
|
@ -396,7 +396,7 @@ proc subInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc mulInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc mulInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` * `b`;
|
||||
if (result > 9223372036854775807
|
||||
|
|
@ -404,90 +404,89 @@ proc mulInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
|||
return result;
|
||||
"""
|
||||
|
||||
proc divInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc divInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) `raiseDivByZero`();
|
||||
if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`();
|
||||
return Math.floor(`a` / `b`);
|
||||
"""
|
||||
|
||||
proc modInt64(a, b: int): int {.noStackFrame, compilerproc.} =
|
||||
proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) `raiseDivByZero`();
|
||||
if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`();
|
||||
return Math.floor(`a` % `b`);
|
||||
"""
|
||||
|
||||
proc NegInt(a: int): int {.compilerproc.} =
|
||||
proc negInt(a: int): int {.compilerproc.} =
|
||||
result = a*(-1)
|
||||
|
||||
proc NegInt64(a: int64): int64 {.compilerproc.} =
|
||||
proc negInt64(a: int64): int64 {.compilerproc.} =
|
||||
result = a*(-1)
|
||||
|
||||
proc AbsInt(a: int): int {.compilerproc.} =
|
||||
proc absInt(a: int): int {.compilerproc.} =
|
||||
result = if a < 0: a*(-1) else: a
|
||||
|
||||
proc AbsInt64(a: int64): int64 {.compilerproc.} =
|
||||
proc absInt64(a: int64): int64 {.compilerproc.} =
|
||||
result = if a < 0: a*(-1) else: a
|
||||
|
||||
proc LeU(a, b: int): bool {.compilerproc.} =
|
||||
proc leU(a, b: int): bool {.compilerproc.} =
|
||||
result = abs(a) <= abs(b)
|
||||
|
||||
proc LtU(a, b: int): bool {.compilerproc.} =
|
||||
proc ltU(a, b: int): bool {.compilerproc.} =
|
||||
result = abs(a) < abs(b)
|
||||
|
||||
proc LeU64(a, b: int64): bool {.compilerproc.} =
|
||||
proc leU64(a, b: int64): bool {.compilerproc.} =
|
||||
result = abs(a) <= abs(b)
|
||||
|
||||
proc LtU64(a, b: int64): bool {.compilerproc.} =
|
||||
proc ltU64(a, b: int64): bool {.compilerproc.} =
|
||||
result = abs(a) < abs(b)
|
||||
|
||||
proc AddU(a, b: int): int {.compilerproc.} =
|
||||
proc addU(a, b: int): int {.compilerproc.} =
|
||||
result = abs(a) + abs(b)
|
||||
proc AddU64(a, b: int64): int64 {.compilerproc.} =
|
||||
proc addU64(a, b: int64): int64 {.compilerproc.} =
|
||||
result = abs(a) + abs(b)
|
||||
|
||||
proc SubU(a, b: int): int {.compilerproc.} =
|
||||
proc subU(a, b: int): int {.compilerproc.} =
|
||||
result = abs(a) - abs(b)
|
||||
proc SubU64(a, b: int64): int64 {.compilerproc.} =
|
||||
proc subU64(a, b: int64): int64 {.compilerproc.} =
|
||||
result = abs(a) - abs(b)
|
||||
|
||||
proc MulU(a, b: int): int {.compilerproc.} =
|
||||
proc mulU(a, b: int): int {.compilerproc.} =
|
||||
result = abs(a) * abs(b)
|
||||
proc MulU64(a, b: int64): int64 {.compilerproc.} =
|
||||
proc mulU64(a, b: int64): int64 {.compilerproc.} =
|
||||
result = abs(a) * abs(b)
|
||||
|
||||
proc DivU(a, b: int): int {.compilerproc.} =
|
||||
proc divU(a, b: int): int {.compilerproc.} =
|
||||
result = abs(a) div abs(b)
|
||||
proc DivU64(a, b: int64): int64 {.compilerproc.} =
|
||||
proc divU64(a, b: int64): int64 {.compilerproc.} =
|
||||
result = abs(a) div abs(b)
|
||||
|
||||
proc ModU(a, b: int): int {.compilerproc.} =
|
||||
proc modU(a, b: int): int {.compilerproc.} =
|
||||
result = abs(a) mod abs(b)
|
||||
proc ModU64(a, b: int64): int64 {.compilerproc.} =
|
||||
proc modU64(a, b: int64): int64 {.compilerproc.} =
|
||||
result = abs(a) mod abs(b)
|
||||
|
||||
proc Ze(a: int): int {.compilerproc.} =
|
||||
result = a
|
||||
proc Ze64(a: int64): int64 {.compilerproc.} =
|
||||
proc ze*(a: int): int {.compilerproc.} =
|
||||
result = a
|
||||
|
||||
proc ToU8(a: int): int8 {.noStackFrame, compilerproc.} =
|
||||
proc ze64*(a: int64): int64 {.compilerproc.} =
|
||||
result = a
|
||||
|
||||
proc ToU8(a: int): int8 {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
return `a`;
|
||||
"""
|
||||
|
||||
proc ToU16(a: int): int16 {.noStackFrame, compilerproc.} =
|
||||
proc ToU16(a: int): int16 {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
return `a`;
|
||||
"""
|
||||
|
||||
proc ToU32(a: int): int32 {.noStackFrame, compilerproc.} =
|
||||
proc ToU32(a: int): int32 {.asmNoStackFrame, compilerproc.} =
|
||||
asm """
|
||||
return `a`;
|
||||
"""
|
||||
|
||||
|
||||
proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
|
||||
proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
|
||||
|
||||
|
|
@ -500,9 +499,9 @@ proc isFatPointer(ti: PNimType): bool =
|
|||
tyArray, tyArrayConstr, tyTuple,
|
||||
tyOpenArray, tySet, tyVar, tyRef, tyPtr}
|
||||
|
||||
proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
|
||||
proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
|
||||
|
||||
proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
||||
proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
||||
case n.kind
|
||||
of nkNone: sysAssert(false, "NimCopyAux")
|
||||
of nkSlot:
|
||||
|
|
@ -518,7 +517,7 @@ proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
|
|||
}
|
||||
"""
|
||||
|
||||
proc NimCopy(x: pointer, ti: PNimType): pointer =
|
||||
proc nimCopy(x: pointer, ti: PNimType): pointer =
|
||||
case ti.kind
|
||||
of tyPtr, tyRef, tyVar, tyNil:
|
||||
if not isFatPointer(ti):
|
||||
|
|
@ -586,7 +585,7 @@ proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} =
|
|||
result = nil
|
||||
|
||||
proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {.
|
||||
noStackFrame, compilerproc.} =
|
||||
asmNoStackFrame, compilerproc.} =
|
||||
# types are fake
|
||||
asm """
|
||||
var result = new Array(`len`);
|
||||
|
|
@ -620,7 +619,7 @@ proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
|
|||
x = x.base
|
||||
return true
|
||||
|
||||
proc addChar(x: string, c: char) {.compilerproc, noStackFrame.} =
|
||||
proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} =
|
||||
asm """
|
||||
`x`[`x`.length-1] = `c`; `x`.push(0);
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ proc reprChar(x: char): string {.compilerRtl.} =
|
|||
|
||||
proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
|
||||
# we read an 'int' but this may have been too large, so mask the other bits:
|
||||
let e = e and (1 shl (typ.size*8)-1)
|
||||
let e = if typ.size == 1: e and 0xff
|
||||
elif typ.size == 2: e and 0xffff
|
||||
else: e
|
||||
# XXX we need a proper narrowing based on signedness here
|
||||
#e and ((1 shl (typ.size*8)) - 1)
|
||||
if ntfEnumHole notin typ.flags:
|
||||
if e <% typ.node.len:
|
||||
return $typ.node.sons[e].name
|
||||
|
|
|
|||
|
|
@ -199,14 +199,14 @@ else:
|
|||
importc: "GetCurrentDirectoryA", dynlib: "kernel32", stdcall.}
|
||||
proc setCurrentDirectoryA*(lpPathName: cstring): int32 {.
|
||||
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.}
|
||||
proc removeDirectoryA*(lpPathName: cstring): int32 {.
|
||||
importc: "RemoveDirectoryA", dynlib: "kernel32", stdcall.}
|
||||
proc setEnvironmentVariableA*(lpName, lpValue: cstring): int32 {.
|
||||
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.}
|
||||
|
||||
when useWinUnicode:
|
||||
|
|
@ -304,7 +304,7 @@ else:
|
|||
dwFileAttributes: int32): WINBOOL {.
|
||||
stdcall, dynlib: "kernel32", importc: "SetFileAttributesA".}
|
||||
|
||||
proc copyFileA*(lpExistingFileName, lpNewFileName: cstring,
|
||||
proc copyFileA*(lpExistingFileName, lpNewFileName: CString,
|
||||
bFailIfExists: cint): cint {.
|
||||
importc: "CopyFileA", stdcall, dynlib: "kernel32".}
|
||||
|
||||
|
|
@ -456,6 +456,7 @@ var
|
|||
|
||||
SO_DONTLINGER* {.importc, header: "Winsock2.h".}: cint
|
||||
SO_EXCLUSIVEADDRUSE* {.importc, header: "Winsock2.h".}: cint # disallow local address reuse
|
||||
SO_ERROR* {.importc, header: "Winsock2.h".}: cint
|
||||
|
||||
proc `==`*(x, y: TSocketHandle): bool {.borrow.}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,7 @@ when defined(windows):
|
|||
elif defined(macosx):
|
||||
const libz = "libz.dylib"
|
||||
else:
|
||||
const libz = "libz.so"
|
||||
const libz = "libz.so.1"
|
||||
|
||||
type
|
||||
Uint* = int32
|
||||
|
|
@ -134,6 +134,7 @@ proc gzerror*(thefile: gzFile, errnum: var int32): pbytef{.cdecl, dynlib: libz,
|
|||
importc: "gzerror".}
|
||||
proc adler32*(adler: uLong, buf: pbytef, length: uInt): uLong{.cdecl,
|
||||
dynlib: libz, importc: "adler32".}
|
||||
## **Warning**: Adler-32 requires at least a few hundred bytes to get rolling.
|
||||
proc crc32*(crc: uLong, buf: pbytef, length: uInt): uLong{.cdecl, dynlib: libz,
|
||||
importc: "crc32".}
|
||||
proc deflateInitu*(strm: var TZStream, level: int32, version: cstring,
|
||||
|
|
|
|||
|
|
@ -299,12 +299,12 @@ proc open*(address: string, server: bool, mode: TConnectionMode = conDEALER,
|
|||
else:
|
||||
if connect(result.s, address) != 0'i32: zmqError()
|
||||
|
||||
proc close*(c: var TConnection) =
|
||||
proc close*(c: TConnection) =
|
||||
## closes the connection.
|
||||
if close(c.s) != 0'i32: zmqError()
|
||||
if term(c.c) != 0'i32: zmqError()
|
||||
|
||||
proc send*(c: var TConnection, msg: string) =
|
||||
proc send*(c: TConnection, msg: string) =
|
||||
## sends a message over the connection.
|
||||
var m: TMsg
|
||||
if msg_init(m, msg.len) != 0'i32: zmqError()
|
||||
|
|
@ -312,7 +312,7 @@ proc send*(c: var TConnection, msg: string) =
|
|||
if send(c.s, m, 0'i32) != 0'i32: zmqError()
|
||||
discard msg_close(m)
|
||||
|
||||
proc receive*(c: var TConnection): string =
|
||||
proc receive*(c: TConnection): string =
|
||||
## receives a message from a connection.
|
||||
var m: TMsg
|
||||
if msg_init(m) != 0'i32: zmqError()
|
||||
|
|
@ -320,4 +320,3 @@ proc receive*(c: var TConnection): string =
|
|||
result = newString(msg_size(m))
|
||||
copyMem(addr(result[0]), msg_data(m), result.len)
|
||||
discard msg_close(m)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue