From 9f99dd0196edda1e5bb6440abb9e7003d26ecfca Mon Sep 17 00:00:00 2001 From: def Date: Sun, 3 Aug 2014 14:11:57 +0200 Subject: [PATCH 01/18] Add list comprehensions to future module --- lib/pure/future.nim | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index b7df05207d..121877ad12 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -115,3 +115,56 @@ macro `->`*(p, b: expr): expr {.immediate.} = ## f(2, 2) result = createProcType(p, b) + +type ListComprehension = object +var lc*: ListComprehension + +macro `[]`*(lc: ListComprehension, x, t): expr = + ## List comprehensions. + ## + ## .. code-block:: nimrod + ## + ## const n = 20 + ## echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), + ## tuple[a,b,c: int]] + + expectLen(x, 3) + expectKind(x, nnkInfix) + expectKind(x[0], nnkIdent) + assert($x[0].ident == "|") + + result = newCall( + newDotExpr( + newIdentNode("result"), + newIdentNode("add")), + x[1]) + + for i in countdown(x[2].len-1, 0): + let y = x[2][i] + expectKind(y, nnkInfix) + expectMinLen(y, 1) + if y[0].kind == nnkIdent and $y[0].ident == "<-": + expectLen(y, 3) + result = newNimNode(nnkForStmt).add(y[1], y[2], result) + else: + result = newIfStmt((y, result)) + + result = newNimNode(nnkCall).add( + newNimNode(nnkPar).add( + newNimNode(nnkLambda).add( + newEmptyNode(), + newEmptyNode(), + newEmptyNode(), + newNimNode(nnkFormalParams).add( + newNimNode(nnkBracketExpr).add( + newIdentNode("seq"), + t)), + newEmptyNode(), + newEmptyNode(), + newStmtList( + newAssignment( + newIdentNode("result"), + newNimNode(nnkPrefix).add( + newIdentNode("@"), + newNimNode(nnkBracket))), + result)))) From c7898a0c81f0d6267e52b0ab77cb156588fed01c Mon Sep 17 00:00:00 2001 From: def Date: Sun, 3 Aug 2014 19:07:45 +0200 Subject: [PATCH 02/18] Extend list comprehension documentation --- lib/pure/future.nim | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 121877ad12..480ee2b0dc 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -119,35 +119,39 @@ macro `->`*(p, b: expr): expr {.immediate.} = type ListComprehension = object var lc*: ListComprehension -macro `[]`*(lc: ListComprehension, x, t): expr = - ## List comprehensions. +macro `[]`*(lc: ListComprehension, comp, typ: expr): expr = + ## List comprehension, returns a sequence. `comp` is the actual list + ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is + ## the type that will be stored inside the result seq. ## ## .. code-block:: nimrod ## + ## echo lc[x | (x <- 1..10, x mod 2 == 0), int] + ## ## const n = 20 ## echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), ## tuple[a,b,c: int]] - expectLen(x, 3) - expectKind(x, nnkInfix) - expectKind(x[0], nnkIdent) - assert($x[0].ident == "|") + expectLen(comp, 3) + expectKind(comp, nnkInfix) + expectKind(comp[0], nnkIdent) + assert($comp[0].ident == "|") result = newCall( newDotExpr( newIdentNode("result"), newIdentNode("add")), - x[1]) + comp[1]) - for i in countdown(x[2].len-1, 0): - let y = x[2][i] - expectKind(y, nnkInfix) - expectMinLen(y, 1) - if y[0].kind == nnkIdent and $y[0].ident == "<-": - expectLen(y, 3) - result = newNimNode(nnkForStmt).add(y[1], y[2], result) + for i in countdown(comp[2].len-1, 0): + let x = comp[2][i] + expectKind(x, nnkInfix) + expectMinLen(x, 1) + if x[0].kind == nnkIdent and $x[0].ident == "<-": + expectLen(x, 3) + result = newNimNode(nnkForStmt).add(x[1], x[2], result) else: - result = newIfStmt((y, result)) + result = newIfStmt((x, result)) result = newNimNode(nnkCall).add( newNimNode(nnkPar).add( @@ -158,7 +162,7 @@ macro `[]`*(lc: ListComprehension, x, t): expr = newNimNode(nnkFormalParams).add( newNimNode(nnkBracketExpr).add( newIdentNode("seq"), - t)), + typ)), newEmptyNode(), newEmptyNode(), newStmtList( From 2476ee0cd7e64ea84b83ebdd728ed46609bff633 Mon Sep 17 00:00:00 2001 From: def Date: Tue, 5 Aug 2014 20:34:07 +0200 Subject: [PATCH 03/18] Add -lm for fesetround and fegetround --- lib/posix/posix.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index a5ee05abbd..0a2211a402 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -29,6 +29,9 @@ {.deadCodeElim:on.} +when defined(Posix) and not defined(haiku): + {.passl: "-lm".} + from times import TTime const From c0422ae8afc8daff021a299b1879d08b15e50df3 Mon Sep 17 00:00:00 2001 From: def Date: Wed, 6 Aug 2014 02:31:15 +0200 Subject: [PATCH 04/18] Move floating point rounding and exceptions handling to math (it's C99 as well, not just POSIX) --- lib/posix/posix.nim | 45 --------------------------------------------- lib/pure/math.nim | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 0a2211a402..e1ecbb518d 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -29,9 +29,6 @@ {.deadCodeElim:on.} -when defined(Posix) and not defined(haiku): - {.passl: "-lm".} - from times import TTime const @@ -102,22 +99,6 @@ type l_pid*: TPid ## Process ID of the process holding the lock; ## returned with F_GETLK. - Tfenv* {.importc: "fenv_t", header: "", final, pure.} = - object ## Represents the entire floating-point environment. The - ## floating-point environment refers collectively to any - ## floating-point status flags and control modes supported - ## by the implementation. - Tfexcept* {.importc: "fexcept_t", header: "", final, pure.} = - object ## Represents the floating-point status flags collectively, - ## including any status the implementation associates with the - ## flags. A floating-point status flag is a system variable - ## whose value is set (but never cleared) when a floating-point - ## exception is raised, which occurs as a side effect of - ## exceptional floating-point arithmetic to provide auxiliary - ## information. A floating-point control mode is a system variable - ## whose value may be set by the user to affect the subsequent - ## behavior of floating-point arithmetic. - TFTW* {.importc: "struct FTW", header: "", final, pure.} = object base*: cint level*: cint @@ -837,18 +818,6 @@ var ## The application expects to access the specified data once and ## then not reuse it thereafter. - FE_DIVBYZERO* {.importc, header: "".}: cint - FE_INEXACT* {.importc, header: "".}: cint - FE_INVALID* {.importc, header: "".}: cint - FE_OVERFLOW* {.importc, header: "".}: cint - FE_UNDERFLOW* {.importc, header: "".}: cint - FE_ALL_EXCEPT* {.importc, header: "".}: cint - FE_DOWNWARD* {.importc, header: "".}: cint - FE_TONEAREST* {.importc, header: "".}: cint - FE_TOWARDZERO* {.importc, header: "".}: cint - FE_UPWARD* {.importc, header: "".}: cint - FE_DFL_ENV* {.importc, header: "".}: cint - when not defined(haiku) and not defined(OpenBSD): var MM_HARD* {.importc, header: "".}: cint @@ -1814,20 +1783,6 @@ proc posix_fadvise*(a1: cint, a2, a3: TOff, a4: cint): cint {. proc posix_fallocate*(a1: cint, a2, a3: TOff): cint {. importc, header: "".} -proc feclearexcept*(a1: cint): cint {.importc, header: "".} -proc fegetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. - importc, header: "".} -proc feraiseexcept*(a1: cint): cint {.importc, header: "".} -proc fesetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. - importc, header: "".} -proc fetestexcept*(a1: cint): cint {.importc, header: "".} -proc fegetround*(): cint {.importc, header: "".} -proc fesetround*(a1: cint): cint {.importc, header: "".} -proc fegetenv*(a1: ptr Tfenv): cint {.importc, header: "".} -proc feholdexcept*(a1: ptr Tfenv): cint {.importc, header: "".} -proc fesetenv*(a1: ptr Tfenv): cint {.importc, header: "".} -proc feupdateenv*(a1: ptr Tfenv): cint {.importc, header: "".} - when not defined(haiku) and not defined(OpenBSD): proc fmtmsg*(a1: int, a2: cstring, a3: cint, a4, a5, a6: cstring): cint {.importc, header: "".} diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 2f7a696b90..cc774539dd 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -40,6 +40,19 @@ const ## after the decimal point ## for Nimrod's ``float`` type. +var + FE_DIVBYZERO* {.importc, header: "".}: cint + FE_INEXACT* {.importc, header: "".}: cint + FE_INVALID* {.importc, header: "".}: cint + FE_OVERFLOW* {.importc, header: "".}: cint + FE_UNDERFLOW* {.importc, header: "".}: cint + FE_ALL_EXCEPT* {.importc, header: "".}: cint + FE_DOWNWARD* {.importc, header: "".}: cint + FE_TONEAREST* {.importc, header: "".}: cint + FE_TOWARDZERO* {.importc, header: "".}: cint + FE_UPWARD* {.importc, header: "".}: cint + FE_DFL_ENV* {.importc, header: "".}: cint + type TFloatClass* = enum ## describes the class a floating point value belongs to. ## This is the type that is returned by `classify`. @@ -51,6 +64,22 @@ type fcInf, ## value is positive infinity fcNegInf ## value is negative infinity + Tfenv* {.importc: "fenv_t", header: "", final, pure.} = + object ## Represents the entire floating-point environment. The + ## floating-point environment refers collectively to any + ## floating-point status flags and control modes supported + ## by the implementation. + Tfexcept* {.importc: "fexcept_t", header: "", final, pure.} = + object ## Represents the floating-point status flags collectively, + ## including any status the implementation associates with the + ## flags. A floating-point status flag is a system variable + ## whose value is set (but never cleared) when a floating-point + ## exception is raised, which occurs as a side effect of + ## exceptional floating-point arithmetic to provide auxiliary + ## information. A floating-point control mode is a system variable + ## whose value may be set by the user to affect the subsequent + ## behavior of floating-point arithmetic. + proc classify*(x: float): TFloatClass = ## classifies a floating point value. Returns `x`'s class as specified by ## `TFloatClass`. @@ -321,6 +350,20 @@ proc standardDeviation*(s: TRunningStat): float = ## computes the current standard deviation of `s` result = sqrt(variance(s)) +proc feclearexcept*(a1: cint): cint {.importc, header: "".} +proc fegetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. + importc, header: "".} +proc feraiseexcept*(a1: cint): cint {.importc, header: "".} +proc fesetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. + importc, header: "".} +proc fetestexcept*(a1: cint): cint {.importc, header: "".} +proc fegetround*(): cint {.importc, header: "".} +proc fesetround*(a1: cint): cint {.importc, header: "".} +proc fegetenv*(a1: ptr Tfenv): cint {.importc, header: "".} +proc feholdexcept*(a1: ptr Tfenv): cint {.importc, header: "".} +proc fesetenv*(a1: ptr Tfenv): cint {.importc, header: "".} +proc feupdateenv*(a1: ptr Tfenv): cint {.importc, header: "".} + {.pop.} {.pop.} From 434dcd7166361fddd84953d8cdd0de68564096ba Mon Sep 17 00:00:00 2001 From: Milos Negovanovic Date: Wed, 20 Aug 2014 10:24:36 +0100 Subject: [PATCH 05/18] Preserve nil <-> NULL between Nimrod and database. --- lib/impure/db_mysql.nim | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index eec4daf00d..eb4092f37c 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -57,7 +57,7 @@ when false: binding: seq[MYSQL_BIND] discard mysql_stmt_close(stmt) -proc dbQuote(s: string): string = +proc dbQuote*(s: string): string = result = "'" for c in items(s): if c == '\'': add(result, "''") @@ -69,7 +69,10 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = var a = 0 for c in items(string(formatstr)): if c == '?': - add(result, dbQuote(args[a])) + if args[a] == nil: + add(result, "NULL") + else: + add(result, dbQuote(args[a])) inc(a) else: add(result, c) @@ -115,7 +118,10 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery, if row == nil: break for i in 0..L-1: setLen(result[i], 0) - add(result[i], row[i]) + if row[i] == nil: + result[i] = nil + else: + add(result[i], row[i]) yield result properFreeResult(sqlres, row) @@ -132,7 +138,10 @@ proc getRow*(db: TDbConn, query: TSqlQuery, if row != nil: for i in 0..L-1: setLen(result[i], 0) - add(result[i], row[i]) + if row[i] == nil: + result[i] = nil + else: + add(result[i], row[i]) properFreeResult(sqlres, row) proc getAllRows*(db: TDbConn, query: TSqlQuery, @@ -150,7 +159,11 @@ proc getAllRows*(db: TDbConn, query: TSqlQuery, if row == nil: break setLen(result, j+1) newSeq(result[j], L) - for i in 0..L-1: result[j][i] = $row[i] + for i in 0..L-1: + if row[i] == nil: + result[j][i] = nil + else: + result[j][i] = $row[i] inc(j) mysql.FreeResult(sqlres) From a019825d8fa1dc03fa9828263cbaa7495fef1114 Mon Sep 17 00:00:00 2001 From: def Date: Fri, 22 Aug 2014 13:12:49 +0200 Subject: [PATCH 06/18] Move fenv to its own module --- lib/impure/fenv.nim | 113 ++++++++++++++++++++++++++++++++++++++++++++ lib/pure/math.nim | 54 --------------------- 2 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 lib/impure/fenv.nim diff --git a/lib/impure/fenv.nim b/lib/impure/fenv.nim new file mode 100644 index 0000000000..9d7c8809b9 --- /dev/null +++ b/lib/impure/fenv.nim @@ -0,0 +1,113 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Floating-point environment. Handling of floating-point rounding and +## exceptions (overflow, zero-devide, etc.). + +{.deadCodeElim:on.} + +when defined(Posix) and not defined(haiku): + {.passl: "-lm".} + +var + FE_DIVBYZERO* {.importc, header: "".}: cint + ## division by zero + FE_INEXACT* {.importc, header: "".}: cint + ## inexact result + FE_INVALID* {.importc, header: "".}: cint + ## invalid operation + FE_OVERFLOW* {.importc, header: "".}: cint + ## result not representable due to overflow + FE_UNDERFLOW* {.importc, header: "".}: cint + ## result not representable due to underflow + FE_ALL_EXCEPT* {.importc, header: "".}: cint + ## bitwise OR of all supported exceptions + FE_DOWNWARD* {.importc, header: "".}: cint + ## round toward -Inf + FE_TONEAREST* {.importc, header: "".}: cint + ## round to nearest + FE_TOWARDZERO* {.importc, header: "".}: cint + ## round toward 0 + FE_UPWARD* {.importc, header: "".}: cint + ## round toward +Inf + FE_DFL_ENV* {.importc, header: "".}: cint + ## macro of type pointer to fenv_t to be used as the argument + ## to functions taking an argument of type fenv_t; in this + ## case the default environment will be used + +type + TFloatClass* = enum ## describes the class a floating point value belongs to. + ## This is the type that is returned by `classify`. + fcNormal, ## value is an ordinary nonzero floating point value + fcSubnormal, ## value is a subnormal (a very small) floating point value + fcZero, ## value is zero + fcNegZero, ## value is the negative zero + fcNan, ## value is Not-A-Number (NAN) + fcInf, ## value is positive infinity + fcNegInf ## value is negative infinity + + Tfenv* {.importc: "fenv_t", header: "", final, pure.} = + object ## Represents the entire floating-point environment. The + ## floating-point environment refers collectively to any + ## floating-point status flags and control modes supported + ## by the implementation. + Tfexcept* {.importc: "fexcept_t", header: "", final, pure.} = + object ## Represents the floating-point status flags collectively, + ## including any status the implementation associates with the + ## flags. A floating-point status flag is a system variable + ## whose value is set (but never cleared) when a floating-point + ## exception is raised, which occurs as a side effect of + ## exceptional floating-point arithmetic to provide auxiliary + ## information. A floating-point control mode is a system variable + ## whose value may be set by the user to affect the subsequent + ## behavior of floating-point arithmetic. + +proc feclearexcept*(excepts: cint): cint {.importc, header: "".} + ## Clear the supported exceptions represented by `excepts`. + +proc fegetexceptflag*(flagp: ptr Tfexcept, excepts: cint): cint {. + importc, header: "".} + ## Store implementation-defined representation of the exception flags + ## indicated by `excepts` in the object pointed to by `flagp`. + +proc feraiseexcept*(excepts: cint): cint {.importc, header: "".} + ## Raise the supported exceptions represented by `excepts`. + +proc fesetexceptflag*(flagp: ptr Tfexcept, excepts: cint): cint {. + importc, header: "".} + ## Set complete status for exceptions indicated by `excepts` according to + ## the representation in the object pointed to by `flagp`. + +proc fetestexcept*(excepts: cint): cint {.importc, header: "".} + ## Determine which of subset of the exceptions specified by `excepts` are + ## currently set. + +proc fegetround*(): cint {.importc, header: "".} + ## Get current rounding direction. + +proc fesetround*(roundingDirection: cint): cint {.importc, header: "".} + ## Establish the rounding direction represented by `roundingDirection`. + +proc fegetenv*(envp: ptr Tfenv): cint {.importc, header: "".} + ## Store the current floating-point environment in the object pointed + ## to by `envp`. + +proc feholdexcept*(envp: ptr Tfenv): cint {.importc, header: "".} + ## Save the current environment in the object pointed to by `envp`, clear + ## exception flags and install a non-stop mode (if available) for all + ## exceptions. + +proc fesetenv*(a1: ptr Tfenv): cint {.importc, header: "".} + ## Establish the floating-point environment represented by the object + ## pointed to by `envp`. + +proc feupdateenv*(envp: ptr Tfenv): cint {.importc, header: "".} + ## Save current exceptions in temporary storage, install environment + ## represented by object pointed to by `envp` and raise exceptions + ## according to saved exceptions. diff --git a/lib/pure/math.nim b/lib/pure/math.nim index cc774539dd..116671874e 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -40,46 +40,6 @@ const ## after the decimal point ## for Nimrod's ``float`` type. -var - FE_DIVBYZERO* {.importc, header: "".}: cint - FE_INEXACT* {.importc, header: "".}: cint - FE_INVALID* {.importc, header: "".}: cint - FE_OVERFLOW* {.importc, header: "".}: cint - FE_UNDERFLOW* {.importc, header: "".}: cint - FE_ALL_EXCEPT* {.importc, header: "".}: cint - FE_DOWNWARD* {.importc, header: "".}: cint - FE_TONEAREST* {.importc, header: "".}: cint - FE_TOWARDZERO* {.importc, header: "".}: cint - FE_UPWARD* {.importc, header: "".}: cint - FE_DFL_ENV* {.importc, header: "".}: cint - -type - TFloatClass* = enum ## describes the class a floating point value belongs to. - ## This is the type that is returned by `classify`. - fcNormal, ## value is an ordinary nonzero floating point value - fcSubnormal, ## value is a subnormal (a very small) floating point value - fcZero, ## value is zero - fcNegZero, ## value is the negative zero - fcNan, ## value is Not-A-Number (NAN) - fcInf, ## value is positive infinity - fcNegInf ## value is negative infinity - - Tfenv* {.importc: "fenv_t", header: "", final, pure.} = - object ## Represents the entire floating-point environment. The - ## floating-point environment refers collectively to any - ## floating-point status flags and control modes supported - ## by the implementation. - Tfexcept* {.importc: "fexcept_t", header: "", final, pure.} = - object ## Represents the floating-point status flags collectively, - ## including any status the implementation associates with the - ## flags. A floating-point status flag is a system variable - ## whose value is set (but never cleared) when a floating-point - ## exception is raised, which occurs as a side effect of - ## exceptional floating-point arithmetic to provide auxiliary - ## information. A floating-point control mode is a system variable - ## whose value may be set by the user to affect the subsequent - ## behavior of floating-point arithmetic. - proc classify*(x: float): TFloatClass = ## classifies a floating point value. Returns `x`'s class as specified by ## `TFloatClass`. @@ -350,20 +310,6 @@ proc standardDeviation*(s: TRunningStat): float = ## computes the current standard deviation of `s` result = sqrt(variance(s)) -proc feclearexcept*(a1: cint): cint {.importc, header: "".} -proc fegetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. - importc, header: "".} -proc feraiseexcept*(a1: cint): cint {.importc, header: "".} -proc fesetexceptflag*(a1: ptr Tfexcept, a2: cint): cint {. - importc, header: "".} -proc fetestexcept*(a1: cint): cint {.importc, header: "".} -proc fegetround*(): cint {.importc, header: "".} -proc fesetround*(a1: cint): cint {.importc, header: "".} -proc fegetenv*(a1: ptr Tfenv): cint {.importc, header: "".} -proc feholdexcept*(a1: ptr Tfenv): cint {.importc, header: "".} -proc fesetenv*(a1: ptr Tfenv): cint {.importc, header: "".} -proc feupdateenv*(a1: ptr Tfenv): cint {.importc, header: "".} - {.pop.} {.pop.} From f59ac26b85302808f135aab5309c7683c4b8f405 Mon Sep 17 00:00:00 2001 From: Milos Negovanovic Date: Fri, 26 Sep 2014 11:23:13 +0100 Subject: [PATCH 07/18] Tweaks for postgres driver (not tested yet). --- lib/impure/db_postgres.nim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index c375d91910..67e769ed20 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -60,7 +60,10 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = var a = 0 for c in items(string(formatstr)): if c == '?': - add(result, dbQuote(args[a])) + if args[a] == nil: + add(result, "NULL") + else: + add(result, dbQuote(args[a])) inc(a) else: add(result, c) @@ -124,7 +127,10 @@ proc setRow(res: PPGresult, r: var TRow, line, cols: int32) = for col in 0..cols-1: setLen(r[col], 0) var x = PQgetvalue(res, line, col) - add(r[col], x) + if x == nil: + r[col] = nil + else: + add(r[col], x) iterator fastRows*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = From 400fd6ab51a0922a7cdaa57ae3e5fa0e55b9159a Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Sat, 4 Oct 2014 22:58:10 +0200 Subject: [PATCH 08/18] Documents json module. --- lib/pure/json.nim | 629 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 539 insertions(+), 90 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index a45900f297..c762206179 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -7,47 +7,71 @@ # distribution, for details about the copyright. # -## This module implements a simple high performance `JSON`:idx: -## parser. JSON (JavaScript Object Notation) is a lightweight -## data-interchange format that is easy for humans to read and write -## (unlike XML). It is easy for machines to parse and generate. -## JSON is based on a subset of the JavaScript Programming Language, -## Standard ECMA-262 3rd Edition - December 1999. +## This module implements a simple high performance `JSON`:idx: parser. `JSON +## (JavaScript Object Notation) `_ is a lightweight +## data-interchange format that is easy for humans to read and write (unlike +## XML). It is easy for machines to parse and generate. JSON is based on a +## subset of the JavaScript Programming Language, `Standard ECMA-262 3rd +## Edition - December 1999 +## `_. ## -## Usage example: +## Parsing small values quickly can be done with the convenience `parseJson() +## <#parseJson,string>`_ proc which returns the whole JSON tree. If you are +## parsing very big JSON inputs or want to skip most of the items in them you +## can initialize your own `TJsonParser <#TJsonParser>`_ with the `open() +## <#open>`_ proc and call `next() <#next>`_ in a loop to process the +## individual parsing events. +## +## If you need to create JSON objects from your Nimrod types you can call procs +## like `newJObject() <#newJObject>`_ (or their equivalent `%() +## <#%,openArray[tuple[string,PJsonNode]]>`_ generic constructor). For +## consistency you can provide your own ``%`` operators for custom object +## types: ## ## .. code-block:: nimrod -## let -## small_json = """{"test": 1.3, "key2": true}""" -## jobj = parseJson(small_json) -## assert (jobj.kind == JObject) -## echo($jobj["test"].fnum) -## echo($jobj["key2"].bval) +## type +## Person = object ## Generic person record. +## age: int ## The age of the person. +## name: string ## The name of the person. ## -## Results in: +## proc `%`(p: Person): PJsonNode = +## ## Converts a Person into a PJsonNode. +## result = %[("age", %p.age), ("name", %p.name)] ## -## .. code-block:: nimrod +## proc test() = +## # Tests making some jsons. +## var p: Person +## p.age = 24 +## p.name = "Minah" +## echo(%p) # { "age": 24, "name": "Minah"} ## -## 1.3000000000000000e+00 -## true +## p.age = 33 +## p.name = "Sojin" +## echo(%p) # { "age": 33, "name": "Sojin"} +## +## If you don't need special logic in your Nimrod objects' serialization code +## you can also use the `marshal module `_ which converts objects +## directly to JSON. import hashes, strutils, lexbase, streams, unicode type - TJsonEventKind* = enum ## enumeration of all events that may occur when parsing - jsonError, ## an error ocurred during parsing - jsonEof, ## end of file reached - jsonString, ## a string literal - jsonInt, ## an integer literal - jsonFloat, ## a float literal - jsonTrue, ## the value ``true`` - jsonFalse, ## the value ``false`` - jsonNull, ## the value ``null`` - jsonObjectStart, ## start of an object: the ``{`` token - jsonObjectEnd, ## end of an object: the ``}`` token - jsonArrayStart, ## start of an array: the ``[`` token - jsonArrayEnd ## start of an array: the ``]`` token + TJsonEventKind* = enum ## Events that may occur when parsing. \ + ## + ## You compare these values agains the result of the `kind() proc <#kind>`_. + jsonError, ## An error ocurred during parsing. + jsonEof, ## End of file reached. + jsonString, ## A string literal. + jsonInt, ## An integer literal. + jsonFloat, ## A float literal. + jsonTrue, ## The value ``true``. + jsonFalse, ## The value ``false``. + jsonNull, ## The value ``null``. + jsonObjectStart, ## Start of an object: the ``{`` token. + jsonObjectEnd, ## End of an object: the ``}`` token. + jsonArrayStart, ## Start of an array: the ``[`` token. + jsonArrayEnd ## Start of an array: the ``]`` token. TTokKind = enum # must be synchronized with TJsonEventKind! tkError, @@ -82,7 +106,9 @@ type stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma, stateExpectObjectComma, stateExpectColon, stateExpectValue - TJsonParser* = object of TBaseLexer ## the parser object. + TJsonParser* = object of TBaseLexer ## The JSON parser object. \ + ## + ## Create a variable of this type and use `open() <#open>`_ on it. a: string tok: TTokKind kind: TJsonEventKind @@ -117,59 +143,129 @@ const ] proc open*(my: var TJsonParser, input: PStream, filename: string) = - ## initializes the parser with an input stream. `Filename` is only used - ## for nice error messages. - lexbase.open(my, input) + ## Initializes the JSON parser with an `input stream `_. + ## + ## The `filename` parameter is not strictly required and is used only for + ## nice error messages. You can pass ``nil`` as long as you never use procs + ## like `errorMsg() <#errorMsg>`_ or `errorMsgExpected() + ## <#errorMsgExpected>`_ but passing a dummy filename like ```` + ## is safer and more user friendly. Example: + ## + ## .. code-block:: nimrod + ## import json, streams + ## + ## var + ## s = newStringStream("some valid json") + ## p: TJsonParser + ## p.open(s, "") + ## + ## Once opened, you can process JSON parsing events with the `next() + ## <#next>`_ proc. my.filename = filename my.state = @[stateStart] my.kind = jsonError my.a = "" -proc close*(my: var TJsonParser) {.inline.} = - ## closes the parser `my` and its associated input stream. +proc close*(my: var TJsonParser) {.inline.} = + ## Closes the parser `my` and its associated input stream. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var + ## s = newStringStream("some valid json") + ## p: TJsonParser + ## p.open(s, "") + ## finally: p.close + ## # write here parsing of input lexbase.close(my) proc str*(my: TJsonParser): string {.inline.} = - ## returns the character data for the events: ``jsonInt``, ``jsonFloat``, - ## ``jsonString`` + ## Returns the character data for the `events <#TJsonEventKind>`_ + ## ``jsonInt``, ``jsonFloat`` and ``jsonString``. + ## + ## This proc will `assert `_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind in {jsonInt, jsonFloat, jsonString}) return my.a proc getInt*(my: TJsonParser): BiggestInt {.inline.} = - ## returns the number for the event: ``jsonInt`` + ## Returns the number for the `jsonInt <#TJsonEventKind>`_ event. + ## + ## This proc will `assert `_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonInt) return parseBiggestInt(my.a) proc getFloat*(my: TJsonParser): float {.inline.} = - ## returns the number for the event: ``jsonFloat`` + ## Returns the number for the `jsonFloat <#TJsonEventKind>`_ event. + ## + ## This proc will `assert `_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonFloat) return parseFloat(my.a) proc kind*(my: TJsonParser): TJsonEventKind {.inline.} = - ## returns the current event type for the JSON parser + ## Returns the current event type for the `JSON parser <#TJsonParser>`_. + ## + ## Call this proc just after `next() <#next>`_ to act on the new event. return my.kind proc getColumn*(my: TJsonParser): int {.inline.} = - ## get the current column the parser has arrived at. + ## Get the current column the parser has arrived at. + ## + ## While this is mostly used by procs like `errorMsg() <#errorMsg>`_ you can + ## use it as well to show user warnings if you are validating JSON values + ## during parsing. See `next() <#next>`_ for the full example: + ## + ## .. code-block:: nimrod + ## case parser.kind + ## ... + ## of jsonString: + ## let inputValue = parser.str + ## if previousValues.contains(inputValue): + ## echo "$1($2, $3) Warning: repeated value '$4'" % [ + ## parser.getFilename, $parser.getLine, $parser.getColumn, + ## inputValue] + ## ... result = getColNumber(my, my.bufpos) proc getLine*(my: TJsonParser): int {.inline.} = - ## get the current line the parser has arrived at. + ## Get the current line the parser has arrived at. + ## + ## While this is mostly used by procs like `errorMsg() <#errorMsg>`_ you can + ## use it as well to indicate user warnings if you are validating JSON values + ## during parsing. See `next() <#next>`_ and `getColumn() <#getColumn>`_ for + ## examples. result = my.lineNumber proc getFilename*(my: TJsonParser): string {.inline.} = - ## get the filename of the file that the parser processes. + ## Get the filename of the file that the parser is processing. + ## + ## This is the value you pass to the `open() <#open>`_ proc. While this is + ## mostly used by procs like `errorMsg() <#errorMsg>`_ you can use it as well + ## to indicate user warnings if you are validating JSON values during + ## parsing. See `next() <#next>`_ and `getColumn() <#getColumn>`_ for + ## examples. result = my.filename proc errorMsg*(my: TJsonParser): string = - ## returns a helpful error message for the event ``jsonError`` + ## Returns a helpful error message for the `jsonError <#TJsonEventKind>`_ + ## event. + ## + ## This proc will `assert `_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonError) result = "$1($2, $3) Error: $4" % [ my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]] proc errorMsgExpected*(my: TJsonParser, e: string): string = - ## returns an error message "`e` expected" in the same format as the - ## other error messages + ## Returns an error message "`e` expected". + ## + ## The message is in the same format as the other error messages which + ## include the parser filename, line and column values. This is used by + ## `raiseParseErr() <#raiseParseErr>`_ to raise an `EJsonParsingError + ## <#EJsonParsingError>`_. result = "$1($2, $3) Error: $4" % [ my.filename, $getLine(my), $getColumn(my), e & " expected"] @@ -382,7 +478,32 @@ proc getTok(my: var TJsonParser): TTokKind = my.tok = result proc next*(my: var TJsonParser) = - ## retrieves the first/next event. This controls the parser. + ## Retrieves the first/next event for the `JSON parser <#TJsonParser>`_. + ## + ## You are meant to call this method inside an infinite loop. After each + ## call, check the result of the `kind() <#kind>`_ proc to know what has to + ## be done next (eg. break out due to end of file). Here is a basic example + ## which simply echoes all found elements by the parser: + ## + ## .. code-block:: nimrod + ## parser.open(stream, "") + ## while true: + ## parser.next + ## case parser.kind + ## of jsonError: + ## echo parser.errorMsg + ## break + ## of jsonEof: break + ## of jsonString: echo parser.str + ## of jsonInt: echo parser.getInt + ## of jsonFloat: echo parser.getFloat + ## of jsonTrue: echo "true" + ## of jsonFalse: echo "false" + ## of jsonNull: echo "null" + ## of jsonObjectStart: echo "{" + ## of jsonObjectEnd: echo "}" + ## of jsonArrayStart: echo "[" + ## of jsonArrayEnd: echo "]" var tk = getTok(my) var i = my.state.len-1 # the following code is a state machine. If we had proper coroutines, @@ -502,7 +623,16 @@ proc next*(my: var TJsonParser) = # ------------- higher level interface --------------------------------------- type - TJsonNodeKind* = enum ## possible JSON node types + TJsonNodeKind* = enum ## Possible `JSON node <#TJsonNodeKind>`_ types. \ + ## + ## To build nodes use the helper procs + ## `newJNull() <#newJNull>`_, + ## `newJBool() <#newJBool>`_, + ## `newJInt() <#newJInt>`_, + ## `newJFloat() <#newJFloat>`_, + ## `newJString() <#newJString>`_, + ## `newJObject() <#newJObject>`_ and + ## `newJArray() <#newJArray>`_. JNull, JBool, JInt, @@ -511,8 +641,9 @@ type JObject, JArray - PJsonNode* = ref TJsonNode ## JSON node - TJsonNode* {.final, pure, acyclic.} = object + PJsonNode* = ref TJsonNode ## Reference to a `JSON node <#TJsonNode>`_. + TJsonNode* {.final, pure, acyclic.} = object ## `Object variant \ + ## `_ wrapping all possible JSON types. case kind*: TJsonNodeKind of JString: str*: string @@ -529,14 +660,36 @@ type of JArray: elems*: seq[PJsonNode] - EJsonParsingError* = object of EInvalidValue ## is raised for a JSON error + EJsonParsingError* = object of EInvalidValue ## Raised during JSON parsing. \ + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let smallJson = """{"test: 1.3, "key2": true}""" + ## try: + ## discard parseJson(smallJson) + ## # --> Bad JSON! input(1, 18) Error: : expected + ## except EJsonParsingError: + ## echo "Bad JSON! " & getCurrentExceptionMsg() proc raiseParseErr*(p: TJsonParser, msg: string) {.noinline, noreturn.} = - ## raises an `EJsonParsingError` exception. + ## Raises an `EJsonParsingError <#EJsonParsingError>`_ exception. + ## + ## The message for the exception will be built passing the `msg` parameter to + ## the `errorMsgExpected() <#errorMsgExpected>`_ proc. raise newException(EJsonParsingError, errorMsgExpected(p, msg)) proc newJString*(s: string): PJsonNode = - ## Creates a new `JString PJsonNode`. + ## Creates a new `JString PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJString("A string") + ## echo node + ## # --> "A string" + ## + ## Or you can use the shorter `%() proc <#%,string>`_. new(result) result.kind = JString result.str = s @@ -547,80 +700,206 @@ proc newJStringMove(s: string): PJsonNode = shallowCopy(result.str, s) proc newJInt*(n: BiggestInt): PJsonNode = - ## Creates a new `JInt PJsonNode`. + ## Creates a new `JInt PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJInt(900_100_200_300) + ## echo node + ## # --> 900100200300 + ## + ## Or you can use the shorter `%() proc <#%,BiggestInt>`_. new(result) result.kind = JInt result.num = n proc newJFloat*(n: float): PJsonNode = - ## Creates a new `JFloat PJsonNode`. + ## Creates a new `JFloat PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJFloat(3.14) + ## echo node + ## # --> 3.14 + ## + ## Or you can use the shorter `%() proc <#%,float>`_. new(result) result.kind = JFloat result.fnum = n proc newJBool*(b: bool): PJsonNode = - ## Creates a new `JBool PJsonNode`. + ## Creates a new `JBool PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJBool(true) + ## echo node + ## # --> true + ## + ## Or you can use the shorter `%() proc <#%,bool>`_. new(result) result.kind = JBool result.bval = b proc newJNull*(): PJsonNode = - ## Creates a new `JNull PJsonNode`. + ## Creates a new `JNull PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJNull() + ## echo node + ## # --> null new(result) proc newJObject*(): PJsonNode = - ## Creates a new `JObject PJsonNode` + ## Creates a new `JObject PJsonNode <#TJsonNodeKind>`_. + ## + ## The `PJsonNode <#PJsonNode>`_ will be initialized with an empty ``fields`` + ## sequence to which you can add new elements. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node.add("age", newJInt(24)) + ## node.add("name", newJString("Minah")) + ## echo node + ## # --> { "age": 24, "name": "Minah"} + ## + ## Or you can use the shorter `%() proc + ## <#%,openArray[tuple[string,PJsonNode]]>`_. new(result) result.kind = JObject result.fields = @[] proc newJArray*(): PJsonNode = - ## Creates a new `JArray PJsonNode` + ## Creates a new `JArray PJsonNode <#TJsonNodeKind>`_. + ## + ## The `PJsonNode <#PJsonNode>`_ will be initialized with an empty ``elems`` + ## sequence to which you can add new elements. Example: + ## + ## .. code-block:: nimrod + ## var node = newJArray() + ## node.add(newJString("Mixing types")) + ## node.add(newJInt(42)) + ## node.add(newJString("is madness")) + ## node.add(newJFloat(3.14)) + ## echo node + ## # --> [ "Mixing types", 42, "is madness", 3.14] + ## + ## Or you can use the shorter `%() proc <#%,openArray[PJsonNode]>`_. new(result) result.kind = JArray result.elems = @[] proc `%`*(s: string): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JString PJsonNode`. + ## Creates a new `JString PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %"A string" + ## echo node + ## # --> "A string" + ## + ## This generic constructor is equivalent to the `newJString() + ## <#newJString>`_ proc. new(result) result.kind = JString result.str = s proc `%`*(n: BiggestInt): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JInt PJsonNode`. + ## Creates a new `JInt PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %900_100_200_300 + ## echo node + ## # --> 900100200300 + ## + ## This generic constructor is equivalent to the `newJInt() <#newJInt>`_ + ## proc. new(result) result.kind = JInt result.num = n proc `%`*(n: float): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JFloat PJsonNode`. + ## Creates a new `JFloat PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %3.14 + ## echo node + ## # --> 3.14 + ## + ## This generic constructor is equivalent to the `newJFloat() <#newJFloat>`_ + ## proc. new(result) result.kind = JFloat result.fnum = n proc `%`*(b: bool): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JBool PJsonNode`. + ## Creates a new `JBool PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %true + ## echo node + ## # --> true + ## + ## This generic constructor is equivalent to the `newJBool() <#newJBool>`_ + ## proc. new(result) result.kind = JBool result.bval = b proc `%`*(keyVals: openArray[tuple[key: string, val: PJsonNode]]): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JObject PJsonNode` + ## Creates a new `JObject PJsonNode <#TJsonNodeKind>`_. + ## + ## Unlike the `newJObject() <#newJObject>`_ proc, which returns an object + ## that has to be further manipulated, you can use this generic constructor + ## to create JSON objects with all their fields in one go. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %24), ("name", %"Minah")] + ## echo node + ## # --> { "age": 24, "name": "Minah"} new(result) result.kind = JObject newSeq(result.fields, keyVals.len) for i, p in pairs(keyVals): result.fields[i] = p proc `%`*(elements: openArray[PJsonNode]): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JArray PJsonNode` + ## Creates a new `JArray PJsonNode <#TJsonNodeKind>`_. + ## + ## Unlike the `newJArray() <#newJArray>`_ proc, which returns an object + ## that has to be further manipulated, you can use this generic constructor + ## to create JSON arrays with all their values in one go. Example: + ## + ## .. code-block:: nimrod + ## let node = %[%"Mixing types", %42, + ## %"is madness", %3.14,] + ## echo node + ## # --> [ "Mixing types", 42, "is madness", 3.14] new(result) result.kind = JArray newSeq(result.elems, elements.len) for i, p in pairs(elements): result.elems[i] = p proc `==`* (a,b: PJsonNode): bool = - ## Check two nodes for equality + ## Check two `PJsonNode <#PJsonNode>`_ nodes for equality. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## assert(%1 == %1) + ## assert(%1 != %2) if a.isNil: if b.isNil: return true return false @@ -644,7 +923,21 @@ proc `==`* (a,b: PJsonNode): bool = a.fields == b.fields proc hash* (n:PJsonNode): THash = - ## Compute the hash for a JSON node + ## Computes the hash for a JSON node. + ## + ## The `THash `_ allows JSON nodes to be used as keys for + ## `sets `_ or `tables `_. Example: + ## + ## .. code-block:: nimrod + ## import json, sets + ## + ## var + ## uniqueValues = initSet[PJsonNode]() + ## values = %[%1, %2, %1, %2, %3] + ## for value in values.elems: + ## discard uniqueValues.containsOrIncl(value) + ## echo uniqueValues + ## # --> {1, 2, 3} case n.kind of JArray: result = hash(n.elems) @@ -662,17 +955,40 @@ proc hash* (n:PJsonNode): THash = result = hash(0) proc len*(n: PJsonNode): int = - ## If `n` is a `JArray`, it returns the number of elements. - ## If `n` is a `JObject`, it returns the number of pairs. - ## Else it returns 0. + ## Returns the number of children items for this `PJsonNode <#PJsonNode>`_. + ## + ## If `n` is a `JArray <#TJsonNodeKind>`_, it will return the number of + ## elements. If `n` is a `JObject <#TJsonNodeKind>`_, it will return the + ## number of key-value pairs. For all other types this proc returns zero. + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## n1 = %[("age", %33), ("name", %"Sojin")] + ## n2 = %[%1, %2, %3, %4, %5, %6, %7] + ## n3 = %"Some odd string we have here" + ## echo n1.len # --> 2 + ## echo n2.len # --> 7 + ## echo n3.len # --> 0 + ## case n.kind of JArray: result = n.elems.len of JObject: result = n.fields.len else: discard proc `[]`*(node: PJsonNode, name: string): PJsonNode = - ## Gets a field from a `JObject`, which must not be nil. - ## If the value at `name` does not exist, returns nil + ## Gets a named field from a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## Returns the value for `name` or nil if `node` doesn't contain such a + ## field. This proc will `assert `_ in debug builds if + ## `name` is ``nil`` or `node` is not a ``JObject``. On release builds it + ## will likely crash. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %40), ("name", %"Britney")] + ## echo node["name"] + ## # --> "Britney" assert(not isNil(node)) assert(node.kind == JObject) for key, item in items(node.fields): @@ -681,35 +997,92 @@ proc `[]`*(node: PJsonNode, name: string): PJsonNode = return nil proc `[]`*(node: PJsonNode, index: int): PJsonNode = - ## Gets the node at `index` in an Array. Result is undefined if `index` - ## is out of bounds + ## Gets the `index` item from a `JArray <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## Returns the specified item. Result is undefined if `index` is out of + ## bounds. This proc will `assert `_ in debug builds if + ## `node` is ``nil`` or not a ``JArray``. Example: + ## + ## .. code-block:: nimrod + ## let node = %[%"Mixing types", %42, + ## %"is madness", %3.14,] + ## echo node[2] + ## # --> "is madness" assert(not isNil(node)) assert(node.kind == JArray) return node.elems[index] proc hasKey*(node: PJsonNode, key: string): bool = - ## Checks if `key` exists in `node`. + ## Returns `true` if `key` exists in a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %40), ("name", %"Britney")] + ## echo node.hasKey("email") + ## # --> false assert(node.kind == JObject) for k, item in items(node.fields): if k == key: return true proc existsKey*(node: PJsonNode, key: string): bool {.deprecated.} = node.hasKey(key) - ## Deprecated for `hasKey` + ## Deprecated for `hasKey() <#hasKey>`_. proc add*(father, child: PJsonNode) = - ## Adds `child` to a JArray node `father`. + ## Adds `child` to a `JArray <#TJsonNodeKind>`_ `PJsonNode <#PJsonNode>`_ + ## `father` node. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a ``JArray``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = %[%"Mixing types", %42] + ## node.add(%"is madness") + ## echo node + ## # --> false assert father.kind == JArray father.elems.add(child) proc add*(obj: PJsonNode, key: string, val: PJsonNode) = - ## Adds ``(key, val)`` pair to the JObject node `obj`. For speed - ## reasons no check for duplicate keys is performed! - ## But ``[]=`` performs the check. + ## Adds ``(key, val)`` pair to a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_ `obj` node. + ## + ## For speed reasons no check for duplicate keys is performed! But ``[]=`` + ## performs the check. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node.add("age", newJInt(12)) + ## # This is wrong! But we need speed… + ## node.add("age", newJInt(24)) + ## echo node + ## # --> { "age": 12, "age": 24} assert obj.kind == JObject obj.fields.add((key, val)) proc `[]=`*(obj: PJsonNode, key: string, val: PJsonNode) = - ## Sets a field from a `JObject`. Performs a check for duplicate keys. + ## Sets a field from a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_ `obj` node. + ## + ## Unlike the `add() <#add,PJsonNode,string,PJsonNode>`_ proc this will + ## perform a check for duplicate keys and replace existing values. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node["age"] = %12 + ## # The new value replaces the previous one. + ## node["age"] = %24 + ## echo node + ## # --> { "age": 24} assert(obj.kind == JObject) for i in 0..obj.fields.len-1: if obj.fields[i].key == key: @@ -736,6 +1109,18 @@ proc `{}=`*(node: PJsonNode, names: varargs[string], value: PJsonNode) = proc delete*(obj: PJsonNode, key: string) = ## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs. + ## + ## If `key` doesn't exist in `obj` ``EInvalidIndex`` will be raised. This + ## proc will `assert `_ in debug builds if `node` is not + ## a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = %[("age", %37), ("name", %"Chris"), ("male", %false)] + ## echo node + ## # --> { "age": 37, "name": "Chris", "male": false} + ## node.delete("age") + ## echo node + ## # --> { "name": "Chris", "male": false} assert(obj.kind == JObject) for i in 0..obj.fields.len-1: if obj.fields[i].key == key: @@ -744,7 +1129,9 @@ proc delete*(obj: PJsonNode, key: string) = raise newException(EInvalidIndex, "key not in object") proc copy*(p: PJsonNode): PJsonNode = - ## Performs a deep copy of `a`. + ## Performs a deep copy of `p`. + ## + ## Modifications to the copy won't affect the original. case p.kind of JString: result = newJString(p.str) @@ -779,6 +1166,12 @@ proc nl(s: var string, ml: bool) = proc escapeJson*(s: string): string = ## Converts a string `s` to its JSON representation. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## echo """name: "Torbjørn"""".escapeJson + ## # --> "name: \"Torbj\u00F8rn\"" result = newStringOfCap(s.len + s.len shr 3) result.add("\"") for x in runes(s): @@ -850,24 +1243,58 @@ proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = true, result.add("null") proc pretty*(node: PJsonNode, indent = 2): string = - ## Converts `node` to its JSON Representation, with indentation and - ## on multiple lines. + ## Converts `node` to a pretty JSON representation. + ## + ## The representation will have indentation use multiple lines. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %33), ("name", %"Sojin")] + ## echo node + ## # --> { "age": 33, "name": "Sojin"} + ## echo node.pretty + ## # --> { + ## # "age": 33, + ## # "name": "Sojin" + ## # } result = "" toPretty(result, node, indent) proc `$`*(node: PJsonNode): string = - ## Converts `node` to its JSON Representation on one line. + ## Converts `node` to its JSON representation on one line. result = "" toPretty(result, node, 1, false) iterator items*(node: PJsonNode): PJsonNode = - ## Iterator for the items of `node`. `node` has to be a JArray. + ## Iterator for the items of `node`. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a `JArray <#TJsonNodeKind>`_. On release builds it will likely crash. + ## Example: + ## + ## .. code-block:: nimrod + ## let numbers = %[%1, %2, %3] + ## for n in numbers.items: + ## echo "Number ", n + ## ## --> Number 1 + ## ## Number 2 + ## ## Number 3 assert node.kind == JArray for i in items(node.elems): yield i iterator pairs*(node: PJsonNode): tuple[key: string, val: PJsonNode] = - ## Iterator for the child elements of `node`. `node` has to be a JObject. + ## Iterator for the child elements of `node`. + ## + ## This proc will `assert `_ in debug builds if `node` is + ## not a `JObject <#TJsonNodeKind>`_. On release builds it will likely crash. + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %[("age", %37), ("name", %"Chris")] + ## for key, value in node.pairs: + ## echo "Key: ", key, ", value: ", value + ## # --> Key: age, value: 37 + ## # Key: name, value: "Chris" assert node.kind == JObject for key, val in items(node.fields): yield (key, val) @@ -926,8 +1353,12 @@ proc parseJson(p: var TJsonParser): PJsonNode = when not defined(js): proc parseJson*(s: PStream, filename: string): PJsonNode = - ## Parses from a stream `s` into a `PJsonNode`. `filename` is only needed - ## for nice error messages. + ## Generic convenience proc to parse stream `s` into a `PJsonNode`. + ## + ## This wraps around `open() <#open>`_ and `next() <#next>`_ to return the + ## full JSON DOM. Errors will be raised as exceptions, this requires the + ## `filename` parameter to not be ``nil`` to avoid crashes. + assert(not isNil(filename)) var p: TJsonParser p.open(s, filename) discard getTok(p) # read first token @@ -936,10 +1367,28 @@ when not defined(js): proc parseJson*(buffer: string): PJsonNode = ## Parses JSON from `buffer`. + ## + ## Specialized version around `parseJson(PStream, string) + ## <#parseJson,PStream,string>`_. Example: + ## + ## .. code-block:: nimrod + ## let + ## smallJson = """{"test": 1.3, "key2": true}""" + ## jobj = parseJson(smallJson) + ## assert jobj.kind == JObject + ## + ## assert jobj["test"].kind == JFloat + ## echo jobj["test"].fnum # --> 1.3 + ## + ## assert jobj["key2"].kind == JBool + ## echo jobj["key2"].bval # --> true result = parseJson(newStringStream(buffer), "input") proc parseFile*(filename: string): PJsonNode = ## Parses `file` into a `PJsonNode`. + ## + ## Specialized version around `parseJson(PStream, string) + ## <#parseJson,PStream,string>`_. var stream = newFileStream(filename, fmRead) if stream == nil: raise newException(EIO, "cannot read from file: " & filename) From 57dadb35dc668c269d0734639bd84d95998a1f19 Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Sat, 4 Oct 2014 22:59:13 +0200 Subject: [PATCH 09/18] Hides TJsonError, it wasn't being used. --- lib/pure/json.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index c762206179..5d51c2d87b 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -89,7 +89,7 @@ type tkColon, tkComma - TJsonError* = enum ## enumeration that lists all errors that can occur + TJsonError = enum ## enumeration that lists all errors that can occur errNone, ## no error errInvalidToken, ## invalid token errStringExpected, ## string expected From aa1fb9a07ea3d48f7350c3dd9a26f2c22e38ae67 Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Sat, 11 Oct 2014 00:53:26 +0200 Subject: [PATCH 10/18] Adds stringification support for nnkPostfix nodes. --- lib/core/macros.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index e290cce329..455f99c9e9 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -620,6 +620,8 @@ proc `body=`*(someProc: PNimrodNode, val: PNimrodNode) {.compileTime.} = someProc[high(someProc)] = val else: badNodeKind someProc.kind, "body=" + +proc basename*(a: PNimrodNode): PNimrodNode {.compiletime.} proc `$`*(node: PNimrodNode): string {.compileTime.} = @@ -627,6 +629,8 @@ proc `$`*(node: PNimrodNode): string {.compileTime.} = case node.kind of nnkIdent: result = $node.ident + of nnkPostfix: + result = $node.basename.ident & "*" of nnkStrLit..nnkTripleStrLit: result = node.strVal else: @@ -669,7 +673,7 @@ proc insert*(a: PNimrodNode; pos: int; b: PNimrodNode) {.compileTime.} = a[i + 1] = a[i] a[pos] = b -proc basename*(a: PNimrodNode): PNimrodNode {.compiletime.} = +proc basename*(a: PNimrodNode): PNimrodNode = ## Pull an identifier from prefix/postfix expressions case a.kind of nnkIdent: return a From 11a2cfb306e50d50eb5c9a35864f5dbbfdabe75f Mon Sep 17 00:00:00 2001 From: Milos Negovanovic Date: Tue, 21 Oct 2014 15:55:02 +0100 Subject: [PATCH 11/18] Add comment. --- lib/impure/db_mysql.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index eb4092f37c..ce48a32edf 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -58,6 +58,7 @@ when false: discard mysql_stmt_close(stmt) proc dbQuote*(s: string): string = + ## DB quotes the string. result = "'" for c in items(s): if c == '\'': add(result, "''") From 2a203e5340654936e3bbe1c0ac392221d15c9aeb Mon Sep 17 00:00:00 2001 From: Milos Negovanovic Date: Tue, 21 Oct 2014 15:56:00 +0100 Subject: [PATCH 12/18] Add comment. --- lib/impure/db_postgres.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index 67e769ed20..510cb8e452 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -48,7 +48,8 @@ proc dbError*(msg: string) {.noreturn.} = e.msg = msg raise e -proc dbQuote(s: string): string = +proc dbQuote*(s: string): string = + ## DB quotes the string. result = "'" for c in items(s): if c == '\'': add(result, "''") From d5b94390dcfe61c645b43305e3673b6c696b39c7 Mon Sep 17 00:00:00 2001 From: Varriount Date: Sat, 25 Oct 2014 13:38:14 -0400 Subject: [PATCH 13/18] Update nsis.tmpl Fix adding the mingw binary to the path variable --- tools/niminst/nsis.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index 974def6340..14b1e7d77f 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -162,6 +162,7 @@ ; Section for adding tools to the PATH variable Section "Setup Path Environment" PathSection ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw" + ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw\bin" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\bin" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\babel" SectionEnd @@ -248,6 +249,7 @@ ; Remove entries from the PATH environment variable ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw" + ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw\bin" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\bin" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\babel" SectionEnd From 6e3720155adec34f938974be1e7d5df07901bd2d Mon Sep 17 00:00:00 2001 From: Reimer Behrends Date: Thu, 30 Oct 2014 03:42:50 +0100 Subject: [PATCH 14/18] Various atomics fixes for gcc/clang. Make atomicInc/atomicDec work with clang, too; also, actually import the memory model constants from C rather than relying on the enum matching the C constants by happenstance. --- lib/system/atomics.nim | 46 ++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim index 695a5f63e0..3ef9d00ec9 100644 --- a/lib/system/atomics.nim +++ b/lib/system/atomics.nim @@ -13,24 +13,30 @@ const someGcc = defined(gcc) or defined(llvm_gcc) or defined(clang) when someGcc and hasThreadSupport: - type - AtomMemModel* = enum - ATOMIC_RELAXED, ## No barriers or synchronization. - ATOMIC_CONSUME, ## Data dependency only for both barrier and - ## synchronization with another thread. - ATOMIC_ACQUIRE, ## Barrier to hoisting of code and synchronizes with - ## release (or stronger) - ## semantic stores from another thread. - ATOMIC_RELEASE, ## Barrier to sinking of code and synchronizes with - ## acquire (or stronger) - ## semantic loads from another thread. - ATOMIC_ACQ_REL, ## Full barrier in both directions and synchronizes - ## with acquire loads - ## and release stores in another thread. - ATOMIC_SEQ_CST ## Full barrier in both directions and synchronizes - ## with acquire loads - ## and release stores in all threads. + type AtomMemModel* = distinct cint + var ATOMIC_RELAXED* {.importc: "__ATOMIC_RELAXED", nodecl.}: AtomMemModel + ## No barriers or synchronization. + var ATOMIC_CONSUME* {.importc: "__ATOMIC_CONSUME", nodecl.}: AtomMemModel + ## Data dependency only for both barrier and + ## synchronization with another thread. + var ATOMIC_ACQUIRE* {.importc: "__ATOMIC_ACQUIRE", nodecl.}: AtomMemModel + ## Barrier to hoisting of code and synchronizes with + ## release (or stronger) + ## semantic stores from another thread. + var ATOMIC_RELEASE* {.importc: "__ATOMIC_RELEASE", nodecl.}: AtomMemModel + ## Barrier to sinking of code and synchronizes with + ## acquire (or stronger) + ## semantic loads from another thread. + var ATOMIC_ACQ_REL* {.importc: "__ATOMIC_ACQ_REL", nodecl.}: AtomMemModel + ## Full barrier in both directions and synchronizes + ## with acquire loads + ## and release stores in another thread. + var ATOMIC_SEQ_CST* {.importc: "__ATOMIC_SEQ_CST", nodecl.}: AtomMemModel + ## Full barrier in both directions and synchronizes + ## with acquire loads + ## and release stores in all threads. + type TAtomType* = TNumber|pointer|ptr|char ## Type Class representing valid types for use with atomic procs @@ -166,14 +172,14 @@ else: result = p[] proc atomicInc*(memLoc: var int, x: int = 1): int = - when defined(gcc) and hasThreadSupport: + when someGcc and hasThreadSupport: result = atomic_add_fetch(memLoc.addr, x, ATOMIC_RELAXED) else: inc(memLoc, x) result = memLoc proc atomicDec*(memLoc: var int, x: int = 1): int = - when defined(gcc) and hasThreadSupport: + when someGcc and hasThreadSupport: when declared(atomic_sub_fetch): result = atomic_sub_fetch(memLoc.addr, x, ATOMIC_RELAXED) else: @@ -196,7 +202,7 @@ else: # XXX is this valid for 'int'? -when (defined(x86) or defined(amd64)) and (defined(gcc) or defined(llvm_gcc)): +when (defined(x86) or defined(amd64)) and someGcc: proc cpuRelax {.inline.} = {.emit: """asm volatile("pause" ::: "memory");""".} elif (defined(x86) or defined(amd64)) and defined(vcc): From 72f4620b2adbf4170f841e38e05e531fd662c247 Mon Sep 17 00:00:00 2001 From: Varriount Date: Sun, 2 Nov 2014 17:47:02 -0500 Subject: [PATCH 15/18] Update nsis.tmpl --- tools/niminst/nsis.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index 14b1e7d77f..e5f72fbc31 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -212,6 +212,7 @@ ; Shortcuts !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}" !insertmacro MUI_STARTMENU_WRITE_END ignore: SectionEnd From be0fbdea655b43ba6118e3093aa21158cac6b635 Mon Sep 17 00:00:00 2001 From: Varriount Date: Sun, 2 Nov 2014 18:32:25 -0500 Subject: [PATCH 16/18] Update nsis.tmpl --- tools/niminst/nsis.tmpl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index e5f72fbc31..1051b65e79 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -204,16 +204,15 @@ abort ${EndIf} + ; Shortcuts # if d.len >= 6: # let startMenuEntry = d[5] # let e = splitFile(startMenuEntry).name.capitalize - CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}" - # end if - - ; Shortcuts !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}" !insertmacro MUI_STARTMENU_WRITE_END + # end if + ignore: SectionEnd #end From 621ead411062c1d3682a8b3414a970f12b43f7ac Mon Sep 17 00:00:00 2001 From: Araq Date: Mon, 3 Nov 2014 00:57:31 +0100 Subject: [PATCH 17/18] only produce the link to the docs if they are installed --- compiler/nimrod.ini | 2 +- tools/niminst/nsis.tmpl | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/nimrod.ini b/compiler/nimrod.ini index ae1c6053f1..7135b3490f 100644 --- a/compiler/nimrod.ini +++ b/compiler/nimrod.ini @@ -126,7 +126,7 @@ Files: "start.bat" BinPath: r"bin;dist\mingw\bin;dist" ; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry -Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip" +Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|doc\overview.html" Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip" Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.1.3.zip|aporia\bin\aporia.exe" ; for now only NSIS supports optional downloads diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index 1051b65e79..40e171d416 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -149,11 +149,6 @@ CreateShortCut "$DESKTOP\?{c.displayName}.lnk" "$INSTDIR\?{c.name}.exe" #end if - ; Add shortcuts for the documentation - #for f in items(c.cat[fcDocStart]): - CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{splitFile(f).name}.lnk" "$INSTDIR\?{f.toWin}" - #end for - ; Write the shortcut to the uninstaller CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk" "$INSTDIR\uninstaller.exe" !insertmacro MUI_STARTMENU_WRITE_END From 6935171b85a1b08b1cdfba2a56d5291219d89b5f Mon Sep 17 00:00:00 2001 From: Varriount Date: Mon, 3 Nov 2014 02:17:39 -0500 Subject: [PATCH 18/18] Fix math.nim on windows --- lib/pure/math.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 97c7b0e052..0f6a07f135 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -14,7 +14,7 @@ ## `_. include "system/inclrtl" - +import "impure/fenv" {.push debugger:off .} # the user does not want to trace a part # of the standard library!