* fixes #15413

* better hide it properly

* see if this makes our list of important packages happy
This commit is contained in:
Andreas Rumpf 2020-10-29 17:32:56 +01:00 • committed by GitHub
commit 87a60c1b28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 21 deletions

View file

@ -7,6 +7,13 @@
- `prelude` now works with the JavaScript target. - `prelude` now works with the JavaScript target.
- Added `ioutils` module containing `duplicate` and `duplicateTo` to duplicate `FileHandle` using C function `dup` and `dup2`. - Added `ioutils` module containing `duplicate` and `duplicateTo` to duplicate `FileHandle` using C function `dup` and `dup2`.
- The JSON module can now handle integer literals and floating point literals of arbitrary length and precision.
Numbers that do not fit the underlying `BiggestInt` or `BiggestFloat` fields are kept as string literals and
one can use external BigNum libraries to handle these. The `parseFloat` family of functions also has now optional
`rawIntegers` and `rawFloats` parameters that can be used to enforce that all integer or float literals remain
in the "raw" string form so that client code can easily treat small and large numbers uniformly.
- Added `randState` template that exposes the default random number generator. Useful for library authors. - Added `randState` template that exposes the default random number generator. Useful for library authors.
## Language changes ## Language changes

View file

@ -177,6 +177,8 @@ type
JsonNode* = ref JsonNodeObj ## JSON node JsonNode* = ref JsonNodeObj ## JSON node
JsonNodeObj* {.acyclic.} = object JsonNodeObj* {.acyclic.} = object
isUnquoted: bool # the JString was a number-like token and
# so shouldn't be quoted
case kind*: JsonNodeKind case kind*: JsonNodeKind
of JString: of JString:
str*: string str*: string
@ -197,6 +199,13 @@ proc newJString*(s: string): JsonNode =
## Creates a new `JString JsonNode`. ## Creates a new `JString JsonNode`.
result = JsonNode(kind: JString, str: s) result = JsonNode(kind: JString, str: s)
proc newJRawNumber(s: string): JsonNode =
## Creates a "raw JS number", that is a number that does not
## fit into Nim's ``BiggestInt`` field. This is really a `JString`
## with the additional information that it should be converted back
## to the string representation without the quotes.
result = JsonNode(kind: JString, str: s, isUnquoted: true)
proc newJStringMove(s: string): JsonNode = proc newJStringMove(s: string): JsonNode =
result = JsonNode(kind: JString) result = JsonNode(kind: JString)
shallowCopy(result.str, s) shallowCopy(result.str, s)
@ -562,6 +571,7 @@ proc copy*(p: JsonNode): JsonNode =
case p.kind case p.kind
of JString: of JString:
result = newJString(p.str) result = newJString(p.str)
result.isUnquoted = p.isUnquoted
of JInt: of JInt:
result = newJInt(p.num) result = newJInt(p.num)
of JFloat: of JFloat:
@ -652,6 +662,9 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
result.add("{}") result.add("{}")
of JString: of JString:
if lstArr: result.indent(currIndent) if lstArr: result.indent(currIndent)
if node.isUnquoted:
result.add node.str
else:
escapeJson(node.str, result) escapeJson(node.str, result)
of JInt: of JInt:
if lstArr: result.indent(currIndent) if lstArr: result.indent(currIndent)
@ -734,6 +747,9 @@ proc toUgly*(result: var string, node: JsonNode) =
result.toUgly value result.toUgly value
result.add "}" result.add "}"
of JString: of JString:
if node.isUnquoted:
result.add node.str
else:
node.str.escapeJson(result) node.str.escapeJson(result)
of JInt: of JInt:
when defined(js): result.add($node.num) when defined(js): result.add($node.num)
@ -783,7 +799,7 @@ iterator mpairs*(node: var JsonNode): tuple[key: string, val: var JsonNode] =
for key, val in mpairs(node.fields): for key, val in mpairs(node.fields):
yield (key, val) yield (key, val)
proc parseJson(p: var JsonParser): JsonNode = proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool): JsonNode =
## Parses JSON from a JSON Parser `p`. ## Parses JSON from a JSON Parser `p`.
case p.tok case p.tok
of tkString: of tkString:
@ -792,10 +808,22 @@ proc parseJson(p: var JsonParser): JsonNode =
p.a = "" p.a = ""
discard getTok(p) discard getTok(p)
of tkInt: of tkInt:
if rawIntegers:
result = newJRawNumber(p.a)
else:
try:
result = newJInt(parseBiggestInt(p.a)) result = newJInt(parseBiggestInt(p.a))
except ValueError:
result = newJRawNumber(p.a)
discard getTok(p) discard getTok(p)
of tkFloat: of tkFloat:
if rawFloats:
result = newJRawNumber(p.a)
else:
try:
result = newJFloat(parseFloat(p.a)) result = newJFloat(parseFloat(p.a))
except ValueError:
result = newJRawNumber(p.a)
discard getTok(p) discard getTok(p)
of tkTrue: of tkTrue:
result = newJBool(true) result = newJBool(true)
@ -815,7 +843,7 @@ proc parseJson(p: var JsonParser): JsonNode =
var key = p.a var key = p.a
discard getTok(p) discard getTok(p)
eat(p, tkColon) eat(p, tkColon)
var val = parseJson(p) var val = parseJson(p, rawIntegers, rawFloats)
result[key] = val result[key] = val
if p.tok != tkComma: break if p.tok != tkComma: break
discard getTok(p) discard getTok(p)
@ -824,39 +852,47 @@ proc parseJson(p: var JsonParser): JsonNode =
result = newJArray() result = newJArray()
discard getTok(p) discard getTok(p)
while p.tok != tkBracketRi: while p.tok != tkBracketRi:
result.add(parseJson(p)) result.add(parseJson(p, rawIntegers, rawFloats))
if p.tok != tkComma: break if p.tok != tkComma: break
discard getTok(p) discard getTok(p)
eat(p, tkBracketRi) eat(p, tkBracketRi)
of tkError, tkCurlyRi, tkBracketRi, tkColon, tkComma, tkEof: of tkError, tkCurlyRi, tkBracketRi, tkColon, tkComma, tkEof:
raiseParseErr(p, "{") raiseParseErr(p, "{")
iterator parseJsonFragments*(s: Stream, filename: string = ""): JsonNode = iterator parseJsonFragments*(s: Stream, filename: string = ""; rawIntegers = false, rawFloats = false): JsonNode =
## Parses from a stream `s` into `JsonNodes`. `filename` is only needed ## Parses from a stream `s` into `JsonNodes`. `filename` is only needed
## for nice error messages. ## for nice error messages.
## The JSON fragments are separated by whitespace. This can be substantially ## The JSON fragments are separated by whitespace. This can be substantially
## faster than the comparable loop ## faster than the comparable loop
## ``for x in splitWhitespace(s): yield parseJson(x)``. ## ``for x in splitWhitespace(s): yield parseJson(x)``.
## This closes the stream `s` after it's done. ## This closes the stream `s` after it's done.
## If `rawIntegers` is true, integer literals will not be converted to a `JInt`
## field but kept as raw numbers via `JString`.
## If `rawFloats` is true, floating point literals will not be converted to a `JFloat`
## field but kept as raw numbers via `JString`.
var p: JsonParser var p: JsonParser
p.open(s, filename) p.open(s, filename)
try: try:
discard getTok(p) # read first token discard getTok(p) # read first token
while p.tok != tkEof: while p.tok != tkEof:
yield p.parseJson() yield p.parseJson(rawIntegers, rawFloats)
finally: finally:
p.close() p.close()
proc parseJson*(s: Stream, filename: string = ""): JsonNode = proc parseJson*(s: Stream, filename: string = ""; rawIntegers = false, rawFloats = false): JsonNode =
## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed
## for nice error messages. ## for nice error messages.
## If `s` contains extra data, it will raise `JsonParsingError`. ## If `s` contains extra data, it will raise `JsonParsingError`.
## This closes the stream `s` after it's done. ## This closes the stream `s` after it's done.
## If `rawIntegers` is true, integer literals will not be converted to a `JInt`
## field but kept as raw numbers via `JString`.
## If `rawFloats` is true, floating point literals will not be converted to a `JFloat`
## field but kept as raw numbers via `JString`.
var p: JsonParser var p: JsonParser
p.open(s, filename) p.open(s, filename)
try: try:
discard getTok(p) # read first token discard getTok(p) # read first token
result = p.parseJson() result = p.parseJson(rawIntegers, rawFloats)
eat(p, tkEof) # check if there is no extra data eat(p, tkEof) # check if there is no extra data
finally: finally:
p.close() p.close()
@ -924,6 +960,7 @@ when defined(js):
of JFloat: of JFloat:
result = newJFloat(cast[float](x)) result = newJFloat(cast[float](x))
of JString: of JString:
# Dunno what to do with isUnquoted here
result = newJString($cast[cstring](x)) result = newJString($cast[cstring](x))
of JBool: of JBool:
result = newJBool(cast[bool](x)) result = newJBool(cast[bool](x))
@ -937,10 +974,14 @@ when defined(js):
return parseNativeJson(buffer).convertObject() return parseNativeJson(buffer).convertObject()
else: else:
proc parseJson*(buffer: string): JsonNode = proc parseJson*(buffer: string; rawIntegers = false, rawFloats = false): JsonNode =
## Parses JSON from `buffer`. ## Parses JSON from `buffer`.
## If `buffer` contains extra data, it will raise `JsonParsingError`. ## If `buffer` contains extra data, it will raise `JsonParsingError`.
result = parseJson(newStringStream(buffer), "input") ## If `rawIntegers` is true, integer literals will not be converted to a `JInt`
## field but kept as raw numbers via `JString`.
## If `rawFloats` is true, floating point literals will not be converted to a `JFloat`
## field but kept as raw numbers via `JString`.
result = parseJson(newStringStream(buffer), "input", rawIntegers, rawFloats)
proc parseFile*(filename: string): JsonNode = proc parseFile*(filename: string): JsonNode =
## Parses `file` into a `JsonNode`. ## Parses `file` into a `JsonNode`.
@ -948,7 +989,7 @@ else:
var stream = newFileStream(filename, fmRead) var stream = newFileStream(filename, fmRead)
if stream == nil: if stream == nil:
raise newException(IOError, "cannot read from file: " & filename) raise newException(IOError, "cannot read from file: " & filename)
result = parseJson(stream, filename) result = parseJson(stream, filename, rawIntegers=false, rawFloats=false)
# -- Json deserialiser. -- # -- Json deserialiser. --
@ -1186,7 +1227,6 @@ when defined(nimFixedForwardGeneric):
else: else:
error("unhandled kind: " & $typeNode.kind, typeNode) error("unhandled kind: " & $typeNode.kind, typeNode)
macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: var string) = macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
let typeSym = getTypeInst(dst) let typeSym = getTypeInst(dst)
let originalJsonPathLen = genSym(nskLet, "originalJsonPathLen") let originalJsonPathLen = genSym(nskLet, "originalJsonPathLen")

View file

@ -232,3 +232,6 @@ doAssert isRefSkipDistinct(MyRef)
doAssert not isRefSkipDistinct(MyObject) doAssert not isRefSkipDistinct(MyObject)
doAssert isRefSkipDistinct(MyDistinct) doAssert isRefSkipDistinct(MyDistinct)
doAssert isRefSkipDistinct(MyOtherDistinct) doAssert isRefSkipDistinct(MyOtherDistinct)
let x = parseJson("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999")
doAssert x.kind == JString