diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index dd4ad3b3a005..7707c1c0d269 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -98,3 +98,4 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasStyleChecks") defineSymbol("nimToOpenArrayCString") defineSymbol("nimHasUsed") + defineSymbol("nimFixedForwardGeneric") diff --git a/compiler/types.nim b/compiler/types.nim index dc5068d0e2f9..022995fa3994 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1121,9 +1121,16 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = result = sameChildrenAux(a, b, c) and sameFlags(a, b) if result and {ExactGenericParams, ExactTypeDescValues} * c.flags != {}: result = a.sym.position == b.sym.position - of tyGenericInvocation, tyGenericBody, tySequence, - tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, tyUncheckedArray, - tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyOpt, tyOwned: + of tyBuiltInTypeClass: + assert a.len == 1 + assert a[0].len == 0 + assert b.len == 1 + assert b[0].len == 0 + result = a[0].kind == b[0].kind + of tyGenericInvocation, tyGenericBody, tySequence, tyOpenArray, tySet, tyRef, + tyPtr, tyVar, tyLent, tySink, tyUncheckedArray, tyArray, tyProc, tyVarargs, + tyOrdinal, tyCompositeTypeClass, tyUserTypeClass, tyUserTypeClassInst, + tyAnd, tyOr, tyNot, tyAnything, tyOpt, tyOwned: cycleCheck() if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n result = sameChildrenAux(a, b, c) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 4dad325bc779..e833f612308b 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -833,50 +833,37 @@ proc parseJson(p: var JsonParser): JsonNode = of tkError, tkCurlyRi, tkBracketRi, tkColon, tkComma, tkEof: raiseParseErr(p, "{") -when not defined(js): - iterator parseJsonFragments*(s: Stream, filename: string = ""): JsonNode = - ## Parses from a stream `s` into `JsonNodes`. `filename` is only needed - ## for nice error messages. - ## The JSON fragments are separated by whitespace. This can be substantially - ## faster than the comparable loop - ## ``for x in splitWhitespace(s): yield parseJson(x)``. - ## This closes the stream `s` after it's done. - var p: JsonParser - p.open(s, filename) - try: - discard getTok(p) # read first token - while p.tok != tkEof: - yield p.parseJson() - finally: - p.close() - - proc parseJson*(s: Stream, filename: string = ""): JsonNode = - ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed - ## for nice error messages. - ## If `s` contains extra data, it will raise `JsonParsingError`. - ## This closes the stream `s` after it's done. - var p: JsonParser - p.open(s, filename) - try: - discard getTok(p) # read first token - result = p.parseJson() - eat(p, tkEof) # check if there is no extra data - finally: - p.close() - - proc parseJson*(buffer: string): JsonNode = - ## Parses JSON from `buffer`. - ## If `buffer` contains extra data, it will raise `JsonParsingError`. - result = parseJson(newStringStream(buffer), "input") - - proc parseFile*(filename: string): JsonNode = - ## Parses `file` into a `JsonNode`. - ## If `file` contains extra data, it will raise `JsonParsingError`. - var stream = newFileStream(filename, fmRead) - if stream == nil: - raise newException(IOError, "cannot read from file: " & filename) - result = parseJson(stream, filename) -else: +iterator parseJsonFragments*(s: Stream, filename: string = ""): JsonNode = + ## Parses from a stream `s` into `JsonNodes`. `filename` is only needed + ## for nice error messages. + ## The JSON fragments are separated by whitespace. This can be substantially + ## faster than the comparable loop + ## ``for x in splitWhitespace(s): yield parseJson(x)``. + ## This closes the stream `s` after it's done. + var p: JsonParser + p.open(s, filename) + try: + discard getTok(p) # read first token + while p.tok != tkEof: + yield p.parseJson() + finally: + p.close() + +proc parseJson*(s: Stream, filename: string = ""): JsonNode = + ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed + ## for nice error messages. + ## If `s` contains extra data, it will raise `JsonParsingError`. + ## This closes the stream `s` after it's done. + var p: JsonParser + p.open(s, filename) + try: + discard getTok(p) # read first token + result = p.parseJson() + eat(p, tkEof) # check if there is no extra data + finally: + p.close() + +when defined(js): from math import `mod` type JSObject = object @@ -946,38 +933,32 @@ else: result = newJNull() proc parseJson*(buffer: string): JsonNode = - return parseNativeJson(buffer).convertObject() - -# -- Json deserialiser macro. -- - -proc createJsonIndexer(jsonNode: NimNode, - index: string | int | NimNode): NimNode - {.compileTime.} = - when index is string: - let indexNode = newStrLitNode(index) - elif index is int: - let indexNode = newIntLitNode(index) - elif index is NimNode: - let indexNode = index - - result = newNimNode(nnkBracketExpr).add( - jsonNode, - indexNode - ) - -proc transformJsonIndexer(jsonNode: NimNode): NimNode = - case jsonNode.kind - of nnkBracketExpr: - result = newNimNode(nnkCurlyExpr) - else: - result = jsonNode.copy() + when nimvm: + return parseJson(newStringStream(buffer), "input") + else: + return parseNativeJson(buffer).convertObject() + +else: + proc parseJson*(buffer: string): JsonNode = + ## Parses JSON from `buffer`. + ## If `buffer` contains extra data, it will raise `JsonParsingError`. + result = parseJson(newStringStream(buffer), "input") - for child in jsonNode: - result.add(transformJsonIndexer(child)) + proc parseFile*(filename: string): JsonNode = + ## Parses `file` into a `JsonNode`. + ## If `file` contains extra data, it will raise `JsonParsingError`. + var stream = newFileStream(filename, fmRead) + if stream == nil: + raise newException(IOError, "cannot read from file: " & filename) + result = parseJson(stream, filename) + +# -- Json deserialiser. -- template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind], ast: string) = - if node.kind notin kinds: + if node == nil: + raise newException(KeyError, "key not found: " & ast) + elif node.kind notin kinds: let msg = "Incorrect JSON kind. Wanted '$1' in '$2' but got '$3'." % [ $kinds, ast, @@ -985,588 +966,232 @@ template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind], ] raise newException(JsonKindError, msg) -proc getEnum(node: JsonNode, ast: string, T: typedesc): T = - when T is SomeInteger: - # TODO: I shouldn't need this proc. - proc convert[T](x: BiggestInt): T = T(x) - verifyJsonKind(node, {JInt}, ast) - return convert[T](node.getBiggestInt()) - else: - verifyJsonKind(node, {JString}, ast) - return parseEnum[T](node.getStr()) - -proc toIdentNode(typeNode: NimNode): NimNode = - ## Converts a Sym type node (returned by getType et al.) into an - ## Ident node. Placing Sym type nodes inside the resulting code AST is - ## unsound (according to @Araq) so this is necessary. - case typeNode.kind - of nnkSym: - return newIdentNode($typeNode) - of nnkBracketExpr: - result = typeNode - for i in 0.. getEnum(`jsonNode`, `kindType`) - result = newCall(bindSym("getEnum"), jsonNode, toStrLit(jsonNode), kindType) - -proc createOfBranchCond(ofBranch, getEnumCall: NimNode): NimNode = - ## Creates an expression that acts as the condition for an ``of`` branch. - var cond = newIdentNode("false") - for ofCond in ofBranch: - if ofCond.kind == nnkRecList: - break - - let comparison = infix(getEnumCall, "==", ofCond) - cond = infix(cond, "or", comparison) - - return cond - -proc processObjField(field, jsonNode: NimNode): seq[NimNode] {.compileTime.} -proc processOfBranch(ofBranch, jsonNode, kindType, - kindJsonNode: NimNode): seq[NimNode] {.compileTime.} = - ## Processes each field inside of an object's ``of`` branch. - ## For each field a new ExprColonExpr node is created and put in the - ## resulting list. - ## - ## Sample ``ofBranch`` AST: - ## - ## .. code-block::plain - ## OfBranch of 0, 1: - ## IntLit 0 foodPos: float - ## IntLit 1 enemyPos: float - ## RecList - ## Sym "foodPos" - ## Sym "enemyPos" - result = @[] - let getEnumCall = createGetEnumCall(kindJsonNode, kindType) - - for branchField in ofBranch[^1]: - let objFields = processObjField(branchField, jsonNode) - - for objField in objFields: - let exprColonExpr = newNimNode(nnkExprColonExpr) - result.add(exprColonExpr) - # Add the name of the field. - exprColonExpr.add(toIdentNode(objField[0])) - - # Add the value of the field. - let cond = createOfBranchCond(ofBranch, getEnumCall) - exprColonExpr.add(newIfStmt( - (cond, objField[1]) - )) - -proc processElseBranch(recCaseNode, elseBranch, jsonNode, kindType, - kindJsonNode: NimNode): seq[NimNode] {.compileTime.} = - ## Processes each field inside of a variant object's ``else`` branch. - ## - ## ..code-block::plain - ## Else - ## RecList - ## Sym "other" - result = @[] - let getEnumCall = createGetEnumCall(kindJsonNode, kindType) - - # We need to build up a list of conditions from each ``of`` branch so that - # we can then negate it to get ``else``. - var cond = newIdentNode("false") - for i in 1 ..< len(recCaseNode): - if recCaseNode[i].kind == nnkElse: - break - - cond = infix(cond, "or", createOfBranchCond(recCaseNode[i], getEnumCall)) - - # Negate the condition. - cond = prefix(cond, "not") - - for branchField in elseBranch[^1]: - let objFields = processObjField(branchField, jsonNode) - - for objField in objFields: - let exprColonExpr = newNimNode(nnkExprColonExpr) - result.add(exprColonExpr) - # Add the name of the field. - exprColonExpr.add(toIdentNode(objField[0])) - - # Add the value of the field. - let ifStmt = newIfStmt((cond, objField[1])) - exprColonExpr.add(ifStmt) - -proc createConstructor(typeSym, jsonNode: NimNode): NimNode {.compileTime.} - -proc detectDistinctType(typeSym: NimNode): NimNode = - let - typeImpl = getTypeImpl(typeSym) - typeInst = getTypeInst(typeSym) - result = if typeImpl.typeKind == ntyDistinct: typeImpl else: typeInst - -proc processObjField(field, jsonNode: NimNode): seq[NimNode] = - ## Process a field from a ``RecList``. - ## - ## The field will typically be a simple ``Sym`` node, but for object variants - ## it may also be a ``RecCase`` in which case things become complicated. - result = @[] - case field.kind - of nnkSym: - # Ordinary field. For example, `name: string`. - let exprColonExpr = newNimNode(nnkExprColonExpr) - result.add(exprColonExpr) - - # Add the field name. - exprColonExpr.add(toIdentNode(field)) - - # Add the field value. - # -> jsonNode["`field`"] - let indexedJsonNode = createJsonIndexer(jsonNode, $field) - let typeNode = detectDistinctType(field) - exprColonExpr.add(createConstructor(typeNode, indexedJsonNode)) - of nnkRecCase: - # A "case" field that introduces a variant. - let exprEqExpr = newNimNode(nnkExprEqExpr) - result.add(exprEqExpr) - - # Add the "case" field name (usually "kind"). - exprEqExpr.add(toIdentNode(field[0])) - - # -> jsonNode["`field[0]`"] - let kindJsonNode = createJsonIndexer(jsonNode, $field[0]) - - # Add the "case" field's value. - let kindType = toIdentNode(getTypeInst(field[0])) - let getEnumSym = bindSym("getEnum") - let astStrLit = toStrLit(kindJsonNode) - let getEnumCall = newCall(getEnumSym, kindJsonNode, astStrLit, kindType) - exprEqExpr.add(getEnumCall) - - # Iterate through each `of` branch. - for i in 1 ..< field.len: - case field[i].kind - of nnkOfBranch: - result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode) - of nnkElse: - result.add processElseBranch(field, field[i], jsonNode, kindType, kindJsonNode) - else: - doAssert false, "Expected OfBranch or Else node kinds, got: " & $field[i].kind - else: - doAssert false, "Unable to process object field: " & $field.kind - - 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, - jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} = - ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``. - ## - ## Sample ``ObjectTy``: - ## - ## .. code-block::plain - ## ObjectTy - ## Empty - ## InheritanceInformation - ## RecList - ## Sym "events" - case obj.kind - of nnkObjectTy, nnkTupleTy: - # Create object constructor. - result = - if obj.kind == nnkObjectTy: newNimNode(nnkObjConstr) - else: newNimNode(nnkPar) - - if obj.kind == nnkObjectTy: - result.add(typeName) # Name of the type to construct. - - # Process each object/tuple field and add it as an exprColonExpr - result.add(processFields(obj, jsonNode)) - - # Object might be null. So we need to check for that. - if isRef: - result = quote do: - verifyJsonKind(`jsonNode`, {JObject, JNull}, astToStr(`jsonNode`)) - if `jsonNode`.kind == JNull: - nil - else: - `result` - else: - result = quote do: - verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`)); - `result` - of nnkEnumTy: - let instType = toIdentNode(getTypeInst(typeName)) - let getEnumCall = createGetEnumCall(jsonNode, instType) - result = quote do: - ( - `getEnumCall` - ) - of nnkSym: - let name = normalize($typeName.getTypeImpl()) - case name - of "string": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`)); - if `jsonNode`.kind == JNull: "" else: `jsonNode`.str - ) - of "biggestint": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`)); - `jsonNode`.num - ) - of "bool": - result = quote do: - ( - verifyJsonKind(`jsonNode`, {JBool}, astToStr(`jsonNode`)); - `jsonNode`.bval - ) +when defined(nimFixedForwardGeneric): + # The following forward declarations don't work in older versions of Nim + + # forward declare all initFromJson + + proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: string) + proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: string) + proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: string) + proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T: distinct](dst: var T;jsonNode: JsonNode; jsonPath: string) + proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: string) + + # initFromJson definitions + + proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JString, JNull}, jsonPath) + # since strings don't have a nil state anymore, this mapping of + # JNull to the default string is questionable. `none(string)` and + # `some("")` have the same potentional json value `JNull`. + if jsonNode.kind == JNull: + dst = "" else: - 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: - doAssert false, "Unable to process type: " & $obj.kind + dst = jsonNode.str - doAssert(not result.isNil(), "processType not initialised.") + proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JBool}, jsonPath) + dst = jsonNode.bval -import options -proc workaroundMacroNone[T](): Option[T] = - none(T) + proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: string) = + dst = jsonNode.copy -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 initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: string) = + verifyJsonKind(jsonNode, {JInt}, jsonPath) + dst = T(jsonNode.num) -proc createConstructor(typeSym, jsonNode: NimNode): NimNode = - ## Accepts a type description, i.e. "ref Type", "seq[Type]", "Type" etc. - ## - ## The ``jsonNode`` refers to the node variable that we are deserialising. - ## - ## Returns an object constructor node. - # echo("--createConsuctor-- \n", treeRepr(typeSym)) - # echo() - - if depth(jsonNode) > 150: - error("The `to` macro does not support ref objects with cycles.", jsonNode) - - case typeSym.kind - of nnkBracketExpr: - var bracketName = ($typeSym[0]).normalize - 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 or `jsonNode`.kind == JNull: `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": - # Ref type. - var typeName = $typeSym[1] - # Remove the `:ObjectType` suffix. - if typeName.endsWith(":ObjectType"): - typeName = typeName[0 .. ^12] - - let obj = getType(typeSym[1]) - result = processType(newIdentNode(typeName), obj, jsonNode, true) - of "range": - let typeNode = typeSym - # Deduce the base type from one of the endpoints - let baseType = getType(typeNode[1]) - - result = createConstructor(baseType, jsonNode) - of "seq": - let seqT = typeSym[1] - let forLoopI = genSym(nskForVar, "i") - let indexerNode = createJsonIndexer(jsonNode, forLoopI) - let constructorNode = createConstructor(detectDistinctType(seqT), indexerNode) - - # Create a statement expression containing a for loop. - result = quote do: - ( - var list: `typeSym` = @[]; - verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 ..< `jsonNode`.len: list.add(`constructorNode`); - list - ) - of "array": - let arrayT = typeSym[2] - let forLoopI = genSym(nskForVar, "i") - let indexerNode = createJsonIndexer(jsonNode, forLoopI) - let constructorNode = createConstructor(arrayT, indexerNode) - - # Create a statement expression containing a for loop. - result = quote do: - ( - var list: `typeSym`; - verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 ..< `jsonNode`.len: list[ - `forLoopI`] = `constructorNode`; - list - ) - of "tuple": - let typeNode = getTypeImpl(typeSym) - result = createConstructor(typeNode, jsonNode) + proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JInt, JFloat}, jsonPath) + if jsonNode.kind == JFloat: + dst = T(jsonNode.fnum) else: - # Generic type or some `seq[T]` alias - let obj = getType(typeSym) - case obj.kind - of nnkBracketExpr: - # probably a `seq[T]` alias - let typeNode = getTypeImpl(typeSym) - result = createConstructor(typeNode, jsonNode) - else: - # generic type - result = processType(typeSym, obj, jsonNode, false) - of nnkSym: - # Handle JsonNode. - if ($typeSym).cmpIgnoreStyle("jsonnode") == 0: - return jsonNode - - # Handle all other types. - let obj = getType(typeSym) - let typeNode = getTypeImpl(typeSym) - if typeNode.typeKind == ntyDistinct: - result = createConstructor(typeNode, jsonNode) - elif obj.kind == nnkBracketExpr: - # When `Sym "Foo"` turns out to be a `ref object` or `tuple` - result = createConstructor(obj, jsonNode) + dst = T(jsonNode.num) + + proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JString}, jsonPath) + dst = parseEnum[T](jsonNode.getStr) + + proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JArray}, jsonPath) + dst.setLen jsonNode.len + for i in 0 ..< jsonNode.len: + initFromJson(dst[i], jsonNode[i], jsonPath & "[" & $i & "]") + + proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: string) = + verifyJsonKind(jsonNode, {JArray}, jsonPath) + for i in 0 ..< jsonNode.len: + initFromJson(dst[i], jsonNode[i], jsonPath & "[" & $i & "]") + + proc initFromJson[T](dst: var Table[string,T];jsonNode: JsonNode; jsonPath: string) = + dst = initTable[string, T]() + verifyJsonKind(jsonNode, {JObject}, jsonPath) + for key in keys(jsonNode.fields): + initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath & "." & key) + + proc initFromJson[T](dst: var OrderedTable[string,T];jsonNode: JsonNode; jsonPath: string) = + dst = initOrderedTable[string,T]() + verifyJsonKind(jsonNode, {JObject}, jsonPath) + for key in keys(jsonNode.fields): + initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath & "." & key) + + proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: string) = + if jsonNode.kind == JNull: + dst = nil else: - result = processType(typeSym, obj, jsonNode, false) - of nnkTupleTy: - result = processType(typeSym, typeSym, jsonNode, false) - of nnkPar, nnkTupleConstr: - # 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) - of nnkDistinctTy: - var baseType = typeSym - # solve nested distinct types - while baseType.typeKind == ntyDistinct: - let impl = getTypeImpl(baseType[0]) - if impl.typeKind != ntyDistinct: - baseType = baseType[0] - break - baseType = impl - let ret = createConstructor(baseType, jsonNode) - let typeInst = getTypeInst(typeSym) + dst = new(ref T) + initFromJson(dst[], jsonNode, jsonPath) + + proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: string) = + if jsonNode != nil and jsonNode.kind != JNull: + dst = some(default(T)) + initFromJson(dst.get, jsonNode, jsonPath) + + macro assignDistinctImpl[T : distinct](dst: var T;jsonNode: JsonNode; jsonPath: string) = + let typInst = getTypeInst(dst) + let typImpl = getTypeImpl(dst) + let baseTyp = typImpl[0] result = quote do: - ( - `typeInst`(`ret`) - ) - else: - doAssert false, "Unable to create constructor for: " & $typeSym.kind + initFromJson( `baseTyp`(`dst`), `jsonNode`, `jsonPath`) - doAssert(not result.isNil(), "Constructor not initialised.") + proc initFromJson[T : distinct](dst: var T; jsonNode: JsonNode; jsonPath: string) = + assignDistinctImpl(dst, jsonNode, jsonPath) -proc postProcess(node: NimNode): NimNode -proc postProcessValue(value: NimNode): NimNode = - ## Looks for object constructors and calls the ``postProcess`` procedure - ## on them. Otherwise it just returns the node as-is. - case value.kind - of nnkObjConstr: - result = postProcess(value) - else: - result = value - for i in 0 ..< len(result): - result[i] = postProcessValue(result[i]) - -proc postProcessExprColonExpr(exprColonExpr, resIdent: NimNode): NimNode = - ## Transform each field mapping in the ExprColonExpr into a simple - ## field assignment. Special processing is performed if the field mapping - ## has an if statement. - ## - ## ..code-block::plain - ## field: (if true: 12) -> if true: `resIdent`.field = 12 - expectKind(exprColonExpr, nnkExprColonExpr) - let fieldName = exprColonExpr[0] - let fieldValue = exprColonExpr[1] - case fieldValue.kind - of nnkIfStmt: - doAssert fieldValue.len == 1, "Cannot postProcess two ElifBranches." - expectKind(fieldValue[0], nnkElifBranch) - - let cond = fieldValue[0][0] - let bodyValue = postProcessValue(fieldValue[0][1]) - doAssert(bodyValue.kind != nnkNilLit) - result = - quote do: - if `cond`: - `resIdent`.`fieldName` = `bodyValue` - else: - let fieldValue = postProcessValue(fieldValue) - doAssert(fieldValue.kind != nnkNilLit) - result = - quote do: - `resIdent`.`fieldName` = `fieldValue` + proc detectIncompatibleType(typeExpr, lineinfoNode: NimNode): void = + if typeExpr.kind == nnkTupleConstr: + error("Use a named tuple instead of: " & typeExpr.repr, lineinfoNode) + proc foldObjectBody(dst, typeNode, tmpSym, jsonNode, jsonPath: NimNode, depth: int): void {.compileTime.} = + if depth > 150: + error("recursion limit reached", typeNode) + case typeNode.kind + of nnkEmpty: + discard + of nnkRecList, nnkTupleTy: + for it in typeNode: + foldObjectBody(dst, it, tmpSym, jsonNode, jsonPath, depth + 1) + + of nnkIdentDefs: + typeNode.expectLen 3 + let fieldSym = typeNode[0] + let fieldNameLit = newLit(fieldSym.strVal) + let fieldType = typeNode[1] + + # Detecting incompatiple tuple types in `assignObjectImpl` only + # would be much cleaner, but the ast for tuple types does not + # contain usable type information. + detectIncompatibleType(fieldType, fieldSym) + + dst.add quote do: + initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath` & "." & `fieldNameLit`) + + of nnkRecCase: + let kindSym = typeNode[0][0] + let kindNameLit = newLit(kindSym.strVal) + let kindType = typeNode[0][1] + let kindOffsetLit = newLit(uint(getOffset(kindSym))) + dst.add quote do: + var kindTmp: `kindType` + initFromJson(kindTmp, `jsonNode`[`kindNameLit`], `jsonPath` & "." & `kindNameLit`) + when defined js: + `tmpSym`.`kindSym` = kindTmp + else: + when nimVm: + `tmpSym`.`kindSym` = kindTmp + else: + # fuck it, assign kind field anyway + ((cast[ptr `kindType`](cast[uint](`tmpSym`.addr) + `kindOffsetLit`))[]) = kindTmp + dst.add nnkCaseStmt.newTree(nnkDotExpr.newTree(tmpSym, kindSym)) + for i in 1 ..< typeNode.len: + foldObjectBody(dst, typeNode[i], tmpSym, jsonNode, jsonPath, depth + 1) + + of nnkOfBranch, nnkElse: + let ofBranch = newNimNode(typeNode.kind) + for i in 0 ..< typeNode.len-1: + ofBranch.add copyNimTree(typeNode[i]) + let dstInner = newNimNode(nnkStmtListExpr) + foldObjectBody(dstInner, typeNode[^1], tmpSym, jsonNode, jsonPath, depth + 1) + # resOuter now contains the inner stmtList + ofBranch.add dstInner + dst[^1].expectKind nnkCaseStmt + dst[^1].add ofBranch + + of nnkObjectTy: + typeNode[0].expectKind nnkEmpty + typeNode[1].expectKind {nnkEmpty, nnkOfInherit} + if typeNode[1].kind == nnkOfInherit: + let base = typeNode[1][0] + var impl = getTypeImpl(base) + while impl.kind in {nnkRefTy, nnkPtrTy}: + impl = getTypeImpl(impl[0]) + foldObjectBody(dst, impl, tmpSym, jsonNode, jsonPath, depth + 1) + let body = typeNode[2] + foldObjectBody(dst, body, tmpSym, jsonNode, jsonPath, depth + 1) -proc postProcess(node: NimNode): NimNode = - ## The ``createConstructor`` proc creates a ObjConstr node which contains - ## if statements for fields that may not be assignable (due to an object - ## variant). Nim doesn't handle this, but may do in the future. - ## - ## For simplicity, we post process the object constructor into multiple - ## assignments. - ## - ## For example: - ## - ## ..code-block::plain - ## Object( (var res = Object(); - ## field: if true: 12 -> if true: res.field = 12; - ## ) res) - result = newNimNode(nnkStmtListExpr) - - expectKind(node, nnkObjConstr) - - # Create the type. - # -> var res = Object() - var resIdent = genSym(nskVar, "res") - var resType = node[0] - - var objConstr = newTree(nnkObjConstr, resType) - result.add newVarStmt(resIdent, objConstr) - - # Process each ExprColonExpr. - for i in 1..