Skip to content

Commit

Permalink
especially nested tuples do not compile (#2)
Browse files Browse the repository at this point in the history
* add test from sealmove

* add option test fwiw

* allow sponsorship

* support static cstring 🤨

* remove concept, fixes #1

* limited nim-1.2 support

* fixup for object example

* that should do it
  • Loading branch information
disruptek authored Dec 30, 2020
1 parent 4369cb8 commit bd92834
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 66 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: disruptek
6 changes: 3 additions & 3 deletions docs/bench.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/eminim.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/packed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 53 additions & 41 deletions jason.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import std/strutils
type
Json* = distinct string ## Serialized JSON.

JasonObject = concept j
for v in fields(j):
v is Jasonable

JasonArray = concept j
for v in j:
v is Jasonable
Expand Down Expand Up @@ -129,14 +125,18 @@ proc jasonify*(node: NimNode): NimNode =
else:
result = newCall(ident"Json", result)

macro jason*(s: string): Json =
macro jason*(s: string or cstring): Json =
## Escapes a string to form "JSON".
runnableExamples:
let j = jason"goats"
assert $j == "\"goats\""

let escapist = bindSym"escapeJson"
result = jasonify newCall(escapist, s)
result = s
# convert cstring into string, first
if s.getType.strVal == "cstring":
result = newCall(bindSym"$", result)
result = jasonify newCall(escapist, result)

macro jason*(b: bool): Json =
## Produce a JSON boolean, either `true` or `false`.
Expand All @@ -160,10 +160,43 @@ func jason*(f: SomeFloat): Json =
## Render any Nim float as a JSON number.
result = Json $f

proc jasonSquare*(a: NimNode): NimNode =
## Render an iterable that isn't a named-tuple or object as a JSON array.
let adder = bindSym"add"
result = newStmtList()

var js = gensym(nskVar, "js")
result.add newVarStmt(js, jasonify"[") # the leading [

# add a loop over the items in the iterable
result.add:
var index = gensym(nskForVar, "index") # make loop var
var value = gensym(nskForVar, "value") # make loop var

var body = nnkStmtList.newNimNode # make body of a loop
body.add:
newIfStmt (infix(index, "!=", newLit 0), # if index != 0
adder.newCall(js, jasonify",")) # js.add Json","

body.add adder.newCall(js, value.jason) # add the json for value

var loop = nnkForStmt.newNimNode # for loop
loop.add index # add loop var
loop.add value # add loop var
loop.add a # add any iterable
loop.add body # add loop body
loop

result.add adder.newCall(js, jasonify"]") # add the trailing ]
result.add js # the last statement in the stmtlist is the json

macro jason*[I, T](a: array[I, T]): Json =
## Render any Nim array as a series of JSON values.
# make sure the array ast has the form we expect
let typ = a.getTypeImpl
# support for nim-1.2
if typ.kind == nnkBracket:
return jasonSquare a
expectKind(typ, nnkBracketExpr)
if len(typ) < 3 or typ[0].strVal != "array":
error "unexpected array form:\n" & treeRepr(typ)
Expand Down Expand Up @@ -209,6 +242,7 @@ else:
## Static JSON encoder for `typ`.
jasonify jason(j).string

staticJason cstring
staticJason string
staticJason bool
staticJason float
Expand Down Expand Up @@ -243,36 +277,6 @@ proc composeWithComma(parent: NimNode; js: NimNode): NimNode =

sep

proc jasonSquare*(a: NimNode): NimNode =
## Render an iterable that isn't a named-tuple or object as a JSON array.
let adder = bindSym"add"
result = newStmtList()

var js = gensym(nskVar, "js")
result.add newVarStmt(js, jasonify"[") # the leading [

# add a loop over the items in the iterable
result.add:
var index = gensym(nskForVar, "index") # make loop var
var value = gensym(nskForVar, "value") # make loop var

var body = nnkStmtList.newNimNode # make body of a loop
body.add:
newIfStmt (infix(index, "!=", newLit 0), # if index != 0
adder.newCall(js, jasonify",")) # js.add Json","

body.add adder.newCall(js, value.jason) # add the json for value

var loop = nnkForStmt.newNimNode # for loop
loop.add index # add loop var
loop.add value # add loop var
loop.add a # add any iterable
loop.add body # add loop body
loop

result.add adder.newCall(js, jasonify"]") # add the trailing ]
result.add js # the last statement in the stmtlist is the json

proc jasonTuple(t: NimNode): NimNode =
## Render an anonymous tuple as a JSON array.
let adder = bindSym"add"
Expand All @@ -295,6 +299,8 @@ macro jason*(a: JasonArray): Json =
runnableExamples:
let j = jason @[1, 3, 5, 7]
assert $j == "[1,3,5,7]"
let k = jason (1, 3, 5, 7)
assert $k == "[1,3,5,7]"

case a.kind
of nnkTupleConstr:
Expand Down Expand Up @@ -334,17 +340,16 @@ proc jasonCurly(o: NimNode): NimNode =
# the last statement in the statement list is the json
result.add js

# i want this to be jason(o: ref Jasonable)
func jason*(o: ref): Json =
## Render a Nim `ref` as either `null` or the value to which it refers.
if o.isNil:
result = Json"null"
else:
result = jason o[]

macro jason*(o: JasonObject): Json =
## Render an anonymous Nim tuple as a JSON array; objects and named
## tuples become JSON objects.
macro jason*(o: tuple): Json =
## Render an anonymous Nim tuple as a JSON array;
## named tuples become JSON objects.
runnableExamples:
let j = jason (1, "too", 3.0)
assert $j == """[1,"too",3.0]"""
Expand All @@ -355,7 +360,14 @@ macro jason*(o: JasonObject): Json =
of nnkTupleConstr:
result = jasonTuple o
else:
# use our object construction code for named tuples, objects
# use our object construction code for named tuples
result = jasonCurly o

macro jason*(o: object): Json =
## Render an object as JSON.
runnableExamples:
let j = jason Exception(name: "jeff", msg: "bummer")
assert $j == """{"parent":null,"name":"jeff","msg":"bummer","trace":[],"up":null}"""
result = jasonCurly o

export jason
2 changes: 1 addition & 1 deletion jason.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description = "compile-time json"
license = "MIT"

requires "nim >= 1.4.0 & < 2.0.0"
requires "https://github.com/disruptek/testes >= 0.7.3 & < 1.0.0"
requires "https://github.com/disruptek/testes >= 0.7.12 & < 1.0.0"
requires "https://github.com/disruptek/criterion < 1.0.0"

task test, "run tests for ci":
Expand Down
2 changes: 1 addition & 1 deletion testes
59 changes: 41 additions & 18 deletions tests/test.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import std/options

import testes
import jason

Expand Down Expand Up @@ -48,34 +50,39 @@ testes:
check Two.jason == "1"

test "array":
check [1, 2, 3].jason == "[1,2,3]"
check @[1, 2, 3].jason == "[1,2,3]"
check:
[1, 2, 3].jason == "[1,2,3]"
@[1, 2, 3].jason == "[1,2,3]"

test "slow array":
let (x, y) = ("3", 4)
check [x, x, x].jason == Json"""["3","3","3"]"""
check [y, y, y].jason == Json"""[4,4,4]"""
check:
[x, x, x].jason == Json"""["3","3","3"]"""
[y, y, y].jason == Json"""[4,4,4]"""

test "ref":
var
x: ref int = new(int)
x[] = 45
check "45" == x.jason
check jason((ref string) nil) == "null"
check:
"45" == x.jason
jason((ref string) nil) == "null"

test "tuple":
let dumb1: (int, string) = (1, "2")
let dumb2: tuple[a: int, b: string] = (1, "2")
let dumb3: tuple[a: int, b: string] = (a: 1, b: "2")

check dumb1.jason == Json"""[1,"2"]"""
check dumb2.jason == Json"""{"a":1,"b":"2"}"""
check dumb3.jason == Json"""{"a":1,"b":"2"}"""
let
dumb1: (int, string) = (1, "2")
dumb2: tuple[a: int, b: string] = (1, "2")
dumb3: tuple[a: int, b: string] = (a: 1, b: "2")
check:
dumb1.jason == Json"""[1,"2"]"""
dumb2.jason == Json"""{"a":1,"b":"2"}"""
dumb3.jason == Json"""{"a":1,"b":"2"}"""

test "slow tuple":
let (x, y) = ("3", 4)
check (x, y).jason == Json"""["3",4]"""
check (a: x, b: y).jason == Json"""{"a":"3","b":4}"""
check:
(x, y).jason == Json"""["3",4]"""
(a: x, b: y).jason == Json"""{"a":"3","b":4}"""

test "object":
type
Expand Down Expand Up @@ -123,6 +130,22 @@ testes:
if n.x mod 2 == 0: jasonify"1"
else: jasonify"0"

check a.jason == "1"
check b.jason == """"odd""""
check c.jason == """["odd","even","odd"]"""
check:
a.jason == "1"
b.jason == """"odd""""
c.jason == """["odd","even","odd"]"""

test "option":
check:
$jason(some "foo") == """{"val":"foo","has":true}"""
$jason(none int) == """{"val":0,"has":false}"""

test "tuple attack":
let x = ((1, 2),(3, 4),(5, 6),(7, 8))
check $jason(x) == "[[1,2],[3,4],[5,6],[7,8]]"
let y = (((1, 2),(3, 4)), ((5, 6),(7, 8))) # (from sealmove)
check $jason(y) == "[[[1,2],[3,4]],[[5,6],[7,8]]]"

test "object example":
let j = jason Exception(name: "jeff", msg: "bummer")
check $j == """{"parent":null,"name":"jeff","msg":"bummer","trace":[],"up":null}"""

0 comments on commit bd92834

Please sign in to comment.