# json-serialization # Copyright (c) 2019-2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) # at your option. # This file may not be copied, modified, or distributed except according to # those terms. {.experimental: "notnil".} import ./reader_desc, ./lexer from json import JsonNode, JsonNodeKind, escapeJson export reader_desc {.push gcsafe, raises: [].} type NumberPart* = enum SignPart IntegerPart FractionPart ExpSignPart ExponentPart CustomNumberHandler* = ##\ ## Custom number parser, result values need to be captured proc(part: NumberPart; dgt: int) {.gcsafe, raises: [].} CustomIntHandler* = ##\ ## Custom integer parser, result values need to be captured proc(dgt: int) {.gcsafe, raises: [].} CustomStringHandler* = ##\ ## Custom text or binary parser, result values need to be captured. proc(b: char) {.gcsafe, raises: [].} # ------------------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------------------ template raiseParserError(r: var JsonReader) = r.raiseUnexpectedValue($r.lex.err) template raiseParserError(r: var JsonReader, err: JsonErrorKind) = r.raiseUnexpectedValue($err) # ------------------------------------------------------------------------------ # Public helpers # ------------------------------------------------------------------------------ template checkError*(r: var JsonReader) = if r.lex.isErr: r.raiseParserError() proc tokKind*(r: var JsonReader): JsonValueKind {.gcsafe, raises: [IOError, JsonReaderError].} = result = r.lex.tokKind r.checkError # ------------------------------------------------------------------------------ # Custom iterators # ------------------------------------------------------------------------------ proc customIntHandler*(r: var JsonReader; handler: CustomIntHandler) {.gcsafe, raises: [IOError, JsonReaderError].} = ## Apply the `handler` argument function for parsing only integer part ## of JsonNumber # TODO: remove temporary token if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) var val: JsonNumber[string] r.lex.scanNumber(val) r.checkError if val.isFloat: r.raiseParserError(errCustomIntExpected) for c in val.integer: handler(ord(c) - ord('0')) proc customNumberHandler*(r: var JsonReader; handler: CustomNumberHandler) {.gcsafe, raises: [IOError, JsonReaderError].} = ## Apply the `handler` argument function for parsing complete JsonNumber # TODO: remove temporary token if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) var val: JsonNumber[string] r.lex.scanNumber(val) r.checkError handler(SignPart, val.sign.toInt) for c in val.integer: handler(IntegerPart, ord(c) - ord('0')) for c in val.fraction: handler(FractionPart, ord(c) - ord('0')) handler(ExpSignPart, val.expSign.toInt) for c in val.exponent: handler(ExponentPart, ord(c) - ord('0')) proc customStringHandler*(r: var JsonReader; limit: int; handler: CustomStringHandler) {.gcsafe, raises: [IOError, JsonReaderError].} = ## Apply the `handler` argument function for parsing a String type ## value. # TODO: remove temporary token if r.tokKind != JsonValueKind.String: r.raiseParserError(errStringExpected) var val: string r.lex.scanString(val, limit) r.checkError for c in val: handler(c) template customIntValueIt*(r: var JsonReader; body: untyped) = ## Convenience wrapper around `customIntHandler()` for parsing integers. ## ## The `body` argument represents a virtual function body. So the current ## digit processing can be exited with `return`. var handler: CustomIntHandler = proc(digit: int) = let it {.inject.} = digit body r.customIntHandler(handler) template customNumberValueIt*(r: var JsonReader; body: untyped) = ## Convenience wrapper around `customIntHandler()` for parsing numbers. ## ## The `body` argument represents a virtual function body. So the current ## digit processing can be exited with `return`. let handler: CustomNumberHandler = proc(part: NumberPart, digit: int) = let it {.inject.} = digit let part {.inject.} = part body r.customNumberHandler(handler) # !!!: don't change limit from untyped to int, it will trigger Nim bug # the second overloaded customStringValueIt will fail to compile template customStringValueIt*(r: var JsonReader; limit: untyped; body: untyped) = ## Convenience wrapper around `customStringHandler()` for parsing a text ## terminating with a double quote character '"'. ## ## The `body` argument represents a virtual function body. So the current ## character processing can be exited with `return`. let handler: CustomStringHandler = proc(c: char) = let it {.inject.} = c body r.customStringHandler(limit, handler) template customStringValueIt*(r: var JsonReader; body: untyped) = ## Convenience wrapper around `customStringHandler()` for parsing a text ## terminating with a double quote character '"'. ## ## The `body` argument represents a virtual function body. So the current ## character processing can be exited with `return`. let handler: CustomStringHandler = proc(c: char) = let it {.inject.} = c body r.customStringHandler(r.lex.conf.stringLengthLimit, handler) # ------------------------------------------------------------------------------ # Public parsers # ------------------------------------------------------------------------------ proc parseString*(r: var JsonReader, limit: int): string {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.String: r.raiseParserError(errStringExpected) r.lex.scanString(result, limit) r.checkError proc parseString*(r: var JsonReader): string {.gcsafe, raises: [IOError, JsonReaderError].} = r.parseString(r.lex.conf.stringLengthLimit) proc parseBool*(r: var JsonReader): bool {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Bool: r.raiseParserError(errBoolExpected) result = r.lex.scanBool() r.checkError proc parseNull*(r: var JsonReader) {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Null: r.raiseParserError(errNullExpected) r.lex.scanNull() r.checkError proc parseNumberImpl[F,T](r: var JsonReader[F]): JsonNumber[T] {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) r.lex.scanNumber(result) r.checkError template parseNumber*(r: var JsonReader, T: type): auto = ## workaround Nim inablity to instantiate result type ## when one the argument is generic type and the other ## is a typedesc type F = typeof(r) parseNumberImpl[F.Flavor, T](r) proc parseNumber*(r: var JsonReader, val: var JsonNumber) {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) r.lex.scanNumber(val) r.checkError proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeSignedInt, portable: bool): T {.gcsafe, raises: [JsonReaderError].}= if val.sign == JsonSign.Neg: if val.integer.uint64 > T.high.uint64 + 1: raiseIntOverflow(r, val.integer, true) elif val.integer == T.high.uint64 + 1: result = T.low else: result = -T(val.integer) else: if val.integer > T.high.uint64: raiseIntOverflow(r, val.integer, false) result = T(val.integer) if portable and result.int64 > maxPortableInt.int64: raiseIntOverflow(r, result.BiggestUInt, false) if portable and result.int64 < minPortableInt.int64: raiseIntOverflow(r, result.BiggestUInt, true) proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeUnsignedInt, portable: bool): T {.gcsafe, raises: [IOError, JsonReaderError].}= if val.sign == JsonSign.Neg: raiseUnexpectedToken(r, etInt) if val.integer > T.high.uint64: raiseIntOverflow(r, val.integer, false) if portable and val.integer > maxPortableInt.uint64: raiseIntOverflow(r, val.integer.BiggestUInt, false) T(val.integer) proc parseInt*(r: var JsonReader, T: type SomeInteger, portable: bool = false): T {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) var val: JsonNumber[uint64] r.lex.scanNumber(val) r.checkError if val.isFloat: r.raiseParserError(errInvalidInt) r.toInt(val, T, portable) proc toFloat*(r: var JsonReader, val: JsonNumber, T: type SomeFloat): T {.gcsafe, raises: [JsonReaderError].}= const powersOfTen = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22] # TODO: this table should be much larger # The largest JSON number value is 1E308 result = T(val.integer) var fraction = T(0.1) for c in val.fraction: result += fraction * T(ord(c) - ord('0')) fraction *= T(0.1) if val.sign == JsonSign.Neg: result = result * T(-1.0) if val.exponent >= uint64(len(powersOfTen)): r.raiseParserError(errExponentTooLarge) if val.expSign == JsonSign.Neg: result = result / powersOfTen[val.exponent] else: result = result * powersOfTen[val.exponent] proc parseFloat*(r: var JsonReader, T: type SomeFloat): T {.gcsafe, raises: [IOError, JsonReaderError].} = if r.tokKind != JsonValueKind.Number: r.raiseParserError(errNumberExpected) var val: JsonNumber[uint64] r.lex.scanNumber(val) r.checkError r.toFloat(val, T) proc parseAsString*(r: var JsonReader, val: var string) {.gcsafe, raises: [IOError, JsonReaderError].} = case r.tokKind of JsonValueKind.String: escapeJson(r.parseString(), val) of JsonValueKind.Number: r.lex.scanNumber(val) r.checkError of JsonValueKind.Object: parseObjectImpl(r.lex): # initial action val.add '{' do: # closing action val.add '}' do: # comma action val.add ',' do: # key action escapeJson(r.parseString(), val) do: # value action val.add ':' r.parseAsString(val) do: # error action r.raiseParserError() of JsonValueKind.Array: parseArrayImpl(r.lex, idx): # initial action val.add '[' do: # closing action val.add ']' do: # comma action val.add ',' do: # value action r.parseAsString(val) do: # error action r.raiseParserError() of JsonValueKind.Bool: if r.parseBool(): val.add "true" else: val.add "false" of JsonValueKind.Null: r.parseNull() val.add "null" proc parseAsString*(r: var JsonReader): JsonString {.gcsafe, raises: [IOError, JsonReaderError].} = var val: string r.parseAsString(val) val.JsonString proc parseValueImpl[F,T](r: var JsonReader[F]): JsonValueRef[T] {.gcsafe, raises: [IOError, JsonReaderError].} = r.lex.scanValue(result) r.checkError template parseValue*(r: var JsonReader, T: type): auto = ## workaround Nim inablity to instantiate result type ## when one the argument is generic type and the other ## is a typedesc type F = typeof(r) parseValueImpl[F.Flavor, T](r) proc parseValue*(r: var JsonReader, val: var JsonValueRef) {.gcsafe, raises: [IOError, JsonReaderError].} = r.lex.scanValue(val) r.checkError template parseArray*(r: var JsonReader; body: untyped) = if r.tokKind != JsonValueKind.Array: r.raiseParserError(errBracketLeExpected) parseArrayImpl(r.lex, idx): discard # initial action do: discard # closing action do: discard # comma action do: body # value action do: r.raiseParserError() # error action template parseArray*(r: var JsonReader; idx: untyped; body: untyped) = if r.tokKind != JsonValueKind.Array: r.raiseParserError(errBracketLeExpected) parseArrayImpl(r.lex, idx): discard # initial action do: discard # closing action do: discard # comma action do: body # value action do: r.raiseParserError() # error action template parseObject*(r: var JsonReader, key: untyped, body: untyped) = if r.tokKind != JsonValueKind.Object: r.raiseParserError(errCurlyLeExpected) parseObjectImpl(r.lex): discard # initial action do: discard # closing action do: discard # comma action do: # key action let key {.inject.} = r.parseString() do: # value action body do: # error action r.raiseParserError() template parseObjectCustomKey*(r: var JsonReader, keyAction: untyped, body: untyped) = if r.tokKind != JsonValueKind.Object: r.raiseParserError(errCurlyLeExpected) parseObjectImpl(r.lex): discard # initial action do: discard # closing action do: discard # comma action do: # key action keyAction do: # value action body do: # error action r.raiseParserError() # ------------------------------------------------------------------------------ # Parse to stdlib's JsonNode # ------------------------------------------------------------------------------ proc parseJsonNode*(r: var JsonReader): JsonNode {.gcsafe, raises: [IOError, JsonReaderError].} proc readJsonNodeField(r: var JsonReader, field: var JsonNode) {.gcsafe, raises: [IOError, JsonReaderError].} = if field.isNil.not: r.raiseUnexpectedValue("Unexpected duplicated field name") field = r.parseJsonNode() proc parseJsonNode(r: var JsonReader): JsonNode = case r.tokKind of JsonValueKind.String: result = JsonNode(kind: JString, str: r.parseString()) of JsonValueKind.Number: var val: JsonNumber[uint64] r.lex.scanNumber(val) r.checkError if val.isFloat: result = JsonNode(kind: JFloat, fnum: r.toFloat(val, typeof(result.fnum))) else: result = JsonNode(kind: JInt, num: r.toInt(val, typeof(result.num), JsonReaderFlag.portableInt in r.lex.flags)) of JsonValueKind.Object: result = JsonNode(kind: JObject) parseObjectImpl(r.lex): discard # initial action do: discard # closing action do: discard # comma action do: # key action let key = r.parseString() do: # value action try: r.readJsonNodeField(result.fields.mgetOrPut(key, nil)) except KeyError: raiseAssert "mgetOrPut should never raise a KeyError" do: # error action r.raiseParserError() of JsonValueKind.Array: result = JsonNode(kind: JArray) parseArrayImpl(r.lex, idx): discard # initial action do: discard # closing action do: discard # comma action do: # value action result.elems.add r.parseJsonNode() do: # error action r.raiseParserError() of JsonValueKind.Bool: result = JsonNode(kind: JBool, bval: r.parseBool()) of JsonValueKind.Null: r.parseNull() result = JsonNode(kind: JNull) # ------------------------------------------------------------------------------ # Misc helpers # ------------------------------------------------------------------------------ proc skipSingleJsValue*(lex: var JsonLexer) {.raises: [IOError, JsonReaderError].} = var val: JsonVoid lex.scanValue(val) if lex.isErr: lex.raiseUnexpectedValue($lex.err) template skipSingleJsValue*(r: var JsonReader) = skipSingleJsValue(r.lex) {.pop.}