diff options
Diffstat (limited to 'lib/impure')
-rw-r--r-- | lib/impure/db_mongo.nim | 227 | ||||
-rwxr-xr-x | lib/impure/db_mysql.nim | 203 | ||||
-rwxr-xr-x | lib/impure/db_postgres.nim | 186 | ||||
-rwxr-xr-x | lib/impure/db_sqlite.nim | 204 | ||||
-rwxr-xr-x | lib/impure/dialogs.nim | 226 | ||||
-rwxr-xr-x | lib/impure/graphics.nim | 567 | ||||
-rw-r--r-- | lib/impure/nre.nim | 751 | ||||
-rw-r--r-- | lib/impure/nre/.gitignore | 9 | ||||
-rw-r--r-- | lib/impure/nre/private/util.nim | 51 | ||||
-rwxr-xr-x | lib/impure/osinfo_posix.nim | 68 | ||||
-rwxr-xr-x | lib/impure/osinfo_win.nim | 405 | ||||
-rw-r--r--[-rwxr-xr-x] | lib/impure/rdstdin.nim | 96 | ||||
-rw-r--r--[-rwxr-xr-x] | lib/impure/re.nim | 766 | ||||
-rwxr-xr-x | lib/impure/ssl.nim | 96 | ||||
-rwxr-xr-x | lib/impure/web.nim | 63 | ||||
-rwxr-xr-x | lib/impure/zipfiles.nim | 167 |
16 files changed, 1324 insertions, 2761 deletions
diff --git a/lib/impure/db_mongo.nim b/lib/impure/db_mongo.nim deleted file mode 100644 index b11db78f8..000000000 --- a/lib/impure/db_mongo.nim +++ /dev/null @@ -1,227 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a higher level wrapper for `mongodb`:idx:. Example: -## -## .. code-block:: nimrod -## -## import mongo, db_mongo, oids, json -## -## var conn = db_mongo.open() -## -## # construct JSON data: -## var data = %{"a": %13, "b": %"my string value", -## "inner": %{"i": %71} } -## -## var id = insertID(conn, "test.test", data) -## -## for v in find(conn, "test.test", "this.a == 13"): -## print v -## -## delete(conn, "test.test", id) -## close(conn) - -import mongo, oids, json - -type - EDb* = object of EIO ## exception that is raised if a database error occurs - TDbConn* = TMongo ## a database connection; alias for ``TMongo`` - - FDb* = object of FIO ## effect that denotes a database operation - FReadDb* = object of FDB ## effect that denotes a read operation - FWriteDb* = object of FDB ## effect that denotes a write operation - -proc dbError*(db: TDbConn, msg: string) {.noreturn.} = - ## raises an EDb exception with message `msg`. - var e: ref EDb - new(e) - if db.errstr[0] != '\0': - e.msg = $db.errstr - else: - e.msg = $db.err & " " & msg - raise e - -proc Close*(db: var TDbConn) {.tags: [FDB].} = - ## closes the database connection. - disconnect(db) - destroy(db) - -proc Open*(host: string = defaultHost, port: int = defaultPort): TDbConn {. - tags: [FDB].} = - ## opens a database connection. Raises `EDb` if the connection could not - ## be established. - init(result) - - let x = connect(result, host, port.cint) - if x != 0'i32: - dbError(result, "cannot open: " & host) - -proc jsonToBSon(b: var TBSon, key: string, j: PJsonNode) = - case j.kind - of JString: - add(b, key, j.str) - of JInt: - add(b, key, j.num) - of JFloat: - add(b, key, j.fnum) - of JBool: - addBool(b, key, ord(j.bval)) - of JNull: - addNull(b, key) - of JObject: - addStartObject(b, key) - for k, v in items(j.fields): - jsonToBSon(b, k, v) - addFinishObject(b) - of JArray: - addStartArray(b, key) - for i, e in pairs(j.elems): - jsonToBSon(b, $i, e) - addFinishArray(b) - -proc jsonToBSon*(j: PJsonNode, oid: TOid): TBSon = - ## converts a JSON value into the BSON format. The result must be - ## ``destroyed`` explicitely! - init(result) - assert j.kind == JObject - add(result, "_id", oid) - for key, val in items(j.fields): - jsonToBSon(result, key, val) - finish(result) - -proc `[]`*(obj: var TBSon, fieldname: cstring): TBSon = - ## retrieves the value belonging to `fieldname`. Raises `EInvalidKey` if - ## the attribute does not exist. - var it = initIter(obj) - let res = find(it, result, fieldname) - if res == bkEOO: - raise newException(EInvalidIndex, "key not in object") - -proc getId*(obj: var TBSon): TOid = - ## retrieves the ``_id`` attribute of `obj`. - var it = initIter(obj) - var b: TBSon - let res = find(it, b, "_id") - if res == bkOID: - result = oidVal(it)[] - else: - raise newException(EInvalidIndex, "_id not in object") - -proc insertID*(db: var TDbConn, namespace: string, data: PJsonNode): TOid {. - tags: [FWriteDb].} = - ## converts `data` to BSON format and inserts it in `namespace`. Returns - ## the generated OID for the ``_id`` field. - result = genOid() - var x = jsonToBSon(data, result) - insert(db, namespace, x) - destroy(x) - -proc insert*(db: var TDbConn, namespace: string, data: PJsonNode) {. - tags: [FWriteDb].} = - ## converts `data` to BSON format and inserts it in `namespace`. - discard InsertID(db, namespace, data) - -proc update*(db: var TDbConn, namespace: string, obj: var TBSon) {. - tags: [FReadDB, FWriteDb].} = - ## updates `obj` in `namespace`. - var cond: TBson - init(cond) - cond.add("_id", getId(obj)) - finish(cond) - update(db, namespace, cond, obj, ord(UPDATE_UPSERT)) - destroy(cond) - -proc update*(db: var TDbConn, namespace: string, oid: TOid, obj: PJsonNode) {. - tags: [FReadDB, FWriteDb].} = - ## updates the data with `oid` to have the new data `obj`. - var a = jsonToBSon(obj, oid) - Update(db, namespace, a) - destroy(a) - -proc delete*(db: var TDbConn, namespace: string, oid: TOid) {. - tags: [FWriteDb].} = - ## Deletes the object belonging to `oid`. - var cond: TBson - init(cond) - cond.add("_id", oid) - finish(cond) - discard remove(db, namespace, cond) - destroy(cond) - -proc delete*(db: var TDbConn, namespace: string, obj: var TBSon) {. - tags: [FWriteDb].} = - ## Deletes the object `obj`. - delete(db, namespace, getId(obj)) - -iterator find*(db: var TDbConn, namespace: string): var TBSon {. - tags: [FReadDB].} = - ## iterates over any object in `namespace`. - var cursor: TCursor - init(cursor, db, namespace) - while next(cursor) == mongo.OK: - yield bson(cursor)[] - destroy(cursor) - -iterator find*(db: var TDbConn, namespace: string, - query, fields: var TBSon): var TBSon {.tags: [FReadDB].} = - ## yields the `fields` of any document that suffices `query`. - var cursor = find(db, namespace, query, fields, 0'i32, 0'i32, 0'i32) - if cursor != nil: - while next(cursor[]) == mongo.OK: - yield bson(cursor[])[] - destroy(cursor[]) - -proc setupFieldnames(fields: varargs[string]): TBSon = - init(result) - for x in fields: add(result, x, 1'i32) - finish(result) - -iterator find*(db: var TDbConn, namespace: string, - query: var TBSon, fields: varargs[string]): var TBSon {. - tags: [FReadDB].} = - ## yields the `fields` of any document that suffices `query`. If `fields` - ## is ``[]`` the whole document is yielded. - var f = setupFieldnames(fields) - var cursor = find(db, namespace, query, f, 0'i32, 0'i32, 0'i32) - if cursor != nil: - while next(cursor[]) == mongo.OK: - yield bson(cursor[])[] - destroy(cursor[]) - destroy(f) - -proc setupQuery(query: string): TBSon = - init(result) - add(result, "$where", query) - finish(result) - -iterator find*(db: var TDbConn, namespace: string, - query: string, fields: varargs[string]): var TBSon {. - tags: [FReadDB].} = - ## yields the `fields` of any document that suffices `query`. If `fields` - ## is ``[]`` the whole document is yielded. - var f = setupFieldnames(fields) - var q = setupQuery(query) - var cursor = find(db, namespace, q, f, 0'i32, 0'i32, 0'i32) - if cursor != nil: - while next(cursor[]) == mongo.OK: - yield bson(cursor[])[] - destroy(cursor[]) - destroy(q) - destroy(f) - -when false: - # this doesn't work this way; would require low level hacking - iterator fieldPairs*(obj: var TBSon): tuple[key: cstring, value: TBSon] = - ## iterates over `obj` and yields all (key, value)-Pairs. - var it = initIter(obj) - var v: TBSon - while next(it) != bkEOO: - let key = key(it) - discard init(v, value(it)) - yield (key, v) diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim deleted file mode 100755 index 91cf8a5eb..000000000 --- a/lib/impure/db_mysql.nim +++ /dev/null @@ -1,203 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## A higher level `mySQL`:idx: database wrapper. The same interface is -## implemented for other databases too. - -import strutils, mysql - -type - TDbConn* = PMySQL ## encapsulates a database connection - TRow* = seq[string] ## a row of a dataset. NULL database values will be - ## transformed always to the empty string. - EDb* = object of EIO ## exception that is raised if a database error occurs - - TSqlQuery* = distinct string ## an SQL query string - - FDb* = object of FIO ## effect that denotes a database operation - FReadDb* = object of FDb ## effect that denotes a read operation - FWriteDb* = object of FDb ## effect that denotes a write operation - -proc dbError(db: TDbConn) {.noreturn.} = - ## raises an EDb exception. - var e: ref EDb - new(e) - e.msg = $mysql.error(db) - raise e - -proc dbError*(msg: string) {.noreturn.} = - ## raises an EDb exception with message `msg`. - var e: ref EDb - new(e) - e.msg = msg - raise e - -when false: - proc dbQueryOpt*(db: TDbConn, query: string, args: varargs[string, `$`]) = - var stmt = mysql_stmt_init(db) - if stmt == nil: dbError(db) - if mysql_stmt_prepare(stmt, query, len(query)) != 0: - dbError(db) - var - binding: seq[MYSQL_BIND] - discard mysql_stmt_close(stmt) - -proc dbQuote(s: string): string = - result = "'" - for c in items(s): - if c == '\'': add(result, "''") - else: add(result, c) - add(result, '\'') - -proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = - result = "" - var a = 0 - for c in items(string(formatstr)): - if c == '?': - add(result, dbQuote(args[a])) - inc(a) - else: - add(result, c) - -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) - return mysql.RealQuery(db, q, q.len) == 0'i32 - -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. - var q = dbFormat(query, args) - if mysql.RealQuery(db, q, q.len) != 0'i32: dbError(db) - -proc newRow(L: int): TRow = - newSeq(result, L) - for i in 0..L-1: result[i] = "" - -proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) = - if row != nil: - while mysql.FetchRow(sqlres) != nil: nil - mysql.FreeResult(sqlres) - -iterator FastRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = - ## executes the query and iterates over the result dataset. This is very - ## fast, but potenially dangerous: If the for-loop-body executes another - ## query, the results can be undefined. For MySQL this is the case!. - rawExec(db, query, args) - var sqlres = mysql.UseResult(db) - if sqlres != nil: - var L = int(mysql.NumFields(sqlres)) - var result = newRow(L) - var row: cstringArray - while true: - row = mysql.FetchRow(sqlres) - if row == nil: break - for i in 0..L-1: - setLen(result[i], 0) - add(result[i], row[i]) - yield result - properFreeResult(sqlres, row) - -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 - ## will return a TRow with empty strings for each column. - rawExec(db, query, args) - var sqlres = mysql.UseResult(db) - if sqlres != nil: - var L = int(mysql.NumFields(sqlres)) - result = newRow(L) - var row = mysql.FetchRow(sqlres) - if row != nil: - for i in 0..L-1: - setLen(result[i], 0) - add(result[i], row[i]) - properFreeResult(sqlres, row) - -proc GetAllRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = - ## executes the query and returns the whole result dataset. - result = @[] - rawExec(db, query, args) - var sqlres = mysql.UseResult(db) - if sqlres != nil: - var L = int(mysql.NumFields(sqlres)) - var row: cstringArray - var j = 0 - while true: - row = mysql.FetchRow(sqlres) - if row == nil: break - setLen(result, j+1) - newSeq(result[j], L) - for i in 0..L-1: result[j][i] = $row[i] - inc(j) - mysql.FreeResult(sqlres) - -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].} = - ## 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. - result = "" - for row in FastRows(db, query, args): - result = row[0] - break - -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. - var q = dbFormat(query, args) - if mysql.RealQuery(db, q, q.len) != 0'i32: - result = -1'i64 - else: - result = mysql.InsertId(db) - -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. - result = TryInsertID(db, query, args) - if result < 0: dbError(db) - -proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): int64 {. - tags: [FReadDB, FWriteDb].} = - ## runs the query (typically "UPDATE") and returns the - ## number of affected rows - rawExec(db, query, args) - result = mysql.AffectedRows(db) - -proc Close*(db: TDbConn) {.tags: [FDb].} = - ## closes the database connection. - if db != nil: mysql.Close(db) - -proc Open*(connection, user, password, database: string): TDbConn {. - tags: [FDb].} = - ## opens a database connection. Raises `EDb` if the connection could not - ## be established. - result = mysql.Init(nil) - if result == nil: dbError("could not open database connection") - if mysql.RealConnect(result, "", user, password, database, - 0'i32, nil, 0) == nil: - var errmsg = $mysql.error(result) - db_mysql.Close(result) - dbError(errmsg) - diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim deleted file mode 100755 index 2dd55e05f..000000000 --- a/lib/impure/db_postgres.nim +++ /dev/null @@ -1,186 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## A higher level `PostgreSQL`:idx: database wrapper. This interface -## is implemented for other databases too. - -import strutils, postgres - -type - TDbConn* = PPGconn ## encapsulates a database connection - TRow* = seq[string] ## a row of a dataset. NULL database values will be - ## transformed always to the empty string. - EDb* = object of EIO ## exception that is raised if a database error occurs - - TSqlQuery* = distinct string ## an SQL query string - - 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.} = - ## 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"`` - ## - ## If assertions are turned off, it does nothing. If assertions are turned - ## on, later versions will check the string for valid syntax. - result = TSqlQuery(query) - -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.} = - ## raises an EDb exception with message `msg`. - var e: ref EDb - new(e) - e.msg = msg - raise e - -proc dbQuote(s: string): string = - result = "'" - for c in items(s): - if c == '\'': add(result, "''") - else: add(result, c) - add(result, '\'') - -proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = - result = "" - var a = 0 - for c in items(string(formatstr)): - if c == '?': - add(result, dbQuote(args[a])) - inc(a) - else: - add(result, c) - -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) - 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) - 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 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) - -iterator FastRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = - ## executes the query and iterates over the result dataset. This is very - ## fast, but potenially dangerous: If the for-loop-body executes another - ## query, the results can be undefined. For Postgres it is safe though. - var res = setupQuery(db, query, 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 - ## will return a TRow with empty strings for each column. - var res = setupQuery(db, query, 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, - 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].} = - ## 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, - 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"), - args), 0, 0) - if not isNil(x): - result = ParseBiggestInt($x) - else: - result = -1 - -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 - ## ``RETURNING id`` to the query, so it only works if your primary key is - ## named ``id``. - result = TryInsertID(db, query, args) - if result < 0: dbError(db) - -proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): int64 {.tags: [ - FReadDB, FWriteDb].} = - ## executes the query (typically "UPDATE") and returns the - ## number of affected rows. - var q = dbFormat(query, args) - var res = PQExec(db, q) - if PQresultStatus(res) != PGRES_COMMAND_OK: dbError(db) - result = parseBiggestInt($PQcmdTuples(res)) - PQclear(res) - -proc Close*(db: TDbConn) {.tags: [FDb].} = - ## closes the database connection. - if db != nil: PQfinish(db) - -proc Open*(connection, user, password, database: string): TDbConn {. - tags: [FDb].} = - ## opens a database connection. Raises `EDb` if the connection could not - ## be established. - result = PQsetdbLogin(nil, nil, nil, nil, database, user, password) - if PQStatus(result) != CONNECTION_OK: dbError(result) # result = nil - - diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim deleted file mode 100755 index 693077553..000000000 --- a/lib/impure/db_sqlite.nim +++ /dev/null @@ -1,204 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## A higher level `SQLite`:idx: database wrapper. This interface -## is implemented for other databases too. - -import strutils, sqlite3 - -type - TDbConn* = PSqlite3 ## encapsulates a database connection - TRow* = seq[string] ## a row of a dataset. NULL database values will be - ## transformed always to the empty string. - EDb* = object of EIO ## exception that is raised if a database error occurs - - TSqlQuery* = distinct string ## an SQL query string - - 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.} = - ## 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"`` - ## - ## If assertions are turned off, it does nothing. If assertions are turned - ## on, later versions will check the string for valid syntax. - result = TSqlQuery(query) - -proc dbError(db: TDbConn) {.noreturn.} = - ## raises an EDb exception. - var e: ref EDb - new(e) - e.msg = $sqlite3.errmsg(db) - raise e - -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 = - result = "'" - for c in items(s): - if c == '\'': add(result, "''") - else: add(result, c) - add(result, '\'') - -proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = - result = "" - var a = 0 - for c in items(string(formatstr)): - if c == '?': - add(result, dbQuote(args[a])) - inc(a) - else: - add(result, c) - -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 stmt: sqlite3.PStmt - if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: - if step(stmt) == SQLITE_DONE: - result = finalize(stmt) == SQLITE_OK - -proc Exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {. - tags: [FReadDB, FWriteDb].} = - ## executes the query and raises EDB if not successful. - if not TryExec(db, query, args): dbError(db) - -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]): PStmt = - var q = dbFormat(query, args) - if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db) - -proc setRow(stmt: PStmt, r: var TRow, cols: cint) = - for col in 0..cols-1: - setLen(r[col], column_bytes(stmt, col)) # set capacity - setLen(r[col], 0) - let x = column_text(stmt, col) - if not isNil(x): 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 - ## fast, but potenially dangerous: If the for-loop-body executes another - ## query, the results can be undefined. For Sqlite it is safe though. - var stmt = setupQuery(db, query, args) - var L = (columnCount(stmt)) - var result = newRow(L) - while step(stmt) == SQLITE_ROW: - setRow(stmt, result, L) - yield result - if finalize(stmt) != SQLITE_OK: dbError(db) - -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 - ## will return a TRow with empty strings for each column. - var stmt = setupQuery(db, query, args) - var L = (columnCount(stmt)) - result = newRow(L) - if step(stmt) == SQLITE_ROW: - setRow(stmt, result, L) - if finalize(stmt) != SQLITE_OK: dbError(db) - -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, - args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = - ## same as `FastRows`, but slower and safe. - for r in FastRows(db, query, args): yield r - -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 stmt = setupQuery(db, query, args) - if step(stmt) == SQLITE_ROW: - let cb = column_bytes(stmt, 0) - if cb == 0: - result = "" - else: - result = newStringOfCap(cb) - add(result, column_text(stmt, 0)) - if finalize(stmt) != SQLITE_OK: dbError(db) - else: - result = "" - -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. - var q = dbFormat(query, args) - var stmt: sqlite3.PStmt - if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: - if step(stmt) == SQLITE_DONE: - if finalize(stmt) == SQLITE_OK: - return last_insert_rowid(db) - result = -1 - -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 - ## ``RETURNING id`` to the query, so it only works if your primary key is - ## named ``id``. - result = TryInsertID(db, query, args) - if result < 0: dbError(db) - -proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, - args: varargs[string, `$`]): int64 {. - tags: [FReadDB, FWriteDb].} = - ## executes the query (typically "UPDATE") and returns the - ## number of affected rows. - Exec(db, query, args) - result = changes(db) - -proc Close*(db: TDbConn) {.tags: [FDB].} = - ## closes the database connection. - if sqlite3.close(db) != SQLITE_OK: dbError(db) - -proc Open*(connection, user, password, database: string): TDbConn {. - tags: [FDB].} = - ## opens a database connection. Raises `EDb` if the connection could not - ## be established. Only the ``connection`` parameter is used for ``sqlite``. - var db: TDbConn - if sqlite3.open(connection, db) == SQLITE_OK: - result = db - else: - dbError(db) - -when isMainModule: - var db = open("db.sql", "", "", "") - Exec(db, sql"create table tbl1(one varchar(10), two smallint)", []) - exec(db, sql"insert into tbl1 values('hello!',10)", []) - exec(db, sql"insert into tbl1 values('goodbye', 20)", []) - #db.query("create table tbl1(one varchar(10), two smallint)") - #db.query("insert into tbl1 values('hello!',10)") - #db.query("insert into tbl1 values('goodbye', 20)") - for r in db.rows(sql"select * from tbl1", []): - echo(r[0], r[1]) - - db_sqlite.close(db) diff --git a/lib/impure/dialogs.nim b/lib/impure/dialogs.nim deleted file mode 100755 index 5bd7bf6f6..000000000 --- a/lib/impure/dialogs.nim +++ /dev/null @@ -1,226 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - - -## This module implements portable dialogs for Nimrod; the implementation -## builds on the GTK interface. On Windows, native dialogs are shown instead. - -import - glib2, gtk2 - -when defined(Windows): - import windows, ShellAPI, os - -proc info*(window: PWindow, msg: string) = - ## Shows an information message to the user. The process waits until the - ## user presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Information", MB_OK or MB_ICONINFORMATION) - else: - var dialog = message_dialog_new(window, - DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT, - MESSAGE_INFO, BUTTONS_OK, "%s", cstring(msg)) - setTitle(dialog, "Information") - discard run(dialog) - destroy(PWidget(dialog)) - -proc warning*(window: PWindow, msg: string) = - ## Shows a warning message to the user. The process waits until the user - ## presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Warning", MB_OK or MB_ICONWARNING) - else: - var dialog = DIALOG(message_dialog_new(window, - DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT, - MESSAGE_WARNING, BUTTONS_OK, "%s", cstring(msg))) - setTitle(dialog, "Warning") - discard run(dialog) - destroy(PWidget(dialog)) - -proc error*(window: PWindow, msg: string) = - ## Shows an error message to the user. The process waits until the user - ## presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Error", MB_OK or MB_ICONERROR) - else: - var dialog = DIALOG(message_dialog_new(window, - DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT, - MESSAGE_ERROR, BUTTONS_OK, "%s", cstring(msg))) - setTitle(dialog, "Error") - discard run(dialog) - destroy(PWidget(dialog)) - - -proc ChooseFileToOpen*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a filename from the user. Returns "" - ## if the user closed the dialog without selecting a file. On Windows, - ## the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047, char] - opf.lStructSize = sizeof(opf).int32 - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_FILEMUSTEXIST - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf).int32 - var res = GetOpenFileName(addr(opf)) - if res != 0: - result = $buf - else: - result = "" - else: - var chooser = file_chooser_dialog_new("Open File", window, - FILE_CHOOSER_ACTION_OPEN, - STOCK_CANCEL, RESPONSE_CANCEL, - STOCK_OPEN, RESPONSE_OK, nil) - if root.len > 0: - discard set_current_folder(chooser, root) - if run(chooser) == cint(RESPONSE_OK): - var x = get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - destroy(PWidget(chooser)) - -proc ChooseFilesToOpen*(window: PWindow, root: string = ""): seq[string] = - ## Opens a dialog that requests filenames from the user. Returns ``@[]`` - ## if the user closed the dialog without selecting a file. On Windows, - ## the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047*4, char] - opf.lStructSize = sizeof(opf).int32 - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_FILEMUSTEXIST or OFN_ALLOWMULTISELECT or OFN_EXPLORER - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf).int32 - var res = GetOpenFileName(addr(opf)) - result = @[] - if res != 0: - # parsing the result is horrible: - var - i = 0 - s: string - path = "" - while buf[i] != '\0': - add(path, buf[i]) - inc(i) - inc(i) - if buf[i] != '\0': - while true: - s = "" - while buf[i] != '\0': - add(s, buf[i]) - inc(i) - add(result, s) - inc(i) - if buf[i] == '\0': break - for i in 0..result.len-1: result[i] = os.joinPath(path, result[i]) - else: - # only one file selected --> gosh, what an ungly thing - # the windows API is - add(result, path) - else: - var chooser = file_chooser_dialog_new("Open Files", window, - FILE_CHOOSER_ACTION_OPEN, - STOCK_CANCEL, RESPONSE_CANCEL, - STOCK_OPEN, RESPONSE_OK, nil) - if root.len > 0: - discard set_current_folder(chooser, root) - set_select_multiple(chooser, true) - result = @[] - if run(chooser) == cint(RESPONSE_OK): - var L = get_filenames(chooser) - var it = L - while it != nil: - add(result, $cast[cstring](it.data)) - g_free(it.data) - it = it.next - free(L) - destroy(PWidget(chooser)) - - -proc ChooseFileToSave*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a filename to save to from the user. - ## Returns "" if the user closed the dialog without selecting a file. - ## On Windows, the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047, char] - opf.lStructSize = sizeof(opf).int32 - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_OVERWRITEPROMPT - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf).int32 - var res = GetSaveFileName(addr(opf)) - if res != 0: - result = $buf - else: - result = "" - else: - var chooser = file_chooser_dialog_new("Save File", window, - FILE_CHOOSER_ACTION_SAVE, - STOCK_CANCEL, RESPONSE_CANCEL, - STOCK_SAVE, RESPONSE_OK, nil) - if root.len > 0: - discard set_current_folder(chooser, root) - set_do_overwrite_confirmation(chooser, true) - if run(chooser) == cint(RESPONSE_OK): - var x = get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - destroy(PWidget(chooser)) - - -proc ChooseDir*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a directory from the user. - ## Returns "" if the user closed the dialog without selecting a directory. - ## On Windows, the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - lpItemID: PItemIDList - BrowseInfo: TBrowseInfo - DisplayName: array [0..MAX_PATH, char] - TempPath: array [0..MAX_PATH, char] - Result = "" - #BrowseInfo.hwndOwner = Application.Handle - BrowseInfo.pszDisplayName = DisplayName - BrowseInfo.ulFlags = 1 #BIF_RETURNONLYFSDIRS - lpItemID = SHBrowseForFolder(cast[LPBrowseInfo](addr(BrowseInfo))) - if lpItemId != nil: - discard SHGetPathFromIDList(lpItemID, TempPath) - Result = $TempPath - discard GlobalFreePtr(lpItemID) - else: - var chooser = file_chooser_dialog_new("Select Directory", window, - FILE_CHOOSER_ACTION_SELECT_FOLDER, - STOCK_CANCEL, RESPONSE_CANCEL, - STOCK_OPEN, RESPONSE_OK, nil) - if root.len > 0: - discard set_current_folder(chooser, root) - if run(chooser) == cint(RESPONSE_OK): - var x = get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - destroy(PWidget(chooser)) - diff --git a/lib/impure/graphics.nim b/lib/impure/graphics.nim deleted file mode 100755 index 0ec2d3903..000000000 --- a/lib/impure/graphics.nim +++ /dev/null @@ -1,567 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements graphical output for Nimrod; the current -## implementation uses SDL but the interface is meant to support multiple -## backends some day. There is no need to init SDL as this module does that -## implicitly. - -import colors, math -from sdl import PSurface # Bug -from sdl_ttf import OpenFont, closeFont - -type - TRect* = tuple[x, y, width, height: int] - TPoint* = tuple[x, y: int] - - PSurface* = ref TSurface ## a surface to draw onto - TSurface* {.pure, final.} = object - w*, h*: Natural - s*: sdl.PSurface - - EGraphics* = object of EIO - - TFont {.pure, final.} = object - f: sdl_ttf.PFont - color: SDL.TColor - PFont* = ref TFont ## represents a font - -proc toSdlColor*(c: TColor): Sdl.TColor = - ## Convert colors.TColor to SDL.TColor - var x = c.extractRGB - result.r = x.r and 0xff - result.g = x.g and 0xff - result.b = x.b and 0xff - -proc createSdlColor*(sur: PSurface, c: TColor, alpha: int = 0): int32 = - ## Creates a color using ``sdl.MapRGBA``. - var x = c.extractRGB - return sdl.MapRGBA(sur.s.format, x.r and 0xff, x.g and 0xff, - x.b and 0xff, alpha and 0xff) - -proc toSdlRect*(r: TRect): sdl.TRect = - ## Convert ``graphics.TRect`` to ``sdl.TRect``. - result.x = int16(r.x) - result.y = int16(r.y) - result.w = uint16(r.width) - result.h = uint16(r.height) - -proc raiseEGraphics = - raise newException(EGraphics, $SDL.GetError()) - -proc surfaceFinalizer(s: PSurface) = sdl.freeSurface(s.s) - -proc newSurface*(width, height: int): PSurface = - ## creates a new surface. - new(result, surfaceFinalizer) - result.w = width - result.h = height - result.s = SDL.CreateRGBSurface(SDL.SWSURFACE, width, height, - 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0) - if result.s == nil: - raiseEGraphics() - - assert(not sdl.MustLock(result.s)) - -proc fontFinalizer(f: PFont) = closeFont(f.f) - -proc newFont*(name = "VeraMono.ttf", size = 9, color = colBlack): PFont = - ## Creates a new font object. Raises ``EIO`` if the font cannot be loaded. - new(result, fontFinalizer) - result.f = OpenFont(name, size.cint) - if result.f == nil: - raise newException(EIO, "Could not open font file: " & name) - result.color = toSdlColor(color) - -var - defaultFont*: PFont ## default font that is used; this needs to initialized - ## by the client! - -proc initDefaultFont*(name = "VeraMono.ttf", size = 9, color = colBlack) = - ## initializes the `defaultFont` var. - defaultFont = newFont(name, size, color) - -proc newScreenSurface*(width, height: int): PSurface = - ## Creates a new screen surface - new(result, surfaceFinalizer) - result.w = width - result.h = height - result.s = SDL.SetVideoMode(width, height, 0, 0) - if result.s == nil: - raiseEGraphics() - -proc writeToBMP*(sur: PSurface, filename: string) = - ## Saves the contents of the surface `sur` to the file `filename` as a - ## BMP file. - if sdl.saveBMP(sur.s, filename) != 0: - raise newException(EIO, "cannot write: " & filename) - -type - TPixels = array[0..1000_000-1, int32] - PPixels = ptr TPixels - -template setPix(video, pitch, x, y, col: expr): stmt = - video[y * pitch + x] = int32(col) - -template getPix(video, pitch, x, y: expr): expr = - colors.TColor(video[y * pitch + x]) - -const - ColSize = 4 - -proc getPixel(sur: PSurface, x, y: Natural): colors.TColor {.inline.} = - assert x <% sur.w - assert y <% sur.h - result = getPix(cast[PPixels](sur.s.pixels), sur.s.pitch.int div ColSize, - x, y) - -proc setPixel(sur: PSurface, x, y: Natural, col: colors.TColor) {.inline.} = - assert x <% sur.w - assert y <% sur.h - var pixs = cast[PPixels](sur.s.pixels) - #pixs[y * (sur.s.pitch div colSize) + x] = int(col) - setPix(pixs, sur.s.pitch.int div ColSize, x, y, col) - -proc `[]`*(sur: PSurface, p: TPoint): TColor = - ## get pixel at position `p`. No range checking is done! - result = getPixel(sur, p.x, p.y) - -proc `[]`*(sur: PSurface, x, y: int): TColor = - ## get pixel at position ``(x, y)``. No range checking is done! - result = getPixel(sur, x, y) - -proc `[]=`*(sur: PSurface, p: TPoint, col: TColor) = - ## set the pixel at position `p`. No range checking is done! - setPixel(sur, p.x, p.y, col) - -proc `[]=`*(sur: PSurface, x, y: int, col: TColor) = - ## set the pixel at position ``(x, y)``. No range checking is done! - setPixel(sur, x, y, col) - -proc blit*(destSurf: PSurface, destRect: TRect, srcSurf: PSurface, - srcRect: TRect) = - ## Copies ``srcSurf`` into ``destSurf`` - var destTRect, srcTRect: SDL.TRect - - destTRect.x = int16(destRect.x) - destTRect.y = int16(destRect.y) - destTRect.w = uint16(destRect.width) - destTRect.h = uint16(destRect.height) - - srcTRect.x = int16(srcRect.x) - srcTRect.y = int16(srcRect.y) - srcTRect.w = uint16(srcRect.width) - srcTRect.h = uint16(srcRect.height) - - if SDL.blitSurface(srcSurf.s, addr(srcTRect), destSurf.s, addr(destTRect)) != 0: - raiseEGraphics() - -proc textBounds*(text: string, font = defaultFont): tuple[width, height: int] = - var w, h: cint - if sdl_ttf.SizeUTF8(font.f, text, w, h) < 0: raiseEGraphics() - result.width = int(w) - result.height = int(h) - -proc drawText*(sur: PSurface, p: TPoint, text: string, font = defaultFont) = - ## Draws text with a transparent background, at location ``p`` with the given - ## font. - var textSur: PSurface # This surface will have the text drawn on it - new(textSur, surfaceFinalizer) - - # Render the text - textSur.s = sdl_ttf.RenderTextBlended(font.f, text, font.color) - # Merge the text surface with sur - sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) - -proc drawText*(sur: PSurface, p: TPoint, text: string, - bg: TColor, font = defaultFont) = - ## Draws text, at location ``p`` with font ``font``. ``bg`` - ## is the background color. - var textSur: PSurface # This surface will have the text drawn on it - new(textSur, surfaceFinalizer) - textSur.s = sdl_ttf.RenderTextShaded(font.f, text, font.color, toSdlColor(bg)) - # Merge the text surface with sur - sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) - -proc drawCircle*(sur: PSurface, p: TPoint, r: Natural, color: TColor) = - ## draws a circle with center `p` and radius `r` with the given color - ## onto the surface `sur`. - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - var a = 1 - r - var py = r - var px = 0 - var x = p.x - var y = p.y - while px <= py + 1: - if x+px <% sur.w: - if y+py <% sur.h: setPix(video, pitch, x+px, y+py, color) - if y-py <% sur.h: setPix(video, pitch, x+px, y-py, color) - - if x-px <% sur.w: - if y+py <% sur.h: setPix(video, pitch, x-px, y+py, color) - if y-py <% sur.h: setPix(video, pitch, x-px, y-py, color) - - if x+py <% sur.w: - if y+px <% sur.h: setPix(video, pitch, x+py, y+px, color) - if y-px <% sur.h: setPix(video, pitch, x+py, y-px, color) - - if x-py <% sur.w: - if y+px <% sur.h: setPix(video, pitch, x-py, y+px, color) - if y-px <% sur.h: setPix(video, pitch, x-py, y-px, color) - - if a < 0: - a = a + (2 * px + 3) - else: - a = a + (2 * (px - py) + 5) - py = py - 1 - px = px + 1 - -proc `>-<`(val: int, s: PSurface): int {.inline.} = - return if val < 0: 0 elif val >= s.w: s.w-1 else: val - -proc `>|<`(val: int, s: PSurface): int {.inline.} = - return if val < 0: 0 elif val >= s.h: s.h-1 else: val - -proc drawLine*(sur: PSurface, p1, p2: TPoint, color: TColor) = - ## draws a line between the two points `p1` and `p2` with the given color - ## onto the surface `sur`. - var stepx, stepy: int = 0 - var x0 = p1.x >-< sur - var x1 = p2.x >-< sur - var y0 = p1.y >|< sur - var y1 = p2.y >|< sur - var dy = y1 - y0 - var dx = x1 - x0 - if dy < 0: - dy = -dy - stepy = -1 - else: - stepy = 1 - if dx < 0: - dx = -dx - stepx = -1 - else: - stepx = 1 - dy = dy * 2 - dx = dx * 2 - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - setPix(video, pitch, x0, y0, color) - if dx > dy: - var fraction = dy - (dx div 2) - while x0 != x1: - if fraction >= 0: - y0 = y0 + stepy - fraction = fraction - dx - x0 = x0 + stepx - fraction = fraction + dy - setPix(video, pitch, x0, y0, color) - else: - var fraction = dx - (dy div 2) - while y0 != y1: - if fraction >= 0: - x0 = x0 + stepx - fraction = fraction - dy - y0 = y0 + stepy - fraction = fraction + dx - setPix(video, pitch, x0, y0, color) - -proc drawHorLine*(sur: PSurface, x, y, w: Natural, Color: TColor) = - ## draws a horizontal line from (x,y) to (x+w-1, y). - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - if y >= 0 and y <= sur.s.h: - for i in 0 .. min(sur.s.w-x, w)-1: - setPix(video, pitch, x + i, y, color) - -proc drawVerLine*(sur: PSurface, x, y, h: Natural, Color: TColor) = - ## draws a vertical line from (x,y) to (x, y+h-1). - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - if x >= 0 and x <= sur.s.w: - for i in 0 .. min(sur.s.h-y, h)-1: - setPix(video, pitch, x, y + i, color) - -proc fillCircle*(s: PSurface, p: TPoint, r: Natural, color: TColor) = - ## draws a circle with center `p` and radius `r` with the given color - ## onto the surface `sur` and fills it. - var a = 1 - r - var py: int = r - var px = 0 - var x = p.x - var y = p.y - while px <= py: - # Fill up the middle half of the circle - DrawVerLine(s, x + px, y, py + 1, color) - DrawVerLine(s, x + px, y - py, py, color) - if px != 0: - DrawVerLine(s, x - px, y, py + 1, color) - DrawVerLine(s, x - px, y - py, py, color) - if a < 0: - a = a + (2 * px + 3) - else: - a = a + (2 * (px - py) + 5) - py = py - 1 - # Fill up the left/right half of the circle - if py >= px: - DrawVerLine(s, x + py + 1, y, px + 1, color) - DrawVerLine(s, x + py + 1, y - px, px, color) - DrawVerLine(s, x - py - 1, y, px + 1, color) - DrawVerLine(s, x - py - 1, y - px, px, color) - px = px + 1 - -proc drawRect*(sur: PSurface, r: TRect, color: TColor) = - ## draws a rectangle. - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - if (r.x >= 0 and r.x <= sur.s.w) and (r.y >= 0 and r.y <= sur.s.h): - var minW = min(sur.s.w - r.x, r.width - 1) - var minH = min(sur.s.h - r.y, r.height - 1) - - # Draw Top - for i in 0 .. minW - 1: - setPix(video, pitch, r.x + i, r.y, color) - setPix(video, pitch, r.x + i, r.y + minH - 1, color) # Draw bottom - - # Draw left side - for i in 0 .. minH - 1: - setPix(video, pitch, r.x, r.y + i, color) - setPix(video, pitch, r.x + minW - 1, r.y + i, color) # Draw right side - -proc fillRect*(sur: PSurface, r: TRect, col: TColor) = - ## Fills a rectangle using sdl's ``FillRect`` function. - var rect = toSdlRect(r) - if sdl.FillRect(sur.s, addr(rect), sur.createSdlColor(col)) == -1: - raiseEGraphics() - -proc Plot4EllipsePoints(sur: PSurface, CX, CY, X, Y: Natural, col: TColor) = - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - if CX+X <= sur.s.w-1: - if CY+Y <= sur.s.h-1: setPix(video, pitch, CX+X, CY+Y, col) - if CY-Y <= sur.s.h-1: setPix(video, pitch, CX+X, CY-Y, col) - if CX-X <= sur.s.w-1: - if CY+Y <= sur.s.h-1: setPix(video, pitch, CX-X, CY+Y, col) - if CY-Y <= sur.s.h-1: setPix(video, pitch, CX-X, CY-Y, col) - -proc drawEllipse*(sur: PSurface, CX, CY, XRadius, YRadius: Natural, - col: TColor) = - ## Draws an ellipse, ``CX`` and ``CY`` specify the center X and Y of the - ## ellipse, ``XRadius`` and ``YRadius`` specify half the width and height - ## of the ellipse. - var - X, Y: Natural - XChange, YChange: Natural - EllipseError: Natural - TwoASquare, TwoBSquare: Natural - StoppingX, StoppingY: Natural - - TwoASquare = 2 * XRadius * XRadius - TwoBSquare = 2 * YRadius * YRadius - X = XRadius - Y = 0 - XChange = YRadius * YRadius * (1 - 2 * XRadius) - YChange = XRadius * XRadius - EllipseError = 0 - StoppingX = TwoBSquare * XRadius - StoppingY = 0 - - while StoppingX >= StoppingY: # 1st set of points, y` > - 1 - sur.Plot4EllipsePoints(CX, CY, X, Y, col) - inc(Y) - inc(StoppingY, TwoASquare) - inc(EllipseError, YChange) - inc(YChange, TwoASquare) - if (2 * EllipseError + XChange) > 0 : - dec(x) - dec(StoppingX, TwoBSquare) - inc(EllipseError, XChange) - inc(XChange, TwoBSquare) - - # 1st point set is done; start the 2nd set of points - X = 0 - Y = YRadius - XChange = YRadius * YRadius - YChange = XRadius * XRadius * (1 - 2 * YRadius) - EllipseError = 0 - StoppingX = 0 - StoppingY = TwoASquare * YRadius - while StoppingX <= StoppingY: - sur.Plot4EllipsePoints(CX, CY, X, Y, col) - inc(X) - inc(StoppingX, TwoBSquare) - inc(EllipseError, XChange) - inc(XChange,TwoBSquare) - if (2 * EllipseError + YChange) > 0: - dec(Y) - dec(StoppingY, TwoASquare) - inc(EllipseError, YChange) - inc(YChange,TwoASquare) - - -proc plotAA(sur: PSurface, x, y: int, c: float, color: TColor) = - if (x > 0 and x < sur.s.w) and (y > 0 and y < sur.s.h): - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - var pixColor = getPix(video, pitch, x, y) - - setPix(video, pitch, x, y, - pixColor.intensity(1.0 - c) + color.intensity(c)) - - -template ipart(x: expr): expr = floor(x) -template cround(x: expr): expr = ipart(x + 0.5) -template fpart(x: expr): expr = x - ipart(x) -template rfpart(x: expr): expr = 1.0 - fpart(x) - -proc drawLineAA*(sur: PSurface, p1, p2: TPoint, color: TColor) = - ## Draws a anti-aliased line from ``p1`` to ``p2``, using Xiaolin Wu's - ## line algorithm - var (x1, x2, y1, y2) = (p1.x.toFloat(), p2.x.toFloat(), - p1.y.toFloat(), p2.y.toFloat()) - var dx = x2 - x1 - var dy = y2 - y1 - - var ax = dx - if ax < 0'f64: - ax = 0'f64 - ax - var ay = dy - if ay < 0'f64: - ay = 0'f64 - ay - - if ax < ay: - swap(x1, y1) - swap(x2, y2) - swap(dx, dy) - - template doPlot(x, y: int, c: float, color: TColor): stmt = - if ax < ay: - sur.PlotAA(y, x, c, color) - else: - sur.PlotAA(x, y, c, color) - - if x2 < x1: - swap(x1, x2) - swap(y1, y2) - - var gradient = dy / dx - # handle first endpoint - var xend = cround(x1) - var yend = y1 + gradient * (xend - x1) - var xgap = rfpart(x1 + 0.5) - var xpxl1 = int(xend) # this will be used in the main loop - var ypxl1 = int(ipart(yend)) - doPlot(xpxl1, ypxl1, rfpart(yend)*xgap, color) - doPlot(xpxl1, ypxl1 + 1, fpart(yend)*xgap, color) - var intery = yend + gradient # first y-intersection for the main loop - - # handle second endpoint - xend = cround(x2) - yend = y2 + gradient * (xend - x2) - xgap = fpart(x2 + 0.5) - var xpxl2 = int(xend) # this will be used in the main loop - var ypxl2 = int(ipart(yend)) - doPlot(xpxl2, ypxl2, rfpart(yend) * xgap, color) - doPlot(xpxl2, ypxl2 + 1, fpart(yend) * xgap, color) - - # main loop - var x = xpxl1 + 1 - while x <= xpxl2-1: - doPlot(x, int(ipart(intery)), rfpart(intery), color) - doPlot(x, int(ipart(intery)) + 1, fpart(intery), color) - intery = intery + gradient - inc(x) - -proc fillSurface*(sur: PSurface, color: TColor) = - ## Fills the entire surface with ``color``. - if sdl.FillRect(sur.s, nil, sur.createSdlColor(color)) == -1: - raiseEGraphics() - -template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {. - immediate.} = - ## Simple template which creates an event loop. ``Event`` is the name of the - ## variable containing the TEvent object. - while True: - var event: SDL.TEvent - if SDL.WaitEvent(addr(event)) == 1: - actions - -if sdl.Init(sdl.INIT_VIDEO) < 0: raiseEGraphics() -if sdl_ttf.Init() < 0: raiseEGraphics() - -when isMainModule: - var surf = newScreenSurface(800, 600) - surf.fillSurface(colWhite) - - # Draw the shapes - surf.drawLineAA((150, 170), (400, 471), colTan) - surf.drawLine((100, 170), (400, 471), colRed) - - surf.drawEllipse(200, 300, 200, 30, colSeaGreen) - surf.drawHorLine(1, 300, 400, colViolet) - # Check if the ellipse is the size it's suppose to be. - surf.drawVerLine(200, 300 - 30 + 1, 60, colViolet) # ^^ | i suppose it is - - surf.drawEllipse(400, 300, 300, 300, colOrange) - surf.drawEllipse(5, 5, 5, 5, colGreen) - - surf.drawHorLine(5, 5, 900, colRed) - surf.drawVerLine(5, 60, 800, colRed) - surf.drawCircle((600, 500), 60, colRed) - - surf.fillRect((50, 50, 100, 100), colFuchsia) - - surf.drawLineAA((592, 160), (592, 280), colPurple) - - #surf.drawText((300, 300), "TEST", colMidnightBlue) - #var textSize = textBounds("TEST") - #surf.drawText((300, 300 + textSize.height), $textSize.width & ", " & - # $textSize.height, colDarkGreen) - - var mouseStartX = -1 - var mouseStartY = -1 - withEvents(surf, event): - var eventp = addr(event) - case event.kind: - of SDL.QUITEV: - break - of SDL.KEYDOWN: - var evk = sdl.EvKeyboard(eventp) - if evk.keysym.sym == SDL.K_LEFT: - surf.drawHorLine(395, 300, 50, colBlack) - echo("Drawing") - else: - echo(evk.keysym.sym) - of SDL.MOUSEBUTTONDOWN: - var mbd = sdl.EvMouseButton(eventp) - if mouseStartX == -1 or mouseStartY == -1: - mouseStartX = int(mbd.x) - mouseStartY = int(mbd.y) - else: - surf.drawLineAA((mouseStartX, mouseStartY), (int(mbd.x), int(mbd.y)), colPurple) - mouseStartX = -1 - mouseStartY = -1 - - of SDL.MouseMotion: - var mm = sdl.EvMouseMotion(eventp) - if mouseStartX != -1 and mouseStartY != -1: - surf.drawLineAA((mouseStartX, mouseStartY), (int(mm.x), int(mm.y)), colPurple) - #echo(mm.x, " ", mm.y, " ", mm.yrel) - - else: - #echo(event.kind) - - SDL.UpdateRect(surf.s, 0, 0, 800, 600) - - surf.writeToBMP("test.bmp") - SDL.Quit() diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim new file mode 100644 index 000000000..39d238055 --- /dev/null +++ b/lib/impure/nre.nim @@ -0,0 +1,751 @@ +# +# Nim's Runtime Library +# (c) Copyright 2015 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when defined(js): + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} + +## What is NRE? +## ============ +## +## A regular expression library for Nim using PCRE to do the hard work. +## +## For documentation on how to write patterns, there exists `the official PCRE +## pattern documentation +## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_. You can also +## search the internet for a wide variety of third-party documentation and +## tools. +## +## .. warning:: If you love `sequtils.toSeq` we have bad news for you. This +## library doesn't work with it due to documented compiler limitations. As +## a workaround, use this: +runnableExamples: + # either `import std/nre except toSeq` or fully qualify `sequtils.toSeq`: + import std/sequtils + iterator iota(n: int): int = + for i in 0..<n: yield i + assert sequtils.toSeq(iota(3)) == @[0, 1, 2] +## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre) +## and [regex](https://github.com/nitely/nim-regex). +## Licencing +## --------- +## +## PCRE has `some additional terms`_ that you must agree to in order to use +## this module. +## +## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt +runnableExamples: + import std/sugar + let vowels = re"[aeoui]" + let bounds = collect: + for match in "moiga".findIter(vowels): match.matchBounds + assert bounds == @[1 .. 1, 2 .. 2, 4 .. 4] + from std/sequtils import toSeq + let s = sequtils.toSeq("moiga".findIter(vowels)) + # fully qualified to avoid confusion with nre.toSeq + assert s.len == 3 + + let firstVowel = "foo".find(vowels) + let hasVowel = firstVowel.isSome() + assert hasVowel + let matchBounds = firstVowel.get().captureBounds[-1] + assert matchBounds.a == 1 + + # as with module `re`, unless specified otherwise, `start` parameter in each + # proc indicates where the scan starts, but outputs are relative to the start + # of the input string, not to `start`: + assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab" + assert find("uxabc", re"ab", start = 3).isNone + +from std/pcre import nil +import nre/private/util +import std/tables +from std/strutils import `%` +import std/options +from std/unicode import runeLenAt + +when defined(nimPreviewSlimSystem): + import std/assertions + +export options + +type + Regex* = ref object + ## Represents the pattern that things are matched against, constructed with + ## `re(string)`. Examples: `re"foo"`, `re(r"(*ANYCRLF)(?x)foo # + ## comment".` + ## + ## `pattern: string` + ## : the string that was used to create the pattern. For details on how + ## to write a pattern, please see `the official PCRE pattern + ## documentation. + ## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_ + ## + ## `captureCount: int` + ## : the number of captures that the pattern has. + ## + ## `captureNameId: Table[string, int]` + ## : a table from the capture names to their numeric id. + ## + ## + ## Options + ## ....... + ## + ## The following options may appear anywhere in the pattern, and they affect + ## the rest of it. + ## + ## - `(?i)` - case insensitive + ## - `(?m)` - multi-line: `^` and `$` match the beginning and end of + ## lines, not of the subject string + ## - `(?s)` - `.` also matches newline (*dotall*) + ## - `(?U)` - expressions are not greedy by default. `?` can be added + ## to a qualifier to make it greedy + ## - `(?x)` - whitespace and comments (`#`) are ignored (*extended*) + ## - `(?X)` - character escapes without special meaning (`\w` vs. + ## `\a`) are errors (*extra*) + ## + ## One or a combination of these options may appear only at the beginning + ## of the pattern: + ## + ## - `(*UTF8)` - treat both the pattern and subject as UTF-8 + ## - `(*UCP)` - Unicode character properties; `\w` matches `я` + ## - `(*U)` - a combination of the two options above + ## - `(*FIRSTLINE*)` - fails if there is not a match on the first line + ## - `(*NO_AUTO_CAPTURE)` - turn off auto-capture for groups; + ## `(?<name>...)` can be used to capture + ## - `(*CR)` - newlines are separated by `\r` + ## - `(*LF)` - newlines are separated by `\n` (UNIX default) + ## - `(*CRLF)` - newlines are separated by `\r\n` (Windows default) + ## - `(*ANYCRLF)` - newlines are separated by any of the above + ## - `(*ANY)` - newlines are separated by any of the above and Unicode + ## newlines: + ## + ## single characters VT (vertical tab, U+000B), FF (form feed, U+000C), + ## NEL (next line, U+0085), LS (line separator, U+2028), and PS + ## (paragraph separator, U+2029). For the 8-bit library, the last two + ## are recognized only in UTF-8 mode. + ## — man pcre + ## + ## - `(*JAVASCRIPT_COMPAT)` - JavaScript compatibility + ## - `(*NO_STUDY)` - turn off studying; study is enabled by default + ## + ## For more details on the leading option groups, see the `Option + ## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_ + ## and the `Newline + ## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_ + ## sections of the `PCRE syntax + ## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_. + ## + ## Some of these options are not part of PCRE and are converted by nre + ## into PCRE flags. These include `NEVER_UTF`, `ANCHORED`, + ## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`, + ## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you + ## will need to pass these as separate flags to PCRE. + pattern*: string + pcreObj: ptr pcre.Pcre ## not nil + pcreExtra: ptr pcre.ExtraData ## nil + + captureNameToId: Table[string, int] + + RegexMatch* = object + ## Usually seen as Option[RegexMatch], it represents the result of an + ## execution. On failure, it is none, on success, it is some. + ## + ## `pattern: Regex` + ## : the pattern that is being matched + ## + ## `str: string` + ## : the string that was matched against + ## + ## `captures[]: string` + ## : the string value of whatever was captured at that id. If the value + ## is invalid, then behavior is undefined. If the id is `-1`, then + ## the whole match is returned. If the given capture was not matched, + ## `nil` is returned. See examples for `match`. + ## + ## `captureBounds[]: HSlice[int, int]` + ## : gets the bounds of the given capture according to the same rules as + ## the above. If the capture is not filled, then `None` is returned. + ## The bounds are both inclusive. See examples for `match`. + ## + ## `match: string` + ## : the full text of the match. + ## + ## `matchBounds: HSlice[int, int]` + ## : the bounds of the match, as in `captureBounds[]` + ## + ## `(captureBounds|captures).toTable` + ## : returns a table with each named capture as a key. + ## + ## `(captureBounds|captures).toSeq` + ## : returns all the captures by their number. + ## + ## `$: string` + ## : same as `match` + pattern*: Regex ## The regex doing the matching. + ## Not nil. + str*: string ## The string that was matched against. + pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match + ## Other items are the captures + ## `a` is inclusive start, `b` is exclusive end + + Captures* = distinct RegexMatch + CaptureBounds* = distinct RegexMatch + + RegexError* = ref object of CatchableError + + RegexInternalError* = ref object of RegexError + ## Internal error in the module, this probably means that there is a bug + + InvalidUnicodeError* = ref object of RegexError + ## Thrown when matching fails due to invalid unicode in strings + pos*: int ## the location of the invalid unicode in bytes + + SyntaxError* = ref object of RegexError + ## Thrown when there is a syntax error in the + ## regular expression string passed in + pos*: int ## the location of the syntax error in bytes + pattern*: string ## the pattern that caused the problem + + StudyError* = ref object of RegexError + ## Thrown when studying the regular expression fails + ## for whatever reason. The message contains the error + ## code. + +proc destroyRegex(pattern: Regex) = + `=destroy`(pattern.pattern) + pcre.free_substring(cast[cstring](pattern.pcreObj)) + if pattern.pcreExtra != nil: + pcre.free_study(pattern.pcreExtra) + `=destroy`(pattern.captureNameToId) + +proc getinfo[T](pattern: Regex, opt: cint): T = + let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result) + + if retcode < 0: + # XXX Error message that doesn't expose implementation details + raise newException(FieldDefect, "Invalid getinfo for $1, errno $2" % [$opt, $retcode]) + +proc getNameToNumberTable(pattern: Regex): Table[string, int] = + let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT) + let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE) + let table = cast[ptr UncheckedArray[uint8]]( + getinfo[int](pattern, pcre.INFO_NAMETABLE)) + + result = initTable[string, int]() + + for i in 0 ..< entryCount: + let pos = i * entrySize + let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1 + var name = "" + + var idx = 2 + while table[pos + idx] != 0: + name.add(char(table[pos + idx])) + idx += 1 + + result[name] = num + +proc initRegex(pattern: string, flags: int, study = true): Regex = + new(result, destroyRegex) + result.pattern = pattern + + var errorMsg: cstring + var errOffset: cint + + result.pcreObj = pcre.compile(cstring(pattern), + # better hope int is at least 4 bytes.. + cint(flags), addr errorMsg, + addr errOffset, nil) + if result.pcreObj == nil: + # failed to compile + raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern) + + if study: + var options: cint = 0 + var hasJit: cint + if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0: + if hasJit == 1'i32: + options = pcre.STUDY_JIT_COMPILE + result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg) + if errorMsg != nil: + raise StudyError(msg: $errorMsg) + + result.captureNameToId = result.getNameToNumberTable() + +proc captureCount*(pattern: Regex): int = + return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT) + +proc captureNameId*(pattern: Regex): Table[string, int] = + return pattern.captureNameToId + +proc matchesCrLf(pattern: Regex): bool = + let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS)) + let newlineFlags = flags and (pcre.NEWLINE_CRLF or + pcre.NEWLINE_ANY or + pcre.NEWLINE_ANYCRLF) + if newlineFlags > 0u32: + return true + + # get flags from build config + var confFlags: cint + if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0: + assert(false, "CONFIG_NEWLINE apparently got screwed up") + + case confFlags + of 13: return false + of 10: return false + of (13 shl 8) or 10: return true + of -2: return true + of -1: return true + else: return false + + +func captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern) + +func captures*(pattern: RegexMatch): Captures = return Captures(pattern) + +func contains*(pattern: CaptureBounds, i: int): bool = + let pattern = RegexMatch(pattern) + pattern.pcreMatchBounds[i + 1].a != -1 + +func contains*(pattern: Captures, i: int): bool = + i in CaptureBounds(pattern) + +func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] = + let pattern = RegexMatch(pattern) + if not (i in pattern.captureBounds): + raise newException(IndexDefect, "Group '" & $i & "' was not captured") + + let bounds = pattern.pcreMatchBounds[i + 1] + int(bounds.a)..int(bounds.b-1) + +func `[]`*(pattern: Captures, i: int): string = + let pattern = RegexMatch(pattern) + let bounds = pattern.captureBounds[i] + + pattern.str.substr(bounds.a, bounds.b) + +func match*(pattern: RegexMatch): string = + return pattern.captures[-1] + +func matchBounds*(pattern: RegexMatch): HSlice[int, int] = + return pattern.captureBounds[-1] + +func contains*(pattern: CaptureBounds, name: string): bool = + let pattern = RegexMatch(pattern) + let nameToId = pattern.pattern.captureNameToId + if not (name in nameToId): + return false + nameToId[name] in pattern.captureBounds + +func contains*(pattern: Captures, name: string): bool = + name in CaptureBounds(pattern) + +func checkNamedCaptured(pattern: RegexMatch, name: string) = + if not (name in pattern.captureBounds): + raise newException(KeyError, "Group '" & name & "' was not captured") + +func `[]`*(pattern: CaptureBounds, name: string): HSlice[int, int] = + let pattern = RegexMatch(pattern) + checkNamedCaptured(pattern, name) + {.noSideEffect.}: + result = pattern.captureBounds[pattern.pattern.captureNameToId[name]] + +func `[]`*(pattern: Captures, name: string): string = + let pattern = RegexMatch(pattern) + checkNamedCaptured(pattern, name) + {.noSideEffect.}: + result = pattern.captures[pattern.pattern.captureNameToId[name]] + +template toTableImpl() {.dirty.} = + for key in RegexMatch(pattern).pattern.captureNameId.keys: + if key in pattern: + result[key] = pattern[key] + +func toTable*(pattern: Captures): Table[string, string] = + result = initTable[string, string]() + toTableImpl() + +func toTable*(pattern: CaptureBounds): Table[string, HSlice[int, int]] = + result = initTable[string, HSlice[int, int]]() + toTableImpl() + +template itemsImpl() {.dirty.} = + for i in 0 ..< RegexMatch(pattern).pattern.captureCount: + # done in this roundabout way to avoid multiple yields (potential code + # bloat) + let nextYieldVal = if i in pattern: + some(pattern[i]) + else: + default + + yield nextYieldVal + +iterator items*(pattern: CaptureBounds, + default = none(HSlice[int, int])): Option[HSlice[int, int]] = + itemsImpl() + +iterator items*(pattern: Captures, + default: Option[string] = none(string)): Option[string] = + itemsImpl() + +proc toSeq*(pattern: CaptureBounds, + default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] = + result = @[] + for it in pattern.items(default): result.add it + +proc toSeq*(pattern: Captures, + default: Option[string] = none(string)): seq[Option[string]] = + result = @[] + for it in pattern.items(default): result.add it + +proc `$`*(pattern: RegexMatch): string = + return pattern.captures[-1] + +proc `==`*(a, b: Regex): bool = + if not a.isNil and not b.isNil: + return a.pattern == b.pattern and + a.pcreObj == b.pcreObj and + a.pcreExtra == b.pcreExtra + else: + return system.`==`(a, b) + +proc `==`*(a, b: RegexMatch): bool = + return a.pattern == b.pattern and + a.str == b.str + +const PcreOptions = { + "NEVER_UTF": pcre.NEVER_UTF, + "ANCHORED": pcre.ANCHORED, + "DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY, + "FIRSTLINE": pcre.FIRSTLINE, + "NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE, + "JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT, + "U": pcre.UTF8 or pcre.UCP +}.toTable + +# Options that are supported inside regular expressions themselves +const SkipOptions = [ + "LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT", + "UTF8", "UTF16", "UTF32", "UTF", "UCP", + "CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE" +] + +proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] = + result = ("", 0, true) + + var optionStart = 0 + var equals = false + for i, c in pattern: + if optionStart == i: + if c != '(': + break + optionStart = i + + elif optionStart == i-1: + if c != '*': + break + + elif c == ')': + let name = pattern[optionStart+2 .. i-1] + if equals or name in SkipOptions: + result.pattern.add pattern[optionStart .. i] + elif PcreOptions.hasKey name: + result.flags = result.flags or PcreOptions[name] + elif name == "NO_STUDY": + result.study = false + else: + break + optionStart = i+1 + equals = false + + elif not equals: + if c == '=': + equals = true + if pattern[optionStart+2 .. i] notin SkipOptions: + break + elif c notin {'A'..'Z', '0'..'9', '_'}: + break + + result.pattern.add pattern[optionStart .. pattern.high] + +proc re*(pattern: string): Regex = + let (pattern, flags, study) = extractOptions(pattern) + initRegex(pattern, flags, study) + +proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] = + var myResult = RegexMatch(pattern: pattern, str: str) + # See PCRE man pages. + # 2x capture count to make room for start-end pairs + # 1x capture count as slack space for PCRE + let vecsize = (pattern.captureCount() + 1) * 3 + # div 2 because each element is 2 cints long + # plus 1 because we need the ceiling, not the floor + myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]]((vecsize + 1) div 2) + myResult.pcreMatchBounds.setLen(vecsize div 3) + + let strlen = if endpos == int.high: str.len else: endpos+1 + doAssert(strlen <= str.len) # don't want buffer overflows + + let execRet = pcre.exec(pattern.pcreObj, + pattern.pcreExtra, + cstring(str), + cint(strlen), + cint(start), + cint(flags), + cast[ptr cint](addr myResult.pcreMatchBounds[0]), + cint(vecsize)) + if execRet >= 0: + return some(myResult) + + case execRet: + of pcre.ERROR_NOMATCH: + return none(RegexMatch) + of pcre.ERROR_NULL: + raise newException(AccessViolationDefect, "Expected non-null parameters") + of pcre.ERROR_BADOPTION: + raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " & + "outdated PCRE.") + of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET: + raise InvalidUnicodeError(msg: "Invalid unicode byte sequence", + pos: myResult.pcreMatchBounds[0].a) + else: + raise RegexInternalError(msg: "Unknown internal error: " & $execRet) + +proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = + ## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the + ## string. + runnableExamples: + assert "foo".match(re"f").isSome + assert "foo".match(re"o").isNone + + assert "abc".match(re"(\w)").get.captures[0] == "a" + assert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a" + assert "abc".match(re"(\w)\w").get.captures[-1] == "ab" + + assert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0 + assert 0 in "abc".match(re"(\w)").get.captureBounds + assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1 + assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2 + return str.matchImpl(pattern, start, endpos, pcre.ANCHORED) + +iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch = + ## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every + ## non-overlapping match: + runnableExamples: + import std/sugar + assert collect(for a in "2222".findIter(re"22"): a.match) == @["22", "22"] + # not @["22", "22", "22"] + ## Arguments are the same as `find(...)<#find,string,Regex,int>`_ + ## + ## Variants: + ## + ## - `proc findAll(...)` returns a `seq[string]` + # see pcredemo for explanation => https://www.pcre.org/original/doc/html/pcredemo.html + let matchesCrLf = pattern.matchesCrLf() + let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and + pcre.UTF8) > 0u32 + let strlen = if endpos == int.high: str.len else: endpos+1 + var offset = start + var match: Option[RegexMatch] + var neverMatched = true + + while true: + var flags = 0 + if match.isSome and + match.get.matchBounds.a > match.get.matchBounds.b: + # 0-len match + flags = pcre.NOTEMPTY_ATSTART + match = str.matchImpl(pattern, offset, endpos, flags) + + if match.isNone: + # either the end of the input or the string + # cannot be split here - we also need to bail + # if we've never matched and we've already tried to... + if flags == 0 or offset >= strlen or neverMatched: # All matches found + break + + if matchesCrLf and offset < (str.len - 1) and + str[offset] == '\r' and str[offset + 1] == '\L': + # if PCRE treats CrLf as newline, skip both at the same time + offset += 2 + elif unicode: + # XXX what about invalid unicode? + offset += str.runeLenAt(offset) + assert(offset <= strlen) + else: + offset += 1 + else: + neverMatched = false + offset = match.get.matchBounds.b + 1 + yield match.get + +proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = + ## Finds the given pattern in the string between the end and start + ## positions. + ## + ## `start` + ## : The start point at which to start matching. `|abc` is `0`; + ## `a|bc` is `1` + ## + ## `endpos` + ## : The maximum index for a match; `int.high` means the end of the + ## string, otherwise it’s an inclusive upper bound. + return str.matchImpl(pattern, start, endpos, 0) + +proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] = + result = @[] + for match in str.findIter(pattern, start, endpos): + result.add(match.match) + +proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool = + ## Determine if the string contains the given pattern between the end and + ## start positions: + ## This function is equivalent to `isSome(str.find(pattern, start, endpos))`. + runnableExamples: + assert "abc".contains(re"bc") + assert not "abc".contains(re"cd") + assert not "abc".contains(re"a", start = 1) + + return isSome(str.find(pattern, start, endpos)) + +proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] = + ## Splits the string with the given regex. This works according to the + ## rules that Perl and Javascript use. + ## + ## `start` behaves the same as in `find(...)<#find,string,Regex,int>`_. + ## + runnableExamples: + # - If the match is zero-width, then the string is still split: + assert "123".split(re"") == @["1", "2", "3"] + + # - If the pattern has a capture in it, it is added after the string + # split: + assert "12".split(re"(\d)") == @["", "1", "", "2", ""] + + # - If `maxsplit != -1`, then the string will only be split + # `maxsplit - 1` times. This means that there will be `maxsplit` + # strings in the output seq. + assert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"] + + result = @[] + var lastIdx = start + var splits = 0 + var bounds = 0 .. -1 + var never_ran = true + + for match in str.findIter(pattern, start = start): + never_ran = false + + # bounds are inclusive: + # + # 0123456 + # ^^^ + # (1, 3) + bounds = match.matchBounds + + # "12".split("") would be @["", "1", "2"], but + # if we skip an empty first match, it's the correct + # @["1", "2"] + if bounds.a <= bounds.b or bounds.a > start: + result.add(str.substr(lastIdx, bounds.a - 1)) + splits += 1 + + lastIdx = bounds.b + 1 + + for cap in match.captures: + # if there are captures, include them in the result + if cap.isSome: + result.add(cap.get) + + if splits == maxSplit - 1: + break + + # "12".split("\b") would be @["1", "2", ""], but + # if we skip an empty last match, it's the correct + # @["1", "2"] + # If matches were never found, then the input string is the result + if bounds.a <= bounds.b or bounds.b < str.high or never_ran: + # last match: Each match takes the previous substring, + # but "1 2".split(/ /) needs to return @["1", "2"]. + # This handles "2" + result.add(str.substr(bounds.b + 1, str.high)) + +template replaceImpl(str: string, pattern: Regex, + replacement: untyped) {.dirty.} = + # XXX seems very similar to split, maybe I can reduce code duplication + # somehow? + result = "" + var lastIdx = 0 + for match {.inject.} in str.findIter(pattern): + let bounds = match.matchBounds + result.add(str.substr(lastIdx, bounds.a - 1)) + let nextVal = replacement + result.add(nextVal) + + lastIdx = bounds.b + 1 + + result.add(str.substr(lastIdx, str.len - 1)) + return result + +proc replace*(str: string, pattern: Regex, + subproc: proc (match: RegexMatch): string): string = + ## Replaces each match of Regex in the string with `subproc`, which should + ## never be or return `nil`. + ## + ## If `subproc` is a `proc (RegexMatch): string`, then it is executed with + ## each match and the return value is the replacement value. + ## + ## If `subproc` is a `proc (string): string`, then it is executed with the + ## full text of the match and the return value is the replacement value. + ## + ## If `subproc` is a string, the syntax is as follows: + ## + ## - `$$` - literal `$` + ## - `$123` - capture number `123` + ## - `$foo` - named capture `foo` + ## - `${foo}` - same as above + ## - `$1$#` - first and second captures + ## - `$#` - first capture + ## - `$0` - full match + ## + ## If a given capture is missing, `IndexDefect` thrown for un-named captures + ## and `KeyError` for named captures. + replaceImpl(str, pattern, subproc(match)) + +proc replace*(str: string, pattern: Regex, + subproc: proc (match: string): string): string = + replaceImpl(str, pattern, subproc(match.match)) + +proc replace*(str: string, pattern: Regex, sub: string): string = + # - 1 because the string numbers are 0-indexed + replaceImpl(str, pattern, + formatStr(sub, match.captures[name], match.captures[id - 1])) + +proc escapeRe*(str: string): string {.gcsafe.} = + ## Escapes the string so it doesn't match any special characters. + ## Incompatible with the Extra flag (`X`). + ## + ## Escaped char: `\ + * ? [ ^ ] $ ( ) { } = ! < > | : -` + runnableExamples: + assert escapeRe("fly+wind") == "fly\\+wind" + assert escapeRe("!") == "\\!" + assert escapeRe("nim*") == "nim\\*" + + #([\\+*?[^\]$(){}=!<>|:-]) + const SpecialCharMatcher = {'\\', '+', '*', '?', '[', '^', ']', '$', '(', + ')', '{', '}', '=', '!', '<', '>', '|', ':', + '-'} + + for c in items(str): + case c + of SpecialCharMatcher: + result.add("\\") + result.add(c) + else: + result.add(c) diff --git a/lib/impure/nre/.gitignore b/lib/impure/nre/.gitignore new file mode 100644 index 000000000..3d647a25e --- /dev/null +++ b/lib/impure/nre/.gitignore @@ -0,0 +1,9 @@ +# all executables +* +!*/ +!*.* +*.exe + +# Wildcard patterns. +*.swp +nimcache diff --git a/lib/impure/nre/private/util.nim b/lib/impure/nre/private/util.nim new file mode 100644 index 000000000..ed8420776 --- /dev/null +++ b/lib/impure/nre/private/util.nim @@ -0,0 +1,51 @@ +## INTERNAL FILE FOR USE ONLY BY nre.nim. +import std/tables + +const Ident = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} +const StartIdent = Ident - {'0'..'9'} + +template formatStr*(howExpr, namegetter, idgetter): untyped = + let how = howExpr + var val = newStringOfCap(how.len) + var i = 0 + var lastNum = 1 + + while i < how.len: + if how[i] != '$': + val.add(how[i]) + i += 1 + else: + if how[i + 1] == '$': + val.add('$') + i += 2 + elif how[i + 1] == '#': + var id {.inject.} = lastNum + val.add(idgetter) + lastNum += 1 + i += 2 + elif how[i + 1] in {'0'..'9'}: + i += 1 + var id {.inject.} = 0 + while i < how.len and how[i] in {'0'..'9'}: + id += (id * 10) + (ord(how[i]) - ord('0')) + i += 1 + val.add(idgetter) + lastNum = id + 1 + elif how[i + 1] in StartIdent: + i += 1 + var name {.inject.} = "" + while i < how.len and how[i] in Ident: + name.add(how[i]) + i += 1 + val.add(namegetter) + elif how[i + 1] == '{': + i += 2 + var name {.inject.} = "" + while i < how.len and how[i] != '}': + name.add(how[i]) + i += 1 + i += 1 + val.add(namegetter) + else: + raise newException(ValueError, "Syntax error in format string at " & $i) + val diff --git a/lib/impure/osinfo_posix.nim b/lib/impure/osinfo_posix.nim deleted file mode 100755 index 4fde82012..000000000 --- a/lib/impure/osinfo_posix.nim +++ /dev/null @@ -1,68 +0,0 @@ -import posix, strutils, os - -type - Tstatfs {.importc: "struct statfs64", - header: "<sys/statfs.h>", final, pure.} = object - f_type: int - f_bsize: int - f_blocks: int - f_bfree: int - f_bavail: int - f_files: int - f_ffree: int - f_fsid: int - f_namelen: int - -proc statfs(path: string, buf: var Tstatfs): int {. - importc, header: "<sys/vfs.h>".} - - -proc getSystemVersion*(): string = - result = "" - - var unix_info: TUtsname - - if uname(unix_info) != 0: - os.OSError() - - if $unix_info.sysname == "Linux": - # Linux - result.add("Linux ") - - result.add($unix_info.release & " ") - result.add($unix_info.machine) - elif $unix_info.sysname == "Darwin": - # Darwin - result.add("Mac OS X ") - if "10" in $unix_info.release: - result.add("v10.6 Snow Leopard") - elif "9" in $unix_info.release: - result.add("v10.5 Leopard") - elif "8" in $unix_info.release: - result.add("v10.4 Tiger") - elif "7" in $unix_info.release: - result.add("v10.3 Panther") - elif "6" in $unix_info.release: - result.add("v10.2 Jaguar") - elif "1.4" in $unix_info.release: - result.add("v10.1 Puma") - elif "1.3" in $unix_info.release: - result.add("v10.0 Cheetah") - elif "0" in $unix_info.release: - result.add("Server 1.0 Hera") - else: - result.add($unix_info.sysname & " " & $unix_info.release) - - -when false: - var unix_info: TUtsname - echo(uname(unix_info)) - echo(unix_info.sysname) - echo("8" in $unix_info.release) - - echo(getSystemVersion()) - - var stfs: TStatfs - echo(statfs("sysinfo_posix.nim", stfs)) - echo(stfs.f_files) - diff --git a/lib/impure/osinfo_win.nim b/lib/impure/osinfo_win.nim deleted file mode 100755 index 46af1ddd6..000000000 --- a/lib/impure/osinfo_win.nim +++ /dev/null @@ -1,405 +0,0 @@ -# XXX clean up this mess! - -import winlean - -const - INVALID_HANDLE_VALUE = int(- 1) # GetStockObject - -type - TMEMORYSTATUSEX {.final, pure.} = object - dwLength: int32 - dwMemoryLoad: int32 - ullTotalPhys: int64 - ullAvailPhys: int64 - ullTotalPageFile: int64 - ullAvailPageFile: int64 - ullTotalVirtual: int64 - ullAvailVirtual: int64 - ullAvailExtendedVirtual: int64 - - SYSTEM_INFO* {.final, pure.} = object - wProcessorArchitecture*: int16 - wReserved*: int16 - dwPageSize*: int32 - lpMinimumApplicationAddress*: pointer - lpMaximumApplicationAddress*: pointer - dwActiveProcessorMask*: int32 - dwNumberOfProcessors*: int32 - dwProcessorType*: int32 - dwAllocationGranularity*: int32 - wProcessorLevel*: int16 - wProcessorRevision*: int16 - - LPSYSTEM_INFO* = ptr SYSTEM_INFO - TSYSTEMINFO* = SYSTEM_INFO - - TMemoryInfo* = object - MemoryLoad*: int ## occupied memory, in percent - TotalPhysMem*: int64 ## Total Physical memory, in bytes - AvailablePhysMem*: int64 ## Available physical memory, in bytes - TotalPageFile*: int64 ## The current committed memory limit - ## for the system or the current process, whichever is smaller, in bytes. - AvailablePageFile*: int64 ## The maximum amount of memory the current process can commit, in bytes. - TotalVirtualMem*: int64 ## Total virtual memory, in bytes - AvailableVirtualMem*: int64 ## Available virtual memory, in bytes - - TOSVERSIONINFOEX {.final, pure.} = object - dwOSVersionInfoSize: int32 - dwMajorVersion: int32 - dwMinorVersion: int32 - dwBuildNumber: int32 - dwPlatformId: int32 - szCSDVersion: array[0..127, char] - wServicePackMajor: int16 - wServicePackMinor: int16 - wSuiteMask: int16 - wProductType: int8 - wReserved: char - - TVersionInfo* = object - majorVersion*: int - minorVersion*: int - buildNumber*: int - platformID*: int - SPVersion*: string ## Full Service pack version string - SPMajor*: int ## Major service pack version - SPMinor*: int ## Minor service pack version - SuiteMask*: int - ProductType*: int - - TPartitionInfo* = tuple[FreeSpace, TotalSpace: Tfiletime] - -const - # SuiteMask - VersionInfo.SuiteMask - VER_SUITE_BACKOFFICE* = 0x00000004 - VER_SUITE_BLADE* = 0x00000400 - VER_SUITE_COMPUTE_SERVER* = 0x00004000 - VER_SUITE_DATACENTER* = 0x00000080 - VER_SUITE_ENTERPRISE* = 0x00000002 - VER_SUITE_EMBEDDEDNT* = 0x00000040 - VER_SUITE_PERSONAL* = 0x00000200 - VER_SUITE_SINGLEUSERTS* = 0x00000100 - VER_SUITE_SMALLBUSINESS* = 0x00000001 - VER_SUITE_SMALLBUSINESS_RESTRICTED* = 0x00000020 - VER_SUITE_STORAGE_SERVER* = 0x00002000 - VER_SUITE_TERMINAL* = 0x00000010 - VER_SUITE_WH_SERVER* = 0x00008000 - - # ProductType - VersionInfo.ProductType - VER_NT_DOMAIN_CONTROLLER* = 0x0000002 - VER_NT_SERVER* = 0x0000003 - VER_NT_WORKSTATION* = 0x0000001 - - VER_PLATFORM_WIN32_NT* = 2 - - # Product Info - getProductInfo() - (Remove unused ones ?) - PRODUCT_BUSINESS* = 0x00000006 - PRODUCT_BUSINESS_N* = 0x00000010 - PRODUCT_CLUSTER_SERVER* = 0x00000012 - PRODUCT_DATACENTER_SERVER* = 0x00000008 - PRODUCT_DATACENTER_SERVER_CORE* = 0x0000000C - PRODUCT_DATACENTER_SERVER_CORE_V* = 0x00000027 - PRODUCT_DATACENTER_SERVER_V* = 0x00000025 - PRODUCT_ENTERPRISE* = 0x00000004 - PRODUCT_ENTERPRISE_E* = 0x00000046 - PRODUCT_ENTERPRISE_N* = 0x0000001B - PRODUCT_ENTERPRISE_SERVER* = 0x0000000A - PRODUCT_ENTERPRISE_SERVER_CORE* = 0x0000000E - PRODUCT_ENTERPRISE_SERVER_CORE_V* = 0x00000029 - PRODUCT_ENTERPRISE_SERVER_IA64* = 0x0000000F - PRODUCT_ENTERPRISE_SERVER_V* = 0x00000026 - PRODUCT_HOME_BASIC* = 0x00000002 - PRODUCT_HOME_BASIC_E* = 0x00000043 - PRODUCT_HOME_BASIC_N* = 0x00000005 - PRODUCT_HOME_PREMIUM* = 0x00000003 - PRODUCT_HOME_PREMIUM_E* = 0x00000044 - PRODUCT_HOME_PREMIUM_N* = 0x0000001A - PRODUCT_HYPERV* = 0x0000002A - PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT* = 0x0000001E - PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING* = 0x00000020 - PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY* = 0x0000001F - PRODUCT_PROFESSIONAL* = 0x00000030 - PRODUCT_PROFESSIONAL_E* = 0x00000045 - PRODUCT_PROFESSIONAL_N* = 0x00000031 - PRODUCT_SERVER_FOR_SMALLBUSINESS* = 0x00000018 - PRODUCT_SERVER_FOR_SMALLBUSINESS_V* = 0x00000023 - PRODUCT_SERVER_FOUNDATION* = 0x00000021 - PRODUCT_SMALLBUSINESS_SERVER* = 0x00000009 - PRODUCT_STANDARD_SERVER* = 0x00000007 - PRODUCT_STANDARD_SERVER_CORE * = 0x0000000D - PRODUCT_STANDARD_SERVER_CORE_V* = 0x00000028 - PRODUCT_STANDARD_SERVER_V* = 0x00000024 - PRODUCT_STARTER* = 0x0000000B - PRODUCT_STARTER_E* = 0x00000042 - PRODUCT_STARTER_N* = 0x0000002F - PRODUCT_STORAGE_ENTERPRISE_SERVER* = 0x00000017 - PRODUCT_STORAGE_EXPRESS_SERVER* = 0x00000014 - PRODUCT_STORAGE_STANDARD_SERVER* = 0x00000015 - PRODUCT_STORAGE_WORKGROUP_SERVER* = 0x00000016 - PRODUCT_UNDEFINED* = 0x00000000 - PRODUCT_ULTIMATE* = 0x00000001 - PRODUCT_ULTIMATE_E* = 0x00000047 - PRODUCT_ULTIMATE_N* = 0x0000001C - PRODUCT_WEB_SERVER* = 0x00000011 - PRODUCT_WEB_SERVER_CORE* = 0x0000001D - - PROCESSOR_ARCHITECTURE_AMD64* = 9 ## x64 (AMD or Intel) - PROCESSOR_ARCHITECTURE_IA64* = 6 ## Intel Itanium Processor Family (IPF) - PROCESSOR_ARCHITECTURE_INTEL* = 0 ## x86 - PROCESSOR_ARCHITECTURE_UNKNOWN* = 0xffff ## Unknown architecture. - - # GetSystemMetrics - SM_SERVERR2 = 89 - -proc GlobalMemoryStatusEx*(lpBuffer: var TMEMORYSTATUSEX){.stdcall, dynlib: "kernel32", - importc: "GlobalMemoryStatusEx".} - -proc getMemoryInfo*(): TMemoryInfo = - ## Retrieves memory info - var statex: TMEMORYSTATUSEX - statex.dwLength = sizeof(statex).int32 - - GlobalMemoryStatusEx(statex) - result.MemoryLoad = statex.dwMemoryLoad - result.TotalPhysMem = statex.ullTotalPhys - result.AvailablePhysMem = statex.ullAvailPhys - result.TotalPageFile = statex.ullTotalPageFile - result.AvailablePageFile = statex.ullAvailPageFile - result.TotalVirtualMem = statex.ullTotalVirtual - result.AvailableVirtualMem = statex.ullAvailExtendedVirtual - -proc GetVersionEx*(lpVersionInformation: var TOSVERSIONINFOEX): WINBOOL{.stdcall, - dynlib: "kernel32", importc: "GetVersionExA".} - -proc GetProcAddress*(hModule: int, lpProcName: cstring): pointer{.stdcall, - dynlib: "kernel32", importc: "GetProcAddress".} - -proc GetModuleHandleA*(lpModuleName: cstring): int{.stdcall, - dynlib: "kernel32", importc.} - -proc getVersionInfo*(): TVersionInfo = - ## Retrieves operating system info - var osvi: TOSVERSIONINFOEX - osvi.dwOSVersionInfoSize = sizeof(osvi).int32 - discard GetVersionEx(osvi) - result.majorVersion = osvi.dwMajorVersion - result.minorVersion = osvi.dwMinorVersion - result.buildNumber = osvi.dwBuildNumber - result.platformID = osvi.dwPlatformId - result.SPVersion = $osvi.szCSDVersion - result.SPMajor = osvi.wServicePackMajor - result.SPMinor = osvi.wServicePackMinor - result.SuiteMask = osvi.wSuiteMask - result.ProductType = osvi.wProductType - -proc getProductInfo*(majorVersion, minorVersion, SPMajorVersion, - SPMinorVersion: int): int = - ## Retrieves Windows' ProductInfo, this function only works in Vista and 7 - - var pGPI = cast[proc (dwOSMajorVersion, dwOSMinorVersion, - dwSpMajorVersion, dwSpMinorVersion: int32, outValue: Pint32)](GetProcAddress( - GetModuleHandleA("kernel32.dll"), "GetProductInfo")) - - if pGPI != nil: - var dwType: int32 - pGPI(int32(majorVersion), int32(minorVersion), int32(SPMajorVersion), int32(SPMinorVersion), addr(dwType)) - result = int(dwType) - else: - return PRODUCT_UNDEFINED - -proc GetSystemInfo*(lpSystemInfo: LPSYSTEM_INFO){.stdcall, dynlib: "kernel32", - importc: "GetSystemInfo".} - -proc getSystemInfo*(): TSYSTEM_INFO = - ## Returns the SystemInfo - - # Use GetNativeSystemInfo if it's available - var pGNSI = cast[proc (lpSystemInfo: LPSYSTEM_INFO)](GetProcAddress( - GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo")) - - var systemi: TSYSTEM_INFO - if pGNSI != nil: - pGNSI(addr(systemi)) - else: - GetSystemInfo(addr(systemi)) - - return systemi - -proc GetSystemMetrics*(nIndex: int32): int32{.stdcall, dynlib: "user32", - importc: "GetSystemMetrics".} - -proc `$`*(osvi: TVersionInfo): string = - ## Turns a VersionInfo object, into a string - - if osvi.platformID == VER_PLATFORM_WIN32_NT and osvi.majorVersion > 4: - result = "Microsoft " - - var si = getSystemInfo() - # Test for the specific product - if osvi.majorVersion == 6: - if osvi.minorVersion == 0: - if osvi.ProductType == VER_NT_WORKSTATION: - result.add("Windows Vista ") - else: result.add("Windows Server 2008 ") - elif osvi.minorVersion == 1: - if osvi.ProductType == VER_NT_WORKSTATION: - result.add("Windows 7 ") - else: result.add("Windows Server 2008 R2 ") - - var dwType = getProductInfo(osvi.majorVersion, osvi.minorVersion, 0, 0) - case dwType - of PRODUCT_ULTIMATE: - result.add("Ultimate Edition") - of PRODUCT_PROFESSIONAL: - result.add("Professional") - of PRODUCT_HOME_PREMIUM: - result.add("Home Premium Edition") - of PRODUCT_HOME_BASIC: - result.add("Home Basic Edition") - of PRODUCT_ENTERPRISE: - result.add("Enterprise Edition") - of PRODUCT_BUSINESS: - result.add("Business Edition") - of PRODUCT_STARTER: - result.add("Starter Edition") - of PRODUCT_CLUSTER_SERVER: - result.add("Cluster Server Edition") - of PRODUCT_DATACENTER_SERVER: - result.add("Datacenter Edition") - of PRODUCT_DATACENTER_SERVER_CORE: - result.add("Datacenter Edition (core installation)") - of PRODUCT_ENTERPRISE_SERVER: - result.add("Enterprise Edition") - of PRODUCT_ENTERPRISE_SERVER_CORE: - result.add("Enterprise Edition (core installation)") - of PRODUCT_ENTERPRISE_SERVER_IA64: - result.add("Enterprise Edition for Itanium-based Systems") - of PRODUCT_SMALLBUSINESS_SERVER: - result.add("Small Business Server") - of PRODUCT_STANDARD_SERVER: - result.add("Standard Edition") - of PRODUCT_STANDARD_SERVER_CORE: - result.add("Standard Edition (core installation)") - of PRODUCT_WEB_SERVER: - result.add("Web Server Edition") - else: - nil - # End of Windows 6.* - - if osvi.majorVersion == 5 and osvi.minorVersion == 2: - if GetSystemMetrics(SM_SERVERR2) != 0: - result.add("Windows Server 2003 R2, ") - elif (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0: # Not sure if this will work - result.add("Windows Storage Server 2003") - elif (osvi.SuiteMask and VER_SUITE_WH_SERVER) != 0: - result.add("Windows Home Server") - elif osvi.ProductType == VER_NT_WORKSTATION and - si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64: - result.add("Windows XP Professional x64 Edition") - else: - result.add("Windows Server 2003, ") - - # Test for the specific product - if osvi.ProductType != VER_NT_WORKSTATION: - if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_IA64: - if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0: - result.add("Datacenter Edition for Itanium-based Systems") - elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0: - result.add("Enterprise Edition for Itanium-based Systems") - elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64: - if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0: - result.add("Datacenter x64 Edition") - elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0: - result.add("Enterprise x64 Edition") - else: - result.add("Standard x64 Edition") - else: - if (osvi.SuiteMask and VER_SUITE_COMPUTE_SERVER) != 0: - result.add("Compute Cluster Edition") - elif (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0: - result.add("Datacenter Edition") - elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0: - result.add("Enterprise Edition") - elif (osvi.SuiteMask and VER_SUITE_BLADE) != 0: - result.add("Web Edition") - else: - result.add("Standard Edition") - # End of 5.2 - - if osvi.majorVersion == 5 and osvi.minorVersion == 1: - result.add("Windows XP ") - if (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0: - result.add("Home Edition") - else: - result.add("Professional") - # End of 5.1 - - if osvi.majorVersion == 5 and osvi.minorVersion == 0: - result.add("Windows 2000 ") - if osvi.ProductType == VER_NT_WORKSTATION: - result.add("Professional") - else: - if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0: - result.add("Datacenter Server") - elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0: - result.add("Advanced Server") - else: - result.add("Server") - # End of 5.0 - - # Include service pack (if any) and build number. - if len(osvi.SPVersion) > 0: - result.add(" ") - result.add(osvi.SPVersion) - - result.add(" (build " & $osvi.buildNumber & ")") - - if osvi.majorVersion >= 6: - if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64: - result.add(", 64-bit") - elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_INTEL: - result.add(", 32-bit") - - else: - # Windows 98 etc... - result = "Unknown version of windows[Kernel version <= 4]" - - -proc getFileSize*(file: string): biggestInt = - var fileData: TWIN32_FIND_DATA - - when useWinUnicode: - var aa = allocWideCString(file) - var hFile = FindFirstFileW(aa, fileData) - dealloc aa - else: - var hFile = FindFirstFileA(file, fileData) - - if hFile == INVALID_HANDLE_VALUE: - raise newException(EIO, $GetLastError()) - - return fileData.nFileSizeLow - -proc GetDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller, - lpTotalNumberOfBytes, - lpTotalNumberOfFreeBytes: var TFiletime): WINBOOL{. - stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".} - -proc getPartitionInfo*(partition: string): TPartitionInfo = - ## Retrieves partition info, for example ``partition`` may be ``"C:\"`` - var FreeBytes, TotalBytes, TotalFreeBytes: TFiletime - var res = GetDiskFreeSpaceEx(r"C:\", FreeBytes, TotalBytes, - TotalFreeBytes) - return (FreeBytes, TotalBytes) - -when isMainModule: - var r = getMemoryInfo() - echo("Memory load: ", r.MemoryLoad, "%") - - var osvi = getVersionInfo() - - echo($osvi) - - echo(getFileSize(r"osinfo_win.nim") div 1024 div 1024) - - echo(rdFileTime(getPartitionInfo(r"C:\")[0])) diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim index cf076e929..f4fc26380 100755..100644 --- a/lib/impure/rdstdin.nim +++ b/lib/impure/rdstdin.nim @@ -1,62 +1,74 @@ # # -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # -## This module contains code for reading from `stdin`:idx:. On UNIX the GNU -## readline library is wrapped and set up to provide default key bindings -## (e.g. you can navigate with the arrow keys). On Windows ``system.readLine`` -## is used. This suffices because Windows' console already provides the +## This module contains code for reading from `stdin`:idx:. On UNIX the +## linenoise library is wrapped and set up to provide default key bindings +## (e.g. you can navigate with the arrow keys). On Windows `system.readLine` +## is used. This suffices because Windows' console already provides the ## wanted functionality. -when defined(Windows): - proc ReadLineFromStdin*(prompt: string): TaintedString {. - tags: [FReadIO, FWriteIO].} = +runnableExamples("-r:off"): + echo readLineFromStdin("Is Nim awesome? (Y/n): ") + var line: string + while true: + let ok = readLineFromStdin("How are you? ", line) + if not ok: break # ctrl-C or ctrl-D will cause a break + if line.len > 0: echo line + echo "exiting" + + +when defined(windows): + when defined(nimPreviewSlimSystem): + import std/syncio + + proc readLineFromStdin*(prompt: string): string {. + tags: [ReadIOEffect, WriteIOEffect].} = ## Reads a line from stdin. stdout.write(prompt) + stdout.flushFile() result = readLine(stdin) - proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool {. - tags: [FReadIO, FWriteIO].} = + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = ## Reads a `line` from stdin. `line` must not be - ## ``nil``! May throw an IO exception. - ## A line of text may be delimited by ``CR``, ``LF`` or - ## ``CRLF``. The newline character(s) are not part of the returned string. - ## Returns ``false`` if the end of the file has been reached, ``true`` - ## otherwise. If ``false`` is returned `line` contains no new data. + ## `nil`! May throw an IO exception. + ## A line of text may be delimited by `CR`, `LF` or + ## `CRLF`. The newline character(s) are not part of the returned string. + ## Returns `false` if the end of the file has been reached, `true` + ## otherwise. If `false` is returned `line` contains no new data. stdout.write(prompt) result = readLine(stdin, line) +elif defined(genode): + proc readLineFromStdin*(prompt: string): string {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine() + + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine(line) + else: - import readline, history - - proc ReadLineFromStdin*(prompt: string): TaintedString {. - tags: [FReadIO, FWriteIO].} = - var buffer = readline.readLine(prompt) - if isNil(buffer): quit(0) - result = TaintedString($buffer) - if result.string.len > 0: - add_history(buffer) - readline.free(buffer) - - proc ReadLineFromStdin*(prompt: string, line: var TaintedString): bool {. - tags: [FReadIO, FWriteIO].} = - var buffer = readline.readLine(prompt) - if isNil(buffer): quit(0) - line = TaintedString($buffer) - if line.string.len > 0: - add_history(buffer) - readline.free(buffer) - # XXX how to determine CTRL+D? - result = true + import std/linenoise - # initialization: - # disable auto-complete: - proc doNothing(a, b: cint): cint {.cdecl, procvar.} = nil - - discard readline.bind_key('\t'.ord, doNothing) + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + var buffer = linenoise.readLine(prompt) + if isNil(buffer): + line.setLen(0) + return false + line = $buffer + if line.len > 0: + historyAdd(buffer) + linenoise.free(buffer) + result = true + proc readLineFromStdin*(prompt: string): string {.inline.} = + if not readLineFromStdin(prompt, result): + raise newException(IOError, "Linenoise returned nil") diff --git a/lib/impure/re.nim b/lib/impure/re.nim index 57dc3a313..053c6ab55 100755..100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -1,126 +1,218 @@ # # -# 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. # -## Regular expression support for Nimrod. Consider using the pegs module -## instead. +when defined(js): + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} + +## Regular expression support for Nim. +## ## This module is implemented by providing a wrapper around the -## `PRCE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ -## C library. This means that your application will depend on the PRCE +## `PCRE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ +## C library. This means that your application will depend on the PCRE ## library's licence when using this module, which should not be a problem ## though. -## PRCE's licence follows: ## -## .. include:: ../doc/regexprs.txt +## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre) +## and [regex](https://github.com/nitely/nim-regex). +## +## PCRE's licence follows: +## +## .. include:: ../../doc/regexprs.txt ## +runnableExamples: + ## Unless specified otherwise, `start` parameter in each proc indicates + ## where the scan starts, but outputs are relative to the start of the input + ## string, not to `start`: + doAssert find("uxabc", re"(?<=x|y)ab", start = 1) == 2 # lookbehind assertion + doAssert find("uxabc", re"ab", start = 3) == -1 # we're past `start` => not found + doAssert not match("xabc", re"^abc$", start = 1) + # can't match start of string since we're starting at 1 + import - pcre, strutils + std/[pcre, strutils, rtarrays] + +when defined(nimPreviewSlimSystem): + import std/syncio const - MaxSubpatterns* = 10 + MaxSubpatterns* = 20 ## defines the maximum number of subpatterns that can be captured. - ## More subpatterns cannot be captured! + ## This limit still exists for `replacef` and `parallelReplace`. type - TRegExFlag* = enum ## options for regular expressions - reIgnoreCase = 0, ## do caseless matching - reMultiLine = 1, ## ``^`` and ``$`` match newlines within data - reDotAll = 2, ## ``.`` matches anything including NL - reExtended = 3, ## ignore whitespace and ``#`` comments - reStudy = 4 ## study the expression (may be omitted if the - ## expression will be used only once) - - TRegExDesc {.pure, final.} = object - h: PPcre - e: ptr TExtra - - TRegEx* = ref TRegExDesc ## a compiled regular expression - - EInvalidRegEx* = object of EInvalidValue + RegexFlag* = enum ## options for regular expressions + reIgnoreCase = 0, ## do caseless matching + reMultiLine = 1, ## `^` and `$` match newlines within data + reDotAll = 2, ## `.` matches anything including NL + reExtended = 3, ## ignore whitespace and `#` comments + reStudy = 4 ## study the expression (may be omitted if the + ## expression will be used only once) + + RegexDesc = object + h: ptr Pcre + e: ptr ExtraData + + Regex* = ref RegexDesc ## a compiled regular expression + + RegexError* = object of ValueError ## is raised if the pattern is no valid regular expression. -proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} = - var e: ref EInvalidRegEx +when defined(gcDestructors): + when defined(nimAllowNonVarDestructor): + proc `=destroy`(x: RegexDesc) = + pcre.free_substring(cast[cstring](x.h)) + if not isNil(x.e): + pcre.free_study(x.e) + else: + proc `=destroy`(x: var RegexDesc) = + pcre.free_substring(cast[cstring](x.h)) + if not isNil(x.e): + pcre.free_study(x.e) + +proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} = + var e: ref RegexError new(e) e.msg = msg raise e - -proc rawCompile(pattern: string, flags: cint): PPcre = + +proc rawCompile(pattern: string, flags: cint): ptr Pcre = var - msg: CString - offset: cint - result = pcre.Compile(pattern, flags, addr(msg), addr(offset), nil) + msg: cstring = "" + offset: cint = 0 + result = pcre.compile(pattern, flags, addr(msg), addr(offset), nil) if result == nil: - raiseInvalidRegEx($msg & "\n" & pattern & "\n" & repeatChar(offset) & "^\n") + raiseInvalidRegex($msg & "\n" & pattern & "\n" & spaces(offset) & "^\n") -proc finalizeRegEx(x: TRegEx) = +proc finalizeRegEx(x: Regex) = # XXX This is a hack, but PCRE does not export its "free" function properly. - # Sigh. The hack relies on PCRE's implementation (see ``pcre_get.c``). - # Fortunately the implementation is unlikely to change. + # Sigh. The hack relies on PCRE's implementation (see `pcre_get.c`). + # Fortunately the implementation is unlikely to change. pcre.free_substring(cast[cstring](x.h)) if not isNil(x.e): - pcre.free_substring(cast[cstring](x.e)) + pcre.free_study(x.e) -proc re*(s: string, flags = {reExtended, reStudy}): TRegEx = - ## Constructor of regular expressions. Note that Nimrod's - ## extended raw string literals support this syntax ``re"[abc]"`` as - ## a short form for ``re(r"[abc]")``. - new(result, finalizeRegEx) +proc re*(s: string, flags = {reStudy}): Regex = + ## Constructor of regular expressions. + ## + ## Note that Nim's + ## extended raw string literals support the syntax `re"[abc]"` as + ## a short form for `re(r"[abc]")`. Also note that since this + ## compiles the regular expression, which is expensive, you should + ## avoid putting it directly in the arguments of the functions like + ## the examples show below if you plan to use it a lot of times, as + ## this will hurt performance immensely. (e.g. outside a loop, ...) + when defined(gcDestructors): + result = Regex() + else: + new(result, finalizeRegEx) result.h = rawCompile(s, cast[cint](flags - {reStudy})) if reStudy in flags: - var msg: cstring - result.e = pcre.study(result.h, 0, msg) + var msg: cstring = "" + var options: cint = 0 + var hasJit: cint = 0 + if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0: + if hasJit == 1'i32: + options = pcre.STUDY_JIT_COMPILE + result.e = pcre.study(result.h, options, addr msg) if not isNil(msg): raiseInvalidRegex($msg) -proc matchOrFind(s: string, pattern: TRegEx, matches: var openarray[string], - start, flags: cint): cint = +proc rex*(s: string, flags = {reStudy, reExtended}): Regex = + ## Constructor for extended regular expressions. + ## + ## The extended means that comments starting with `#` and + ## whitespace are ignored. + result = re(s, flags) + +proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} = + ## Return a Nim string built from a slice of a cstring buffer. + ## Don't assume cstring is '\0' terminated + let sz = ePos - sPos + result = newString(sz+1) + copyMem(addr(result[0]), unsafeAddr(b[sPos]), sz) + result.setLen(sz) + +proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string], + start, bufSize, flags: cint): cint = var - rawMatches: array[0..maxSubpatterns * 3 - 1, cint] - res = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, start, flags, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize, start, flags, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) if res < 0'i32: return res for i in 1..int(res)-1: var a = rawMatches[i * 2] var b = rawMatches[i * 2 + 1] - if a >= 0'i32: matches[i-1] = substr(s, int(a), int(b)-1) + if a >= 0'i32: + matches[i-1] = bufSubstr(buf, int(a), int(b)) else: matches[i-1] = "" return rawMatches[1] - rawMatches[0] - -proc findBounds*(s: string, pattern: TRegEx, matches: var openarray[string], - start = 0): tuple[first, last: int] = - ## returns the starting position and end position of `pattern` in `s` + +const MaxReBufSize* = high(cint) + ## Maximum PCRE (API 1) buffer start/size equal to `high(cint)`, which even + ## for 64-bit systems can be either 2`31`:sup:-1 or 2`63`:sup:-1. + +proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the starting position and end position of `pattern` in `buf` + ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated), ## and the captured ## substrings in the array `matches`. If it does not match, nothing - ## is written into `matches` and ``(-1,0)`` is returned. + ## is written into `matches` and `(-1,0)` is returned. + ## + ## Note: The memory for `matches` needs to be allocated before this function is + ## called, otherwise it will just remain empty. var - rawMatches: array[0..maxSubpatterns * 3 - 1, cint] - res = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, start.cint, 0'i32, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) if res < 0'i32: return (-1, 0) for i in 1..int(res)-1: var a = rawMatches[i * 2] var b = rawMatches[i * 2 + 1] - if a >= 0'i32: matches[i-1] = substr(s, int(a), int(b)-1) + if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) else: matches[i-1] = "" return (rawMatches[0].int, rawMatches[1].int - 1) - -proc findBounds*(s: string, pattern: TRegEx, - matches: var openarray[tuple[first, last: int]], - start = 0): tuple[first, last: int] = - ## returns the starting position and end position of ``pattern`` in ``s`` - ## and the captured substrings in the array `matches`. + +proc findBounds*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): tuple[first, last: int] {.inline.} = + ## returns the starting position and end position of `pattern` in `s` + ## and the captured substrings in the array `matches`. + ## If it does not match, nothing + ## is written into `matches` and `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + var matches = newSeq[string](1) + let (first, last) = findBounds("Hello World", re"(W\w+)", matches) + doAssert first == 6 + doAssert last == 10 + doAssert matches[0] == "World" + result = findBounds(cstring(s), pattern, matches, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc findBounds*(buf: cstring, pattern: Regex, + matches: var openArray[tuple[first, last: int]], + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the starting position and end position of `pattern` in `buf` + ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated), + ## and the captured substrings in the array `matches`. ## If it does not match, nothing is written into `matches` and - ## ``(-1,0)`` is returned. + ## `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. var - rawMatches: array[0..maxSubpatterns * 3 - 1, cint] - res = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, start.cint, 0'i32, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) if res < 0'i32: return (-1, 0) for i in 1..int(res)-1: var a = rawMatches[i * 2] @@ -129,206 +221,322 @@ proc findBounds*(s: string, pattern: TRegEx, else: matches[i-1] = (-1,0) return (rawMatches[0].int, rawMatches[1].int - 1) -proc findBounds*(s: string, pattern: TRegEx, - start = 0): tuple[first, last: int] = - ## returns the starting position of `pattern` in `s`. If it does not - ## match, ``(-1,0)`` is returned. +proc findBounds*(s: string, pattern: Regex, + matches: var openArray[tuple[first, last: int]], + start = 0): tuple[first, last: int] {.inline.} = + ## returns the starting position and end position of `pattern` in `s` + ## and the captured substrings in the array `matches`. + ## If it does not match, nothing is written into `matches` and + ## `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + var matches = newSeq[tuple[first, last: int]](1) + let (first, last) = findBounds("Hello World", re"(\w+)", matches) + doAssert first == 0 + doAssert last == 4 + doAssert matches[0] == (0, 4) + result = findBounds(cstring(s), pattern, matches, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc findBoundsImpl(buf: cstring, pattern: Regex, + start = 0, bufSize = 0, flags = 0): tuple[first, last: int] = + var rtarray = initRtArray[cint](3) + let rawMatches = rtarray.getRawData + let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags.int32, + cast[ptr cint](rawMatches), 3) + + if res < 0'i32: + result = (-1, 0) + else: + result = (int(rawMatches[0]), int(rawMatches[1]-1)) + +proc findBounds*(buf: cstring, pattern: Regex, + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the `first` and `last` position of `pattern` in `buf`, + ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## If it does not match, `(-1,0)` is returned. var - rawMatches: array[0..3 - 1, cint] - res = pcre.Exec(pattern.h, nil, s, len(s).cint, start.cint, 0'i32, - cast[ptr cint](addr(rawMatches)), 3) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), 3) if res < 0'i32: return (int(res), 0) return (int(rawMatches[0]), int(rawMatches[1]-1)) - -proc matchOrFind(s: string, pattern: TRegEx, start, flags: cint): cint = - var rawMatches: array [0..maxSubpatterns * 3 - 1, cint] - result = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, start, flags, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + +proc findBounds*(s: string, pattern: Regex, + start = 0): tuple[first, last: int] {.inline.} = + ## returns the `first` and `last` position of `pattern` in `s`. + ## If it does not match, `(-1,0)` is returned. + ## + ## Note: there is a speed improvement if the matches do not need to be captured. + runnableExamples: + assert findBounds("01234abc89", re"abc") == (5,7) + result = findBounds(cstring(s), pattern, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int, flags: cint): cint = + var + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + result = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags, + cast[ptr cint](rawMatches), 3) if result >= 0'i32: result = rawMatches[1] - rawMatches[0] -proc match*(s: string, pattern: TRegEx, matches: var openarray[string], - start = 0): bool = - ## returns ``true`` if ``s[start..]`` matches the ``pattern`` and - ## the captured substrings in the array ``matches``. If it does not - ## match, nothing is written into ``matches`` and ``false`` is - ## returned. - return matchOrFind(s, pattern, matches, start.cint, - pcre.ANCHORED) == cint(s.len - start) +proc matchLen*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = matchOrFind(cstring(s), pattern, matches, start.cint, s.len.cint, pcre.ANCHORED) + +proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + return matchOrFind(buf, pattern, matches, start.cint, bufSize.cint, pcre.ANCHORED) -proc match*(s: string, pattern: TRegEx, start = 0): bool = - ## returns ``true`` if ``s[start..]`` matches the ``pattern``. - return matchOrFind(s, pattern, start.cint, pcre.ANCHORED) == cint(s.len-start) +proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + runnableExamples: + doAssert matchLen("abcdefg", re"cde", 2) == 3 + doAssert matchLen("abcdefg", re"abcde") == 5 + doAssert matchLen("abcdefg", re"cde") == -1 + result = matchOrFind(cstring(s), pattern, start.cint, s.len.cint, pcre.ANCHORED) -proc matchLen*(s: string, pattern: TRegEx, matches: var openarray[string], - start = 0): int = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length +proc matchLen*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length ## of zero can happen. - return matchOrFind(s, pattern, matches, start.cint, pcre.ANCHORED) - -proc matchLen*(s: string, pattern: TRegEx, start = 0): int = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. - return matchOrFind(s, pattern, start.cint, pcre.ANCHORED) - -proc find*(s: string, pattern: TRegEx, matches: var openarray[string], - start = 0): int = - ## returns the starting position of ``pattern`` in ``s`` and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and -1 is returned. + result = matchOrFind(buf, pattern, start.cint, bufSize, pcre.ANCHORED) + +proc match*(s: string, pattern: Regex, start = 0): bool {.inline.} = + ## returns `true` if `s[start..]` matches the `pattern`. + result = matchLen(cstring(s), pattern, start, s.len) != -1 + +proc match*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): bool {.inline.} = + ## returns `true` if `s[start..]` matches the `pattern` and + ## the captured substrings in the array `matches`. If it does not + ## match, nothing is written into `matches` and `false` is + ## returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + import std/sequtils + var matches: array[2, string] + if match("abcdefg", re"c(d)ef(g)", matches, 2): + doAssert toSeq(matches) == @["d", "g"] + result = matchLen(cstring(s), pattern, matches, start, s.len) != -1 + +proc match*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): bool {.inline.} = + ## returns `true` if `buf[start..<bufSize]` matches the `pattern` and + ## the captured substrings in the array `matches`. If it does not + ## match, nothing is written into `matches` and `false` is + ## returned. + ## `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = matchLen(buf, pattern, matches, start, bufSize) != -1 + +proc find*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): int = + ## returns the starting position of `pattern` in `buf` and the captured + ## substrings in the array `matches`. If it does not match, nothing + ## is written into `matches` and `-1` is returned. + ## `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. var - rawMatches: array[0..maxSubpatterns * 3 - 1, cint] - res = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, start.cint, 0'i32, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) if res < 0'i32: return res for i in 1..int(res)-1: var a = rawMatches[i * 2] var b = rawMatches[i * 2 + 1] - if a >= 0'i32: matches[i-1] = substr(s, int(a), int(b)-1) + if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) else: matches[i-1] = "" return rawMatches[0] -proc find*(s: string, pattern: TRegEx, start = 0): int = - ## returns the starting position of ``pattern`` in ``s``. If it does not - ## match, -1 is returned. +proc find*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): int {.inline.} = + ## returns the starting position of `pattern` in `s` and the captured + ## substrings in the array `matches`. If it does not match, nothing + ## is written into `matches` and `-1` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = find(cstring(s), pattern, matches, start, s.len) + +proc find*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int = + ## returns the starting position of `pattern` in `buf`, + ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## If it does not match, `-1` is returned. var - rawMatches: array[0..3 - 1, cint] - res = pcre.Exec(pattern.h, nil, s, len(s).cint, start.cint, 0'i32, - cast[ptr cint](addr(rawMatches)), 3) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), 3) if res < 0'i32: return res return rawMatches[0] - -iterator findAll*(s: string, pattern: TRegEx, start = 0): string = - ## yields all matching *substrings* of `s` that match `pattern`. - var i = int32(start) - var rawMatches: array[0..maxSubpatterns * 3 - 1, cint] + +proc find*(s: string, pattern: Regex, start = 0): int {.inline.} = + ## returns the starting position of `pattern` in `s`. If it does not + ## match, `-1` is returned. We start the scan at `start`. + runnableExamples: + doAssert find("abcdefg", re"cde") == 2 + doAssert find("abcdefg", re"abc") == 0 + doAssert find("abcdefg", re"zz") == -1 # not found + doAssert find("abcdefg", re"cde", start = 2) == 2 # still 2 + doAssert find("abcdefg", re"cde", start = 3) == -1 # we're past the start position + doAssert find("xabc", re"(?<=x|y)abc", start = 1) == 1 + # lookbehind assertion `(?<=x|y)` can look behind `start` + result = find(cstring(s), pattern, start, s.len) + +iterator findAll*(s: string, pattern: Regex, start = 0): string = + ## Yields all matching *substrings* of `s` that match `pattern`. + ## + ## Note that since this is an iterator you should not modify the string you + ## are iterating over: bad things could happen. + var + i = int32(start) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData while true: - let res = pcre.Exec(pattern.h, pattern.e, s, len(s).cint, i, 0'i32, - cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3) + let res = pcre.exec(pattern.h, pattern.e, s, len(s).cint, i, 0'i32, + cast[ptr cint](rawMatches), 3) if res < 0'i32: break let a = rawMatches[0] let b = rawMatches[1] + if a == b and a == i: break yield substr(s, int(a), int(b)-1) i = b -proc findAll*(s: string, pattern: TRegEx, start = 0): seq[string] = - ## returns all matching *substrings* of `s` that match `pattern`. - ## If it does not match, @[] is returned. - accumulateResult(findAll(s, pattern, start)) +iterator findAll*(buf: cstring, pattern: Regex, start = 0, bufSize: int): string = + ## Yields all matching `substrings` of `s` that match `pattern`. + ## + ## Note that since this is an iterator you should not modify the string you + ## are iterating over: bad things could happen. + var + i = int32(start) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + while true: + let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, i, 0'i32, + cast[ptr cint](rawMatches), 3) + if res < 0'i32: break + let a = rawMatches[0] + let b = rawMatches[1] + if a == b and a == i: break + var str = newString(b-a) + copyMem(str[0].addr, unsafeAddr(buf[a]), b-a) + yield str + i = b -when not defined(nimhygiene): - {.pragma: inject.} +proc findAll*(s: string, pattern: Regex, start = 0): seq[string] {.inline.} = + ## returns all matching `substrings` of `s` that match `pattern`. + ## If it does not match, `@[]` is returned. + result = @[] + for x in findAll(s, pattern, start): result.add x -template `=~` *(s: string, pattern: TRegEx): expr = - ## This calls ``match`` with an implicit declared ``matches`` array that - ## can be used in the scope of the ``=~`` call: - ## - ## .. code-block:: nimrod - ## - ## if line =~ re"\s*(\w+)\s*\=\s*(\w+)": - ## # matches a key=value pair: - ## echo("Key: ", matches[0]) - ## echo("Value: ", matches[1]) - ## elif line =~ re"\s*(\#.*)": - ## # matches a comment - ## # note that the implicit ``matches`` array is different from the - ## # ``matches`` array of the first branch - ## echo("comment: ", matches[0]) - ## else: - ## echo("syntax error") - ## - when not definedInScope(matches): - var matches {.inject.}: array[0..re.maxSubPatterns-1, string] +template `=~` *(s: string, pattern: Regex): untyped = + ## This calls `match` with an implicit declared `matches` array that + ## can be used in the scope of the `=~` call: + runnableExamples: + proc parse(line: string): string = + if line =~ re"\s*(\w+)\s*\=\s*(\w+)": # matches a key=value pair: + result = $(matches[0], matches[1]) + elif line =~ re"\s*(\#.*)": # matches a comment + # note that the implicit `matches` array is different from 1st branch + result = $(matches[0],) + else: raiseAssert "unreachable" + doAssert not declared(matches) + doAssert parse("NAME = LENA") == """("NAME", "LENA")""" + doAssert parse(" # comment ... ") == """("# comment ... ",)""" + bind MaxSubpatterns + when not declaredInScope(matches): + var matches {.inject.}: array[MaxSubpatterns, string] match(s, pattern, matches) # ------------------------- more string handling ------------------------------ -proc contains*(s: string, pattern: TRegEx, start = 0): bool = - ## same as ``find(s, pattern, start) >= 0`` +proc contains*(s: string, pattern: Regex, start = 0): bool {.inline.} = + ## same as `find(s, pattern, start) >= 0` return find(s, pattern, start) >= 0 -proc contains*(s: string, pattern: TRegEx, matches: var openArray[string], - start = 0): bool = - ## same as ``find(s, pattern, matches, start) >= 0`` +proc contains*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): bool {.inline.} = + ## same as `find(s, pattern, matches, start) >= 0` + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. return find(s, pattern, matches, start) >= 0 -proc startsWith*(s: string, prefix: TRegEx): bool = +proc startsWith*(s: string, prefix: Regex): bool {.inline.} = ## returns true if `s` starts with the pattern `prefix` result = matchLen(s, prefix) >= 0 -proc endsWith*(s: string, suffix: TRegEx): bool = - ## returns true if `s` ends with the pattern `prefix` +proc endsWith*(s: string, suffix: Regex): bool {.inline.} = + ## returns true if `s` ends with the pattern `suffix` for i in 0 .. s.len-1: if matchLen(s, suffix, i) == s.len - i: return true -proc replace*(s: string, sub: TRegEx, by = ""): string = - ## Replaces `sub` in `s` by the string `by`. Captures cannot be - ## accessed in `by`. Examples: - ## - ## .. code-block:: nimrod - ## "var1=key; var2=key2".replace(re"(\w+)'='(\w+)") - ## - ## Results in: - ## - ## .. code-block:: nimrod - ## - ## "; " +proc replace*(s: string, sub: Regex, by = ""): string = + ## Replaces `sub` in `s` by the string `by`. Captures cannot be + ## accessed in `by`. + runnableExamples: + doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)") == "; " + doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)", "?") == "?; ?" result = "" var prev = 0 - while true: - var match = findBounds(s, sub, prev) + var flags = int32(0) + while prev < s.len: + var match = findBoundsImpl(s.cstring, sub, prev, s.len, flags) + flags = 0 if match.first < 0: break add(result, substr(s, prev, match.first-1)) add(result, by) + if match.first > match.last: + # 0-len match + flags = pcre.NOTEMPTY_ATSTART prev = match.last + 1 add(result, substr(s, prev)) - -proc replacef*(s: string, sub: TRegEx, by: string): string = + +proc replacef*(s: string, sub: Regex, by: string): string = ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by` - ## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples: - ## - ## .. code-block:: nimrod - ## "var1=key; var2=key2".replacef(re"(\w+)'='(\w+)", "$1<-$2$2") - ## - ## Results in: - ## - ## .. code-block:: nimrod - ## - ## "var1<-keykey; val2<-key2key2" + ## with the notation `$i` and `$#` (see strutils.\`%\`). + runnableExamples: + doAssert "var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2") == + "var1<-keykey; var2<-key2key2" result = "" - var caps: array[0..maxSubpatterns-1, string] + var caps: array[MaxSubpatterns, string] var prev = 0 - while true: + while prev < s.len: var match = findBounds(s, sub, caps, prev) if match.first < 0: break add(result, substr(s, prev, match.first-1)) addf(result, by, caps) + if match.last + 1 == prev: break prev = match.last + 1 add(result, substr(s, prev)) - when false: - result = "" - var i = 0 - var caps: array[0..maxSubpatterns-1, string] - while i < s.len: - var x = matchLen(s, sub, caps, i) - if x <= 0: - add(result, s[i]) - inc(i) - else: - addf(result, by, caps) - inc(i, x) - # substr the rest: - add(result, substr(s, i)) - -proc parallelReplace*(s: string, subs: openArray[ - tuple[pattern: TRegEx, repl: string]]): string = + +proc multiReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string = ## Returns a modified copy of `s` with the substitutions in `subs` ## applied in parallel. result = "" var i = 0 - var caps: array[0..maxSubpatterns-1, string] + var caps: array[MaxSubpatterns, string] while i < s.len: block searchSubs: for j in 0..high(subs): @@ -340,120 +548,64 @@ proc parallelReplace*(s: string, subs: openArray[ add(result, s[i]) inc(i) # copy the rest: - add(result, substr(s, i)) - + add(result, substr(s, i)) + proc transformFile*(infile, outfile: string, - subs: openArray[tuple[pattern: TRegEx, repl: string]]) = + subs: openArray[tuple[pattern: Regex, repl: string]]) = ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an + ## `parallelReplace`) and writes back to `outfile`. Raises `IOError` if an ## error occurs. This is supposed to be used for quick scripting. - var x = readFile(infile).string - writeFile(outfile, x.parallelReplace(subs)) - -iterator split*(s: string, sep: TRegEx): string = + var x = readFile(infile) + writeFile(outfile, x.multiReplace(subs)) + +iterator split*(s: string, sep: Regex; maxsplit = -1): string = ## Splits the string `s` into substrings. ## - ## Substrings are separated by the regular expression `sep`. - ## Examples: - ## - ## .. code-block:: nimrod - ## for word in split("00232this02939is39an22example111", re"\d+"): - ## writeln(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: nimrod - ## "this" - ## "is" - ## "an" - ## "example" - ## - var - first = 0 - last = 0 - while last < len(s): - var x = matchLen(s, sep, last) - if x > 0: inc(last, x) - first = last - while last < len(s): + ## Substrings are separated by the regular expression `sep` + ## (and the portion matched by `sep` is not returned). + runnableExamples: + import std/sequtils + doAssert toSeq(split("00232this02939is39an22example111", re"\d+")) == + @["", "this", "is", "an", "example", ""] + var last = 0 + var splits = maxsplit + var x = -1 + if len(s) == 0: + last = 1 + if matchLen(s, sep, 0) == 0: + x = 0 + while last <= len(s): + var first = last + var sepLen = 1 + if x == 0: inc(last) + while last < len(s): x = matchLen(s, sep, last) - if x > 0: break - if first < last: - yield substr(s, first, last-1) + if x >= 0: + sepLen = x + break + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) -proc split*(s: string, sep: TRegEx): seq[string] = - ## Splits the string `s` into substrings. - accumulateResult(split(s, sep)) - -proc escapeRe*(s: string): string = - ## escapes `s` so that it is matched verbatim when used as a regular +proc split*(s: string, sep: Regex, maxsplit = -1): seq[string] {.inline.} = + ## Splits the string `s` into a seq of substrings. + ## + ## The portion matched by `sep` is not returned. + result = @[] + for x in split(s, sep, maxsplit): result.add x + +proc escapeRe*(s: string): string = + ## escapes `s` so that it is matched verbatim when used as a regular ## expression. result = "" for c in items(s): case c of 'a'..'z', 'A'..'Z', '0'..'9', '_': result.add(c) - else: + else: result.add("\\x") result.add(toHex(ord(c), 2)) - -const ## common regular expressions - reIdentifier* = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b" ## describes an identifier - reNatural* = r"\b\d+\b" ## describes a natural number - reInteger* = r"\b[-+]?\d+\b" ## describes an integer - reHex* = r"\b0[xX][0-9a-fA-F]+\b" ## describes a hexadecimal number - reBinary* = r"\b0[bB][01]+\b" ## describes a binary number (example: 0b11101) - reOctal* = r"\b0[oO][0-7]+\b" ## describes an octal number (example: 0o777) - reFloat* = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b" - ## describes a floating point number - reEmail* = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" & - r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)" & - r"*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" & - r"(?:[a-zA-Z]{2}|com|org|" & - r"net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b" - ## describes a common email address - reURL* = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms\-help):" & - r"((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b" - ## describes an URL - -when isMainModule: - assert match("(a b c)", re"\( .* \)") - assert match("WHiLe", re("while", {reIgnoreCase})) - - assert "0158787".match(re"\d+") - assert "ABC 0232".match(re"\w+\s+\d+") - assert "ABC".match(re"\d+ | \w+") - - assert matchLen("key", re(reIdentifier)) == 3 - - var pattern = re"[a-z0-9]+\s*=\s*[a-z0-9]+" - assert matchLen("key1= cal9", pattern) == 11 - - assert find("_____abc_______", re"abc") == 5 - - var matches: array[0..5, string] - if match("abcdefg", re"c(d)ef(g)", matches, 2): - assert matches[0] == "d" - assert matches[1] == "g" - else: - assert false - - if "abc" =~ re"(a)bcxyz|(\w+)": - assert matches[1] == "abc" - else: - assert false - - assert "var1=key; var2=key2".endsWith(re"\w+=\w+") - assert("var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2") == - "var1<-keykey; var2<-key2key2") - assert("var1=key; var2=key2".replace(re"(\w+)=(\w+)", "$1<-$2$2") == - "$1<-$2$2; $1<-$2$2") - - for word in split("00232this02939is39an22example111", re"\d+"): - writeln(stdout, word) - - for x in findAll("abcdef", re"^{.}", 3): - assert x == "d" - for x in findAll("abcdef", re".", 3): - echo x diff --git a/lib/impure/ssl.nim b/lib/impure/ssl.nim deleted file mode 100755 index 54d524c7b..000000000 --- a/lib/impure/ssl.nim +++ /dev/null @@ -1,96 +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 provides an easy to use sockets-style -## nimrod interface to the OpenSSL library. - -{.deprecated.} - -import openssl, strutils, os - -type - TSecureSocket* {.final.} = object - ssl: PSSL - bio: PBIO - -proc connect*(sock: var TSecureSocket, address: string, - port: int): Int = - ## Connects to the specified `address` on the specified `port`. - ## Returns the result of the certificate validation. - SslLoadErrorStrings() - ERR_load_BIO_strings() - - if SSL_library_init() != 1: - OSError() - - var ctx = SSL_CTX_new(SSLv23_client_method()) - if ctx == nil: - ERR_print_errors_fp(stderr) - OSError() - - #if SSL_CTX_load_verify_locations(ctx, - # "/tmp/openssl-0.9.8e/certs/vsign1.pem", NIL) == 0: - # echo("Failed load verify locations") - # ERR_print_errors_fp(stderr) - - sock.bio = BIO_new_ssl_connect(ctx) - if BIO_get_ssl(sock.bio, addr(sock.ssl)) == 0: - OSError() - - if BIO_set_conn_hostname(sock.bio, address & ":" & $port) != 1: - OSError() - - if BIO_do_connect(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - OSError() - - result = SSL_get_verify_result(sock.ssl) - -proc recvLine*(sock: TSecureSocket, line: var TaintedString): bool = - ## Acts in a similar fashion to the `recvLine` in the sockets module. - ## Returns false when no data is available to be read. - ## `Line` must be initialized and not nil! - setLen(line.string, 0) - while True: - var c: array[0..0, char] - var n = BIO_read(sock.bio, c, c.len.cint) - if n <= 0: return False - if c[0] == '\r': - n = BIO_read(sock.bio, c, c.len.cint) - if n > 0 and c[0] == '\L': - return True - elif n <= 0: - return False - elif c[0] == '\L': return True - add(line.string, c) - - -proc send*(sock: TSecureSocket, data: string) = - ## Writes `data` to the socket. - if BIO_write(sock.bio, data, data.len.cint) <= 0: - OSError() - -proc close*(sock: TSecureSocket) = - ## Closes the socket - if BIO_free(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - OSError() - -when isMainModule: - var s: TSecureSocket - echo connect(s, "smtp.gmail.com", 465) - - #var buffer: array[0..255, char] - #echo BIO_read(bio, buffer, buffer.len) - var buffer: string = "" - - echo s.recvLine(buffer) - echo buffer - echo buffer.len - diff --git a/lib/impure/web.nim b/lib/impure/web.nim deleted file mode 100755 index 5f04422d1..000000000 --- a/lib/impure/web.nim +++ /dev/null @@ -1,63 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains simple high-level procedures for dealing with the -## web. Use cases: -## -## * requesting URLs -## * sending and retrieving emails -## * sending and retrieving files from an FTP server -## -## Currently only requesting URLs is implemented. The implementation depends -## on the libcurl library! -## -## **Deprecated since version 0.8.8:** Use the -## `httpclient <httpclient.html>`_ module instead. -## - -{.deprecated.} - -import libcurl, streams - -proc curlwrapperWrite(p: pointer, size, nmemb: int, - data: pointer): int {.cdecl.} = - var stream = cast[PStream](data) - stream.writeData(p, size*nmemb) - return size*nmemb - -proc URLretrieveStream*(url: string): PStream = - ## retrieves the given `url` and returns a stream which one can read from to - ## obtain the contents. Returns nil if an error occurs. - result = newStringStream() - var hCurl = easy_init() - if hCurl == nil: return nil - if easy_setopt(hCurl, OPT_URL, url) != E_OK: return nil - if easy_setopt(hCurl, OPT_WRITEFUNCTION, - curlwrapperWrite) != E_OK: return nil - if easy_setopt(hCurl, OPT_WRITEDATA, result) != E_OK: return nil - if easy_perform(hCurl) != E_OK: return nil - easy_cleanup(hCurl) - -proc URLretrieveString*(url: string): TaintedString = - ## retrieves the given `url` and returns the contents. Returns nil if an - ## error occurs. - var stream = newStringStream() - var hCurl = easy_init() - if hCurl == nil: return - if easy_setopt(hCurl, OPT_URL, url) != E_OK: return - if easy_setopt(hCurl, OPT_WRITEFUNCTION, - curlwrapperWrite) != E_OK: return - if easy_setopt(hCurl, OPT_WRITEDATA, stream) != E_OK: return - if easy_perform(hCurl) != E_OK: return - easy_cleanup(hCurl) - result = stream.data.TaintedString - -when isMainModule: - echo URLretrieveString("http://nimrod-code.org/") - diff --git a/lib/impure/zipfiles.nim b/lib/impure/zipfiles.nim deleted file mode 100755 index 029d8527d..000000000 --- a/lib/impure/zipfiles.nim +++ /dev/null @@ -1,167 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a zip archive creator/reader/modifier. - -import - streams, libzip, times, os - -type - TZipArchive* = object of TObject ## represents a zip archive - mode: TFileMode - w: PZip - - -proc zipError(z: var TZipArchive) = - var e: ref EIO - new(e) - e.msg = $zip_strerror(z.w) - raise e - -proc open*(z: var TZipArchive, filename: string, mode: TFileMode = fmRead): bool = - ## Opens a zip file for reading, writing or appending. All file modes are - ## supported. Returns true iff successful, false otherwise. - var err, flags: int32 - case mode - of fmRead, fmReadWriteExisting, fmAppend: flags = 0 - of fmWrite: - if existsFile(filename): removeFile(filename) - flags = ZIP_CREATE or ZIP_EXCL - of fmReadWrite: flags = ZIP_CREATE - z.w = zip_open(filename, flags, addr(err)) - z.mode = mode - result = z.w != nil - -proc close*(z: var TZipArchive) = - ## Closes a zip file. - zip_close(z.w) - -proc createDir*(z: var TZipArchive, dir: string) = - ## Creates a directory within the `z` archive. This does not fail if the - ## directory already exists. Note that for adding a file like - ## ``"path1/path2/filename"`` it is not necessary - ## to create the ``"path/path2"`` subdirectories - it will be done - ## automatically by ``addFile``. - assert(z.mode != fmRead) - discard zip_add_dir(z.w, dir) - zip_error_clear(z.w) - -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) - var zipsrc = zip_source_file(z.w, src, 0, -1) - if zipsrc == nil: - #echo("Dest: " & dest) - #echo("Src: " & src) - zipError(z) - if zip_add(z.w, dest, zipsrc) < 0'i32: - zip_source_free(zipsrc) - zipError(z) - -proc addFile*(z: var TZipArchive, file: string) = - ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is - ## the name of the destination. - addFile(z, file, file) - -proc mySourceCallback(state, data: pointer, len: int, - cmd: Tzip_source_cmd): int {.cdecl.} = - var src = cast[PStream](state) - case cmd - of ZIP_SOURCE_OPEN: - if src.setPositionImpl != nil: setPosition(src, 0) # reset - of ZIP_SOURCE_READ: - result = readData(src, data, len) - of ZIP_SOURCE_CLOSE: close(src) - of ZIP_SOURCE_STAT: - var stat = cast[PZipStat](data) - zip_stat_init(stat) - stat.size = high(int32)-1 # we don't know the size - stat.mtime = getTime() - result = sizeof(TZipStat) - of ZIP_SOURCE_ERROR: - var err = cast[ptr array[0..1, cint]](data) - err[0] = ZIP_ER_INTERNAL - err[1] = 0 - result = 2*sizeof(cint) - of constZIP_SOURCE_FREE: GC_unref(src) - else: assert(false) - -proc addFile*(z: var TZipArchive, dest: string, src: PStream) = - ## Adds a file named with `dest` to the archive `z`. `dest` - ## may contain a path. The file's content is read from the `src` stream. - assert(z.mode != fmRead) - GC_ref(src) - var zipsrc = zip_source_function(z.w, mySourceCallback, cast[pointer](src)) - if zipsrc == nil: zipError(z) - if zip_add(z.w, dest, zipsrc) < 0'i32: - zip_source_free(zipsrc) - zipError(z) - -# -------------- zip file stream --------------------------------------------- - -type - TZipFileStream = object of TStream - f: Pzip_file - - PZipFileStream* = - ref TZipFileStream ## a reader stream of a file within a zip archive - -proc fsClose(s: PStream) = zip_fclose(PZipFileStream(s).f) -proc fsReadData(s: PStream, buffer: pointer, bufLen: int): int = - result = zip_fread(PZipFileStream(s).f, buffer, bufLen) - -proc newZipFileStream(f: PZipFile): PZipFileStream = - new(result) - result.f = f - result.closeImpl = fsClose - result.readDataImpl = fsReadData - # other methods are nil! - -# ---------------------------------------------------------------------------- - -proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = - ## returns a stream that can be used to read the file named `filename` - ## from the archive `z`. Returns nil in case of an error. - ## The returned stream does not support the `setPosition`, `getPosition`, - ## `writeData` or `atEnd` methods. - var x = zip_fopen(z.w, filename, 0'i32) - if x != nil: result = newZipFileStream(x) - -iterator walkFiles*(z: var TZipArchive): string = - ## walks over all files in the archive `z` and returns the filename - ## (including the path). - var i = 0'i32 - var num = zip_get_num_files(z.w) - while i < num: - yield $zip_get_name(z.w, i, 0'i32) - inc(i) - - -proc extractFile*(z: var TZipArchive, srcFile: string, dest: PStream) = - ## extracts a file from the zip archive `z` to the destination stream. - var strm = getStream(z, srcFile) - while true: - if not strm.atEnd: - dest.write(strm.readStr(1)) - else: break - dest.flush() - strm.close() - -proc extractFile*(z: var TZipArchive, srcFile: string, dest: string) = - ## extracts a file from the zip archive `z` to the destination filename. - var file = newFileStream(dest, fmReadWrite) - extractFile(z, srcFile, file) - file.close() - -proc extractAll*(z: var TZipArchive, dest: string) = - ## extracts all files from archive `z` to the destination directory. - for file in walkFiles(z): - extractFile(z, file, dest / extractFilename(file)) - |