YAML::PP::Grammar(3)

✨ NAME ✨

okay bestie, this is YAML::PP::Grammar(3) from User Contributed Perl Documentation, and it is literally the YAML grammar. That's the whole vibe. We're in part 1 of 3, so stay with me πŸ’–


🌸 GRAMMAR 🌸

okay so this is the Grammar in YAML. No cap, it's right here in all its glory β€” every key, callback, state transition and comment preserved exactly. Periodt.

# START OF YAML INLINE

# DO NOT CHANGE THIS
# This grammar is automatically generated from etc/grammar.yaml

---
NODETYPE_NODE:
  DASH:
    match: cb_seqstart
    EOL: { new: FULLNODE }
    WS: { new: FULLNODE }

  DEFAULT: { new: NODETYPE_SCALAR_OR_MAP }

NODETYPE_SCALAR_OR_MAP:

  # Flow nodes can follow tabs
  WS: { new: FULLMAPVALUE_INLINE }

  ALIAS:
    match: cb_alias
    EOL: { match: cb_send_alias_from_stack }
    WS:
      COLON:
        match: cb_insert_map_alias
        EOL: { new: FULLNODE }
        WS: { new: FULLMAPVALUE_INLINE }

  QUESTION:
    match: cb_questionstart
    EOL: { new: FULLNODE }
    WS: { new: FULLNODE }

  QUOTED:
    match: cb_take_quoted
    EOL: { match: cb_send_scalar }
    WS:
      COLON:
        match: cb_insert_map
        EOL: { new: FULLNODE }
        WS: { new: FULLMAPVALUE_INLINE }
    COLON:
      match: cb_insert_map
      EOL: { new: FULLNODE }
      WS: { new: FULLMAPVALUE_INLINE }

  QUOTED_MULTILINE:
    match: cb_quoted_multiline
    EOL: {  }

  PLAIN:
    match: cb_start_plain
    EOL:
      match: cb_send_scalar
    WS:
      COLON:
        match: cb_insert_map
        EOL: { new: FULLNODE }
        WS: { new: FULLMAPVALUE_INLINE }
    COLON:
      match: cb_insert_map
      EOL: { new: FULLNODE }
      WS: { new: FULLMAPVALUE_INLINE }

  PLAIN_MULTI:
    match: cb_send_plain_multi
    EOL: { }

  COLON:
    match: cb_insert_empty_map
    EOL: { new: FULLNODE }
    WS: { new: FULLMAPVALUE_INLINE }

  BLOCK_SCALAR:
    match: cb_send_block_scalar
    EOL: { }

  FLOWSEQ_START:
    match: cb_start_flowseq
    new: NEWFLOWSEQ

  FLOWMAP_START:
    match: cb_start_flowmap
    new: NEWFLOWMAP

  DOC_END:
    match: cb_end_document
    EOL: { }

  DOC_START:
    match: cb_end_doc_start_document
    EOL: { new: FULLNODE }
    WS: { new: FULLNODE }

  EOL:
    new: NODETYPE_SCALAR_OR_MAP

NODETYPE_COMPLEX:
  COLON:
    match: cb_complexcolon
    EOL: { new: FULLNODE }
    WS: { new: FULLNODE }
  DEFAULT:
    match: cb_empty_complexvalue
    new: NODETYPE_MAP
  EOL:
    new: NODETYPE_COMPLEX

RULE_FULLFLOWSCALAR:
  ANCHOR:
    match: cb_anchor
    EOL: { new: RULE_FULLFLOWSCALAR_ANCHOR }
    DEFAULT: { new: RULE_FULLFLOWSCALAR_ANCHOR }
  TAG:
    match: cb_tag
    EOL: { new: RULE_FULLFLOWSCALAR_TAG }
    DEFAULT: { new: RULE_FULLFLOWSCALAR_TAG }
  DEFAULT: { new: RULE_FLOWSCALAR }

RULE_FULLFLOWSCALAR_ANCHOR:
  WS: { new: RULE_FULLFLOWSCALAR_ANCHOR }
  TAG:
    match: cb_tag
    WS: { new: RULE_FLOWSCALAR }
    EOL: { new: RULE_FLOWSCALAR }
  DEFAULT: { new: RULE_FLOWSCALAR }

