Sanitized exceptions and raises pragmas

* Added raises pragma to every proc in yaml.nim
 * Properly encapsulate exceptions from YamlStreams
 * Discovered a Nim compiler bug and added a workaround
 * Added possibility to query line, column and line content from
   YamlParser
 * Updated rst documentation
 * Actually call callback in fastparse if available
 * Fixed parsing YAML version
This commit is contained in:
Felix Krause 2016-01-24 18:24:09 +01:00
parent d05971d5ac
commit a08f4c1e4e
7 changed files with 251 additions and 136 deletions

View File

@ -208,11 +208,8 @@ Output:
* Documentation: * Documentation:
- Document yaml.serialization - Document yaml.serialization
- Setup github pages site with proper API documentation
* Lexer: * Lexer:
- Add type hints for more scalar types - Add type hints for more scalar types
* Parser:
- Properly handle leading spaces in block scalars
* Serialization: * Serialization:
- Support for more standard library types - Support for more standard library types
- Support for ref and ptr types - Support for ref and ptr types

View File

@ -44,6 +44,15 @@ proc newYamlParser*(tagLib: TagLibrary = initExtendedTagLibrary(),
result.tagLib = tagLib result.tagLib = tagLib
result.callback = callback result.callback = callback
proc getLineNumber*(p: YamlParser): int = p.lexer.lineNumber
proc getColNumber*(p: YamlParser): int = p.tokenstart + 1 # column is 1-based
proc getLineContent*(p: YamlParser, marker: bool = true): string =
result = p.lexer.getCurrentLine(false)
if marker:
result.add(repeat(' ', p.tokenstart) & "^\n")
template debug(message: string) {.dirty.} = template debug(message: string) {.dirty.} =
when defined(yamlDebug): when defined(yamlDebug):
try: styledWriteLine(stdout, fgBlue, message) try: styledWriteLine(stdout, fgBlue, message)
@ -52,15 +61,14 @@ template debug(message: string) {.dirty.} =
template parserError(message: string) {.dirty.} = template parserError(message: string) {.dirty.} =
var e = newException(YamlParserError, message) var e = newException(YamlParserError, message)
e.line = p.lexer.lineNumber e.line = p.lexer.lineNumber
e.column = p.tokenstart e.column = p.tokenstart + 1
e.lineContent = p.lexer.getCurrentLine(false) & e.lineContent = p.getLineContent(true)
repeat(' ', p.tokenstart) & "^\n"
raise e raise e
template lexerError(lx: BaseLexer, message: string) {.dirty.} = template lexerError(lx: BaseLexer, message: string) {.dirty.} =
var e = newException(YamlParserError, message) var e = newException(YamlParserError, message)
e.line = lx.lineNumber e.line = lx.lineNumber
e.column = lx.bufpos e.column = lx.bufpos + 1
e.lineContent = lx.getCurrentLine(false) & e.lineContent = lx.getCurrentLine(false) &
repeat(' ', lx.getColNumber(lx.bufpos)) & "^\n" repeat(' ', lx.getColNumber(lx.bufpos)) & "^\n"
raise e raise e
@ -408,12 +416,18 @@ template yamlVersion(lexer: BaseLexer, o: var string) =
c = lexer.buf[lexer.bufpos] c = lexer.buf[lexer.bufpos]
if lexer.buf[lexer.bufpos] != '.': if lexer.buf[lexer.bufpos] != '.':
lexerError(lexer, "Invalid YAML version number") lexerError(lexer, "Invalid YAML version number")
o.add('.')
lexer.bufpos.inc() lexer.bufpos.inc()
if lexer.buf[lexer.bufpos] notin digits: c = lexer.buf[lexer.bufpos]
if c notin digits:
lexerError(lexer, "Invalid YAML version number") lexerError(lexer, "Invalid YAML version number")
o.add(c)
lexer.bufpos.inc() lexer.bufpos.inc()
while lexer.buf[lexer.bufpos] in digits: c = lexer.buf[lexer.bufpos]
while c in digits:
o.add(c)
lexer.bufpos.inc() lexer.bufpos.inc()
c = lexer.buf[lexer.bufpos]
if lexer.buf[lexer.bufpos] notin spaceOrLineEnd: if lexer.buf[lexer.bufpos] notin spaceOrLineEnd:
lexerError(lexer, "Invalid YAML version number") lexerError(lexer, "Invalid YAML version number")
@ -1006,8 +1020,10 @@ proc parse*(p: YamlParser, s: Stream): YamlStream =
startToken() startToken()
p.lexer.yamlVersion(version) p.lexer.yamlVersion(version)
if version != "1.2": if version != "1.2":
echo "version is not 1.2!" if p.callback != nil:
# TODO: warning (unknown version) p.callback(p.lexer.lineNumber, p.getColNumber(),
p.getLineContent(),
"Version is not 1.2, but " & version)
discard discard
p.lexer.lineEnding() p.lexer.lineEnding()
handleLineEnd(false) handleLineEnd(false)
@ -1020,7 +1036,9 @@ proc parse*(p: YamlParser, s: Stream): YamlStream =
p.lexer.lineEnding() p.lexer.lineEnding()
handleLineEnd(false) handleLineEnd(false)
of ldUnknown: of ldUnknown:
# TODO: warning (unknown directive) if p.callback != nil:
p.callback(p.lexer.lineNumber, p.getColNumber(),
p.getLineContent(), "Unknown directive")
p.lexer.finishLine() p.lexer.finishLine()
handleLineEnd(false) handleLineEnd(false)
of ' ', '\t': of ' ', '\t':

