Support serializing tuples. Multiple bugfixes.

* Parser: Properly support explicit keys in flow style.
 * Parser: Properly support object tags in flow style.
 * Serializer: Basic support for tuples.
 * Serializer: Properly parse int and bool types when given as
   string with explicit tag.
This commit is contained in:
Felix Krause 2016-01-05 16:54:14 +01:00
parent aa98c5863e
commit d64184c025
5 changed files with 121 additions and 38 deletions

View File

@ -213,18 +213,25 @@ Output:
- Add type hints for more scalar types - Add type hints for more scalar types
* Parser: * Parser:
- Properly handle leading spaces in block scalars - Properly handle leading spaces in block scalars
- Raise exceptions instead of yielding yamlError tokens, which are a pain to
handle for the caller.
- Add an optional warning callback instead of yielding yamlWarning tokens,
which are a pain to handle for the caller.
- Oh and did I mention `yamlError` and `yamlWarning` are evil and need to
die.
* Serialization: * Serialization:
- Generate local tags and use them
- Support for tuples
- Support for more standard library types - Support for more standard library types
- Support for ref and ptr types - Support for ref and ptr types
- Support for anchors and aliases - Support for anchors and aliases (requires ref and ptr types)
- Support polymorphism - Support polymorphism (requires ref and ptr types)
- Support variant objects - Support variant objects
- Support transient fields (i.e. fields that will not be (de-)serialized on
objects and tuples)
- Make it possible for user to define a tag URI for custom types - Make it possible for user to define a tag URI for custom types
- Use `concept` type class `Serializable` or something - Use `concept` type class `Serializable` or something
* General: * General:
- Proper error handling (do not use `ValueError` for everything) - Proper error handling (do not use `ValueError` for everything)
- Proper error handling, seriously
- Document exceptions with `raises` pragmas in code - Document exceptions with `raises` pragmas in code
## License ## License

View File

@ -25,7 +25,7 @@ type
# from here on tokens only in content # from here on tokens only in content
tLineStart, tLineStart,
# control characters # control characters
tColon, tDash, tQuestionmark, tComma, tOpeningBrace, tColon, tDash, tQuestionMark, tComma, tOpeningBrace,
tOpeningBracket, tClosingBrace, tClosingBracket, tPipe, tGreater, tOpeningBracket, tClosingBrace, tClosingBracket, tPipe, tGreater,
# block scalar header # block scalar header
tBlockIndentationIndicator, tPlus, tBlockIndentationIndicator, tPlus,
@ -887,6 +887,7 @@ iterator tokens(my: var YamlLexer): YamlLexerToken {.closure.} =
my.content = "!" my.content = "!"
yield(tTagHandle) yield(tTagHandle)
my.content = suffix my.content = suffix
my.content.add(c)
state = ylTagSuffix state = ylTagSuffix
of ' ', '\t', EndOfFile, '\r', '\x0A': of ' ', '\t', EndOfFile, '\r', '\x0A':
let suffix = my.content[1..^1] let suffix = my.content[1..^1]

View File

