From f3f6f5de9eff98c549b9a2c3108b32de8de3fe30 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Tue, 5 Jan 2016 19:58:46 +0100 Subject: [PATCH] Added explicit raises pragma to present() * Added YamlPresenter*Error exceptions * Added raises pragma to present() * catch exceptions from YamlStream and Stream in present() and put them as cause into YamlPresenter*Error * Added loadToJson() proc * Give "~" the type hint yTypeNull * Always output "null" for yTagNull/yTypeNull scalars when presenter is in JSON mode --- README.md | 2 - private/json.nim | 8 +- private/lexer.nim | 4 +- private/presenter.nim | 381 ++++++++++++++++++++++++------------------ yaml.nim | 33 +++- 5 files changed, 259 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index ef09c95..da185b0 100644 --- a/README.md +++ b/README.md @@ -213,8 +213,6 @@ Output: - Add type hints for more scalar types * Parser: - Properly handle leading spaces in block scalars - - Add an optional warning callback instead of yielding `yamlWarning` tokens, - which are a pain to handle for the caller. * Serialization: - Support for more standard library types - Support for ref and ptr types diff --git a/private/json.nim b/private/json.nim index 854d859..dadf22c 100644 --- a/private/json.nim +++ b/private/json.nim @@ -154,4 +154,10 @@ proc constructJson*(s: YamlStream): seq[JsonNode] = val: anchors[event.aliasTarget])) levels[levels.high].key = nil else: - discard # will never happen \ No newline at end of file + discard # will never happen + +proc loadToJson*(s: Stream): seq[JsonNode] = + var + parser = newParser(coreTagLibrary()) + events = parser.parse(s) + return constructJson(events) \ No newline at end of file diff --git a/private/lexer.nim b/private/lexer.nim index 16385ae..1d107e8 100644 --- a/private/lexer.nim +++ b/private/lexer.nim @@ -79,7 +79,7 @@ type ythLowerOF, ythLowerTR, ythLowerTRU, ythLowerYE, - + ythPointLowerIN, ythPointLowerN, ythPointLowerNA, ythMinus, yth0, ythInt, ythDecimal, ythNumE, ythNumEPlusMinus, @@ -269,6 +269,8 @@ macro typeHintStateMachine(c: untyped, content: untyped): stmt = template advanceTypeHint(ch: char) {.dirty.} = typeHintStateMachine ch: + of '~': + ythInitial => ythNULL of '.': [yth0, ythInt] => ythDecimal [ythInitial, ythMinus] => ythPoint diff --git a/private/presenter.nim b/private/presenter.nim index b69de45..2d6bafc 100644 --- a/private/presenter.nim +++ b/private/presenter.nim @@ -12,113 +12,137 @@ type dFlowSequenceItem, dFlowImplicitMapStart, dFlowExplicitMapStart, dFlowSequenceStart -proc needsEscaping(scalar: string): bool = +proc needsEscaping(scalar: string): bool {.raises: [].} = scalar.len == 0 or scalar.find({'{', '}', '[', ']', ',', '#', '-', ':', '?', '%', '\x0A', '\c'}) != -1 -proc writeDoubleQuoted(scalar: string, s: Stream) = - s.write('"') - for c in scalar: - if c == '"': - s.write('\\') - s.write(c) - s.write('"') +proc writeDoubleQuoted(scalar: string, s: Stream) + {.raises: [YamlPresenterOutputError].} = + try: + s.write('"') + for c in scalar: + if c == '"': + s.write('\\') + s.write(c) + s.write('"') + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e + +template safeWrite(s: string or char) {.dirty.} = + try: + target.write(s) + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e proc startItem(target: Stream, style: YamlPresentationStyle, indentation: int, - state: var DumperState) = - case state - of dBlockExplicitMapValue: - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write("? ") - state = dBlockExplicitMapKey - of dBlockExplicitMapKey: - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write(": ") - state = dBlockExplicitMapValue - of dBlockImplicitMapValue: - target.write('\x0A') - target.write(repeat(' ', indentation)) - state = dBlockImplicitMapKey - of dBlockImplicitMapKey: - target.write(": ") - state = dBlockImplicitMapValue - of dFlowExplicitMapKey: - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write(": ") - state = dFlowExplicitMapValue - of dFlowExplicitMapValue: - target.write(",\x0A") - target.write(repeat(' ', indentation)) - target.write("? ") - state = dFlowExplicitMapKey - of dFlowImplicitMapStart: - if style == ypsJson: - target.write("\x0A") - target.write(repeat(' ', indentation)) - state = dFlowImplicitMapKey - of dFlowExplicitMapStart: - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write("? ") - state = dFlowExplicitMapKey - of dFlowImplicitMapKey: - target.write(": ") - state = dFlowImplicitMapValue - of dFlowImplicitMapValue: - if style == ypsJson: - target.write(",\x0A") - target.write(repeat(' ', indentation)) - else: - target.write(", ") - state = dFlowImplicitMapKey - of dBlockSequenceItem: - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write("- ") - of dFlowSequenceStart: - case style - of ypsMinimal, ypsDefault: - discard - of ypsCanonical, ypsJson: + state: var DumperState) {.raises: [YamlPresenterOutputError].} = + try: + case state + of dBlockExplicitMapValue: target.write('\x0A') target.write(repeat(' ', indentation)) - of ypsBlockOnly: - discard # can never happen - state = dFlowSequenceItem - of dFlowSequenceItem: - case style - of ypsMinimal, ypsDefault: - target.write(", ") - of ypsCanonical, ypsJson: + target.write("? ") + state = dBlockExplicitMapKey + of dBlockExplicitMapKey: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write(": ") + state = dBlockExplicitMapValue + of dBlockImplicitMapValue: + target.write('\x0A') + target.write(repeat(' ', indentation)) + state = dBlockImplicitMapKey + of dBlockImplicitMapKey: + target.write(": ") + state = dBlockImplicitMapValue + of dFlowExplicitMapKey: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write(": ") + state = dFlowExplicitMapValue + of dFlowExplicitMapValue: target.write(",\x0A") target.write(repeat(' ', indentation)) - of ypsBlockOnly: - discard # can never happen + target.write("? ") + state = dFlowExplicitMapKey + of dFlowImplicitMapStart: + if style == ypsJson: + target.write("\x0A") + target.write(repeat(' ', indentation)) + state = dFlowImplicitMapKey + of dFlowExplicitMapStart: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write("? ") + state = dFlowExplicitMapKey + of dFlowImplicitMapKey: + target.write(": ") + state = dFlowImplicitMapValue + of dFlowImplicitMapValue: + if style == ypsJson: + target.write(",\x0A") + target.write(repeat(' ', indentation)) + else: + target.write(", ") + state = dFlowImplicitMapKey + of dBlockSequenceItem: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write("- ") + of dFlowSequenceStart: + case style + of ypsMinimal, ypsDefault: + discard + of ypsCanonical, ypsJson: + target.write('\x0A') + target.write(repeat(' ', indentation)) + of ypsBlockOnly: + discard # can never happen + state = dFlowSequenceItem + of dFlowSequenceItem: + case style + of ypsMinimal, ypsDefault: + target.write(", ") + of ypsCanonical, ypsJson: + target.write(",\x0A") + target.write(repeat(' ', indentation)) + of ypsBlockOnly: + discard # can never happen + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e proc writeTagAndAnchor(target: Stream, tag: TagId, tagLib: YamlTagLibrary, - anchor: AnchorId) = - if tag notin [yTagQuestionMark, yTagExclamationMark]: - let tagUri = tagLib.uri(tag) - if tagUri.startsWith(tagLib.secondaryPrefix): - target.write("!!") - target.write(tagUri[18..^1]) + anchor: AnchorId) {.raises:[YamlPresenterOutputError].} = + try: + if tag notin [yTagQuestionMark, yTagExclamationMark]: + let tagUri = tagLib.uri(tag) + if tagUri.startsWith(tagLib.secondaryPrefix): + target.write("!!") + target.write(tagUri[18..^1]) + target.write(' ') + elif tagUri.startsWith("!"): + target.write(tagUri) + target.write(' ') + else: + target.write("!<") + target.write(tagUri) + target.write("> ") + if anchor != yAnchorNone: + target.write("&") + # TODO: properly select an anchor + target.write(cast[byte]('a') + cast[byte](anchor)) target.write(' ') - elif tagUri.startsWith("!"): - target.write(tagUri) - target.write(' ') - else: - target.write("!<") - target.write(tagUri) - target.write("> ") - if anchor != yAnchorNone: - target.write("&") - # TODO: properly select an anchor - target.write(cast[byte]('a') + cast[byte](anchor)) - target.write(' ') + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, style: YamlPresentationStyle = ypsDefault, @@ -129,10 +153,15 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, while true: while cached.len > 0: yield cached.dequeue() - let item = s() - if finished(s): - break - cached.enqueue(item) + try: + let item = s() + if finished(s): + break + cached.enqueue(item) + except: + var e = newException(YamlPresenterStreamError, "") + e.cause = getCurrentException() + raise e indentation = 0 levels = newSeq[DumperState]() @@ -141,14 +170,20 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of yamlStartDocument: if style != ypsJson: # TODO: tag directives - target.write("%YAML 1.2\x0A") - if tagLib.secondaryPrefix != yamlTagRepositoryPrefix: - target.write("%TAG !! " & tagLib.secondaryPrefix & '\x0A') - target.write("--- ") + try: + target.write("%YAML 1.2\x0A") + if tagLib.secondaryPrefix != yamlTagRepositoryPrefix: + target.write("%TAG !! " & + tagLib.secondaryPrefix & '\x0A') + target.write("--- ") + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e of yamlScalar: if levels.len == 0: if style != ypsJson: - target.write('\x0A') + safeWrite('\x0A') else: startItem(target, style, indentation, levels[levels.high]) if style != ypsJson: @@ -159,18 +194,18 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, if item.scalarTag in [yTagQuestionMark, yTagBoolean] and item.scalarType in [yTypeBoolTrue, yTypeBoolFalse]: if item.scalarType == yTypeBoolTrue: - target.write("true") + safeWrite("true") else: - target.write("false") + safeWrite("false") elif item.scalarTag in [yTagQuestionMark, yTagNull] and item.scalarType == yTypeNull: - target.write("null") + safeWrite("null") elif item.scalarTag in [yTagQuestionMark, yTagFloat] and item.scalarType in [yTypeFloatInf, yTypeFloatNaN]: - raise newException(YamlPresenterError, + raise newException(YamlPresenterJsonError, "Infinity and not-a-number values cannot be presented as JSON!") else: - target.write(item.scalarContent) + safeWrite(item.scalarContent) elif style == ypsCanonical or item.scalarContent.needsEscaping or (style == ypsJson and (item.scalarTag notin [yTagQuestionMark, yTagInteger, yTagFloat, @@ -180,37 +215,47 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, yTypeNull]))): writeDoubleQuoted(item.scalarContent, target) else: - target.write(item.scalarContent) + safeWrite(item.scalarContent) of yamlAlias: assert levels.len > 0 startItem(target, style, indentation, levels[levels.high]) - target.write('*') - target.write(cast[byte]('a') + cast[byte](item.aliasTarget)) + try: + target.write('*') + target.write(cast[byte]('a') + cast[byte](item.aliasTarget)) + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e of yamlStartSequence: var nextState: DumperState case style of ypsDefault: var length = 0 while true: - let next = s() - assert (not finished(s)) - cached.enqueue(next) - case next.kind - of yamlScalar: - length += 2 + next.scalarContent.len - of yamlAlias: - length += 6 - of yamlEndSequence: - break - else: - length = int.high - break + try: + let next = s() + assert (not finished(s)) + cached.enqueue(next) + case next.kind + of yamlScalar: + length += 2 + next.scalarContent.len + of yamlAlias: + length += 6 + of yamlEndSequence: + break + else: + length = int.high + break + except: + var e = newException(YamlPresenterStreamError, "") + e.cause = getCurrentException() + raise e nextState = if length <= 60: dFlowSequenceStart else: dBlockSequenceItem of ypsJson: if levels[levels.high] in [dFlowImplicitMapStart, dFlowImplicitMapValue]: - raise newException(YamlPresenterError, + raise newException(YamlPresenterJsonError, "Cannot have sequence as map key in JSON output!") nextState = dFlowSequenceStart of ypsMinimal, ypsCanonical: @@ -227,7 +272,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, if style != ypsJson: writeTagAndAnchor(target, item.seqTag, tagLib, item.seqAnchor) - target.write('\x0A') + safeWrite('\x0A') indentation += indentationStep else: startItem(target, style, indentation, levels[levels.high]) @@ -237,7 +282,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, indentation += indentationStep if nextState == dFlowSequenceStart: - target.write('[') + safeWrite('[') if levels.len > 0 and style in [ypsJson, ypsCanonical] and levels[levels.high] in [dBlockExplicitMapKey, dBlockExplicitMapValue, @@ -251,19 +296,24 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of ypsDefault: var length = 0 while true: - let next = s() - assert (not finished(s)) - cached.enqueue(next) - case next.kind - of yamlScalar: - length += 2 + next.scalarContent.len - of yamlAlias: - length += 6 - of yamlEndMap: - break - else: - length = int.high - break + try: + let next = s() + assert (not finished(s)) + cached.enqueue(next) + case next.kind + of yamlScalar: + length += 2 + next.scalarContent.len + of yamlAlias: + length += 6 + of yamlEndMap: + break + else: + length = int.high + break + except: + var e = newException(YamlPresenterStreamError, "") + e.cause = getCurrentException() + raise e nextState = if length <= 60: dFlowImplicitMapStart else: if item.mapMayHaveKeyObjects: dBlockExplicitMapValue else: dBlockImplicitMapValue @@ -275,7 +325,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of ypsJson: if levels[levels.high] in [dFlowImplicitMapStart, dFlowImplicitMapValue]: - raise newException(YamlPresenterError, + raise newException(YamlPresenterJsonError, "Cannot have map as map key in JSON output!") nextState = dFlowImplicitMapStart of ypsBlockOnly: @@ -290,7 +340,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, item.mapTag, tagLib, item.mapAnchor) else: if style != ypsJson: - target.write('\x0A') + safeWrite('\x0A') writeTagAndAnchor(target, item.mapTag, tagLib, item.mapAnchor) indentation += indentationStep @@ -310,7 +360,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, indentation += indentationStep if nextState in [dFlowImplicitMapStart, dFlowExplicitMapStart]: - target.write('{') + safeWrite('{') if levels.len > 0 and style in [ypsJson, ypsCanonical] and levels[levels.high] in [dBlockExplicitMapKey, dBlockExplicitMapValue, @@ -325,12 +375,17 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of dFlowSequenceItem: case style of ypsDefault, ypsMinimal, ypsBlockOnly: - target.write(']') + safeWrite(']') of ypsJson, ypsCanonical: indentation -= indentationStep - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write(']') + try: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write(']') + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e if levels.len == 0 or levels[levels.high] notin [dBlockExplicitMapKey, dBlockExplicitMapValue, dBlockImplicitMapKey, dBlockImplicitMapValue, @@ -343,7 +398,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, dBlockImplicitMapKey, dBlockImplicitMapValue, dBlockSequenceItem]: indentation -= indentationStep - target.write(']') + safeWrite(']') of dBlockSequenceItem: discard else: @@ -355,12 +410,17 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of dFlowImplicitMapValue, dFlowExplicitMapValue: case style of ypsDefault, ypsMinimal, ypsBlockOnly: - target.write('}') + safeWrite('}') of ypsJson, ypsCanonical: indentation -= indentationStep - target.write('\x0A') - target.write(repeat(' ', indentation)) - target.write('}') + try: + target.write('\x0A') + target.write(repeat(' ', indentation)) + target.write('}') + except: + var e = newException(YamlPresenterOutputError, "") + e.cause = getCurrentException() + raise e if levels.len == 0 or levels[levels.high] notin [dBlockExplicitMapKey, dBlockExplicitMapValue, dBlockImplicitMapKey, dBlockImplicitMapValue, @@ -373,18 +433,23 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, dBlockImplicitMapKey, dBlockImplicitMapValue, dBlockSequenceItem]: indentation -= indentationStep - target.write('}') + safeWrite('}') of dBlockImplicitMapValue, dBlockExplicitMapValue: discard else: assert false indentation -= indentationStep of yamlEndDocument: - let next = s() - if finished(s): - break - target.write("...\x0A") - cached.enqueue(next) + try: + let next = s() + if finished(s): + break + cached.enqueue(next) + except: + var e = newException(YamlPresenterStreamError, "") + e.cause = getCurrentException() + raise e + safeWrite("...\x0A") proc transform*(input: Stream, output: Stream, style: YamlPresentationStyle, indentationStep: int = 2) = diff --git a/yaml.nim b/yaml.nim index a23906e..0d610a4 100644 --- a/yaml.nim +++ b/yaml.nim @@ -227,15 +227,27 @@ type ## Some elements in this list are vague. For a detailed description of a ## valid YAML character stream, see the YAML specification. - YamlPresenterError* = object of Exception - ## Exception that may be raised by the YAML presenter. Currently, the - ## only ocassion this exception may be raised is when the presenter is + YamlPresenterJsonError* = object of Exception + ## Exception that may be raised by the YAML presenter when it is ## instructed to output JSON, but is unable to do so. This may occur if: ## ## - The given `YamlStream <#YamlStream>`_ contains a map which has any ## non-scalar type as key. ## - Any float scalar bears a ``NaN`` or positive/negative infinity - ## value + ## value + + YamlPresenterOutputError* = object of Exception + ## Exception that may be raised by the YAML presenter. This occurs if + ## writing character data to the output stream raises any exception. The + ## exception that has been catched is retrievable from ``cause``. + cause*: ref Exception + + YamlPresenterStreamError* = object of Exception + ## Exception that may be raised by the YAML presenter. This occurs if + ## an exception is raised while retrieving the next item from a + ## `YamlStream <#YamlStream>`_. The exception that has been catched is + ## retrievable from ``cause``. + cause*: ref Exception const # failsafe schema @@ -372,8 +384,8 @@ proc parse*(parser: YamlSequentialParser, s: Stream): proc constructJson*(s: YamlStream): seq[JsonNode] ## Construct an in-memory JSON tree from a YAML event stream. The stream may - ## not contain any tags apart from those in ``coreTagLibrary``. Anchors and - ## aliases will be resolved. Maps in the input must not contain + ## not contain any tags apart from those in ``coreTagLibrary``. Anchors and + ## aliases will be resolved. Maps in the input must not contain ## non-scalars as keys. Each element of the result represents one document ## in the YAML stream. ## @@ -383,10 +395,17 @@ proc constructJson*(s: YamlStream): seq[JsonNode] ## of the JSON specification. Nim's JSON implementation currently does not ## check for these values and will output invalid JSON when rendering one ## of these values into a JSON character stream. + +proc loadToJson*(s: Stream): seq[JsonNode] + ## Uses `YamlSequentialParser <#YamlSequentialParser>`_ and + ## `constructJson <#constructJson>`_ to construct an in-memory JSON tree + ## from a YAML character stream. proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, style: YamlPresentationStyle = ypsDefault, - indentationStep: int = 2) + indentationStep: int = 2) {.raises: [YamlPresenterJsonError, + YamlPresenterOutputError, + YamlPresenterStreamError].} ## Convert ``s`` to a YAML character stream and write it to ``target``. proc transform*(input: Stream, output: Stream, style: YamlPresentationStyle,