diff --git a/private/events.nim b/private/events.nim index 35b0206..8588f65 100644 --- a/private/events.nim +++ b/private/events.nim @@ -23,9 +23,6 @@ proc `==`*(left: YamlStreamEvent, right: YamlStreamEvent): bool = left.scalarType == right.scalarType of yamlAlias: result = left.aliasTarget == right.aliasTarget - of yamlWarning: - result = left.description == right.description and - left.line == right.line and left.column == right.column proc `$`*(event: YamlStreamEvent): string = result = $event.kind & '(' @@ -48,9 +45,6 @@ proc `$`*(event: YamlStreamEvent): string = result &= ", content=\"" & event.scalarContent & '\"' of yamlAlias: result &= "aliasTarget=" & $event.aliasTarget - of yamlWarning: - result &= "line=" & $event.line & ", column=" & $event.column - result &= ", description=\"" & event.description & '\"' result &= ")" proc startDocEvent*(): YamlStreamEvent = diff --git a/private/json.nim b/private/json.nim index c75be8d..854d859 100644 --- a/private/json.nim +++ b/private/json.nim @@ -138,9 +138,6 @@ proc constructJson*(s: YamlStream): seq[JsonNode] = discard # will never happen else: discard # wait for yamlEndDocument - of yamlWarning: - echo "YAML warning at line ", event.line, ", column ", event.column, - ": ", event.description of yamlAlias: # we can savely assume that the alias exists in anchors # (else the parser would have already thrown an exception) diff --git a/private/parser.nim b/private/parser.nim index fbe607b..0ab506f 100644 --- a/private/parser.nim +++ b/private/parser.nim @@ -57,6 +57,10 @@ proc newParser*(tagLib: YamlTagLibrary): YamlSequentialParser = result.tagLib = tagLib result.anchors = initOrderedTable[string, AnchorId]() +proc setWarningCallback*(parser: YamlSequentialParser, + callback: YamlWarningCallback) = + parser.callback = callback + proc anchor*(parser: YamlSequentialParser, id: AnchorId): string = for pair in parser.anchors.pairs: if pair[1] == id: @@ -64,8 +68,8 @@ proc anchor*(parser: YamlSequentialParser, id: AnchorId): string = return nil template yieldWarning(d: string) {.dirty.} = - yield YamlStreamEvent(kind: yamlWarning, description: d, - line: lex.line, column: lex.column) + if parser.callback != nil: + parser.callback(lex.line, lex.column, lex.getCurrentLine(), d) template raiseError(message: string) {.dirty.} = var e = newException(YamlParserError, message) diff --git a/private/presenter.nim b/private/presenter.nim index 0af03ae..b69de45 100644 --- a/private/presenter.nim +++ b/private/presenter.nim @@ -155,13 +155,22 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, writeTagAndAnchor(target, item.scalarTag, tagLib, item.scalarAnchor) - if (style == ypsJson and item.scalarTag in [yTagQuestionMark, - yTagBoolean] and - item.scalarType in [yTypeBoolTrue, yTypeBoolFalse]): - if item.scalarType == yTypeBoolTrue: - target.write("true") + if style == ypsJson: + if item.scalarTag in [yTagQuestionMark, yTagBoolean] and + item.scalarType in [yTypeBoolTrue, yTypeBoolFalse]: + if item.scalarType == yTypeBoolTrue: + target.write("true") + else: + target.write("false") + elif item.scalarTag in [yTagQuestionMark, yTagNull] and + item.scalarType == yTypeNull: + target.write("null") + elif item.scalarTag in [yTagQuestionMark, yTagFloat] and + item.scalarType in [yTypeFloatInf, yTypeFloatNaN]: + raise newException(YamlPresenterError, + "Infinity and not-a-number values cannot be presented as JSON!") else: - target.write("false") + target.write(item.scalarContent) elif style == ypsCanonical or item.scalarContent.needsEscaping or (style == ypsJson and (item.scalarTag notin [yTagQuestionMark, yTagInteger, yTagFloat, @@ -173,10 +182,8 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, else: target.write(item.scalarContent) of yamlAlias: - if levels.len == 0: - raise newException(ValueError, "Malformed YamlStream") - else: - startItem(target, style, indentation, levels[levels.high]) + assert levels.len > 0 + startItem(target, style, indentation, levels[levels.high]) target.write('*') target.write(cast[byte]('a') + cast[byte](item.aliasTarget)) of yamlStartSequence: @@ -186,8 +193,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, var length = 0 while true: let next = s() - if finished(s): - raise newException(ValueError, "Malformed YamlStream") + assert (not finished(s)) cached.enqueue(next) case next.kind of yamlScalar: @@ -201,7 +207,13 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, break nextState = if length <= 60: dFlowSequenceStart else: dBlockSequenceItem - of ypsMinimal, ypsJson, ypsCanonical: + of ypsJson: + if levels[levels.high] in + [dFlowImplicitMapStart, dFlowImplicitMapValue]: + raise newException(YamlPresenterError, + "Cannot have sequence as map key in JSON output!") + nextState = dFlowSequenceStart + of ypsMinimal, ypsCanonical: nextState = dFlowSequenceStart of ypsBlockOnly: nextState = dBlockSequenceItem @@ -240,8 +252,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, var length = 0 while true: let next = s() - if finished(s): - raise newException(ValueError, "Malformed YamlStream") + assert (not finished(s)) cached.enqueue(next) case next.kind of yamlScalar: @@ -262,6 +273,10 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of ypsCanonical: nextState = dFlowExplicitMapStart of ypsJson: + if levels[levels.high] in + [dFlowImplicitMapStart, dFlowImplicitMapValue]: + raise newException(YamlPresenterError, + "Cannot have map as map key in JSON output!") nextState = dFlowImplicitMapStart of ypsBlockOnly: nextState = if item.mapMayHaveKeyObjects: @@ -305,8 +320,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, levels.add(nextState) of yamlEndSequence: - if levels.len == 0: - raise newException(ValueError, "Malformed YamlStream") + assert levels.len > 0 case levels.pop() of dFlowSequenceItem: case style @@ -333,11 +347,10 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of dBlockSequenceItem: discard else: - raise newException(ValueError, "Malformed YamlStream") + assert false indentation -= indentationStep of yamlEndMap: - if levels.len == 0: - raise newException(ValueError, "Malformed YamlStream") + assert levels.len > 0 case levels.pop() of dFlowImplicitMapValue, dFlowExplicitMapValue: case style @@ -364,7 +377,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, of dBlockImplicitMapValue, dBlockExplicitMapValue: discard else: - raise newException(ValueError, "Malformed YamlStream") + assert false indentation -= indentationStep of yamlEndDocument: let next = s() @@ -372,8 +385,6 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary, break target.write("...\x0A") cached.enqueue(next) - of yamlWarning: - discard proc transform*(input: Stream, output: Stream, style: YamlPresentationStyle, indentationStep: int = 2) = diff --git a/test/parsing.nim b/test/parsing.nim index 47d56fc..3c9a5f1 100644 --- a/test/parsing.nim +++ b/test/parsing.nim @@ -48,8 +48,6 @@ proc alias(target: AnchorId): YamlStreamEvent = proc printDifference(expected, actual: YamlStreamEvent) = if expected.kind != actual.kind: echo "expected " & $expected.kind & ", got " & $actual.kind - if actual.kind == yamlWarning: - echo "Warning message: " & actual.description else: case expected.kind of yamlScalar: @@ -117,7 +115,7 @@ template ensure(input: string, expected: varargs[YamlStreamEvent]) {.dirty.} = break i.inc() except YamlParserError: - let e = cast[YamlParserError](getCurrentException()) + let e = cast[ref YamlParserError](getCurrentException()) echo "Parser error:", getCurrentExceptionMsg() echo e.lineContent fail() diff --git a/yaml.nim b/yaml.nim index f946890..a23906e 100644 --- a/yaml.nim +++ b/yaml.nim @@ -50,7 +50,7 @@ type ## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds ## are discussed in ``YamlStreamEvent``. yamlStartDocument, yamlEndDocument, yamlStartMap, yamlEndMap, - yamlStartSequence, yamlEndSequence, yamlScalar, yamlAlias, yamlWarning + yamlStartSequence, yamlEndSequence, yamlScalar, yamlAlias TagId* = distinct int ## \ ## A ``TagId`` identifies a tag URI, like for example @@ -107,24 +107,13 @@ type discard of yamlAlias: aliasTarget* : AnchorId - of yamlWarning: - description* : string - line* : int - column* : int YamlStream* = iterator(): YamlStreamEvent ## \ ## A ``YamlStream`` is an iterator that yields a well-formed stream of ## ``YamlStreamEvents``. Well-formed means that every ``yamlStartMap`` ## is terminated by a ``yamlEndMap``, every ``yamlStartSequence`` is ## terminated by a ``yamlEndSequence`` and every ``yamlStartDocument`` - ## is terminated by a ``yamlEndDocument``. The only exception to this - ## rule is a ``yamlError``, which may occur anywhere in the stream and - ## must be the last element in the stream, which may leave any number of - ## objects open. - ## - ## A ``yamlWarning`` may also occur anywhere in the stream, but will not - ## invalidate the structure of the event stream, and may not abruptly - ## end the stream as ``yamlError`` does. + ## is terminated by a ``yamlEndDocument``. ## ## The creator of a ``YamlStream`` is responsible for it being ## well-formed. A user of the stream may assume that it is well-formed @@ -157,6 +146,15 @@ type nextCustomTagId*: TagId secondaryPrefix*: string + YamlWarningCallback* = proc(line, column: int, lineContent: string, + message: string) + ## Callback for parser warnings. Currently, this callback may be called + ## on two occasions while parsing a YAML document stream: + ## + ## - If the version number in the ``%YAML`` directive does not match + ## ``1.2``. + ## - If there is an unknown directive encountered. + YamlSequentialParser* = ref object ## A parser object. Retains its ``YamlTagLibrary`` across calls to ## `parse <#parse,YamlSequentialParser,Stream,YamlStream>`_. Can be used @@ -165,6 +163,7 @@ type ## ``yamlEndDocument`` is yielded). tagLib: YamlTagLibrary anchors: OrderedTable[string, AnchorId] + callback: YamlWarningCallback YamlPresentationStyle* = enum ## Different styles for YAML character stream output. @@ -186,7 +185,18 @@ type ## flow style at all. ypsMinimal, ypsCanonical, ypsDefault, ypsJson, ypsBlockOnly - YamlParserError* = object of Exception + YamlLoadingError* = object of Exception + ## Base class for all exceptions that may be raised during the process + ## of loading a YAML character stream. + line*: int ## line number (1-based) where the error was encountered + column*: int ## \ + ## column number (1-based) where the error was encountered + lineContent*: string ## \ + ## content of the line where the error was encountered. Includes a + ## second line with a marker ``^`` at the position where the error + ## was encountered, as returned by ``lexbase.getCurrentLine``. + + YamlParserError* = object of YamlLoadingError ## A parser error is raised if the character stream that is parsed is ## not a valid YAML character stream. This stream cannot and will not be ## parsed wholly nor partially and all events that have been emitted by @@ -216,16 +226,16 @@ type ## ## Some elements in this list are vague. For a detailed description of a ## valid YAML character stream, see the YAML specification. - line*: int ## line number (1-based) where the error was encountered - column*: int ## \ - ## column number (1-based) where the error was encountered - lineContent*: string ## \ - ## content of the line where the error was encountered. Includes a - ## second line with a marker ``^`` at the position where the error - ## was encountered, as returned by ``lexbase.getCurrentLine``. YamlPresenterError* = object of Exception - ## Exception that may be raised by the YAML presenter. + ## Exception that may be raised by the YAML presenter. Currently, the + ## only ocassion this exception may be raised is when the presenter 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 const # failsafe schema @@ -350,6 +360,9 @@ proc extendedTagLibrary*(): YamlTagLibrary proc newParser*(tagLib: YamlTagLibrary): YamlSequentialParser ## Instanciates a parser +proc setWarningCallback*(parser: YamlSequentialParser, + callback: YamlWarningCallback) + proc anchor*(parser: YamlSequentialParser, id: AnchorId): string ## Get the anchor name which an ``AnchorId`` maps to diff --git a/yaml/serialization.nim b/yaml/serialization.nim index a93a28e..a9cf00b 100644 --- a/yaml/serialization.nim +++ b/yaml/serialization.nim @@ -233,7 +233,7 @@ proc safeTagUri*(id: TagId): string = proc construct*(s: YamlStream, result: var string) = let item = s() if finished(s) or item.kind != yamlScalar: - raise newException(ValueError, "Construction error!" & $item.description) + raise newException(ValueError, "Construction error!") if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark, yTagString]: raise newException(ValueError, "Wrong tag for string.") result = item.scalarContent @@ -306,15 +306,27 @@ proc construct*(s: YamlStream, result: var float) = proc serialize*(value: float, tagStyle: YamlTagStyle = ytsNone): YamlStream = result = iterator(): YamlStreamEvent = - var asString = case value - of Inf: ".inf" - of NegInf: "-.inf" - of NaN: ".nan" - else: $value + 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) + scalarAnchor: yAnchorNone, scalarContent: asString, + scalarType: hint) proc yamlTag*(T: typedesc[bool]): TagId {.inline.} = yTagBoolean