RULE_FULLFLOWSCALAR_TAG:
  WS: { new: RULE_FULLFLOWSCALAR_TAG }
  ANCHOR:
    match: cb_anchor
    WS: { new: RULE_FLOWSCALAR }
    EOL: { new: RULE_FLOWSCALAR }
  DEFAULT: { new: RULE_FLOWSCALAR }

RULE_FLOWSCALAR:
  FLOWSEQ_START: { match: cb_start_flowseq, new: NEWFLOWSEQ }
  FLOWMAP_START: { match: cb_start_flowmap, new: NEWFLOWMAP }

  ALIAS: { match: cb_send_alias, return: 1 }

  QUOTED:
    match: cb_take_quoted
    EOL: { match: cb_send_scalar }
    WS: { match: cb_send_scalar, return: 1 }
    DEFAULT: { match: cb_send_scalar, return: 1 }

  QUOTED_MULTILINE: { match: cb_quoted_multiline, return: 1 }

  PLAIN:
    match: cb_start_plain
    EOL: { match: cb_send_scalar }
    DEFAULT: { match: cb_send_scalar, return: 1 }

  PLAIN_MULTI: { match: cb_send_plain_multi, return: 1 }

  FLOW_COMMA: { match: cb_empty_flow_mapkey, return: 1 }

  FLOWMAP_END:
    match: cb_end_flowmap_empty
    return: 1

FLOWSEQ:
  FLOWSEQ_START: { match: cb_start_flowseq, new: NEWFLOWSEQ }
  FLOWMAP_START: { match: cb_start_flowmap, new: NEWFLOWMAP }

  ALIAS: { match: cb_send_flow_alias, new: FLOWSEQ_NEXT }

  PLAIN:
    match: cb_start_plain
    EOL:
      match: cb_send_scalar
      new: FLOWSEQ_NEXT
    DEFAULT:
      new: FLOWSEQ_MAYBE_KEY
  PLAIN_MULTI: { match: cb_send_plain_multi, new: FLOWSEQ_NEXT }

  QUOTED:
    match: cb_take_quoted
    EOL:
      match: cb_send_scalar
      new: FLOWSEQ_NEXT
    DEFAULT:
      new: FLOWSEQ_MAYBE_KEY
  QUOTED_MULTILINE: { match: cb_quoted_multiline, new: FLOWSEQ_NEXT }

  COLON:
    WS:
      match: cb_insert_empty_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR
    EOL:
      match: cb_insert_empty_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR

FLOWSEQ_PROPS:
  FLOWSEQ_START: { match: cb_start_flowseq, new: NEWFLOWSEQ }
  FLOWMAP_START: { match: cb_start_flowmap, new: NEWFLOWMAP }

  PLAIN:
    match: cb_start_plain
    EOL:
      match: cb_send_scalar
      new: FLOWSEQ_NEXT
    DEFAULT:
      new: FLOWSEQ_MAYBE_KEY
  PLAIN_MULTI: { match: cb_send_plain_multi, new: FLOWSEQ_NEXT }

  QUOTED:
    match: cb_take_quoted
    EOL:
      match: cb_send_scalar
      new: FLOWSEQ_NEXT
    DEFAULT:
      new: FLOWSEQ_MAYBE_KEY
  QUOTED_MULTILINE: { match: cb_quoted_multiline, new: FLOWSEQ_NEXT }

  FLOW_COMMA:
    match: cb_empty_flowseq_comma
    return: 1
  FLOWSEQ_END:
    match: cb_empty_flowseq_end
    return: 1

  COLON:
    WS:
      match: cb_insert_empty_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR
    EOL:
      match: cb_insert_empty_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR

FLOWSEQ_EMPTY:
  FLOW_COMMA:
    match: cb_empty_flowseq_comma
    return: 1
  FLOWSEQ_END:
    match: cb_empty_flowseq_end
    return: 1

FLOWSEQ_NEXT:
  WS: { new: FLOWSEQ_NEXT }
  EOL: { new: FLOWSEQ_NEXT }

  FLOW_COMMA:
    match: cb_flow_comma
    return: 1
  FLOWSEQ_END:
    match: cb_end_flowseq
    return: 1

