'assert' hides EAssertionFailsure; stdlib makes use of 'tags'

This commit is contained in:
Araq 2012-11-18 01:36:20 +01:00
commit 3c0a6a8962
16 changed files with 242 additions and 161 deletions

View file

@ -59,6 +59,7 @@ proc InitDefines*() =
DefineSymbol("nimhygiene") DefineSymbol("nimhygiene")
DefineSymbol("niminheritable") DefineSymbol("niminheritable")
DefineSymbol("nimmixin") DefineSymbol("nimmixin")
DefineSymbol("nimeffects")
# add platform specific symbols: # add platform specific symbols:
case targetCPU case targetCPU

View file

@ -102,7 +102,8 @@ proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
throws(a.exc, e) throws(a.exc, e)
proc mergeEffects(a: PEffects, b: PNode, useLineInfo: bool) = proc mergeEffects(a: PEffects, b: PNode, useLineInfo: bool) =
for effect in items(b): addEffect(a, effect, useLineInfo) if not b.isNil:
for effect in items(b): addEffect(a, effect, useLineInfo)
proc addTag(a: PEffects, e: PNode, useLineInfo=true) = proc addTag(a: PEffects, e: PNode, useLineInfo=true) =
var aa = a.tags var aa = a.tags
@ -113,7 +114,8 @@ proc addTag(a: PEffects, e: PNode, useLineInfo=true) =
throws(a.tags, e) throws(a.tags, e)
proc mergeTags(a: PEffects, b: PNode, useLineInfo: bool) = proc mergeTags(a: PEffects, b: PNode, useLineInfo: bool) =
for effect in items(b): addTag(a, effect, useLineInfo) if not b.isNil:
for effect in items(b): addTag(a, effect, useLineInfo)
proc listEffects(a: PEffects) = proc listEffects(a: PEffects) =
for e in items(a.exc): Message(e.info, hintUser, typeToString(e.typ)) for e in items(a.exc): Message(e.info, hintUser, typeToString(e.typ))
@ -125,7 +127,7 @@ proc catches(tracked: PEffects, e: PType) =
var i = tracked.bottom var i = tracked.bottom
while i < L: while i < L:
# r supertype of e? # r supertype of e?
if inheritanceDiff(tracked.exc[i].excType, e) <= 0: if safeInheritanceDiff(tracked.exc[i].excType, e) <= 0:
tracked.exc.sons[i] = tracked.exc.sons[L-1] tracked.exc.sons[i] = tracked.exc.sons[L-1]
dec L dec L
else: else:
@ -272,7 +274,7 @@ proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
for r in items(real): for r in items(real):
block search: block search:
for s in 0 .. <spec.len: for s in 0 .. <spec.len:
if inheritanceDiff(r.excType, spec[s].typ) <= 0: if safeInheritanceDiff(r.excType, spec[s].typ) <= 0:
used.incl(s) used.incl(s)
break search break search
# XXX call graph analysis would be nice here! # XXX call graph analysis would be nice here!
@ -341,6 +343,6 @@ proc trackProc*(s: PSym, body: PNode) =
let tagsSpec = effectSpec(p, wTags) let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ", checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ",
hints=on) hints=off)
# after the check, use the formal spec: # after the check, use the formal spec:
effects.sons[tagEffects] = tagsSpec effects.sons[tagEffects] = tagsSpec

View file

@ -1190,16 +1190,23 @@ proc baseOfDistinct*(t: PType): PType =
internalAssert parent != nil internalAssert parent != nil
parent.sons[0] = it.sons[0] parent.sons[0] = it.sons[0]
proc safeInheritanceDiff*(a, b: PType): int =
# same as inheritanceDiff but checks for tyError:
if a.kind == tyError or b.kind == tyError:
result = -1
else:
result = inheritanceDiff(a, b)
proc compatibleEffectsAux(se, re: PNode): bool = proc compatibleEffectsAux(se, re: PNode): bool =
if re.isNil: return false if re.isNil: return false
for r in items(re): for r in items(re):
block search: block search:
for s in items(se): for s in items(se):
if inheritanceDiff(r.typ, s.typ) <= 0: if safeInheritanceDiff(r.typ, s.typ) <= 0:
break search break search
return false return false
result = true result = true
proc compatibleEffects*(formal, actual: PType): bool = proc compatibleEffects*(formal, actual: PType): bool =
# for proc type compatibility checking: # for proc type compatibility checking:
assert formal.kind == tyProc and actual.kind == tyProc assert formal.kind == tyProc and actual.kind == tyProc

View file

@ -23,6 +23,13 @@ type
## in preventDeadlocks-mode guarantees re-entrancy. ## in preventDeadlocks-mode guarantees re-entrancy.
TCond* = TSysCond ## Nimrod condition variable TCond* = TSysCond ## Nimrod condition variable
FLock* = object of TEffect ## effect that denotes that some lock operation
## is performed
FAquireLock* = object of FLock ## effect that denotes that some lock is
## aquired
FReleaseLock* = object of FLock ## effect that denotes that some lock is
## released
const const
noDeadlocks = defined(preventDeadlocks) noDeadlocks = defined(preventDeadlocks)
maxLocksPerThread* = 10 ## max number of locks a thread can hold maxLocksPerThread* = 10 ## max number of locks a thread can hold
@ -51,7 +58,7 @@ proc DeinitLock*(lock: var TLock) {.inline.} =
## Frees the resources associated with the lock. ## Frees the resources associated with the lock.
DeinitSys(lock) DeinitSys(lock)
proc TryAcquire*(lock: var TLock): bool = proc TryAcquire*(lock: var TLock): bool {.tags: [FAquireLock].} =
## Tries to acquire the given lock. Returns `true` on success. ## Tries to acquire the given lock. Returns `true` on success.
result = TryAcquireSys(lock) result = TryAcquireSys(lock)
when noDeadlocks: when noDeadlocks:
@ -83,7 +90,7 @@ proc TryAcquire*(lock: var TLock): bool =
inc(locksLen) inc(locksLen)
assert OrderedLocks() assert OrderedLocks()
proc Acquire*(lock: var TLock) = proc Acquire*(lock: var TLock) {.tags: [FAquireLock].} =
## Acquires the given lock. ## Acquires the given lock.
when nodeadlocks: when nodeadlocks:
var p = addr(lock) var p = addr(lock)
@ -128,7 +135,7 @@ proc Acquire*(lock: var TLock) =
else: else:
AcquireSys(lock) AcquireSys(lock)
proc Release*(lock: var TLock) = proc Release*(lock: var TLock) {.tags: [FReleaseLock].} =
## Releases the given lock. ## Releases the given lock.
when nodeadlocks: when nodeadlocks:
var p = addr(lock) var p = addr(lock)

View file