View File

@ -62,11 +62,11 @@ macro typeHintStateMachine(c: untyped, content: untyped): stmt =
newIdentNode("typeHintState"), copyNimTree(rule[2])))) newIdentNode("typeHintState"), copyNimTree(rule[2]))))
stateBranches.add(stateBranch) stateBranches.add(stateBranch)
stateBranches.add(newNimNode(nnkElse).add(newStmtList( stateBranches.add(newNimNode(nnkElse).add(newStmtList(
newNimNode(nnkReturnStmt).add(newIdentNode("yTypeString"))))) newNimNode(nnkReturnStmt).add(newIdentNode("yTypeUnknown")))))
charBranch.add(newStmtList(stateBranches)) charBranch.add(newStmtList(stateBranches))
result.add(charBranch) result.add(charBranch)
result.add(newNimNode(nnkElse).add(newStmtList( result.add(newNimNode(nnkElse).add(newStmtList(
newNimNode(nnkReturnStmt).add(newIdentNode("yTypeString"))))) newNimNode(nnkReturnStmt).add(newIdentNode("yTypeUnknown")))))
template advanceTypeHint(ch: char) {.dirty.} = template advanceTypeHint(ch: char) {.dirty.} =
typeHintStateMachine ch: typeHintStateMachine ch:

View File