FLOWSEQ_MAYBE_KEY:
  WS: { new: FLOWSEQ_MAYBE_KEY }

  COLON:
    WS:
      match: cb_insert_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR
    EOL:
      match: cb_insert_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR
    DEFAULT:
      match: cb_insert_implicit_flowseq_map
      new: RULE_FULLFLOWSCALAR
  DEFAULT:
    new: FLOWSEQ_NEXT

FLOWMAP_CONTENT:
  FLOWSEQ_START: { match: cb_start_flowseq, new: NEWFLOWSEQ }
  FLOWMAP_START: { match: cb_start_flowmap, new: NEWFLOWMAP }

  ALIAS: { match: cb_send_alias, return: 1 }

  PLAIN: { match: cb_flowkey_plain, return: 1 }
  PLAIN_MULTI: { match: cb_send_plain_multi, return: 1 }

  QUOTED: { match: cb_flowkey_quoted, return: 1 }
  QUOTED_MULTILINE: { match: cb_quoted_multiline, return: 1 }

  COLON:
    WS:
      match: cb_empty_flow_mapkey
      new: RULE_FULLFLOWSCALAR
    EOL:
      match: cb_empty_flow_mapkey
      new: RULE_FULLFLOWSCALAR

FLOWMAP_PROPS:
  FLOWSEQ_START: { match: cb_start_flowseq, new: NEWFLOWSEQ }
  FLOWMAP_START: { match: cb_start_flowmap, new: NEWFLOWMAP }

  PLAIN: { match: cb_flowkey_plain, return: 1 }
  PLAIN_MULTI: { match: cb_send_plain_multi, return: 1 }

  QUOTED: { match: cb_flowkey_quoted, return: 1 }
  QUOTED_MULTILINE: { match: cb_quoted_multiline, return: 1 }

  COLON:
    WS:
      match: cb_empty_flow_mapkey
      new: RULE_FULLFLOWSCALAR
    EOL:
      match: cb_empty_flow_mapkey
      new: RULE_FULLFLOWSCALAR
  FLOW_COMMA:
    match: cb_empty_flowmap_key_value
    return: 1
  FLOWMAP_END:
    match: cb_end_empty_flowmap_key_value
    return: 1

FLOWMAP_EMPTYKEY:
  FLOW_COMMA:
    match: cb_empty_flowmap_key_value
    return: 1
  FLOWMAP_END:
    match: cb_end_empty_flowmap_key_value
    return: 1

NEWFLOWSEQ:
  EOL: { new: NEWFLOWSEQ }
  WS: { new: NEWFLOWSEQ }

  ANCHOR:
    match: cb_anchor
    WS: { new: NEWFLOWSEQ_ANCHOR_SPC }
    EOL: { new: NEWFLOWSEQ_ANCHOR_SPC }
    DEFAULT: { new: NEWFLOWSEQ_ANCHOR }

  TAG:
    match: cb_tag
    WS: { new: NEWFLOWSEQ_TAG_SPC }
    EOL: { new: NEWFLOWSEQ_TAG_SPC }
    DEFAULT: { new: NEWFLOWSEQ_TAG }

  FLOWSEQ_END:
    match: cb_end_flowseq
    return: 1

  DEFAULT: { new: FLOWSEQ }

NODETYPE_FLOWSEQ:
  EOL: { new: NODETYPE_FLOWSEQ }
  WS: { new: NODETYPE_FLOWSEQ }
  FLOWSEQ_END:
    match: cb_end_flowseq
    return: 1
  DEFAULT: { new: NEWFLOWSEQ }

NODETYPE_FLOWMAPVALUE:
  WS: { new: NODETYPE_FLOWMAPVALUE }
  EOL: { new: NODETYPE_FLOWMAPVALUE }
  COLON:
    match: cb_flow_colon
    WS: { new: RULE_FULLFLOWSCALAR }
    EOL: { new: RULE_FULLFLOWSCALAR }
    DEFAULT: { new: RULE_FULLFLOWSCALAR }
  FLOW_COMMA:
    match: cb_empty_flowmap_value
    return: 1
  FLOWMAP_END:
    match: cb_end_flowmap_empty
    return: 1

NEWFLOWSEQ_ANCHOR:
  DEFAULT: { new: FLOWSEQ_EMPTY }
