Skip to content

Commit

Permalink
Basic support for Json flavours without default object serialization (#…
Browse files Browse the repository at this point in the history
…66)

Other changes:

* Migrate many procs accepting JsonReader to JsonLexer in order to
  reduce the number of generic instantiations and the resulting code
  bloat
  • Loading branch information
zah authored Dec 19, 2023
1 parent 230e226 commit f42567c
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 176 deletions.
1 change: 0 additions & 1 deletion json_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ import

export
serialization, format, reader, writer

29 changes: 29 additions & 0 deletions json_serialization/format.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,32 @@ template supports*(_: type Json, T: type): bool =
# The JSON format should support every type
true

template flavorUsesAutomaticObjectSerialization*(T: type DefaultFlavor): bool = true
template flavorOmitsOptionalFields*(T: type DefaultFlavor): bool = false
template flavorRequiresAllFields*(T: type DefaultFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false

# We create overloads of these traits to force the mixin treatment of the symbols
type DummyFlavor* = object
template flavorUsesAutomaticObjectSerialization*(T: type DummyFlavor): bool = true
template flavorOmitsOptionalFields*(T: type DummyFlavor): bool = false
template flavorRequiresAllFields*(T: type DummyFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DummyFlavor): bool = false

template createJsonFlavor*(FlavorName: untyped,
mimeTypeValue = "application/json",
automaticObjectSerialization = false,
requireAllFields = true,
omitOptionalFields = true,
allowUnknownFields = true) {.dirty.} =
type FlavorName* = object

template Reader*(T: type FlavorName): type = Reader(Json, FlavorName)
template Writer*(T: type FlavorName): type = Writer(Json, FlavorName)
template PreferredOutputType*(T: type FlavorName): type = string
template mimeType*(T: type FlavorName): string = mimeTypeValue

template flavorUsesAutomaticObjectSerialization*(T: type FlavorName): bool = automaticObjectSerialization
template flavorOmitsOptionalFields*(T: type FlavorName): bool = omitOptionalFields
template flavorRequiresAllFields*(T: type FlavorName): bool = requireAllFields
template flavorAllowsUnknownFields*(T: type FlavorName): bool = allowUnknownFields
35 changes: 17 additions & 18 deletions json_serialization/lexer.nim
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{.push raises: [].}

import
std/[unicode, json],
std/[json, unicode],
faststreams/inputs,
types

from std/strutils import isDigit

export
inputs, types

{.push raises: [].}

type
CustomIntHandler* = ##\
## Custom decimal integer parser, result values need to be captured
Expand Down Expand Up @@ -113,6 +115,7 @@ proc renderTok*(lexer: var JsonLexer, output: var string)
lexer.scanString
else:
discard

# The real stuff
case lexer.tokKind
of tkError, tkEof, tkNumeric, tkExInt, tkExNegInt, tkQuoted, tkExBlob:
Expand Down Expand Up @@ -153,23 +156,20 @@ template peek(s: InputStream): char =
template read(s: InputStream): char =
char inputs.read(s)

proc hexCharValue(c: char): int =
func hexCharValue(c: char): int =
case c
of '0'..'9': ord(c) - ord('0')
of 'a'..'f': ord(c) - ord('a') + 10
of 'A'..'F': ord(c) - ord('A') + 10
else: -1

proc isDigit(c: char): bool =
return (c >= '0' and c <= '9')

proc col*(lexer: JsonLexer): int =
func col*(lexer: JsonLexer): int =
lexer.stream.pos - lexer.lineStartPos

proc tokenStartCol*(lexer: JsonLexer): int =
func tokenStartCol*(lexer: JsonLexer): int =
1 + lexer.tokenStart - lexer.lineStartPos

proc init*(T: type JsonLexer, stream: InputStream, mode = defaultJsonMode): T =
func init*(T: type JsonLexer, stream: InputStream, mode = defaultJsonMode): T =
T(stream: stream,
mode: mode,
line: 1,
Expand Down Expand Up @@ -205,7 +205,7 @@ proc scanHexRune(lexer: var JsonLexer): int
if hexValue == -1: error errHexCharExpected
result = (result shl 4) or hexValue

proc scanString(lexer: var JsonLexer) =
proc scanString(lexer: var JsonLexer) {.raises: [IOError].} =
lexer.tokKind = tkString
lexer.strVal.setLen 0
lexer.tokenStart = lexer.stream.pos
Expand Down Expand Up @@ -256,7 +256,7 @@ proc scanString(lexer: var JsonLexer) =
else:
lexer.strVal.add c

proc handleLF(lexer: var JsonLexer) =
func handleLF(lexer: var JsonLexer) =
advance lexer.stream
lexer.line += 1
lexer.lineStartPos = lexer.stream.pos
Expand Down Expand Up @@ -343,7 +343,7 @@ proc scanSign(lexer: var JsonLexer): int
elif c == '+':
requireMoreNumberChars: result = 0
advance lexer.stream
return 1
1

proc scanInt(lexer: var JsonLexer): (uint64,bool)
{.gcsafe, raises: [IOError].} =
Expand Down Expand Up @@ -371,7 +371,6 @@ proc scanInt(lexer: var JsonLexer): (uint64,bool)
# Fetch next digit
c = eatDigitAndPeek() # implicit auto-return


proc scanNumber(lexer: var JsonLexer)
{.gcsafe, raises: [IOError].} =
var sign = lexer.scanSign()
Expand Down Expand Up @@ -422,9 +421,10 @@ proc scanNumber(lexer: var JsonLexer)
lexer.floatVal = lexer.floatVal / powersOfTen[exponent]

proc scanIdentifier(lexer: var JsonLexer,
expectedIdent: string, expectedTok: TokKind) =
expectedIdent: string, expectedTok: TokKind)
{.raises: [IOError].} =
for c in expectedIdent:
if c != lexer.stream.read():
if c != requireNextChar():
lexer.tokKind = tkError
return
lexer.tokKind = expectedTok
Expand Down Expand Up @@ -492,11 +492,10 @@ proc tok*(lexer: var JsonLexer): TokKind
lexer.accept
lexer.tokKind

proc lazyTok*(lexer: JsonLexer): TokKind =
func lazyTok*(lexer: JsonLexer): TokKind =
## Preliminary token state unless accepted, already
lexer.tokKind


proc customIntHandler*(lexer: var JsonLexer; handler: CustomIntHandler)
{.gcsafe, raises: [IOError].} =
## Apply the `handler` argument function for parsing a `tkNumeric` type
Expand Down
Loading

0 comments on commit f42567c

Please sign in to comment.