@ -11,7 +11,8 @@ type
ypBlockAfterAlias, ypBlockAfterColon, ypBlockMultilineScalar, ypBlockAfterAlias, ypBlockAfterColon, ypBlockMultilineScalar,
ypBlockLineEnd, ypBlockScalarHeader, ypBlockScalar, ypFlow, ypBlockLineEnd, ypBlockScalarHeader, ypBlockScalar, ypFlow,
ypFlowAfterObject, ypFlowAfterTag, ypFlowAfterAnchor, ypFlowAfterObject, ypFlowAfterTag, ypFlowAfterAnchor,
ypFlowAfterAnchorAndTag, ypExpectingDocumentEnd, ypAfterDirectivesEnd ypFlowAfterQuestionMark, ypFlowAfterAnchorAndTag,
ypExpectingDocumentEnd, ypAfterDirectivesEnd
DocumentLevelMode = enum DocumentLevelMode = enum
mBlockSequenceItem, mFlowSequenceItem, mExplicitBlockMapKey, mBlockSequenceItem, mFlowSequenceItem, mExplicitBlockMapKey,
@ -112,7 +113,8 @@ template yieldScalar(content: string, typeHint: YamlTypeHint,
"scalar[\"", content, "\", type=", typeHint, "]" "scalar[\"", content, "\", type=", typeHint, "]"
if objectTag.len > 0: if objectTag.len > 0:
if tag.len > 0: if tag.len > 0:
yieldError("Duplicate tag for scalar") yieldError("Duplicate tag for scalar (tag=" & tag & ", objectTag=" &
objectTag)
tag = objectTag tag = objectTag
objectTag = "" objectTag = ""
yield YamlStreamEvent(kind: yamlScalar, yield YamlStreamEvent(kind: yamlScalar,
@ -809,6 +811,8 @@ proc parse*(parser: YamlSequentialParser, s: Stream): YamlStream =
indentationColumn: -1) indentationColumn: -1)
else: else:
yieldUnexpectedToken("scalar, comma or map end") yieldUnexpectedToken("scalar, comma or map end")
of tQuestionMark:
state = ypFlowAfterQuestionMark
of tComma: of tComma:
yieldScalar("", yTypeUnknown) yieldScalar("", yTypeUnknown)
level = ancestry.pop() level = ancestry.pop()
@ -826,6 +830,10 @@ proc parse*(parser: YamlSequentialParser, s: Stream): YamlStream =
if level.mode != mUnknown: if level.mode != mUnknown:
yieldUnexpectedToken() yieldUnexpectedToken()
level.mode = mFlowMapKey level.mode = mFlowMapKey
if objectTag.len > 0:
assert tag.len == 0
tag = objectTag
objectTag = ""
yieldStart(yamlStartMap) yieldStart(yamlStartMap)
ancestry.add(level) ancestry.add(level)
level = DocumentLevel(mode: mUnknown, indicatorColumn: -1, level = DocumentLevel(mode: mUnknown, indicatorColumn: -1,
@ -834,6 +842,10 @@ proc parse*(parser: YamlSequentialParser, s: Stream): YamlStream =
if level.mode != mUnknown: if level.mode != mUnknown:
yieldUnexpectedToken() yieldUnexpectedToken()
level.mode = mFlowSequenceItem level.mode = mFlowSequenceItem
if objectTag.len > 0:
assert tag.len == 0
tag = objectTag
objectTag = ""
yieldStart(yamlStartSequence) yieldStart(yamlStartSequence)
ancestry.add(level) ancestry.add(level)
level = DocumentLevel(mode: mUnknown, indicatorColumn: -1, level = DocumentLevel(mode: mUnknown, indicatorColumn: -1,
@ -884,6 +896,15 @@ proc parse*(parser: YamlSequentialParser, s: Stream): YamlStream =
level = ancestry.pop() level = ancestry.pop()
else: else:
yieldUnexpectedToken() yieldUnexpectedToken()
of ypFlowAfterQuestionMark:
case token
of tScalar, tScalarPart, tColon, tComma, tOpeningBrace,
tOpeningBracket, tClosingBrace, tClosingBracket, tTagHandle,
tAnchor, tAlias:
state = ypFlow
continue
else:
yieldUnexpectedToken()
of ypFlowAfterTag: of ypFlowAfterTag:
case token case token
of tTagHandle: of tTagHandle:

View File

@ -13,6 +13,7 @@ proc registerUri*(tagLib: var YamlTagLibrary, uri: string): TagId =
tagLib.tags[uri] = tagLib.nextCustomTagId tagLib.tags[uri] = tagLib.nextCustomTagId
result = tagLib.nextCustomTagId result = tagLib.nextCustomTagId
tagLib.nextCustomTagId = cast[TagId](cast[int](tagLib.nextCustomTagId) + 1) tagLib.nextCustomTagId = cast[TagId](cast[int](tagLib.nextCustomTagId) + 1)
echo "registered ", uri, " as: ", result
proc uri*(tagLib: YamlTagLibrary, id: TagId): string = proc uri*(tagLib: YamlTagLibrary, id: TagId): string =
for iUri, iId in tagLib.tags.pairs: for iUri, iId in tagLib.tags.pairs:

View File

@ -1,5 +1,5 @@
import "../yaml" import "../yaml"
import macros, strutils, streams, tables, json, hashes import macros, strutils, streams, tables, json, hashes, re
export yaml, streams, tables, json export yaml, streams, tables, json
type type
@ -33,11 +33,13 @@ var
static: static:
iterator objectFields(n: NimNode): tuple[name: NimNode, t: NimNode] = iterator objectFields(n: NimNode): tuple[name: NimNode, t: NimNode] =
assert n.kind == nnkRecList assert n.kind in [nnkRecList, nnkTupleTy]
for identDefs in n.children: for identDefs in n.children:
let numFields = identDefs.len - 2 let numFields = identDefs.len - 2
for i in 0..numFields - 1: for i in 0..numFields - 1:
yield (name: identDefs[i], t: identDefs[^2]) yield (name: identDefs[i], t: identDefs[^2])
var existingTuples = newSeq[NimNode]()
template presentTag(t: typedesc, tagStyle: YamlTagStyle): TagId = template presentTag(t: typedesc, tagStyle: YamlTagStyle): TagId =
if tagStyle == ytsNone: yTagQuestionMark else: yamlTag(t) if tagStyle == ytsNone: yTagQuestionMark else: yamlTag(t)
@ -56,22 +58,56 @@ macro make_serializable*(types: stmt): stmt =
let let
tName = $typedef[0].symbol tName = $typedef[0].symbol
tIdent = newIdentNode(tName) tIdent = newIdentNode(tName)
tUri = "!nim:" & tName var
tUri: NimNode
recList: NimNode
assert typedef[1].kind == nnkEmpty assert typedef[1].kind == nnkEmpty
let objectTy = typedef[2] let objectTy = typedef[2]
assert objectTy.kind == nnkObjectTy case objectTy.kind
assert objectTy[0].kind == nnkEmpty of nnkObjectTy:
assert objectTy[1].kind == nnkEmpty assert objectTy[0].kind == nnkEmpty
let recList = objectTy[2] assert objectTy[1].kind == nnkEmpty
assert recList.kind == nnkRecList 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() # yamlTag()
var yamlTagProc = newProc(newIdentNode("yamlTag"), [ var yamlTagProc = newProc(newIdentNode("yamlTag"), [
newIdentNode("TagId"), newIdentNode("TagId"),
newIdentDefs(newIdentNode("T"), newNimNode(nnkBracketExpr).add( newIdentDefs(newIdentNode("T"), newNimNode(nnkBracketExpr).add(
newIdentNode("typedesc"), tIdent))]) newIdentNode("typedesc"), tIdent))])
var impl = newStmtList(newCall("lazyLoadTag", newStrLitNode(tUri))) var impl = newStmtList(newCall("lazyLoadTag", tUri))
yamlTagProc[6] = impl yamlTagProc[6] = impl
result.add(yamlTagProc) result.add(yamlTagProc)
@ -93,6 +129,7 @@ macro make_serializable*(types: stmt): stmt =
if finished(s): if finished(s):
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
while event.kind != yamlEndMap: while event.kind != yamlEndMap:
if event.kind == yamlError: echo event.description
assert event.kind == yamlScalar assert event.kind == yamlScalar
assert event.scalarTag in [yTagQuestionMark, yTagString] assert event.scalarTag in [yTagQuestionMark, yTagString]
case hash(event.scalarContent) case hash(event.scalarContent)
@ -102,7 +139,7 @@ macro make_serializable*(types: stmt): stmt =
event = s() event = s()
if finished(s): if finished(s):
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
var keyCase = impl[5][1][2] var keyCase = impl[5][1][3]
assert keyCase.kind == nnkCaseStmt assert keyCase.kind == nnkCaseStmt
for field in objectFields(recList): for field in objectFields(recList):
let nameHash = hash($field.name.ident) let nameHash = hash($field.name.ident)
@ -178,6 +215,7 @@ macro make_serializable*(types: stmt): stmt =
nnkIteratorDef))) nnkIteratorDef)))
serializeProc[6] = impl serializeProc[6] = impl
result.add(serializeProc) result.add(serializeProc)
echo result.repr
proc prepend*(event: YamlStreamEvent, s: YamlStream): YamlStream = proc prepend*(event: YamlStreamEvent, s: YamlStream): YamlStream =
result = iterator(): YamlStreamEvent = result = iterator(): YamlStreamEvent =
@ -187,6 +225,13 @@ proc prepend*(event: YamlStreamEvent, s: YamlStream): YamlStream =
proc yamlTag*(T: typedesc[string]): TagId {.inline.} = yTagString 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) = proc construct*(s: YamlStream, result: var string) =
let item = s() let item = s()
if finished(s) or item.kind != yamlScalar: if finished(s) or item.kind != yamlScalar:
@ -208,8 +253,8 @@ proc construct*(s: YamlStream, result: var int) =
let item = s() let item = s()
if finished(s) or item.kind != yamlScalar: if finished(s) or item.kind != yamlScalar:
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
if item.scalarTag notin [yTagQuestionMark, yTagInteger] or if item.scalarTag != yTagInteger and not (
item.scalarType != yTypeInteger: item.scalarTag == yTagQuestionMark and item.scalarType == yTypeInteger):
raise newException(ValueError, "Wrong scalar type for int.") raise newException(ValueError, "Wrong scalar type for int.")
result = parseInt(item.scalarContent) result = parseInt(item.scalarContent)
@ -226,8 +271,8 @@ proc contruct*(s: YamlStream, result: var int64) =
let item = s() let item = s()
if finished(s) or item.kind != yamlScalar: if finished(s) or item.kind != yamlScalar:
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
if item.scalarTag notin [yTagQuestionMark, yTagInteger] or if item.scalarTag != yTagInteger and not (
item.scalarType != yTypeInteger: item.scalarTag == yTagQuestionMark and item.scalarType == yTypeInteger):
raise newException(ValueError, "Wrong scalar type for int64.") raise newException(ValueError, "Wrong scalar type for int64.")
result = parseBiggestInt(item.scalarContent) result = parseBiggestInt(item.scalarContent)
@ -244,7 +289,8 @@ proc construct*(s: YamlStream, result: var float) =
let item = s() let item = s()
if finished(s) or item.kind != yamlScalar: if finished(s) or item.kind != yamlScalar:
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
if item.scalarTag notin [yTagQuestionMark, yTagFloat]: if item.scalarTag != yTagFloat and not (
item.scalarTag == yTagQuestionMark and item.scalarType == yTypeFloat):
raise newException(ValueError, "Wrong scalar type for float.") raise newException(ValueError, "Wrong scalar type for float.")
case item.scalarType case item.scalarType
of yTypeFloat: of yTypeFloat:
@ -278,16 +324,27 @@ proc construct*(s: YamlStream, result: var bool) =
let item = s() let item = s()
if finished(s) or item.kind != yamlScalar: if finished(s) or item.kind != yamlScalar:
raise newException(ValueError, "Construction error!") raise newException(ValueError, "Construction error!")
if item.scalarTag notin [yTagQuestionMark, yTagBoolean]: case item.scalarTag
raise newException(ValueError, "Wrong scalar type for bool.") of yTagQuestionMark:
case item.scalarType case item.scalarType
of yTypeBoolTrue: of yTypeBoolTrue:
result = true result = true
of yTypeBoolFalse: of yTypeBoolFalse:
result = false 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: else:
raise newException(ValueError, "Wrong scalar type for bool.") raise newException(ValueError, "Wrong scalar type for bool")
proc serialize*(value: bool, proc serialize*(value: bool,
tagStyle: YamlTagStyle = ytsNone): YamlStream = tagStyle: YamlTagStyle = ytsNone): YamlStream =
result = iterator(): YamlStreamEvent = result = iterator(): YamlStreamEvent =
@ -297,11 +354,7 @@ proc serialize*(value: bool,
if value: "y" else: "n") if value: "y" else: "n")
proc yamlTag*[I](T: typedesc[seq[I]]): TagId {.inline.} = proc yamlTag*[I](T: typedesc[seq[I]]): TagId {.inline.} =
let let uri = "!nim:seq(" & safeTagUri(yamlTag(I)) & ")"
childUri = serializationTagLibrary.uri(yamlTag(I))
childId = if childUri[0] == '!':
childUri[1..childUri.len-1] else: childUri
uri = "!nim:seq(" & childId & ")"
result = lazyLoadTag(uri) result = lazyLoadTag(uri)
proc construct*[T](s: YamlStream, result: var seq[T]) = proc construct*[T](s: YamlStream, result: var seq[T]) =
@ -389,7 +442,7 @@ proc serialize*[K, V](value: Table[K, V],
proc load*[K](input: Stream, target: var K) = proc load*[K](input: Stream, target: var K) =
var var
parser = newParser(coreTagLibrary()) parser = newParser(serializationTagLibrary)
events = parser.parse(input) events = parser.parse(input)
assert events().kind == yamlStartDocument assert events().kind == yamlStartDocument
construct(events, target) construct(events, target)