Got rid of yamlWarning, use YamlPresenterError

* 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
This commit is contained in:
Felix Krause 2016-01-05 19:06:55 +01:00
parent d2baa1749e
commit 18753b1a4a
7 changed files with 95 additions and 66 deletions

View File

@ -23,9 +23,6 @@ proc `==`*(left: YamlStreamEvent, right: YamlStreamEvent): bool =
left.scalarType == right.scalarType left.scalarType == right.scalarType
of yamlAlias: of yamlAlias:
result = left.aliasTarget == right.aliasTarget 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 = proc `$`*(event: YamlStreamEvent): string =
result = $event.kind & '(' result = $event.kind & '('
@ -48,9 +45,6 @@ proc `$`*(event: YamlStreamEvent): string =
result &= ", content=\"" & event.scalarContent & '\"' result &= ", content=\"" & event.scalarContent & '\"'
of yamlAlias: of yamlAlias:
result &= "aliasTarget=" & $event.aliasTarget result &= "aliasTarget=" & $event.aliasTarget
of yamlWarning:
result &= "line=" & $event.line & ", column=" & $event.column
result &= ", description=\"" & event.description & '\"'
result &= ")" result &= ")"
proc startDocEvent*(): YamlStreamEvent = proc startDocEvent*(): YamlStreamEvent =

View File

@ -138,9 +138,6 @@ proc constructJson*(s: YamlStream): seq[JsonNode] =
discard # will never happen discard # will never happen
else: else:
discard # wait for yamlEndDocument discard # wait for yamlEndDocument
of yamlWarning:
echo "YAML warning at line ", event.line, ", column ", event.column,
": ", event.description
of yamlAlias: of yamlAlias:
# we can savely assume that the alias exists in anchors # we can savely assume that the alias exists in anchors
# (else the parser would have already thrown an exception) # (else the parser would have already thrown an exception)

View File

@ -57,6 +57,10 @@ proc newParser*(tagLib: YamlTagLibrary): YamlSequentialParser =
result.tagLib = tagLib result.tagLib = tagLib
result.anchors = initOrderedTable[string, AnchorId]() result.anchors = initOrderedTable[string, AnchorId]()
proc setWarningCallback*(parser: YamlSequentialParser,
callback: YamlWarningCallback) =
parser.callback = callback
proc anchor*(parser: YamlSequentialParser, id: AnchorId): string = proc anchor*(parser: YamlSequentialParser, id: AnchorId): string =
for pair in parser.anchors.pairs: for pair in parser.anchors.pairs:
if pair[1] == id: if pair[1] == id:
@ -64,8 +68,8 @@ proc anchor*(parser: YamlSequentialParser, id: AnchorId): string =
return nil return nil
template yieldWarning(d: string) {.dirty.} = template yieldWarning(d: string) {.dirty.} =
yield YamlStreamEvent(kind: yamlWarning, description: d, if parser.callback != nil:
line: lex.line, column: lex.column) parser.callback(lex.line, lex.column, lex.getCurrentLine(), d)
template raiseError(message: string) {.dirty.} = template raiseError(message: string) {.dirty.} =
var e = newException(YamlParserError, message) var e = newException(YamlParserError, message)

View File