@ -7,9 +7,11 @@
type type
Level = tuple[node: JsonNode, key: string] Level = tuple[node: JsonNode, key: string]
proc initLevel(node: JsonNode): Level = (node: node, key: cast[string](nil)) proc initLevel(node: JsonNode): Level {.raises: [].} =
(node: node, key: cast[string](nil))
proc jsonFromScalar(content: string, tag: TagId): JsonNode = proc jsonFromScalar(content: string, tag: TagId): JsonNode
{.raises: [YamlConstructionError].}=
new(result) new(result)
var mappedType: TypeHint var mappedType: TypeHint
@ -17,7 +19,7 @@ proc jsonFromScalar(content: string, tag: TagId): JsonNode =
of yTagQuestionMark: of yTagQuestionMark:
mappedType = guessType(content) mappedType = guessType(content)
of yTagExclamationMark, yTagString: of yTagExclamationMark, yTagString:
mappedType = yTypeString mappedType = yTypeUnknown
of yTagBoolean: of yTagBoolean:
case guessType(content) case guessType(content)
of yTypeBoolTrue: of yTypeBoolTrue:
@ -25,41 +27,56 @@ proc jsonFromScalar(content: string, tag: TagId): JsonNode =
of yTypeBoolFalse: of yTypeBoolFalse:
mappedType = yTypeBoolFalse mappedType = yTypeBoolFalse
else: else:
raise newException(ValueError, "Invalid boolean value: " & content) raise newException(YamlConstructionError,
"Invalid boolean value: " & content)
of yTagInteger: of yTagInteger:
mappedType = yTypeInteger mappedType = yTypeInteger
of yTagNull: of yTagNull:
mappedType = yTypeNull mappedType = yTypeNull
of yTagFloat: of yTagFloat:
mappedType = yTypeFloat case guessType(content)
## TODO: NaN, inf of yTypeFloat:
mappedType = yTypeFloat
of yTypeFloatInf:
mappedType = yTypeFloatInf
of yTypeFloatNaN:
mappedType = yTypeFloatNaN
else:
raise newException(YamlConstructionError,
"Invalid float value: " & content)
else: else:
mappedType = yTypeUnknown mappedType = yTypeUnknown
case mappedType try:
of yTypeInteger: case mappedType
result.kind = JInt of yTypeInteger:
result.num = parseBiggestInt(content) result.kind = JInt
of yTypeFloat: result.num = parseBiggestInt(content)
result.kind = JFloat of yTypeFloat:
result.fnum = parseFloat(content) result.kind = JFloat
of yTypeFloatInf: result.fnum = parseFloat(content)
result.kind = JFloat of yTypeFloatInf:
result.fnum = if content[0] == '-': NegInf else: Inf result.kind = JFloat
of yTypeFloatNaN: result.fnum = if content[0] == '-': NegInf else: Inf
result.kind = JFloat of yTypeFloatNaN:
result.fnum = NaN result.kind = JFloat
of yTypeBoolTrue: result.fnum = NaN
result.kind = JBool of yTypeBoolTrue:
result.bval = true result.kind = JBool
of yTypeBoolFalse: result.bval = true
result.kind = JBool of yTypeBoolFalse:
result.bval = false result.kind = JBool
of yTypeNull: result.bval = false
result.kind = JNull of yTypeNull:
else: result.kind = JNull
result.kind = JString else:
result.str = content result.kind = JString
result.str = content
except ValueError:
var e = newException(YamlConstructionError,
"Cannot parse numeric value")
e.parent = getCurrentException()
raise e
proc constructJson*(s: YamlStream): seq[JsonNode] = proc constructJson*(s: YamlStream): seq[JsonNode] =
newSeq(result, 0) newSeq(result, 0)
@ -67,8 +84,19 @@ proc constructJson*(s: YamlStream): seq[JsonNode] =
var var
levels = newSeq[Level]() levels = newSeq[Level]()
anchors = initTable[AnchorId, JsonNode]() anchors = initTable[AnchorId, JsonNode]()
safeIter = iterator(): YamlStreamEvent
for event in s(): {.raises: [YamlConstructionStreamError].} =
while true:
var item: YamlStreamEvent
try:
item = s()
if finished(s): break
except Exception:
var e = newException(YamlConstructionStreamError, "")
e.parent = getCurrentException()
raise e
yield item
for event in safeIter():
case event.kind case event.kind
of yamlStartDocument: of yamlStartDocument:
# we don't need to do anything here; root node will be created # we don't need to do anything here; root node will be created
@ -104,7 +132,7 @@ proc constructJson*(s: YamlStream): seq[JsonNode] =
# JSON only allows strings as keys # JSON only allows strings as keys
levels[levels.high].key = event.scalarContent levels[levels.high].key = event.scalarContent
if event.scalarAnchor != yAnchorNone: if event.scalarAnchor != yAnchorNone:
raise newException(ValueError, raise newException(YamlConstructionError,
"scalar keys may not have anchors in JSON") "scalar keys may not have anchors in JSON")
else: else:
let jsonScalar = jsonFromScalar(event.scalarContent, let jsonScalar = jsonFromScalar(event.scalarContent,
@ -125,7 +153,7 @@ proc constructJson*(s: YamlStream): seq[JsonNode] =
of JObject: of JObject:
if isNil(levels[levels.high].key): if isNil(levels[levels.high].key):
echo level.node.pretty() echo level.node.pretty()
raise newException(ValueError, raise newException(YamlConstructionError,
"non-scalar as key not allowed in JSON") "non-scalar as key not allowed in JSON")
else: else:
levels[levels.high].node.fields.add( levels[levels.high].node.fields.add(
@ -140,15 +168,26 @@ proc constructJson*(s: YamlStream): seq[JsonNode] =
# (else the parser would have already thrown an exception) # (else the parser would have already thrown an exception)
case levels[levels.high].node.kind case levels[levels.high].node.kind
of JArray: of JArray:
levels[levels.high].node.elems.add(anchors[event.aliasTarget]) try:
levels[levels.high].node.elems.add(
anchors[event.aliasTarget])
except KeyError:
# we can safely assume that this doesn't happen. It would
# have resulted in a parser error earlier.
assert(false)
of JObject: of JObject:
if isNil(levels[levels.high].key): if isNil(levels[levels.high].key):
raise newException(ValueError, raise newException(YamlConstructionError,
"cannot use alias node as key in JSON") "cannot use alias node as key in JSON")
else: else:
levels[levels.high].node.fields.add( try:
(key: levels[levels.high].key, levels[levels.high].node.fields.add(
val: anchors[event.aliasTarget])) (key: levels[levels.high].key,
val: anchors[event.aliasTarget]))
except KeyError:
# we can safely assume that this doesn't happen. It would
# have resulted in a parser error earlier.
assert(false)
levels[levels.high].key = nil levels[levels.high].key = nil
else: else:
discard # will never happen discard # will never happen
@ -157,4 +196,23 @@ proc loadToJson*(s: Stream): seq[JsonNode] =
var var
parser = newYamlParser(initCoreTagLibrary()) parser = newYamlParser(initCoreTagLibrary())
events = parser.parse(s) events = parser.parse(s)
return constructJson(events) try:
return constructJson(events)
except YamlConstructionError:
var e = cast[ref YamlConstructionError](getCurrentException())
e.line = parser.getLineNumber()
e.column = parser.getColNumber()
e.lineContent = parser.getLineContent()
raise e
except YamlConstructionStreamError:
let e = getCurrentException()
if e.parent is IOError:
raise cast[ref IOError](e.parent)
elif e.parent is YamlParserError:
raise cast[ref YamlParserError](e.parent)
else:
# can never happen
assert(false)
except Exception:
# compiler bug: https://github.com/nim-lang/Nim/issues/3772
assert(false)

View File

@ -27,7 +27,7 @@ proc writeDoubleQuoted(scalar: string, s: Stream)
s.write('"') s.write('"')
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
template safeWrite(s: string or char) {.dirty.} = template safeWrite(s: string or char) {.dirty.} =
@ -35,7 +35,7 @@ template safeWrite(s: string or char) {.dirty.} =
target.write(s) target.write(s)
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
proc startItem(target: Stream, style: PresentationStyle, indentation: int, proc startItem(target: Stream, style: PresentationStyle, indentation: int,
@ -121,7 +121,7 @@ proc startItem(target: Stream, style: PresentationStyle, indentation: int,
discard # can never happen discard # can never happen
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
proc writeTagAndAnchor(target: Stream, tag: TagId, tagLib: TagLibrary, proc writeTagAndAnchor(target: Stream, tag: TagId, tagLib: TagLibrary,
@ -147,7 +147,7 @@ proc writeTagAndAnchor(target: Stream, tag: TagId, tagLib: TagLibrary,
target.write(' ') target.write(' ')
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary, proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
@ -166,7 +166,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
cached.enqueue(item) cached.enqueue(item)
except: except:
var e = newException(YamlPresenterStreamError, "") var e = newException(YamlPresenterStreamError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
indentation = 0 indentation = 0
levels = newSeq[DumperState]() levels = newSeq[DumperState]()
@ -184,7 +184,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
target.write("--- ") target.write("--- ")
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
of yamlScalar: of yamlScalar:
if levels.len == 0: if levels.len == 0:
@ -229,7 +229,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
target.write(cast[byte]('a') + cast[byte](item.aliasTarget)) target.write(cast[byte]('a') + cast[byte](item.aliasTarget))
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
of yamlStartSequence: of yamlStartSequence:
var nextState: DumperState var nextState: DumperState
@ -253,7 +253,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
break break
except: except:
var e = newException(YamlPresenterStreamError, "") var e = newException(YamlPresenterStreamError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
nextState = if length <= 60: dFlowSequenceStart else: nextState = if length <= 60: dFlowSequenceStart else:
dBlockSequenceItem dBlockSequenceItem
@ -318,7 +318,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
mps = mpNeedBlock mps = mpNeedBlock
except: except:
var e = newException(YamlPresenterStreamError, "") var e = newException(YamlPresenterStreamError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
nextState = if mps == mpNeedBlock: dBlockMapValue else: nextState = if mps == mpNeedBlock: dBlockMapValue else:
dBlockInlineMap dBlockInlineMap
@ -384,7 +384,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
target.write(']') target.write(']')
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
if levels.len == 0 or levels[levels.high] notin if levels.len == 0 or levels[levels.high] notin
[dBlockExplicitMapKey, dBlockMapValue, [dBlockExplicitMapKey, dBlockMapValue,
@ -418,7 +418,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
target.write('}') target.write('}')
except: except:
var e = newException(YamlPresenterOutputError, "") var e = newException(YamlPresenterOutputError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
if levels.len == 0 or levels[levels.high] notin if levels.len == 0 or levels[levels.high] notin
[dBlockExplicitMapKey, dBlockMapValue, [dBlockExplicitMapKey, dBlockMapValue,
@ -444,7 +444,7 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
cached.enqueue(next) cached.enqueue(next)
except: except:
var e = newException(YamlPresenterStreamError, "") var e = newException(YamlPresenterStreamError, "")
e.cause = getCurrentException() e.parent = getCurrentException()
raise e raise e
safeWrite("...\x0A") safeWrite("...\x0A")
@ -454,37 +454,52 @@ proc transform*(input: Stream, output: Stream, style: PresentationStyle,
taglib = initExtendedTagLibrary() taglib = initExtendedTagLibrary()
parser = newYamlParser(tagLib) parser = newYamlParser(tagLib)
events = parser.parse(input) events = parser.parse(input)
if style == psCanonical: try:
var specificTagEvents = iterator(): YamlStreamEvent = if style == psCanonical:
for e in events(): var specificTagEvents = iterator(): YamlStreamEvent =
var event = e for e in events():
case event.kind var event = e
of yamlStartDocument, yamlEndDocument, yamlEndMap, yamlAlias, case event.kind
yamlEndSequence: of yamlStartDocument, yamlEndDocument, yamlEndMap,
discard yamlAlias, yamlEndSequence:
of yamlStartMap: discard
if event.mapTag in [yTagQuestionMark, yTagExclamationMark]: of yamlStartMap:
event.mapTag = yTagMap if event.mapTag in [yTagQuestionMark,
of yamlStartSequence: yTagExclamationMark]:
if event.seqTag in [yTagQuestionMark, yTagExclamationMark]: event.mapTag = yTagMap
event.seqTag = yTagSequence of yamlStartSequence:
of yamlScalar: if event.seqTag in [yTagQuestionMark,
if event.scalarTag == yTagQuestionMark: yTagExclamationMark]:
case guessType(event.scalarContent) event.seqTag = yTagSequence
of yTypeInteger: of yamlScalar:
event.scalarTag = yTagInteger if event.scalarTag == yTagQuestionMark:
of yTypeFloat, yTypeFloatInf, yTypeFloatNaN: case guessType(event.scalarContent)
event.scalarTag = yTagFloat of yTypeInteger:
of yTypeBoolTrue, yTypeBoolFalse: event.scalarTag = yTagInteger
event.scalarTag = yTagBoolean of yTypeFloat, yTypeFloatInf, yTypeFloatNaN:
of yTypeNull: event.scalarTag = yTagFloat
event.scalarTag = yTagNull of yTypeBoolTrue, yTypeBoolFalse:
of yTypeString, yTypeUnknown: event.scalarTag = yTagBoolean
of yTypeNull:
event.scalarTag = yTagNull
of yTypeUnknown:
event.scalarTag = yTagString
elif event.scalarTag == yTagExclamationMark:
event.scalarTag = yTagString event.scalarTag = yTagString
elif event.scalarTag == yTagExclamationMark: yield event
event.scalarTag = yTagString present(specificTagEvents, output, tagLib, style,
yield event indentationStep)
present(specificTagEvents, output, tagLib, style, else:
indentationStep) present(events, output, tagLib, style, indentationStep)
else: except YamlPresenterStreamError:
present(events, output, tagLib, style, indentationStep) let e = getCurrentException()
if e.parent is IOError:
raise cast[ref IOError](e.parent)
elif e.parent is YamlParserError:
raise cast[ref YamlParserError](e.parent)
else:
# never happens
assert(false)
except Exception:
# compiler bug: https://github.com/nim-lang/Nim/issues/3772
assert(false)

View File

@ -1 +1 @@
import parsing, serializing import parsing, constructingJson, serializing

View File

@ -25,10 +25,13 @@ when defined(yamlDebug):
type type
TypeHint* = enum TypeHint* = enum
## A type hint is a friendly message from the YAML lexer, telling you ## A type hint can be computed from scalar content and tells you what
## it thinks a scalar string probably is of a certain type. You are not ## NimYAML thinks the scalar's type is. It is generated by
## required to adhere to this information. The first matching RegEx will ## `guessType <#guessType,string,TypeHint>`_ The first matching RegEx
## be the type hint of a scalar string. ## in the following table will be the type hint of a scalar string.
##
## You can use it to determine the type of YAML scalars that have a '?'
## non-specific tag, but using this feature is completely optional.
## ##
## ================== ========================= ## ================== =========================
## Name RegEx ## Name RegEx
@ -40,14 +43,10 @@ type
## ``yTypeBoolTrue`` ``y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON`` ## ``yTypeBoolTrue`` ``y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON``
## ``yTypeBoolFalse`` ``n|N|no|No|NO|false|False|FALSE|off|Off|OFF`` ## ``yTypeBoolFalse`` ``n|N|no|No|NO|false|False|FALSE|off|Off|OFF``
## ``yTypeNull`` ``~ | null | Null | NULL`` ## ``yTypeNull`` ``~ | null | Null | NULL``
## ``yTypeString`` *none*
## ``yTypeUnknown`` ``*`` ## ``yTypeUnknown`` ``*``
## ================== ========================= ## ================== =========================
##
## The value `yTypeString` is not returned based on RegExes, but for
## scalars that are quoted within the YAML input character stream.
yTypeInteger, yTypeFloat, yTypeFloatInf, yTypeFloatNaN, yTypeBoolTrue, yTypeInteger, yTypeFloat, yTypeFloatInf, yTypeFloatNaN, yTypeBoolTrue,
yTypeBoolFalse, yTypeNull, yTypeString, yTypeUnknown yTypeBoolFalse, yTypeNull, yTypeUnknown
YamlStreamEventKind* = enum YamlStreamEventKind* = enum
## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds ## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds
@ -59,11 +58,13 @@ type
## A ``TagId`` identifies a tag URI, like for example ## A ``TagId`` identifies a tag URI, like for example
## ``"tag:yaml.org,2002:str"``. The URI corresponding to a ``TagId`` can ## ``"tag:yaml.org,2002:str"``. The URI corresponding to a ``TagId`` can
## be queried from the `TagLibrary <#TagLibrary>`_ which was ## be queried from the `TagLibrary <#TagLibrary>`_ which was
## used to create this ``TagId`` with ## used to create this ``TagId``; e.g. when you parse a YAML character
## `uri <#uri,TagLibrary,TagId>`_. URI strings are ## stream, the ``TagLibrary`` of the parser is the one which generates
## mapped to ``TagId`` s for efficiency reasons (you do not need to ## the resulting ``TagId`` s.
## compare strings every time) and to be able to discover unknown tag ##
## URIs early in the parsing process. ## URI strings are mapped to ``TagId`` s for efficiency reasons (you
## do not need to compare strings every time) and to be able to
## discover unknown tag URIs early in the parsing process.
AnchorId* = distinct int ## \ AnchorId* = distinct int ## \
## An ``AnchorId`` identifies an anchor in the current document. It ## An ``AnchorId`` identifies an anchor in the current document. It
@ -105,7 +106,8 @@ type
## ``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``. ## is terminated by a ``yamlEndDocument``. Moreover, every emitted map
## has an even number of children.
## ##
## 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
@ -122,8 +124,14 @@ type
## `extendedTagLibrary <#extendedTagLibrary>`_. ## `extendedTagLibrary <#extendedTagLibrary>`_.
## ##
## When `YamlParser <#YamlParser>`_ encounters tags not existing in the ## When `YamlParser <#YamlParser>`_ encounters tags not existing in the
## tag library, it will assign ``nextCustomTagId`` to the URI, add it ## tag library, it will use
## to the tag library and increase ``nextCustomTagId``. ## `registerTagUri <#registerTagUri,TagLibrary,string,TagId>`_ to add
## the tag to the library.
##
## You can base your tag library on common tag libraries by initializing
## them with `initFailsafeTagLibrary <#initFailsafeTagLibrary>`_,
## `initCoreTagLibrary <#initCoreTagLibrary>`_ or
## `initExtendedTagLibrary <#initExtendedTagLibrary>`_.
tags*: Table[string, TagId] tags*: Table[string, TagId]
nextCustomTagId*: TagId nextCustomTagId*: TagId
secondaryPrefix*: string secondaryPrefix*: string
@ -179,7 +187,7 @@ type
lineContent*: string ## \ lineContent*: string ## \
## content of the line where the error was encountered. Includes a ## content of the line where the error was encountered. Includes a
## second line with a marker ``^`` at the position where the error ## second line with a marker ``^`` at the position where the error
## was encountered, as returned by ``lexbase.getCurrentLine``. ## was encountered.
YamlParserError* = object of YamlLoadingError 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
@ -223,16 +231,26 @@ type
YamlPresenterOutputError* = object of Exception YamlPresenterOutputError* = object of Exception
## Exception that may be raised by the YAML presenter. This occurs if ## Exception that may be raised by the YAML presenter. This occurs if
## writing character data to the output stream raises any exception. The ## writing character data to the output stream raises any exception.
## exception that has been catched is retrievable from ``cause``. ## The error that has occurred is available from ``parent``.
cause*: ref Exception
YamlPresenterStreamError* = object of Exception YamlPresenterStreamError* = object of Exception
## Exception that may be raised by the YAML presenter. This occurs if ## Exception that may be raised by the YAML presenter. This occurs if
## an exception is raised while retrieving the next item from a ## an exception is raised while retrieving the next item from a
## `YamlStream <#YamlStream>`_. The exception that has been catched is ## `YamlStream <#YamlStream>`_. The error that has occurred is
## retrievable from ``cause``. ## available from ``parent``.
cause*: ref Exception
YamlConstructionError* = object of YamlLoadingError
## Exception that may be raised when constructing data objects from a
## `YamlStream <#YamlStream>`_. The fields ``line``, ``column`` and
## ``lineContent`` are only available if the costructing proc also does
## parsing, because otherwise this information is not available to the
## costruction proc.
YamlConstructionStreamError* = object of YamlLoadingError
## Exception that may be raised by a constructor if the input
## `YamlStream <#YamlStream>`_ raises an error. The error that has
## occurred is available from ``parent``.
const const
# failsafe schema # failsafe schema
@ -286,10 +304,10 @@ const
# interface # interface
proc `==`*(left: YamlStreamEvent, right: YamlStreamEvent): bool proc `==`*(left: YamlStreamEvent, right: YamlStreamEvent): bool {.raises: [].}
## compares all existing fields of the given items ## compares all existing fields of the given items
proc `$`*(event: YamlStreamEvent): string proc `$`*(event: YamlStreamEvent): string {.raises: [].}
## outputs a human-readable string describing the given event ## outputs a human-readable string describing the given event
proc startDocEvent*(): YamlStreamEvent {.inline, raises: [].} proc startDocEvent*(): YamlStreamEvent {.inline, raises: [].}
@ -315,34 +333,34 @@ proc `==`*(left, right: AnchorId): bool {.borrow.}
proc `$`*(id: AnchorId): string {.borrow.} proc `$`*(id: AnchorId): string {.borrow.}
proc hash*(id: AnchorId): Hash {.borrow.} proc hash*(id: AnchorId): Hash {.borrow.}
proc initTagLibrary*(): TagLibrary proc initTagLibrary*(): TagLibrary {.raises: [].}
## initializes the ``tags`` table and sets ``nextCustomTagId`` to ## initializes the ``tags`` table and sets ``nextCustomTagId`` to
## ``yFirstCustomTagId``. ## ``yFirstCustomTagId``.
proc registerUri*(tagLib: TagLibrary, uri: string): TagId proc registerUri*(tagLib: TagLibrary, uri: string): TagId {.raises: [].}
## registers a custom tag URI with a ``TagLibrary``. The URI will get ## registers a custom tag URI with a ``TagLibrary``. The URI will get
## the ``TagId`` ``nextCustomTagId``, which will be incremented. ## the ``TagId`` ``nextCustomTagId``, which will be incremented.
proc uri*(tagLib: TagLibrary, id: TagId): string proc uri*(tagLib: TagLibrary, id: TagId): string {.raises: [KeyError].}
## retrieve the URI a ``TagId`` maps to. ## retrieve the URI a ``TagId`` maps to.
# these should be consts, but the Nim VM still has problems handling tables # these should be consts, but the Nim VM still has problems handling tables
# properly, so we use let instead. # properly, so we use let instead.
proc initFailsafeTagLibrary*(): TagLibrary proc initFailsafeTagLibrary*(): TagLibrary {.raises: [].}
## Contains only: ## Contains only:
## - ``!`` ## - ``!``
## - ``?`` ## - ``?``
## - ``!!str`` ## - ``!!str``
## - ``!!map`` ## - ``!!map``
## - ``!!seq`` ## - ``!!seq``
proc initCoreTagLibrary*(): TagLibrary proc initCoreTagLibrary*(): TagLibrary {.raises: [].}
## Contains everything in ``initFailsafeTagLibrary`` plus: ## Contains everything in ``initFailsafeTagLibrary`` plus:
## - ``!!null`` ## - ``!!null``
## - ``!!bool`` ## - ``!!bool``
## - ``!!int`` ## - ``!!int``
## - ``!!float`` ## - ``!!float``
proc initExtendedTagLibrary*(): TagLibrary proc initExtendedTagLibrary*(): TagLibrary {.raises: [].}
## Contains everything from ``initCoreTagLibrary`` plus: ## Contains everything from ``initCoreTagLibrary`` plus:
## - ``!!omap`` ## - ``!!omap``
## - ``!!pairs`` ## - ``!!pairs``
@ -356,12 +374,17 @@ proc initExtendedTagLibrary*(): TagLibrary
proc guessType*(scalar: string): TypeHint {.raises: [].} proc guessType*(scalar: string): TypeHint {.raises: [].}
proc newYamlParser*(tagLib: TagLibrary = initExtendedTagLibrary(), proc newYamlParser*(tagLib: TagLibrary = initExtendedTagLibrary(),
callback: WarningCallback = nil): YamlParser callback: WarningCallback = nil): YamlParser {.raises: [].}
proc getLineNumber*(p: YamlParser): int {.raises: [].}
proc getColNumber*(p: YamlParser): int {.raises: [].}
proc getLineContent*(p: YamlParser, marker: bool = true): string {.raises: [].}
proc parse*(p: YamlParser, s: Stream): proc parse*(p: YamlParser, s: Stream):
YamlStream {.raises: [IOError, YamlParserError].} YamlStream {.raises: [IOError, YamlParserError].}
proc constructJson*(s: YamlStream): seq[JsonNode] proc constructJson*(s: YamlStream): seq[JsonNode]
{.raises: [YamlConstructionError, YamlConstructionStreamError].}
## Construct an in-memory JSON tree from a YAML event stream. The stream may ## 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 ## not contain any tags apart from those in ``coreTagLibrary``. Anchors and
## aliases will be resolved. Maps in the input must not contain ## aliases will be resolved. Maps in the input must not contain
@ -376,7 +399,8 @@ proc constructJson*(s: YamlStream): seq[JsonNode]
## of these values into a JSON character stream. ## of these values into a JSON character stream.
proc loadToJson*(s: Stream): seq[JsonNode] proc loadToJson*(s: Stream): seq[JsonNode]
## Uses `YamlSequentialParser <#YamlSequentialParser>`_ and {.raises: [IOError, YamlParserError, YamlConstructionError].}
## Uses `YamlParser <#YamlParser>`_ and
## `constructJson <#constructJson>`_ to construct an in-memory JSON tree ## `constructJson <#constructJson>`_ to construct an in-memory JSON tree
## from a YAML character stream. ## from a YAML character stream.
@ -388,9 +412,12 @@ proc present*(s: YamlStream, target: Stream, tagLib: TagLibrary,
## Convert ``s`` to a YAML character stream and write it to ``target``. ## Convert ``s`` to a YAML character stream and write it to ``target``.
proc transform*(input: Stream, output: Stream, style: PresentationStyle, proc transform*(input: Stream, output: Stream, style: PresentationStyle,
indentationStep: int = 2) indentationStep: int = 2) {.raises: [IOError, YamlParserError,
YamlPresenterJsonError,
YamlPresenterOutputError].}
## Parser ``input`` as YAML character stream and then dump it to ``output`` ## Parser ``input`` as YAML character stream and then dump it to ``output``
## without resolving any tags, anchors and aliases. ## while resolving non-specific tags to the ones in the YAML core tag
## library.
# implementation # implementation