NEWFLOWSEQ_TAG:
  DEFAULT: { new: FLOWSEQ_EMPTY }

πŸ¦‹ NEWFLOWSEQ_ANCHOR_SPC

so you already grabbed an anchor inside a flow seq and now you're just chillin in whitespace waiting for something to happen. lived here tbh.

  • WS β†’ { new: NEWFLOWSEQ_ANCHOR_SPC }
  • EOL β†’ { new: NEWFLOWSEQ_ANCHOR_SPC }
  • TAG β€” match cb_tag
    • WS β†’ { new: FLOWSEQ_PROPS }
    • EOL β†’ { new: FLOWSEQ_PROPS }
    • DEFAULT β†’ { new: FLOWSEQ_EMPTY }
  • DEFAULT β†’ { new: FLOWSEQ_PROPS }

πŸŽ€ NEWFLOWSEQ_TAG_SPC

the tag twin of the state above πŸ’… same energy, but it's out here hunting for an ANCHOR instead.

  • WS β†’ { new: NEWFLOWSEQ_TAG_SPC }
  • EOL β†’ { new: NEWFLOWSEQ_TAG_SPC }
  • ANCHOR β€” match cb_anchor
    • WS β†’ { new: FLOWSEQ_PROPS }
    • EOL β†’ { new: FLOWSEQ_PROPS }
    • DEFAULT β†’ { new: FLOWSEQ_EMPTY }
  • DEFAULT β†’ { new: FLOWSEQ_PROPS }

🫢 NEWFLOWMAP_ANCHOR

one singular line, she's low maintenance, she's iconic:

  • DEFAULT β†’ { new: FLOWMAP_EMPTYKEY }

🌸 NEWFLOWMAP_TAG

same deal, tag flavoured:

  • DEFAULT β†’ { new: FLOWMAP_EMPTYKEY }

✨ NEWFLOWMAP_ANCHOR_SPC

whitespace era after an anchor in a flow map. notice how she can also catch a TAG on the way through πŸ‘‘

  • WS β†’ { new: NEWFLOWMAP_ANCHOR_SPC }
  • EOL β†’ { new: NEWFLOWMAP_ANCHOR_SPC }
  • TAG β€” match cb_tag
    • WS β†’ { new: FLOWMAP_PROPS }
    • EOL β†’ { new: FLOWMAP_PROPS }
    • DEFAULT β†’ { new: FLOWMAP_EMPTYKEY }
  • DEFAULT β†’ { new: FLOWMAP_PROPS }

🌈 NEWFLOWMAP_TAG_SPC

and the mirror image, where the ANCHOR can still slide in:

  • WS β†’ { new: NEWFLOWMAP_TAG_SPC }
  • EOL β†’ { new: NEWFLOWMAP_TAG_SPC }
  • ANCHOR β€” match cb_anchor
    • WS β†’ { new: FLOWMAP_PROPS }
    • EOL β†’ { new: FLOWMAP_PROPS }
    • DEFAULT β†’ { new: FLOWMAP_EMPTYKEY }
  • DEFAULT β†’ { new: FLOWMAP_PROPS }

πŸ‘‘ NEWFLOWMAP

the moment you see a ? it's giving explicit key energy and we hop to FLOWMAP_EXPLICIT_KEY. everything else? vibes, we stay put.

  • EOL β†’ { new: NEWFLOWMAP }
  • WS β†’ { new: NEWFLOWMAP }
  • QUESTION β†’ { match: cb_flow_question, new: FLOWMAP_EXPLICIT_KEY }
  • DEFAULT β†’ { new: FLOWMAP }

πŸ’€ FLOWMAP_EXPLICIT_KEY

the explicit key state, and bestie you can leave it two ways β€” a FLOWMAP_END or a FLOW_COMMA. and yes, those are two different callbacks, don't confuse them:

  • WS β†’ { new: FLOWMAP_EXPLICIT_KEY }
  • EOL β†’ { new: FLOWMAP_EXPLICIT_KEY }
  • FLOWMAP_END β€” match cb_end_empty_flowmap_key_value, then return: 1
  • FLOW_COMMA β€” match cb_empty_flowmap_key_value, then return: 1
  • DEFAULT β†’ { new: FLOWMAP }