@ -155,13 +155,22 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
writeTagAndAnchor(target, writeTagAndAnchor(target,
item.scalarTag, tagLib, item.scalarAnchor) item.scalarTag, tagLib, item.scalarAnchor)
if (style == ypsJson and item.scalarTag in [yTagQuestionMark, if style == ypsJson:
yTagBoolean] and if item.scalarTag in [yTagQuestionMark, yTagBoolean] and
item.scalarType in [yTypeBoolTrue, yTypeBoolFalse]): item.scalarType in [yTypeBoolTrue, yTypeBoolFalse]:
if item.scalarType == yTypeBoolTrue: if item.scalarType == yTypeBoolTrue:
target.write("true") target.write("true")
else: else:
target.write("false") 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(item.scalarContent)
elif style == ypsCanonical or item.scalarContent.needsEscaping or elif style == ypsCanonical or item.scalarContent.needsEscaping or
(style == ypsJson and (style == ypsJson and
(item.scalarTag notin [yTagQuestionMark, yTagInteger, yTagFloat, (item.scalarTag notin [yTagQuestionMark, yTagInteger, yTagFloat,
@ -173,9 +182,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
else: else:
target.write(item.scalarContent) target.write(item.scalarContent)
of yamlAlias: of yamlAlias:
if levels.len == 0: assert levels.len > 0
raise newException(ValueError, "Malformed YamlStream")
else:
startItem(target, style, indentation, levels[levels.high]) startItem(target, style, indentation, levels[levels.high])
target.write('*') target.write('*')
target.write(cast[byte]('a') + cast[byte](item.aliasTarget)) target.write(cast[byte]('a') + cast[byte](item.aliasTarget))
@ -186,8 +193,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
var length = 0 var length = 0
while true: while true:
let next = s() let next = s()
if finished(s): assert (not finished(s))
raise newException(ValueError, "Malformed YamlStream")
cached.enqueue(next) cached.enqueue(next)
case next.kind case next.kind
of yamlScalar: of yamlScalar:
@ -201,7 +207,13 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
break break
nextState = if length <= 60: dFlowSequenceStart else: nextState = if length <= 60: dFlowSequenceStart else:
dBlockSequenceItem 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 nextState = dFlowSequenceStart
of ypsBlockOnly: of ypsBlockOnly:
nextState = dBlockSequenceItem nextState = dBlockSequenceItem
@ -240,8 +252,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
var length = 0 var length = 0
while true: while true:
let next = s() let next = s()
if finished(s): assert (not finished(s))
raise newException(ValueError, "Malformed YamlStream")
cached.enqueue(next) cached.enqueue(next)
case next.kind case next.kind
of yamlScalar: of yamlScalar:
@ -262,6 +273,10 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
of ypsCanonical: of ypsCanonical:
nextState = dFlowExplicitMapStart nextState = dFlowExplicitMapStart
of ypsJson: of ypsJson:
if levels[levels.high] in
[dFlowImplicitMapStart, dFlowImplicitMapValue]:
raise newException(YamlPresenterError,
"Cannot have map as map key in JSON output!")
nextState = dFlowImplicitMapStart nextState = dFlowImplicitMapStart
of ypsBlockOnly: of ypsBlockOnly:
nextState = if item.mapMayHaveKeyObjects: nextState = if item.mapMayHaveKeyObjects:
@ -305,8 +320,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
levels.add(nextState) levels.add(nextState)
of yamlEndSequence: of yamlEndSequence:
if levels.len == 0: assert levels.len > 0
raise newException(ValueError, "Malformed YamlStream")
case levels.pop() case levels.pop()
of dFlowSequenceItem: of dFlowSequenceItem:
case style case style
@ -333,11 +347,10 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
of dBlockSequenceItem: of dBlockSequenceItem:
discard discard
else: else:
raise newException(ValueError, "Malformed YamlStream") assert false
indentation -= indentationStep indentation -= indentationStep
of yamlEndMap: of yamlEndMap:
if levels.len == 0: assert levels.len > 0
raise newException(ValueError, "Malformed YamlStream")
case levels.pop() case levels.pop()
of dFlowImplicitMapValue, dFlowExplicitMapValue: of dFlowImplicitMapValue, dFlowExplicitMapValue:
case style case style
@ -364,7 +377,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
of dBlockImplicitMapValue, dBlockExplicitMapValue: of dBlockImplicitMapValue, dBlockExplicitMapValue:
discard discard
else: else:
raise newException(ValueError, "Malformed YamlStream") assert false
indentation -= indentationStep indentation -= indentationStep
of yamlEndDocument: of yamlEndDocument:
let next = s() let next = s()
@ -372,8 +385,6 @@ proc present*(s: YamlStream, target: Stream, tagLib: YamlTagLibrary,
break break
target.write("...\x0A") target.write("...\x0A")
cached.enqueue(next) cached.enqueue(next)
of yamlWarning:
discard
proc transform*(input: Stream, output: Stream, style: YamlPresentationStyle, proc transform*(input: Stream, output: Stream, style: YamlPresentationStyle,
indentationStep: int = 2) = indentationStep: int = 2) =

View File

@ -48,8 +48,6 @@ proc alias(target: AnchorId): YamlStreamEvent =
proc printDifference(expected, actual: YamlStreamEvent) = proc printDifference(expected, actual: YamlStreamEvent) =
if expected.kind != actual.kind: if expected.kind != actual.kind:
echo "expected " & $expected.kind & ", got " & $actual.kind echo "expected " & $expected.kind & ", got " & $actual.kind
if actual.kind == yamlWarning:
echo "Warning message: " & actual.description
else: else:
case expected.kind case expected.kind
of yamlScalar: of yamlScalar:
@ -117,7 +115,7 @@ template ensure(input: string, expected: varargs[YamlStreamEvent]) {.dirty.} =
break break
i.inc() i.inc()
except YamlParserError: except YamlParserError:
let e = cast[YamlParserError](getCurrentException()) let e = cast[ref YamlParserError](getCurrentException())
echo "Parser error:", getCurrentExceptionMsg() echo "Parser error:", getCurrentExceptionMsg()
echo e.lineContent echo e.lineContent
fail() fail()

View File

@ -50,7 +50,7 @@ type
## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds ## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds
## are discussed in ``YamlStreamEvent``. ## are discussed in ``YamlStreamEvent``.
yamlStartDocument, yamlEndDocument, yamlStartMap, yamlEndMap, yamlStartDocument, yamlEndDocument, yamlStartMap, yamlEndMap,
yamlStartSequence, yamlEndSequence, yamlScalar, yamlAlias, yamlWarning yamlStartSequence, yamlEndSequence, yamlScalar, yamlAlias
TagId* = distinct int ## \ TagId* = distinct int ## \
## A ``TagId`` identifies a tag URI, like for example ## A ``TagId`` identifies a tag URI, like for example
@ -107,24 +107,13 @@ type
discard discard
of yamlAlias: of yamlAlias:
aliasTarget* : AnchorId aliasTarget* : AnchorId
of yamlWarning:
description* : string
line* : int
column* : int
YamlStream* = iterator(): YamlStreamEvent ## \ YamlStream* = iterator(): YamlStreamEvent ## \
## A ``YamlStream`` is an iterator that yields a well-formed stream of ## A ``YamlStream`` is an iterator that yields a well-formed stream of
## ``YamlStreamEvents``. Well-formed means that every ``yamlStartMap`` ## ``YamlStreamEvents``. Well-formed means that every ``yamlStartMap``
## is terminated by a ``yamlEndMap``, every ``yamlStartSequence`` is ## is terminated by a ``yamlEndMap``, every ``yamlStartSequence`` is
## terminated by a ``yamlEndSequence`` and every ``yamlStartDocument`` ## terminated by a ``yamlEndSequence`` and every ``yamlStartDocument``
## is terminated by a ``yamlEndDocument``. The only exception to this ## is terminated by a ``yamlEndDocument``.
## 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.
## ##
## The creator of a ``YamlStream`` is responsible for it being ## The creator of a ``YamlStream`` is responsible for it being
## well-formed. A user of the stream may assume that it is well-formed ## well-formed. A user of the stream may assume that it is well-formed
@ -157,6 +146,15 @@ type
nextCustomTagId*: TagId nextCustomTagId*: TagId
secondaryPrefix*: string 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 YamlSequentialParser* = ref object
## A parser object. Retains its ``YamlTagLibrary`` across calls to ## A parser object. Retains its ``YamlTagLibrary`` across calls to
## `parse <#parse,YamlSequentialParser,Stream,YamlStream>`_. Can be used ## `parse <#parse,YamlSequentialParser,Stream,YamlStream>`_. Can be used
@ -165,6 +163,7 @@ type
## ``yamlEndDocument`` is yielded). ## ``yamlEndDocument`` is yielded).
tagLib: YamlTagLibrary tagLib: YamlTagLibrary
anchors: OrderedTable[string, AnchorId] anchors: OrderedTable[string, AnchorId]
callback: YamlWarningCallback
YamlPresentationStyle* = enum YamlPresentationStyle* = enum
## Different styles for YAML character stream output. ## Different styles for YAML character stream output.
@ -186,7 +185,18 @@ type
## flow style at all. ## flow style at all.
ypsMinimal, ypsCanonical, ypsDefault, ypsJson, ypsBlockOnly 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 ## 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 ## 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 ## 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 ## Some elements in this list are vague. For a detailed description of a
## valid YAML character stream, see the YAML specification. ## 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 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 const
# failsafe schema # failsafe schema
@ -350,6 +360,9 @@ proc extendedTagLibrary*(): YamlTagLibrary
proc newParser*(tagLib: YamlTagLibrary): YamlSequentialParser proc newParser*(tagLib: YamlTagLibrary): YamlSequentialParser
## Instanciates a parser ## Instanciates a parser
proc setWarningCallback*(parser: YamlSequentialParser,
callback: YamlWarningCallback)
proc anchor*(parser: YamlSequentialParser, id: AnchorId): string proc anchor*(parser: YamlSequentialParser, id: AnchorId): string
## Get the anchor name which an ``AnchorId`` maps to ## Get the anchor name which an ``AnchorId`` maps to

View File

@ -233,7 +233,7 @@ proc safeTagUri*(id: TagId): string =
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:
raise newException(ValueError, "Construction error!" & $item.description) raise newException(ValueError, "Construction error!")
if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark, yTagString]: if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark, yTagString]:
raise newException(ValueError, "Wrong tag for string.") raise newException(ValueError, "Wrong tag for string.")
result = item.scalarContent result = item.scalarContent
@ -306,15 +306,27 @@ proc construct*(s: YamlStream, result: var float) =
proc serialize*(value: float, proc serialize*(value: float,
tagStyle: YamlTagStyle = ytsNone): YamlStream = tagStyle: YamlTagStyle = ytsNone): YamlStream =
result = iterator(): YamlStreamEvent = result = iterator(): YamlStreamEvent =
var asString = case value var
of Inf: ".inf" asString: string
of NegInf: "-.inf" hint: YamlTypeHint
of NaN: ".nan" case value
else: $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, yield YamlStreamEvent(kind: yamlScalar,
scalarTag: presentTag(float, tagStyle), scalarTag: presentTag(float, tagStyle),
scalarAnchor: yAnchorNone, scalarContent: asString) scalarAnchor: yAnchorNone, scalarContent: asString,
scalarType: hint)
proc yamlTag*(T: typedesc[bool]): TagId {.inline.} = yTagBoolean proc yamlTag*(T: typedesc[bool]): TagId {.inline.} = yTagBoolean