@ -33,6 +33,10 @@ type
EDb* = object of EIO ## exception that is raised if a database error occurs EDb* = object of EIO ## exception that is raised if a database error occurs
TDbConn* = TMongo ## a database connection; alias for ``TMongo`` TDbConn* = TMongo ## a database connection; alias for ``TMongo``
FDb* = object of FIO ## effect that denotes a database operation
FReadDb* = object of FReadIO ## effect that denotes a read operation
FWriteDb* = object of FWriteIO ## effect that denotes a write operation
proc dbError*(db: TDbConn, msg: string) {.noreturn.} = proc dbError*(db: TDbConn, msg: string) {.noreturn.} =
## raises an EDb exception with message `msg`. ## raises an EDb exception with message `msg`.
var e: ref EDb var e: ref EDb
@ -43,12 +47,13 @@ proc dbError*(db: TDbConn, msg: string) {.noreturn.} =
e.msg = $db.err & " " & msg e.msg = $db.err & " " & msg
raise e raise e
proc Close*(db: var TDbConn) = proc Close*(db: var TDbConn) {.tags: [FDB].} =
## closes the database connection. ## closes the database connection.
disconnect(db) disconnect(db)
destroy(db) destroy(db)
proc Open*(host: string = defaultHost, port: int = defaultPort): TDbConn = proc Open*(host: string = defaultHost, port: int = defaultPort): TDbConn {.
tags: [FDB].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. ## be established.
init(result) init(result)
@ -108,7 +113,8 @@ proc getId*(obj: var TBSon): TOid =
else: else:
raise newException(EInvalidIndex, "_id not in object") raise newException(EInvalidIndex, "_id not in object")
proc insertID*(db: var TDbConn, namespace: string, data: PJsonNode): TOid = proc insertID*(db: var TDbConn, namespace: string, data: PJsonNode): TOid {.
tags: [FWriteDb].} =
## converts `data` to BSON format and inserts it in `namespace`. Returns ## converts `data` to BSON format and inserts it in `namespace`. Returns
## the generated OID for the ``_id`` field. ## the generated OID for the ``_id`` field.
result = genOid() result = genOid()
@ -116,11 +122,13 @@ proc insertID*(db: var TDbConn, namespace: string, data: PJsonNode): TOid =
insert(db, namespace, x) insert(db, namespace, x)
destroy(x) destroy(x)
proc insert*(db: var TDbConn, namespace: string, data: PJsonNode) = proc insert*(db: var TDbConn, namespace: string, data: PJsonNode) {.
tags: [FWriteDb].} =
## converts `data` to BSON format and inserts it in `namespace`. ## converts `data` to BSON format and inserts it in `namespace`.
discard InsertID(db, namespace, data) discard InsertID(db, namespace, data)
proc update*(db: var TDbConn, namespace: string, obj: var TBSon) = proc update*(db: var TDbConn, namespace: string, obj: var TBSon) {.
tags: [FReadDB, FWriteDb].} =
## updates `obj` in `namespace`. ## updates `obj` in `namespace`.
var cond: TBson var cond: TBson
init(cond) init(cond)
@ -129,13 +137,15 @@ proc update*(db: var TDbConn, namespace: string, obj: var TBSon) =
update(db, namespace, cond, obj, ord(UPDATE_UPSERT)) update(db, namespace, cond, obj, ord(UPDATE_UPSERT))
destroy(cond) destroy(cond)
proc update*(db: var TDbConn, namespace: string, oid: TOid, obj: PJsonNode) = proc update*(db: var TDbConn, namespace: string, oid: TOid, obj: PJsonNode) {.
tags: [FReadDB, FWriteDb].} =
## updates the data with `oid` to have the new data `obj`. ## updates the data with `oid` to have the new data `obj`.
var a = jsonToBSon(obj, oid) var a = jsonToBSon(obj, oid)
Update(db, namespace, a) Update(db, namespace, a)
destroy(a) destroy(a)
proc delete*(db: var TDbConn, namespace: string, oid: TOid) = proc delete*(db: var TDbConn, namespace: string, oid: TOid) {.
tags: [FWriteDb].} =
## Deletes the object belonging to `oid`. ## Deletes the object belonging to `oid`.
var cond: TBson var cond: TBson
init(cond) init(cond)
@ -144,11 +154,13 @@ proc delete*(db: var TDbConn, namespace: string, oid: TOid) =
discard remove(db, namespace, cond) discard remove(db, namespace, cond)
destroy(cond) destroy(cond)
proc delete*(db: var TDbConn, namespace: string, obj: var TBSon) = proc delete*(db: var TDbConn, namespace: string, obj: var TBSon) {.
tags: [FWriteDb].} =
## Deletes the object `obj`. ## Deletes the object `obj`.
delete(db, namespace, getId(obj)) delete(db, namespace, getId(obj))
iterator find*(db: var TDbConn, namespace: string): var TBSon = iterator find*(db: var TDbConn, namespace: string): var TBSon {.
tags: [FReadDB].} =
## iterates over any object in `namespace`. ## iterates over any object in `namespace`.
var cursor: TCursor var cursor: TCursor
init(cursor, db, namespace) init(cursor, db, namespace)
@ -157,7 +169,7 @@ iterator find*(db: var TDbConn, namespace: string): var TBSon =
destroy(cursor) destroy(cursor)
iterator find*(db: var TDbConn, namespace: string, iterator find*(db: var TDbConn, namespace: string,
query, fields: var TBSon): var TBSon = query, fields: var TBSon): var TBSon {.tags: [FReadDB].} =
## yields the `fields` of any document that suffices `query`. ## yields the `fields` of any document that suffices `query`.
var cursor = find(db, namespace, query, fields, 0'i32, 0'i32, 0'i32) var cursor = find(db, namespace, query, fields, 0'i32, 0'i32, 0'i32)
if cursor != nil: if cursor != nil:
@ -171,7 +183,8 @@ proc setupFieldnames(fields: varargs[string]): TBSon =
finish(result) finish(result)
iterator find*(db: var TDbConn, namespace: string, iterator find*(db: var TDbConn, namespace: string,
query: var TBSon, fields: varargs[string]): var TBSon = query: var TBSon, fields: varargs[string]): var TBSon {.
tags: [FReadDB].} =
## yields the `fields` of any document that suffices `query`. If `fields` ## yields the `fields` of any document that suffices `query`. If `fields`
## is ``[]`` the whole document is yielded. ## is ``[]`` the whole document is yielded.
var f = setupFieldnames(fields) var f = setupFieldnames(fields)
@ -188,7 +201,8 @@ proc setupQuery(query: string): TBSon =
finish(result) finish(result)
iterator find*(db: var TDbConn, namespace: string, iterator find*(db: var TDbConn, namespace: string,
query: string, fields: varargs[string]): var TBSon = query: string, fields: varargs[string]): var TBSon {.
tags: [FReadDB].} =
## yields the `fields` of any document that suffices `query`. If `fields` ## yields the `fields` of any document that suffices `query`. If `fields`
## is ``[]`` the whole document is yielded. ## is ``[]`` the whole document is yielded.
var f = setupFieldnames(fields) var f = setupFieldnames(fields)

View file

@ -18,7 +18,11 @@ type
EDb* = object of EIO ## exception that is raised if a database error occurs EDb* = object of EIO ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string TSqlQuery* = distinct string ## an SQL query string
FDb* = object of FIO ## effect that denotes a database operation
FReadDb* = object of FReadIO ## effect that denotes a read operation
FWriteDb* = object of FWriteIO ## effect that denotes a write operation
proc dbError(db: TDbConn) {.noreturn.} = proc dbError(db: TDbConn) {.noreturn.} =
## raises an EDb exception. ## raises an EDb exception.
var e: ref EDb var e: ref EDb
@ -60,12 +64,18 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
else: else:
add(result, c) add(result, c)
proc TryExec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): bool = 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. ## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args) var q = dbFormat(query, args)
return mysql.RealQuery(db, q, q.len) == 0'i32 return mysql.RealQuery(db, q, q.len) == 0'i32
proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) = proc rawExec(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) =
var q = dbFormat(query, args)
if mysql.RealQuery(db, q, q.len) != 0'i32: dbError(db)
proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
var q = dbFormat(query, args) var q = dbFormat(query, args)
if mysql.RealQuery(db, q, q.len) != 0'i32: dbError(db) if mysql.RealQuery(db, q, q.len) != 0'i32: dbError(db)
@ -80,11 +90,11 @@ proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
mysql.FreeResult(sqlres) mysql.FreeResult(sqlres)
iterator FastRows*(db: TDbConn, query: TSqlQuery, iterator FastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For MySQL this is the case!. ## query, the results can be undefined. For MySQL this is the case!.
Exec(db, query, args) rawExec(db, query, args)
var sqlres = mysql.UseResult(db) var sqlres = mysql.UseResult(db)
if sqlres != nil: if sqlres != nil:
var L = int(mysql.NumFields(sqlres)) var L = int(mysql.NumFields(sqlres))
@ -100,9 +110,9 @@ iterator FastRows*(db: TDbConn, query: TSqlQuery,
properFreeResult(sqlres, row) properFreeResult(sqlres, row)
proc getRow*(db: TDbConn, query: TSqlQuery, proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## retrieves a single row. ## retrieves a single row.
Exec(db, query, args) rawExec(db, query, args)
var sqlres = mysql.UseResult(db) var sqlres = mysql.UseResult(db)
if sqlres != nil: if sqlres != nil:
var L = int(mysql.NumFields(sqlres)) var L = int(mysql.NumFields(sqlres))
@ -115,10 +125,10 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
properFreeResult(sqlres, row) properFreeResult(sqlres, row)
proc GetAllRows*(db: TDbConn, query: TSqlQuery, proc GetAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] = args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
Exec(db, query, args) rawExec(db, query, args)
var sqlres = mysql.UseResult(db) var sqlres = mysql.UseResult(db)
if sqlres != nil: if sqlres != nil:
var L = int(mysql.NumFields(sqlres)) var L = int(mysql.NumFields(sqlres))
@ -134,12 +144,12 @@ proc GetAllRows*(db: TDbConn, query: TSqlQuery,
mysql.FreeResult(sqlres) mysql.FreeResult(sqlres)
iterator Rows*(db: TDbConn, query: TSqlQuery, iterator Rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## same as `FastRows`, but slower and safe. ## same as `FastRows`, but slower and safe.
for r in items(GetAllRows(db, query, args)): yield r for r in items(GetAllRows(db, query, args)): yield r
proc GetValue*(db: TDbConn, query: TSqlQuery, proc GetValue*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): string = args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the result dataset's the first column ## executes the query and returns the result dataset's the first column
## of the first row. Returns "" if the dataset contains no rows. This uses ## of the first row. Returns "" if the dataset contains no rows. This uses
## `FastRows`, so it inherits its fragile behaviour. ## `FastRows`, so it inherits its fragile behaviour.
@ -149,7 +159,7 @@ proc GetValue*(db: TDbConn, query: TSqlQuery,
break break
proc TryInsertID*(db: TDbConn, query: TSqlQuery, proc TryInsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error. ## generated ID for the row or -1 in case of an error.
var q = dbFormat(query, args) var q = dbFormat(query, args)
@ -159,24 +169,26 @@ proc TryInsertID*(db: TDbConn, query: TSqlQuery,
result = mysql.InsertId(db) result = mysql.InsertId(db)
proc InsertID*(db: TDbConn, query: TSqlQuery, proc InsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. ## generated ID for the row.
result = TryInsertID(db, query, args) result = TryInsertID(db, query, args)
if result < 0: dbError(db) if result < 0: dbError(db)
proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.
tags: [FReadDB, FWriteDb].} =
## runs the query (typically "UPDATE") and returns the ## runs the query (typically "UPDATE") and returns the
## number of affected rows ## number of affected rows
Exec(db, query, args) rawExec(db, query, args)
result = mysql.AffectedRows(db) result = mysql.AffectedRows(db)
proc Close*(db: TDbConn) = proc Close*(db: TDbConn) {.tags: [FDb].} =
## closes the database connection. ## closes the database connection.
if db != nil: mysql.Close(db) if db != nil: mysql.Close(db)
proc Open*(connection, user, password, database: string): TDbConn = proc Open*(connection, user, password, database: string): TDbConn {.
tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. ## be established.
result = mysql.Init(nil) result = mysql.Init(nil)

View file

@ -18,6 +18,10 @@ type
EDb* = object of EIO ## exception that is raised if a database error occurs EDb* = object of EIO ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string TSqlQuery* = distinct string ## an SQL query string
FDb* = object of FIO ## effect that denotes a database operation
FReadDb* = object of FReadIO ## effect that denotes a read operation
FWriteDb* = object of FWriteIO ## 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 ## constructs a TSqlQuery from the string `query`. This is supposed to be
@ -60,14 +64,15 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
add(result, c) add(result, c)
proc TryExec*(db: TDbConn, query: TSqlQuery, proc TryExec*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): bool = args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args) var q = dbFormat(query, args)
var res = PQExec(db, q) var res = PQExec(db, q)
result = PQresultStatus(res) == PGRES_COMMAND_OK result = PQresultStatus(res) == PGRES_COMMAND_OK
PQclear(res) PQclear(res)
proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) = proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
var q = dbFormat(query, args) var q = dbFormat(query, args)
var res = PQExec(db, q) var res = PQExec(db, q)
@ -91,7 +96,7 @@ proc setRow(res: PPGresult, r: var TRow, line, cols: int32) =
add(r[col], x) add(r[col], x)
iterator FastRows*(db: TDbConn, query: TSqlQuery, iterator FastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Postgres it is safe though. ## query, the results can be undefined. For Postgres it is safe though.
@ -104,7 +109,7 @@ iterator FastRows*(db: TDbConn, query: TSqlQuery,
PQclear(res) PQclear(res)
proc getRow*(db: TDbConn, query: TSqlQuery, proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## retrieves a single row. ## retrieves a single row.
var res = setupQuery(db, query, args) var res = setupQuery(db, query, args)
var L = PQnfields(res) var L = PQnfields(res)
@ -113,38 +118,39 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
PQclear(res) PQclear(res)
proc GetAllRows*(db: TDbConn, query: TSqlQuery, proc GetAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] = args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
for r in FastRows(db, query, args): for r in FastRows(db, query, args):
result.add(r) result.add(r)
iterator Rows*(db: TDbConn, query: TSqlQuery, iterator Rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## same as `FastRows`, but slower and safe. ## same as `FastRows`, but slower and safe.
for r in items(GetAllRows(db, query, args)): yield r for r in items(GetAllRows(db, query, args)): yield r
proc GetValue*(db: TDbConn, query: TSqlQuery, proc GetValue*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): string = args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the result dataset's the first column ## executes the query and returns the result dataset's the first column
## of the first row. Returns "" if the dataset contains no rows. ## of the first row. Returns "" if the dataset contains no rows.
var x = PQgetvalue(setupQuery(db, query, args), 0, 0) var x = PQgetvalue(setupQuery(db, query, args), 0, 0)
result = if isNil(x): "" else: $x result = if isNil(x): "" else: $x
proc TryInsertID*(db: TDbConn, query: TSqlQuery, proc TryInsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].}=
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error. For Postgre this adds ## 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 ## ``RETURNING id`` to the query, so it only works if your primary key is
## named ``id``. ## named ``id``.
var val = GetValue(db, TSqlQuery(string(query) & " RETURNING id"), args) var x = PQgetvalue(setupQuery(db, TSqlQuery(string(query) & " RETURNING id"),
if val.len > 0: args), 0, 0)
result = ParseBiggestInt(val) if not isNil(x):
result = ParseBiggestInt($x)
else: else:
result = -1 result = -1
proc InsertID*(db: TDbConn, query: TSqlQuery, proc InsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds ## generated ID for the row. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is ## ``RETURNING id`` to the query, so it only works if your primary key is
@ -153,7 +159,8 @@ proc InsertID*(db: TDbConn, query: TSqlQuery,
if result < 0: dbError(db) if result < 0: dbError(db)
proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [
FReadDB, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the ## executes the query (typically "UPDATE") and returns the
## number of affected rows. ## number of affected rows.
var q = dbFormat(query, args) var q = dbFormat(query, args)
@ -162,11 +169,12 @@ proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery,
result = parseBiggestInt($PQcmdTuples(res)) result = parseBiggestInt($PQcmdTuples(res))
PQclear(res) PQclear(res)
proc Close*(db: TDbConn) = proc Close*(db: TDbConn) {.tags: [FDb].} =
## closes the database connection. ## closes the database connection.
if db != nil: PQfinish(db) if db != nil: PQfinish(db)
proc Open*(connection, user, password, database: string): TDbConn = proc Open*(connection, user, password, database: string): TDbConn {.
tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. ## be established.
result = PQsetdbLogin(nil, nil, nil, nil, database, user, password) result = PQsetdbLogin(nil, nil, nil, nil, database, user, password)

View file

@ -19,6 +19,10 @@ type
TSqlQuery* = distinct string ## an SQL query string TSqlQuery* = distinct string ## an SQL query string
FDb* = object of FIO ## effect that denotes a database operation
FReadDb* = object of FReadIO ## effect that denotes a read operation
FWriteDb* = object of FWriteIO ## 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 ## constructs a TSqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier: ## used as a raw-string-literal modifier:
@ -60,7 +64,7 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
add(result, c) add(result, c)
proc TryExec*(db: TDbConn, query: TSqlQuery, proc TryExec*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): bool = args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args) var q = dbFormat(query, args)
var stmt: sqlite3.PStmt var stmt: sqlite3.PStmt
@ -68,7 +72,8 @@ proc TryExec*(db: TDbConn, query: TSqlQuery,
if step(stmt) == SQLITE_DONE: if step(stmt) == SQLITE_DONE:
result = finalize(stmt) == SQLITE_OK result = finalize(stmt) == SQLITE_OK
proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) = proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
if not TryExec(db, query, args): dbError(db) if not TryExec(db, query, args): dbError(db)
@ -89,7 +94,7 @@ proc setRow(stmt: PStmt, r: var TRow, cols: cint) =
if not isNil(x): add(r[col], x) if not isNil(x): add(r[col], x)
iterator FastRows*(db: TDbConn, query: TSqlQuery, iterator FastRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Sqlite it is safe though. ## query, the results can be undefined. For Sqlite it is safe though.
@ -102,7 +107,7 @@ iterator FastRows*(db: TDbConn, query: TSqlQuery,
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
proc getRow*(db: TDbConn, query: TSqlQuery, proc getRow*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## retrieves a single row. ## retrieves a single row.
var stmt = setupQuery(db, query, args) var stmt = setupQuery(db, query, args)
var L = (columnCount(stmt)) var L = (columnCount(stmt))
@ -112,19 +117,19 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
proc GetAllRows*(db: TDbConn, query: TSqlQuery, proc GetAllRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): seq[TRow] = args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
for r in FastRows(db, query, args): for r in FastRows(db, query, args):
result.add(r) result.add(r)
iterator Rows*(db: TDbConn, query: TSqlQuery, iterator Rows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): TRow = args: varargs[string, `$`]): TRow {.tags: [FReadDB].} =
## same as `FastRows`, but slower and safe. ## same as `FastRows`, but slower and safe.
for r in FastRows(db, query, args): yield r for r in FastRows(db, query, args): yield r
proc GetValue*(db: TDbConn, query: TSqlQuery, proc GetValue*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): string = args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the result dataset's the first column ## executes the query and returns the result dataset's the first column
## of the first row. Returns "" if the dataset contains no rows. ## of the first row. Returns "" if the dataset contains no rows.
var stmt = setupQuery(db, query, args) var stmt = setupQuery(db, query, args)
@ -140,16 +145,19 @@ proc GetValue*(db: TDbConn, query: TSqlQuery,
result = "" result = ""
proc TryInsertID*(db: TDbConn, query: TSqlQuery, proc TryInsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error. ## generated ID for the row or -1 in case of an error.
if tryExec(db, query, args): var q = dbFormat(query, args)
result = last_insert_rowid(db) var stmt: sqlite3.PStmt
else: if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK:
result = -1 if step(stmt) == SQLITE_DONE:
if finalize(stmt) == SQLITE_OK:
return last_insert_rowid(db)
result = -1
proc InsertID*(db: TDbConn, query: TSqlQuery, proc InsertID*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds ## generated ID for the row. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is ## ``RETURNING id`` to the query, so it only works if your primary key is
@ -158,17 +166,19 @@ proc InsertID*(db: TDbConn, query: TSqlQuery,
if result < 0: dbError(db) if result < 0: dbError(db)
proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery,
args: varargs[string, `$`]): int64 = args: varargs[string, `$`]): int64 {.
tags: [FReadDB, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the ## executes the query (typically "UPDATE") and returns the
## number of affected rows. ## number of affected rows.
Exec(db, query, args) Exec(db, query, args)
result = changes(db) result = changes(db)
proc Close*(db: TDbConn) = proc Close*(db: TDbConn) {.tags: [FDB].} =
## closes the database connection. ## closes the database connection.
if sqlite3.close(db) != SQLITE_OK: dbError(db) if sqlite3.close(db) != SQLITE_OK: dbError(db)
proc Open*(connection, user, password, database: string): TDbConn = proc Open*(connection, user, password, database: string): TDbConn {.
tags: [FDB].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. Only the ``connection`` parameter is used for ``sqlite``. ## be established. Only the ``connection`` parameter is used for ``sqlite``.
var db: TDbConn var db: TDbConn

View file

@ -14,12 +14,14 @@
## wanted functionality. ## wanted functionality.
when defined(Windows): when defined(Windows):
proc ReadLineFromStdin*(prompt: string): TaintedString = proc ReadLineFromStdin*(prompt: string): TaintedString {.
tags: [FReadIO, FWriteIO].} =
## Reads a line from stdin. ## Reads a line from stdin.
stdout.write(prompt) stdout.write(prompt)
result = readLine(stdin) result = readLine(stdin)
proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool = proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool {.
tags: [FReadIO, FWriteIO].} =
## Reads a `line` from stdin. `line` must not be ## Reads a `line` from stdin. `line` must not be
## ``nil``! May throw an IO exception. ## ``nil``! May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## A line of text may be delimited by ``CR``, ``LF`` or
@ -32,7 +34,8 @@ when defined(Windows):
else: else:
import readline, history import readline, history
proc ReadLineFromStdin*(prompt: string): TaintedString = proc ReadLineFromStdin*(prompt: string): TaintedString {.
tags: [FReadIO, FWriteIO].} =
var buffer = readline.readLine(prompt) var buffer = readline.readLine(prompt)
if isNil(buffer): quit(0) if isNil(buffer): quit(0)
result = TaintedString($buffer) result = TaintedString($buffer)
@ -40,7 +43,8 @@ else:
add_history(buffer) add_history(buffer)
readline.free(buffer) readline.free(buffer)
proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool = proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool {.
tags: [FReadIO, FWriteIO].} =
var buffer = readline.readLine(prompt) var buffer = readline.readLine(prompt)
if isNil(buffer): quit(0) if isNil(buffer): quit(0)
line = TaintedString($buffer) line = TaintedString($buffer)

View file

@ -58,5 +58,5 @@ proc URLretrieveString*(url: string): TaintedString =
result = stream.data.TaintedString result = stream.data.TaintedString
when isMainModule: when isMainModule:
echo URLretrieveString("http://nimrod.ethexor.com/") echo URLretrieveString("http://nimrod-code.org/")

View file

@ -43,11 +43,12 @@ type
proc execProcess*(command: string, proc execProcess*(command: string,
options: set[TProcessOption] = {poStdErrToStdOut, options: set[TProcessOption] = {poStdErrToStdOut,
poUseShell}): TaintedString {. poUseShell}): TaintedString {.
rtl, extern: "nosp$1".} rtl, extern: "nosp$1",
tags: [FExecIO, FReadIO].}
## A convenience procedure that executes ``command`` with ``startProcess`` ## A convenience procedure that executes ``command`` with ``startProcess``
## and returns its output as a string. ## and returns its output as a string.
proc execCmd*(command: string): int {.rtl, extern: "nosp$1".} proc execCmd*(command: string): int {.rtl, extern: "nosp$1", tags: [FExecIO].}
## Executes ``command`` and returns its error code. Standard input, output, ## Executes ``command`` and returns its error code. Standard input, output,
## error streams are inherited from the calling process. This operation ## error streams are inherited from the calling process. This operation
## is also often called `system`:idx:. ## is also often called `system`:idx:.
@ -57,7 +58,7 @@ proc startProcess*(command: string,
args: openarray[string] = [], args: openarray[string] = [],
env: PStringTable = nil, env: PStringTable = nil,
options: set[TProcessOption] = {poStdErrToStdOut}): options: set[TProcessOption] = {poStdErrToStdOut}):
PProcess {.rtl, extern: "nosp$1".} PProcess {.rtl, extern: "nosp$1", tags: [FExecIO].}
## Starts a process. `Command` is the executable file, `workingDir` is the ## Starts a process. `Command` is the executable file, `workingDir` is the
## process's working directory. If ``workingDir == ""`` the current directory ## process's working directory. If ``workingDir == ""`` the current directory
## is used. `args` are the command line arguments that are passed to the ## is used. `args` are the command line arguments that are passed to the
@ -73,7 +74,7 @@ proc startProcess*(command: string,
## but ``EOS`` is raised in case of an error. ## but ``EOS`` is raised in case of an error.
proc startCmd*(command: string, options: set[TProcessOption] = { proc startCmd*(command: string, options: set[TProcessOption] = {
poStdErrToStdOut, poUseShell}): PProcess = poStdErrToStdOut, poUseShell}): PProcess {.tags: [FExecIO].} =
## a simpler version of `startProcess` that parses the command line into ## a simpler version of `startProcess` that parses the command line into
## program and arguments and then calls `startProcess` with the empty string ## program and arguments and then calls `startProcess` with the empty string
## for `workingDir` and the nil string table for `env`. ## for `workingDir` and the nil string table for `env`.
@ -83,38 +84,39 @@ proc startCmd*(command: string, options: set[TProcessOption] = {
for i in 1 .. c.len-1: a[i-1] = c[i] for i in 1 .. c.len-1: a[i-1] = c[i]
result = startProcess(command=c[0], args=a, options=options) result = startProcess(command=c[0], args=a, options=options)
proc close*(p: PProcess) {.rtl, extern: "nosp$1".} proc close*(p: PProcess) {.rtl, extern: "nosp$1", tags: [].}
## When the process has finished executing, cleanup related handles ## When the process has finished executing, cleanup related handles
proc suspend*(p: PProcess) {.rtl, extern: "nosp$1".} proc suspend*(p: PProcess) {.rtl, extern: "nosp$1", tags: [].}
## Suspends the process `p`. ## Suspends the process `p`.
proc resume*(p: PProcess) {.rtl, extern: "nosp$1".} proc resume*(p: PProcess) {.rtl, extern: "nosp$1", tags: [].}
## Resumes the process `p`. ## Resumes the process `p`.
proc terminate*(p: PProcess) {.rtl, extern: "nosp$1".} proc terminate*(p: PProcess) {.rtl, extern: "nosp$1", tags: [].}
## Terminates the process `p`. ## Terminates the process `p`.
proc running*(p: PProcess): bool {.rtl, extern: "nosp$1".} proc running*(p: PProcess): bool {.rtl, extern: "nosp$1", tags: [].}
## Returns true iff the process `p` is still running. Returns immediately. ## Returns true iff the process `p` is still running. Returns immediately.
proc processID*(p: PProcess): int {.rtl, extern: "nosp$1".} = proc processID*(p: PProcess): int {.rtl, extern: "nosp$1".} =
## returns `p`'s process ID. ## returns `p`'s process ID.
return p.id return p.id
proc waitForExit*(p: PProcess, timeout: int = -1): int {.rtl, extern: "nosp$1".} proc waitForExit*(p: PProcess, timeout: int = -1): int {.rtl,
extern: "nosp$1", tags: [].}
## waits for the process to finish and returns `p`'s error code. ## waits for the process to finish and returns `p`'s error code.
proc peekExitCode*(p: PProcess): int proc peekExitCode*(p: PProcess): int {.tags: [].}
## return -1 if the process is still running. Otherwise the process' exit code ## return -1 if the process is still running. Otherwise the process' exit code
proc inputStream*(p: PProcess): PStream {.rtl, extern: "nosp$1".} proc inputStream*(p: PProcess): PStream {.rtl, extern: "nosp$1", tags: [].}
## opens ``p``'s input stream for writing to ## opens ``p``'s input stream for writing to
proc outputStream*(p: PProcess): PStream {.rtl, extern: "nosp$1".} proc outputStream*(p: PProcess): PStream {.rtl, extern: "nosp$1", tags: [].}
## opens ``p``'s output stream for reading from ## opens ``p``'s output stream for reading from
proc errorStream*(p: PProcess): PStream {.rtl, extern: "nosp$1".} proc errorStream*(p: PProcess): PStream {.rtl, extern: "nosp$1", tags: [].}
## opens ``p``'s output stream for reading from ## opens ``p``'s output stream for reading from
when defined(macosx) or defined(bsd): when defined(macosx) or defined(bsd):
@ -156,7 +158,8 @@ proc countProcessors*(): int {.rtl, extern: "nosp$1".} =
proc execProcesses*(cmds: openArray[string], proc execProcesses*(cmds: openArray[string],
options = {poStdErrToStdOut, poParentStreams}, options = {poStdErrToStdOut, poParentStreams},
n = countProcessors()): int {.rtl, extern: "nosp$1".} = n = countProcessors()): int {.rtl, extern: "nosp$1",
tags: [FExecIO].} =
## executes the commands `cmds` in parallel. Creates `n` processes ## executes the commands `cmds` in parallel. Creates `n` processes
## that execute in parallel. The highest return value of all processes ## that execute in parallel. The highest return value of all processes
## is returned. ## is returned.
@ -731,7 +734,7 @@ elif not defined(useNimRtl):
proc execCmdEx*(command: string, options: set[TProcessOption] = { proc execCmdEx*(command: string, options: set[TProcessOption] = {
poStdErrToStdOut, poUseShell}): tuple[ poStdErrToStdOut, poUseShell}): tuple[
output: TaintedString, output: TaintedString,
exitCode: int] = exitCode: int] {.tags: [FExecIO, FReadIO].} =
## a convenience proc that runs the `command`, grabs all its output and ## a convenience proc that runs the `command`, grabs all its output and
## exit code and returns both. ## exit code and returns both.
var p = startCmd(command, options) var p = startCmd(command, options)

View file

@ -23,14 +23,15 @@ type
## here shouldn't be used directly. They are ## here shouldn't be used directly. They are
## accessible so that a stream implementation ## accessible so that a stream implementation
## can override them. ## can override them.
closeImpl*: proc (s: PStream) {.nimcall.} closeImpl*: proc (s: PStream) {.nimcall, tags: [].}
atEndImpl*: proc (s: PStream): bool {.nimcall.} atEndImpl*: proc (s: PStream): bool {.nimcall, tags: [].}
setPositionImpl*: proc (s: PStream, pos: int) {.nimcall.} setPositionImpl*: proc (s: PStream, pos: int) {.nimcall, tags: [].}
getPositionImpl*: proc (s: PStream): int {.nimcall.} getPositionImpl*: proc (s: PStream): int {.nimcall, tags: [].}
readDataImpl*: proc (s: PStream, buffer: pointer, readDataImpl*: proc (s: PStream, buffer: pointer,
bufLen: int): int {.nimcall.} bufLen: int): int {.nimcall, tags: [FReadIO].}
writeDataImpl*: proc (s: PStream, buffer: pointer, bufLen: int) {.nimcall.} writeDataImpl*: proc (s: PStream, buffer: pointer, bufLen: int) {.nimcall,
flushImpl*: proc (s: PStream) {.nimcall.} tags: [FWriteIO].}
flushImpl*: proc (s: PStream) {.nimcall, tags: [FWriteIO].}
proc flush*(s: PStream) = proc flush*(s: PStream) =
## flushes the buffers that the stream `s` might use. ## flushes the buffers that the stream `s` might use.

View file

@ -1080,21 +1080,21 @@ proc equalMem*(a, b: Pointer, size: int): bool {.
## otherwise. Like any procedure dealing with raw memory this is ## otherwise. Like any procedure dealing with raw memory this is
## *unsafe*. ## *unsafe*.
proc alloc*(size: int): pointer {.noconv, rtl.} proc alloc*(size: int): pointer {.noconv, rtl, tags: [].}
## allocates a new memory block with at least ``size`` bytes. The ## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or ## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is not initialized, so reading ## ``dealloc(block)``. The block is not initialized, so reading
## from it before writing to it is undefined behaviour! ## from it before writing to it is undefined behaviour!
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `allocShared` to allocate from a shared heap. ## Use `allocShared` to allocate from a shared heap.
proc alloc0*(size: int): pointer {.noconv, rtl.} proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].}
## allocates a new memory block with at least ``size`` bytes. The ## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or ## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is initialized with all bytes ## ``dealloc(block)``. The block is initialized with all bytes
## containing zero, so it is somewhat safer than ``alloc``. ## containing zero, so it is somewhat safer than ``alloc``.
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `allocShared0` to allocate from a shared heap. ## Use `allocShared0` to allocate from a shared heap.
proc realloc*(p: Pointer, newsize: int): pointer {.noconv, rtl.} proc realloc*(p: Pointer, newsize: int): pointer {.noconv, rtl, tags: [].}
## grows or shrinks a given memory block. If p is **nil** then a new ## grows or shrinks a given memory block. If p is **nil** then a new
## memory block is returned. In either way the block has at least ## memory block is returned. In either way the block has at least
## ``newsize`` bytes. If ``newsize == 0`` and p is not **nil** ## ``newsize`` bytes. If ``newsize == 0`` and p is not **nil**
@ -1102,7 +1102,7 @@ proc realloc*(p: Pointer, newsize: int): pointer {.noconv, rtl.}
## be freed with ``dealloc``. ## be freed with ``dealloc``.
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `reallocShared` to reallocate from a shared heap. ## Use `reallocShared` to reallocate from a shared heap.
proc dealloc*(p: Pointer) {.noconv, rtl.} proc dealloc*(p: Pointer) {.noconv, rtl, tags: [].}
## frees the memory allocated with ``alloc``, ``alloc0`` or ## frees the memory allocated with ``alloc``, ``alloc0`` or
## ``realloc``. This procedure is dangerous! If one forgets to ## ``realloc``. This procedure is dangerous! If one forgets to
## free the memory a leak occurs; if one tries to access freed ## free the memory a leak occurs; if one tries to access freed
@ -1618,7 +1618,7 @@ var
## do when setting this. If ``localRaiseHook`` returns false, the exception ## do when setting this. If ``localRaiseHook`` returns false, the exception
## is caught and does not propagate further through the call stack. ## is caught and does not propagate further through the call stack.
outOfMemHook*: proc () {.nimcall.} outOfMemHook*: proc () {.nimcall, tags: [].}
## set this variable to provide a procedure that should be called ## set this variable to provide a procedure that should be called
## in case of an `out of memory`:idx: event. The standard handler ## in case of an `out of memory`:idx: event. The standard handler
## writes an error message and terminates the program. `outOfMemHook` can ## writes an error message and terminates the program. `outOfMemHook` can
@ -1668,7 +1668,7 @@ else:
proc add*(x: var cstring, y: cstring) {.magic: "AppendStrStr".} proc add*(x: var cstring, y: cstring) {.magic: "AppendStrStr".}
proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo".} proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO].}
## special built-in that takes a variable number of arguments. Each argument ## special built-in that takes a variable number of arguments. Each argument
## is converted to a string via ``$``, so it works for user-defined ## is converted to a string via ``$``, so it works for user-defined
## types that have an overloaded ``$`` operator. ## types that have an overloaded ``$`` operator.
@ -1677,7 +1677,8 @@ proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo".}
## Unlike other IO operations this is guaranteed to be thread-safe as ## Unlike other IO operations this is guaranteed to be thread-safe as
## ``echo`` is very often used for debugging convenience. ## ``echo`` is very often used for debugging convenience.
proc debugEcho*[T](x: varargs[T, `$`]) {.magic: "Echo", noSideEffect.} proc debugEcho*[T](x: varargs[T, `$`]) {.magic: "Echo", noSideEffect,
tags: [], raises: [].}
## Same as ``echo``, but as a special semantic rule, ``debugEcho`` pretends ## Same as ``echo``, but as a special semantic rule, ``debugEcho`` pretends
## to be free of side effects, so that it can be used for debugging routines ## to be free of side effects, so that it can be used for debugging routines
## marked as ``noSideEffect``. ## marked as ``noSideEffect``.
@ -1780,14 +1781,14 @@ when not defined(EcmaScript) and not defined(NimrodVM):
## to ``stderr``. ## to ``stderr``.
proc Open*(f: var TFile, filename: string, proc Open*(f: var TFile, filename: string,
mode: TFileMode = fmRead, bufSize: int = -1): Bool mode: TFileMode = fmRead, bufSize: int = -1): Bool {.tags: [].}
## Opens a file named `filename` with given `mode`. ## Opens a file named `filename` with given `mode`.
## ##
## Default mode is readonly. Returns true iff the file could be opened. ## Default mode is readonly. Returns true iff the file could be opened.
## This throws no exception if the file could not be opened. ## This throws no exception if the file could not be opened.
proc Open*(f: var TFile, filehandle: TFileHandle, proc Open*(f: var TFile, filehandle: TFileHandle,
mode: TFileMode = fmRead): Bool mode: TFileMode = fmRead): Bool {.tags: [].}
## Creates a ``TFile`` from a `filehandle` with given `mode`. ## Creates a ``TFile`` from a `filehandle` with given `mode`.
## ##
## Default mode is readonly. Returns true iff the file could be opened. ## Default mode is readonly. Returns true iff the file could be opened.
@ -1801,56 +1802,57 @@ when not defined(EcmaScript) and not defined(NimrodVM):
if not open(result, filename, mode, bufSize): if not open(result, filename, mode, bufSize):
raise newException(EIO, "cannot open: " & filename) raise newException(EIO, "cannot open: " & filename)
proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {.
tags: [].}
## reopens the file `f` with given `filename` and `mode`. This ## reopens the file `f` with given `filename` and `mode`. This
## is often used to redirect the `stdin`, `stdout` or `stderr` ## is often used to redirect the `stdin`, `stdout` or `stderr`
## file variables. ## file variables.
## ##
## Default mode is readonly. Returns true iff the file could be reopened. ## Default mode is readonly. Returns true iff the file could be reopened.
proc Close*(f: TFile) {.importc: "fclose", nodecl.} proc Close*(f: TFile) {.importc: "fclose", nodecl, tags: [].}
## Closes the file. ## Closes the file.
proc EndOfFile*(f: TFile): Bool proc EndOfFile*(f: TFile): Bool {.tags: [].}
## Returns true iff `f` is at the end. ## Returns true iff `f` is at the end.
proc readChar*(f: TFile): char {.importc: "fgetc", nodecl.} proc readChar*(f: TFile): char {.importc: "fgetc", nodecl, tags: [FReadIO].}
## Reads a single character from the stream `f`. If the stream ## Reads a single character from the stream `f`. If the stream
## has no more characters, `EEndOfFile` is raised. ## has no more characters, `EEndOfFile` is raised.
proc FlushFile*(f: TFile) {.importc: "fflush", noDecl.} proc FlushFile*(f: TFile) {.importc: "fflush", noDecl, tags: [FWriteIO].}
## Flushes `f`'s buffer. ## Flushes `f`'s buffer.
proc readAll*(file: TFile): TaintedString proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].}
## Reads all data from the stream `file`. Raises an IO exception ## Reads all data from the stream `file`. Raises an IO exception
## in case of an error ## in case of an error
proc readFile*(filename: string): TaintedString proc readFile*(filename: string): TaintedString {.tags: [FReadIO].}
## Opens a file named `filename` for reading. Then calls `readAll` ## Opens a file named `filename` for reading. Then calls `readAll`
## and closes the file afterwards. Returns the string. ## and closes the file afterwards. Returns the string.
## Raises an IO exception in case of an error. ## Raises an IO exception in case of an error.
proc writeFile*(filename, content: string) proc writeFile*(filename, content: string) {.tags: [FWriteIO].}
## Opens a file named `filename` for writing. Then writes the ## Opens a file named `filename` for writing. Then writes the
## `content` completely to the file and closes the file afterwards. ## `content` completely to the file and closes the file afterwards.
## Raises an IO exception in case of an error. ## Raises an IO exception in case of an error.
proc write*(f: TFile, r: float) proc write*(f: TFile, r: float) {.tags: [FWriteIO].}
proc write*(f: TFile, i: int) proc write*(f: TFile, i: int) {.tags: [FWriteIO].}
proc write*(f: TFile, i: biggestInt) proc write*(f: TFile, i: biggestInt) {.tags: [FWriteIO].}
proc write*(f: TFile, r: biggestFloat) proc write*(f: TFile, r: biggestFloat) {.tags: [FWriteIO].}
proc write*(f: TFile, s: string) proc write*(f: TFile, s: string) {.tags: [FWriteIO].}
proc write*(f: TFile, b: Bool) proc write*(f: TFile, b: Bool) {.tags: [FWriteIO].}
proc write*(f: TFile, c: char) proc write*(f: TFile, c: char) {.tags: [FWriteIO].}
proc write*(f: TFile, c: cstring) proc write*(f: TFile, c: cstring) {.tags: [FWriteIO].}
proc write*(f: TFile, a: varargs[string, `$`]) proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO].}
## Writes a value to the file `f`. May throw an IO exception. ## Writes a value to the file `f`. May throw an IO exception.
proc readLine*(f: TFile): TaintedString proc readLine*(f: TFile): TaintedString {.tags: [FReadIO].}
## reads a line of text from the file `f`. May throw an IO exception. ## reads a line of text from the file `f`. May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## A line of text may be delimited by ``CR``, ``LF`` or
## ``CRLF``. The newline character(s) are not part of the returned string. ## ``CRLF``. The newline character(s) are not part of the returned string.
proc readLine*(f: TFile, line: var TaintedString): bool proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO].}
## reads a line of text from the file `f` into `line`. `line` must not be ## reads a line of text from the file `f` into `line`. `line` must not be
## ``nil``! May throw an IO exception. ## ``nil``! May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## A line of text may be delimited by ``CR``, ``LF`` or
@ -1858,39 +1860,44 @@ when not defined(EcmaScript) and not defined(NimrodVM):
## Returns ``false`` if the end of the file has been reached, ``true`` ## Returns ``false`` if the end of the file has been reached, ``true``
## otherwise. If ``false`` is returned `line` contains no new data. ## otherwise. If ``false`` is returned `line` contains no new data.
proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline.} proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, tags: [FWriteIO].}
## writes the values `x` to `f` and then writes "\n". ## writes the values `x` to `f` and then writes "\n".
## May throw an IO exception. ## May throw an IO exception.
proc getFileSize*(f: TFile): int64 proc getFileSize*(f: TFile): int64 {.tags: [FReadIO].}
## retrieves the file size (in bytes) of `f`. ## retrieves the file size (in bytes) of `f`.
proc ReadBytes*(f: TFile, a: var openarray[int8], start, len: int): int proc ReadBytes*(f: TFile, a: var openarray[int8], start, len: int): int {.
tags: [FReadIO].}
## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns
## the actual number of bytes that have been read which may be less than ## the actual number of bytes that have been read which may be less than
## `len` (if not as many bytes are remaining), but not greater. ## `len` (if not as many bytes are remaining), but not greater.
proc ReadChars*(f: TFile, a: var openarray[char], start, len: int): int proc ReadChars*(f: TFile, a: var openarray[char], start, len: int): int {.
tags: [FReadIO].}
## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns
## the actual number of bytes that have been read which may be less than ## the actual number of bytes that have been read which may be less than
## `len` (if not as many bytes are remaining), but not greater. ## `len` (if not as many bytes are remaining), but not greater.
proc readBuffer*(f: TFile, buffer: pointer, len: int): int proc readBuffer*(f: TFile, buffer: pointer, len: int): int {.tags: [FReadIO].}
## reads `len` bytes into the buffer pointed to by `buffer`. Returns ## reads `len` bytes into the buffer pointed to by `buffer`. Returns
## the actual number of bytes that have been read which may be less than ## the actual number of bytes that have been read which may be less than
## `len` (if not as many bytes are remaining), but not greater. ## `len` (if not as many bytes are remaining), but not greater.
proc writeBytes*(f: TFile, a: openarray[int8], start, len: int): int proc writeBytes*(f: TFile, a: openarray[int8], start, len: int): int {.
tags: [FWriteIO].}
## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns
## the number of actual written bytes, which may be less than `len` in case ## the number of actual written bytes, which may be less than `len` in case
## of an error. ## of an error.
proc writeChars*(f: tFile, a: openarray[char], start, len: int): int proc writeChars*(f: tFile, a: openarray[char], start, len: int): int {.
tags: [FWriteIO].}
## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns
## the number of actual written bytes, which may be less than `len` in case ## the number of actual written bytes, which may be less than `len` in case
## of an error. ## of an error.
proc writeBuffer*(f: TFile, buffer: pointer, len: int): int proc writeBuffer*(f: TFile, buffer: pointer, len: int): int {.
tags: [FWriteIO].}
## writes the bytes of buffer pointed to by the parameter `buffer` to the ## writes the bytes of buffer pointed to by the parameter `buffer` to the
## file `f`. Returns the number of actual written bytes, which may be less ## file `f`. Returns the number of actual written bytes, which may be less
## than `len` in case of an error. ## than `len` in case of an error.
@ -1971,7 +1978,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
## allows you to override the behaviour of your application when CTRL+C ## allows you to override the behaviour of your application when CTRL+C
## is pressed. Only one such hook is supported. ## is pressed. Only one such hook is supported.
proc writeStackTrace*() proc writeStackTrace*() {.tags: [FWriteIO].}
## writes the current stack trace to ``stderr``. This is only works ## writes the current stack trace to ``stderr``. This is only works
## for debug builds. ## for debug builds.
when hostOS != "standalone": when hostOS != "standalone":
@ -2029,7 +2036,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
when hasThreadSupport: when hasThreadSupport:
include "system/channels" include "system/channels"
iterator lines*(filename: string): TaintedString = iterator lines*(filename: string): TaintedString {.tags: [FReadIO].} =
## Iterate over any line in the file named `filename`. ## Iterate over any line in the file named `filename`.
## If the file does not exist `EIO` is raised. ## If the file does not exist `EIO` is raised.
var f = open(filename) var f = open(filename)
@ -2037,7 +2044,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
while f.readLine(res): yield res while f.readLine(res): yield res
close(f) close(f)
iterator lines*(f: TFile): TaintedString = iterator lines*(f: TFile): TaintedString {.tags: [FReadIO].} =
## Iterate over any line in the file `f`. ## Iterate over any line in the file `f`.
var res = TaintedString(newStringOfCap(80)) var res = TaintedString(newStringOfCap(80))
while f.readLine(res): yield TaintedString(res) while f.readLine(res): yield TaintedString(res)
@ -2308,18 +2315,25 @@ proc InstantiationInfo*(index = -1): tuple[filename: string, line: int] {.
proc raiseAssert*(msg: string) {.noinline.} = proc raiseAssert*(msg: string) {.noinline.} =
raise newException(EAssertionFailed, msg) raise newException(EAssertionFailed, msg)
when true:
proc hiddenRaiseAssert(msg: string) {.raises: [].} =
# trick the compiler to not list ``EAssertionFailed`` when called
# by ``assert``.
type THide = proc (msg: string) {.noinline, raises: [], noSideEffect.}
THide(raiseAssert)(msg)
template assert*(cond: bool, msg = "") = template assert*(cond: bool, msg = "") =
## provides a means to implement `programming by contracts`:idx: in Nimrod. ## provides a means to implement `programming by contracts`:idx: in Nimrod.
## ``assert`` evaluates expression ``cond`` and if ``cond`` is false, it ## ``assert`` evaluates expression ``cond`` and if ``cond`` is false, it
## raises an ``EAssertionFailure`` exception. However, the compiler may ## raises an ``EAssertionFailure`` exception. However, the compiler may
## not generate any code at all for ``assert`` if it is advised to do so. ## not generate any code at all for ``assert`` if it is advised to do so.
## Use ``assert`` for debugging purposes only. ## Use ``assert`` for debugging purposes only.
bind InstantiationInfo bind InstantiationInfo, hiddenRaiseAssert
when compileOption("assertions"): when compileOption("assertions"):
{.line.}: {.line.}:
if not cond: if not cond:
raiseAssert(astToStr(cond) & ' ' & msg) hiddenRaiseAssert(astToStr(cond) & ' ' & msg)
template doAssert*(cond: bool, msg = "") = template doAssert*(cond: bool, msg = "") =
## same as `assert` but is always turned on and not affected by the ## same as `assert` but is always turned on and not affected by the

View file

@ -16,23 +16,25 @@
# of the standard library! # of the standard library!
proc fputs(c: cstring, f: TFile) {.importc: "fputs", noDecl.} proc fputs(c: cstring, f: TFile) {.importc: "fputs", noDecl, tags: [FWriteIO].}
proc fgets(c: cstring, n: int, f: TFile): cstring {.importc: "fgets", noDecl.} proc fgets(c: cstring, n: int, f: TFile): cstring {.importc: "fgets", noDecl,
proc fgetc(stream: TFile): cint {.importc: "fgetc", nodecl.} tags: [FReadIO].}
proc ungetc(c: cint, f: TFile) {.importc: "ungetc", nodecl.} proc fgetc(stream: TFile): cint {.importc: "fgetc", nodecl, tags: [FReadIO].}
proc putc(c: Char, stream: TFile) {.importc: "putc", nodecl.} proc ungetc(c: cint, f: TFile) {.importc: "ungetc", nodecl, tags: [].}
proc fprintf(f: TFile, frmt: CString) {.importc: "fprintf", nodecl, varargs.} proc putc(c: Char, stream: TFile) {.importc: "putc", nodecl, tags: [FWriteIO].}
proc strlen(c: cstring): int {.importc: "strlen", nodecl.} proc fprintf(f: TFile, frmt: CString) {.importc: "fprintf", nodecl, varargs,
tags: [FWriteIO].}
proc strlen(c: cstring): int {.importc: "strlen", nodecl, tags: [].}
# C routine that is used here: # C routine that is used here:
proc fread(buf: Pointer, size, n: int, f: TFile): int {. proc fread(buf: Pointer, size, n: int, f: TFile): int {.
importc: "fread", noDecl.} importc: "fread", noDecl, tags: [FReadIO].}
proc fseek(f: TFile, offset: clong, whence: int): int {. proc fseek(f: TFile, offset: clong, whence: int): int {.
importc: "fseek", noDecl.} importc: "fseek", noDecl, tags: [].}
proc ftell(f: TFile): int {.importc: "ftell", noDecl.} proc ftell(f: TFile): int {.importc: "ftell", noDecl, tags: [].}
proc setvbuf(stream: TFile, buf: pointer, typ, size: cint): cint {. proc setvbuf(stream: TFile, buf: pointer, typ, size: cint): cint {.
importc, nodecl.} importc, nodecl, tags: [].}
proc write(f: TFile, c: cstring) = fputs(c, f) proc write(f: TFile, c: cstring) = fputs(c, f)

View file

@ -1,7 +1,9 @@
version 0.9.2 version 0.9.2
============= =============
- make the stdlib aware of tags: sockets, os
- test&finish first class iterators: - test&finish first class iterators:
* allow return in first class iterators
* nested iterators * nested iterators
* arglist as a type? * arglist as a type?
* tyIterator? * tyIterator?

View file

@ -19,13 +19,6 @@ between threads, so no "stop the world" mechanism is necessary. An unsafe
shared memory heap is also provided for the increased efficiency that results shared memory heap is also provided for the increased efficiency that results
from that model. from that model.
..
Don't give me that marketing crap. What is Nimrod?
--------------------------------------------------
Nimrod = Mutable value based datatypes + static binding + sugar to make
this programming modell as convenient as possible
Why is it named Nimrod? Why is it named Nimrod?
----------------------- -----------------------
@ -57,10 +50,11 @@ memory heap.
How is Nimrod licensed? How is Nimrod licensed?
----------------------- -----------------------
The Nimrod compiler is GPL licensed, the runtime library is LGPL licensed. The Nimrod compiler is GPL licensed, the runtime library is LGPL licensed
with a special exception that allows for static linking.
This means that you can use any license for your own programs developed with This means that you can use any license for your own programs developed with
Nimrod. If I receive enough requests with good arguments, I may change the Nimrod.
license of Nimrod to the BSD license.
How stable is Nimrod? How stable is Nimrod?
--------------------- ---------------------