mirror of https://github.com/status-im/NimYAML.git
995 lines
37 KiB
Nim
995 lines
37 KiB
Nim
# NimYAML - YAML implementation in Nim
|
|
# (c) Copyright 2016 Felix Krause
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
|
|
## ==================
|
|
## Module yaml.parser
|
|
## ==================
|
|
##
|
|
## This is the low-level parser API. A ``YamlParser`` enables you to parse any
|
|
## non-nil string or Stream object as YAML character stream.
|
|
|
|
import tables, strutils, macros, streams
|
|
import taglib, stream, private/lex, private/internal, data
|
|
|
|
when defined(nimNoNil):
|
|
{.experimental: "notnil".}
|
|
|
|
type
|
|
YamlParser* = object
|
|
## A parser object. Retains its ``TagLibrary`` across calls to
|
|
## `parse <#parse,YamlParser,Stream>`_. Can be used
|
|
## to access anchor names while parsing a YAML character stream, but
|
|
## only until the document goes out of scope (i.e. until
|
|
## ``yamlEndDocument`` is yielded).
|
|
tagLib: TagLibrary
|
|
issueWarnings: bool
|
|
|
|
State = proc(c: Context, e: var Event): bool {.locks: 0, gcSafe.}
|
|
|
|
Level = object
|
|
state: State
|
|
indentation: int
|
|
|
|
Context = ref object of YamlStream
|
|
p: YamlParser
|
|
lex: Lexer
|
|
levels: seq[Level]
|
|
|
|
headerProps, inlineProps: Properties
|
|
headerStart, inlineStart: Mark
|
|
blockIndentation: int
|
|
|
|
YamlLoadingError* = object of ValueError
|
|
## Base class for all exceptions that may be raised during the process
|
|
## of loading a YAML character stream.
|
|
mark*: Mark ## position at which the error has occurred.
|
|
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.
|
|
|
|
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
|
|
## the YamlStream the parser provides should be discarded.
|
|
##
|
|
## A character stream is invalid YAML if and only if at least one of the
|
|
## following conditions apply:
|
|
##
|
|
## - There are invalid characters in an element whose contents is
|
|
## restricted to a limited set of characters. For example, there are
|
|
## characters in a tag URI which are not valid URI characters.
|
|
## - An element has invalid indentation. This can happen for example if
|
|
## a block list element indicated by ``"- "`` is less indented than
|
|
## the element in the previous line, but there is no block sequence
|
|
## list open at the same indentation level.
|
|
## - The YAML structure is invalid. For example, an explicit block map
|
|
## indicated by ``"? "`` and ``": "`` may not suddenly have a block
|
|
## sequence item (``"- "``) at the same indentation level. Another
|
|
## possible violation is closing a flow style object with the wrong
|
|
## closing character (``}``, ``]``) or not closing it at all.
|
|
## - A custom tag shorthand is used that has not previously been
|
|
## declared with a ``%TAG`` directive.
|
|
## - Multiple tags or anchors are defined for the same node.
|
|
## - An alias is used which does not map to any anchor that has
|
|
## previously been declared in the same document.
|
|
## - An alias has a tag or anchor associated with it.
|
|
##
|
|
## Some elements in this list are vague. For a detailed description of a
|
|
## valid YAML character stream, see the YAML specification.
|
|
|
|
const defaultProperties = (yAnchorNone, yTagQuestionMark)
|
|
|
|
# parser states
|
|
|
|
{.push gcSafe, locks: 0.}
|
|
proc atStreamStart(c: Context, e: var Event): bool
|
|
proc atStreamEnd(c: Context, e : var Event): bool
|
|
proc beforeDoc(c: Context, e: var Event): bool
|
|
proc beforeDocEnd(c: Context, e: var Event): bool
|
|
proc afterDirectivesEnd(c: Context, e: var Event): bool
|
|
proc beforeImplicitRoot(c: Context, e: var Event): bool
|
|
proc atBlockIndentation(c: Context, e: var Event): bool
|
|
proc beforeBlockIndentation(c: Context, e: var Event): bool
|
|
proc beforeNodeProperties(c: Context, e: var Event): bool
|
|
proc requireImplicitMapStart(c: Context, e: var Event): bool
|
|
proc afterCompactParent(c: Context, e: var Event): bool
|
|
proc afterCompactParentProps(c: Context, e: var Event): bool
|
|
proc requireInlineBlockItem(c: Context, e: var Event): bool
|
|
proc beforeFlowItemProps(c: Context, e: var Event): bool
|
|
proc inBlockSeq(c: Context, e: var Event): bool
|
|
proc beforeBlockMapValue(c: Context, e: var Event): bool
|
|
proc atBlockIndentationProps(c: Context, e: var Event): bool
|
|
proc afterFlowSeqSep(c: Context, e: var Event): bool
|
|
proc afterFlowMapSep(c: Context, e: var Event): bool
|
|
proc atBlockMapKeyProps(c: Context, e: var Event): bool
|
|
proc afterImplicitKey(c: Context, e: var Event): bool
|
|
proc afterBlockParent(c: Context, e: var Event): bool
|
|
proc afterBlockParentProps(c: Context, e: var Event): bool
|
|
proc afterImplicitPairStart(c: Context, e: var Event): bool
|
|
proc beforePairValue(c: Context, e: var Event): bool
|
|
proc atEmptyPairKey(c: Context, e: var Event): bool
|
|
proc afterFlowMapValue(c: Context, e: var Event): bool
|
|
proc afterFlowSeqSepProps(c: Context, e: var Event): bool
|
|
proc afterFlowSeqItem(c: Context, e: var Event): bool
|
|
proc afterPairValue(c: Context, e: var Event): bool
|
|
{.pop.}
|
|
|
|
proc init[T](c: Context, source: T) {.inline.} =
|
|
c.levels.add(Level(state: atStreamStart, indentation: -2))
|
|
c.nextImpl = proc(s: YamlStream, e: var Event): bool =
|
|
let c = Context(s)
|
|
return c.levels[^1].state(c, e)
|
|
c.headerProps = defaultProperties
|
|
c.inlineProps = defaultProperties
|
|
c.lex.init(source)
|
|
|
|
# interface
|
|
|
|
proc init*(p: var YamlParser, tagLib: TagLibrary = initExtendedTagLibrary(),
|
|
issueWarnings: bool = false) =
|
|
## Creates a YAML parser. if ``callback`` is not ``nil``, it will be called
|
|
## whenever the parser yields a warning.
|
|
p.tagLib = tagLib
|
|
p.issueWarnings = issueWarnings
|
|
|
|
proc parse*(p: YamlParser, s: Stream): YamlStream =
|
|
let c = new(Context)
|
|
c.init(s)
|
|
return c
|
|
|
|
proc parse*(p: YamlParser, s: string): YamlStream =
|
|
let c = new(Context)
|
|
c.init(s)
|
|
return c
|
|
|
|
# implementation
|
|
|
|
template debug(message: string) {.dirty.} =
|
|
when defined(yamlDebug):
|
|
try: styledWriteLine(stdout, fgBlue, message)
|
|
except IOError: discard
|
|
|
|
proc isEmpty(props: Properties): bool =
|
|
result = props.anchor == yAnchorNone and
|
|
props.tag == yTagQuestionMark
|
|
|
|
proc generateError(c: Context, message: string):
|
|
ref YamlParserError {.raises: [].} =
|
|
result = (ref YamlParserError)(
|
|
msg: message, parent: nil, mark: c.lex.curStartPos,
|
|
lineContent: c.lex.currentLine())
|
|
|
|
proc parseTag(c: Context): TagId =
|
|
let handle = c.lex.fullLexeme()
|
|
var uri = c.p.tagLib.resolve(handle)
|
|
if uri == "":
|
|
raise c.generateError("unknown handle: " & escape(handle))
|
|
c.lex.next()
|
|
if c.lex.cur != Token.Suffix:
|
|
raise c.generateError("unexpected token (expected tag suffix): " & $c.lex.cur)
|
|
uri.add(c.lex.evaluated)
|
|
try:
|
|
return c.p.tagLib.tags[uri]
|
|
except KeyError:
|
|
return c.p.tagLib.registerUri(uri)
|
|
|
|
proc toStyle(t: Token): ScalarStyle =
|
|
return (case t
|
|
of Plain: ssPlain
|
|
of SingleQuoted: ssSingleQuoted
|
|
of DoubleQuoted: ssDoubleQuoted
|
|
of Literal: ssLiteral
|
|
of Folded: ssFolded
|
|
else: ssAny)
|
|
|
|
proc atStreamStart(c: Context, e: var Event): bool =
|
|
c.levels[0] = Level(state: atStreamEnd, indentation: -2)
|
|
c.levels.add(Level(state: beforeDoc, indentation: -1))
|
|
e = Event(startPos: c.lex.curStartPos, endPos: c.lex.curStartPos, kind: yamlStartStream)
|
|
return true
|
|
|
|
proc atStreamEnd(c: Context, e : var Event): bool =
|
|
e = Event(startPos: c.lex.curStartPos,
|
|
endPos: c.lex.curStartPos, kind: yamlEndStream)
|
|
return true
|
|
|
|
proc beforeDoc(c: Context, e: var Event): bool =
|
|
var version = ""
|
|
var seenDirectives = false
|
|
while true:
|
|
case c.lex.cur
|
|
of DocumentEnd:
|
|
if seenDirectives:
|
|
raise c.generateError("Missing `---` after directives")
|
|
c.lex.next()
|
|
of DirectivesEnd:
|
|
c.lex.next()
|
|
c.levels[1].state = beforeDocEnd
|
|
c.levels.add(Level(state: afterDirectivesEnd, indentation: -1))
|
|
return true
|
|
of StreamEnd:
|
|
discard c.levels.pop()
|
|
return false
|
|
of Indentation:
|
|
e = Event(kind: yamlStartDoc, explicitDirectivesEnd: false, version: version)
|
|
c.levels[^1].state = beforeDocEnd
|
|
c.levels.add(Level(state: beforeImplicitRoot, indentation: -1))
|
|
return true
|
|
of YamlDirective:
|
|
seenDirectives = true
|
|
c.lex.next()
|
|
if c.lex.cur != Token.DirectiveParam:
|
|
raise c.generateError("Invalid token (expected YAML version string): " & $c.lex.cur)
|
|
elif version != "":
|
|
raise c.generateError("Duplicate %YAML")
|
|
version = c.lex.fullLexeme()
|
|
if version != "1.2" and c.p.issueWarnings:
|
|
discard # TODO
|
|
c.lex.next()
|
|
of TagDirective:
|
|
seenDirectives = true
|
|
c.lex.next()
|
|
if c.lex.cur != Token.TagHandle:
|
|
raise c.generateError("Invalid token (expected tag handle): " & $c.lex.cur)
|
|
let tagHandle = c.lex.fullLexeme()
|
|
c.lex.next()
|
|
if c.lex.cur != Token.Suffix:
|
|
raise c.generateError("Invalid token (expected tag URI): " & $c.lex.cur)
|
|
c.p.tagLib.registerHandle(tagHandle, c.lex.fullLexeme())
|
|
c.lex.next()
|
|
of UnknownDirective:
|
|
seenDirectives = true
|
|
# TODO: issue warning
|
|
while true:
|
|
c.lex.next()
|
|
if c.lex.cur != Token.DirectiveParam: break
|
|
else:
|
|
raise c.generateError("Unexpected token (expected directive or document start): " & $c.lex.cur)
|
|
|
|
proc afterDirectivesEnd(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of TagHandle, VerbatimTag, Token.Anchor:
|
|
c.inlineStart = c.lex.curStartPos
|
|
c.levels.add(Level(state: beforeNodeProperties, indentation: 0))
|
|
of Indentation:
|
|
c.headerStart = c.inlineStart
|
|
c.levels[^1].state = atBlockIndentation
|
|
c.levels.add(Level(state: beforeBlockIndentation, indentation: 0))
|
|
of DocumentEnd:
|
|
e = scalarEvent("", c.inlineProps, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
of Folded, Literal:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps,
|
|
if c.lex.cur == Token.Folded: ssFolded else: ssLiteral,
|
|
c.lex.curStartPos, c.lex.curEndPos)
|
|
else:
|
|
raise c.generateError("Illegal content at `---`: " & $c.lex.cur)
|
|
|
|
proc beforeImplicitRoot(c: Context, e: var Event): bool =
|
|
if c.lex.cur != Token.Indentation:
|
|
raise c.generateError("Unexpected token (expected line start): " & $c.lex.cur)
|
|
c.inlineStart = c.lex.curEndPos
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
c.lex.next()
|
|
case c.lex.cur
|
|
of SeqItemInd, MapKeyInd, MapValueInd:
|
|
c.levels[^1].state = afterCompactParent
|
|
return false
|
|
of scalarTokenKind:
|
|
c.levels[^1].state = requireImplicitMapStart
|
|
return false
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = requireImplicitMapStart
|
|
c.levels.add(Level(state: beforeNodeProperties, indentation: 0))
|
|
of MapStart, SeqStart:
|
|
c.levels[^1].state = afterCompactParentProps
|
|
return false
|
|
else:
|
|
raise c.generateError("Unexpected token (expected collection start): " & $c.lex.cur)
|
|
|
|
proc requireImplicitMapStart(c: Context, e: var Event): bool =
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
c.peek = e
|
|
e = startMapEvent(csBlock, c.headerProps, c.headerStart, headerEnd)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
else:
|
|
if not isEmpty(c.headerProps):
|
|
raise c.generateError("Alias may not have properties")
|
|
discard c.levels.pop()
|
|
return true
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur),
|
|
c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
case c.lex.cur
|
|
of Token.MapValueInd:
|
|
if c.lex.lastScalarWasMultiline():
|
|
raise c.generateError("Implicit mapping key may not be multiline")
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, c.headerProps,
|
|
c.headerStart, headerEnd)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
of Indentation, DocumentEnd, DirectivesEnd, StreamEnd:
|
|
raise c.generateError("Scalar at root level requires `---`")
|
|
else: discard
|
|
return true
|
|
of MapStart, SeqStart:
|
|
c.levels[^1].state = beforeFlowItemProps
|
|
return false
|
|
of Indentation:
|
|
raise c.generateError("Standalone node properties not allowed on non-header line")
|
|
else:
|
|
raise c.generateError("Unexpected token (expected implicit mapping key): " & $c.lex.cur)
|
|
|
|
proc atBlockIndentation(c: Context, e: var Event): bool =
|
|
if c.blockIndentation == c.levels[^1].indentation and
|
|
(c.lex.cur != Token.SeqItemInd or
|
|
c.levels[^3].state == inBlockSeq):
|
|
e = scalarEvent(c.lex.evaluated, c.headerProps, ssPlain,
|
|
c.headerStart, c.headerStart)
|
|
c.headerProps = defaultProperties
|
|
discard c.levels.pop()
|
|
discard c.levels.pop()
|
|
return true
|
|
c.inlineStart = c.lex.curStartPos
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
if isEmpty(c.headerProps):
|
|
c.levels[^1].state = requireInlineBlockItem
|
|
else:
|
|
c.levels[^1].state = requireImplicitMapStart
|
|
c.levels.add(Level(state: beforeBlockIndentation, indentation: 0))
|
|
return false
|
|
of SeqItemInd:
|
|
e = startSeqEvent(csBlock, c.headerProps,
|
|
c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1] = Level(state: inBlockSeq, indentation: c.lex.indentation)
|
|
c.levels.add(Level(state: beforeBlockIndentation, indentation: 0))
|
|
c.levels.add(Level(state: afterCompactParent, indentation: c.lex.indentation))
|
|
c.lex.next()
|
|
return true
|
|
of MapKeyInd:
|
|
e = startMapEvent(csBlock, c.headerProps,
|
|
c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1] = Level(state: beforeBlockMapValue, indentation: 0)
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent, indentation: c.lex.indentation))
|
|
c.lex.next()
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
e = scalarEvent(c.lex.evaluated, c.headerProps,
|
|
toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
if c.lex.lastScalarWasMultiline():
|
|
raise c.generateError("Implicit mapping key may not be multiline")
|
|
let props = e.scalarProperties
|
|
e.scalarProperties = defaultProperties
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, props, c.headerStart, headerEnd)
|
|
c.levels[^1].state = afterImplicitKey
|
|
else:
|
|
discard c.levels.pop()
|
|
return true
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, c.headerProps, c.headerStart, headerEnd)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
elif not isEmpty(c.headerProps):
|
|
raise c.generateError("Alias may not have properties")
|
|
else:
|
|
discard c.levels.pop()
|
|
return true
|
|
else:
|
|
c.levels[^1].state = atBlockIndentationProps
|
|
|
|
proc atBlockIndentationProps(c: Context, e: var Event): bool =
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of MapValueInd:
|
|
c.peek = scalarEvent("", c.inlineProps, ssPlain, c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
e = startMapEvent(csBlock, c.headerProps, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
return true
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
if c.lex.lastScalarWasMultiline():
|
|
raise c.generateError("Implicit mapping key may not be multiline")
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, c.headerProps, c.headerStart, headerEnd)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
else:
|
|
discard c.levels.pop()
|
|
return true
|
|
of MapStart:
|
|
e = startMapEvent(csFlow, c.headerProps, c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterFlowMapSep
|
|
c.lex.next()
|
|
return true
|
|
of SeqStart:
|
|
e = startSeqEvent(csFlow, c.headerProps, c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1].state = afterFlowSeqSep
|
|
c.lex.next()
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected block content): " & $c.lex.cur)
|
|
|
|
proc beforeNodeProperties(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of TagHandle:
|
|
if c.inlineProps.tag != yTagQuestionMark:
|
|
raise c.generateError("Only one tag allowed per node")
|
|
c.inlineProps.tag = c.parseTag()
|
|
of VerbatimTag:
|
|
if c.inlineProps.tag != yTagQuestionMark:
|
|
raise c.generateError("Only one tag allowed per node")
|
|
try:
|
|
c.inlineProps.tag = c.p.taglib.tags[c.lex.evaluated]
|
|
except KeyError:
|
|
c.inlineProps.tag = c.p.taglib.registerUri(c.lex.evaluated)
|
|
of Token.Anchor:
|
|
if c.inlineProps.anchor != yAnchorNone:
|
|
raise c.generateError("Only one anchor allowed per node")
|
|
c.inlineProps.anchor = c.lex.shortLexeme().Anchor
|
|
of Indentation:
|
|
c.headerProps = c.inlineProps
|
|
c.inlineProps = defaultProperties
|
|
discard c.levels.pop()
|
|
return false
|
|
of Alias:
|
|
raise c.generateError("Alias may not have node properties")
|
|
else:
|
|
discard c.levels.pop()
|
|
return false
|
|
c.lex.next()
|
|
return false
|
|
|
|
proc afterCompactParent(c: Context, e: var Event): bool =
|
|
c.inlineStart = c.lex.curStartPos
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = afterCompactParentProps
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
of SeqItemInd:
|
|
e = startSeqEvent(csBlock, c.headerProps, c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1] = Level(state: inBlockSeq, indentation: c.lex.indentation)
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent))
|
|
c.lex.next()
|
|
return true
|
|
of MapKeyInd:
|
|
e = startMapEvent(csBlock, c.headerProps, c.headerStart, c.lex.curEndPos)
|
|
c.headerProps = defaultProperties
|
|
c.levels[^1] = Level(state: beforeBlockMapValue, indentation: c.lex.indentation)
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent))
|
|
return true
|
|
else:
|
|
c.levels[^1].state = afterCompactParentProps
|
|
return false
|
|
|
|
proc afterCompactParentProps(c: Context, e: var Event): bool =
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
return false
|
|
of Indentation:
|
|
c.headerStart = c.inlineStart
|
|
c.levels[^1] = Level(state: atBlockIndentation, indentation: c.levels[^3].indentation)
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
return false
|
|
of StreamEnd, DocumentEnd, DirectivesEnd:
|
|
e = scalarEvent("", c.inlineProps, ssPlain, c.inlineStart, c.lex.curStartPos)
|
|
c.inlineProps = defaultProperties
|
|
discard c.levels.pop()
|
|
return true
|
|
of MapValueInd:
|
|
c.peek = scalarEvent("", c.inlineProps, ssPlain, c.inlineStart, c.lex.curStartPos)
|
|
c.inlineProps = defaultProperties
|
|
e = startMapEvent(csBlock, defaultProperties, c.lex.curStartPos, c.lex.curStartPos)
|
|
c.levels[^1].state = afterImplicitKey
|
|
return true
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
let headerEnd = c.lex.curStartPos
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, defaultProperties, headerEnd, headerEnd)
|
|
c.levels[^1].state = afterImplicitKey
|
|
else:
|
|
discard c.levels.pop()
|
|
return true
|
|
of scalarTokenKind:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur),
|
|
c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
let headerEnd = c.lex.curStartPos
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
if c.lex.lastScalarWasMultiline():
|
|
raise c.generateError("Implicit mapping key may not be multiline")
|
|
c.peek = move(e)
|
|
e = startMapEvent(csBlock, defaultProperties, headerEnd, headerEnd)
|
|
c.levels[^1].state = afterImplicitKey
|
|
else:
|
|
discard c.levels.pop()
|
|
return true
|
|
of MapStart:
|
|
e = startMapEvent(csFlow, c.inlineProps, c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
c.levels[^1].state = afterFlowMapSep
|
|
c.lex.next()
|
|
return true
|
|
of SeqStart:
|
|
e = startSeqEvent(csFlow, c.inlineProps, c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
c.levels[^1].state = afterFlowSeqSep
|
|
c.lex.next()
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected newline or flow item start: " & $c.lex.cur)
|
|
|
|
proc afterBlockParent(c: Context, e: var Event): bool =
|
|
c.inlineStart = c.lex.curStartPos
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = afterBlockParentProps
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
of SeqItemInd, MapKeyInd:
|
|
raise c.generateError("Compact notation not allowed after implicit key")
|
|
else:
|
|
c.levels[^1].state = afterBlockParentProps
|
|
return false
|
|
|
|
proc afterBlockParentProps(c: Context, e: var Event): bool =
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
return false
|
|
of MapValueInd:
|
|
raise c.generateError("Compact notation not allowed after implicit key")
|
|
of scalarTokenKind:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
raise c.generateError("Compact notation not allowed after implicit key")
|
|
discard c.levels.pop()
|
|
return true
|
|
else:
|
|
c.levels[^1].state = afterCompactParentProps
|
|
return false
|
|
|
|
proc requireInlineBlockItem(c: Context, e: var Event): bool =
|
|
c.levels[^1].indentation = c.lex.indentation
|
|
case c.lex.cur
|
|
of Indentation:
|
|
raise c.generateError("Node properties may not stand alone on a line")
|
|
else:
|
|
c.levels[^1].state = afterCompactParentProps
|
|
return false
|
|
|
|
proc beforeDocEnd(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of DocumentEnd:
|
|
e = endDocEvent(false, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels[^1].state = beforeDoc
|
|
c.lex.next()
|
|
of StreamEnd:
|
|
e = endDocEvent(true, c.lex.curStartPos, c.lex.curEndPos)
|
|
discard c.levels.pop()
|
|
of DirectivesEnd:
|
|
e = endDocEvent(true, c.lex.curStartPos, c.lex.curStartPos)
|
|
c.levels[^1].state = beforeDoc
|
|
else:
|
|
raise c.generateError("Unexpected token (expected document end): " & $c.lex.cur)
|
|
return true
|
|
|
|
proc inBlockSeq(c: Context, e: var Event): bool =
|
|
if c.blockIndentation > c.levels[^1].indentation:
|
|
raise c.generateError("Invalid indentation: got " & $c.blockIndentation & ", expected " & $c.levels[^1].indentation)
|
|
case c.lex.cur
|
|
of SeqItemInd:
|
|
c.lex.next()
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent, indentation: c.blockIndentation))
|
|
return false
|
|
else:
|
|
if c.levels[^3].indentation == c.levels[^1].indentation:
|
|
e = endSeqEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
discard c.levels.pop()
|
|
discard c.levels.pop()
|
|
else:
|
|
raise c.generateError("Illegal token (expected block sequence indicator): " & $c.lex.cur)
|
|
|
|
proc beforeBlockMapKey(c: Context, e: var Event): bool =
|
|
if c.blockIndentation > c.levels[^1].indentation:
|
|
raise c.generateError("Invalid indentation: got " & $c.blockIndentation & ", expected " & $c.levels[^1].indentation)
|
|
case c.lex.cur
|
|
of MapKeyInd:
|
|
c.levels[^1].state = beforeBlockMapValue
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent, indentation: c.blockIndentation))
|
|
c.lex.next()
|
|
return false
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = atBlockMapKeyProps
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
return false
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
c.levels[^1].state = atBlockMapKeyProps
|
|
return false
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
c.lex.next()
|
|
c.levels[^1].state = afterImplicitKey
|
|
return true
|
|
of MapValueInd:
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels[^1].state = beforeBlockMapValue
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected mapping key): " & $c.lex.cur)
|
|
|
|
proc atBlockMapKeyProps(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
if c.lex.lastScalarWasMultiline():
|
|
raise c.generateError("Implicit mapping key may not be multiline")
|
|
of MapValueInd:
|
|
e = scalarEvent("", c.inlineProps, ssPlain, c.inlineStart, c.lex.curStartPos)
|
|
c.inlineProps = defaultProperties
|
|
c.levels[^1].state = afterImplicitKey
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected implicit mapping key): " & $c.lex.cur)
|
|
c.lex.next()
|
|
c.levels[^1].state = afterImplicitKey
|
|
return true
|
|
|
|
proc afterImplicitKey(c: Context, e: var Event): bool =
|
|
if c.lex.cur != Token.MapValueInd:
|
|
raise c.generateError("Unexpected token (expected ':'): " & $c.lex.cur)
|
|
c.lex.next()
|
|
c.levels[^1].state = beforeBlockMapKey
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterBlockParent, indentation: c.blockIndentation))
|
|
return false
|
|
|
|
proc beforeBlockMapValue(c: Context, e: var Event): bool =
|
|
if c.blockIndentation > c.levels[^1].indentation:
|
|
raise c.generateError("Invalid indentation")
|
|
case c.lex.cur
|
|
of MapValueInd:
|
|
c.levels[^1].state = beforeBlockMapKey
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
c.levels.add(Level(state: afterCompactParent, indentation: c.blockIndentation))
|
|
c.lex.next()
|
|
of MapKeyInd, Plain, SingleQuoted, DoubleQuoted, nodePropertyKind:
|
|
# the value is allowed to be missing after an explicit key
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels[^1].state = beforeBlockMapKey
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected mapping value): " & $c.lex.cur)
|
|
|
|
proc beforeBlockIndentation(c: Context, e: var Event): bool =
|
|
proc endBlockNode(e: var Event) =
|
|
if c.levels[^1].state == beforeBlockMapKey:
|
|
e = endMapEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
elif c.levels[^1].state == beforeBlockMapValue:
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels[^1].state = beforeBlockMapKey
|
|
c.levels.add(Level(state: beforeBlockIndentation))
|
|
return
|
|
elif c.levels[^1].state == inBlockSeq:
|
|
e = endSeqEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
elif c.levels[^1].state == atBlockIndentation:
|
|
e = scalarEvent("", c.headerProps, ssPlain, c.headerStart, c.headerStart)
|
|
c.headerProps = defaultProperties
|
|
elif c.levels[^1].state == beforeBlockIndentation:
|
|
raise c.generateError("Unexpected double beforeBlockIndentation")
|
|
else:
|
|
raise c.generateError("Internal error (please report this bug)")
|
|
discard c.levels.pop()
|
|
discard c.levels.pop()
|
|
case c.lex.cur
|
|
of Indentation:
|
|
c.blockIndentation = c.lex.indentation
|
|
if c.blockIndentation < c.levels[^1].indentation:
|
|
endBlockNode(e)
|
|
return true
|
|
else:
|
|
c.lex.next()
|
|
return false
|
|
of StreamEnd, DocumentEnd, DirectivesEnd:
|
|
c.blockIndentation = 0
|
|
if c.levels[^1].state != beforeDocEnd:
|
|
endBlockNode(e)
|
|
return true
|
|
else:
|
|
return false
|
|
else:
|
|
raise c.generateError("Unexpected content after node in block context (expected newline): " & $c.lex.cur)
|
|
|
|
proc beforeFlowItem(c: Context, e: var Event): bool =
|
|
c.inlineStart = c.lex.curStartPos
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = beforeFlowItemProps
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
return true
|
|
else:
|
|
c.levels[^1].state = beforeFlowItemProps
|
|
return false
|
|
|
|
proc beforeFlowItemProps(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of nodePropertyKind:
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
of Alias:
|
|
e = aliasEvent(c.lex.shortLexeme().Anchor, c.inlineStart, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
of scalarTokenKind:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
of MapStart:
|
|
e = startMapEvent(csFlow, c.inlineProps, c.inlineStart, c.lex.curEndPos)
|
|
c.levels[^1].state = afterFlowMapSep
|
|
c.lex.next()
|
|
of SeqStart:
|
|
e = startSeqEvent(csFlow, c.inlineProps, c.inlineStart, c.lex.curEndPos)
|
|
c.levels[^1].state = afterFlowSeqSep
|
|
c.lex.next()
|
|
of MapEnd, SeqEnd, SeqSep, MapValueInd:
|
|
e = scalarEvent("", c.inlineProps, ssPlain, c.inlineStart, c.lex.curEndPos)
|
|
discard c.levels.pop()
|
|
else:
|
|
raise c.generateError("Unexpected token (expected flow node): " & $c.lex.cur)
|
|
c.inlineProps = defaultProperties
|
|
return true
|
|
|
|
proc afterFlowMapKey(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of MapValueInd:
|
|
c.levels[^1].state = afterFlowMapValue
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
c.lex.next()
|
|
return false
|
|
of SeqSep, MapEnd:
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels[^1].state = afterFlowMapValue
|
|
return true
|
|
else:
|
|
raise c.generateError("Unexpected token (expected ':'): " & $c.lex.cur)
|
|
|
|
proc afterFlowMapValue(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of SeqSep:
|
|
c.levels[^1].state = afterFlowMapSep
|
|
c.lex.next()
|
|
return false
|
|
of MapEnd:
|
|
e = endMapEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
return true
|
|
of Plain, SingleQuoted, DoubleQuoted, MapKeyInd, Token.Anchor, Alias, MapStart, SeqStart:
|
|
raise c.generateError("Missing ','")
|
|
else:
|
|
raise c.generateError("Unexpected token (expected ',' or '}'): " & $c.lex.cur)
|
|
|
|
proc afterFlowSeqItem(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of SeqSep:
|
|
c.levels[^1].state = afterFlowSeqSep
|
|
c.lex.next()
|
|
return false
|
|
of SeqEnd:
|
|
e = endSeqEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
return true
|
|
of Plain, SingleQuoted, DoubleQuoted, MapKeyInd, Token.Anchor, Alias, MapStart, SeqStart:
|
|
raise c.generateError("Missing ','")
|
|
else:
|
|
raise c.generateError("Unexpected token (expected ',' or ']'): " & $c.lex.cur)
|
|
|
|
proc afterFlowMapSep(c: Context, e: var Event): bool =
|
|
case c.lex.cur
|
|
of MapKeyInd:
|
|
c.lex.next()
|
|
of MapEnd:
|
|
e = endMapEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
return true
|
|
else: discard
|
|
c.levels[^1].state = afterFlowMapKey
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
return false
|
|
|
|
proc possibleNextSequenceItem(c: Context, e: var Event, endToken: Token, afterProps, afterItem: State): bool =
|
|
c.inlineStart = c.lex.curStartPos
|
|
case c.lex.cur
|
|
of SeqSep:
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curStartPos)
|
|
c.lex.next()
|
|
return true
|
|
of nodePropertyKind:
|
|
c.levels[^1].state = afterProps
|
|
c.levels.add(Level(state: beforeNodeProperties))
|
|
return false
|
|
of Plain, SingleQuoted, DoubleQuoted:
|
|
c.levels[^1].state = afterProps
|
|
return false
|
|
of MapKeyInd:
|
|
c.levels[^1].state = afterItem
|
|
e = startMapEvent(csFlow, defaultProperties, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.lex.next()
|
|
c.levels.add(Level(state: beforePairValue))
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
return true
|
|
of MapValueInd:
|
|
c.levels[^1].state = afterItem
|
|
e = startMapEvent(csFlow, defaultProperties, c.lex.curStartPos, c.lex.curEndPos)
|
|
c.levels.add(Level(state: atEmptyPairKey))
|
|
return true
|
|
else:
|
|
if c.lex.cur == endToken:
|
|
e = endSeqEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
c.lex.next()
|
|
discard c.levels.pop()
|
|
return true
|
|
else:
|
|
c.levels[^1].state = afterItem
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
return false
|
|
|
|
proc afterFlowSeqSep(c: Context, e: var Event): bool =
|
|
return possibleNextSequenceItem(c, e, Token.SeqEnd, afterFlowSeqSepProps, afterFlowSeqItem)
|
|
|
|
proc forcedNextSequenceItem(c: Context, e: var Event): bool =
|
|
if c.lex.cur in {Token.Plain, Token.SingleQuoted, Token.DoubleQuoted}:
|
|
e = scalarEvent(c.lex.evaluated, c.inlineProps, toStyle(c.lex.cur), c.inlineStart, c.lex.curEndPos)
|
|
c.inlineProps = defaultProperties
|
|
c.lex.next()
|
|
if c.lex.cur == Token.MapValueInd:
|
|
c.peek = move(e)
|
|
e = startMapEvent(csFlow, defaultProperties, c.lex.curStartPos, c.lex.curStartPos)
|
|
c.levels.add(Level(state: afterImplicitPairStart))
|
|
return true
|
|
else:
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
return false
|
|
|
|
proc afterFlowSeqSepProps(c: Context, e: var Event): bool =
|
|
c.levels[^1].state = afterFlowSeqItem
|
|
return forcedNextSequenceItem(c, e)
|
|
|
|
proc atEmptyPairKey(c: Context, e: var Event): bool =
|
|
c.levels[^1].state = beforePairValue
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curStartPos)
|
|
return true
|
|
|
|
proc beforePairValue(c: Context, e: var Event): bool =
|
|
if c.lex.cur == Token.MapValueInd:
|
|
c.levels[^1].state = afterPairValue
|
|
c.levels.add(Level(state: beforeFlowItem))
|
|
c.lex.next()
|
|
return false
|
|
else:
|
|
# pair ends here without value
|
|
e = scalarEvent("", defaultProperties, ssPlain, c.lex.curStartPos, c.lex.curEndPos)
|
|
discard c.levels.pop()
|
|
return true
|
|
|
|
proc afterImplicitPairStart(c: Context, e: var Event): bool =
|
|
c.lex.next()
|
|
c.levels[^1].state = afterPairValue
|
|
c.levels.add(Level(state: beforeFLowItem))
|
|
return false
|
|
|
|
proc afterPairValue(c: Context, e: var Event): bool =
|
|
e = endMapEvent(c.lex.curStartPos, c.lex.curEndPos)
|
|
discard c.levels.pop()
|
|
return true
|
|
|
|
# TODO --------------
|
|
|
|
|
|
proc display*(p: YamlParser, event: Event): string =
|
|
## Generate a representation of the given event with proper visualization of
|
|
## anchor and tag (if any). The generated representation is conformant to the
|
|
## format used in the yaml test suite.
|
|
##
|
|
## This proc is an informed version of ``$`` on ``YamlStreamEvent`` which can
|
|
## properly display the anchor and tag name as it occurs in the input.
|
|
## However, it shall only be used while using the streaming API because after
|
|
## finishing the parsing of a document, the parser drops all information about
|
|
## anchor and tag names.
|
|
case event.kind
|
|
of yamlStartStream: result = "+STR"
|
|
of yamlEndStream: result = "-STR"
|
|
of yamlEndMap: result = "-MAP"
|
|
of yamlEndSeq: result = "-SEQ"
|
|
of yamlStartDoc:
|
|
result = "+DOC"
|
|
when defined(yamlScalarRepInd):
|
|
if event.explicitDirectivesEnd: result &= " ---"
|
|
of yamlEndDoc:
|
|
result = "-DOC"
|
|
when defined(yamlScalarRepInd):
|
|
if event.explicitDocumentEnd: result &= " ..."
|
|
of yamlStartMap:
|
|
result = "+MAP" & renderAttrs(event.mapProperties, true)
|
|
of yamlStartSeq:
|
|
result = "+SEQ" & renderAttrs(event.seqProperties, true)
|
|
of yamlScalar:
|
|
when defined(yamlScalarRepInd):
|
|
result = "=VAL" & renderAttrs(event.scalarProperties,
|
|
event.scalarRep == srPlain)
|
|
case event.scalarRep
|
|
of srPlain: result &= " :"
|
|
of srSingleQuoted: result &= " \'"
|
|
of srDoubleQuoted: result &= " \""
|
|
of srLiteral: result &= " |"
|
|
of srFolded: result &= " >"
|
|
else:
|
|
let isPlain = event.scalarProperties.tag == yTagExclamationmark
|
|
result = "=VAL" & renderAttrs(event.scalarProperties, isPlain)
|
|
if isPlain: result &= " :"
|
|
else: result &= " \""
|
|
result &= yamlTestSuiteEscape(event.scalarContent)
|
|
of yamlAlias: result = "=ALI *" & $event.aliasTarget |