Merge branch 'devel' of github.com:nim-lang/Nim into devel
This commit is contained in:
commit
f9ddd00d3a
5 changed files with 315 additions and 29 deletions
|
|
@ -764,8 +764,9 @@ proc callCCompiler*(projectfile: string) =
|
||||||
add(objfiles, quoteShell(
|
add(objfiles, quoteShell(
|
||||||
addFileExt(objFile, CC[cCompiler].objExt)))
|
addFileExt(objFile, CC[cCompiler].objExt)))
|
||||||
for x in toCompile:
|
for x in toCompile:
|
||||||
|
let objFile = if noAbsolutePaths(): x.obj.extractFilename else: x.obj
|
||||||
add(objfiles, ' ')
|
add(objfiles, ' ')
|
||||||
add(objfiles, quoteShell(x.obj))
|
add(objfiles, quoteShell(objFile))
|
||||||
|
|
||||||
linkCmd = getLinkCmd(projectfile, objfiles)
|
linkCmd = getLinkCmd(projectfile, objfiles)
|
||||||
if optCompileOnly notin gGlobalOptions:
|
if optCompileOnly notin gGlobalOptions:
|
||||||
|
|
|
||||||
|
|
@ -1346,6 +1346,16 @@ proc createJsonIndexer(jsonNode: NimNode,
|
||||||
indexNode
|
indexNode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc transformJsonIndexer(jsonNode: NimNode): NimNode =
|
||||||
|
case jsonNode.kind
|
||||||
|
of nnkBracketExpr:
|
||||||
|
result = newNimNode(nnkCurlyExpr)
|
||||||
|
else:
|
||||||
|
result = jsonNode.copy()
|
||||||
|
|
||||||
|
for child in jsonNode:
|
||||||
|
result.add(transformJsonIndexer(child))
|
||||||
|
|
||||||
template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
|
template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
|
||||||
ast: string) =
|
ast: string) =
|
||||||
if node.kind notin kinds:
|
if node.kind notin kinds:
|
||||||
|
|
@ -1524,6 +1534,35 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
|
||||||
|
|
||||||
doAssert result.len > 0
|
doAssert result.len > 0
|
||||||
|
|
||||||
|
proc processFields(obj: NimNode,
|
||||||
|
jsonNode: NimNode): seq[NimNode] {.compileTime.} =
|
||||||
|
## Process all the fields of an ``ObjectTy`` and any of its
|
||||||
|
## parent type's fields (via inheritance).
|
||||||
|
result = @[]
|
||||||
|
case obj.kind
|
||||||
|
of nnkObjectTy:
|
||||||
|
expectKind(obj[2], nnkRecList)
|
||||||
|
for field in obj[2]:
|
||||||
|
let nodes = processObjField(field, jsonNode)
|
||||||
|
result.add(nodes)
|
||||||
|
|
||||||
|
# process parent type fields
|
||||||
|
case obj[1].kind
|
||||||
|
of nnkBracketExpr:
|
||||||
|
assert $obj[1][0] == "ref"
|
||||||
|
result.add(processFields(getType(obj[1][1]), jsonNode))
|
||||||
|
of nnkSym:
|
||||||
|
result.add(processFields(getType(obj[1]), jsonNode))
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
of nnkTupleTy:
|
||||||
|
for identDefs in obj:
|
||||||
|
expectKind(identDefs, nnkIdentDefs)
|
||||||
|
let nodes = processObjField(identDefs[0], jsonNode)
|
||||||
|
result.add(nodes)
|
||||||
|
else:
|
||||||
|
doAssert false, "Unable to process field type: " & $obj.kind
|
||||||
|
|
||||||
proc processType(typeName: NimNode, obj: NimNode,
|
proc processType(typeName: NimNode, obj: NimNode,
|
||||||
jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} =
|
jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} =
|
||||||
## Process a type such as ``Sym "float"`` or ``ObjectTy ...``.
|
## Process a type such as ``Sym "float"`` or ``ObjectTy ...``.
|
||||||
|
|
@ -1533,20 +1572,21 @@ proc processType(typeName: NimNode, obj: NimNode,
|
||||||
## .. code-block::plain
|
## .. code-block::plain
|
||||||
## ObjectTy
|
## ObjectTy
|
||||||
## Empty
|
## Empty
|
||||||
## Empty
|
## InheritanceInformation
|
||||||
## RecList
|
## RecList
|
||||||
## Sym "events"
|
## Sym "events"
|
||||||
case obj.kind
|
case obj.kind
|
||||||
of nnkObjectTy:
|
of nnkObjectTy, nnkTupleTy:
|
||||||
# Create object constructor.
|
# Create object constructor.
|
||||||
result = newNimNode(nnkObjConstr)
|
result =
|
||||||
result.add(typeName) # Name of the type to construct.
|
if obj.kind == nnkObjectTy: newNimNode(nnkObjConstr)
|
||||||
|
else: newNimNode(nnkPar)
|
||||||
|
|
||||||
# Process each object field and add it as an exprColonExpr
|
if obj.kind == nnkObjectTy:
|
||||||
expectKind(obj[2], nnkRecList)
|
result.add(typeName) # Name of the type to construct.
|
||||||
for field in obj[2]:
|
|
||||||
let nodes = processObjField(field, jsonNode)
|
# Process each object/tuple field and add it as an exprColonExpr
|
||||||
result.add(nodes)
|
result.add(processFields(obj, jsonNode))
|
||||||
|
|
||||||
# Object might be null. So we need to check for that.
|
# Object might be null. So we need to check for that.
|
||||||
if isRef:
|
if isRef:
|
||||||
|
|
@ -1569,25 +1609,14 @@ proc processType(typeName: NimNode, obj: NimNode,
|
||||||
`getEnumCall`
|
`getEnumCall`
|
||||||
)
|
)
|
||||||
of nnkSym:
|
of nnkSym:
|
||||||
case ($typeName).normalize
|
let name = ($typeName).normalize
|
||||||
of "float":
|
case name
|
||||||
result = quote do:
|
|
||||||
(
|
|
||||||
verifyJsonKind(`jsonNode`, {JFloat, JInt}, astToStr(`jsonNode`));
|
|
||||||
if `jsonNode`.kind == JFloat: `jsonNode`.fnum else: `jsonNode`.num.float
|
|
||||||
)
|
|
||||||
of "string":
|
of "string":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`));
|
verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`));
|
||||||
if `jsonNode`.kind == JNull: nil else: `jsonNode`.str
|
if `jsonNode`.kind == JNull: nil else: `jsonNode`.str
|
||||||
)
|
)
|
||||||
of "int":
|
|
||||||
result = quote do:
|
|
||||||
(
|
|
||||||
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
|
|
||||||
`jsonNode`.num.int
|
|
||||||
)
|
|
||||||
of "biggestint":
|
of "biggestint":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
|
|
@ -1601,12 +1630,36 @@ proc processType(typeName: NimNode, obj: NimNode,
|
||||||
`jsonNode`.bval
|
`jsonNode`.bval
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
doAssert false, "Unable to process nnkSym " & $typeName
|
if name.startsWith("int") or name.startsWith("uint"):
|
||||||
|
result = quote do:
|
||||||
|
(
|
||||||
|
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
|
||||||
|
`jsonNode`.num.`obj`
|
||||||
|
)
|
||||||
|
elif name.startsWith("float"):
|
||||||
|
result = quote do:
|
||||||
|
(
|
||||||
|
verifyJsonKind(`jsonNode`, {JInt, JFloat}, astToStr(`jsonNode`));
|
||||||
|
if `jsonNode`.kind == JFloat: `jsonNode`.fnum.`obj` else: `jsonNode`.num.`obj`
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
doAssert false, "Unable to process nnkSym " & $typeName
|
||||||
else:
|
else:
|
||||||
doAssert false, "Unable to process type: " & $obj.kind
|
doAssert false, "Unable to process type: " & $obj.kind
|
||||||
|
|
||||||
doAssert(not result.isNil(), "processType not initialised.")
|
doAssert(not result.isNil(), "processType not initialised.")
|
||||||
|
|
||||||
|
import options
|
||||||
|
proc workaroundMacroNone[T](): Option[T] =
|
||||||
|
none(T)
|
||||||
|
|
||||||
|
proc depth(n: NimNode, current = 0): int =
|
||||||
|
result = 1
|
||||||
|
for child in n:
|
||||||
|
let d = 1 + child.depth(current + 1)
|
||||||
|
if d > result:
|
||||||
|
result = d
|
||||||
|
|
||||||
proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
|
proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
|
||||||
## Accepts a type description, i.e. "ref Type", "seq[Type]", "Type" etc.
|
## Accepts a type description, i.e. "ref Type", "seq[Type]", "Type" etc.
|
||||||
##
|
##
|
||||||
|
|
@ -1616,10 +1669,50 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
|
||||||
# echo("--createConsuctor-- \n", treeRepr(typeSym))
|
# echo("--createConsuctor-- \n", treeRepr(typeSym))
|
||||||
# echo()
|
# echo()
|
||||||
|
|
||||||
|
if depth(jsonNode) > 150:
|
||||||
|
error("The `to` macro does not support ref objects with cycles.", jsonNode)
|
||||||
|
|
||||||
case typeSym.kind
|
case typeSym.kind
|
||||||
of nnkBracketExpr:
|
of nnkBracketExpr:
|
||||||
var bracketName = ($typeSym[0]).normalize
|
var bracketName = ($typeSym[0]).normalize
|
||||||
case bracketName
|
case bracketName
|
||||||
|
of "option":
|
||||||
|
# TODO: Would be good to verify that this is Option[T] from
|
||||||
|
# options module I suppose.
|
||||||
|
let lenientJsonNode = transformJsonIndexer(jsonNode)
|
||||||
|
|
||||||
|
let optionGeneric = typeSym[1]
|
||||||
|
let value = createConstructor(typeSym[1], jsonNode)
|
||||||
|
let workaround = bindSym("workaroundMacroNone") # TODO: Nim Bug: This shouldn't be necessary.
|
||||||
|
|
||||||
|
result = quote do:
|
||||||
|
(
|
||||||
|
if `lenientJsonNode`.isNil: `workaround`[`optionGeneric`]() else: some[`optionGeneric`](`value`)
|
||||||
|
)
|
||||||
|
of "table", "orderedtable":
|
||||||
|
let tableKeyType = typeSym[1]
|
||||||
|
if ($tableKeyType).cmpIgnoreStyle("string") != 0:
|
||||||
|
error("JSON doesn't support keys of type " & $tableKeyType)
|
||||||
|
let tableValueType = typeSym[2]
|
||||||
|
|
||||||
|
let forLoopKey = genSym(nskForVar, "key")
|
||||||
|
let indexerNode = createJsonIndexer(jsonNode, forLoopKey)
|
||||||
|
let constructorNode = createConstructor(tableValueType, indexerNode)
|
||||||
|
|
||||||
|
let tableInit =
|
||||||
|
if bracketName == "table":
|
||||||
|
bindSym("initTable")
|
||||||
|
else:
|
||||||
|
bindSym("initOrderedTable")
|
||||||
|
|
||||||
|
# Create a statement expression containing a for loop.
|
||||||
|
result = quote do:
|
||||||
|
(
|
||||||
|
var map = `tableInit`[`tableKeyType`, `tableValueType`]();
|
||||||
|
verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`));
|
||||||
|
for `forLoopKey` in keys(`jsonNode`.fields): map[`forLoopKey`] = `constructorNode`;
|
||||||
|
map
|
||||||
|
)
|
||||||
of "ref":
|
of "ref":
|
||||||
# Ref type.
|
# Ref type.
|
||||||
var typeName = $typeSym[1]
|
var typeName = $typeSym[1]
|
||||||
|
|
@ -1663,12 +1756,23 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
|
||||||
let obj = getType(typeSym)
|
let obj = getType(typeSym)
|
||||||
result = processType(typeSym, obj, jsonNode, false)
|
result = processType(typeSym, obj, jsonNode, false)
|
||||||
of nnkSym:
|
of nnkSym:
|
||||||
|
# Handle JsonNode.
|
||||||
|
if ($typeSym).cmpIgnoreStyle("jsonnode") == 0:
|
||||||
|
return jsonNode
|
||||||
|
|
||||||
|
# Handle all other types.
|
||||||
let obj = getType(typeSym)
|
let obj = getType(typeSym)
|
||||||
if obj.kind == nnkBracketExpr:
|
if obj.kind == nnkBracketExpr:
|
||||||
# When `Sym "Foo"` turns out to be a `ref object`.
|
# When `Sym "Foo"` turns out to be a `ref object`.
|
||||||
result = createConstructor(obj, jsonNode)
|
result = createConstructor(obj, jsonNode)
|
||||||
else:
|
else:
|
||||||
result = processType(typeSym, obj, jsonNode, false)
|
result = processType(typeSym, obj, jsonNode, false)
|
||||||
|
of nnkTupleTy:
|
||||||
|
result = processType(typeSym, typeSym, jsonNode, false)
|
||||||
|
of nnkPar:
|
||||||
|
# TODO: The fact that `jsonNode` here works to give a good line number
|
||||||
|
# is weird. Specifying typeSym should work but doesn't.
|
||||||
|
error("Use a named tuple instead of: " & $toStrLit(typeSym), jsonNode)
|
||||||
else:
|
else:
|
||||||
doAssert false, "Unable to create constructor for: " & $typeSym.kind
|
doAssert false, "Unable to create constructor for: " & $typeSym.kind
|
||||||
|
|
||||||
|
|
@ -1796,10 +1900,18 @@ macro to*(node: JsonNode, T: typedesc): untyped =
|
||||||
expectKind(typeNode, nnkBracketExpr)
|
expectKind(typeNode, nnkBracketExpr)
|
||||||
doAssert(($typeNode[0]).normalize == "typedesc")
|
doAssert(($typeNode[0]).normalize == "typedesc")
|
||||||
|
|
||||||
result = createConstructor(typeNode[1], node)
|
# Create `temp` variable to store the result in case the user calls this
|
||||||
# TODO: Rename postProcessValue and move it (?)
|
# on `parseJson` (see bug #6604).
|
||||||
result = postProcessValue(result)
|
result = newNimNode(nnkStmtListExpr)
|
||||||
|
let temp = genSym(nskLet, "temp")
|
||||||
|
result.add quote do:
|
||||||
|
let `temp` = `node`
|
||||||
|
|
||||||
|
let constructor = createConstructor(typeNode[1], temp)
|
||||||
|
# TODO: Rename postProcessValue and move it (?)
|
||||||
|
result.add(postProcessValue(constructor))
|
||||||
|
|
||||||
|
# echo(treeRepr(result))
|
||||||
# echo(toStrLit(result))
|
# echo(toStrLit(result))
|
||||||
|
|
||||||
when false:
|
when false:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ discard """
|
||||||
file: "tjsonmacro.nim"
|
file: "tjsonmacro.nim"
|
||||||
output: ""
|
output: ""
|
||||||
"""
|
"""
|
||||||
import json, strutils
|
import json, strutils, options, tables
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
# Tests inspired by own use case (with some additional tests).
|
# Tests inspired by own use case (with some additional tests).
|
||||||
|
|
@ -247,3 +247,137 @@ when isMainModule:
|
||||||
let jnode = %b
|
let jnode = %b
|
||||||
let data = jnode.to(Bird)
|
let data = jnode.to(Bird)
|
||||||
doAssert data == b
|
doAssert data == b
|
||||||
|
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
MsgBase = ref object of RootObj
|
||||||
|
name*: string
|
||||||
|
|
||||||
|
MsgChallenge = ref object of MsgBase
|
||||||
|
challenge*: string
|
||||||
|
|
||||||
|
let data = %*{"name": "foo", "challenge": "bar"}
|
||||||
|
let msg = data.to(MsgChallenge)
|
||||||
|
doAssert msg.name == "foo"
|
||||||
|
doAssert msg.challenge == "bar"
|
||||||
|
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Color = enum Red, Brown
|
||||||
|
Thing = object
|
||||||
|
animal: tuple[fur: bool, legs: int]
|
||||||
|
color: Color
|
||||||
|
|
||||||
|
var j = parseJson("""
|
||||||
|
{"animal":{"fur":true,"legs":6},"color":"Red"}
|
||||||
|
""")
|
||||||
|
|
||||||
|
let parsed = to(j, Thing)
|
||||||
|
doAssert parsed.animal.fur
|
||||||
|
doAssert parsed.animal.legs == 6
|
||||||
|
doAssert parsed.color == Red
|
||||||
|
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Car = object
|
||||||
|
engine: tuple[name: string, capacity: float]
|
||||||
|
model: string
|
||||||
|
|
||||||
|
let j = """
|
||||||
|
{"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
|
||||||
|
"""
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
proc mulTest: JsonNode =
|
||||||
|
i.inc()
|
||||||
|
return parseJson(j)
|
||||||
|
|
||||||
|
let parsed = mulTest().to(Car)
|
||||||
|
doAssert parsed.engine.name == "V8"
|
||||||
|
|
||||||
|
doAssert i == 1
|
||||||
|
|
||||||
|
block:
|
||||||
|
# Option[T] support!
|
||||||
|
type
|
||||||
|
Car1 = object # TODO: Codegen bug when `Car`
|
||||||
|
engine: tuple[name: string, capacity: Option[float]]
|
||||||
|
model: string
|
||||||
|
year: Option[int]
|
||||||
|
|
||||||
|
let noYear = """
|
||||||
|
{"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let noYearParsed = parseJson(noYear)
|
||||||
|
let noYearDeser = to(noYearParsed, Car1)
|
||||||
|
doAssert noYearDeser.engine.capacity == some(5.5)
|
||||||
|
doAssert noYearDeser.year.isNone
|
||||||
|
doAssert noYearDeser.engine.name == "V8"
|
||||||
|
|
||||||
|
# Table[T, Y] support.
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Friend = object
|
||||||
|
name: string
|
||||||
|
age: int
|
||||||
|
|
||||||
|
Dynamic = object
|
||||||
|
name: string
|
||||||
|
friends: Table[string, Friend]
|
||||||
|
|
||||||
|
let data = """
|
||||||
|
{"friends": {
|
||||||
|
"John": {"name": "John", "age": 35},
|
||||||
|
"Elizabeth": {"name": "Elizabeth", "age": 23}
|
||||||
|
}, "name": "Dominik"}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let dataParsed = parseJson(data)
|
||||||
|
let dataDeser = to(dataParsed, Dynamic)
|
||||||
|
doAssert dataDeser.name == "Dominik"
|
||||||
|
doAssert dataDeser.friends["John"].age == 35
|
||||||
|
doAssert dataDeser.friends["Elizabeth"].age == 23
|
||||||
|
|
||||||
|
# JsonNode support
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Test = object
|
||||||
|
name: string
|
||||||
|
fallback: JsonNode
|
||||||
|
|
||||||
|
let data = """
|
||||||
|
{"name": "FooBar", "fallback": 56.42}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let dataParsed = parseJson(data)
|
||||||
|
let dataDeser = to(dataParsed, Test)
|
||||||
|
doAssert dataDeser.name == "FooBar"
|
||||||
|
doAssert dataDeser.fallback.kind == JFloat
|
||||||
|
doAssert dataDeser.fallback.getFloat() == 56.42
|
||||||
|
|
||||||
|
# int64, float64 etc support.
|
||||||
|
block:
|
||||||
|
type
|
||||||
|
Test1 = object
|
||||||
|
a: int8
|
||||||
|
b: int16
|
||||||
|
c: int32
|
||||||
|
d: int64
|
||||||
|
e: uint8
|
||||||
|
f: uint16
|
||||||
|
g: uint32
|
||||||
|
h: uint64
|
||||||
|
i: float32
|
||||||
|
j: float64
|
||||||
|
|
||||||
|
let data = """
|
||||||
|
{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7,
|
||||||
|
"h": 8, "i": 9.9, "j": 10.10}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let dataParsed = parseJson(data)
|
||||||
|
let dataDeser = to(dataParsed, Test1)
|
||||||
|
doAssert dataDeser.a == 1
|
||||||
|
doAssert dataDeser.f == 6
|
||||||
|
doAssert dataDeser.i == 9.9'f32
|
||||||
18
tests/stdlib/tjsonmacro_reject.nim
Normal file
18
tests/stdlib/tjsonmacro_reject.nim
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
discard """
|
||||||
|
file: "tjsonmacro_reject.nim"
|
||||||
|
line: 11
|
||||||
|
errormsg: "Use a named tuple instead of: (string, float)"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
type
|
||||||
|
Car = object
|
||||||
|
engine: (string, float)
|
||||||
|
model: string
|
||||||
|
|
||||||
|
let j = """
|
||||||
|
{"engine": {"name": "V8", "capacity": 5.5}, model: "Skyline"}
|
||||||
|
"""
|
||||||
|
let parsed = parseJson(j)
|
||||||
|
echo(to(parsed, Car))
|
||||||
21
tests/stdlib/tjsonmacro_reject2.nim
Normal file
21
tests/stdlib/tjsonmacro_reject2.nim
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
discard """
|
||||||
|
file: "tjsonmacro_reject2.nim"
|
||||||
|
line: 10
|
||||||
|
errormsg: "The `to` macro does not support ref objects with cycles."
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
type
|
||||||
|
Misdirection = object
|
||||||
|
cycle: Cycle
|
||||||
|
|
||||||
|
Cycle = ref object
|
||||||
|
foo: string
|
||||||
|
cycle: Misdirection
|
||||||
|
|
||||||
|
let data = """
|
||||||
|
{"cycle": null}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let dataParsed = parseJson(data)
|
||||||
|
let dataDeser = to(dataParsed, Cycle)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue