mirror of
https://github.com/status-im/NimYAML.git
synced 2025-01-12 12:34:23 +00:00
18753b1a4a
* Added YamlWarningCallback that may be used to capture parser warnings * Removed yamlWarning as event kind * Replaced checks for well-formedness in presenter with asserts * Added checks for JSON compatibility of YamlStream in presenter * Added proper type hints for special float values in serializer to be able to check for them in the presenter
471 lines
19 KiB
Nim
471 lines
19 KiB
Nim
import "../yaml"
|
|
import macros, strutils, streams, tables, json, hashes, re
|
|
export yaml, streams, tables, json
|
|
|
|
type
|
|
YamlTagStyle* = enum
|
|
ytsNone,
|
|
ytsRootOnly,
|
|
ytsAll
|
|
|
|
proc initSerializationTagLibrary(): YamlTagLibrary =
|
|
result = initTagLibrary()
|
|
result.tags["!"] = yTagExclamationMark
|
|
result.tags["?"] = yTagQuestionMark
|
|
result.tags["tag:yaml.org,2002:str"] = yTagString
|
|
result.tags["tag:yaml.org,2002:null"] = yTagNull
|
|
result.tags["tag:yaml.org,2002:bool"] = yTagBoolean
|
|
result.tags["tag:yaml.org,2002:int"] = yTagInteger
|
|
result.tags["tag:yaml.org,2002:float"] = yTagFloat
|
|
result.tags["tag:yaml.org,2002:timestamp"] = yTagTimestamp
|
|
result.tags["tag:yaml.org,2002:value"] = yTagValue
|
|
result.tags["tag:yaml.org,2002:binary"] = yTagBinary
|
|
|
|
var
|
|
serializationTagLibrary* = initSerializationTagLibrary() ## \
|
|
## contains all local tags that are used for type serialization. Does
|
|
## not contain any of the specific default tags for sequences or maps,
|
|
## as those are not suited for Nim's static type system.
|
|
##
|
|
## Should not be modified manually. Will be extended by
|
|
## `make_serializable <#make_serializable,stmt,stmt`_.
|
|
|
|
|
|
static:
|
|
iterator objectFields(n: NimNode): tuple[name: NimNode, t: NimNode] =
|
|
assert n.kind in [nnkRecList, nnkTupleTy]
|
|
for identDefs in n.children:
|
|
let numFields = identDefs.len - 2
|
|
for i in 0..numFields - 1:
|
|
yield (name: identDefs[i], t: identDefs[^2])
|
|
|
|
var existingTuples = newSeq[NimNode]()
|
|
|
|
template presentTag(t: typedesc, tagStyle: YamlTagStyle): TagId =
|
|
if tagStyle == ytsNone: yTagQuestionMark else: yamlTag(t)
|
|
|
|
proc lazyLoadTag*(uri: string): TagId {.inline.} =
|
|
try:
|
|
result = serializationTagLibrary.tags[uri]
|
|
except KeyError:
|
|
result = serializationTagLibrary.registerUri(uri)
|
|
|
|
macro make_serializable*(types: stmt): stmt =
|
|
assert types.kind == nnkTypeSection
|
|
result = newStmtList(types)
|
|
for typedef in types.children:
|
|
assert typedef.kind == nnkTypeDef
|
|
let
|
|
tName = $typedef[0].symbol
|
|
tIdent = newIdentNode(tName)
|
|
var
|
|
tUri: NimNode
|
|
recList: NimNode
|
|
assert typedef[1].kind == nnkEmpty
|
|
let objectTy = typedef[2]
|
|
case objectTy.kind
|
|
of nnkObjectTy:
|
|
assert objectTy[0].kind == nnkEmpty
|
|
assert objectTy[1].kind == nnkEmpty
|
|
tUri = newStrLitNode("!nim:" & tName)
|
|
recList = objectTy[2]
|
|
of nnkTupleTy:
|
|
if objectTy in existingTuples:
|
|
continue
|
|
existingTuples.add(objectTy)
|
|
|
|
recList = objectTy
|
|
tUri = newStmtList()
|
|
var
|
|
first = true
|
|
curStrLit = "!nim:tuple("
|
|
curInfix = tUri
|
|
for field in objectFields(recList):
|
|
if first:
|
|
first = false
|
|
else:
|
|
curStrLit &= ","
|
|
curStrLit &= $field.name & "="
|
|
var tmp = newNimNode(nnkInfix).add(newIdentNode("&"),
|
|
newStrLitNode(curStrLit))
|
|
curInfix.add(tmp)
|
|
curInfix = tmp
|
|
tmp = newNimNode(nnkInfix).add(newIdentNode("&"),
|
|
newCall("safeTagUri", newCall("yamlTag",
|
|
newCall("type", field.t))))
|
|
curInfix.add(tmp)
|
|
curInfix = tmp
|
|
curStrLit = ""
|
|
curInfix.add(newStrLitNode(curStrLit & ")"))
|
|
tUri = tUri[0]
|
|
else:
|
|
assert false
|
|
|
|
# yamlTag()
|
|
|
|
var yamlTagProc = newProc(newIdentNode("yamlTag"), [
|
|
newIdentNode("TagId"),
|
|
newIdentDefs(newIdentNode("T"), newNimNode(nnkBracketExpr).add(
|
|
newIdentNode("typedesc"), tIdent))])
|
|
var impl = newStmtList(newCall("lazyLoadTag", tUri))
|
|
yamlTagProc[6] = impl
|
|
result.add(yamlTagProc)
|
|
|
|
# construct()
|
|
|
|
var constructProc = newProc(newIdentNode("construct"), [
|
|
newEmptyNode(),
|
|
newIdentDefs(newIdentNode("s"), newIdentNode("YamlStream")),
|
|
newIdentDefs(newIdentNode("result"),
|
|
newNimNode(nnkVarTy).add(tIdent))])
|
|
impl = quote do:
|
|
var event = s()
|
|
if finished(s) or event.kind != yamlStartMap:
|
|
raise newException(ValueError, "Construction error!")
|
|
if event.mapTag != yTagQuestionMark and
|
|
event.mapTag != yamlTag(type(`tIdent`)):
|
|
raise newException(ValueError, "Wrong tag for " & `tName`)
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!")
|
|
while event.kind != yamlEndMap:
|
|
assert event.kind == yamlScalar
|
|
assert event.scalarTag in [yTagQuestionMark, yTagString]
|
|
case hash(event.scalarContent)
|
|
else:
|
|
raise newException(ValueError, "Unknown key for " &
|
|
`tName` & ": " & event.scalarContent)
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!")
|
|
var keyCase = impl[5][1][2]
|
|
assert keyCase.kind == nnkCaseStmt
|
|
for field in objectFields(recList):
|
|
let nameHash = hash($field.name.ident)
|
|
keyCase.insert(1, newNimNode(nnkOfBranch).add(
|
|
newIntLitNode(nameHash)).add(newStmtList(
|
|
newCall("construct", [newIdentNode("s"), newDotExpr(
|
|
newIdentNode("result"), field.name)])
|
|
))
|
|
)
|
|
|
|
constructProc[6] = impl
|
|
result.add(constructProc)
|
|
|
|
# serialize()
|
|
|
|
var serializeProc = newProc(newIdentNode("serialize"), [
|
|
newIdentNode("YamlStream"),
|
|
newIdentDefs(newIdentNode("value"), tIdent),
|
|
newIdentDefs(newIdentNode("tagStyle"),
|
|
newIdentNode("YamlTagStyle"),
|
|
newIdentNode("ytsNone"))])
|
|
var iterBody = newStmtList(
|
|
newLetStmt(newIdentNode("childTagStyle"), newNimNode(nnkIfExpr).add(
|
|
newNimNode(nnkElifExpr).add(
|
|
newNimNode(nnkInfix).add(newIdentNode("=="),
|
|
newIdentNode("tagStyle"), newIdentNode("ytsRootOnly")),
|
|
newIdentNode("ytsNone")
|
|
), newNimNode(nnkElseExpr).add(newIdentNode("tagStyle")))),
|
|
newNimNode(nnkYieldStmt).add(
|
|
newNimNode(nnkObjConstr).add(newIdentNode("YamlStreamEvent"),
|
|
newNimNode(nnkExprColonExpr).add(newIdentNode("kind"),
|
|
newIdentNode("yamlStartMap")),
|
|
newNimNode(nnkExprColonExpr).add(newIdentNode("mapTag"),
|
|
newNimNode(nnkIfExpr).add(newNimNode(nnkElifExpr).add(
|
|
newNimNode(nnkInfix).add(newIdentNode("=="),
|
|
newIdentNode("tagStyle"),
|
|
newIdentNode("ytsNone")),
|
|
newIdentNode("yTagQuestionMark")
|
|
), newNimNode(nnkElseExpr).add(
|
|
newCall("yamlTag", newCall("type", tIdent))
|
|
))),
|
|
newNimNode(nnkExprColonExpr).add(newIdentNode("mapAnchor"),
|
|
newIdentNode("yAnchorNone"))
|
|
)
|
|
), newNimNode(nnkYieldStmt).add(newNimNode(nnkObjConstr).add(
|
|
newIdentNode("YamlStreamEvent"), newNimNode(nnkExprColonExpr).add(
|
|
newIdentNode("kind"), newIdentNode("yamlEndMap")
|
|
)
|
|
)))
|
|
|
|
var i = 2
|
|
for field in objectFields(recList):
|
|
let
|
|
fieldIterIdent = newIdentNode($field.name & "Events")
|
|
fieldNameString = newStrLitNode($field.name)
|
|
iterbody.insert(i, quote do:
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(string,
|
|
childTagStyle),
|
|
scalarAnchor: yAnchorNone,
|
|
scalarContent: `fieldNameString`)
|
|
)
|
|
iterbody.insert(i + 1, newVarStmt(fieldIterIdent,
|
|
newCall("serialize", newDotExpr(newIdentNode("value"),
|
|
field.name), newIdentNode("childTagStyle"))))
|
|
iterbody.insert(i + 2, quote do:
|
|
for event in `fieldIterIdent`():
|
|
yield event
|
|
)
|
|
i += 3
|
|
impl = newStmtList(newAssignment(newIdentNode("result"), newProc(
|
|
newEmptyNode(), [newIdentNode("YamlStreamEvent")], iterBody,
|
|
nnkIteratorDef)))
|
|
serializeProc[6] = impl
|
|
result.add(serializeProc)
|
|
|
|
proc prepend*(event: YamlStreamEvent, s: YamlStream): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
yield event
|
|
for e in s():
|
|
yield e
|
|
|
|
proc yamlTag*(T: typedesc[string]): TagId {.inline.} = yTagString
|
|
|
|
proc safeTagUri*(id: TagId): string =
|
|
let uri = serializationTagLibrary.uri(id)
|
|
if uri.len > 0 and uri[0] == '!':
|
|
return uri[1..uri.len - 1]
|
|
else:
|
|
return uri
|
|
|
|
proc construct*(s: YamlStream, result: var string) =
|
|
let item = s()
|
|
if finished(s) or item.kind != yamlScalar:
|
|
raise newException(ValueError, "Construction error!")
|
|
if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark, yTagString]:
|
|
raise newException(ValueError, "Wrong tag for string.")
|
|
result = item.scalarContent
|
|
|
|
proc serialize*(value: string,
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(string, tagStyle),
|
|
scalarAnchor: yAnchorNone, scalarContent: value)
|
|
|
|
proc yamlTag*(T: typedesc[int]): TagId {.inline.} = yTagInteger
|
|
|
|
proc construct*(s: YamlStream, result: var int) =
|
|
let item = s()
|
|
if finished(s) or item.kind != yamlScalar:
|
|
raise newException(ValueError, "Construction error!")
|
|
if item.scalarTag != yTagInteger and not (
|
|
item.scalarTag == yTagQuestionMark and item.scalarType == yTypeInteger):
|
|
raise newException(ValueError, "Wrong scalar type for int.")
|
|
result = parseInt(item.scalarContent)
|
|
|
|
proc serialize*(value: int,
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(int, tagStyle),
|
|
scalarAnchor: yAnchorNone, scalarContent: $value)
|
|
|
|
proc yamlTag*(T: typedesc[int64]): TagId {.inline.} = yTagInteger
|
|
|
|
proc contruct*(s: YamlStream, result: var int64) =
|
|
let item = s()
|
|
if finished(s) or item.kind != yamlScalar:
|
|
raise newException(ValueError, "Construction error!")
|
|
if item.scalarTag != yTagInteger and not (
|
|
item.scalarTag == yTagQuestionMark and item.scalarType == yTypeInteger):
|
|
raise newException(ValueError, "Wrong scalar type for int64.")
|
|
result = parseBiggestInt(item.scalarContent)
|
|
|
|
proc serialize*(value: int64,
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(int64, tagStyle),
|
|
scalarAnchor: yAnchorNone, scalarContent: $value)
|
|
|
|
proc yamlTag*(T: typedesc[float]): TagId {.inline.} = yTagFloat
|
|
|
|
proc construct*(s: YamlStream, result: var float) =
|
|
let item = s()
|
|
if finished(s) or item.kind != yamlScalar:
|
|
raise newException(ValueError, "Construction error!")
|
|
if item.scalarTag != yTagFloat and not (
|
|
item.scalarTag == yTagQuestionMark and item.scalarType == yTypeFloat):
|
|
raise newException(ValueError, "Wrong scalar type for float.")
|
|
case item.scalarType
|
|
of yTypeFloat:
|
|
result = parseFloat(item.scalarContent)
|
|
of yTypeFloatInf:
|
|
if item.scalarContent[0] == '-':
|
|
result = NegInf
|
|
else:
|
|
result = Inf
|
|
of yTypeFloatNaN:
|
|
result = NaN
|
|
else:
|
|
raise newException(ValueError, "Wrong scalar type for float.")
|
|
|
|
proc serialize*(value: float,
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
var
|
|
asString: string
|
|
hint: YamlTypeHint
|
|
case value
|
|
of Inf:
|
|
asString = ".inf"
|
|
hint = yTypeFloatInf
|
|
of NegInf:
|
|
asString = "-.inf"
|
|
hint = yTypeFloatInf
|
|
of NaN:
|
|
asString = ".nan"
|
|
hint = yTypeFloatNaN
|
|
else:
|
|
asString = $value
|
|
hint = yTypeFloat
|
|
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(float, tagStyle),
|
|
scalarAnchor: yAnchorNone, scalarContent: asString,
|
|
scalarType: hint)
|
|
|
|
proc yamlTag*(T: typedesc[bool]): TagId {.inline.} = yTagBoolean
|
|
|
|
proc construct*(s: YamlStream, result: var bool) =
|
|
let item = s()
|
|
if finished(s) or item.kind != yamlScalar:
|
|
raise newException(ValueError, "Construction error!")
|
|
case item.scalarTag
|
|
of yTagQuestionMark:
|
|
case item.scalarType
|
|
of yTypeBoolTrue:
|
|
result = true
|
|
of yTypeBoolFalse:
|
|
result = false
|
|
else:
|
|
raise newException(ValueError, "Wrong scalar type for bool.")
|
|
of yTagBoolean:
|
|
if item.scalarContent.match(
|
|
re"y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON"):
|
|
result = true
|
|
elif item.scalarContent.match(
|
|
re"n|N|no|No|NO|false|False|FALSE|off|Off|OFF"):
|
|
result = false
|
|
else:
|
|
raise newException(ValueError, "Wrong content for bool.")
|
|
else:
|
|
raise newException(ValueError, "Wrong scalar type for bool")
|
|
|
|
proc serialize*(value: bool,
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
yield YamlStreamEvent(kind: yamlScalar,
|
|
scalarTag: presentTag(bool, tagStyle),
|
|
scalarAnchor: yAnchorNone, scalarContent:
|
|
if value: "y" else: "n")
|
|
|
|
proc yamlTag*[I](T: typedesc[seq[I]]): TagId {.inline.} =
|
|
let uri = "!nim:seq(" & safeTagUri(yamlTag(I)) & ")"
|
|
result = lazyLoadTag(uri)
|
|
|
|
proc construct*[T](s: YamlStream, result: var seq[T]) =
|
|
var event = s()
|
|
if finished(s) or event.kind != yamlStartSequence:
|
|
raise newException(ValueError, "Construction error!1")
|
|
if event.seqTag != yTagQuestionMark and
|
|
event.seqTag != yamlTag(seq[T]):
|
|
raise newException(ValueError, "Wrong sequence type for seq[T]")
|
|
result = newSeq[T]()
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!2")
|
|
while event.kind != yamlEndSequence:
|
|
var
|
|
item: T
|
|
events = prepend(event, s)
|
|
construct(events, item)
|
|
result.add(item)
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!3")
|
|
|
|
proc serialize*[T](value: seq[T],
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
let childTagStyle = if tagStyle == ytsRootOnly: ytsNone else: tagStyle
|
|
yield YamlStreamEvent(kind: yamlStartSequence,
|
|
seqTag: presentTag(seq[T], tagStyle),
|
|
seqAnchor: yAnchorNone)
|
|
for item in value:
|
|
var events = serialize(item, childTagStyle)
|
|
for event in events():
|
|
yield event
|
|
yield YamlStreamEvent(kind: yamlEndSequence)
|
|
|
|
proc yamlTag*[K, V](T: typedesc[Table[K, V]]): TagId {.inline.} =
|
|
let
|
|
keyUri = serializationTagLibrary.uri(yamlTag(K))
|
|
valueUri = serializationTagLibrary.uri(yamlTag(V))
|
|
keyIdent = if keyUri[0] == '!': keyUri[1..keyUri.len - 1] else: keyUri
|
|
valueIdent = if valueUri[0] == '!':
|
|
valueUri[1..valueUri.len - 1] else: valueUri
|
|
uri = "!nim:Table(" & keyUri & "," & valueUri & ")"
|
|
result = lazyLoadTag(uri)
|
|
|
|
proc construct*[K, V](s: YamlStream, result: var Table[K, V]) =
|
|
var event = s()
|
|
if finished(s) or event.kind != yamlStartMap:
|
|
raise newException(ValueError, "Construction error!")
|
|
if event.mapTag != yTagQuestionMark and
|
|
event.mapTag != yamlTag(Table[K, V]):
|
|
raise newException(ValueError, "Wrong map type for Table[K, V]")
|
|
result = initTable[K, V]()
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!")
|
|
while event.kind != yamlEndMap:
|
|
var
|
|
key: K
|
|
value: V
|
|
events = prepend(event, s)
|
|
construct(events, key)
|
|
construct(s, value)
|
|
result[key] = value
|
|
event = s()
|
|
if finished(s):
|
|
raise newException(ValueError, "Construction error!")
|
|
|
|
proc serialize*[K, V](value: Table[K, V],
|
|
tagStyle: YamlTagStyle = ytsNone): YamlStream =
|
|
result = iterator(): YamlStreamEvent =
|
|
let childTagStyle = if tagStyle == ytsRootOnly: ytsNone else: tagStyle
|
|
yield YamlStreamEvent(kind: yamlStartMap,
|
|
mapTag: presentTag(Table[K, V], tagStyle),
|
|
mapAnchor: yAnchorNone)
|
|
for key, value in value.pairs:
|
|
var events = serialize(key, childTagStyle)
|
|
for event in events():
|
|
yield event
|
|
events = serialize(value, childTagStyle)
|
|
for event in events():
|
|
yield event
|
|
yield YamlStreamEvent(kind: yamlEndMap)
|
|
|
|
proc load*[K](input: Stream, target: var K) =
|
|
var
|
|
parser = newParser(serializationTagLibrary)
|
|
events = parser.parse(input)
|
|
assert events().kind == yamlStartDocument
|
|
construct(events, target)
|
|
assert events().kind == yamlEndDocument
|
|
|
|
proc dump*[K](value: K, target: Stream,
|
|
style: YamlPresentationStyle = ypsDefault,
|
|
tagStyle: YamlTagStyle = ytsRootOnly, indentationStep: int = 2) =
|
|
var serialized = serialize(value,
|
|
if style == ypsCanonical: ytsAll else: tagStyle)
|
|
var events = iterator(): YamlStreamEvent =
|
|
yield YamlStreamEvent(kind: yamlStartDocument)
|
|
for event in serialized():
|
|
yield event
|
|
yield YamlStreamEvent(kind: yamlEndDocument)
|
|
present(events, target, serializationTagLibrary, style, indentationStep) |