/ yaml / parser.nim
parser.nim
   1  #            NimYAML - YAML implementation in Nim
   2  #        (c) Copyright 2015-2023 Felix Krause
   3  #
   4  #    See the file "copying.txt", included in this
   5  #    distribution, for details about the copyright.
   6  
   7  ## ==================
   8  ## Module yaml/parser
   9  ## ==================
  10  ##
  11  ## This is the low-level parser API. A ``YamlParser`` enables you to parse any
  12  ## non-nil string or Stream object as YAML character stream.
  13  
  14  import tables, strutils, macros, streams
  15  import stream, private/lex, private/internal, private/escaping, data
  16  
  17  when defined(nimNoNil):
  18      {.experimental: "notnil".}
  19  
  20  type
  21    YamlParser* = object
  22      ## A parser object. Retains its ``TagLibrary`` across calls to
  23      ## `parse <#parse,YamlParser,Stream>`_. Can be used
  24      ## to access anchor names while parsing a YAML character stream, but
  25      ## only until the document goes out of scope (i.e. until
  26      ## ``yamlEndDocument`` is yielded).
  27      issueWarnings: bool
  28    
  29    YamlParserError* = object of YamlLoadingError
  30      ## A parser error is raised if the character stream that is parsed is
  31      ## not a valid YAML character stream. This stream cannot and will not be
  32      ## parsed wholly nor partially and all events that have been emitted by
  33      ## the YamlStream the parser provides should be discarded.
  34      ##
  35      ## A character stream is invalid YAML if and only if at least one of the
  36      ## following conditions apply:
  37      ##
  38      ## - There are invalid characters in an element whose contents is
  39      ##   restricted to a limited set of characters. For example, there are
  40      ##   characters in a tag URI which are not valid URI characters.
  41      ## - An element has invalid indentation. This can happen for example if
  42      ##   a block list element indicated by ``"- "`` is less indented than
  43      ##   the element in the previous line, but there is no block sequence
  44      ##   list open at the same indentation level.
  45      ## - The YAML structure is invalid. For example, an explicit block map
  46      ##   indicated by ``"? "`` and ``": "`` may not suddenly have a block
  47      ##   sequence item (``"- "``) at the same indentation level. Another
  48      ##   possible violation is closing a flow style object with the wrong
  49      ##   closing character (``}``, ``]``) or not closing it at all.
  50      ## - A custom tag shorthand is used that has not previously been
  51      ##   declared with a ``%TAG`` directive.
  52      ## - Multiple tags or anchors are defined for the same node.
  53      ## - An alias is used which does not map to any anchor that has
  54      ##   previously been declared in the same document.
  55      ## - An alias has a tag or anchor associated with it.
  56      ##
  57      ## Some elements in this list are vague. For a detailed description of a
  58      ## valid YAML character stream, see the YAML specification.
  59  
  60    State = proc(ctx: Context, e: var Event): bool {.gcSafe, raises: [CatchableError].}
  61  
  62    Level = object
  63      state: State
  64      indentation: int
  65  
  66    Context = ref object of YamlStream
  67      handles: seq[tuple[handle, uriPrefix: string]]
  68      issueWarnings: bool
  69      lex: Lexer
  70      levels: seq[Level]
  71      keyCache: seq[Event]
  72      keyCachePos: int
  73      caching: bool
  74  
  75      headerProps, inlineProps: Properties
  76      headerStart, inlineStart: Mark
  77      blockIndentation: int
  78  
  79  const defaultProperties = (yAnchorNone, yTagQuestionMark)
  80  
  81  # parser states
  82  
  83  {.push gcSafe, raises: [CatchableError].}
  84  proc atStreamStart(ctx: Context, e: var Event): bool
  85  proc atStreamEnd(ctx: Context, e : var Event): bool {.hint[XCannotRaiseY]: off.}
  86  proc beforeDoc(ctx: Context, e: var Event): bool
  87  proc beforeDocEnd(ctx: Context, e: var Event): bool
  88  proc afterDirectivesEnd(ctx: Context, e: var Event): bool
  89  proc beforeImplicitRoot(ctx: Context, e: var Event): bool
  90  proc atBlockIndentation(ctx: Context, e: var Event): bool
  91  proc beforeBlockIndentation(ctx: Context, e: var Event): bool
  92  proc beforeNodeProperties(ctx: Context, e: var Event): bool
  93  proc afterCompactParent(ctx: Context, e: var Event): bool
  94  proc afterCompactParentProps(ctx: Context, e: var Event): bool
  95  proc mergePropsOnNewline(ctx: Context, e: var Event): bool
  96  proc beforeFlowItemProps(ctx: Context, e: var Event): bool
  97  proc inBlockSeq(ctx: Context, e: var Event): bool
  98  proc beforeBlockMapValue(ctx: Context, e: var Event): bool
  99  proc atBlockIndentationProps(ctx: Context, e: var Event): bool
 100  proc beforeFlowItem(ctx: Context, e: var Event): bool
 101  proc afterFlowSeqSep(ctx: Context, e: var Event): bool
 102  proc afterFlowMapSep(ctx: Context, e: var Event): bool
 103  proc atBlockMapKeyProps(ctx: Context, e: var Event): bool
 104  proc afterImplicitKey(ctx: Context, e: var Event): bool
 105  proc afterBlockParent(ctx: Context, e: var Event): bool
 106  proc afterBlockParentProps(ctx: Context, e: var Event): bool
 107  proc afterImplicitPairStart(ctx: Context, e: var Event): bool
 108  proc beforePairValue(ctx: Context, e: var Event): bool
 109  proc atEmptyPairKey(ctx: Context, e: var Event): bool {.hint[XCannotRaiseY]: off.}
 110  proc afterFlowMapValue(ctx: Context, e: var Event): bool
 111  proc afterFlowSeqSepProps(ctx: Context, e: var Event): bool
 112  proc afterFlowSeqItem(ctx: Context, e: var Event): bool
 113  proc afterPairValue(ctx: Context, e: var Event): bool {.hint[XCannotRaiseY]: off.}
 114  proc emitCached(ctx: Context, e: var Event): bool {.hint[XCannotRaiseY]: off.}
 115  {.pop.}
 116  
 117  template pushLevel(ctx: Context, newState: State, newIndent: int) =
 118    debug("parser: push " & newState.astToStr & ", indent = " & $newIndent)
 119    ctx.levels.add(Level(state: newState, indentation: newIndent))
 120  
 121  template pushLevel(ctx: Context, newState: State) =
 122    debug("parser: push " & newState.astToStr)
 123    ctx.levels.add(Level(state: newState))
 124  
 125  template transition(ctx: Context, newState: State) =
 126    debug("parser: transition " & newState.astToStr)
 127    ctx.levels[^1].state = newState
 128  
 129  template transition(ctx: Context, newState: State, newIndent) =
 130    debug("parser: transtion " & newState.astToStr & ", indent = " & $newIndent)
 131    ctx.levels[^1] = Level(state: newState, indentation: newIndent)
 132  
 133  template updateIndentation(ctx: Context, newIndent: int) =
 134    debug("parser: update indent = " & $newIndent)
 135    ctx.levels[^1].indentation = newIndent
 136  
 137  template popLevel(ctx: Context) =
 138    debug("parser: pop")
 139    discard ctx.levels.pop()
 140  
 141  proc resolveHandle(ctx: Context, handle: string): string {.raises: [].} =
 142    for item in ctx.handles:
 143      if item.handle == handle:
 144        return item.uriPrefix
 145    return ""
 146  
 147  proc init[T](ctx: Context, p: YamlParser, source: T) {.inline.} =
 148    ctx.pushLevel(atStreamStart, -2)
 149    ctx.nextImpl = proc(s: YamlStream, e: var Event): bool {.raises: [CatchableError].} =
 150      let c = Context(s)
 151      return c.levels[^1].state(c, e)
 152    ctx.lastTokenContextImpl = proc(s: YamlStream, lineContent: var string): bool =
 153      lineContent = Context(s).lex.currentLine()
 154      return true
 155    ctx.headerProps = defaultProperties
 156    ctx.inlineProps = defaultProperties
 157    ctx.issueWarnings = p.issueWarnings
 158    ctx.lex.init(source)
 159    ctx.keyCachePos = 0
 160    ctx.caching = false
 161  
 162  # interface
 163  
 164  proc init*(p: var YamlParser, issueWarnings: bool = false) =
 165    ## Initializes a YAML parser.
 166    p.issueWarnings = issueWarnings
 167  
 168  proc initYamlParser*(issueWarnings: bool = false): YamlParser =
 169    ## Creates an initializes YAML parser and returns it
 170    result.issueWarnings = issueWarnings
 171  
 172  proc parse*(p: YamlParser, s: Stream): YamlStream =
 173    let ctx = new(Context)
 174    ctx.init(p, s)
 175    return ctx
 176  
 177  proc parse*(p: YamlParser, s: string): YamlStream =
 178    let ctx = new(Context)
 179    ctx.init(p, s)
 180    return ctx
 181  
 182  # implementation
 183  
 184  proc isEmpty(props: Properties): bool =
 185    result = props.anchor == yAnchorNone and
 186             props.tag == yTagQuestionMark
 187  
 188  proc generateError(ctx: Context, message: string):
 189      ref YamlParserError {.raises: [], .} =
 190    result = (ref YamlParserError)(
 191      msg: message, parent: nil, mark: ctx.lex.curStartPos,
 192      lineContent: ctx.lex.currentLine())
 193  
 194  proc safeNext(ctx: Context) =
 195    try:
 196      ctx.lex.next()
 197    except LexerError as e:
 198      raise (ref YamlParserError)(
 199        msg: e.msg, parent: nil, mark: Mark(line: e.line, column: e.column),
 200        lineContent: e.lineContent)
 201  
 202  proc parseTag(ctx: Context): Tag =
 203    let handle = ctx.lex.fullLexeme()
 204    var uri = ctx.resolveHandle(handle)
 205    if uri == "":
 206      raise ctx.generateError("unknown handle: " & escape(handle))
 207    ctx.safeNext()
 208    if ctx.lex.cur != Token.Suffix:
 209      raise ctx.generateError("unexpected token (expected tag suffix): " & $ctx.lex.cur)
 210    uri.add(ctx.lex.evaluated)
 211    return Tag(uri)
 212  
 213  proc toStyle(t: Token): ScalarStyle =
 214    return (case t
 215      of Plain: ssPlain
 216      of SingleQuoted: ssSingleQuoted
 217      of DoubleQuoted: ssDoubleQuoted
 218      of Literal: ssLiteral
 219      of Folded: ssFolded
 220      else: ssAny)
 221  
 222  proc mergeProps(ctx: Context, src, target: var Properties) =
 223    if src.tag != yTagQuestionMark:
 224      if target.tag != yTagQuestionMark:
 225        raise ctx.generateError("Only one tag allowed per node")
 226      target.tag = src.tag
 227      src.tag = yTagQuestionMark
 228    if src.anchor != yAnchorNone:
 229      if target.anchor != yAnchorNone:
 230        raise ctx.generateError("Only one anchor allowed per node")
 231      target.anchor = src.anchor
 232      src.anchor = yAnchorNone
 233  
 234  proc autoScalarTag(props: Properties, t: Token): Properties =
 235    result = props
 236    if t in {Token.SingleQuoted, Token.DoubleQuoted} and
 237        props.tag == yTagQuestionMark:
 238      result.tag = yTagExclamationMark
 239  
 240  proc atStreamStart(ctx: Context, e: var Event): bool =
 241    ctx.transition(atStreamEnd)
 242    ctx.pushLevel(beforeDoc, -1)
 243    e = Event(startPos: ctx.lex.curStartPos, endPos: ctx.lex.curStartPos, kind: yamlStartStream)
 244    ctx.safeNext()
 245    resetHandles(ctx.handles)
 246    return true
 247  
 248  proc atStreamEnd(ctx: Context, e : var Event): bool =
 249    e = Event(startPos: ctx.lex.curStartPos,
 250              endPos: ctx.lex.curStartPos, kind: yamlEndStream)
 251    return true
 252  
 253  proc beforeDoc(ctx: Context, e: var Event): bool =
 254    var version = ""
 255    var seenDirectives = false
 256    while true:
 257      case ctx.lex.cur
 258      of DocumentEnd:
 259        if seenDirectives:
 260          raise ctx.generateError("Missing `---` after directives")
 261        ctx.safeNext()
 262      of DirectivesEnd:
 263        e = startDocEvent(true, version, ctx.handles, ctx.lex.curStartPos, ctx.lex.curEndPos)
 264        ctx.safeNext()
 265        ctx.transition(beforeDocEnd)
 266        ctx.pushLevel(afterDirectivesEnd, -1)
 267        return true
 268      of StreamEnd:
 269        if seenDirectives:
 270          raise ctx.generateError("Missing `---` after directives")
 271        ctx.popLevel()
 272        return false
 273      of Indentation:
 274        e = startDocEvent(false, version, ctx.handles, ctx.lex.curStartPos, ctx.lex.curEndPos)
 275        ctx.transition(beforeDocEnd)
 276        ctx.pushLevel(beforeImplicitRoot, -1)
 277        return true
 278      of YamlDirective:
 279        seenDirectives = true
 280        ctx.safeNext()
 281        if ctx.lex.cur != Token.DirectiveParam:
 282          raise ctx.generateError("Invalid token (expected YAML version string): " & $ctx.lex.cur)
 283        elif version != "":
 284          raise ctx.generateError("Duplicate %YAML")
 285        version = ctx.lex.fullLexeme()
 286        if version != "1.2" and ctx.issueWarnings:
 287          discard # TODO
 288        ctx.safeNext()
 289      of TagDirective:
 290        seenDirectives = true
 291        ctx.safeNext()
 292        if ctx.lex.cur != Token.TagHandle:
 293          raise ctx.generateError("Invalid token (expected tag handle): " & $ctx.lex.cur)
 294        let tagHandle = ctx.lex.fullLexeme()
 295        ctx.safeNext()
 296        if ctx.lex.cur != Token.Suffix:
 297          raise ctx.generateError("Invalid token (expected tag URI): " & $ctx.lex.cur)
 298        discard registerHandle(ctx.handles, tagHandle, ctx.lex.evaluated)
 299        ctx.safeNext()
 300      of UnknownDirective:
 301        seenDirectives = true
 302        # TODO: issue warning
 303        while true:
 304          ctx.safeNext()
 305          if ctx.lex.cur != Token.DirectiveParam: break
 306      else:
 307        raise ctx.generateError("Unexpected token (expected directive or document start): " & $ctx.lex.cur)
 308  
 309  proc afterDirectivesEnd(ctx: Context, e: var Event): bool =
 310    case ctx.lex.cur
 311    of nodePropertyKind:
 312      ctx.inlineStart = ctx.lex.curStartPos
 313      ctx.pushLevel(beforeNodeProperties)
 314      return false
 315    of Indentation:
 316      ctx.headerStart = ctx.inlineStart
 317      ctx.transition(atBlockIndentation)
 318      ctx.pushLevel(beforeBlockIndentation)
 319      return false
 320    of DocumentEnd, DirectivesEnd, StreamEnd:
 321      e = scalarEvent("", ctx.inlineProps, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
 322      ctx.popLevel()
 323      return true
 324    of scalarTokenKind:
 325      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 326                      toStyle(ctx.lex.cur), ctx.lex.curStartPos, ctx.lex.curEndPos)
 327      ctx.popLevel()
 328      ctx.safeNext()
 329      return true
 330    else:
 331      raise ctx.generateError("Illegal content at `---`: " & $ctx.lex.cur)
 332  
 333  proc beforeImplicitRoot(ctx: Context, e: var Event): bool =
 334    if ctx.lex.cur != Token.Indentation:
 335      raise ctx.generateError("Unexpected token (expected line start): " & $ctx.lex.cur)
 336    ctx.inlineStart = ctx.lex.curEndPos
 337    ctx.headerStart = ctx.lex.curEndPos
 338    ctx.updateIndentation(ctx.lex.recentIndentation())
 339    ctx.safeNext()
 340    case ctx.lex.cur
 341    of SeqItemInd, MapKeyInd, MapValueInd:
 342      ctx.transition(afterCompactParent)
 343      return false
 344    of scalarTokenKind, MapStart, SeqStart:
 345      ctx.transition(atBlockIndentationProps)
 346      return false
 347    of nodePropertyKind:
 348      ctx.transition(atBlockIndentationProps)
 349      ctx.pushLevel(beforeNodeProperties)
 350    else:
 351      raise ctx.generateError("Unexpected token (expected collection start): " & $ctx.lex.cur)
 352  
 353  proc atBlockIndentation(ctx: Context, e: var Event): bool =
 354    if ctx.blockIndentation == ctx.levels[^1].indentation and
 355        (ctx.lex.cur != Token.SeqItemInd or
 356         ctx.levels[^3].state == inBlockSeq):
 357      e = scalarEvent("", ctx.headerProps, ssPlain,
 358                      ctx.headerStart, ctx.headerStart)
 359      ctx.headerProps = defaultProperties
 360      ctx.popLevel()
 361      ctx.popLevel()
 362      return true
 363    ctx.inlineStart = ctx.lex.curStartPos
 364    ctx.updateIndentation(ctx.lex.recentIndentation())
 365    case ctx.lex.cur
 366    of nodePropertyKind:
 367      if isEmpty(ctx.headerProps):
 368        ctx.transition(mergePropsOnNewline)
 369      else:
 370        ctx.transition(atBlockIndentationProps)
 371      ctx.pushLevel(beforeNodeProperties)
 372      return false
 373    of SeqItemInd:
 374      e = startSeqEvent(csBlock, ctx.headerProps,
 375                        ctx.headerStart, ctx.lex.curEndPos)
 376      ctx.headerProps = defaultProperties
 377      ctx.transition(inBlockSeq, ctx.lex.recentIndentation())
 378      ctx.pushLevel(beforeBlockIndentation)
 379      ctx.pushLevel(afterCompactParent, ctx.lex.recentIndentation())
 380      ctx.safeNext()
 381      return true
 382    of MapKeyInd:
 383      e = startMapEvent(csBlock, ctx.headerProps,
 384                        ctx.headerStart, ctx.lex.curEndPos)
 385      ctx.headerProps = defaultProperties
 386      ctx.transition(beforeBlockMapValue, ctx.lex.recentIndentation())
 387      ctx.pushLevel(beforeBlockIndentation)
 388      ctx.pushLevel(afterCompactParent, ctx.lex.recentIndentation())
 389      ctx.safeNext()
 390      return true
 391    of Plain, SingleQuoted, DoubleQuoted:
 392      ctx.updateIndentation(ctx.lex.recentIndentation())
 393      let scalarToken = ctx.lex.cur
 394      e = scalarEvent(ctx.lex.evaluated, ctx.headerProps,
 395                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 396      ctx.headerProps = defaultProperties
 397      let headerEnd = ctx.lex.curStartPos
 398      ctx.safeNext()
 399      if ctx.lex.cur == Token.MapValueInd:
 400        if ctx.lex.lastScalarWasMultiline():
 401          raise ctx.generateError("Implicit mapping key may not be multiline")
 402        let props = e.scalarProperties
 403        e.scalarProperties = autoScalarTag(defaultProperties, scalarToken)
 404        ctx.keyCache.add(move(e))
 405        e = startMapEvent(csBlock, props, ctx.headerStart, headerEnd)
 406        ctx.transition(afterImplicitKey)
 407        ctx.pushLevel(emitCached)
 408      else:
 409        e.scalarProperties = autoScalarTag(e.scalarProperties, scalarToken)
 410        ctx.popLevel()
 411      return true
 412    of Alias:
 413      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 414      ctx.inlineProps = defaultProperties
 415      let headerEnd = ctx.lex.curStartPos
 416      ctx.safeNext()
 417      if ctx.lex.cur == Token.MapValueInd:
 418        ctx.keyCache.add(move(e))
 419        e = startMapEvent(csBlock, ctx.headerProps, ctx.headerStart, headerEnd)
 420        ctx.headerProps = defaultProperties
 421        ctx.transition(afterImplicitKey)
 422        ctx.pushLevel(emitCached)
 423      elif not isEmpty(ctx.headerProps):
 424        raise ctx.generateError("Alias may not have properties")
 425      else:
 426        ctx.popLevel()
 427      return true
 428    else:
 429      ctx.transition(atBlockIndentationProps)
 430      return false
 431  
 432  proc atBlockIndentationProps(ctx: Context, e: var Event): bool =
 433    ctx.updateIndentation(ctx.lex.recentIndentation())
 434    case ctx.lex.cur
 435    of MapValueInd:
 436      ctx.keyCache.add(scalarEvent("", ctx.inlineProps, ssPlain, ctx.inlineStart, ctx.lex.curEndPos))
 437      ctx.inlineProps = defaultProperties
 438      e = startMapEvent(csBlock, ctx.headerProps, ctx.lex.curStartPos, ctx.lex.curEndPos)
 439      ctx.headerProps = defaultProperties
 440      ctx.transition(afterImplicitKey)
 441      ctx.pushLevel(emitCached)
 442      return true
 443    of Plain, SingleQuoted, DoubleQuoted:
 444      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 445                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 446      ctx.inlineProps = defaultProperties
 447      let headerEnd = ctx.lex.curStartPos
 448      ctx.safeNext()
 449      if ctx.lex.cur == Token.MapValueInd:
 450        if ctx.lex.lastScalarWasMultiline():
 451          raise ctx.generateError("Implicit mapping key may not be multiline")
 452        ctx.keyCache.add(move(e))
 453        e = startMapEvent(csBlock, ctx.headerProps, ctx.headerStart, headerEnd)
 454        ctx.headerProps = defaultProperties
 455        ctx.transition(afterImplicitKey)
 456        ctx.pushLevel(emitCached)
 457      else:
 458        ctx.mergeProps(ctx.headerProps, e.scalarProperties)
 459        ctx.popLevel()
 460      return true
 461    of MapStart, SeqStart:
 462      let
 463        startPos = ctx.lex.curStartPos
 464        indent = ctx.lex.currentIndentation()
 465        levelDepth = ctx.levels.len
 466      ctx.transition(beforeFlowItemProps)
 467      ctx.caching = true
 468      while ctx.levels.len >= levelDepth:
 469        ctx.keyCache.add(ctx.next())
 470      ctx.caching = false
 471      if ctx.lex.cur == Token.MapValueInd:
 472        ctx.pushLevel(afterImplicitKey, indent)
 473        ctx.pushLevel(emitCached)
 474        if ctx.lex.curStartPos.line != startPos.line:
 475          raise ctx.generateError("Implicit mapping key may not be multiline")
 476        e = startMapEvent(csBlock, ctx.headerProps, ctx.headerStart, startPos)
 477        ctx.headerProps = defaultProperties
 478        return true
 479      else:
 480        ctx.pushLevel(emitCached)
 481        return false
 482    of Literal, Folded:
 483      ctx.mergeProps(ctx.inlineProps, ctx.headerProps)
 484      e = scalarEvent(ctx.lex.evaluated, ctx.headerProps, toStyle(ctx.lex.cur),
 485                      ctx.inlineStart, ctx.lex.curEndPos)
 486      ctx.headerProps = defaultProperties
 487      ctx.safeNext()
 488      ctx.popLevel()
 489      return true
 490    of Indentation:
 491      ctx.safeNext()
 492      ctx.transition(atBlockIndentation)
 493      return false
 494    of StreamEnd, DocumentEnd, DirectivesEnd:
 495      e = scalarEvent("", ctx.inlineProps, ssPlain, ctx.inlineStart, ctx.lex.curStartPos)
 496      ctx.inlineProps = defaultProperties
 497      ctx.popLevel()
 498      return true
 499    else:
 500      raise ctx.generateError("Unexpected token (expected block content): " & $ctx.lex.cur)
 501  
 502  proc beforeNodeProperties(ctx: Context, e: var Event): bool =
 503    case ctx.lex.cur
 504    of TagHandle:
 505      if ctx.inlineProps.tag != yTagQuestionMark:
 506        raise ctx.generateError("Only one tag allowed per node")
 507      ctx.inlineProps.tag = ctx.parseTag()
 508    of VerbatimTag:
 509      if ctx.inlineProps.tag != yTagQuestionMark:
 510        raise ctx.generateError("Only one tag allowed per node")
 511      ctx.inlineProps.tag = Tag(move(ctx.lex.evaluated))
 512    of Token.Anchor:
 513      if ctx.inlineProps.anchor != yAnchorNone:
 514        raise ctx.generateError("Only one anchor allowed per node")
 515      ctx.inlineProps.anchor = ctx.lex.shortLexeme().Anchor
 516    of Indentation:
 517      ctx.mergeProps(ctx.inlineProps, ctx.headerProps)
 518      ctx.popLevel()
 519      return false
 520    of Alias:
 521      raise ctx.generateError("Alias may not have node properties")
 522    else:
 523      ctx.popLevel()
 524      return false
 525    ctx.safeNext()
 526    return false
 527  
 528  proc afterCompactParent(ctx: Context, e: var Event): bool =
 529    ctx.inlineStart = ctx.lex.curStartPos
 530    case ctx.lex.cur
 531    of nodePropertyKind:
 532      ctx.transition(afterCompactParentProps)
 533      ctx.pushLevel(beforeNodeProperties)
 534    of SeqItemInd:
 535      e = startSeqEvent(csBlock, ctx.headerProps, ctx.headerStart, ctx.lex.curEndPos)
 536      ctx.headerProps = defaultProperties
 537      ctx.transition(inBlockSeq, ctx.lex.recentIndentation())
 538      ctx.pushLevel(beforeBlockIndentation)
 539      ctx.pushLevel(afterCompactParent, ctx.lex.recentIndentation())
 540      ctx.safeNext()
 541      return true
 542    of MapKeyInd:
 543      e = startMapEvent(csBlock, ctx.headerProps, ctx.headerStart, ctx.lex.curEndPos)
 544      ctx.headerProps = defaultProperties
 545      ctx.transition(beforeBlockMapValue, ctx.lex.recentIndentation())
 546      ctx.pushLevel(beforeBlockIndentation)
 547      ctx.pushLevel(afterCompactParent, ctx.lex.recentIndentation)
 548      ctx.safeNext()
 549      return true
 550    else:
 551      ctx.transition(afterCompactParentProps)
 552      return false
 553  
 554  proc afterCompactParentProps(ctx: Context, e: var Event): bool =
 555    ctx.updateIndentation(ctx.lex.recentIndentation())
 556    case ctx.lex.cur
 557    of nodePropertyKind:
 558      ctx.pushLevel(beforeNodeProperties)
 559      return false
 560    of Indentation:
 561      ctx.headerStart = ctx.inlineStart
 562      ctx.transition(atBlockIndentation, ctx.levels[^3].indentation)
 563      ctx.pushLevel(beforeBlockIndentation)
 564      return false
 565    of MapValueInd:
 566      ctx.keyCache.add(scalarEvent("", ctx.inlineProps, ssPlain, ctx.inlineStart, ctx.lex.curStartPos))
 567      ctx.inlineProps = defaultProperties
 568      e = startMapEvent(csBlock, defaultProperties, ctx.lex.curStartPos, ctx.lex.curStartPos)
 569      ctx.transition(afterImplicitKey)
 570      ctx.pushLevel(emitCached)
 571      return true
 572    of Alias:
 573      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 574      let headerEnd = ctx.lex.curStartPos
 575      ctx.safeNext()
 576      if ctx.lex.cur == Token.MapValueInd:
 577        ctx.keyCache.add(move(e))
 578        e = startMapEvent(csBlock, defaultProperties, headerEnd, headerEnd)
 579        ctx.transition(afterImplicitKey)
 580        ctx.pushLevel(emitCached)
 581      else:
 582        ctx.popLevel()
 583      return true
 584    of scalarTokenKind:
 585      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 586                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 587      ctx.inlineProps = defaultProperties
 588      let headerEnd = ctx.lex.curStartPos
 589      ctx.updateIndentation(ctx.lex.recentIndentation())
 590      ctx.safeNext()
 591      if ctx.lex.cur == Token.MapValueInd:
 592        if ctx.lex.lastScalarWasMultiline():
 593          raise ctx.generateError("Implicit mapping key may not be multiline")
 594        ctx.keyCache.add(move(e))
 595        e = startMapEvent(csBlock, defaultProperties, headerEnd, headerEnd)
 596        ctx.transition(afterImplicitKey)
 597        ctx.pushLevel(emitCached)
 598      else:
 599        ctx.popLevel()
 600      return true
 601    of MapStart, SeqStart, StreamEnd, DocumentEnd, DirectivesEnd:
 602      ctx.transition(atBlockIndentationProps)
 603      return false
 604    else:
 605      raise ctx.generateError("Unexpected token (expected newline or flow item start: " & $ctx.lex.cur)
 606  
 607  proc afterBlockParent(ctx: Context, e: var Event): bool =
 608    ctx.inlineStart = ctx.lex.curStartPos
 609    case ctx.lex.cur
 610    of nodePropertyKind:
 611      ctx.transition(afterBlockParentProps)
 612      ctx.pushLevel(beforeNodeProperties)
 613    of SeqItemInd, MapKeyInd:
 614      raise ctx.generateError("Compact notation not allowed after implicit key")
 615    else:
 616      ctx.transition(afterBlockParentProps)
 617    return false
 618  
 619  proc afterBlockParentProps(ctx: Context, e: var Event): bool =
 620    ctx.updateIndentation(ctx.lex.recentIndentation())
 621    case ctx.lex.cur
 622    of nodePropertyKind:
 623      ctx.pushLevel(beforeNodeProperties)
 624      return false
 625    of MapValueInd:
 626      raise ctx.generateError("Compact notation not allowed after implicit key")
 627    of scalarTokenKind:
 628      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 629                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 630      ctx.inlineProps = defaultProperties
 631      ctx.safeNext()
 632      if ctx.lex.cur == Token.MapValueInd:
 633        raise ctx.generateError("Compact notation not allowed after implicit key")
 634      ctx.popLevel()
 635      return true
 636    else:
 637      ctx.transition(afterCompactParentProps)
 638      return false
 639  
 640  proc mergePropsOnNewline(ctx: Context, e: var Event): bool =
 641    ctx.updateIndentation(ctx.lex.recentIndentation())
 642    if ctx.lex.cur == Token.Indentation:
 643      ctx.mergeProps(ctx.inlineProps, ctx.headerProps)
 644    ctx.transition(afterCompactParentProps)
 645    return false
 646  
 647  proc beforeDocEnd(ctx: Context, e: var Event): bool =
 648    case ctx.lex.cur
 649    of DocumentEnd:
 650      e = endDocEvent(true, ctx.lex.curStartPos, ctx.lex.curEndPos)
 651      ctx.transition(beforeDoc)
 652      ctx.safeNext()
 653      resetHandles(ctx.handles)
 654    of StreamEnd:
 655      e = endDocEvent(false, ctx.lex.curStartPos, ctx.lex.curEndPos)
 656      ctx.popLevel()
 657    of DirectivesEnd:
 658      e = endDocEvent(false, ctx.lex.curStartPos, ctx.lex.curStartPos)
 659      ctx.transition(beforeDoc)
 660      resetHandles(ctx.handles)
 661    else:
 662      raise ctx.generateError("Unexpected token (expected document end): " & $ctx.lex.cur)
 663    return true
 664  
 665  proc inBlockSeq(ctx: Context, e: var Event): bool =
 666    if ctx.blockIndentation > ctx.levels[^1].indentation:
 667      raise ctx.generateError("Invalid indentation: got " & $ctx.blockIndentation & ", expected " & $ctx.levels[^1].indentation)
 668    case ctx.lex.cur
 669    of SeqItemInd:
 670      ctx.safeNext()
 671      ctx.pushLevel(beforeBlockIndentation)
 672      ctx.pushLevel(afterCompactParent, ctx.blockIndentation)
 673      return false
 674    else:
 675      if ctx.levels[^3].indentation == ctx.levels[^1].indentation:
 676        e = endSeqEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 677        ctx.popLevel()
 678        ctx.popLevel()
 679        return true
 680      else:
 681        raise ctx.generateError("Illegal token (expected block sequence indicator): " & $ctx.lex.cur)
 682  
 683  proc beforeBlockMapKey(ctx: Context, e: var Event): bool =
 684    if ctx.blockIndentation > ctx.levels[^1].indentation:
 685      raise ctx.generateError("Invalid indentation: got " & $ctx.blockIndentation & ", expected " & $ctx.levels[^1].indentation)
 686    ctx.inlineStart = ctx.lex.curStartPos
 687    case ctx.lex.cur
 688    of MapKeyInd:
 689      ctx.transition(beforeBlockMapValue)
 690      ctx.pushLevel(beforeBlockIndentation)
 691      ctx.pushLevel(afterCompactParent, ctx.blockIndentation)
 692      ctx.safeNext()
 693      return false
 694    of nodePropertyKind:
 695      ctx.transition(atBlockMapKeyProps)
 696      ctx.pushLevel(beforeNodeProperties)
 697      return false
 698    of Plain, SingleQuoted, DoubleQuoted:
 699      ctx.transition(atBlockMapKeyProps)
 700      return false
 701    of Alias:
 702      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 703      ctx.safeNext()
 704      ctx.transition(afterImplicitKey)
 705      return true
 706    of MapValueInd:
 707      e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
 708      ctx.transition(beforeBlockMapValue)
 709      return true
 710    else:
 711      raise ctx.generateError("Unexpected token (expected mapping key): " & $ctx.lex.cur)
 712  
 713  proc atBlockMapKeyProps(ctx: Context, e: var Event): bool =
 714    case ctx.lex.cur
 715    of nodePropertyKind:
 716      ctx.pushLevel(beforeNodeProperties)
 717    of Alias:
 718      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 719    of Plain, SingleQuoted, DoubleQuoted:
 720      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 721                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 722      ctx.inlineProps = defaultProperties
 723      if ctx.lex.lastScalarWasMultiline():
 724        raise ctx.generateError("Implicit mapping key may not be multiline")
 725    of MapValueInd:
 726      e = scalarEvent("", ctx.inlineProps, ssPlain, ctx.inlineStart, ctx.lex.curStartPos)
 727      ctx.inlineProps = defaultProperties
 728      ctx.transition(afterImplicitKey)
 729      return true
 730    else:
 731      raise ctx.generateError("Unexpected token (expected implicit mapping key): " & $ctx.lex.cur)
 732    ctx.safeNext()
 733    ctx.transition(afterImplicitKey)
 734    return true
 735  
 736  proc afterImplicitKey(ctx: Context, e: var Event): bool =
 737    if ctx.lex.cur != Token.MapValueInd:
 738      raise ctx.generateError("Unexpected token (expected ':'): " & $ctx.lex.cur)
 739    ctx.safeNext()
 740    ctx.transition(beforeBlockMapKey)
 741    ctx.pushLevel(beforeBlockIndentation)
 742    ctx.pushLevel(afterBlockParent, max(0, ctx.levels[^2].indentation))
 743    return false
 744  
 745  proc beforeBlockMapValue(ctx: Context, e: var Event): bool =
 746    if ctx.blockIndentation > ctx.levels[^1].indentation:
 747      raise ctx.generateError("Invalid indentation")
 748    case ctx.lex.cur
 749    of MapValueInd:
 750      ctx.transition(beforeBlockMapKey)
 751      ctx.pushLevel(beforeBlockIndentation)
 752      ctx.pushLevel(afterCompactParent, ctx.blockIndentation)
 753      ctx.safeNext()
 754    of MapKeyInd, Plain, SingleQuoted, DoubleQuoted, nodePropertyKind:
 755      # the value is allowed to be missing after an explicit key
 756      e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
 757      ctx.transition(beforeBlockMapKey)
 758      return true
 759    else:
 760      raise ctx.generateError("Unexpected token (expected mapping value): " & $ctx.lex.cur)
 761  
 762  proc beforeBlockIndentation(ctx: Context, e: var Event): bool =
 763    proc endBlockNode(e: var Event) =
 764      if ctx.levels[^1].state == beforeBlockMapKey:
 765        e = endMapEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 766      elif ctx.levels[^1].state == beforeBlockMapValue:
 767        e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
 768        ctx.transition(beforeBlockMapKey)
 769        ctx.pushLevel(beforeBlockIndentation)
 770        return
 771      elif ctx.levels[^1].state == inBlockSeq:
 772        e = endSeqEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 773      elif ctx.levels[^1].state == atBlockIndentation:
 774        e = scalarEvent("", ctx.headerProps, ssPlain, ctx.headerStart, ctx.headerStart)
 775        ctx.headerProps = defaultProperties
 776      elif ctx.levels[^1].state == beforeBlockIndentation:
 777        raise ctx.generateError("Unexpected double beforeBlockIndentation")
 778      else:
 779        raise ctx.generateError("Internal error (please report this bug): unexpected state at endBlockNode")
 780      ctx.popLevel()
 781    ctx.popLevel()
 782    case ctx.lex.cur
 783    of Indentation:
 784      ctx.blockIndentation = ctx.lex.currentIndentation()
 785      if ctx.blockIndentation < ctx.levels[^1].indentation:
 786        endBlockNode(e)
 787        return true
 788      else:
 789        ctx.safeNext()
 790        return false
 791    of StreamEnd, DocumentEnd, DirectivesEnd:
 792      ctx.blockIndentation = 0
 793      if ctx.levels[^1].state != beforeDocEnd:
 794        endBlockNode(e)
 795        return true
 796      else:
 797        return false
 798    else:
 799      raise ctx.generateError("Unexpected content after node in block context (expected newline): " & $ctx.lex.cur)
 800  
 801  proc beforeFlowItem(ctx: Context, e: var Event): bool =
 802    ctx.inlineStart = ctx.lex.curStartPos
 803    case ctx.lex.cur
 804    of nodePropertyKind:
 805      ctx.transition(beforeFlowItemProps)
 806      ctx.pushLevel(beforeNodeProperties)
 807    of Alias:
 808      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 809      ctx.safeNext()
 810      ctx.popLevel()
 811      return true
 812    else:
 813      ctx.transition(beforeFlowItemProps)
 814    return false
 815  
 816  proc beforeFlowItemProps(ctx: Context, e: var Event): bool =
 817    case ctx.lex.cur
 818    of nodePropertyKind:
 819      ctx.pushLevel(beforeNodeProperties)
 820    of Alias:
 821      e = aliasEvent(ctx.lex.shortLexeme().Anchor, ctx.inlineStart, ctx.lex.curEndPos)
 822      ctx.safeNext()
 823      ctx.popLevel()
 824    of scalarTokenKind:
 825      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 826                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 827      ctx.inlineProps = defaultProperties
 828      ctx.safeNext()
 829      ctx.popLevel()
 830    of MapStart:
 831      e = startMapEvent(csFlow, ctx.inlineProps, ctx.inlineStart, ctx.lex.curEndPos)
 832      ctx.transition(afterFlowMapSep)
 833      ctx.safeNext()
 834    of SeqStart:
 835      e = startSeqEvent(csFlow, ctx.inlineProps, ctx.inlineStart, ctx.lex.curEndPos)
 836      ctx.transition(afterFlowSeqSep)
 837      ctx.safeNext()
 838    of MapEnd, SeqEnd, SeqSep, MapValueInd:
 839      e = scalarEvent("", ctx.inlineProps, ssPlain, ctx.inlineStart, ctx.lex.curEndPos)
 840      ctx.popLevel()
 841    else:
 842      raise ctx.generateError("Unexpected token (expected flow node): " & $ctx.lex.cur)
 843    ctx.inlineProps = defaultProperties
 844    return true
 845  
 846  proc afterFlowMapKey(ctx: Context, e: var Event): bool =
 847    case ctx.lex.cur
 848    of MapValueInd:
 849      ctx.transition(afterFlowMapValue)
 850      ctx.pushLevel(beforeFlowItem)
 851      ctx.safeNext()
 852      return false
 853    of SeqSep, MapEnd:
 854      e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
 855      ctx.transition(afterFlowMapValue)
 856      return true
 857    else:
 858      raise ctx.generateError("Unexpected token (expected ':'): " & $ctx.lex.cur)
 859  
 860  proc afterFlowMapValue(ctx: Context, e: var Event): bool =
 861    case ctx.lex.cur
 862    of SeqSep:
 863      ctx.transition(afterFlowMapSep)
 864      ctx.safeNext()
 865      return false
 866    of MapEnd:
 867      e = endMapEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 868      ctx.safeNext()
 869      ctx.popLevel()
 870      return true
 871    of Plain, SingleQuoted, DoubleQuoted, MapKeyInd, Token.Anchor, Alias, MapStart, SeqStart:
 872      raise ctx.generateError("Missing ','")
 873    else:
 874      raise ctx.generateError("Unexpected token (expected ',' or '}'): " & $ctx.lex.cur)
 875  
 876  proc afterFlowSeqItem(ctx: Context, e: var Event): bool =
 877    case ctx.lex.cur
 878    of SeqSep:
 879      ctx.transition(afterFlowSeqSep)
 880      ctx.safeNext()
 881      return false
 882    of SeqEnd:
 883      e = endSeqEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 884      ctx.safeNext()
 885      ctx.popLevel()
 886      return true
 887    of Plain, SingleQuoted, DoubleQuoted, MapKeyInd, Token.Anchor, Alias, MapStart, SeqStart:
 888      raise ctx.generateError("Missing ','")
 889    else:
 890      raise ctx.generateError("Unexpected token (expected ',' or ']'): " & $ctx.lex.cur)
 891  
 892  proc afterFlowMapSep(ctx: Context, e: var Event): bool =
 893    case ctx.lex.cur
 894    of MapKeyInd:
 895      ctx.safeNext()
 896    of MapEnd:
 897      e = endMapEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 898      ctx.safeNext()
 899      ctx.popLevel()
 900      return true
 901    of SeqSep:
 902      raise ctx.generateError("Missing mapping entry between commas (use '?' for an empty mapping entry)")
 903    else: discard
 904    ctx.transition(afterFlowMapKey)
 905    ctx.pushLevel(beforeFlowItem)
 906    return false
 907  
 908  proc afterFlowSeqSep(ctx: Context, e: var Event): bool =
 909    ctx.inlineStart = ctx.lex.curStartPos
 910    case ctx.lex.cur
 911    of SeqSep:
 912      e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curStartPos)
 913      ctx.safeNext()
 914      return true
 915    of nodePropertyKind:
 916      ctx.transition(afterFlowSeqSepProps)
 917      ctx.pushLevel(beforeNodeProperties)
 918      return false
 919    of Plain, SingleQuoted, DoubleQuoted, MapStart, SeqStart:
 920      ctx.transition(afterFlowSeqSepProps)
 921      return false
 922    of MapKeyInd:
 923      ctx.transition(afterFlowSeqSepProps)
 924      e = startMapEvent(csFlow, defaultProperties, ctx.lex.curStartPos, ctx.lex.curEndPos)
 925      ctx.safeNext()
 926      ctx.transition(afterFlowSeqItem)
 927      ctx.pushLevel(beforePairValue)
 928      ctx.pushLevel(beforeFlowItem)
 929      return true
 930    of MapValueInd:
 931      ctx.transition(afterFlowSeqItem)
 932      e = startMapEvent(csFlow, defaultProperties, ctx.lex.curStartPos, ctx.lex.curEndPos)
 933      ctx.pushLevel(atEmptyPairKey)
 934      return true
 935    of SeqEnd:
 936      e = endSeqEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
 937      ctx.safeNext()
 938      ctx.popLevel()
 939      return true
 940    else:
 941      ctx.transition(afterFlowSeqItem)
 942      ctx.pushLevel(beforeFlowItem)
 943      return false
 944  
 945  proc afterFlowSeqSepProps(ctx: Context, e: var Event): bool =
 946    # here we handle potential implicit single pairs within flow sequences.
 947    ctx.transition(afterFlowSeqItem)
 948    case ctx.lex.cur
 949    of Plain, SingleQuoted, DoubleQuoted:
 950      e = scalarEvent(ctx.lex.evaluated, autoScalarTag(ctx.inlineProps, ctx.lex.cur),
 951                      toStyle(ctx.lex.cur), ctx.inlineStart, ctx.lex.curEndPos)
 952      ctx.inlineProps = defaultProperties
 953      ctx.safeNext()
 954      if ctx.lex.cur == Token.MapValueInd:
 955        ctx.pushLevel(afterImplicitPairStart)
 956        if ctx.caching:
 957          ctx.keyCache.add(startMapEvent(csFlow, defaultProperties, ctx.lex.curStartPos, ctx.lex.curStartPos))
 958        else:
 959          ctx.keyCache.add(move(e))
 960          e = startMapEvent(csFlow, defaultProperties, ctx.lex.curStartPos, ctx.lex.curStartPos)
 961          ctx.pushLevel(emitCached)
 962      return true
 963    of MapStart, SeqStart:
 964      let
 965        startPos = ctx.lex.curStartPos
 966        indent = ctx.levels[^1].indentation
 967        cacheStart = ctx.keyCache.len
 968        levelDepth = ctx.levels.len
 969        alreadyCaching = ctx.caching
 970      ctx.pushLevel(beforeFlowItemProps)
 971      ctx.caching = true
 972      while ctx.levels.len > levelDepth:
 973        ctx.keyCache.add(ctx.next())
 974      ctx.caching = alreadyCaching
 975      if ctx.lex.cur == Token.MapValueInd:
 976        ctx.pushLevel(afterImplicitPairStart, indent)
 977        if ctx.lex.curStartPos.line != startPos.line:
 978          raise ctx.generateError("Implicit mapping key may not be multiline")
 979        if not alreadyCaching:
 980          ctx.pushLevel(emitCached)
 981          e = startMapEvent(csPair, defaultProperties, startPos, startPos)
 982          return true
 983        else:
 984          # we are already filling a cache.
 985          # so we just squeeze the map start in.
 986          ctx.keyCache.insert(startMapEvent(csPair, defaultProperties, startPos, startPos), cacheStart)
 987          return false
 988      else:
 989        if not alreadyCaching:
 990          ctx.pushLevel(emitCached)
 991        return false
 992    else:
 993      ctx.pushLevel(beforeFlowItem)
 994      return false
 995  
 996  proc atEmptyPairKey(ctx: Context, e: var Event): bool =
 997    ctx.transition(beforePairValue)
 998    e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curStartPos)
 999    return true
