diff options
Diffstat (limited to 'lib')
61 files changed, 1875 insertions, 1973 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 334a4b8c7..455f99c9e 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -620,6 +620,8 @@ proc `body=`*(someProc: PNimrodNode, val: PNimrodNode) {.compileTime.} = someProc[high(someProc)] = val else: badNodeKind someProc.kind, "body=" + +proc basename*(a: PNimrodNode): PNimrodNode {.compiletime.} proc `$`*(node: PNimrodNode): string {.compileTime.} = @@ -627,7 +629,9 @@ proc `$`*(node: PNimrodNode): string {.compileTime.} = case node.kind of nnkIdent: result = $node.ident - of nnkStrLit: + of nnkPostfix: + result = $node.basename.ident & "*" + of nnkStrLit..nnkTripleStrLit: result = node.strVal else: badNodeKind node.kind, "$" @@ -669,7 +673,7 @@ proc insert*(a: PNimrodNode; pos: int; b: PNimrodNode) {.compileTime.} = a[i + 1] = a[i] a[pos] = b -proc basename*(a: PNimrodNode): PNimrodNode {.compiletime.} = +proc basename*(a: PNimrodNode): PNimrodNode = ## Pull an identifier from prefix/postfix expressions case a.kind of nnkIdent: return a diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index 57e11664b..8df1b3dfb 100644 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -102,7 +102,7 @@ proc newAny(value: pointer, rawType: PNimType): TAny = result.value = value result.rawType = rawType -when defined(system.TVarSlot): +when declared(system.TVarSlot): proc toAny*(x: TVarSlot): TAny {.inline.} = ## constructs a ``TAny`` object from a variable slot ``x``. ## This captures `x`'s address, so `x` can be modified with its diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index eec4daf00..ce48a32ed 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -57,7 +57,8 @@ when false: binding: seq[MYSQL_BIND] discard mysql_stmt_close(stmt) -proc dbQuote(s: string): string = +proc dbQuote*(s: string): string = + ## DB quotes the string. result = "'" for c in items(s): if c == '\'': add(result, "''") @@ -69,7 +70,10 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = var a = 0 for c in items(string(formatstr)): if c == '?': - add(result, dbQuote(args[a])) + if args[a] == nil: + add(result, "NULL") + else: + add(result, dbQuote(args[a])) inc(a) else: add(result, c) @@ -115,7 +119,10 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery, if row == nil: break for i in 0..L-1: setLen(result[i], 0) - add(result[i], row[i]) + if row[i] == nil: + result[i] = nil + else: + add(result[i], row[i]) yield result properFreeResult(sqlres, row) @@ -132,7 +139,10 @@ proc getRow*(db: TDbConn, query: TSqlQuery, if row != nil: for i in 0..L-1: setLen(result[i], 0) - add(result[i], row[i]) + if row[i] == nil: + result[i] = nil + else: + add(result[i], row[i]) properFreeResult(sqlres, row) proc getAllRows*(db: TDbConn, query: TSqlQuery, @@ -150,7 +160,11 @@ proc getAllRows*(db: TDbConn, query: TSqlQuery, if row == nil: break setLen(result, j+1) newSeq(result[j], L) - for i in 0..L-1: result[j][i] = $row[i] + for i in 0..L-1: + if row[i] == nil: + result[j][i] = nil + else: + result[j][i] = $row[i] inc(j) mysql.FreeResult(sqlres) diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index f6ae93303..510cb8e45 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -19,12 +19,13 @@ type EDb* = object of EIO ## exception that is raised if a database error occurs TSqlQuery* = distinct string ## an SQL query string + TSqlPrepared* = distinct string ## a identifier for the prepared queries 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 sql*(query: string): TSqlQuery {.noSideEffect, inline.} = + +proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = ## constructs a TSqlQuery from the string `query`. This is supposed to be ## used as a raw-string-literal modifier: ## ``sql"update user set counter = counter + 1"`` @@ -33,21 +34,22 @@ proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = ## on, later versions will check the string for valid syntax. result = TSqlQuery(query) -proc dbError(db: TDbConn) {.noreturn.} = +proc dbError*(db: TDbConn) {.noreturn.} = ## raises an EDb exception. var e: ref EDb new(e) e.msg = $PQerrorMessage(db) raise e -proc dbError*(msg: string) {.noreturn.} = +proc dbError*(msg: string) {.noreturn.} = ## raises an EDb exception with message `msg`. var e: ref EDb new(e) e.msg = msg raise e -proc dbQuote(s: string): string = +proc dbQuote*(s: string): string = + ## DB quotes the string. result = "'" for c in items(s): if c == '\'': add(result, "''") @@ -59,43 +61,78 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = var a = 0 for c in items(string(formatstr)): if c == '?': - add(result, dbQuote(args[a])) + if args[a] == nil: + add(result, "NULL") + else: + add(result, dbQuote(args[a])) inc(a) - else: + else: add(result, c) -proc tryExec*(db: TDbConn, query: TSqlQuery, +proc tryExec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} = ## tries to execute the query and returns true if successful, false otherwise. - var q = dbFormat(query, args) - var res = PQExec(db, q) + var arr = allocCStringArray(args) + var res = PQexecParams(db, query.string, int32(args.len), nil, arr, + nil, nil, 0) + deallocCStringArray(arr) result = PQresultStatus(res) == PGRES_COMMAND_OK PQclear(res) proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {. tags: [FReadDB, FWriteDb].} = ## executes the query and raises EDB if not successful. - var q = dbFormat(query, args) - var res = PQExec(db, q) + var arr = allocCStringArray(args) + var res = PQexecParams(db, query.string, int32(args.len), nil, arr, + nil, nil, 0) + deallocCStringArray(arr) if PQresultStatus(res) != PGRES_COMMAND_OK: dbError(db) PQclear(res) - + +proc exec*(db: TDbConn, stmtName: TSqlPrepared, + args: varargs[string]) {.tags: [FReadDB, FWriteDb].} = + var arr = allocCStringArray(args) + var res = PQexecPrepared(db, stmtName.string, int32(args.len), arr, + nil, nil, 0) + deallocCStringArray(arr) + if PQResultStatus(res) != PGRES_COMMAND_OK: dbError(db) + PQclear(res) + proc newRow(L: int): TRow = newSeq(result, L) for i in 0..L-1: result[i] = "" -proc setupQuery(db: TDbConn, query: TSqlQuery, - args: varargs[string]): PPGresult = - var q = dbFormat(query, args) - result = PQExec(db, q) - if PQresultStatus(result) != PGRES_TUPLES_OK: dbError(db) - +proc setupQuery(db: TDbConn, query: TSqlQuery, + args: varargs[string]): PPGresult = + var arr = allocCStringArray(args) + result = PQexecParams(db, query.string, int32(args.len), nil, arr, + nil, nil, 0) + deallocCStringArray(arr) + if PQResultStatus(result) != PGRES_TUPLES_OK: dbError(db) + +proc setupQuery(db: TDbConn, stmtName: TSqlPrepared, + args: varargs[string]): PPGresult = + var arr = allocCStringArray(args) + result = PQexecPrepared(db, stmtName.string, int32(args.len), arr, + nil, nil, 0) + deallocCStringArray(arr) + if PQResultStatus(result) != PGRES_TUPLES_OK: dbError(db) + +proc prepare*(db: TDbConn; stmtName: string, query: TSqlQuery; + nParams: int): TSqlPrepared = + var res = PQprepare(db, stmtName, query.string, int32(nParams), nil) + if PQResultStatus(res) != PGRES_COMMAND_OK: dbError(db) + return TSqlPrepared(stmtName) + proc setRow(res: PPGresult, r: var TRow, line, cols: int32) = for col in 0..cols-1: setLen(r[col], 0) var x = PQgetvalue(res, line, col) - add(r[col], x) - + if x == nil: + r[col] = nil + else: + add(r[col], x) + iterator fastRows*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = ## executes the query and iterates over the result dataset. This is very @@ -109,6 +146,17 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery, yield result PQclear(res) +iterator fastRows*(db: TDbConn, stmtName: TSqlPrepared, + args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = + ## executes the prepared query and iterates over the result dataset. + var res = setupQuery(db, stmtName, args) + var L = PQnfields(res) + var result = newRow(L) + for i in 0..PQntuples(res)-1: + setRow(res, result, i, L) + yield result + PQclear(res) + proc getRow*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = ## retrieves a single row. If the query doesn't return any rows, this proc @@ -119,40 +167,55 @@ proc getRow*(db: TDbConn, query: TSqlQuery, setRow(res, result, 0, L) PQclear(res) -proc getAllRows*(db: TDbConn, query: TSqlQuery, +proc getRow*(db: TDbConn, stmtName: TSqlPrepared, + args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = + var res = setupQuery(db, stmtName, args) + var L = PQnfields(res) + result = newRow(L) + setRow(res, result, 0, L) + PQclear(res) + +proc getAllRows*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = ## executes the query and returns the whole result dataset. result = @[] for r in FastRows(db, query, args): result.add(r) -iterator rows*(db: TDbConn, query: TSqlQuery, +proc getAllRows*(db: TDbConn, stmtName: TSqlPrepared, + args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = + ## executes the prepared query and returns the whole result dataset. + result = @[] + for r in FastRows(db, stmtName, args): + result.add(r) + +iterator rows*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = ## same as `FastRows`, but slower and safe. for r in items(GetAllRows(db, query, args)): yield r -proc getValue*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): string {.tags: [FReadDB].} = +proc getValue*(db: TDbConn, query: TSqlQuery, + args: varargs[string, `$`]): string {.tags: [FReadDB].} = ## executes the query and returns the first column of the first row of the ## result dataset. Returns "" if the dataset contains no rows or the database ## value is NULL. var x = PQgetvalue(setupQuery(db, query, args), 0, 0) result = if isNil(x): "" else: $x -proc tryInsertID*(db: TDbConn, query: TSqlQuery, +proc tryInsertID*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): int64 {.tags: [FWriteDb].}= ## executes the query (typically "INSERT") and returns the ## generated ID for the row or -1 in case of an error. For Postgre this adds ## ``RETURNING id`` to the query, so it only works if your primary key is ## named ``id``. - var x = PQgetvalue(setupQuery(db, TSqlQuery(string(query) & " RETURNING id"), + var x = PQgetvalue(setupQuery(db, TSqlQuery(string(query) & " RETURNING id"), args), 0, 0) if not isNil(x): result = ParseBiggestInt($x) else: result = -1 -proc insertID*(db: TDbConn, query: TSqlQuery, +proc insertID*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} = ## executes the query (typically "INSERT") and returns the ## generated ID for the row. For Postgre this adds @@ -161,9 +224,9 @@ proc insertID*(db: TDbConn, query: TSqlQuery, result = TryInsertID(db, query, args) if result < 0: dbError(db) -proc execAffectedRows*(db: TDbConn, query: TSqlQuery, +proc execAffectedRows*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): int64 {.tags: [ - FReadDB, FWriteDb].} = + FReadDB, FWriteDb].} = ## executes the query (typically "UPDATE") and returns the ## number of affected rows. var q = dbFormat(query, args) @@ -172,7 +235,7 @@ proc execAffectedRows*(db: TDbConn, query: TSqlQuery, result = parseBiggestInt($PQcmdTuples(res)) PQclear(res) -proc close*(db: TDbConn) {.tags: [FDb].} = +proc close*(db: TDbConn) {.tags: [FDb].} = ## closes the database connection. if db != nil: PQfinish(db) diff --git a/lib/impure/re.nim b/lib/impure/re.nim index f6511dab4..ac07b2d6b 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -243,7 +243,7 @@ template `=~` *(s: string, pattern: TRegex): expr = ## echo("syntax error") ## bind maxSubPatterns - when not definedInScope(matches): + when not declaredInScope(matches): var matches {.inject.}: array[0..MaxSubpatterns-1, string] match(s, pattern, matches) diff --git a/lib/impure/zipfiles.nim b/lib/impure/zipfiles.nim index 1726449d8..b9f89dda0 100644 --- a/lib/impure/zipfiles.nim +++ b/lib/impure/zipfiles.nim @@ -56,6 +56,8 @@ proc addFile*(z: var TZipArchive, dest, src: string) = ## Adds the file `src` to the archive `z` with the name `dest`. `dest` ## may contain a path that will be created. assert(z.mode != fmRead) + if not fileExists(src): + raise newException(EIO, "File '" & src & "' does not exist") var zipsrc = zip_source_file(z.w, src, 0, -1) if zipsrc == nil: #echo("Dest: " & dest) diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 80fbf3a51..ff371f4e1 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -50,7 +50,7 @@ const "break", "case", "cast", "const", "continue", "converter", "discard", "distinct", "div", "do", "elif", "else", "end", "enum", "except", "export", "finally", "for", "from", "generic", "if", "import", "in", "include", - "interface", "is", "isnot", "iterator", "lambda", "let", "macro", "method", + "interface", "is", "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", "try", "tuple", "type", "using", "var", "when", "while", "with", diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index e1ecbb518..9334ceeae 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -1551,6 +1551,16 @@ var MSG_OOB* {.importc, header: "<sys/socket.h>".}: cint ## Out-of-band data. + +when defined(linux): + var + MAP_POPULATE* {.importc, header: "<sys/mman.h>".}: cint + ## Populate (prefault) page tables for a mapping. +else: + var + MAP_POPULATE*: cint = 0 + + when defined(macosx): var MSG_HAVEMORE* {.importc, header: "<sys/socket.h>".}: cint diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index d410f8ce1..0ea8ef43b 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, macros +import os, oids, tables, strutils, macros, times import rawsockets, net @@ -27,12 +27,12 @@ export TPort, TSocketFlags ## **Note:** This module is still largely experimental. -# TODO: Discarded void PFutures need to be checked for exception. # TODO: ``except`` statement (without `try`) does not work. # TODO: Multiple exception names in a ``except`` don't work. # TODO: The effect system (raises: []) has trouble with my try transformation. # TODO: Can't await in a 'except' body # TODO: getCurrentException(Msg) don't work +# TODO: Check if yielded future is nil and throw a more meaningful exception # -- Futures @@ -41,27 +41,41 @@ type cb: proc () {.closure,gcsafe.} finished: bool error*: ref EBase - stackTrace: string ## For debugging purposes only. + errorStackTrace*: string + when not defined(release): + stackTrace: string ## For debugging purposes only. + id: int + fromProc: string PFuture*[T] = ref object of PFutureBase value: T -proc newFuture*[T](): PFuture[T] = +var currentID* = 0 +proc newFuture*[T](fromProc: string = "unspecified"): PFuture[T] = ## Creates a new future. + ## + ## Specifying ``fromProc``, which is a string specifying the name of the proc + ## that this future belongs to, is a good habit as it helps with debugging. new(result) result.finished = false - result.stackTrace = getStackTrace() + when not defined(release): + result.stackTrace = getStackTrace() + result.id = currentID + result.fromProc = fromProc + currentID.inc() proc checkFinished[T](future: PFuture[T]) = - if future.finished: - echo("<----->") - echo(future.stackTrace) - echo("-----") - when T is string: - echo("Contents: ", future.value.repr) - echo("<----->") - echo("Future already finished, cannot finish twice.") - assert false + when not defined(release): + if future.finished: + echo("<-----> ", future.id, " ", future.fromProc) + echo(future.stackTrace) + echo("-----") + when T is string: + echo("Contents: ", future.value.repr) + echo("<----->") + echo("Future already finished, cannot finish twice.") + echo getStackTrace() + assert false proc complete*[T](future: PFuture[T], val: T) = ## Completes ``future`` with value ``val``. @@ -88,6 +102,8 @@ proc fail*[T](future: PFuture[T], error: ref EBase) = checkFinished(future) future.finished = true future.error = error + future.errorStackTrace = + if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error) if future.cb != nil: future.cb() else: @@ -115,13 +131,24 @@ proc `callback=`*[T](future: PFuture[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) +proc echoOriginalStackTrace[T](future: PFuture[T]) = + # TODO: Come up with something better. + when not defined(release): + echo("Original stack trace in ", future.fromProc, ":") + if not future.errorStackTrace.isNil() and future.errorStackTrace != "": + echo(future.errorStackTrace) + else: + echo("Empty or nil stack trace.") + 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 + if future.error != nil: + echoOriginalStackTrace(future) + raise future.error when T isnot void: return future.value else: @@ -150,7 +177,44 @@ proc asyncCheck*[T](future: PFuture[T]) = ## This should be used instead of ``discard`` to discard void futures. future.callback = proc () = - if future.failed: raise future.error + if future.failed: + echoOriginalStackTrace(future) + raise future.error + +proc `and`*[T, Y](fut1: PFuture[T], fut2: PFuture[Y]): PFuture[void] = + ## Returns a future which will complete once both ``fut1`` and ``fut2`` + ## complete. + var retFuture = newFuture[void]("asyncdispatch.`and`") + fut1.callback = + proc () = + if fut2.finished: retFuture.complete() + fut2.callback = + proc () = + if fut1.finished: retFuture.complete() + return retFuture + +proc `or`*[T, Y](fut1: PFuture[T], fut2: PFuture[Y]): PFuture[void] = + ## Returns a future which will complete once either ``fut1`` or ``fut2`` + ## complete. + var retFuture = newFuture[void]("asyncdispatch.`or`") + proc cb() = + if not retFuture.finished: retFuture.complete() + fut1.callback = cb + fut2.callback = cb + return retFuture + +type + PDispatcherBase = ref object of PObject + timers: seq[tuple[finishAt: float, fut: PFuture[void]]] + +proc processTimers(p: PDispatcherBase) = + var oldTimers = p.timers + p.timers = @[] + for t in oldTimers: + if epochTime() >= t.finishAt: + t.fut.complete() + else: + p.timers.add(t) when defined(windows) or defined(nimdoc): import winlean, sets, hashes @@ -162,7 +226,7 @@ when defined(windows) or defined(nimdoc): cb: proc (sock: TAsyncFD, bytesTransferred: DWORD, errcode: TOSErrorCode) {.closure,gcsafe.} - PDispatcher* = ref object + PDispatcher* = ref object of PDispatcherBase ioPort: THandle handles: TSet[TAsyncFD] @@ -181,6 +245,7 @@ when defined(windows) or defined(nimdoc): new result result.ioPort = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) result.handles = initSet[TAsyncFD]() + result.timers = @[] var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -207,8 +272,9 @@ when defined(windows) or defined(nimdoc): 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.") + if p.handles.len == 0 and p.timers.len == 0: + raise newException(EInvalidValue, + "No handles or timers registered in dispatcher.") let llTimeout = if timeout == -1: winlean.INFINITE @@ -242,6 +308,9 @@ when defined(windows) or defined(nimdoc): discard else: osError(errCode) + # Timer processing. + processTimers(p) + var connectExPtr: pointer = nil var acceptExPtr: pointer = nil var getAcceptExSockAddrsPtr: pointer = nil @@ -314,7 +383,7 @@ when defined(windows) or defined(nimdoc): ## Returns a ``PFuture`` which will complete when the connection succeeds ## or an error occurs. verifyPresence(socket) - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("connect") # Apparently ``ConnectEx`` expects the socket to be initially bound: var saddr: Tsockaddr_in saddr.sin_family = int16(toInt(af)) @@ -384,7 +453,7 @@ when defined(windows) or defined(nimdoc): # '\0' in the message currently signifies a socket disconnect. Who # knows what will happen when someone sends that to our socket. verifyPresence(socket) - var retFuture = newFuture[string]() + var retFuture = newFuture[string]("recv") var dataBuf: TWSABuf dataBuf.buf = cast[cstring](alloc0(size)) dataBuf.len = size @@ -405,7 +474,10 @@ when defined(windows) or defined(nimdoc): copyMem(addr data[0], addr dataBuf.buf[0], bytesCount) retFuture.complete($data) else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + if flags.isDisconnectionError(errcode): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) if dataBuf.buf != nil: dealloc dataBuf.buf dataBuf.buf = nil @@ -459,7 +531,7 @@ when defined(windows) or defined(nimdoc): ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. verifyPresence(socket) - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("send") var dataBuf: TWSABuf dataBuf.buf = data # since this is not used in a callback, this is fine @@ -474,7 +546,10 @@ when defined(windows) or defined(nimdoc): if errcode == TOSErrorCode(-1): retFuture.complete() else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + if flags.isDisconnectionError(errcode): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) let ret = WSASend(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, @@ -494,15 +569,21 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture - proc acceptAddr*(socket: TAsyncFD): + proc acceptAddr*(socket: TAsyncFD, flags = {TSocketFlags.SafeDisconn}): 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. + ## The resulting client socket is automatically registered to the + ## dispatcher. + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. verifyPresence(socket) - var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]() + var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]("acceptAddr") var clientSock = newRawSocket() if clientSock == osInvalidSocket: osError(osLastError()) @@ -534,6 +615,18 @@ when defined(windows) or defined(nimdoc): client: clientSock.TAsyncFD) ) + template failAccept(errcode): stmt = + if flags.isDisconnectionError(errcode): + var newAcceptFut = acceptAddr(socket, flags) + newAcceptFut.callback = + proc () = + if newAcceptFut.failed: + retFuture.fail(newAcceptFut.readError) + else: + retFuture.complete(newAcceptFut.read) + else: + retFuture.fail(newException(EOS, osErrorMsg(errcode))) + var ol = PCustomOverlapped() GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: @@ -542,7 +635,7 @@ when defined(windows) or defined(nimdoc): if errcode == TOSErrorCode(-1): completeAccept() else: - retFuture.fail(newException(EOS, osErrorMsg(errcode))) + failAccept(errcode) ) # http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx @@ -555,7 +648,7 @@ when defined(windows) or defined(nimdoc): if not ret: let err = osLastError() if err.int32 != ERROR_IO_PENDING: - retFuture.fail(newException(EOS, osErrorMsg(err))) + failAccept(err) GC_unref(ol) else: completeAccept() @@ -606,7 +699,7 @@ else: readCBs: seq[TCallback] writeCBs: seq[TCallback] - PDispatcher* = ref object + PDispatcher* = ref object of PDispatcherBase selector: PSelector proc `==`*(x, y: TAsyncFD): bool {.borrow.} @@ -614,6 +707,7 @@ else: proc newDispatcher*(): PDispatcher = new result result.selector = newSelector() + result.timers = @[] var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -693,10 +787,12 @@ else: else: # FD no longer a part of the selector. Likely been closed # (e.g. socket disconnected). + + processTimers(p) proc connect*(socket: TAsyncFD, address: string, port: TPort, af = AF_INET): PFuture[void] = - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("connect") proc cb(sock: TAsyncFD): bool = # We have connected. @@ -731,7 +827,7 @@ else: proc recv*(socket: TAsyncFD, size: int, flags = {TSocketFlags.SafeDisconn}): PFuture[string] = - var retFuture = newFuture[string]() + var retFuture = newFuture[string]("recv") var readBuffer = newString(size) @@ -762,7 +858,7 @@ else: proc send*(socket: TAsyncFD, data: string, flags = {TSocketFlags.SafeDisconn}): PFuture[void] = - var retFuture = newFuture[void]() + var retFuture = newFuture[void]("send") var written = 0 @@ -792,9 +888,10 @@ else: addWrite(socket, cb) return retFuture - proc acceptAddr*(socket: TAsyncFD): + proc acceptAddr*(socket: TAsyncFD, flags = {TSocketFlags.SafeDisconn}): PFuture[tuple[address: string, client: TAsyncFD]] = - var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]() + var retFuture = newFuture[tuple[address: string, + client: TAsyncFD]]("acceptAddr") proc cb(sock: TAsyncFD): bool = result = true var sockAddress: Tsockaddr_in @@ -807,19 +904,31 @@ else: if lastError.int32 == EINTR: return false else: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if flags.isDisconnectionError(lastError): + 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] = +proc sleepAsync*(ms: int): PFuture[void] = + ## Suspends the execution of the current async procedure for the next + ## ``ms`` miliseconds. + var retFuture = newFuture[void]("sleepAsync") + let p = getGlobalDispatcher() + p.timers.add((epochTime() + (ms / 1000), retFuture)) + return retFuture + +proc accept*(socket: TAsyncFD, + flags = {TSocketFlags.SafeDisconn}): 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) + var retFut = newFuture[TAsyncFD]("accept") + var fut = acceptAddr(socket, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) = assert future.finished @@ -845,11 +954,16 @@ template createCb*(retFutureSym, iteratorNameSym, else: next.callback = cb except: - retFutureSym.fail(getCurrentException()) + if retFutureSym.finished: + # Take a look at tasyncexceptions for the bug which this fixes. + # That test explains it better than I can here. + raise + else: + retFutureSym.fail(getCurrentException()) cb() #{.pop.} proc generateExceptionCheck(futSym, - exceptBranch, rootReceiver: PNimrodNode): PNimrodNode {.compileTime.} = + exceptBranch, rootReceiver, fromNode: PNimrodNode): PNimrodNode {.compileTime.} = if exceptBranch == nil: result = rootReceiver else: @@ -869,20 +983,21 @@ proc generateExceptionCheck(futSym, ) ) ) - let elseNode = newNimNode(nnkElse) - elseNode.add newNimNode(nnkStmtList) + let elseNode = newNimNode(nnkElse, fromNode) + elseNode.add newNimNode(nnkStmtList, fromNode) elseNode[0].add rootReceiver result.add elseNode template createVar(result: var PNimrodNode, futSymName: string, asyncProc: PNimrodNode, - valueReceiver, rootReceiver: expr) = - result = newNimNode(nnkStmtList) + valueReceiver, rootReceiver: expr, + fromNode: PNimrodNode) = + result = newNimNode(nnkStmtList, fromNode) var futSym = genSym(nskVar, "future") result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y - result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x> + result.add newNimNode(nnkYieldStmt, fromNode).add(futSym) # -> yield future<x> valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read - result.add generateExceptionCheck(futSym, exceptBranch, rootReceiver) + result.add generateExceptionCheck(futSym, exceptBranch, rootReceiver, fromNode) proc processBody(node, retFutureSym: PNimrodNode, subTypeIsVoid: bool, @@ -891,7 +1006,7 @@ proc processBody(node, retFutureSym: PNimrodNode, result = node case node.kind of nnkReturnStmt: - result = newNimNode(nnkStmtList) + result = newNimNode(nnkStmtList, node) if node[0].kind == nnkEmpty: if not subtypeIsVoid: result.add newCall(newIdentNode("complete"), retFutureSym, @@ -902,36 +1017,36 @@ proc processBody(node, retFutureSym: PNimrodNode, result.add newCall(newIdentNode("complete"), retFutureSym, node[0].processBody(retFutureSym, subtypeIsVoid, exceptBranch)) - result.add newNimNode(nnkReturnStmt).add(newNilLit()) + result.add newNimNode(nnkReturnStmt, node).add(newNilLit()) return # Don't process the children of this return stmt - of nnkCommand: + of nnkCommand, nnkCall: if node[0].kind == nnkIdent and node[0].ident == !"await": case node[1].kind - of nnkIdent: + of nnkIdent, nnkInfix: # await x - result = newNimNode(nnkYieldStmt).add(node[1]) # -> yield x - of nnkCall: + result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x + of nnkCall, nnkCommand: # await foo(p, x) var futureValue: PNimrodNode result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue, - futureValue) + futureValue, node) 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": + elif node.len > 1 and node[1].kind == nnkCommand and + node[1][0].kind == nnkIdent and node[1][0].ident == !"await": # foo await x var newCommand = node result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1], - newCommand) + newCommand, node) of nnkVarSection, nnkLetSection: case node[0][2].kind of nnkCommand: - if node[0][2][0].ident == !"await": + if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await": # var x = await y var newVarSection = node # TODO: Should this use copyNimNode? result.createVar("future" & $node[0][0].ident, node[0][2][1], - newVarSection[0][2], newVarSection) + newVarSection[0][2], newVarSection, node) else: discard of nnkAsgn: case node[1].kind @@ -939,7 +1054,7 @@ proc processBody(node, retFutureSym: PNimrodNode, if node[1][0].ident == !"await": # x = await y var newAsgn = node - result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn) + result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node) else: discard of nnkDiscardStmt: # discard await x @@ -947,10 +1062,10 @@ proc processBody(node, retFutureSym: PNimrodNode, node[0][0].ident == !"await": var newDiscard = node result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], - newDiscard[0], newDiscard) + newDiscard[0], newDiscard, node) of nnkTryStmt: # try: await x; except: ... - result = newNimNode(nnkStmtList) + result = newNimNode(nnkStmtList, node) proc processForTry(n: PNimrodNode, i: var int, res: PNimrodNode): bool {.compileTime.} = result = false @@ -1009,7 +1124,7 @@ macro async*(prc: stmt): stmt {.immediate.} = (returnType.kind == nnkBracketExpr and returnType[1].kind == nnkIdent and returnType[1].ident == !"void") - var outerProcBody = newNimNode(nnkStmtList) + var outerProcBody = newNimNode(nnkStmtList, prc[6]) # -> var retFuture = newFuture[T]() var retFutureSym = genSym(nskVar, "retFuture") @@ -1019,9 +1134,10 @@ macro async*(prc: stmt): stmt {.immediate.} = outerProcBody.add( newVarStmt(retFutureSym, newCall( - newNimNode(nnkBracketExpr).add( + newNimNode(nnkBracketExpr, prc[6]).add( newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`. - subRetType)))) # Get type from return type of this proc + subRetType), + newLit(prc[0].getName)))) # Get type from return type of this proc # -> iterator nameIter(): PFutureBase {.closure.} = # -> var result: T @@ -1030,7 +1146,7 @@ macro async*(prc: stmt): stmt {.immediate.} = var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil) if not subtypeIsVoid: - procBody.insert(0, newNimNode(nnkVarSection).add( + procBody.insert(0, newNimNode(nnkVarSection, prc[6]).add( newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T procBody.add( newCall(newIdentNode("complete"), @@ -1041,7 +1157,7 @@ macro async*(prc: stmt): stmt {.immediate.} = var closureIterator = newProc(iteratorNameSym, [newIdentNode("PFutureBase")], procBody, nnkIteratorDef) - closureIterator[4] = newNimNode(nnkPragma).add(newIdentNode("closure")) + closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure")) outerProcBody.add(closureIterator) # -> createCb(retFuture) @@ -1051,7 +1167,7 @@ macro async*(prc: stmt): stmt {.immediate.} = outerProcBody.add procCb # -> return retFuture - outerProcBody.add newNimNode(nnkReturnStmt).add(retFutureSym) + outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym) result = prc @@ -1068,8 +1184,8 @@ macro async*(prc: stmt): stmt {.immediate.} = result[6] = outerProcBody #echo(treeRepr(result)) - #if prc[0].getName == "routeReq": - #echo(toStrLit(result)) + #if prc[0].getName == "getFile": + # echo(toStrLit(result)) proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once @@ -1110,3 +1226,11 @@ proc runForever*() = ## Begins a never ending global dispatcher poll loop. while true: poll() + +proc waitFor*[T](fut: PFuture[T]) = + ## **Blocks** the current thread until the specified future completes. + while not fut.finished: + poll() + + if fut.failed: + raise fut.error diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim new file mode 100644 index 000000000..f1b1d1400 --- /dev/null +++ b/lib/pure/asyncftpclient.nim @@ -0,0 +1,295 @@ +# +# +# 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, asyncnet, strutils, parseutils, os, times + +from ftpclient import TFtpBase, EInvalidReply, TFtpEvent +from net import bufferSize + +type + TAsyncFtpClient* = TFtpBase[PAsyncSocket] + PAsyncFtpClient* = ref TAsyncFtpClient + + ProgressChangedProc* = + proc (total, progress: BiggestInt, speed: float): + PFuture[void] {.closure, gcsafe.} + +proc expectReply(ftp: PAsyncFtpClient): PFuture[TaintedString] = + result = ftp.csock.recvLine() + +proc send*(ftp: PAsyncFtpClient, m: string): PFuture[TaintedString] {.async.} = + ## Send a message to the server, and wait for a primary reply. + ## ``\c\L`` is added for you. + await ftp.csock.send(m & "\c\L") + return await ftp.expectReply() + +proc assertReply(received: TaintedString, expected: varargs[string]) = + for i in items(expected): + if received.string.startsWith(i): return + raise newException(EInvalidReply, + "Expected reply '$1' got: $2" % + [expected.join("' or '"), received.string]) + +proc pasv(ftp: PAsyncFtpClient) {.async.} = + ## Negotiate a data connection. + ftp.dsock = newAsyncSocket() + + var pasvMsg = (await ftp.send("PASV")).string.strip.TaintedString + assertReply(pasvMsg, "227") + var betweenParens = captureBetween(pasvMsg.string, '(', ')') + var nums = betweenParens.split(',') + var ip = nums[0.. -3] + var port = nums[-2.. -1] + var properPort = port[0].parseInt()*256+port[1].parseInt() + await ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) + ftp.dsockConnected = True + +proc normalizePathSep(path: string): string = + return replace(path, '\\', '/') + +proc connect*(ftp: PAsyncFtpClient) {.async.} = + ## Connect to the FTP server specified by ``ftp``. + await ftp.csock.connect(ftp.address, ftp.port) + + var reply = await ftp.expectReply() + if reply.startsWith("120"): + # 120 Service ready in nnn minutes. + # We wait until we receive 220. + reply = await ftp.expectReply() + assertReply(reply, "220") + + if ftp.user != "": + assertReply(await(ftp.send("USER " & ftp.user)), "230", "331") + + if ftp.pass != "": + assertReply(await(ftp.send("PASS " & ftp.pass)), "230") + +proc pwd*(ftp: PAsyncFtpClient): PFuture[TaintedString] {.async.} = + ## Returns the current working directory. + let wd = await ftp.send("PWD") + assertReply wd, "257" + return wd.string.captureBetween('"').TaintedString # " + +proc cd*(ftp: PAsyncFtpClient, dir: string) {.async.} = + ## Changes the current directory on the remote FTP server to ``dir``. + assertReply(await(ftp.send("CWD " & dir.normalizePathSep)), "250") + +proc cdup*(ftp: PAsyncFtpClient) {.async.} = + ## Changes the current directory to the parent of the current directory. + assertReply(await(ftp.send("CDUP")), "200") + +proc getLines(ftp: PAsyncFtpClient): PFuture[string] {.async.} = + ## Downloads text data in ASCII mode + result = "" + assert ftp.dsockConnected + while ftp.dsockConnected: + let r = await ftp.dsock.recvLine() + if r.string == "": + ftp.dsockConnected = false + else: + result.add(r.string & "\n") + + assertReply(await(ftp.expectReply()), "226") + +proc listDirs*(ftp: PAsyncFtpClient, dir = ""): PFuture[seq[string]] {.async.} = + ## Returns a list of filenames in the given directory. If ``dir`` is "", + ## the current directory is used. If ``async`` is true, this + ## function will return immediately and it will be your job to + ## use asyncio's ``poll`` to progress this operation. + await ftp.pasv() + + assertReply(await(ftp.send("NLST " & dir.normalizePathSep)), ["125", "150"]) + + result = splitLines(await ftp.getLines()) + +proc existsFile*(ftp: PAsyncFtpClient, file: string): PFuture[bool] {.async.} = + ## Determines whether ``file`` exists. + var files = await ftp.listDirs() + for f in items(files): + if f.normalizePathSep == file.normalizePathSep: return true + +proc createDir*(ftp: PAsyncFtpClient, dir: string, recursive = false){.async.} = + ## Creates a directory ``dir``. If ``recursive`` is true, the topmost + ## subdirectory of ``dir`` will be created first, following the secondmost... + ## etc. this allows you to give a full path as the ``dir`` without worrying + ## about subdirectories not existing. + if not recursive: + assertReply(await(ftp.send("MKD " & dir.normalizePathSep)), "257") + else: + var reply = TaintedString"" + var previousDirs = "" + for p in split(dir, {os.dirSep, os.altSep}): + if p != "": + previousDirs.add(p) + reply = await ftp.send("MKD " & previousDirs) + previousDirs.add('/') + assertReply reply, "257" + +proc chmod*(ftp: PAsyncFtpClient, path: string, + permissions: set[TFilePermission]) {.async.} = + ## Changes permission of ``path`` to ``permissions``. + var userOctal = 0 + var groupOctal = 0 + var otherOctal = 0 + for i in items(permissions): + case i + of fpUserExec: userOctal.inc(1) + of fpUserWrite: userOctal.inc(2) + of fpUserRead: userOctal.inc(4) + of fpGroupExec: groupOctal.inc(1) + of fpGroupWrite: groupOctal.inc(2) + of fpGroupRead: groupOctal.inc(4) + of fpOthersExec: otherOctal.inc(1) + of fpOthersWrite: otherOctal.inc(2) + of fpOthersRead: otherOctal.inc(4) + + var perm = $userOctal & $groupOctal & $otherOctal + assertReply(await(ftp.send("SITE CHMOD " & perm & + " " & path.normalizePathSep)), "200") + +proc list*(ftp: PAsyncFtpClient, dir = ""): PFuture[string] {.async.} = + ## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current + ## working directory. + await ftp.pasv() + + let reply = await ftp.send("LIST" & " " & dir.normalizePathSep) + assertReply(reply, ["125", "150"]) + + result = await ftp.getLines() + +proc retrText*(ftp: PAsyncFtpClient, file: string): PFuture[string] {.async.} = + ## Retrieves ``file``. File must be ASCII text. + await ftp.pasv() + let reply = await ftp.send("RETR " & file.normalizePathSep) + assertReply(reply, ["125", "150"]) + + result = await ftp.getLines() + +proc getFile(ftp: PAsyncFtpClient, file: TFile, total: BiggestInt, + onProgressChanged: ProgressChangedProc) {.async.} = + assert ftp.dsockConnected + var progress = 0 + var progressInSecond = 0 + var countdownFut = sleepAsync(1000) + var dataFut = ftp.dsock.recv(bufferSize) + while ftp.dsockConnected: + await dataFut or countdownFut + if countdownFut.finished: + asyncCheck onProgressChanged(total, progress, + progressInSecond.float) + progressInSecond = 0 + countdownFut = sleepAsync(1000) + + if dataFut.finished: + let data = dataFut.read + if data != "": + progress.inc(data.len) + progressInSecond.inc(data.len) + file.write(data) + dataFut = ftp.dsock.recv(bufferSize) + else: + ftp.dsockConnected = False + + assertReply(await(ftp.expectReply()), "226") + +proc defaultOnProgressChanged*(total, progress: BiggestInt, + speed: float): PFuture[void] {.nimcall,gcsafe.} = + ## Default FTP ``onProgressChanged`` handler. Does nothing. + result = newFuture[void]() + #echo(total, " ", progress, " ", speed) + result.complete() + +proc retrFile*(ftp: PAsyncFtpClient, file, dest: string, + onProgressChanged = defaultOnProgressChanged) {.async.} = + ## Downloads ``file`` and saves it to ``dest``. + ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function + ## when the download is finished. The event's ``filename`` field will be equal + ## to ``file``. + var destFile = open(dest, mode = fmWrite) + await ftp.pasv() + var reply = await ftp.send("RETR " & file.normalizePathSep) + assertReply reply, ["125", "150"] + if {'(', ')'} notin reply.string: + raise newException(EInvalidReply, "Reply has no file size.") + var fileSize: biggestInt + if reply.string.captureBetween('(', ')').parseBiggestInt(fileSize) == 0: + raise newException(EInvalidReply, "Reply has no file size.") + + await getFile(ftp, destFile, fileSize, onProgressChanged) + +proc doUpload(ftp: PAsyncFtpClient, file: TFile, + onProgressChanged: ProgressChangedProc) {.async.} = + assert ftp.dsockConnected + + let total = file.getFileSize() + var data = newStringOfCap(4000) + var progress = 0 + var progressInSecond = 0 + var countdownFut = sleepAsync(1000) + var sendFut: PFuture[void] = nil + while ftp.dsockConnected: + if sendFut == nil or sendFut.finished: + progress.inc(data.len) + progressInSecond.inc(data.len) + # TODO: Async file reading. + let len = file.readBuffer(addr(data[0]), 4000) + setLen(data, len) + if len == 0: + # File finished uploading. + ftp.dsock.close() + ftp.dsockConnected = false + + assertReply(await(ftp.expectReply()), "226") + else: + sendFut = ftp.dsock.send(data) + + if countdownFut.finished: + asyncCheck onProgressChanged(total, progress, progressInSecond.float) + progressInSecond = 0 + countdownFut = sleepAsync(1000) + + await countdownFut or sendFut + +proc storeFile*(ftp: PAsyncFtpClient, file, dest: string, + onProgressChanged = defaultOnProgressChanged) {.async.} = + ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this + ## function asynchronously is recommended to view the progress of + ## the download. + ## The ``EvStore`` event is passed to the specified ``handleEvent`` function + ## when the upload is finished, and the ``filename`` field will be + ## equal to ``file``. + var destFile = open(file) + await ftp.pasv() + + let reply = await ftp.send("STOR " & dest.normalizePathSep) + assertReply reply, ["125", "150"] + + await doUpload(ftp, destFile, onProgressChanged) + +proc newAsyncFtpClient*(address: string, port = TPort(21), + user, pass = ""): PAsyncFtpClient = + ## Creates a new ``PAsyncFtpClient`` object. + new result + result.user = user + result.pass = pass + result.address = address + result.port = port + result.dsockConnected = false + result.csock = newAsyncSocket() + +when isMainModule: + var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") + proc main(ftp: PAsyncFtpClient) {.async.} = + await ftp.connect() + echo await ftp.pwd() + echo await ftp.listDirs() + await ftp.storeFile("payload.jpg", "payload.jpg") + await ftp.retrFile("payload.jpg", "payload2.jpg") + echo("Finished") + + waitFor main(ftp) diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index ee6658fd1..c8bd5cfc1 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -11,14 +11,14 @@ ## ## **Note:** This module is still largely experimental. -import strtabs, asyncnet, asyncdispatch, parseutils, parseurl, strutils +import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils type TRequest* = object client*: PAsyncSocket # TODO: Separate this into a Response object? reqMethod*: string headers*: PStringTable protocol*: tuple[orig: string, major, minor: int] - url*: TURL + url*: TUri hostname*: string ## The hostname of the client that made the request. body*: string @@ -97,7 +97,8 @@ proc sendStatus(client: PAsyncSocket, status: string): PFuture[void] = client.send("HTTP/1.1 " & status & "\c\L") proc processClient(client: PAsyncSocket, address: string, - callback: proc (request: TRequest): PFuture[void]) {.async.} = + callback: proc (request: TRequest): + PFuture[void] {.closure, gcsafe.}) {.async.} = while true: # GET /path HTTP/1.1 # Header: val @@ -135,7 +136,7 @@ proc processClient(client: PAsyncSocket, address: string, request.headers[kv.key] = kv.value request.reqMethod = reqMethod - request.url = parseUrl(path) + request.url = parseUri(path) try: request.protocol = protocol.parseProtocol() except EInvalidValue: @@ -184,7 +185,7 @@ proc processClient(client: PAsyncSocket, address: string, break proc serve*(server: PAsyncHttpServer, port: TPort, - callback: proc (request: TRequest): PFuture[void], + callback: proc (request: TRequest): PFuture[void] {.closure,gcsafe.}, address = "") {.async.} = ## Starts the process of listening for incoming HTTP connections on the ## specified address and port. @@ -199,19 +200,23 @@ proc serve*(server: PAsyncHttpServer, port: TPort, #var (address, client) = await server.socket.acceptAddr() var fut = await server.socket.acceptAddr() asyncCheck processClient(fut.client, fut.address, callback) + #echo(f.isNil) + #echo(f.repr) proc close*(server: PAsyncHttpServer) = ## Terminates the async http server instance. server.socket.close() when isMainModule: - var server = newAsyncHttpServer() - proc cb(req: TRequest) {.async.} = - #echo(req.reqMethod, " ", req.url) - #echo(req.headers) - let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT", - "Content-type": "text/plain; charset=utf-8"} - await req.respond(Http200, "Hello World", headers.newStringTable()) - - asyncCheck server.serve(TPort(5555), cb) - runForever() + proc main = + var server = newAsyncHttpServer() + proc cb(req: TRequest) {.async.} = + #echo(req.reqMethod, " ", req.url) + #echo(req.headers) + let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT", + "Content-type": "text/plain; charset=utf-8"} + await req.respond(Http200, "Hello World", headers.newStringTable()) + + asyncCheck server.serve(TPort(5555), cb) + runForever() + main() diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index c68ca4350..6b67bf4b5 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -671,25 +671,26 @@ when isMainModule: testRead(s, 2) disp.register(client) - var d = newDispatcher() - - var s = AsyncSocket() - s.connect("amber.tenthbit.net", TPort(6667)) - s.handleConnect = - proc (s: PAsyncSocket) = - testConnect(s, 1) - s.handleRead = - proc (s: PAsyncSocket) = - testRead(s, 1) - d.register(s) - - var server = AsyncSocket() - server.handleAccept = - proc (s: PAsyncSocket) = - testAccept(s, d, 78) - server.bindAddr(TPort(5555)) - server.listen() - d.register(server) - - while d.poll(-1): discard + proc main = + var d = newDispatcher() + + var s = AsyncSocket() + s.connect("amber.tenthbit.net", TPort(6667)) + s.handleConnect = + proc (s: PAsyncSocket) = + testConnect(s, 1) + s.handleRead = + proc (s: PAsyncSocket) = + testRead(s, 1) + d.register(s) + + var server = AsyncSocket() + server.handleAccept = + proc (s: PAsyncSocket) = + testAccept(s, d, 78) + server.bindAddr(TPort(5555)) + server.listen() + d.register(server) + while d.poll(-1): discard + main() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 374ac77e3..5095d9461 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -36,9 +36,9 @@ ## let client = await server.accept() ## clients.add client ## -## processClient(client) +## asyncCheck processClient(client) ## -## serve() +## asyncCheck serve() ## runForever() ## ## @@ -135,13 +135,13 @@ proc send*(socket: PAsyncSocket, data: string, assert socket != nil result = send(socket.fd.TAsyncFD, data, flags) -proc acceptAddr*(socket: PAsyncSocket): +proc acceptAddr*(socket: PAsyncSocket, flags = {TSocketFlags.SafeDisconn}): 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) + var retFuture = newFuture[tuple[address: string, client: PAsyncSocket]]("asyncnet.acceptAddr") + var fut = acceptAddr(socket.fd.TAsyncFD, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: TAsyncFD]]) = assert future.finished @@ -153,12 +153,13 @@ proc acceptAddr*(socket: PAsyncSocket): retFuture.complete(resultTup) return retFuture -proc accept*(socket: PAsyncSocket): PFuture[PAsyncSocket] = +proc accept*(socket: PAsyncSocket, + flags = {TSocketFlags.SafeDisconn}): 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) + var retFut = newFuture[PAsyncSocket]("asyncnet.accept") + var fut = acceptAddr(socket, flags) fut.callback = proc (future: PFuture[tuple[address: string, client: PAsyncSocket]]) = assert future.finished diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index c50c4165b..2629e9f40 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -409,6 +409,23 @@ template mapIt*(varSeq, pred: expr) = let it {.inject.} = varSeq[i] varSeq[i] = pred +template newSeqWith*(len: int, init: expr): expr = + ## creates a new sequence, calling `init` to initialize each value. Example: + ## + ## .. code-block:: nimrod + ## var seq2D = newSeqWith(20, newSeq[bool](10)) + ## seq2D[0][0] = true + ## seq2D[1][0] = true + ## seq2D[0][1] = true + ## + ## import math + ## var seqRand = newSeqWith(20, random(10)) + ## echo seqRand + var result {.gensym.} = newSeq[type(init)](len) + for i in 0 .. <len: + result[i] = init + result + when isMainModule: import strutils block: # concat test @@ -557,4 +574,11 @@ when isMainModule: doAssert b.distribute(5, true)[4].len == 5 doAssert b.distribute(5, false)[4].len == 2 + block: # newSeqWith tests + var seq2D = newSeqWith(4, newSeq[bool](2)) + seq2D[0][0] = true + seq2D[1][0] = true + seq2D[0][1] = true + doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]] + echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 42cdc682f..ce901963e 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -128,7 +128,7 @@ proc mget*[A](s: var TSet[A], key: A): var A = ## for sharing. assert s.isValid, "The set needs to be initialized." var index = rawGet(s, key) - if index >= 0: result = t.data[index].key + if index >= 0: result = s.data[index].key else: raise newException(EInvalidKey, "key not found: " & $key) proc contains*[A](s: TSet[A], key: A): bool = @@ -713,6 +713,24 @@ proc `$`*[A](s: TOrderedSet[A]): string = assert s.isValid, "The set needs to be initialized." dollarImpl() +proc `==`*[A](s, t: TOrderedSet[A]): bool = + ## Equality for ordered sets. + if s.counter != t.counter: return false + var h = s.first + var g = s.first + var compared = 0 + while h >= 0 and g >= 0: + var nxh = s.data[h].next + var nxg = t.data[g].next + if s.data[h].slot == seFilled and s.data[g].slot == seFilled: + if s.data[h].key == s.data[g].key: + inc compared + else: + return false + h = nxh + g = nxg + result = compared == s.counter + proc testModule() = ## Internal micro test to validate docstrings and such. block isValidTest: @@ -858,7 +876,7 @@ proc testModule() = var b = initOrderedSet[int]() for x in [2, 4, 5]: b.incl(x) assert($a == $b) - # assert(a == b) # https://github.com/Araq/Nimrod/issues/1413 + assert(a == b) # https://github.com/Araq/Nimrod/issues/1413 block initBlocks: var a: TOrderedSet[int] diff --git a/lib/pure/concurrency/cpuinfo.nim b/lib/pure/concurrency/cpuinfo.nim index dfa819f64..8d7f28f8e 100644 --- a/lib/pure/concurrency/cpuinfo.nim +++ b/lib/pure/concurrency/cpuinfo.nim @@ -18,15 +18,24 @@ when not defined(windows): when defined(linux): import linux + +when defined(freebsd) or defined(macosx): + {.emit:"#include <sys/types.h>".} + +when defined(openbsd) or defined(netbsd): + {.emit:"#include <sys/param.h>".} when defined(macosx) or defined(bsd): + # we HAVE to emit param.h before sysctl.h so we cannot use .header here + # either. The amount of archaic bullshit in Poonix based OSes is just insane. + {.emit:"#include <sys/sysctl.h>".} const CTL_HW = 6 HW_AVAILCPU = 25 HW_NCPU = 3 proc sysctl(x: ptr array[0..3, cint], y: cint, z: pointer, a: var csize, b: pointer, c: int): cint {. - importc: "sysctl", header: "<sys/sysctl.h>".} + importc: "sysctl", nodecl.} proc countProcessors*(): int {.rtl, extern: "ncpi$1".} = ## returns the numer of the processors/cores the machine has. diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index fd1041918..f46822d94 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -9,6 +9,9 @@ ## Implements Nimrod's 'spawn'. +when not compileOption("threads"): + {.error: "Threadpool requires --threads:on option.".} + import cpuinfo, cpuload, locks {.push stackTrace:off.} @@ -92,7 +95,7 @@ type FlowVarBase* = ref FlowVarBaseObj ## untyped base class for 'FlowVar[T]' FlowVarBaseObj = object of TObject - ready, usesCondVar: bool + ready, usesCondVar, awaited: bool cv: CondVar #\ # for 'awaitAny' support ai: ptr AwaitInfo @@ -126,15 +129,15 @@ type proc await*(fv: FlowVarBase) = ## waits until the value for the flowVar arrives. Usually it is not necessary ## to call this explicitly. - if fv.usesCondVar: - fv.usesCondVar = false + if fv.usesCondVar and not fv.awaited: + fv.awaited = true await(fv.cv) destroyCondVar(fv.cv) proc finished(fv: FlowVarBase) = doAssert fv.ai.isNil, "flowVar is still attached to an 'awaitAny'" # we have to protect against the rare cases where the owner of the flowVar - # simply disregards the flowVar and yet the "flowVarr" has not yet written + # simply disregards the flowVar and yet the "flowVar" has not yet written # anything to it: await(fv) if fv.data.isNil: return @@ -207,6 +210,7 @@ proc `^`*[T](fv: FlowVar[T]): T = ## blocks until the value is available and then returns this value. await(fv) when T is string or T is seq: + # XXX closures? deepCopy? result = cast[T](fv.data) else: result = fv.blob @@ -264,6 +268,10 @@ proc slave(w: ptr Worker) {.thread.} = w.shutdown = false atomicDec currentPoolSize +var + workers: array[MaxThreadPoolSize, TThread[ptr Worker]] + workersData: array[MaxThreadPoolSize, Worker] + proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) = ## sets the minimal thread pool size. The default value of this is 4. minPoolSize = size @@ -272,10 +280,10 @@ proc setMaxPoolSize*(size: range[1..MaxThreadPoolSize]) = ## sets the minimal thread pool size. The default value of this ## is ``MaxThreadPoolSize``. maxPoolSize = size - -var - workers: array[MaxThreadPoolSize, TThread[ptr Worker]] - workersData: array[MaxThreadPoolSize, Worker] + if currentPoolSize > maxPoolSize: + for i in maxPoolSize..currentPoolSize-1: + let w = addr(workersData[i]) + w.shutdown = true proc activateThread(i: int) {.noinline.} = workersData[i].taskArrived = createCondVar() diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index d1cf36a87..49bf92980 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -28,8 +28,9 @@ proc parseCookies*(s: string): PStringTable = if s[i] == '\0': break inc(i) # skip ';' -proc setCookie*(key, value: string, domain = "", path = "", - expires = "", noName = false): string = +proc setCookie*(key, value: string, domain = "", path = "", + expires = "", noName = false, + secure = false, httpOnly = false): string = ## Creates a command in the format of ## ``Set-Cookie: key=value; Domain=...; ...`` result = "" @@ -38,16 +39,20 @@ proc setCookie*(key, value: string, domain = "", path = "", if domain != "": result.add("; Domain=" & domain) if path != "": result.add("; Path=" & path) if expires != "": result.add("; Expires=" & expires) + if secure: result.add("; secure") + if httpOnly: result.add("; HttpOnly") proc setCookie*(key, value: string, expires: TTimeInfo, - domain = "", path = "", noName = false): string = + domain = "", path = "", noName = false, + secure = false, httpOnly = false): string = ## Creates a command in the format of ## ``Set-Cookie: key=value; Domain=...; ...`` ## ## **Note:** UTC is assumed as the timezone for ``expires``. return setCookie(key, value, domain, path, - format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"), noname) + format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'UTC'"), + noname, secure, httpOnly) when isMainModule: var tim = TTime(int(getTime()) + 76 * (60 * 60 * 24)) @@ -55,5 +60,3 @@ when isMainModule: echo(setCookie("test", "value", tim.getGMTime())) echo parseCookies("uid=1; kp=2") - - \ No newline at end of file diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index 53f6688b9..e8d3f762e 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -10,6 +10,10 @@ include "system/inclrtl" import sockets, strutils, parseutils, times, os, asyncio +from asyncnet import nil +from rawsockets import nil +from asyncdispatch import PFuture + ## This module **partially** implements an FTP client as specified ## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_. ## @@ -34,34 +38,32 @@ import sockets, strutils, parseutils, times, os, asyncio type - TFTPClient* = object of TObject - case isAsync: bool - of false: - csock: TSocket # Command connection socket - dsock: TSocket # Data connection socket - else: - dummyA, dummyB: pointer # workaround a Nimrod API issue - asyncCSock: PAsyncSocket - asyncDSock: PAsyncSocket + PFtpBase*[SockType] = ref TFtpBase[SockType] + TFtpBase*[SockType] = object + csock*: SockType + dsock*: SockType + when SockType is asyncio.PAsyncSocket: handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent){.closure,gcsafe.} disp: PDispatcher asyncDSockID: PDelegate - user, pass: string - address: string - port: TPort + user*, pass*: string + address*: string + when SockType is asyncnet.PAsyncSocket: + port*: rawsockets.TPort + else: + port*: TPort - jobInProgress: bool - job: ref TFTPJob + jobInProgress*: bool + job*: PFTPJob[SockType] - dsockConnected: bool - - PFTPClient* = ref TFTPClient + dsockConnected*: bool FTPJobType* = enum JRetrText, JRetr, JStore - TFTPJob = object - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall, gcsafe.} + PFtpJob[T] = ref TFtpJob[T] + TFTPJob[T] = object + prc: proc (ftp: PFTPBase[T], async: bool): bool {.nimcall, gcsafe.} case typ*: FTPJobType of JRetrText: lines: string @@ -75,8 +77,11 @@ type toStore: string # Data left to upload (Only used with async) else: nil + TFtpClient* = TFtpBase[TSocket] + PFtpClient* = ref TFTPClient + PAsyncFTPClient* = ref TAsyncFTPClient ## Async alternative to TFTPClient. - TAsyncFTPClient* = object of TFTPClient + TAsyncFTPClient* = TFtpBase[asyncio.PAsyncSocket] FTPEventType* = enum EvTransferProgress, EvLines, EvRetr, EvStore @@ -106,30 +111,30 @@ proc ftpClient*(address: string, port = TPort(21), result.address = address result.port = port - result.isAsync = false result.dsockConnected = false result.csock = socket() if result.csock == InvalidSocket: osError(osLastError()) -proc getDSock(ftp: PFTPClient): TSocket = - if ftp.isAsync: return ftp.asyncDSock else: return ftp.dsock +proc getDSock[T](ftp: PFTPBase[T]): TSocket = + return ftp.dsock -proc getCSock(ftp: PFTPClient): TSocket = - if ftp.isAsync: return ftp.asyncCSock else: return ftp.csock +proc getCSock[T](ftp: PFTPBase[T]): TSocket = + return ftp.csock template blockingOperation(sock: TSocket, body: stmt) {.immediate.} = - if ftp.isAsync: - sock.setBlocking(true) body - if ftp.isAsync: - sock.setBlocking(false) -proc expectReply(ftp: PFTPClient): TaintedString = +template blockingOperation(sock: asyncio.PAsyncSocket, body: stmt) {.immediate.} = + sock.setBlocking(true) + body + sock.setBlocking(false) + +proc expectReply[T](ftp: PFtpBase[T]): TaintedString = result = TaintedString"" blockingOperation(ftp.getCSock()): ftp.getCSock().readLine(result) -proc send*(ftp: PFTPClient, m: string): TaintedString = +proc send*[T](ftp: PFtpBase[T], m: string): TaintedString = ## Send a message to the server, and wait for a primary reply. ## ``\c\L`` is added for you. blockingOperation(ftp.getCSock()): @@ -149,8 +154,8 @@ proc assertReply(received: TaintedString, expected: varargs[string]) = "Expected reply '$1' got: $2" % [expected.join("' or '"), received.string]) -proc createJob(ftp: PFTPClient, - prc: proc (ftp: PFTPClient, async: bool): bool {. +proc createJob[T](ftp: PFtpBase[T], + prc: proc (ftp: PFtpBase[T], async: bool): bool {. nimcall,gcsafe.}, cmd: FTPJobType) = if ftp.jobInProgress: @@ -165,7 +170,7 @@ proc createJob(ftp: PFTPClient, of JRetr, JStore: ftp.job.toStore = "" -proc deleteJob(ftp: PFTPClient) = +proc deleteJob[T](ftp: PFtpBase[T]) = assert ftp.jobInProgress ftp.jobInProgress = false case ftp.job.typ @@ -173,12 +178,9 @@ proc deleteJob(ftp: PFTPClient) = ftp.job.lines = "" of JRetr, JStore: ftp.job.file.close() - if ftp.isAsync: - ftp.asyncDSock.close() - else: - ftp.dsock.close() + ftp.dsock.close() -proc handleTask(s: PAsyncSocket, ftp: PFTPClient) = +proc handleTask(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: if ftp.job.typ in {JRetr, JStore}: if epochTime() - ftp.job.lastProgressReport >= 1.0: @@ -193,12 +195,12 @@ proc handleTask(s: PAsyncSocket, ftp: PFTPClient) = ftp.job.oneSecond = 0 ftp.handleEvent(PAsyncFTPClient(ftp), r) -proc handleWrite(s: PAsyncSocket, ftp: PFTPClient) = +proc handleWrite(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: if ftp.job.typ == JStore: assert (not ftp.job.prc(ftp, true)) -proc handleConnect(s: PAsyncSocket, ftp: PFTPClient) = +proc handleConnect(s: PAsyncSocket, ftp: PAsyncFTPClient) = ftp.dsockConnected = true assert(ftp.jobInProgress) if ftp.job.typ == JStore: @@ -206,30 +208,32 @@ proc handleConnect(s: PAsyncSocket, ftp: PFTPClient) = else: s.delHandleWrite() -proc handleRead(s: PAsyncSocket, ftp: PFTPClient) = +proc handleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = assert ftp.jobInProgress assert ftp.job.typ != JStore # This can never return true, because it shouldn't check for code # 226 from csock. assert(not ftp.job.prc(ftp, true)) -proc pasv(ftp: PFTPClient) = +proc pasv[T](ftp: PFtpBase[T]) = ## Negotiate a data connection. - if not ftp.isAsync: + when T is TSocket: ftp.dsock = socket() if ftp.dsock == InvalidSocket: osError(osLastError()) - else: - ftp.asyncDSock = AsyncSocket() - ftp.asyncDSock.handleRead = + elif T is PAsyncSocket: + ftp.dsock = AsyncSocket() + ftp.dsock.handleRead = proc (s: PAsyncSocket) = handleRead(s, ftp) - ftp.asyncDSock.handleConnect = + ftp.dsock.handleConnect = proc (s: PAsyncSocket) = handleConnect(s, ftp) - ftp.asyncDSock.handleTask = + ftp.dsock.handleTask = proc (s: PAsyncSocket) = handleTask(s, ftp) - ftp.disp.register(ftp.asyncDSock) + ftp.disp.register(ftp.dsock) + else: + {.fatal: "Incorrect socket instantiation".} var pasvMsg = ftp.send("PASV").string.strip.TaintedString assertReply(pasvMsg, "227") @@ -238,23 +242,24 @@ proc pasv(ftp: PFTPClient) = var ip = nums[0.. -3] var port = nums[-2.. -1] var properPort = port[0].parseInt()*256+port[1].parseInt() - if ftp.isAsync: - ftp.asyncDSock.connect(ip.join("."), TPort(properPort.toU16)) + ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) + when T is PAsyncSocket: ftp.dsockConnected = False else: - ftp.dsock.connect(ip.join("."), TPort(properPort.toU16)) ftp.dsockConnected = True proc normalizePathSep(path: string): string = return replace(path, '\\', '/') -proc connect*(ftp: PFTPClient) = +proc connect*[T](ftp: PFtpBase[T]) = ## Connect to the FTP server specified by ``ftp``. - if ftp.isAsync: - blockingOperation(ftp.asyncCSock): - ftp.asyncCSock.connect(ftp.address, ftp.port) - else: + when T is PAsyncSocket: + blockingOperation(ftp.csock): + ftp.csock.connect(ftp.address, ftp.port) + elif T is TSocket: ftp.csock.connect(ftp.address, ftp.port) + else: + {.fatal: "Incorrect socket instantiation".} # TODO: Handle 120? or let user handle it. assertReply ftp.expectReply(), "220" @@ -279,25 +284,27 @@ proc cdup*(ftp: PFTPClient) = ## Changes the current directory to the parent of the current directory. assertReply ftp.send("CDUP"), "200" -proc getLines(ftp: PFTPClient, async: bool = false): bool = +proc getLines[T](ftp: PFtpBase[T], async: bool = false): bool = ## Downloads text data in ASCII mode ## Returns true if the download is complete. ## It doesn't if `async` is true, because it doesn't check for 226 then. if ftp.dsockConnected: var r = TaintedString"" - if ftp.isAsync: + when T is PAsyncSocket: if ftp.asyncDSock.readLine(r): if r.string == "": ftp.dsockConnected = false else: ftp.job.lines.add(r.string & "\n") - else: + elif T is TSocket: assert(not async) ftp.dsock.readLine(r) if r.string == "": ftp.dsockConnected = false else: ftp.job.lines.add(r.string & "\n") + else: + {.fatal: "Incorrect socket instantiation".} if not async: var readSocks: seq[TSocket] = @[ftp.getCSock()] @@ -307,14 +314,14 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool = assertReply ftp.expectReply(), "226" return true -proc listDirs*(ftp: PFTPClient, dir: string = "", +proc listDirs*[T](ftp: PFtpBase[T], dir: string = "", async = false): seq[string] = ## Returns a list of filenames in the given directory. If ``dir`` is "", ## the current directory is used. If ``async`` is true, this ## function will return immediately and it will be your job to ## use asyncio's ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply ftp.send("NLST " & dir.normalizePathSep), ["125", "150"] @@ -384,12 +391,12 @@ proc chmod*(ftp: PFTPClient, path: string, assertReply ftp.send("SITE CHMOD " & perm & " " & path.normalizePathSep), "200" -proc list*(ftp: PFTPClient, dir: string = "", async = false): string = +proc list*[T](ftp: PFtpBase[T], dir: string = "", async = false): string = ## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current ## working directory. If ``async`` is true, this function will return ## immediately and it will be your job to call asyncio's ## ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply(ftp.send("LIST" & " " & dir.normalizePathSep), ["125", "150"]) @@ -401,11 +408,11 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string = else: return "" -proc retrText*(ftp: PFTPClient, file: string, async = false): string = +proc retrText*[T](ftp: PFtpBase[T], file: string, async = false): string = ## Retrieves ``file``. File must be ASCII text. ## If ``async`` is true, this function will return immediately and ## it will be your job to call asyncio's ``poll`` to progress this operation. - ftp.createJob(getLines, JRetrText) + ftp.createJob(getLines[T], JRetrText) ftp.pasv() assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"] @@ -416,15 +423,17 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string = else: return "" -proc getFile(ftp: PFTPClient, async = false): bool = +proc getFile[T](ftp: PFtpBase[T], async = false): bool = if ftp.dsockConnected: var r = "".TaintedString var bytesRead = 0 var returned = false if async: - if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.") - bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize) - returned = bytesRead != -1 + when T is TSocket: + raise newException(EFTP, "FTPClient must be async.") + else: + bytesRead = ftp.dsock.recvAsync(r, BufferSize) + returned = bytesRead != -1 else: bytesRead = getDSock(ftp).recv(r, BufferSize) returned = true @@ -443,13 +452,13 @@ proc getFile(ftp: PFTPClient, async = false): bool = assertReply ftp.expectReply(), "226" return true -proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = +proc retrFile*[T](ftp: PFtpBase[T], file, dest: string, async = false) = ## Downloads ``file`` and saves it to ``dest``. Usage of this function ## asynchronously is recommended to view the progress of the download. ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function ## when the download is finished, and the ``filename`` field will be equal ## to ``file``. - ftp.createJob(getFile, JRetr) + ftp.createJob(getFile[T], JRetr) ftp.job.file = open(dest, mode = fmWrite) ftp.pasv() var reply = ftp.send("RETR " & file.normalizePathSep) @@ -468,11 +477,11 @@ proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = while not ftp.job.prc(ftp, false): discard ftp.deleteJob() -proc doUpload(ftp: PFTPClient, async = false): bool = +proc doUpload[T](ftp: PFtpBase[T], async = false): bool = if ftp.dsockConnected: if ftp.job.toStore.len() > 0: assert(async) - let bytesSent = ftp.asyncDSock.sendAsync(ftp.job.toStore) + let bytesSent = ftp.dsock.sendAsync(ftp.job.toStore) if bytesSent == ftp.job.toStore.len: ftp.job.toStore = "" elif bytesSent != ftp.job.toStore.len and bytesSent != 0: @@ -485,7 +494,7 @@ proc doUpload(ftp: PFTPClient, async = false): bool = setLen(s, len) if len == 0: # File finished uploading. - if ftp.isAsync: ftp.asyncDSock.close() else: ftp.dsock.close() + ftp.dsock.close() ftp.dsockConnected = false if not async: @@ -496,7 +505,7 @@ proc doUpload(ftp: PFTPClient, async = false): bool = if not async: getDSock(ftp).send(s) else: - let bytesSent = ftp.asyncDSock.sendAsync(s) + let bytesSent = ftp.dsock.sendAsync(s) if bytesSent == 0: ftp.job.toStore.add(s) elif bytesSent != s.len: @@ -506,14 +515,14 @@ proc doUpload(ftp: PFTPClient, async = false): bool = ftp.job.progress.inc(len) ftp.job.oneSecond.inc(len) -proc store*(ftp: PFTPClient, file, dest: string, async = false) = +proc store*[T](ftp: PFtpBase[T], file, dest: string, async = false) = ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## function asynchronously is recommended to view the progress of ## the download. ## The ``EvStore`` event is passed to the specified ``handleEvent`` function ## when the upload is finished, and the ``filename`` field will be ## equal to ``file``. - ftp.createJob(doUpload, JStore) + ftp.createJob(doUpload[T], JStore) ftp.job.file = open(file) ftp.job.total = ftp.job.file.getFileSize() ftp.job.lastProgressReport = epochTime() @@ -526,16 +535,12 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) = while not ftp.job.prc(ftp, false): discard ftp.deleteJob() -proc close*(ftp: PFTPClient) = +proc close*[T](ftp: PFTPBase[T]) = ## Terminates the connection to the server. assertReply ftp.send("QUIT"), "221" if ftp.jobInProgress: ftp.deleteJob() - if ftp.isAsync: - ftp.asyncCSock.close() - ftp.asyncDSock.close() - else: - ftp.csock.close() - ftp.dsock.close() + ftp.csock.close() + ftp.dsock.close() proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: @@ -572,66 +577,65 @@ proc asyncFTPClient*(address: string, port = TPort(21), dres.pass = pass dres.address = address dres.port = port - dres.isAsync = true dres.dsockConnected = false dres.handleEvent = handleEvent - dres.asyncCSock = AsyncSocket() - dres.asyncCSock.handleRead = + dres.csock = AsyncSocket() + dres.csock.handleRead = proc (s: PAsyncSocket) = csockHandleRead(s, dres) result = dres proc register*(d: PDispatcher, ftp: PAsyncFTPClient): PDelegate {.discardable.} = ## Registers ``ftp`` with dispatcher ``d``. - assert ftp.isAsync ftp.disp = d - return ftp.disp.register(ftp.asyncCSock) + return ftp.disp.register(ftp.csock) when isMainModule: - var d = newDispatcher() - let hev = - proc (ftp: PAsyncFTPClient, event: TFTPEvent) = - case event.typ - of EvStore: - echo("Upload finished!") - ftp.retrFile("payload.JPG", "payload2.JPG", async = true) - of EvTransferProgress: - var time: int64 = -1 - if event.speed != 0: - time = (event.bytesTotal - event.bytesFinished) div event.speed - echo(event.currentJob) - echo(event.speed div 1000, " kb/s. - ", - event.bytesFinished, "/", event.bytesTotal, - " - ", time, " seconds") - echo(d.len) - of EvRetr: - echo("Download finished!") - ftp.close() - echo d.len - else: assert(false) - var ftp = asyncFTPClient("picheta.me", user = "test", pass = "asf", handleEvent = hev) - - d.register(ftp) - d.len.echo() - ftp.connect() - echo "connected" - ftp.store("payload.JPG", "payload.JPG", async = true) - d.len.echo() - echo "uploading..." - while true: - if not d.poll(): break - + proc main = + var d = newDispatcher() + let hev = + proc (ftp: PAsyncFTPClient, event: TFTPEvent) = + case event.typ + of EvStore: + echo("Upload finished!") + ftp.retrFile("payload.jpg", "payload2.jpg", async = true) + of EvTransferProgress: + var time: int64 = -1 + if event.speed != 0: + time = (event.bytesTotal - event.bytesFinished) div event.speed + echo(event.currentJob) + echo(event.speed div 1000, " kb/s. - ", + event.bytesFinished, "/", event.bytesTotal, + " - ", time, " seconds") + echo(d.len) + of EvRetr: + echo("Download finished!") + ftp.close() + echo d.len + else: assert(false) + var ftp = asyncFTPClient("example.com", user = "foo", pass = "bar", handleEvent = hev) + + d.register(ftp) + d.len.echo() + ftp.connect() + echo "connected" + ftp.store("payload.jpg", "payload.jpg", async = true) + d.len.echo() + echo "uploading..." + while true: + if not d.poll(): break + main() when isMainModule and false: - var ftp = ftpClient("picheta.me", user = "asdasd", pass = "asfwq") + var ftp = ftpClient("example.com", user = "foo", pass = "bar") ftp.connect() echo ftp.pwd() echo ftp.list() echo("uploading") - ftp.store("payload.JPG", "payload.JPG", async = false) + ftp.store("payload.jpg", "payload.jpg", async = false) echo("Upload complete") - ftp.retrFile("payload.JPG", "payload2.JPG", async = false) + ftp.retrFile("payload.jpg", "payload2.jpg", async = false) echo("Download complete") sleep(5000) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 9bacc80d6..4db6ac6ed 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -654,8 +654,7 @@ when isMainModule: resp = await client.request("http://nimrod-lang.org/download.html") echo("Got response: ", resp.status) - asyncCheck main() - runForever() + waitFor main() else: #downloadFile("http://force7.de/nimrod/index.html", "nimrodindex.html") diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim deleted file mode 100644 index 49d9a9a34..000000000 --- a/lib/pure/irc.nim +++ /dev/null @@ -1,503 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements an asynchronous IRC client. -## -## Currently this module requires at least some knowledge of the IRC protocol. -## It provides a function for sending raw messages to the IRC server, together -## with some basic functions like sending a message to a channel. -## It automizes the process of keeping the connection alive, so you don't -## need to reply to PING messages. In fact, the server is also PING'ed to check -## the amount of lag. -## -## .. code-block:: Nimrod -## -## var client = irc("picheta.me", joinChans = @["#bots"]) -## client.connect() -## while True: -## var event: TIRCEvent -## if client.poll(event): -## case event.typ -## of EvConnected: nil -## of EvDisconnected: -## client.reconnect() -## of EvMsg: -## # Write your message reading code here. -## -## **Warning:** The API of this module is unstable, and therefore is subject -## to change. - -include "system/inclrtl" - -import sockets, strutils, parseutils, times, asyncio, os - -type - TIRC* = object of TObject - address: string - port: TPort - nick, user, realname, serverPass: string - case isAsync: bool - of true: - handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure, gcsafe.} - asyncSock: PAsyncSocket - myDispatcher: PDispatcher - of false: - dummyA: pointer - dummyB: pointer # workaround a Nimrod API issue - dummyC: pointer - sock: TSocket - status: TInfo - lastPing: float - lastPong: float - lag: float - channelsToJoin: seq[string] - msgLimit: bool - messageBuffer: seq[tuple[timeToSend: float, m: string]] - lastReconnect: float - - PIRC* = ref TIRC - - PAsyncIRC* = ref TAsyncIRC - TAsyncIRC* = object of TIRC - - TIRCMType* = enum - MUnknown, - MNumeric, - MPrivMsg, - MJoin, - MPart, - MMode, - MTopic, - MInvite, - MKick, - MQuit, - MNick, - MNotice, - MPing, - MPong, - MError - - TIRCEventType* = enum - EvMsg, EvConnected, EvDisconnected - TIRCEvent* = object ## IRC Event - case typ*: TIRCEventType - of EvConnected: - ## Connected to server. - ## Only occurs with AsyncIRC. - nil - of EvDisconnected: - ## Disconnected from the server - nil - of EvMsg: ## Message from the server - cmd*: TIRCMType ## Command (e.g. PRIVMSG) - nick*, user*, host*, servername*: string - numeric*: string ## Only applies to ``MNumeric`` - params*: seq[string] ## Parameters of the IRC message - origin*: string ## The channel/user that this msg originated from - raw*: string ## Raw IRC message - timestamp*: TTime ## UNIX epoch time the message was received - -proc send*(irc: PIRC, message: string, sendImmediately = false) = - ## Sends ``message`` as a raw command. It adds ``\c\L`` for you. - var sendMsg = true - if irc.msgLimit and not sendImmediately: - var timeToSend = epochTime() - if irc.messageBuffer.len() >= 3: - timeToSend = (irc.messageBuffer[irc.messageBuffer.len()-1][0] + 2.0) - - irc.messageBuffer.add((timeToSend, message)) - sendMsg = false - - if sendMsg: - try: - if irc.isAsync: - irc.asyncSock.send(message & "\c\L") - else: - irc.sock.send(message & "\c\L") - except EOS: - # Assuming disconnection of every EOS could be bad, - # but I can't exactly check for EBrokenPipe. - irc.status = SockClosed - -proc privmsg*(irc: PIRC, target, message: string) = - ## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user. - irc.send("PRIVMSG $1 :$2" % [target, message]) - -proc notice*(irc: PIRC, target, message: string) = - ## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user. - irc.send("NOTICE $1 :$2" % [target, message]) - -proc join*(irc: PIRC, channel: string, key = "") = - ## Joins ``channel``. - ## - ## If key is not ``""``, then channel is assumed to be key protected and this - ## function will join the channel using ``key``. - if key == "": - irc.send("JOIN " & channel) - else: - irc.send("JOIN " & channel & " " & key) - -proc part*(irc: PIRC, channel, message: string) = - ## Leaves ``channel`` with ``message``. - irc.send("PART " & channel & " :" & message) - -proc close*(irc: PIRC) = - ## Closes connection to an IRC server. - ## - ## **Warning:** This procedure does not send a ``QUIT`` message to the server. - irc.status = SockClosed - if irc.isAsync: - irc.asyncSock.close() - else: - irc.sock.close() - -proc isNumber(s: string): bool = - ## Checks if `s` contains only numbers. - var i = 0 - while s[i] in {'0'..'9'}: inc(i) - result = i == s.len and s.len > 0 - -proc parseMessage(msg: string): TIRCEvent = - result.typ = EvMsg - result.cmd = MUnknown - result.raw = msg - result.timestamp = times.getTime() - var i = 0 - # Process the prefix - if msg[i] == ':': - inc(i) # Skip `:` - var nick = "" - i.inc msg.parseUntil(nick, {'!', ' '}, i) - result.nick = "" - result.serverName = "" - if msg[i] == '!': - result.nick = nick - inc(i) # Skip `!` - i.inc msg.parseUntil(result.user, {'@'}, i) - inc(i) # Skip `@` - i.inc msg.parseUntil(result.host, {' '}, i) - inc(i) # Skip ` ` - else: - result.serverName = nick - inc(i) # Skip ` ` - - # Process command - var cmd = "" - i.inc msg.parseUntil(cmd, {' '}, i) - - if cmd.isNumber: - result.cmd = MNumeric - result.numeric = cmd - else: - case cmd - of "PRIVMSG": result.cmd = MPrivMsg - of "JOIN": result.cmd = MJoin - of "PART": result.cmd = MPart - of "PONG": result.cmd = MPong - of "PING": result.cmd = MPing - of "MODE": result.cmd = MMode - of "TOPIC": result.cmd = MTopic - of "INVITE": result.cmd = MInvite - of "KICK": result.cmd = MKick - of "QUIT": result.cmd = MQuit - of "NICK": result.cmd = MNick - of "NOTICE": result.cmd = MNotice - of "ERROR": result.cmd = MError - else: result.cmd = MUnknown - - # Don't skip space here. It is skipped in the following While loop. - - # Params - result.params = @[] - var param = "" - while msg[i] != '\0' and msg[i] != ':': - inc(i) # Skip ` `. - i.inc msg.parseUntil(param, {' ', ':', '\0'}, i) - if param != "": - result.params.add(param) - param.setlen(0) - - if msg[i] == ':': - inc(i) # Skip `:`. - result.params.add(msg[i..msg.len-1]) - -proc connect*(irc: PIRC) = - ## Connects to an IRC server as specified by ``irc``. - assert(irc.address != "") - assert(irc.port != TPort(0)) - - irc.sock.connect(irc.address, irc.port) - - irc.status = SockConnected - - # Greet the server :) - if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) - irc.send("NICK " & irc.nick, true) - irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - -proc reconnect*(irc: PIRC, timeout = 5000) = - ## Reconnects to an IRC server. - ## - ## ``Timeout`` specifies the time to wait in miliseconds between multiple - ## consecutive reconnections. - ## - ## This should be used when an ``EvDisconnected`` event occurs. - let secSinceReconnect = int(epochTime() - irc.lastReconnect) - if secSinceReconnect < timeout: - sleep(timeout - secSinceReconnect) - irc.sock = socket() - if irc.sock == InvalidSocket: osError(osLastError()) - irc.connect() - irc.lastReconnect = epochTime() - -proc irc*(address: string, port: TPort = 6667.TPort, - nick = "NimrodBot", - user = "NimrodBot", - realname = "NimrodBot", serverPass = "", - joinChans: seq[string] = @[], - msgLimit: bool = true): PIRC = - ## Creates a ``TIRC`` object. - new(result) - result.address = address - result.port = port - result.nick = nick - result.user = user - result.realname = realname - result.serverPass = serverPass - result.lastPing = epochTime() - result.lastPong = -1.0 - result.lag = -1.0 - result.channelsToJoin = joinChans - result.msgLimit = msgLimit - result.messageBuffer = @[] - result.status = SockIdle - result.sock = socket() - if result.sock == InvalidSocket: osError(osLastError()) - -proc processLine(irc: PIRC, line: string): TIRCEvent = - if line.len == 0: - irc.close() - result.typ = EvDisconnected - else: - result = parseMessage(line) - # Get the origin - result.origin = result.params[0] - if result.origin == irc.nick and - result.nick != "": result.origin = result.nick - - if result.cmd == MError: - irc.close() - result.typ = EvDisconnected - return - - if result.cmd == MPing: - irc.send("PONG " & result.params[0]) - if result.cmd == MPong: - irc.lag = epochTime() - parseFloat(result.params[result.params.high]) - irc.lastPong = epochTime() - if result.cmd == MNumeric: - if result.numeric == "001": - # Check the nickname. - if irc.nick != result.params[0]: - assert ' ' notin result.params[0] - irc.nick = result.params[0] - for chan in items(irc.channelsToJoin): - irc.join(chan) - if result.cmd == MNick: - if result.nick == irc.nick: - irc.nick = result.params[0] - -proc processOther(irc: PIRC, ev: var TIRCEvent): bool = - result = false - if epochTime() - irc.lastPing >= 20.0: - irc.lastPing = epochTime() - irc.send("PING :" & formatFloat(irc.lastPing), true) - - if epochTime() - irc.lastPong >= 120.0 and irc.lastPong != -1.0: - irc.close() - ev.typ = EvDisconnected # TODO: EvTimeout? - return true - - for i in 0..irc.messageBuffer.len-1: - if epochTime() >= irc.messageBuffer[0][0]: - irc.send(irc.messageBuffer[0].m, true) - irc.messageBuffer.delete(0) - else: - break # messageBuffer is guaranteed to be from the quickest to the - # later-est. - -proc poll*(irc: PIRC, ev: var TIRCEvent, - timeout: int = 500): bool = - ## This function parses a single message from the IRC server and returns - ## a TIRCEvent. - ## - ## This function should be called often as it also handles pinging - ## the server. - ## - ## This function provides a somewhat asynchronous IRC implementation, although - ## it should only be used for simple things for example an IRC bot which does - ## not need to be running many time critical tasks in the background. If you - ## require this, use the asyncio implementation. - - if not (irc.status == SockConnected): - # Do not close the socket here, it is already closed! - ev.typ = EvDisconnected - var line = TaintedString"" - var socks = @[irc.sock] - var ret = socks.select(timeout) - if ret == -1: osError(osLastError()) - if socks.len() != 0 and ret != 0: - irc.sock.readLine(line) - ev = irc.processLine(line.string) - result = true - - if processOther(irc, ev): result = true - -proc getLag*(irc: PIRC): float = - ## Returns the latency between this client and the IRC server in seconds. - ## - ## If latency is unknown, returns -1.0. - return irc.lag - -proc isConnected*(irc: PIRC): bool = - ## Returns whether this IRC client is connected to an IRC server. - return irc.status == SockConnected - -proc getNick*(irc: PIRC): string = - ## Returns the current nickname of the client. - return irc.nick - -# -- Asyncio dispatcher - -proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) = - # Greet the server :) - if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) - irc.send("NICK " & irc.nick, true) - irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - irc.status = SockConnected - - var ev: TIRCEvent - ev.typ = EvConnected - irc.handleEvent(irc, ev) - -proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) = - var line = "".TaintedString - var ret = s.readLine(line) - if ret: - if line == "": - var ev: TIRCEvent - irc.close() - ev.typ = EvDisconnected - irc.handleEvent(irc, ev) - else: - var ev = irc.processLine(line.string) - irc.handleEvent(irc, ev) - -proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) = - var ev: TIRCEvent - if irc.processOther(ev): - irc.handleEvent(irc, ev) - -proc register*(d: PDispatcher, irc: PAsyncIRC) = - ## Registers ``irc`` with dispatcher ``d``. - irc.asyncSock.handleConnect = - proc (s: PAsyncSocket) = - handleConnect(s, irc) - irc.asyncSock.handleRead = - proc (s: PAsyncSocket) = - handleRead(s, irc) - irc.asyncSock.handleTask = - proc (s: PAsyncSocket) = - handleTask(s, irc) - d.register(irc.asyncSock) - irc.myDispatcher = d - -proc connect*(irc: PAsyncIRC) = - ## Equivalent of connect for ``TIRC`` but specifically created for asyncio. - assert(irc.address != "") - assert(irc.port != TPort(0)) - - irc.asyncSock.connect(irc.address, irc.port) - -proc reconnect*(irc: PAsyncIRC, timeout = 5000) = - ## Reconnects to an IRC server. - ## - ## ``Timeout`` specifies the time to wait in miliseconds between multiple - ## consecutive reconnections. - ## - ## This should be used when an ``EvDisconnected`` event occurs. - ## - ## When successfully reconnected an ``EvConnected`` event will occur. - let secSinceReconnect = int(epochTime() - irc.lastReconnect) - if secSinceReconnect < timeout: - sleep(timeout - secSinceReconnect) - irc.asyncSock = AsyncSocket() - irc.myDispatcher.register(irc) - irc.connect() - irc.lastReconnect = epochTime() - -proc asyncIRC*(address: string, port: TPort = 6667.TPort, - nick = "NimrodBot", - user = "NimrodBot", - realname = "NimrodBot", serverPass = "", - joinChans: seq[string] = @[], - msgLimit: bool = true, - ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure,gcsafe.} - ): PAsyncIRC = - ## Use this function if you want to use asyncio's dispatcher. - ## - ## **Note:** Do **NOT** use this if you're writing a simple IRC bot which only - ## requires one task to be run, i.e. this should not be used if you want a - ## synchronous IRC client implementation, use ``irc`` for that. - - new(result) - result.isAsync = true - result.address = address - result.port = port - result.nick = nick - result.user = user - result.realname = realname - result.serverPass = serverPass - result.lastPing = epochTime() - result.lastPong = -1.0 - result.lag = -1.0 - result.channelsToJoin = joinChans - result.msgLimit = msgLimit - result.messageBuffer = @[] - result.handleEvent = ircEvent - result.asyncSock = AsyncSocket() - -when isMainModule: - #var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)") - #echo(repr(m)) - - - - var client = irc("amber.tenthbit.net", nick="TestBot1234", - joinChans = @["#flood"]) - client.connect() - while true: - var event: TIRCEvent - if client.poll(event): - case event.typ - of EvConnected: - discard - of EvDisconnected: - break - of EvMsg: - if event.cmd == MPrivMsg: - var msg = event.params[event.params.high] - if msg == "|test": client.privmsg(event.origin, "hello") - if msg == "|excessFlood": - for i in 0..10: - client.privmsg(event.origin, "TEST" & $i) - - #echo( repr(event) ) - #echo("Lag: ", formatFloat(client.getLag())) - - diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 116671874..97c7b0e05 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -208,7 +208,7 @@ when not defined(JS): proc randomize(seed: int) = srand(cint(seed)) - when defined(srand48): srand48(seed) + when declared(srand48): srand48(seed) proc random(max: int): int = result = int(rand()) mod max @@ -268,8 +268,13 @@ proc `mod`*(x, y: float): float = result = if y == 0.0: x else: x - y * (x/y).floor proc random*[T](x: TSlice[T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b-1`. result = random(x.b - x.a) + x.a - + +proc random[T](a: openarray[T]): T = + ## returns a random element from the openarray `a`. + result = a[random(a.low..a.len)] + type TRunningStat* {.pure,final.} = object ## an accumulator for statistical data n*: int ## number of pushed data diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 31fefc6c8..ffeb0beff 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -54,7 +54,7 @@ proc mapMem*(m: var TMemFile, mode: TFileMode = fmRead, nil, mappedSize, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: MAP_PRIVATE else: MAP_SHARED, + if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), m.handle, offset) if result == cast[pointer](MAP_FAILED): osError(osLastError()) @@ -207,7 +207,7 @@ proc open*(filename: string, mode: TFileMode = fmRead, nil, result.size, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: MAP_PRIVATE else: MAP_SHARED, + if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), result.handle, offset) diff --git a/lib/pure/net.nim b/lib/pure/net.nim index ddc2bbe2d..696527467 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -11,292 +11,10 @@ {.deadCodeElim: on.} import rawsockets, os, strutils, unsigned, parseutils, times -export TPort, `$` +export TPort, `$`, `==` const useWinVersion = defined(Windows) or defined(nimdoc) -type - IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address - IPv6, ## IPv6 address - IPv4 ## IPv4 address - - TIpAddress* = object ## stores an arbitrary IP address - case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) - of IpAddressFamily.IPv6: - address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in - ## case of IPv6 - of IpAddressFamily.IPv4: - address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in - ## case of IPv4 - -proc IPv4_any*(): TIpAddress = - ## Returns the IPv4 any address, which can be used to listen on all available - ## network adapters - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [0'u8, 0, 0, 0]) - -proc IPv4_loopback*(): TIpAddress = - ## Returns the IPv4 loopback address (127.0.0.1) - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [127'u8, 0, 0, 1]) - -proc IPv4_broadcast*(): TIpAddress = - ## Returns the IPv4 broadcast address (255.255.255.255) - result = TIpAddress( - family: IpAddressFamily.IPv4, - address_v4: [255'u8, 255, 255, 255]) - -proc IPv6_any*(): TIpAddress = - ## Returns the IPv6 any address (::0), which can be used - ## to listen on all available network adapters - result = TIpAddress( - family: IpAddressFamily.IPv6, - address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - -proc IPv6_loopback*(): TIpAddress = - ## Returns the IPv6 loopback address (::1) - result = TIpAddress( - family: IpAddressFamily.IPv6, - address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) - -proc `==`*(lhs, rhs: TIpAddress): bool = - ## Compares two IpAddresses for Equality. Returns two if the addresses are equal - if lhs.family != rhs.family: return false - if lhs.family == IpAddressFamily.IPv4: - for i in low(lhs.address_v4) .. high(lhs.address_v4): - if lhs.address_v4[i] != rhs.address_v4[i]: return false - else: # IPv6 - for i in low(lhs.address_v6) .. high(lhs.address_v6): - if lhs.address_v6[i] != rhs.address_v6[i]: return false - return true - -proc `$`*(address: TIpAddress): string = - ## Converts an TIpAddress into the textual representation - result = "" - case address.family - of IpAddressFamily.IPv4: - for i in 0 .. 3: - if i != 0: - result.add('.') - result.add($address.address_v4[i]) - of IpAddressFamily.IPv6: - var - currentZeroStart = -1 - currentZeroCount = 0 - biggestZeroStart = -1 - biggestZeroCount = 0 - # Look for the largest block of zeros - for i in 0..7: - var isZero = address.address_v6[i*2] == 0 and address.address_v6[i*2+1] == 0 - if isZero: - if currentZeroStart == -1: - currentZeroStart = i - currentZeroCount = 1 - else: - currentZeroCount.inc() - if currentZeroCount > biggestZeroCount: - biggestZeroCount = currentZeroCount - biggestZeroStart = currentZeroStart - else: - currentZeroStart = -1 - - if biggestZeroCount == 8: # Special case ::0 - result.add("::") - else: # Print address - var printedLastGroup = false - for i in 0..7: - var word:uint16 = (cast[uint16](address.address_v6[i*2])) shl 8 - word = word or cast[uint16](address.address_v6[i*2+1]) - - if biggestZeroCount != 0 and # Check if group is in skip group - (i >= biggestZeroStart and i < (biggestZeroStart + biggestZeroCount)): - if i == biggestZeroStart: # skip start - result.add("::") - printedLastGroup = false - else: - if printedLastGroup: - result.add(':') - var - afterLeadingZeros = false - mask = 0xF000'u16 - for j in 0'u16..3'u16: - var val = (mask and word) shr (4'u16*(3'u16-j)) - if val != 0 or afterLeadingZeros: - if val < 0xA: - result.add(chr(uint16(ord('0'))+val)) - else: # val >= 0xA - result.add(chr(uint16(ord('a'))+val-0xA)) - afterLeadingZeros = true - mask = mask shr 4 - printedLastGroup = true - -proc parseIPv4Address(address_str: string): TIpAddress = - ## Parses IPv4 adresses - ## Raises EInvalidValue on errors - var - byteCount = 0 - currentByte:uint16 = 0 - seperatorValid = false - - result.family = IpAddressFamily.IPv4 - - for i in 0 .. high(address_str): - if address_str[i] in strutils.Digits: # Character is a number - currentByte = currentByte * 10 + - cast[uint16](ord(address_str[i]) - ord('0')) - if currentByte > 255'u16: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - seperatorValid = true - elif address_str[i] == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v4[byteCount] = cast[uint8](currentByte) - currentByte = 0 - byteCount.inc - seperatorValid = false - else: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - if byteCount != 3 or not seperatorValid: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v4[byteCount] = cast[uint8](currentByte) - -proc parseIPv6Address(address_str: string): TIpAddress = - ## Parses IPv6 adresses - ## Raises EInvalidValue on errors - result.family = IpAddressFamily.IPv6 - if address_str.len < 2: - raise newException(EInvalidValue, "Invalid IP Address") - - var - groupCount = 0 - currentGroupStart = 0 - currentShort:uint32 = 0 - seperatorValid = true - dualColonGroup = -1 - lastWasColon = false - v4StartPos = -1 - byteCount = 0 - - for i,c in address_str: - if c == ':': - if not seperatorValid: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid seperator") - if lastWasColon: - if dualColonGroup != -1: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains more than one \"::\" seperator") - dualColonGroup = groupCount - seperatorValid = false - elif i != 0 and i != high(address_str): - if groupCount >= 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) - result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) - currentShort = 0 - groupCount.inc() - if dualColonGroup != -1: seperatorValid = false - elif i == 0: # only valid if address starts with :: - if address_str[1] != ':': - raise newException(EInvalidValue, - "Invalid IP Address. Address may not start with \":\"") - else: # i == high(address_str) - only valid if address ends with :: - if address_str[high(address_str)-1] != ':': - raise newException(EInvalidValue, - "Invalid IP Address. Address may not end with \":\"") - lastWasColon = true - currentGroupStart = i + 1 - elif c == '.': # Switch to parse IPv4 mode - if i < 3 or not seperatorValid or groupCount >= 7: - raise newException(EInvalidValue, "Invalid IP Address") - v4StartPos = currentGroupStart - currentShort = 0 - seperatorValid = false - break - elif c in strutils.HexDigits: - if c in strutils.Digits: # Normal digit - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('0')) - elif c >= 'a' and c <= 'f': # Lower case hex - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('a')) + 10 - else: # Upper case hex - currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('A')) + 10 - if currentShort > 65535'u32: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - lastWasColon = false - seperatorValid = true - else: - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - - if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff - if seperatorValid: # Copy remaining data - if groupCount >= 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) - result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) - groupCount.inc() - else: # Must parse IPv4 address - for i,c in address_str[v4StartPos..high(address_str)]: - if c in strutils.Digits: # Character is a number - currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) - if currentShort > 255'u32: - raise newException(EInvalidValue, - "Invalid IP Address. Value is out of range") - seperatorValid = true - elif c == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) - currentShort = 0 - byteCount.inc() - seperatorValid = false - else: # Invalid character - raise newException(EInvalidValue, - "Invalid IP Address. Address contains an invalid character") - - if byteCount != 3 or not seperatorValid: - raise newException(EInvalidValue, "Invalid IP Address") - result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) - groupCount += 2 - - # Shift and fill zeros in case of :: - if groupCount > 8: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - elif groupCount < 8: # must fill - if dualColonGroup == -1: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too few groups") - var toFill = 8 - groupCount # The number of groups to fill - var toShift = groupCount - dualColonGroup # Nr of known groups after :: - for i in 0..2*toShift-1: # shift - result.address_v6[15-i] = result.address_v6[groupCount*2-i-1] - for i in 0..2*toFill-1: # fill with 0s - result.address_v6[dualColonGroup*2+i] = 0 - elif dualColonGroup != -1: - raise newException(EInvalidValue, - "Invalid IP Address. The address consists of too many groups") - -proc parseIpAddress*(address_str: string): TIpAddress = - ## Parses an IP address - ## Raises EInvalidValue on error - if address_str == nil: - raise newException(EInvalidValue, "IP Address string is nil") - if address_str.contains(':'): - return parseIPv6Address(address_str) - else: - return parseIPv4Address(address_str) - when defined(ssl): import openssl @@ -361,7 +79,7 @@ proc isDisconnectionError*(flags: set[TSocketFlags], when useWinVersion: TSocketFlags.SafeDisconn in flags and lastError.int32 in {WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, - WSAEDISCON} + WSAEDISCON, ERROR_NETNAME_DELETED} else: TSocketFlags.SafeDisconn in flags and lastError.int32 in {ECONNRESET, EPIPE, ENETRESET} @@ -569,8 +287,8 @@ proc bindAddr*(socket: PSocket, port = TPort(0), address = "") {. osError(osLastError()) dealloc(aiList) -proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. - tags: [FReadIO].} = +proc acceptAddr*(server: PSocket, client: var PSocket, address: var string, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FReadIO].} = ## Blocks until a connection is being made from a client. When a connection ## is made sets ``client`` to the client socket and ``address`` to the address ## of the connecting client. @@ -581,6 +299,11 @@ proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. assert(client != nil) var sockAddress: Tsockaddr_in var addrLen = sizeof(sockAddress).TSocklen @@ -589,6 +312,8 @@ proc acceptAddr*(server: PSocket, client: var PSocket, address: var string) {. if sock == osInvalidSocket: let err = osLastError() + if flags.isDisconnectionError(err): + acceptAddr(server, client, address, flags) osError(err) else: client.fd = sock @@ -658,15 +383,20 @@ when false: #defined(ssl): acceptAddrPlain(AcceptNoClient, AcceptSuccess): doHandshake() -proc accept*(server: PSocket, client: var PSocket) {.tags: [FReadIO].} = +proc accept*(server: PSocket, client: var PSocket, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FReadIO].} = ## Equivalent to ``acceptAddr`` but doesn't return the address, only the ## socket. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. - + ## + ## The ``accept`` call may result in an error if the connecting socket + ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` + ## flag is specified then this error will not be raised and instead + ## accept will be called again. var addrDummy = "" - acceptAddr(server, client, addrDummy) + acceptAddr(server, client, addrDummy, flags) proc close*(socket: PSocket) = ## Closes a socket. @@ -1173,3 +903,285 @@ proc isSSL*(socket: PSocket): bool = return socket.isSSL proc getFD*(socket: PSocket): TSocketHandle = return socket.fd ## Returns the socket's file descriptor + +type + IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address + IPv6, ## IPv6 address + IPv4 ## IPv4 address + + TIpAddress* = object ## stores an arbitrary IP address + case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) + of IpAddressFamily.IPv6: + address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in + ## case of IPv6 + of IpAddressFamily.IPv4: + address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in + ## case of IPv4 + +proc IPv4_any*(): TIpAddress = + ## Returns the IPv4 any address, which can be used to listen on all available + ## network adapters + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [0'u8, 0, 0, 0]) + +proc IPv4_loopback*(): TIpAddress = + ## Returns the IPv4 loopback address (127.0.0.1) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [127'u8, 0, 0, 1]) + +proc IPv4_broadcast*(): TIpAddress = + ## Returns the IPv4 broadcast address (255.255.255.255) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [255'u8, 255, 255, 255]) + +proc IPv6_any*(): TIpAddress = + ## Returns the IPv6 any address (::0), which can be used + ## to listen on all available network adapters + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + +proc IPv6_loopback*(): TIpAddress = + ## Returns the IPv6 loopback address (::1) + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + +proc `==`*(lhs, rhs: TIpAddress): bool = + ## Compares two IpAddresses for Equality. Returns two if the addresses are equal + if lhs.family != rhs.family: return false + if lhs.family == IpAddressFamily.IPv4: + for i in low(lhs.address_v4) .. high(lhs.address_v4): + if lhs.address_v4[i] != rhs.address_v4[i]: return false + else: # IPv6 + for i in low(lhs.address_v6) .. high(lhs.address_v6): + if lhs.address_v6[i] != rhs.address_v6[i]: return false + return true + +proc `$`*(address: TIpAddress): string = + ## Converts an TIpAddress into the textual representation + result = "" + case address.family + of IpAddressFamily.IPv4: + for i in 0 .. 3: + if i != 0: + result.add('.') + result.add($address.address_v4[i]) + of IpAddressFamily.IPv6: + var + currentZeroStart = -1 + currentZeroCount = 0 + biggestZeroStart = -1 + biggestZeroCount = 0 + # Look for the largest block of zeros + for i in 0..7: + var isZero = address.address_v6[i*2] == 0 and address.address_v6[i*2+1] == 0 + if isZero: + if currentZeroStart == -1: + currentZeroStart = i + currentZeroCount = 1 + else: + currentZeroCount.inc() + if currentZeroCount > biggestZeroCount: + biggestZeroCount = currentZeroCount + biggestZeroStart = currentZeroStart + else: + currentZeroStart = -1 + + if biggestZeroCount == 8: # Special case ::0 + result.add("::") + else: # Print address + var printedLastGroup = false + for i in 0..7: + var word:uint16 = (cast[uint16](address.address_v6[i*2])) shl 8 + word = word or cast[uint16](address.address_v6[i*2+1]) + + if biggestZeroCount != 0 and # Check if group is in skip group + (i >= biggestZeroStart and i < (biggestZeroStart + biggestZeroCount)): + if i == biggestZeroStart: # skip start + result.add("::") + printedLastGroup = false + else: + if printedLastGroup: + result.add(':') + var + afterLeadingZeros = false + mask = 0xF000'u16 + for j in 0'u16..3'u16: + var val = (mask and word) shr (4'u16*(3'u16-j)) + if val != 0 or afterLeadingZeros: + if val < 0xA: + result.add(chr(uint16(ord('0'))+val)) + else: # val >= 0xA + result.add(chr(uint16(ord('a'))+val-0xA)) + afterLeadingZeros = true + mask = mask shr 4 + printedLastGroup = true + +proc parseIPv4Address(address_str: string): TIpAddress = + ## Parses IPv4 adresses + ## Raises EInvalidValue on errors + var + byteCount = 0 + currentByte:uint16 = 0 + seperatorValid = false + + result.family = IpAddressFamily.IPv4 + + for i in 0 .. high(address_str): + if address_str[i] in strutils.Digits: # Character is a number + currentByte = currentByte * 10 + + cast[uint16](ord(address_str[i]) - ord('0')) + if currentByte > 255'u16: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + seperatorValid = true + elif address_str[i] == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v4[byteCount] = cast[uint8](currentByte) + currentByte = 0 + byteCount.inc + seperatorValid = false + else: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v4[byteCount] = cast[uint8](currentByte) + +proc parseIPv6Address(address_str: string): TIpAddress = + ## Parses IPv6 adresses + ## Raises EInvalidValue on errors + result.family = IpAddressFamily.IPv6 + if address_str.len < 2: + raise newException(EInvalidValue, "Invalid IP Address") + + var + groupCount = 0 + currentGroupStart = 0 + currentShort:uint32 = 0 + seperatorValid = true + dualColonGroup = -1 + lastWasColon = false + v4StartPos = -1 + byteCount = 0 + + for i,c in address_str: + if c == ':': + if not seperatorValid: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid seperator") + if lastWasColon: + if dualColonGroup != -1: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains more than one \"::\" seperator") + dualColonGroup = groupCount + seperatorValid = false + elif i != 0 and i != high(address_str): + if groupCount >= 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + currentShort = 0 + groupCount.inc() + if dualColonGroup != -1: seperatorValid = false + elif i == 0: # only valid if address starts with :: + if address_str[1] != ':': + raise newException(EInvalidValue, + "Invalid IP Address. Address may not start with \":\"") + else: # i == high(address_str) - only valid if address ends with :: + if address_str[high(address_str)-1] != ':': + raise newException(EInvalidValue, + "Invalid IP Address. Address may not end with \":\"") + lastWasColon = true + currentGroupStart = i + 1 + elif c == '.': # Switch to parse IPv4 mode + if i < 3 or not seperatorValid or groupCount >= 7: + raise newException(EInvalidValue, "Invalid IP Address") + v4StartPos = currentGroupStart + currentShort = 0 + seperatorValid = false + break + elif c in strutils.HexDigits: + if c in strutils.Digits: # Normal digit + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('0')) + elif c >= 'a' and c <= 'f': # Lower case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('a')) + 10 + else: # Upper case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('A')) + 10 + if currentShort > 65535'u32: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + lastWasColon = false + seperatorValid = true + else: + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + + if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff + if seperatorValid: # Copy remaining data + if groupCount >= 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + groupCount.inc() + else: # Must parse IPv4 address + for i,c in address_str[v4StartPos..high(address_str)]: + if c in strutils.Digits: # Character is a number + currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) + if currentShort > 255'u32: + raise newException(EInvalidValue, + "Invalid IP Address. Value is out of range") + seperatorValid = true + elif c == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + currentShort = 0 + byteCount.inc() + seperatorValid = false + else: # Invalid character + raise newException(EInvalidValue, + "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + groupCount += 2 + + # Shift and fill zeros in case of :: + if groupCount > 8: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + elif groupCount < 8: # must fill + if dualColonGroup == -1: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too few groups") + var toFill = 8 - groupCount # The number of groups to fill + var toShift = groupCount - dualColonGroup # Nr of known groups after :: + for i in 0..2*toShift-1: # shift + result.address_v6[15-i] = result.address_v6[groupCount*2-i-1] + for i in 0..2*toFill-1: # fill with 0s + result.address_v6[dualColonGroup*2+i] = 0 + elif dualColonGroup != -1: + raise newException(EInvalidValue, + "Invalid IP Address. The address consists of too many groups") + +proc parseIpAddress*(address_str: string): TIpAddress = + ## Parses an IP address + ## Raises EInvalidValue on error + if address_str == nil: + raise newException(EInvalidValue, "IP Address string is nil") + if address_str.contains(':'): + return parseIPv6Address(address_str) + else: + return parseIPv4Address(address_str) diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index ab7cd1944..6f94d0656 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -26,7 +26,7 @@ const withThreads = compileOption("threads") tickCountCorrection = 50_000 -when not defined(system.TStackTrace): +when not declared(system.TStackTrace): type TStackTrace = array [0..20, cstring] # We use a simple hash table of bounded size to keep track of the stack traces: @@ -146,7 +146,7 @@ proc `//`(a, b: int): string = result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2)) proc writeProfile() {.noconv.} = - when defined(system.TStackTrace): + when declared(system.TStackTrace): system.profilerHook = nil const filename = "profile_results.txt" echo "writing " & filename & "..." @@ -189,16 +189,16 @@ var disabled: int proc disableProfiling*() = - when defined(system.TStackTrace): + when declared(system.TStackTrace): atomicDec disabled system.profilerHook = nil proc enableProfiling*() = - when defined(system.TStackTrace): + when declared(system.TStackTrace): if atomicInc(disabled) >= 0: system.profilerHook = hook -when defined(system.TStackTrace): +when declared(system.TStackTrace): system.profilerHook = hook addQuitProc(writeProfile) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a70bfa7f1..71089494f 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -365,8 +365,9 @@ when defined(windows): template getFilename(f: expr): expr = $f.cFilename proc skipFindData(f: TWIN32_FIND_DATA): bool {.inline.} = + # Note - takes advantage of null delimiter in the cstring const dot = ord('.') - result = f.cFileName[0].int == dot and(f.cFileName[1].int == 0 or + result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or f.cFileName[1].int == dot and f.cFileName[2].int == 0) proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", @@ -997,7 +998,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", if c_rename(source, dest) != 0'i32: raise newException(EOS, $strerror(errno)) -when not defined(ENOENT) and not defined(Windows): +when not declared(ENOENT) and not defined(Windows): when NoFakeVars: const ENOENT = cint(2) # 2 on most systems including Solaris else: @@ -1331,10 +1332,10 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ proc rawCreateDir(dir: string) = when defined(solaris): - if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST and errno != ENOSYS: + if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST and errno != ENOSYS: osError(osLastError()) elif defined(unix): - if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST: + if mkdir(dir, 0o777) != 0'i32 and errno != EEXIST: osError(osLastError()) else: when useWinUnicode: @@ -1457,7 +1458,8 @@ proc parseCmdLine*(c: string): seq[string] {. var a = "" while true: setLen(a, 0) - while c[i] == ' ' or c[i] == '\t': inc(i) + # eat all delimiting whitespace + while c[i] == ' ' or c[i] == '\t' or c [i] == '\l' or c [i] == '\r' : inc(i) when defined(windows): # parse a single argument according to the above rules: if c[i] == '\0': break @@ -1614,11 +1616,11 @@ when defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(paramCount): + ## when declared(paramCount): ## # Use paramCount() here ## else: ## # Do something else! @@ -1637,11 +1639,11 @@ when defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(paramStr): + ## when declared(paramStr): ## # Use paramStr() here ## else: ## # Do something else! @@ -1681,7 +1683,7 @@ elif not defined(createNimRtl): # Docstring in nimdoc block. result = cmdCount-1 -when defined(paramCount) or defined(nimdoc): +when declared(paramCount) or defined(nimdoc): proc commandLineParams*(): seq[TaintedString] = ## Convenience proc which returns the command line parameters. ## @@ -1690,11 +1692,11 @@ when defined(paramCount) or defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `defined() <system.html#defined>`_. + ## can test for its availability with `declared() <system.html#declared>`_. ## Example: ## ## .. code-block:: nimrod - ## when defined(commandLineParams): + ## when declared(commandLineParams): ## # Use commandLineParams() here ## else: ## # Do something else! @@ -1713,7 +1715,7 @@ when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): when not (defined(windows) or defined(macosx)): proc getApplHeuristic(): string = - when defined(paramStr): + when declared(paramStr): result = string(paramStr(0)) # POSIX guaranties that this contains the executable # as it has been executed by the calling process @@ -1860,12 +1862,12 @@ proc expandTilde*(path: string): string = when defined(Windows): type - DeviceId = int32 - FileId = int64 + DeviceId* = int32 + FileId* = int64 else: type - DeviceId = TDev - FileId = TIno + DeviceId* = TDev + FileId* = TIno type FileInfo* = object @@ -1908,6 +1910,7 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr = if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: formalInfo.kind = succ(result.kind) + else: template checkAndIncludeMode(rawMode, formalMode: expr) = if (rawInfo.st_mode and rawMode) != 0'i32: @@ -1994,4 +1997,26 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = osError(osLastError()) rawToFormalFileInfo(rawInfo, result) +proc isHidden*(path: string): bool = + ## Determines whether a given path is hidden or not. Returns false if the + ## file doesn't exist. The given path must be accessible from the current + ## working directory of the program. + ## + ## On Windows, a file is hidden if the file's 'hidden' attribute is set. + ## On Unix-like systems, a file is hidden if it starts with a '.' (period) + ## and is not *just* '.' or '..' ' ." + when defined(Windows): + wrapUnary(attributes, getFileAttributesW, path) + if attributes != -1'i32: + result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32 + else: + if fileExists(path): + let + fileName = extractFilename(path) + nameLen = len(fileName) + if nameLen == 2: + result = (fileName[0] == '.') and (fileName[1] != '.') + elif nameLen > 2: + result = (fileName[0] == '.') and (fileName[3] != '.') + {.pop.} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index c74fa1ceb..3c181bf53 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -643,7 +643,7 @@ elif not defined(useNimRtl): data.workingDir = workingDir - when defined(posix_spawn) and not defined(useFork) and + when declared(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux): pid = startProcessAuxSpawn(data) else: diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 68ae537c7..f43853fe6 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -37,7 +37,7 @@ type ## or the argument, ``value`` is not "" if ## the option was given a value -when defined(os.paramCount): +when declared(os.paramCount): # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! @@ -127,7 +127,7 @@ proc cmdLineRest*(p: TOptParser): TaintedString {. ## retrieves the rest of the command line that has not been parsed yet. result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString -when defined(initOptParser): +when declared(initOptParser): iterator getopt*(): tuple[kind: TCmdLineKind, key, val: TaintedString] = ## This is an convenience iterator for iterating over the command line. diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 5e79d8a18..7638171d1 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -119,7 +119,7 @@ proc cmdLineRest*(p: TOptParser): TaintedString {.rtl, extern: "npo$1", deprecat type TGetoptResult* = tuple[kind: TCmdLineKind, key, val: TaintedString] -when defined(paramCount): +when declared(paramCount): iterator getopt*(): TGetoptResult = ## This is an convenience iterator for iterating over the command line. ## This uses the TOptParser object. Example: diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 68b1ab223..efe169c1d 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -870,7 +870,7 @@ template `=~`*(s: string, pattern: TPeg): bool = ## echo("syntax error") ## bind maxSubpatterns - when not definedInScope(matches): + when not declaredInScope(matches): var matches {.inject.}: array[0..MaxSubpatterns-1, string] match(s, pattern, matches) diff --git a/lib/pure/rawsockets.nim b/lib/pure/rawsockets.nim index d96741846..fea09dfa2 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/rawsockets.nim @@ -22,7 +22,7 @@ const useWinVersion = defined(Windows) or defined(nimdoc) when useWinVersion: import winlean export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, - WSAEDISCON + WSAEDISCON, ERROR_NETNAME_DELETED else: import posix export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL, diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim index 4a6c3f530..eb3792bce 100644 --- a/lib/pure/ropes.nim +++ b/lib/pure/ropes.nim @@ -58,8 +58,8 @@ proc newRope(data: string): PRope = result.data = data var - cache: PRope # the root of the cache tree - N: PRope # dummy rope needed for splay algorithm + cache {.threadvar.}: PRope # the root of the cache tree + N {.threadvar.}: PRope # dummy rope needed for splay algorithm when countCacheMisses: var misses, hits: int diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 6f8924d83..1d17de233 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -803,6 +803,36 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = if result != -1: return return -1 +proc count*(s: string, sub: string, overlapping: bool = false): int {.noSideEffect, + rtl, extern: "nsuCountString".} = + ## Count the occurences of a substring `sub` in the string `s`. + ## Overlapping occurences of `sub` only count when `overlapping` + ## is set to true. + var i = 0 + while true: + i = s.find(sub, i) + if i < 0: + break + if overlapping: + inc i + else: + i += sub.len + inc result + +proc count*(s: string, sub: char): int {.noSideEffect, + rtl, extern: "nsuCountChar".} = + ## Count the occurences of the character `sub` in the string `s`. + for c in s: + if c == sub: + inc result + +proc count*(s: string, subs: set[char]): int {.noSideEffect, + rtl, extern: "nsuCountCharSet".} = + ## Count the occurences of the group of character `subs` in the string `s`. + for c in s: + if c in subs: + inc result + proc quoteIfContainsWhite*(s: string): string {.deprecated.} = ## Returns ``'"' & s & '"'`` if `s` contains a space and does not ## start with a quote, else returns `s`. @@ -1354,3 +1384,8 @@ when isMainModule: doAssert parseEnum[TMyEnum]("enu_D") == enuD doAssert parseEnum("invalid enum value", enC) == enC + + doAssert count("foofoofoo", "foofoo") == 1 + doAssert count("foofoofoo", "foofoo", overlapping = true) == 2 + doAssert count("foofoofoo", 'f') == 3 + doAssert count("foofoofoobar", {'f','b'}) == 4 diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 92797744a..ed87610d6 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -84,7 +84,8 @@ proc getFormatArg(p: var TFormatParser, a: openArray[string]): int = if result >=% a.len: raiseInvalidFormat("index out of bounds: " & $result) p.i = i -proc scanDollar(p: var TFormatParser, a: openarray[string], s: var string) +proc scanDollar(p: var TFormatParser, a: openarray[string], s: var string) {. + noSideEffect.} proc emitChar(p: var TFormatParser, x: var string, ch: char) {.inline.} = x.add(ch) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 498511899..8b33d2c73 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -63,44 +63,44 @@ elif defined(windows): elif defined(JS): type TTime* {.final, importc.} = object - getDay: proc (): int {.tags: [], raises: [].} - getFullYear: proc (): int {.tags: [], raises: [].} - getHours: proc (): int {.tags: [], raises: [].} - getMilliseconds: proc (): int {.tags: [], raises: [].} - getMinutes: proc (): int {.tags: [], raises: [].} - getMonth: proc (): int {.tags: [], raises: [].} - getSeconds: proc (): int {.tags: [], raises: [].} - getTime: proc (): int {.tags: [], raises: [].} - getTimezoneOffset: proc (): int {.tags: [], raises: [].} - getDate: proc (): int {.tags: [], raises: [].} - getUTCDate: proc (): int {.tags: [], raises: [].} - getUTCFullYear: proc (): int {.tags: [], raises: [].} - getUTCHours: proc (): int {.tags: [], raises: [].} - getUTCMilliseconds: proc (): int {.tags: [], raises: [].} - getUTCMinutes: proc (): int {.tags: [], raises: [].} - getUTCMonth: proc (): int {.tags: [], raises: [].} - getUTCSeconds: proc (): int {.tags: [], raises: [].} - getUTCDay: proc (): int {.tags: [], raises: [].} - getYear: proc (): int {.tags: [], raises: [].} - parse: proc (s: cstring): TTime {.tags: [], raises: [].} - setDate: proc (x: int) {.tags: [], raises: [].} - setFullYear: proc (x: int) {.tags: [], raises: [].} - setHours: proc (x: int) {.tags: [], raises: [].} - setMilliseconds: proc (x: int) {.tags: [], raises: [].} - setMinutes: proc (x: int) {.tags: [], raises: [].} - setMonth: proc (x: int) {.tags: [], raises: [].} - setSeconds: proc (x: int) {.tags: [], raises: [].} - setTime: proc (x: int) {.tags: [], raises: [].} - setUTCDate: proc (x: int) {.tags: [], raises: [].} - setUTCFullYear: proc (x: int) {.tags: [], raises: [].} - setUTCHours: proc (x: int) {.tags: [], raises: [].} - setUTCMilliseconds: proc (x: int) {.tags: [], raises: [].} - setUTCMinutes: proc (x: int) {.tags: [], raises: [].} - setUTCMonth: proc (x: int) {.tags: [], raises: [].} - setUTCSeconds: proc (x: int) {.tags: [], raises: [].} - setYear: proc (x: int) {.tags: [], raises: [].} - toGMTString: proc (): cstring {.tags: [], raises: [].} - toLocaleString: proc (): cstring {.tags: [], raises: [].} + getDay: proc (): int {.tags: [], raises: [], gcsafe.} + getFullYear: proc (): int {.tags: [], raises: [], gcsafe.} + getHours: proc (): int {.tags: [], raises: [], gcsafe.} + getMilliseconds: proc (): int {.tags: [], raises: [], gcsafe.} + getMinutes: proc (): int {.tags: [], raises: [], gcsafe.} + getMonth: proc (): int {.tags: [], raises: [], gcsafe.} + getSeconds: proc (): int {.tags: [], raises: [], gcsafe.} + getTime: proc (): int {.tags: [], raises: [], gcsafe.} + getTimezoneOffset: proc (): int {.tags: [], raises: [], gcsafe.} + getDate: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCDate: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCFullYear: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCHours: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMilliseconds: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMinutes: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCMonth: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCSeconds: proc (): int {.tags: [], raises: [], gcsafe.} + getUTCDay: proc (): int {.tags: [], raises: [], gcsafe.} + getYear: proc (): int {.tags: [], raises: [], gcsafe.} + parse: proc (s: cstring): TTime {.tags: [], raises: [], gcsafe.} + setDate: proc (x: int) {.tags: [], raises: [], gcsafe.} + setFullYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + setHours: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMilliseconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMinutes: proc (x: int) {.tags: [], raises: [], gcsafe.} + setMonth: proc (x: int) {.tags: [], raises: [], gcsafe.} + setSeconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setTime: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCDate: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCFullYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCHours: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMilliseconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMinutes: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCMonth: proc (x: int) {.tags: [], raises: [], gcsafe.} + setUTCSeconds: proc (x: int) {.tags: [], raises: [], gcsafe.} + setYear: proc (x: int) {.tags: [], raises: [], gcsafe.} + toGMTString: proc (): cstring {.tags: [], raises: [], gcsafe.} + toLocaleString: proc (): cstring {.tags: [], raises: [], gcsafe.} type TTimeInfo* = object of TObject ## represents a time in different parts @@ -513,7 +513,7 @@ elif defined(JS): result.setFullYear(timeInfo.year) result.setDate(timeInfo.monthday) - proc `$`(timeInfo: TTimeInfo): string = return $(TimeInfoToTIme(timeInfo)) + proc `$`(timeInfo: TTimeInfo): string = return $(timeInfoToTime(timeInfo)) proc `$`(time: TTime): string = return $time.toLocaleString() proc `-` (a, b: TTime): int64 = diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index e7bd363cf..3203ee699 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -11,7 +11,26 @@ ## working with types proc name*(t: typedesc): string {.magic: "TypeTrait".} - ## Returns the name of the given type + ## Returns the name of the given type. + ## + ## Example: + ## + ## .. code-block:: + ## + ## import typetraits + ## + ## proc `$`*[T](some:typedesc[T]): string = name(T) + ## + ## template test(x): stmt = + ## echo "type: ", type(x), ", value: ", x + ## + ## test 42 + ## # --> type: int, value: 42 + ## test "Foo" + ## # --> type: string, value: Foo + ## test(@['A','B']) + ## # --> type: seq[char], value: @[A, B] + proc arity*(t: typedesc): int {.magic: "TypeTrait".} - ## Returns the arity of the given type \ No newline at end of file + ## Returns the arity of the given type diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index f5640a1b4..7cc95f0ad 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -19,7 +19,7 @@ import macros -when defined(stdout): +when declared(stdout): import os when not defined(ECMAScript): @@ -99,7 +99,7 @@ template fail* = when not defined(ECMAScript): if AbortOnError: quit(1) - when defined(TestStatusIMPL): + when declared(TestStatusIMPL): TestStatusIMPL = FAILED else: program_result += 1 @@ -188,7 +188,7 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = result = getAst(expectBody(errorTypes, exp.lineinfo, body)) -when defined(stdout): +when declared(stdout): ## Reading settings var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index 95b48a850..1af7db7d5 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -151,6 +151,8 @@ proc addEscaped*(result: var string, s: string) = of '>': result.add(">") of '&': result.add("&") of '"': result.add(""") + of '\'': result.add("'") + of '/': result.add("/") else: result.add(c) proc escape*(s: string): string = @@ -164,6 +166,8 @@ proc escape*(s: string): string = ## ``>`` ``>`` ## ``&`` ``&`` ## ``"`` ``"`` + ## ``'`` ``'`` + ## ``/`` ``/`` ## ------------ ------------------- result = newStringOfCap(s.len) addEscaped(result, s) diff --git a/lib/system.nim b/lib/system.nim index d77b4fdee..0df8849f5 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -13,6 +13,21 @@ ## Each module implicitly imports the System module; it must not be listed ## explicitly. Because of this there cannot be a user-defined module named ## ``system``. +## +## Exception hierarchy +## =================== +## +## For visual convenience here is the exception inheritance hierarchy +## represented as a tree: +## +## .. include:: ../doc/exception_hierarchy_fragment.txt +## +## Module system +## ============= +## + +# That lonesome header above is to prevent :idx: entries from being mentioned +# in the global index as part of the previous header (Exception hierarchy). type int* {.magic: Int.} ## default integer type; bitwidth depends on @@ -83,16 +98,8 @@ type proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## Special compile-time procedure that checks whether `x` is - ## defined. `x` has to be an identifier or a qualified identifier. - ## This can be used to check whether a library provides a certain - ## feature or not: - ## - ## .. code-block:: Nimrod - ## when not defined(strutils.toUpper): - ## # provide our own toUpper proc here, because strutils is - ## # missing it. - ## - ## You can also check external symbols introduced through the compiler's + ## defined. + ## `x` is an external symbol introduced through the compiler's ## `-d:x switch <nimrodc.html#compile-time-symbols>`_ to enable build time ## conditionals: ## @@ -101,13 +108,28 @@ proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## # Do here programmer friendly expensive sanity checks. ## # Put here the normal code +proc declared*(x: expr): bool {.magic: "Defined", noSideEffect.} + ## Special compile-time procedure that checks whether `x` is + ## declared. `x` has to be an identifier or a qualified identifier. + ## This can be used to check whether a library provides a certain + ## feature or not: + ## + ## .. code-block:: Nimrod + ## when not defined(strutils.toUpper): + ## # provide our own toUpper proc here, because strutils is + ## # missing it. + when defined(useNimRtl): {.deadCodeElim: on.} proc definedInScope*(x: expr): bool {. + magic: "DefinedInScope", noSideEffect, deprecated.} + ## **Deprecated since version 0.9.6**: Use ``declaredInScope`` instead. + +proc declaredInScope*(x: expr): bool {. magic: "DefinedInScope", noSideEffect.} ## Special compile-time procedure that checks whether `x` is - ## defined in the current scope. `x` has to be an identifier. + ## declared in the current scope. `x` has to be an identifier. proc `not` *(x: bool): bool {.magic: "Not", noSideEffect.} ## Boolean not; returns true iff ``x == false``. @@ -301,9 +323,11 @@ type FWriteIO* = object of FIO ## Effect describing a write IO operation. FExecIO* = object of FIO ## Effect describing an executing IO operation. - E_Base* {.compilerproc.} = object of TObject ## base exception class; - ## each exception has to - ## inherit from `E_Base`. + E_Base* {.compilerproc.} = object of TObject ## \ + ## Base exception class. + ## + ## Each exception has to inherit from `E_Base`. See the full `exception + ## hierarchy`_. parent: ref E_Base ## parent exception (can be used as a stack) name: cstring ## The exception's name is its Nimrod identifier. ## This field is filled automatically in the @@ -313,99 +337,142 @@ type ## is bad style. trace: string - EAsynch* = object of E_Base ## Abstract exception class for - ## *asynchronous exceptions* (interrupts). - ## This is rarely needed: Most - ## exception types inherit from `ESynch` - ESynch* = object of E_Base ## Abstract exception class for - ## *synchronous exceptions*. Most exceptions - ## should be inherited (directly or indirectly) - ## from ESynch. - ESystem* = object of ESynch ## Abstract class for exceptions that the runtime - ## system raises. - EIO* = object of ESystem ## raised if an IO error occured. - EOS* = object of ESystem ## raised if an operating system service failed. + EAsynch* = object of E_Base ## \ + ## Abstract exception class for *asynchronous exceptions* (interrupts). + ## + ## This is rarely needed: most exception types inherit from `ESynch + ## <#ESynch>`_. See the full `exception hierarchy`_. + EControlC* = object of EAsynch ## \ + ## Raised for Ctrl+C key presses in console applications. + ## + ## See the full `exception hierarchy`_. + ESynch* = object of E_Base ## \ + ## Abstract exception class for *synchronous exceptions*. + ## + ## Most exceptions should be inherited (directly or indirectly) from + ## `ESynch` instead of from `EAsynch <#EAsynch>`_. See the full `exception + ## hierarchy`_. + ESystem* = object of ESynch ## \ + ## Abstract class for exceptions that the runtime system raises. + ## + ## See the full `exception hierarchy`_. + EIO* = object of ESystem ## \ + ## Raised if an IO error occured. + ## + ## See the full `exception hierarchy`_. + EOS* = object of ESystem ## \ + ## Raised if an operating system service failed. + ## + ## See the full `exception hierarchy`_. 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 - ## could not be fullfilled. - EArithmetic* = object of ESynch ## raised if any kind of arithmetic - ## error occured. - EDivByZero* {.compilerproc.} = - object of EArithmetic ## is the exception class for integer divide-by-zero - ## errors. - EOverflow* {.compilerproc.} = - object of EArithmetic ## is the exception class for integer calculations - ## whose results are too large to fit in the - ## provided bits. - - EAccessViolation* {.compilerproc.} = - object of ESynch ## the exception class for invalid memory access errors - - EAssertionFailed* {.compilerproc.} = - object of ESynch ## is the exception class for Assert - ## procedures that is raised if the - ## assertion proves wrong - - EControlC* = object of EAsynch ## is the exception class for Ctrl+C - ## key presses in console applications. - - EInvalidValue* = object of ESynch ## is the exception class for string - ## and object conversion errors. - EInvalidKey* = object of EInvalidValue ## is the exception class if a key - ## cannot be found in a table. - - EOutOfMemory* = object of ESystem ## is the exception class for - ## unsuccessful attempts to allocate - ## memory. - - EInvalidIndex* = object of ESynch ## is raised if an array index is out - ## of bounds. - EInvalidField* = object of ESynch ## is raised if a record field is not - ## accessible because its dicriminant's - ## value does not fit. - - EOutOfRange* = object of ESynch ## is raised if a range check error - ## occurred. - - EStackOverflow* = object of ESystem ## is raised if the hardware stack - ## used for subroutine calls overflowed. - - ENoExceptionToReraise* = object of ESynch ## is raised if there is no - ## exception to reraise. - - EInvalidObjectAssignment* = - object of ESynch ## is raised if an object gets assigned to its - ## parent's object. - - EInvalidObjectConversion* = - object of ESynch ## is raised if an object is converted to an incompatible - ## object type. - - EFloatingPoint* = object of ESynch ## base class for floating point exceptions - EFloatInvalidOp* {.compilerproc.} = - object of EFloatingPoint ## Invalid operation according to IEEE: Raised by - ## 0.0/0.0, for example. - EFloatDivByZero* {.compilerproc.} = - object of EFloatingPoint ## Division by zero. Divisor is zero and dividend - ## is a finite nonzero number. - EFloatOverflow* {.compilerproc.} = - object of EFloatingPoint ## Overflow. Operation produces a result - ## that exceeds the range of the exponent - EFloatUnderflow* {.compilerproc.} = - object of EFloatingPoint ## Underflow. Operation produces a result - ## that is too small to be represented as - ## a normal number - EFloatInexact* {.compilerproc.} = - object of EFloatingPoint ## Inexact. Operation produces a result - ## that cannot be represented with infinite - ## precision -- for example, 2.0 / 3.0, log(1.1) - ## NOTE: Nimrod currently does not detect these! - EDeadThread* = - object of ESynch ## is raised if it is attempted to send a message to a - ## dead thread. - + EInvalidLibrary* = object of EOS ## \ + ## Raised if a dynamic library could not be loaded. + ## + ## See the full `exception hierarchy`_. + EResourceExhausted* = object of ESystem ## \ + ## Raised if a resource request could not be fullfilled. + ## + ## See the full `exception hierarchy`_. + EArithmetic* = object of ESynch ## \ + ## Raised if any kind of arithmetic error occured. + ## + ## See the full `exception hierarchy`_. + EDivByZero* {.compilerproc.} = object of EArithmetic ## \ + ## Raised for runtime integer divide-by-zero errors. + ## + ## See the full `exception hierarchy`_. + EOverflow* {.compilerproc.} = object of EArithmetic ## \ + ## Raised for runtime integer overflows. + ## + ## This happens for calculations whose results are too large to fit in the + ## provided bits. See the full `exception hierarchy`_. + EAccessViolation* {.compilerproc.} = object of ESynch ## \ + ## Raised for invalid memory access errors + ## + ## See the full `exception hierarchy`_. + EAssertionFailed* {.compilerproc.} = object of ESynch ## \ + ## Raised when assertion is proved wrong. + ## + ## Usually the result of using the `assert() template <#assert>`_. See the + ## full `exception hierarchy`_. + EInvalidValue* = object of ESynch ## \ + ## Raised for string and object conversion errors. + EInvalidKey* = object of EInvalidValue ## \ + ## Raised if a key cannot be found in a table. + ## + ## Mostly used by the `tables <tables.html>`_ module, it can also be raised + ## by other collection modules like `sets <sets.html>`_ or `strtabs + ## <strtabs.html>`_. See the full `exception hierarchy`_. + EOutOfMemory* = object of ESystem ## \ + ## Raised for unsuccessful attempts to allocate memory. + ## + ## See the full `exception hierarchy`_. + EInvalidIndex* = object of ESynch ## \ + ## Raised if an array index is out of bounds. + ## + ## See the full `exception hierarchy`_. + EInvalidField* = object of ESynch ## \ + ## Raised if a record field is not accessible because its dicriminant's + ## value does not fit. + ## + ## See the full `exception hierarchy`_. + EOutOfRange* = object of ESynch ## \ + ## Raised if a range check error occurred. + ## + ## See the full `exception hierarchy`_. + EStackOverflow* = object of ESystem ## \ + ## Raised if the hardware stack used for subroutine calls overflowed. + ## + ## See the full `exception hierarchy`_. + ENoExceptionToReraise* = object of ESynch ## \ + ## Raised if there is no exception to reraise. + ## + ## See the full `exception hierarchy`_. + EInvalidObjectAssignment* = object of ESynch ## \ + ## Raised if an object gets assigned to its parent's object. + ## + ## See the full `exception hierarchy`_. + EInvalidObjectConversion* = object of ESynch ## \ + ## Raised if an object is converted to an incompatible object type. + ## + ## See the full `exception hierarchy`_. + EFloatingPoint* = object of ESynch ## \ + ## Base class for floating point exceptions. + ## + ## See the full `exception hierarchy`_. + EFloatInvalidOp* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised by invalid operations according to IEEE. + ## + ## Raised by ``0.0/0.0``, for example. See the full `exception + ## hierarchy`_. + EFloatDivByZero* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised by division by zero. + ## + ## Divisor is zero and dividend is a finite nonzero number. See the full + ## `exception hierarchy`_. + EFloatOverflow* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for overflows. + ## + ## The operation produced a result that exceeds the range of the exponent. + ## See the full `exception hierarchy`_. + EFloatUnderflow* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for underflows. + ## + ## The operation produced a result that is too small to be represented as a + ## normal number. See the full `exception hierarchy`_. + EFloatInexact* {.compilerproc.} = object of EFloatingPoint ## \ + ## Raised for inexact results. + ## + ## The operation produced a result that cannot be represented with infinite + ## precision -- for example: ``2.0 / 3.0, log(1.1)`` + ## + ## **NOTE**: Nimrod currently does not detect these! See the full + ## `exception hierarchy`_. + EDeadThread* = object of ESynch ## \ + ## Raised if it is attempted to send a message to a dead thread. + ## + ## See the full `exception hierarchy`_. + TResult* = enum Failure, Success proc sizeof*[T](x: T): Natural {.magic: "SizeOf", noSideEffect.} @@ -782,13 +849,13 @@ proc contains*[T](s: TSlice[T], value: T): bool {.noSideEffect, inline.} = ## assert((1..3).contains(4) == false) result = s.a <= value and value <= s.b -template `in` * (x, y: expr): expr {.immediate.} = contains(y, x) +template `in` * (x, y: expr): expr {.immediate, dirty.} = contains(y, x) ## Sugar for contains ## ## .. code-block:: Nimrod ## assert(1 in (1..3) == true) ## assert(5 in (1..3) == false) -template `notin` * (x, y: expr): expr {.immediate.} = not contains(y, x) +template `notin` * (x, y: expr): expr {.immediate, dirty.} = not contains(y, x) ## Sugar for not containing ## ## .. code-block:: Nimrod @@ -1930,16 +1997,16 @@ when not defined(nimrodVM) and hostOS != "standalone": ## returns an informative string about the GC's activity. This may be useful ## for tweaking. - proc GC_ref*[T](x: ref T) {.magic: "GCref".} - proc GC_ref*[T](x: seq[T]) {.magic: "GCref".} - proc GC_ref*(x: string) {.magic: "GCref".} + proc GC_ref*[T](x: ref T) {.magic: "GCref", gcsafe.} + proc GC_ref*[T](x: seq[T]) {.magic: "GCref", gcsafe.} + proc GC_ref*(x: string) {.magic: "GCref", gcsafe.} ## marks the object `x` as referenced, so that it will not be freed until ## it is unmarked via `GC_unref`. If called n-times for the same object `x`, ## n calls to `GC_unref` are needed to unmark `x`. - proc GC_unref*[T](x: ref T) {.magic: "GCunref".} - proc GC_unref*[T](x: seq[T]) {.magic: "GCunref".} - proc GC_unref*(x: string) {.magic: "GCunref".} + proc GC_unref*[T](x: ref T) {.magic: "GCunref", gcsafe.} + proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", gcsafe.} + proc GC_unref*(x: string) {.magic: "GCunref", gcsafe.} ## see the documentation of `GC_ref`. template accumulateResult*(iter: expr) = @@ -2058,7 +2125,7 @@ template newException*(exceptn: typedesc, message: string): expr = when hostOS == "standalone": include panicoverride -when not defined(sysFatal): +when not declared(sysFatal): template sysFatal(exceptn: typedesc, message: string) = when hostOS == "standalone": panic(message) @@ -2110,11 +2177,17 @@ when not defined(JS): #and not defined(NimrodVM): # WARNING: This is very fragile! An array size of 8 does not work on my # Linux 64bit system. -- That's because the stack direction is the other # way round. - when defined(setStackBottom): + when declared(setStackBottom): var locals {.volatile.}: pointer locals = addr(locals) setStackBottom(locals) + proc initStackBottomWith(locals: pointer) {.inline, compilerproc.} = + # We need to keep initStackBottom around for now to avoid + # bootstrapping problems. + when declared(setStackBottom): + setStackBottom(locals) + var strDesc: TNimType @@ -2383,7 +2456,7 @@ when not defined(JS): #and not defined(NimrodVM): hasRaiseAction: bool raiseAction: proc (e: ref E_Base): bool {.closure.} - when defined(initAllocator): + when declared(initAllocator): initAllocator() when hasThreadSupport: include "system/syslocks" @@ -2500,11 +2573,11 @@ when not defined(JS): #and not defined(NimrodVM): include "system/assign" include "system/repr" - proc getCurrentException*(): ref E_Base {.compilerRtl, inl.} = + proc getCurrentException*(): ref E_Base {.compilerRtl, inl, gcsafe.} = ## retrieves the current exception; if there is none, nil is returned. result = currException - proc getCurrentExceptionMsg*(): string {.inline.} = + proc getCurrentExceptionMsg*(): string {.inline, gcsafe.} = ## retrieves the error message that was attached to the current ## exception; if there is none, "" is returned. var e = getCurrentException() @@ -2956,7 +3029,7 @@ proc compiles*(x): bool {.magic: "Compiles", noSideEffect.} = ## echo "'+' for integers is available" discard -when defined(initDebugger): +when declared(initDebugger): initDebugger() when hostOS != "standalone": @@ -2997,18 +3070,12 @@ proc locals*(): TObject {.magic: "Locals", noSideEffect.} = ## # -> B is 1 discard -proc deepCopy*[T](x: T): T {.magic: "DeepCopy", noSideEffect.} = - ## performs a deep copy of `x`. This is also used by the code generator - ## for the implementation of ``spawn``. - discard +when hostOS != "standalone" and not defined(NimrodVM) and not defined(JS): + proc deepCopy*[T](x: var T, y: T) {.noSideEffect, magic: "DeepCopy".} = + ## performs a deep copy of `x`. This is also used by the code generator + ## for the implementation of ``spawn``. + discard -{.pop.} #{.push warning[GcMem]: off.} + include "system/deepcopy" -when not defined(booting): - type - semistatic*[T] = static[T] | T - # indicates a param of proc specialized for each static value, - # but also accepting run-time values - - template isStatic*(x): expr = compiles(static(x)) - # checks whether `x` is a value known at compile-time +{.pop.} #{.push warning[GcMem]: off.} diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 511a006d3..673d55582 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -39,7 +39,7 @@ var c_stderr {.importc: "stderr", nodecl.}: C_TextFileStar # constants faked as variables: -when not defined(SIGINT): +when not declared(SIGINT): when NoFakeVars: when defined(windows): const @@ -78,10 +78,23 @@ when defined(macosx): else: template SIGBUS: expr = SIGSEGV -proc c_longjmp(jmpb: C_JmpBuf, retval: cint) {. - header: "<setjmp.h>", importc: "longjmp".} -proc c_setjmp(jmpb: C_JmpBuf): cint {. - header: "<setjmp.h>", importc: "setjmp".} +when defined(nimSigSetjmp) and not defined(nimStdSetjmp): + proc c_longjmp(jmpb: C_JmpBuf, retval: cint) {. + header: "<setjmp.h>", importc: "siglongjmp".} + template c_setjmp(jmpb: C_JmpBuf): cint = + proc c_sigsetjmp(jmpb: C_JmpBuf, savemask: cint): cint {. + header: "<setjmp.h>", importc: "sigsetjmp".} + c_sigsetjmp(jmpb, 0) +elif defined(nimRawSetjmp) and not defined(nimStdSetjmp): + proc c_longjmp(jmpb: C_JmpBuf, retval: cint) {. + header: "<setjmp.h>", importc: "_longjmp".} + proc c_setjmp(jmpb: C_JmpBuf): cint {. + header: "<setjmp.h>", importc: "_setjmp".} +else: + proc c_longjmp(jmpb: C_JmpBuf, retval: cint) {. + header: "<setjmp.h>", importc: "longjmp".} + proc c_setjmp(jmpb: C_JmpBuf): cint {. + header: "<setjmp.h>", importc: "setjmp".} proc c_signal(sig: cint, handler: proc (a: cint) {.noconv.}) {. importc: "signal", header: "<signal.h>".} @@ -132,7 +145,7 @@ proc c_realloc(p: pointer, newsize: int): pointer {. importc: "realloc", header: "<stdlib.h>".} when hostOS != "standalone": - when not defined(errno): + when not declared(errno): when defined(NimrodVM): var vmErrnoWrapper {.importc.}: ptr cint template errno: expr = diff --git a/lib/system/arithm.nim b/lib/system/arithm.nim index d9b3aebac..7672947cd 100644 --- a/lib/system/arithm.nim +++ b/lib/system/arithm.nim @@ -114,63 +114,69 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc): proc addInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = # a in eax, and b in edx asm """ - mov eax, `a` - add eax, `b` + mov eax, ecx + add eax, edx jno theEnd call `raiseOverflow` theEnd: + ret """ proc subInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - sub eax, `b` + mov eax, ecx + sub eax, edx jno theEnd call `raiseOverflow` theEnd: + ret """ proc negInt(a: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` + mov eax, ecx neg eax jno theEnd call `raiseOverflow` theEnd: + ret """ proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx idiv ecx jno theEnd call `raiseOverflow` theEnd: + ret """ proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx idiv ecx jno theEnd call `raiseOverflow` theEnd: mov eax, edx + ret """ proc mulInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, `a` - mov ecx, `b` + mov eax, ecx + mov ecx, edx xor edx, edx imul ecx jno theEnd call `raiseOverflow` theEnd: + ret """ elif false: # asmVersion and (defined(gcc) or defined(llvm_gcc)): @@ -241,26 +247,26 @@ elif false: # asmVersion and (defined(gcc) or defined(llvm_gcc)): """ # Platform independent versions of the above (slower!) -when not defined(addInt): +when not declared(addInt): proc addInt(a, b: int): int {.compilerProc, inline.} = result = a +% b if (result xor a) >= 0 or (result xor b) >= 0: return result raiseOverflow() -when not defined(subInt): +when not declared(subInt): proc subInt(a, b: int): int {.compilerProc, inline.} = result = a -% b if (result xor a) >= 0 or (result xor not b) >= 0: return result raiseOverflow() -when not defined(negInt): +when not declared(negInt): proc negInt(a: int): int {.compilerProc, inline.} = if a != low(int): return -a raiseOverflow() -when not defined(divInt): +when not declared(divInt): proc divInt(a, b: int): int {.compilerProc, inline.} = if b == 0: raiseDivByZero() @@ -268,13 +274,13 @@ when not defined(divInt): raiseOverflow() return a div b -when not defined(modInt): +when not declared(modInt): proc modInt(a, b: int): int {.compilerProc, inline.} = if b == 0: raiseDivByZero() return a mod b -when not defined(mulInt): +when not declared(mulInt): # # This code has been inspired by Python's source code. # The native int product x*y is either exactly right or *way* off, being diff --git a/lib/system/assign.nim b/lib/system/assign.nim index 2ae945fb1..0e27eb57f 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -89,14 +89,10 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = copyMem(dest, src, mt.size) # copy raw bits proc genericAssign(dest, src: pointer, mt: PNimType) {.compilerProc.} = - GC_disable() genericAssignAux(dest, src, mt, false) - GC_enable() proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.compilerProc.} = - GC_disable() genericAssignAux(dest, src, mt, true) - GC_enable() when false: proc debugNimType(t: PNimType) = diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim index 43b3f0438..3ef9d00ec 100644 --- a/lib/system/atomics.nim +++ b/lib/system/atomics.nim @@ -7,30 +7,36 @@ # distribution, for details about the copyright. # -## Atomic operations for Nimrod. +# Atomic operations for Nimrod. {.push stackTrace:off.} const someGcc = defined(gcc) or defined(llvm_gcc) or defined(clang) when someGcc and hasThreadSupport: - type - AtomMemModel* = enum - ATOMIC_RELAXED, ## No barriers or synchronization. - ATOMIC_CONSUME, ## Data dependency only for both barrier and - ## synchronization with another thread. - ATOMIC_ACQUIRE, ## Barrier to hoisting of code and synchronizes with - ## release (or stronger) - ## semantic stores from another thread. - ATOMIC_RELEASE, ## Barrier to sinking of code and synchronizes with - ## acquire (or stronger) - ## semantic loads from another thread. - ATOMIC_ACQ_REL, ## Full barrier in both directions and synchronizes - ## with acquire loads - ## and release stores in another thread. - ATOMIC_SEQ_CST ## Full barrier in both directions and synchronizes - ## with acquire loads - ## and release stores in all threads. - + type AtomMemModel* = distinct cint + var ATOMIC_RELAXED* {.importc: "__ATOMIC_RELAXED", nodecl.}: AtomMemModel + ## No barriers or synchronization. + var ATOMIC_CONSUME* {.importc: "__ATOMIC_CONSUME", nodecl.}: AtomMemModel + ## Data dependency only for both barrier and + ## synchronization with another thread. + var ATOMIC_ACQUIRE* {.importc: "__ATOMIC_ACQUIRE", nodecl.}: AtomMemModel + ## Barrier to hoisting of code and synchronizes with + ## release (or stronger) + ## semantic stores from another thread. + var ATOMIC_RELEASE* {.importc: "__ATOMIC_RELEASE", nodecl.}: AtomMemModel + ## Barrier to sinking of code and synchronizes with + ## acquire (or stronger) + ## semantic loads from another thread. + var ATOMIC_ACQ_REL* {.importc: "__ATOMIC_ACQ_REL", nodecl.}: AtomMemModel + ## Full barrier in both directions and synchronizes + ## with acquire loads + ## and release stores in another thread. + var ATOMIC_SEQ_CST* {.importc: "__ATOMIC_SEQ_CST", nodecl.}: AtomMemModel + ## Full barrier in both directions and synchronizes + ## with acquire loads + ## and release stores in all threads. + + type TAtomType* = TNumber|pointer|ptr|char ## Type Class representing valid types for use with atomic procs @@ -166,15 +172,15 @@ else: result = p[] proc atomicInc*(memLoc: var int, x: int = 1): int = - when defined(gcc) and hasThreadSupport: + when someGcc and hasThreadSupport: result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED) else: inc(memLoc, x) result = memLoc proc atomicDec*(memLoc: var int, x: int = 1): int = - when defined(gcc) and hasThreadSupport: - when defined(atomic_sub_fetch): + when someGcc and hasThreadSupport: + when declared(atomic_sub_fetch): result = atomic_sub_fetch(memLoc.addr, x, ATOMIC_RELAXED) else: result = atomic_add_fetch(memLoc.addr, -x, ATOMIC_RELAXED) @@ -196,19 +202,19 @@ else: # XXX is this valid for 'int'? -when (defined(x86) or defined(amd64)) and (defined(gcc) or defined(llvm_gcc)): +when (defined(x86) or defined(amd64)) and someGcc: proc cpuRelax {.inline.} = {.emit: """asm volatile("pause" ::: "memory");""".} elif (defined(x86) or defined(amd64)) and defined(vcc): proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".} -elif defined(intelc): +elif defined(icl): proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".} elif false: from os import sleep proc cpuRelax {.inline.} = os.sleep(1) -when not defined(fence) and hasThreadSupport: +when not declared(fence) and hasThreadSupport: # XXX fixme proc fence*() {.inline.} = var dummy: bool diff --git a/lib/system/channels.nim b/lib/system/channels.nim index e5535dbdc..a5d5c0802 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -14,7 +14,7 @@ ## **Note:** The current implementation of message passing is slow and does ## not work with cyclic data structures. -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} type @@ -226,15 +226,16 @@ proc recv*[TMsg](c: var TChannel[TMsg]): TMsg = llRecv(q, addr(result), cast[PNimType](getTypeInfo(result))) releaseSys(q.lock) -proc tryRecv*[TMsg](c: var TChannel[TMsg]): tuple[dataAvaliable: bool, +proc tryRecv*[TMsg](c: var TChannel[TMsg]): tuple[dataAvailable: bool, msg: TMsg] = ## try to receives a message from the channel `c` if available. Otherwise ## it returns ``(false, default(msg))``. var q = cast[PRawChannel](addr(c)) - if q.mask != ChannelDeadMask: - lockChannel(q): + if q.mask != ChannelDeadMask: + if tryAcquireSys(q.lock): llRecv(q, addr(result.msg), cast[PNimType](getTypeInfo(result.msg))) - result.dataAvaliable = true + result.dataAvailable = true + releaseSys(q.lock) proc peek*[TMsg](c: var TChannel[TMsg]): int = ## returns the current number of messages in the channel `c`. Returns -1 diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim new file mode 100644 index 000000000..e7eb1cdb4 --- /dev/null +++ b/lib/system/deepcopy.nim @@ -0,0 +1,141 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) {.gcsafe.} +proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode) {.gcsafe.} = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + case n.kind + of nkSlot: + genericDeepCopyAux(cast[pointer](d +% n.offset), + cast[pointer](s +% n.offset), n.typ) + of nkList: + for i in 0..n.len-1: + genericDeepCopyAux(dest, src, n.sons[i]) + of nkCase: + var dd = selectBranch(dest, n) + var m = selectBranch(src, n) + # reset if different branches are in use; note different branches also + # imply that's not self-assignment (``x = x``)! + if m != dd and dd != nil: + genericResetAux(dest, dd) + copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset), + n.typ.size) + if m != nil: + genericDeepCopyAux(dest, src, m) + of nkNone: sysAssert(false, "genericDeepCopyAux") + +proc copyDeepString(src: NimString): NimString {.inline.} = + if src != nil: + result = rawNewString(src.space) + result.len = src.len + c_memcpy(result.data, src.data, (src.len + 1) * sizeof(char)) + +proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + sysAssert(mt != nil, "genericDeepCopyAux 2") + case mt.kind + of tyString: + var x = cast[PPointer](dest) + var s2 = cast[PPointer](s)[] + if s2 == nil: + unsureAsgnRef(x, s2) + else: + unsureAsgnRef(x, copyDeepString(cast[NimString](s2))) + of tySequence: + var s2 = cast[PPointer](src)[] + var seq = cast[PGenericSeq](s2) + var x = cast[PPointer](dest) + if s2 == nil: + unsureAsgnRef(x, s2) + return + sysAssert(dest != nil, "genericDeepCopyAux 3") + unsureAsgnRef(x, newSeq(mt, seq.len)) + var dst = cast[TAddress](cast[PPointer](dest)[]) + for i in 0..seq.len-1: + genericDeepCopyAux( + cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + cast[pointer](cast[TAddress](s2) +% i *% mt.base.size +% + GenericSeqSize), + mt.base) + of tyObject: + # we need to copy m_type field for tyObject, as it could be empty for + # sequence reallocations: + var pint = cast[ptr PNimType](dest) + pint[] = cast[ptr PNimType](src)[] + if mt.base != nil: + genericDeepCopyAux(dest, src, mt.base) + genericDeepCopyAux(dest, src, mt.node) + of tyTuple: + genericDeepCopyAux(dest, src, mt.node) + of tyArray, tyArrayConstr: + for i in 0..(mt.size div mt.base.size)-1: + genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), + cast[pointer](s +% i*% mt.base.size), mt.base) + of tyRef: + if mt.base.deepCopy != nil: + let z = mt.base.deepCopy(cast[PPointer](src)[]) + unsureAsgnRef(cast[PPointer](dest), z) + else: + # we modify the header of the cell temporarily; instead of the type + # field we store a forwarding pointer. XXX This is bad when the cloning + # fails due to OOM etc. + let s2 = cast[PPointer](src)[] + if s2 == nil: + unsureAsgnRef(cast[PPointer](dest), s2) + return + when declared(usrToCell): + # unfortunately we only have cycle detection for our native GCs. + let x = usrToCell(s2) + let forw = cast[int](x.typ) + if (forw and 1) == 1: + # we stored a forwarding pointer, so let's use that: + let z = cast[pointer](forw and not 1) + unsureAsgnRef(cast[PPointer](dest), z) + else: + let realType = x.typ + let z = newObj(realType, realType.base.size) + + unsureAsgnRef(cast[PPointer](dest), z) + x.typ = cast[PNimType](cast[int](z) or 1) + genericDeepCopyAux(z, s2, realType.base) + x.typ = realType + else: + let realType = mt + let z = newObj(realType, realType.base.size) + unsureAsgnRef(cast[PPointer](dest), z) + genericDeepCopyAux(z, s2, realType.base) + of tyPtr: + # no cycle check here, but also not really required + if mt.base.deepCopy != nil: + cast[PPointer](dest)[] = mt.base.deepCopy(cast[PPointer](s)[]) + else: + cast[PPointer](dest)[] = cast[PPointer](s)[] + else: + copyMem(dest, src, mt.size) + +proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = + genericDeepCopyAux(dest, src, mt) + +proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} = + # also invoked for 'string' + var src = src + genericDeepCopy(dest, addr(src), mt) + +proc genericDeepCopyOpenArray(dest, src: pointer, len: int, + mt: PNimType) {.compilerproc.} = + var + d = cast[TAddress](dest) + s = cast[TAddress](src) + for i in 0..len-1: + genericDeepCopy(cast[pointer](d +% i*% mt.base.size), + cast[pointer](s +% i*% mt.base.size), mt.base) diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index e1a5a958f..3c5436afb 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -38,11 +38,11 @@ proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.} proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.} var - framePtr {.rtlThreadVar.}: PFrame - excHandler {.rtlThreadVar.}: PSafePoint + framePtr {.threadvar.}: PFrame + excHandler {.threadvar.}: PSafePoint # list of exception handlers # a global variable for the root of all try blocks - currException {.rtlThreadVar.}: ref E_Base + currException {.threadvar.}: ref E_Base proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev @@ -307,7 +307,7 @@ when not defined(noSignalHandler): action("SIGBUS: Illegal storage access. (Attempt to read from nil?)\n") else: block platformSpecificSignal: - when defined(SIGPIPE): + when declared(SIGPIPE): if s == SIGPIPE: action("SIGPIPE: Pipe closed.\n") break platformSpecificSignal @@ -336,7 +336,7 @@ when not defined(noSignalHandler): c_signal(SIGFPE, signalHandler) c_signal(SIGILL, signalHandler) c_signal(SIGBUS, signalHandler) - when defined(SIGPIPE): + when declared(SIGPIPE): c_signal(SIGPIPE, signalHandler) registerSignalHandler() # call it in initialization section diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 3b85fe600..0c1fc7748 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -30,7 +30,7 @@ const # cycles instead of the complex # algorithm -when withRealTime and not defined(getTicks): +when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): proc nimProfile(requestedSize: int) @@ -413,7 +413,7 @@ proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} = {.push stackTrace: off, profiler:off.} proc gcInvariant*() = sysAssert(allocInv(gch.region), "injected") - when defined(markForDebug): + when declared(markForDebug): markForDebug(gch) {.pop.} diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 31c99a601..132da9885 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -26,7 +26,7 @@ const # this seems to be a good value withRealTime = defined(useRealtimeGC) -when withRealTime and not defined(getTicks): +when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): proc nimProfile(requestedSize: int) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 64174e60f..ef8f50831 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -when defined(NimString): +when declared(NimString): # we are in system module: {.pragma: codegenType, compilerproc.} else: @@ -86,6 +86,7 @@ type node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum finalizer: pointer # the finalizer for the type marker: proc (p: pointer, op: int) {.nimcall, gcsafe.} # marker proc for GC + deepcopy: proc (p: pointer): pointer {.nimcall, gcsafe.} PNimType = ptr TNimType # node.len may be the ``first`` element of a set diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8766906e3..423f63e2a 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -515,7 +515,7 @@ proc isFatPointer(ti: PNimType): bool = 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: @@ -566,7 +566,7 @@ proc nimCopy(x: pointer, ti: PNimType): pointer = else: result = x -proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} = +proc genericReset(x: pointer, ti: PNimType): pointer {.compilerproc.} = case ti.kind of tyPtr, tyRef, tyVar, tyNil: if not isFatPointer(ti): diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index a09b6cf93..606743f51 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -131,6 +131,14 @@ when defined(boehmgc): if result == nil: raiseOutOfMem() proc deallocShared(p: pointer) = boehmDealloc(p) + when hasThreadSupport: + proc getFreeSharedMem(): int = + boehmGetFreeBytes() + proc getTotalSharedMem(): int = + boehmGetHeapSize() + proc getOccupiedSharedMem(): int = + getTotalSharedMem() - getFreeSharedMem() + #boehmGCincremental() proc GC_disable() = boehmGC_disable() @@ -164,11 +172,11 @@ when defined(boehmgc): proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard - proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src type @@ -180,7 +188,7 @@ when defined(boehmgc): proc alloc0(r: var TMemRegion, size: int): pointer = result = alloc(size) zeroMem(result, size) - proc dealloc(r: var TMemRegion, p: Pointer) = boehmDealloc(p) + proc dealloc(r: var TMemRegion, p: pointer) = boehmDealloc(p) proc deallocOsPages(r: var TMemRegion) {.inline.} = discard proc deallocOsPages() {.inline.} = discard @@ -239,11 +247,11 @@ elif defined(nogc) and defined(useMalloc): proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard - proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src type @@ -292,11 +300,11 @@ elif defined(nogc): proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard - proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src - proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} = + proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline.} = dest[] = src var allocator {.rtlThreadVar.}: TMemRegion diff --git a/lib/system/repr.nim b/lib/system/repr.nim index f8f949668..8e1bc5f26 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -121,7 +121,7 @@ proc reprSet(p: pointer, typ: PNimType): string {.compilerRtl.} = type TReprClosure {.final.} = object # we cannot use a global variable here # as this wouldn't be thread-safe - when defined(TCellSet): + when declared(TCellSet): marked: TCellSet recdepth: int # do not recurse endlessly indent: int # indentation @@ -130,16 +130,16 @@ when not defined(useNimRtl): proc initReprClosure(cl: var TReprClosure) = # Important: cellsets does not lock the heap when doing allocations! We # have to do it here ... - when hasThreadSupport and hasSharedHeap and defined(heapLock): + when hasThreadSupport and hasSharedHeap and declared(heapLock): AcquireSys(HeapLock) - when defined(TCellSet): + when declared(TCellSet): init(cl.marked) cl.recdepth = -1 # default is to display everything! cl.indent = 0 proc deinitReprClosure(cl: var TReprClosure) = - when defined(TCellSet): deinit(cl.marked) - when hasThreadSupport and hasSharedHeap and defined(heapLock): + when declared(TCellSet): deinit(cl.marked) + when hasThreadSupport and hasSharedHeap and declared(heapLock): ReleaseSys(HeapLock) proc reprBreak(result: var string, cl: TReprClosure) = @@ -201,7 +201,7 @@ when not defined(useNimRtl): proc reprRef(result: var string, p: pointer, typ: PNimType, cl: var TReprClosure) = # we know that p is not nil here: - when defined(TCellSet): + when declared(TCellSet): when defined(boehmGC) or defined(nogc): var cell = cast[PCell](p) else: @@ -221,7 +221,7 @@ when not defined(useNimRtl): dec(cl.recdepth) case typ.kind of tySet: reprSetAux(result, p, typ) - of tyArray: reprArray(result, p, typ, cl) + of tyArray, tyArrayConstr: reprArray(result, p, typ, cl) of tyTuple: reprRecord(result, p, typ, cl) of tyObject: var t = cast[ptr PNimType](p)[] @@ -275,7 +275,7 @@ when not defined(useNimRtl): cl: TReprClosure initReprClosure(cl) result = "" - if typ.kind in {tyObject, tyTuple, tyArray, tySet}: + if typ.kind in {tyObject, tyTuple, tyArray, tyArrayConstr, tySet}: reprAux(result, p, typ, cl) else: var p = p diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim index 95cdba65d..5161104a9 100644 --- a/lib/system/sysspawn.nim +++ b/lib/system/sysspawn.nim @@ -9,7 +9,7 @@ ## Implements Nimrod's 'spawn'. -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} {.push stackTrace:off.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 9db8ce378..bc79bb254 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -32,7 +32,7 @@ proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = return a.len == b.len and c_memcmp(a.data, b.data, a.len * sizeof(char)) == 0'i32 -when defined(allocAtomic): +when declared(allocAtomic): template allocStr(size: expr): expr = cast[NimString](allocAtomic(size)) else: @@ -85,7 +85,7 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = if src != nil: var s = src.space if s < 8: s = 7 - when defined(newObjRC1): + when declared(newObjRC1): result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) + s+1)) else: diff --git a/lib/system/threads.nim b/lib/system/threads.nim index d3b3aa457..c30c57fb9 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -1,17 +1,17 @@ # # -# Nimrod's Runtime Library +# Nim's Runtime Library # (c) Copyright 2012 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # -## Thread support for Nimrod. **Note**: This is part of the system module. +## Thread support for Nim. **Note**: This is part of the system module. ## Do not import it directly. To activate thread support you need to compile ## with the ``--threads:on`` command line switch. ## -## Nimrod's memory model for threads is quite different from other common +## Nim's memory model for threads is quite different from other common ## programming languages (C, Pascal): Each thread has its own ## (garbage collected) heap and sharing of memory is restricted. This helps ## to prevent race conditions and improves efficiency. See `the manual for @@ -19,7 +19,7 @@ ## ## Example: ## -## .. code-block:: nimrod +## .. code-block:: Nim ## ## import locks ## @@ -39,7 +39,7 @@ ## createThread(thr[i], threadFunc, (i*10, i*10+5)) ## joinThreads(thr) -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} const @@ -190,7 +190,7 @@ var globalsSlot = threadVarAlloc() when emulatedThreadVars: proc GetThreadLocalVars(): pointer {.compilerRtl, inl.} = - result = addr(cast[PGcThread](ThreadVarGetValue(globalsSlot)).tls) + result = addr(cast[PGcThread](threadVarGetValue(globalsSlot)).tls) when useStackMaskHack: proc maskStackPointer(offset: int): pointer {.compilerRtl, inl.} = @@ -210,7 +210,7 @@ when not defined(useNimRtl): initGC() when emulatedThreadVars: - if NimThreadVarsSize() > sizeof(TThreadLocalStorage): + if nimThreadVarsSize() > sizeof(TThreadLocalStorage): echo "too large thread local storage size requested" quit 1 @@ -245,14 +245,14 @@ when not defined(useNimRtl): # the GC can examine the stacks? proc stopTheWord() = discard -# We jump through some hops here to ensure that Nimrod thread procs can have -# the Nimrod calling convention. This is needed because thread procs are +# We jump through some hops here to ensure that Nim thread procs can have +# the Nim calling convention. This is needed because thread procs are # ``stdcall`` on Windows and ``noconv`` on UNIX. Alternative would be to just # use ``stdcall`` since it is mapped to ``noconv`` on UNIX anyway. type TThread* {.pure, final.}[TArg] = - object of TGcThread ## Nimrod thread. A thread is a heavy object (~14K) + object of TGcThread ## Nim thread. A thread is a heavy object (~14K) ## that **must not** be part of a message! Use ## a ``TThreadId`` for that. when TArg is void: @@ -267,7 +267,7 @@ when not defined(boehmgc) and not hasSharedHeap: proc deallocOsPages() template threadProcWrapperBody(closure: expr) {.immediate.} = - when defined(globalsSlot): ThreadVarSetValue(globalsSlot, closure) + when declared(globalsSlot): threadVarSetValue(globalsSlot, closure) var t = cast[ptr TThread[TArg]](closure) when useStackMaskHack: var tls: TThreadLocalStorage @@ -275,13 +275,13 @@ template threadProcWrapperBody(closure: expr) {.immediate.} = # init the GC for this thread: setStackBottom(addr(t)) initGC() - when defined(registerThread): + when declared(registerThread): t.stackBottom = addr(t) registerThread(t) when TArg is void: t.dataFn() else: t.dataFn(t.data) - when defined(registerThread): unregisterThread(t) - when defined(deallocOsPages): deallocOsPages() + when declared(registerThread): unregisterThread(t) + when declared(deallocOsPages): deallocOsPages() # Since an unhandled exception terminates the whole process (!), there is # no need for a ``try finally`` here, nor would it be correct: The current # exception is tried to be re-raised by the code-gen after the ``finally``! @@ -305,22 +305,26 @@ proc running*[TArg](t: TThread[TArg]): bool {.inline.} = ## returns true if `t` is running. result = t.dataFn != nil -proc joinThread*[TArg](t: TThread[TArg]) {.inline.} = - ## waits for the thread `t` to finish. - when hostOS == "windows": +when hostOS == "windows": + proc joinThread*[TArg](t: TThread[TArg]) {.inline.} = + ## waits for the thread `t` to finish. discard waitForSingleObject(t.sys, -1'i32) - else: - discard pthread_join(t.sys, nil) -proc joinThreads*[TArg](t: varargs[TThread[TArg]]) = - ## waits for every thread in `t` to finish. - when hostOS == "windows": + proc joinThreads*[TArg](t: varargs[TThread[TArg]]) = + ## waits for every thread in `t` to finish. var a: array[0..255, TSysThread] sysAssert a.len >= t.len, "a.len >= t.len" for i in 0..t.high: a[i] = t[i].sys - discard waitForMultipleObjects(t.len.int32, + discard waitForMultipleObjects(t.len.int32, cast[ptr TSysThread](addr(a)), 1, -1) - else: + +else: + proc joinThread*[TArg](t: TThread[TArg]) {.inline.} = + ## waits for the thread `t` to finish. + discard pthread_join(t.sys, nil) + + proc joinThreads*[TArg](t: varargs[TThread[TArg]]) = + ## waits for every thread in `t` to finish. for i in 0..t.high: joinThread(t[i]) when false: @@ -332,25 +336,35 @@ when false: discard TerminateThread(t.sys, 1'i32) else: discard pthread_cancel(t.sys) - when defined(registerThread): unregisterThread(addr(t)) + when declared(registerThread): unregisterThread(addr(t)) t.dataFn = nil -proc createThread*[TArg](t: var TThread[TArg], - tp: proc (arg: TArg) {.thread.}, - param: TArg) = - ## creates a new thread `t` and starts its execution. Entry point is the - ## proc `tp`. `param` is passed to `tp`. `TArg` can be ``void`` if you - ## don't need to pass any data to the thread. - when TArg isnot void: t.data = param - t.dataFn = tp - when hasSharedHeap: t.stackSize = ThreadStackSize - when hostOS == "windows": +when hostOS == "windows": + proc createThread*[TArg](t: var TThread[TArg], + tp: proc (arg: TArg) {.thread.}, + param: TArg) = + ## creates a new thread `t` and starts its execution. Entry point is the + ## proc `tp`. `param` is passed to `tp`. `TArg` can be ``void`` if you + ## don't need to pass any data to the thread. + when TArg isnot void: t.data = param + t.dataFn = tp + when hasSharedHeap: t.stackSize = ThreadStackSize var dummyThreadId: int32 t.sys = createThread(nil, ThreadStackSize, threadProcWrapper[TArg], addr(t), 0'i32, dummyThreadId) if t.sys <= 0: raise newException(EResourceExhausted, "cannot create thread") - else: + +else: + proc createThread*[TArg](t: var TThread[TArg], + tp: proc (arg: TArg) {.thread.}, + param: TArg) = + ## creates a new thread `t` and starts its execution. Entry point is the + ## proc `tp`. `param` is passed to `tp`. `TArg` can be ``void`` if you + ## don't need to pass any data to the thread. + when TArg isnot void: t.data = param + t.dataFn = tp + when hasSharedHeap: t.stackSize = ThreadStackSize var a {.noinit.}: Tpthread_attr pthread_attr_init(a) pthread_attr_setstacksize(a, ThreadStackSize) @@ -364,7 +378,7 @@ proc threadId*[TArg](t: var TThread[TArg]): TThreadId[TArg] {.inline.} = proc myThreadId*[TArg](): TThreadId[TArg] = ## returns the thread ID of the thread that calls this proc. This is unsafe ## because the type ``TArg`` is not checked for consistency! - result = cast[TThreadId[TArg]](ThreadVarGetValue(globalsSlot)) + result = cast[TThreadId[TArg]](threadVarGetValue(globalsSlot)) when false: proc mainThreadId*[TArg](): TThreadId[TArg] = diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index e2a5d87e9..cd64ff410 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -7,10 +7,10 @@ # distribution, for details about the copyright. # -## Nimrod support for C/C++'s `wide strings`:idx:. This is part of the system -## module! Do not import it directly! +# Nimrod support for C/C++'s `wide strings`:idx:. This is part of the system +# module! Do not import it directly! -when not defined(NimString): +when not declared(NimString): {.error: "You must not import this module explicitly".} type @@ -103,7 +103,7 @@ proc newWideCString*(source: cstring, L: int): WideCString = proc newWideCString*(s: cstring): WideCString = if s.isNil: return nil - when not defined(c_strlen): + when not declared(c_strlen): proc c_strlen(a: cstring): int {. header: "<string.h>", noSideEffect, importc: "strlen".} diff --git a/lib/windows/ole2.nim b/lib/windows/ole2.nim deleted file mode 100644 index ec0ab8f5d..000000000 --- a/lib/windows/ole2.nim +++ /dev/null @@ -1,208 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2006 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -import - windows - -const - GUID_NULL*: TGUID = (D1: 0x00000000, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000]) - IID_IUnknown*: TGUID = (D1: 0x00000000, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IClassFactory*: TGUID = (D1: 0x00000001, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMarshal*: TGUID = (D1: 0x00000003, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMalloc*: TGUID = (D1: 0x00000002, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStdMarshalInfo*: TGUID = (D1: 0x00000018, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IExternalConnection*: TGUID = (D1: 0x00000019, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IEnumUnknown*: TGUID = (D1: 0x00000100, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IBindCtx*: TGUID = (D1: 0x0000000E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumMoniker*: TGUID = (D1: 0x00000102, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRunnableObject*: TGUID = (D1: 0x00000126, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRunningObjectTable*: TGUID = (D1: 0x00000010, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IPersist*: TGUID = (D1: 0x0000010C, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistStream*: TGUID = (D1: 0x00000109, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IMoniker*: TGUID = (D1: 0x0000000F, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumString*: TGUID = (D1: 0x00000101, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStream*: TGUID = (D1: 0x0000000C, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumStatStg*: TGUID = (D1: 0x0000000D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IStorage*: TGUID = (D1: 0x0000000B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistFile*: TGUID = (D1: 0x0000010B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IPersistStorage*: TGUID = (D1: 0x0000010A, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ILockBytes*: TGUID = (D1: 0x0000000A, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumFormatEtc*: TGUID = (D1: 0x00000103, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumStatData*: TGUID = (D1: 0x00000105, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRootStorage*: TGUID = (D1: 0x00000012, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IAdviseSink*: TGUID = (D1: 0x0000010F, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IAdviseSink2*: TGUID = (D1: 0x00000125, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDataObject*: TGUID = (D1: 0x0000010E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDataAdviseHolder*: TGUID = (D1: 0x00000110, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IMessageFilter*: TGUID = (D1: 0x00000016, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IRpcChannelBuffer*: TGUID = (D1: 0xD5F56B60, D2: 0x0000593B, - D3: 0x0000101A, D4: [0x000000B5, 0x00000069, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002D, 0x000000BF, 0x0000007A]) - IID_IRpcProxyBuffer*: TGUID = (D1: 0xD5F56A34, D2: 0x0000593B, D3: 0x0000101A, D4: [ - 0x000000B5, 0x00000069, 0x00000008, 0x00000000, 0x0000002B, 0x0000002D, - 0x000000BF, 0x0000007A]) - IID_IRpcStubBuffer*: TGUID = (D1: 0xD5F56AFC, D2: 0x0000593B, D3: 0x0000101A, D4: [ - 0x000000B5, 0x00000069, 0x00000008, 0x00000000, 0x0000002B, 0x0000002D, - 0x000000BF, 0x0000007A]) - IID_IPSFactoryBuffer*: TGUID = (D1: 0xD5F569D0, D2: 0x0000593B, - D3: 0x0000101A, D4: [0x000000B5, 0x00000069, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002D, 0x000000BF, 0x0000007A]) - IID_ICreateTypeInfo*: TGUID = (D1: 0x00020405, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ICreateTypeLib*: TGUID = (D1: 0x00020406, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDispatch*: TGUID = (D1: 0x00020400, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumVariant*: TGUID = (D1: 0x00020404, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeComp*: TGUID = (D1: 0x00020403, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeInfo*: TGUID = (D1: 0x00020401, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_ITypeLib*: TGUID = (D1: 0x00020402, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IErrorInfo*: TGUID = (D1: 0x1CF2B120, D2: 0x0000547D, D3: 0x0000101B, D4: [ - 0x0000008E, 0x00000065, 0x00000008, 0x00000000, 0x0000002B, 0x0000002B, - 0x000000D1, 0x00000019]) - IID_ICreateErrorInfo*: TGUID = (D1: 0x22F03340, D2: 0x0000547D, - D3: 0x0000101B, D4: [0x0000008E, 0x00000065, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002B, 0x000000D1, 0x00000019]) - IID_ISupportErrorInfo*: TGUID = (D1: 0xDF0B3D60, D2: 0x0000548F, - D3: 0x0000101B, D4: [0x0000008E, 0x00000065, 0x00000008, 0x00000000, - 0x0000002B, 0x0000002B, 0x000000D1, 0x00000019]) - IID_IOleAdviseHolder*: TGUID = (D1: 0x00000111, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleCache*: TGUID = (D1: 0x0000011E, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleCache2*: TGUID = (D1: 0x00000128, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleCacheControl*: TGUID = (D1: 0x00000129, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IParseDisplayName*: TGUID = (D1: 0x0000011A, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleContainer*: TGUID = (D1: 0x0000011B, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleClientSite*: TGUID = (D1: 0x00000118, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleObject*: TGUID = (D1: 0x00000112, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleWindow*: TGUID = (D1: 0x00000114, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleLink*: TGUID = (D1: 0x0000011D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IOleItemContainer*: TGUID = (D1: 0x0000011C, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceUIWindow*: TGUID = (D1: 0x00000115, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceActiveObject*: TGUID = (D1: 0x00000117, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceFrame*: TGUID = (D1: 0x00000116, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceObject*: TGUID = (D1: 0x00000113, D2: 0x00000000, - D3: 0x00000000, D4: [0x000000C0, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000046]) - IID_IOleInPlaceSite*: TGUID = (D1: 0x00000119, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IViewObject*: TGUID = (D1: 0x0000010D, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IViewObject2*: TGUID = (D1: 0x00000127, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDropSource*: TGUID = (D1: 0x00000121, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IDropTarget*: TGUID = (D1: 0x00000122, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) - IID_IEnumOleVerb*: TGUID = (D1: 0x00000104, D2: 0x00000000, D3: 0x00000000, D4: [ - 0x000000C0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000046]) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index dcae6ffaf..09696b67f 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -664,6 +664,7 @@ const WSAEDISCON* = 10101 WSAENETRESET* = 10052 WSAETIMEDOUT* = 10060 + ERROR_NETNAME_DELETED* = 64 proc CreateIoCompletionPort*(FileHandle: THANDLE, ExistingCompletionPort: THANDLE, CompletionKey: DWORD, diff --git a/lib/wrappers/postgres.nim b/lib/wrappers/postgres.nim index d99e5651c..ce78d3435 100644 --- a/lib/wrappers/postgres.nim +++ b/lib/wrappers/postgres.nim @@ -213,6 +213,8 @@ proc PQexecParams*(conn: PPGconn, command: cstring, nParams: int32, paramTypes: POid, paramValues: cstringArray, paramLengths, paramFormats: ptr int32, resultFormat: int32): PPGresult{. cdecl, dynlib: dllName, importc: "PQexecParams".} +proc PQprepare*(conn: PPGconn, stmtName, query: cstring, nParams: int32, + paramTypes: POid): PPGresult{.cdecl, dynlib: dllName, importc: "PQprepare".} proc PQexecPrepared*(conn: PPGconn, stmtName: cstring, nParams: int32, paramValues: cstringArray, paramLengths, paramFormats: ptr int32, resultFormat: int32): PPGresult{. diff --git a/lib/wrappers/sdl/sdl_ttf.nim b/lib/wrappers/sdl/sdl_ttf.nim index f501e31d8..45247df4d 100644 --- a/lib/wrappers/sdl/sdl_ttf.nim +++ b/lib/wrappers/sdl/sdl_ttf.nim @@ -333,11 +333,5 @@ proc VERSION*(X: var sdl.Tversion) = X.patch = PATCHLEVEL -when not (defined(Workaround_RenderText_Solid)): - proc RenderText_Solid*(font: PFont, text: cstring, fg: TColor): PSurface{. - cdecl, importc: "TTF_RenderText_Solid", dynlib: ttfLibName.} -else: - proc RenderText_Solid(font: PFont, text: cstring, fg: TColor): PSurface = - var Black: TColor # initialized to zero - result = RenderText_Shaded(font, text, fg, Black) - +proc RenderText_Solid*(font: PFont, text: cstring, fg: TColor): PSurface{. + cdecl, importc: "TTF_RenderText_Solid", dynlib: ttfLibName.} diff --git a/lib/wrappers/zmq.nim b/lib/wrappers/zmq.nim deleted file mode 100644 index 9826ab813..000000000 --- a/lib/wrappers/zmq.nim +++ /dev/null @@ -1,322 +0,0 @@ -# Nimrod wrapper of 0mq -# Generated by c2nim with modifications and enhancement from Andreas Rumpf -# Original licence follows: - -# -# Copyright (c) 2007-2011 iMatix Corporation -# Copyright (c) 2007-2011 Other contributors as noted in the AUTHORS file -# -# This file is part of 0MQ. -# -# 0MQ is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# 0MQ is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# - -# Generated from zmq version 2.1.5 - -## Nimrod 0mq wrapper. This file contains the low level C wrappers as well as -## some higher level constructs. The higher level constructs are easily -## recognizable because they are the only ones that have documentation. -## -## Example of a client: -## -## .. code-block:: nimrod -## import zmq -## -## var connection = zmq.open("tcp://localhost:5555", server=false) -## echo("Connecting...") -## for i in 0..10: -## echo("Sending hello...", i) -## send(connection, "Hello") -## var reply = receive(connection) -## echo("Received ...", reply) -## close(connection) -## -## Example of a server: -## -## .. code-block:: nimrod -## -## import zmq -## var connection = zmq.open("tcp://*:5555", server=true) -## while True: -## var request = receive(connection) -## echo("Received: ", request) -## send(connection, "World") -## close(connection) - -{.deadCodeElim: on.} -when defined(windows): - const - zmqdll* = "zmq.dll" -elif defined(macosx): - const - zmqdll* = "libzmq.dylib" -else: - const - zmqdll* = "libzmq.so" - -# A number random enough not to collide with different errno ranges on -# different OSes. The assumption is that error_t is at least 32-bit type. -const - HAUSNUMERO* = 156384712 - # On Windows platform some of the standard POSIX errnos are not defined. - ENOTSUP* = (HAUSNUMERO + 1) - EPROTONOSUPPORT* = (HAUSNUMERO + 2) - ENOBUFS* = (HAUSNUMERO + 3) - ENETDOWN* = (HAUSNUMERO + 4) - EADDRINUSE* = (HAUSNUMERO + 5) - EADDRNOTAVAIL* = (HAUSNUMERO + 6) - ECONNREFUSED* = (HAUSNUMERO + 7) - EINPROGRESS* = (HAUSNUMERO + 8) - # Native 0MQ error codes. - EFSM* = (HAUSNUMERO + 51) - ENOCOMPATPROTO* = (HAUSNUMERO + 52) - ETERM* = (HAUSNUMERO + 53) - EMTHREAD* = (HAUSNUMERO + 54) - # Maximal size of "Very Small Message". VSMs are passed by value - # to avoid excessive memory allocation/deallocation. - # If VMSs larger than 255 bytes are required, type of 'vsm_size' - # field in msg_t structure should be modified accordingly. - MAX_VSM_SIZE* = 30 - - POLLIN* = 1 - POLLOUT* = 2 - POLLERR* = 4 - - STREAMER* = 1 - FORWARDER* = 2 - QUEUE* = 3 - - PAIR* = 0 - PUB* = 1 - SUB* = 2 - REQ* = 3 - REP* = 4 - DEALER* = 5 - ROUTER* = 6 - PULL* = 7 - PUSH* = 8 - XPUB* = 9 - XSUB* = 10 - XREQ* = DEALER # Old alias, remove in 3.x - XREP* = ROUTER # Old alias, remove in 3.x - UPSTREAM* = PULL # Old alias, remove in 3.x - DOWNSTREAM* = PUSH # Old alias, remove in 3.x - -type - # Message types. These integers may be stored in 'content' member of the - # message instead of regular pointer to the data. - TMsgTypes* = enum - DELIMITER = 31, - VSM = 32 - # Message flags. MSG_SHARED is strictly speaking not a message flag - # (it has no equivalent in the wire format), however, making it a flag - # allows us to pack the stucture tighter and thus improve performance. - TMsgFlags* = enum - MSG_MORE = 1, - MSG_SHARED = 128, - MSG_MASK = 129 # Merges all the flags - # A message. Note that 'content' is not a pointer to the raw data. - # Rather it is pointer to zmq::msg_content_t structure - # (see src/msg_content.hpp for its definition). - TMsg*{.pure, final.} = object - content*: pointer - flags*: char - vsm_size*: char - vsm_data*: array[0..MAX_VSM_SIZE - 1, char] - - TFreeFn = proc (data, hint: pointer) {.noconv.} - - TContext {.final, pure.} = object - PContext* = ptr TContext - - # Socket Types - TSocket {.final, pure.} = object - PSocket* = ptr TSocket - - # Socket options. - TSockOptions* = enum - HWM = 1, - SWAP = 3, - AFFINITY = 4, - IDENTITY = 5, - SUBSCRIBE = 6, - UNSUBSCRIBE = 7, - RATE = 8, - RECOVERY_IVL = 9, - MCAST_LOOP = 10, - SNDBUF = 11, - RCVBUF = 12, - RCVMORE = 13, - FD = 14, - EVENTS = 15, - theTYPE = 16, - LINGER = 17, - RECONNECT_IVL = 18, - BACKLOG = 19, - RECOVERY_IVL_MSEC = 20, # opt. recovery time, reconcile in 3.x - RECONNECT_IVL_MAX = 21 - - # Send/recv options. - TSendRecvOptions* = enum - NOBLOCK, SNDMORE - - TPollItem*{.pure, final.} = object - socket*: PSocket - fd*: cint - events*: cshort - revents*: cshort - -# Run-time API version detection - -proc version*(major: var cint, minor: var cint, patch: var cint){.cdecl, - importc: "zmq_version", dynlib: zmqdll.} -#**************************************************************************** -# 0MQ errors. -#**************************************************************************** - -# This function retrieves the errno as it is known to 0MQ library. The goal -# of this function is to make the code 100% portable, including where 0MQ -# compiled with certain CRT library (on Windows) is linked to an -# application that uses different CRT library. - -proc errno*(): cint{.cdecl, importc: "zmq_errno", dynlib: zmqdll.} -# Resolves system errors and 0MQ errors to human-readable string. - -proc strerror*(errnum: cint): cstring {.cdecl, importc: "zmq_strerror", - dynlib: zmqdll.} -#**************************************************************************** -# 0MQ message definition. -#**************************************************************************** - -proc msg_init*(msg: var TMsg): cint{.cdecl, importc: "zmq_msg_init", - dynlib: zmqdll.} -proc msg_init*(msg: var TMsg, size: int): cint{.cdecl, - importc: "zmq_msg_init_size", dynlib: zmqdll.} -proc msg_init*(msg: var TMsg, data: cstring, size: int, - ffn: TFreeFn, hint: pointer): cint{.cdecl, - importc: "zmq_msg_init_data", dynlib: zmqdll.} -proc msg_close*(msg: var TMsg): cint {.cdecl, importc: "zmq_msg_close", - dynlib: zmqdll.} -proc msg_move*(dest, src: var TMsg): cint{.cdecl, - importc: "zmq_msg_move", dynlib: zmqdll.} -proc msg_copy*(dest, src: var TMsg): cint{.cdecl, - importc: "zmq_msg_copy", dynlib: zmqdll.} -proc msg_data*(msg: var TMsg): cstring {.cdecl, importc: "zmq_msg_data", - dynlib: zmqdll.} -proc msg_size*(msg: var TMsg): int {.cdecl, importc: "zmq_msg_size", - dynlib: zmqdll.} - -#**************************************************************************** -# 0MQ infrastructure (a.k.a. context) initialisation & termination. -#**************************************************************************** - -proc init*(io_threads: cint): PContext {.cdecl, importc: "zmq_init", - dynlib: zmqdll.} -proc term*(context: PContext): cint {.cdecl, importc: "zmq_term", - dynlib: zmqdll.} -#**************************************************************************** -# 0MQ socket definition. -#**************************************************************************** - -proc socket*(context: PContext, theType: cint): PSocket {.cdecl, - importc: "zmq_socket", dynlib: zmqdll.} -proc close*(s: PSocket): cint{.cdecl, importc: "zmq_close", dynlib: zmqdll.} -proc setsockopt*(s: PSocket, option: cint, optval: pointer, - optvallen: int): cint {.cdecl, importc: "zmq_setsockopt", - dynlib: zmqdll.} -proc getsockopt*(s: PSocket, option: cint, optval: pointer, - optvallen: ptr int): cint{.cdecl, - importc: "zmq_getsockopt", dynlib: zmqdll.} -proc bindAddr*(s: PSocket, address: cstring): cint{.cdecl, importc: "zmq_bind", - dynlib: zmqdll.} -proc connect*(s: PSocket, address: cstring): cint{.cdecl, - importc: "zmq_connect", dynlib: zmqdll.} -proc send*(s: PSocket, msg: var TMsg, flags: cint): cint{.cdecl, - importc: "zmq_send", dynlib: zmqdll.} -proc recv*(s: PSocket, msg: var TMsg, flags: cint): cint{.cdecl, - importc: "zmq_recv", dynlib: zmqdll.} -#**************************************************************************** -# I/O multiplexing. -#**************************************************************************** - -proc poll*(items: ptr TPollItem, nitems: cint, timeout: int): cint{. - cdecl, importc: "zmq_poll", dynlib: zmqdll.} - -#**************************************************************************** -# Built-in devices -#**************************************************************************** - -proc device*(device: cint, insocket, outsocket: PSocket): cint{. - cdecl, importc: "zmq_device", dynlib: zmqdll.} - -type - EZmq* = object of ESynch ## exception that is raised if something fails - TConnection* {.pure, final.} = object ## a connection - c*: PContext ## the embedded context - s*: PSocket ## the embedded socket - - TConnectionMode* = enum ## connection mode - conPAIR = 0, - conPUB = 1, - conSUB = 2, - conREQ = 3, - conREP = 4, - conDEALER = 5, - conROUTER = 6, - conPULL = 7, - conPUSH = 8, - conXPUB = 9, - conXSUB = 10 - -proc zmqError*() {.noinline, noreturn.} = - ## raises EZmq with error message from `zmq.strerror`. - var e: ref EZmq - new(e) - e.msg = $strerror(errno()) - raise e - -proc open*(address: string, server: bool, mode: TConnectionMode = conDEALER, - numthreads = 4): TConnection = - ## opens a new connection. If `server` is true, it uses `bindAddr` for the - ## underlying socket, otherwise it opens the socket with `connect`. - result.c = init(cint(numthreads)) - if result.c == nil: zmqError() - result.s = socket(result.c, cint(ord(mode))) - if result.s == nil: zmqError() - if server: - if bindAddr(result.s, address) != 0'i32: zmqError() - else: - if connect(result.s, address) != 0'i32: zmqError() - -proc close*(c: TConnection) = - ## closes the connection. - if close(c.s) != 0'i32: zmqError() - if term(c.c) != 0'i32: zmqError() - -proc send*(c: TConnection, msg: string) = - ## sends a message over the connection. - var m: TMsg - if msg_init(m, msg.len) != 0'i32: zmqError() - copyMem(msg_data(m), cstring(msg), msg.len) - if send(c.s, m, 0'i32) != 0'i32: zmqError() - discard msg_close(m) - -proc receive*(c: TConnection): string = - ## receives a message from a connection. - var m: TMsg - if msg_init(m) != 0'i32: zmqError() - if recv(c.s, m, 0'i32) != 0'i32: zmqError() - result = newString(msg_size(m)) - copyMem(addr(result[0]), msg_data(m), result.len) - discard msg_close(m) |