fr fr look at those two match names again. cb_end_empty_flowmap_key_value vs cb_empty_flowmap_key_value. one letter of a difference. 🫠 not me almost typing them as one.

🧚 FLOWMAP

the main character of flow maps ✨ properties, ends, empty keys β€” she does it all.

  • EOL β†’ { new: FLOWMAP }
  • WS β†’ { new: FLOWMAP }
  • ANCHOR β€” match cb_anchor
    • WS β†’ { new: NEWFLOWMAP_ANCHOR_SPC }
    • EOL β†’ { new: NEWFLOWMAP_ANCHOR_SPC }
    • DEFAULT β†’ { new: NEWFLOWMAP_ANCHOR }
  • TAG β€” match cb_tag
    • WS β†’ { new: NEWFLOWMAP_TAG_SPC }
    • EOL β†’ { new: NEWFLOWMAP_TAG_SPC }
    • DEFAULT β†’ { new: NEWFLOWMAP_TAG }
  • FLOWMAP_END β€” match cb_end_flowmap, then return: 1
  • COLON
    • WS
      • match cb_empty_flow_mapkey
      • new RULE_FULLFLOWSCALAR
    • EOL
      • match cb_empty_flow_mapkey
      • new RULE_FULLFLOWSCALAR
  • DEFAULT β†’ { new: FLOWMAP_CONTENT }

πŸ’œ NODETYPE_FLOWMAP

once the node type is settled as a flow map, a comma means fresh start (NEWFLOWMAP) and an end means we're done out here.

  • EOL β†’ { new: NODETYPE_FLOWMAP }
  • WS β†’ { new: NODETYPE_FLOWMAP }
  • FLOWMAP_END β€” match cb_end_flowmap, then return: 1
  • FLOW_COMMA β†’ { match: cb_flow_comma, new: NEWFLOWMAP }
  • DEFAULT β†’ { new: NEWFLOWMAP }

πŸ“ END_FLOW

tiny but mighty. one job:

  • EOL β€” match cb_end_outer_flow, then return: 1

πŸ”₯ RULE_MAPKEY

the big one. this is the rule that figures out what kind of key you're dealing with, and every branch has opinions.

  • QUESTION β€” match cb_question
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • ALIAS β€” match cb_send_alias_key
    • WS
      • COLON
        • EOL β†’ { new: FULLNODE }
        • WS β†’ { new: FULLMAPVALUE_INLINE }
  • QUOTED β€” match cb_take_quoted_key
    • WS
      • COLON
        • EOL β†’ { new: FULLNODE }
        • WS β†’ { new: FULLMAPVALUE_INLINE }
    • COLON
      • EOL β†’ { new: FULLNODE }
      • WS β†’ { new: FULLMAPVALUE_INLINE }
  • PLAIN β€” match cb_mapkey
    • WS
      • COLON β€” match cb_send_mapkey
        • EOL β†’ { new: FULLNODE }
        • WS β†’ { new: FULLMAPVALUE_INLINE }
    • COLON β€” match cb_send_mapkey
      • EOL β†’ { new: FULLNODE }
      • WS β†’ { new: FULLMAPVALUE_INLINE }
  • COLON β€” match cb_empty_mapkey
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLMAPVALUE_INLINE }
  • DOC_END β€” match cb_end_document
    • EOL β†’ { }
  • DOC_START β€” match cb_end_doc_start_document
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • EOL β†’ { new: RULE_MAPKEY }
🫢 notice ALIAS only has the WS β†’ COLON path and no top-level COLON, and QUOTED has both. that asymmetry is on purpose, don't go "fixing" it.

πŸ¦‹ NODETYPE_SEQ

seq mode. a dash means new item, and we're bouncing into a full node for the value.

  • DASH β€” match cb_seqitem
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • DOC_END β€” match cb_end_document
    • EOL β†’ { }
  • DOC_START β€” match cb_end_doc_start_document
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • EOL β†’ { new: NODETYPE_SEQ }

πŸŽ€ NODETYPE_MAP