1000  
1001  proc beforePairValue(ctx: Context, e: var Event): bool =
1002    if ctx.lex.cur == Token.MapValueInd:
1003      ctx.transition(afterPairValue)
1004      ctx.pushLevel(beforeFlowItem)
1005      ctx.safeNext()
1006      return false
1007    else:
1008      # pair ends here without value
1009      e = scalarEvent("", defaultProperties, ssPlain, ctx.lex.curStartPos, ctx.lex.curEndPos)
1010      ctx.popLevel()
1011      return true
1012  
1013  proc afterImplicitPairStart(ctx: Context, e: var Event): bool =
1014    ctx.safeNext()
1015    ctx.transition(afterPairValue)
1016    ctx.pushLevel(beforeFlowItem)
1017    return false
1018  
1019  proc afterPairValue(ctx: Context, e: var Event): bool =
1020    e = endMapEvent(ctx.lex.curStartPos, ctx.lex.curEndPos)
1021    ctx.popLevel()
1022    return true
1023  
1024  proc emitCached(ctx: Context, e: var Event): bool =
1025    debug("emitCollection key: pos = " & $ctx.keyCachePos & ", len = " & $ctx.keyCache.len)
1026    yAssert(ctx.keyCachePos < ctx.keyCache.len)
1027    e = move(ctx.keyCache[ctx.keyCachePos])
1028    inc(ctx.keyCachePos)
1029    if ctx.keyCachePos == len(ctx.keyCache):
1030      ctx.keyCache.setLen(0)
1031      ctx.keyCachePos = 0
1032      ctx.popLevel()
1033    return true
1034  
1035  proc display*(p: YamlParser, event: Event): string =
1036    ## Generate a representation of the given event with proper visualization of
1037    ## anchor and tag (if any). The generated representation is conformant to the
1038    ## format used in the yaml test suite.
1039    ##
1040    ## This proc is an informed version of ``$`` on ``YamlStreamEvent`` which can
1041    ## properly display the anchor and tag name as it occurs in the input.
1042    ## However, it shall only be used while using the streaming API because after
1043    ## finishing the parsing of a document, the parser drops all information about
1044    ## anchor and tag names.
1045    case event.kind
1046    of yamlStartStream: result = "+STR"
1047    of yamlEndStream: result = "-STR"
1048    of yamlEndMap: result = "-MAP"
1049    of yamlEndSeq: result = "-SEQ"
1050    of yamlStartDoc:
1051      result = "+DOC"
1052      if event.explicitDirectivesEnd: result &= " ---"
1053    of yamlEndDoc:
1054      result = "-DOC"
1055      if event.explicitDocumentEnd: result &= " ..."
1056    of yamlStartMap:
1057      result = "+MAP" & renderAttrs(event.mapProperties, true)
1058    of yamlStartSeq:
1059      result = "+SEQ" & renderAttrs(event.seqProperties, true)
1060    of yamlScalar:
1061      result = "=VAL" & renderAttrs(event.scalarProperties,
1062                                    event.scalarStyle in {ssPlain, ssFolded, ssLiteral})
1063      case event.scalarStyle
1064      of ssPlain, ssAny: result &= " :"
1065      of ssSingleQuoted: result &= " \'"
1066      of ssDoubleQuoted: result &= " \""
1067      of ssLiteral: result &= " |"
1068      of ssFolded: result &= " >"
1069      result &= yamlTestSuiteEscape(event.scalarContent)
1070    of yamlAlias: result = "=ALI *" & $event.aliasTarget