map mode, and look at her β€” she's cool with ANCHOR then TAG, TAG then ANCHOR, or neither. flexible queen.

  • ANCHOR β€” match cb_anchor
    • WS
      • TAG β€” match cb_tag
        • WS β†’ { new: RULE_MAPKEY }
      • DEFAULT β†’ { new: RULE_MAPKEY }
  • TAG β€” match cb_tag
    • WS
      • ANCHOR β€” match cb_anchor
        • WS β†’ { new: RULE_MAPKEY }
      • DEFAULT β†’ { new: RULE_MAPKEY }
  • DEFAULT β†’ { new: RULE_MAPKEY }

🌸 FULLNODE_ANCHOR

you've got an anchor, now you're assembling the rest of the node. tags, eols, and the eventual NODETYPE_NODE.

  • TAG β€” match cb_tag
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
    • WS
      • ANCHOR β€” match cb_anchor
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • ANCHOR β€” match cb_anchor
    • WS
      • TAG β€” match cb_tag
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • EOL β†’ { new: FULLNODE_ANCHOR }
  • DEFAULT β†’ { new: NODETYPE_NODE }

⭐ FULLNODE_TAG

tag first this time. and yeah, that DEFAULT line has a lil trailing comma in it β€” { new: NODETYPE_SCALAR_OR_MAP, } β€” she's got baggage, we keep her exactly as she is πŸ’…

  • ANCHOR β€” match cb_anchor
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
    • WS
      • TAG β€” match cb_tag
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP, }
  • TAG β€” match cb_tag
    • WS
      • ANCHOR β€” match cb_anchor
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • EOL β†’ { new: FULLNODE_TAG }
  • DEFAULT β†’ { new: NODETYPE_NODE }

🌈 FULLNODE_TAG_ANCHOR

both properties already handled, so EOL just loops back to herself and everything else becomes a node.

  • ANCHOR β€” match cb_anchor
    • WS
      • TAG β€” match cb_tag
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • TAG β€” match cb_tag
    • WS
      • ANCHOR β€” match cb_anchor
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • EOL β†’ { new: FULLNODE_TAG_ANCHOR }
  • DEFAULT β†’ { new: NODETYPE_NODE }

πŸ”₯ FULLNODE

the fully loaded version, where cb_property_eol can fire at every turn and cart you off to one of the property states 😭

  • ANCHOR β€” match cb_anchor
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_ANCHOR }
    • WS
      • TAG β€” match cb_tag
        • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • TAG β€” match cb_tag
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG }
    • WS
      • ANCHOR β€” match cb_anchor
        • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
        • WS β†’ { new: NODETYPE_SCALAR_OR_MAP }
      • DEFAULT β†’ { new: NODETYPE_SCALAR_OR_MAP }
  • EOL β†’ { new: FULLNODE }
  • DEFAULT β†’ { new: NODETYPE_NODE }

πŸ’œ FULLMAPVALUE_INLINE

a map value that lives on the same line. properties get checked, then it's NODETYPE_MAPVALUE_INLINE time.

  • ANCHOR β€” match cb_anchor
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_ANCHOR }
    • WS
      • TAG β€” match cb_tag
        • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
        • WS β†’ { new: NODETYPE_MAPVALUE_INLINE }
      • DEFAULT β†’ { new: NODETYPE_MAPVALUE_INLINE }
  • TAG β€” match cb_tag
    • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG }
    • WS
      • ANCHOR β€” match cb_anchor
        • EOL β†’ { match: cb_property_eol, new: FULLNODE_TAG_ANCHOR }
        • WS β†’ { new: NODETYPE_MAPVALUE_INLINE }
      • DEFAULT β†’ { new: NODETYPE_MAPVALUE_INLINE }
  • DEFAULT β†’ { new: NODETYPE_MAPVALUE_INLINE }

πŸ“ NODETYPE_MAPVALUE_INLINE

the actual dispatch board for inline map values. every scalar flavour gets a callback, and flow collections get started up 🎬

  • ALIAS β€” match cb_send_alias
    • EOL β†’ { }
  • QUOTED β€” match cb_take_quoted
    • EOL β†’ { match: cb_send_scalar }
  • QUOTED_MULTILINE β€” match cb_quoted_multiline
    • EOL β†’ { }
  • PLAIN β€” match cb_start_plain
    • EOL β€” match cb_send_scalar
  • PLAIN_MULTI β€” match cb_send_plain_multi
    • EOL β†’ { }
  • BLOCK_SCALAR β€” match cb_send_block_scalar
    • EOL β†’ { }
  • FLOWSEQ_START β€” match cb_start_flowseq
    • new NEWFLOWSEQ
  • FLOWMAP_START β€” match cb_start_flowmap
    • new NEWFLOWMAP
  • DOC_END β€” match cb_end_document
    • EOL β†’ { }
🧚 note that FLOWSEQ_START and FLOWMAP_START are the only two here written with a bare new: line instead of { new: ... }. that's how the source has them, no cap. don't normalise it.

πŸ‘‘ DOCUMENT_END

we're past the document, but a new DOC_START still means a fresh FULLNODE.

  • DOC_END β€” match cb_end_document
    • EOL β†’ { }
  • DOC_START β€” match cb_end_doc_start_document
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • EOL β†’ { new: DOCUMENT_END }

πŸ’… STREAM

the top of the whole thing ✨ directives, explicit doc starts, implicit doc starts, empty doc ends β€” she's seen it all.

  • DOC_END β€” match cb_end_document_empty
    • EOL β†’ { }
  • DOC_START β€” match cb_doc_start_explicit
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }
  • YAML_DIRECTIVE β€” match cb_set_yaml_version_directive
    • EOL β†’ { new: DIRECTIVE }
    • WS β†’ { new: DIRECTIVE }
  • RESERVED_DIRECTIVE β€” match cb_reserved_directive
    • EOL β†’ { new: DIRECTIVE }
    • WS β†’ { new: DIRECTIVE }
  • TAG_DIRECTIVE β€” match cb_tag_directive
    • EOL β†’ { new: DIRECTIVE }
    • WS β†’ { new: DIRECTIVE }
  • EOL β†’ { new: STREAM }
  • DEFAULT β€” match cb_doc_start_implicit
    • new FULLNODE

🧚 DIRECTIVE

after a directive, an explicit document start is the move 🎬

  • DOC_START β€” match cb_doc_start_explicit
    • EOL β†’ { new: FULLNODE }
    • WS β†’ { new: FULLNODE }

...and we're back in the state table πŸŽ€ last stretch, bestie, I promise.

             YAML_DIRECTIVE:
               match: cb_set_yaml_version_directive
               EOL: { new: DIRECTIVE }
               WS: { new: DIRECTIVE }
             RESERVED_DIRECTIVE:
               match: cb_reserved_directive
               EOL: { new: DIRECTIVE }
               WS: { new: DIRECTIVE }
             TAG_DIRECTIVE:
               match: cb_tag_directive
               EOL: { new: DIRECTIVE }
               WS: { new: DIRECTIVE }

             EOL:
               new: DIRECTIVE

           # END OF YAML INLINE

Okay so look at these last three states and notice the pattern, because it's so clean 😭✨ each directive state does the exact same trio of things:

  • it matches on its own callback β€” cb_set_yaml_version_directive for YAML_DIRECTIVE, cb_reserved_directive for RESERVED_DIRECTIVE, and cb_tag_directive for TAG_DIRECTIVE
  • on EOL it goes to the DIRECTIVE state via { new: DIRECTIVE } πŸ’«
  • on WS β€” same thing, { new: DIRECTIVE } 🫢

so both a newline and whitespace bounce you into DIRECTIVE. no cap, that's the whole vibe of these three. they're literally triplets πŸ’…

And then the very last state, EOL, just does new: DIRECTIVE. that's it. that's her whole job πŸ‘‘

Then that final comment line, # END OF YAML INLINE, and the inline-grammar chunk of the page is done β€” we ate and left no crumbs πŸ“


for real though, heads up: if you're editing or extending this, keep every state name and callback name exactly as written, because this is a state machine and it does not do vibes-based matching πŸ’€

and that's a wrap on the grammar, bestie πŸ¦‹πŸ’– this page is YAML::PP::Grammar(3), part of the User Contributed Perl Documentation, and it rides along with perl v5.42.0, dated 2026-04-24 β€” so if you're reading this, that's your version and date right there, no scrolling required 😌✨ go forth and parse some YAML, ily, bye πŸ‘‹πŸŽ€

πŸ“¦ where this page comes from