aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/D-YAML/source/dyaml/parser.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext_depends/D-YAML/source/dyaml/parser.d')
-rw-r--r--src/ext_depends/D-YAML/source/dyaml/parser.d958
1 files changed, 958 insertions, 0 deletions
diff --git a/src/ext_depends/D-YAML/source/dyaml/parser.d b/src/ext_depends/D-YAML/source/dyaml/parser.d
new file mode 100644
index 0000000..7e0b78a
--- /dev/null
+++ b/src/ext_depends/D-YAML/source/dyaml/parser.d
@@ -0,0 +1,958 @@
+
+// Copyright Ferdinand Majerech 2011-2014.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+/**
+ * YAML parser.
+ * Code based on PyYAML: http://www.pyyaml.org
+ */
+module dyaml.parser;
+
+
+import std.algorithm;
+import std.array;
+import std.conv;
+import std.exception;
+import std.typecons;
+
+import dyaml.event;
+import dyaml.exception;
+import dyaml.scanner;
+import dyaml.style;
+import dyaml.token;
+import dyaml.tagdirective;
+
+
+package:
+/**
+ * The following YAML grammar is LL(1) and is parsed by a recursive descent
+ * parser.
+ *
+ * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+ * implicit_document ::= block_node DOCUMENT-END*
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ * block_node_or_indentless_sequence ::=
+ * ALIAS
+ * | properties (block_content | indentless_block_sequence)?
+ * | block_content
+ * | indentless_block_sequence
+ * block_node ::= ALIAS
+ * | properties block_content?
+ * | block_content
+ * flow_node ::= ALIAS
+ * | properties flow_content?
+ * | flow_content
+ * properties ::= TAG ANCHOR? | ANCHOR TAG?
+ * block_content ::= block_collection | flow_collection | SCALAR
+ * flow_content ::= flow_collection | SCALAR
+ * block_collection ::= block_sequence | block_mapping
+ * flow_collection ::= flow_sequence | flow_mapping
+ * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
+ * indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+ * block_mapping ::= BLOCK-MAPPING_START
+ * ((KEY block_node_or_indentless_sequence?)?
+ * (VALUE block_node_or_indentless_sequence?)?)*
+ * BLOCK-END
+ * flow_sequence ::= FLOW-SEQUENCE-START
+ * (flow_sequence_entry FLOW-ENTRY)*
+ * flow_sequence_entry?
+ * FLOW-SEQUENCE-END
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ * flow_mapping ::= FLOW-MAPPING-START
+ * (flow_mapping_entry FLOW-ENTRY)*
+ * flow_mapping_entry?
+ * FLOW-MAPPING-END
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ *
+ * FIRST sets:
+ *
+ * stream: { STREAM-START }
+ * explicit_document: { DIRECTIVE DOCUMENT-START }
+ * implicit_document: FIRST(block_node)
+ * block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
+ * flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
+ * block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
+ * flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
+ * block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
+ * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
+ * block_sequence: { BLOCK-SEQUENCE-START }
+ * block_mapping: { BLOCK-MAPPING-START }
+ * block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
+ * indentless_sequence: { ENTRY }
+ * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
+ * flow_sequence: { FLOW-SEQUENCE-START }
+ * flow_mapping: { FLOW-MAPPING-START }
+ * flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
+ * flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
+ */
+
+
+/**
+ * Marked exception thrown at parser errors.
+ *
+ * See_Also: MarkedYAMLException
+ */
+class ParserException : MarkedYAMLException
+{
+ mixin MarkedExceptionCtors;
+}
+
+/// Generates events from tokens provided by a Scanner.
+///
+/// While Parser receives tokens with non-const character slices, the events it
+/// produces are immutable strings, which are usually the same slices, cast to string.
+/// Parser is the last layer of D:YAML that may possibly do any modifications to these
+/// slices.
+final class Parser
+{
+ private:
+ ///Default tag handle shortcuts and replacements.
+ static TagDirective[] defaultTagDirectives_ =
+ [TagDirective("!", "!"), TagDirective("!!", "tag:yaml.org,2002:")];
+
+ ///Scanner providing YAML tokens.
+ Scanner scanner_;
+
+ ///Event produced by the most recent state.
+ Event currentEvent_;
+
+ ///YAML version string.
+ string YAMLVersion_ = null;
+ ///Tag handle shortcuts and replacements.
+ TagDirective[] tagDirectives_;
+
+ ///Stack of states.
+ Appender!(Event delegate() @safe[]) states_;
+ ///Stack of marks used to keep track of extents of e.g. YAML collections.
+ Appender!(Mark[]) marks_;
+
+ ///Current state.
+ Event delegate() @safe state_;
+
+ public:
+ ///Construct a Parser using specified Scanner.
+ this(Scanner scanner) @safe
+ {
+ state_ = &parseStreamStart;
+ scanner_ = scanner;
+ states_.reserve(32);
+ marks_.reserve(32);
+ }
+
+ /**
+ * Check if any events are left. May have side effects in some cases.
+ */
+ bool empty() @safe
+ {
+ ensureState();
+ return currentEvent_.isNull;
+ }
+
+ /**
+ * Return the current event.
+ *
+ * Must not be called if there are no events left.
+ */
+ Event front() @safe
+ {
+ ensureState();
+ assert(!currentEvent_.isNull, "No event left to peek");
+ return currentEvent_;
+ }
+
+ /**
+ * Skip to the next event.
+ *
+ * Must not be called if there are no events left.
+ */
+ void popFront() @safe
+ {
+ currentEvent_.id = EventID.invalid;
+ ensureState();
+ }
+
+ private:
+ /// If current event is invalid, load the next valid one if possible.
+ void ensureState() @safe
+ {
+ if(currentEvent_.isNull && state_ !is null)
+ {
+ currentEvent_ = state_();
+ }
+ }
+ ///Pop and return the newest state in states_.
+ Event delegate() @safe popState() @safe
+ {
+ enforce(states_.data.length > 0,
+ new YAMLException("Parser: Need to pop state but no states left to pop"));
+ const result = states_.data.back;
+ states_.shrinkTo(states_.data.length - 1);
+ return result;
+ }
+
+ ///Pop and return the newest mark in marks_.
+ Mark popMark() @safe
+ {
+ enforce(marks_.data.length > 0,
+ new YAMLException("Parser: Need to pop mark but no marks left to pop"));
+ const result = marks_.data.back;
+ marks_.shrinkTo(marks_.data.length - 1);
+ return result;
+ }
+
+ /// Push a state on the stack
+ void pushState(Event delegate() @safe state) @safe
+ {
+ states_ ~= state;
+ }
+ /// Push a mark on the stack
+ void pushMark(Mark mark) @safe
+ {
+ marks_ ~= mark;
+ }
+
+ /**
+ * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+ * implicit_document ::= block_node DOCUMENT-END*
+ * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ */
+
+ ///Parse stream start.
+ Event parseStreamStart() @safe
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+ state_ = &parseImplicitDocumentStart;
+ return streamStartEvent(token.startMark, token.endMark);
+ }
+
+ /// Parse implicit document start, unless explicit detected: if so, parse explicit.
+ Event parseImplicitDocumentStart() @safe
+ {
+ // Parse an implicit document.
+ if(!scanner_.front.id.among!(TokenID.directive, TokenID.documentStart,
+ TokenID.streamEnd))
+ {
+ tagDirectives_ = defaultTagDirectives_;
+ const token = scanner_.front;
+
+ pushState(&parseDocumentEnd);
+ state_ = &parseBlockNode;
+
+ return documentStartEvent(token.startMark, token.endMark, false, null, null);
+ }
+ return parseDocumentStart();
+ }
+
+ ///Parse explicit document start.
+ Event parseDocumentStart() @safe
+ {
+ //Parse any extra document end indicators.
+ while(scanner_.front.id == TokenID.documentEnd)
+ {
+ scanner_.popFront();
+ }
+
+ //Parse an explicit document.
+ if(scanner_.front.id != TokenID.streamEnd)
+ {
+ const startMark = scanner_.front.startMark;
+
+ auto tagDirectives = processDirectives();
+ enforce(scanner_.front.id == TokenID.documentStart,
+ new ParserException("Expected document start but found " ~
+ scanner_.front.idString,
+ scanner_.front.startMark));
+
+ const endMark = scanner_.front.endMark;
+ scanner_.popFront();
+ pushState(&parseDocumentEnd);
+ state_ = &parseDocumentContent;
+ return documentStartEvent(startMark, endMark, true, YAMLVersion_, tagDirectives);
+ }
+ else
+ {
+ //Parse the end of the stream.
+ const token = scanner_.front;
+ scanner_.popFront();
+ assert(states_.data.length == 0);
+ assert(marks_.data.length == 0);
+ state_ = null;
+ return streamEndEvent(token.startMark, token.endMark);
+ }
+ }
+
+ ///Parse document end (explicit or implicit).
+ Event parseDocumentEnd() @safe
+ {
+ Mark startMark = scanner_.front.startMark;
+ const bool explicit = scanner_.front.id == TokenID.documentEnd;
+ Mark endMark = startMark;
+ if (explicit)
+ {
+ endMark = scanner_.front.endMark;
+ scanner_.popFront();
+ }
+
+ state_ = &parseDocumentStart;
+
+ return documentEndEvent(startMark, endMark, explicit);
+ }
+
+ ///Parse document content.
+ Event parseDocumentContent() @safe
+ {
+ if(scanner_.front.id.among!(TokenID.directive, TokenID.documentStart,
+ TokenID.documentEnd, TokenID.streamEnd))
+ {
+ state_ = popState();
+ return processEmptyScalar(scanner_.front.startMark);
+ }
+ return parseBlockNode();
+ }
+
+ /// Process directives at the beginning of a document.
+ TagDirective[] processDirectives() @safe
+ {
+ // Destroy version and tag handles from previous document.
+ YAMLVersion_ = null;
+ tagDirectives_.length = 0;
+
+ // Process directives.
+ while(scanner_.front.id == TokenID.directive)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+ string value = token.value.idup;
+ if(token.directive == DirectiveType.yaml)
+ {
+ enforce(YAMLVersion_ is null,
+ new ParserException("Duplicate YAML directive", token.startMark));
+ const minor = value.split(".")[0];
+ enforce(minor == "1",
+ new ParserException("Incompatible document (version 1.x is required)",
+ token.startMark));
+ YAMLVersion_ = value;
+ }
+ else if(token.directive == DirectiveType.tag)
+ {
+ auto handle = value[0 .. token.valueDivider];
+
+ foreach(ref pair; tagDirectives_)
+ {
+ // handle
+ const h = pair.handle;
+ enforce(h != handle, new ParserException("Duplicate tag handle: " ~ handle,
+ token.startMark));
+ }
+ tagDirectives_ ~=
+ TagDirective(handle, value[token.valueDivider .. $]);
+ }
+ // Any other directive type is ignored (only YAML and TAG are in YAML
+ // 1.1/1.2, any other directives are "reserved")
+ }
+
+ TagDirective[] value = tagDirectives_;
+
+ //Add any default tag handles that haven't been overridden.
+ foreach(ref defaultPair; defaultTagDirectives_)
+ {
+ bool found;
+ foreach(ref pair; tagDirectives_) if(defaultPair.handle == pair.handle)
+ {
+ found = true;
+ break;
+ }
+ if(!found) {tagDirectives_ ~= defaultPair; }
+ }
+
+ return value;
+ }
+
+ /**
+ * block_node_or_indentless_sequence ::= ALIAS
+ * | properties (block_content | indentless_block_sequence)?
+ * | block_content
+ * | indentless_block_sequence
+ * block_node ::= ALIAS
+ * | properties block_content?
+ * | block_content
+ * flow_node ::= ALIAS
+ * | properties flow_content?
+ * | flow_content
+ * properties ::= TAG ANCHOR? | ANCHOR TAG?
+ * block_content ::= block_collection | flow_collection | SCALAR
+ * flow_content ::= flow_collection | SCALAR
+ * block_collection ::= block_sequence | block_mapping
+ * flow_collection ::= flow_sequence | flow_mapping
+ */
+
+ ///Parse a node.
+ Event parseNode(const Flag!"block" block,
+ const Flag!"indentlessSequence" indentlessSequence = No.indentlessSequence)
+ @trusted
+ {
+ if(scanner_.front.id == TokenID.alias_)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+ state_ = popState();
+ return aliasEvent(token.startMark, token.endMark,
+ cast(string)token.value);
+ }
+
+ string anchor;
+ string tag;
+ Mark startMark, endMark, tagMark;
+ bool invalidMarks = true;
+ // The index in the tag string where tag handle ends and tag suffix starts.
+ uint tagHandleEnd;
+
+ //Get anchor/tag if detected. Return false otherwise.
+ bool get(const TokenID id, const Flag!"first" first, ref string target) @safe
+ {
+ if(scanner_.front.id != id){return false;}
+ invalidMarks = false;
+ const token = scanner_.front;
+ scanner_.popFront();
+ if(first){startMark = token.startMark;}
+ if(id == TokenID.tag)
+ {
+ tagMark = token.startMark;
+ tagHandleEnd = token.valueDivider;
+ }
+ endMark = token.endMark;
+ target = token.value.idup;
+ return true;
+ }
+
+ //Anchor and/or tag can be in any order.
+ if(get(TokenID.anchor, Yes.first, anchor)){get(TokenID.tag, No.first, tag);}
+ else if(get(TokenID.tag, Yes.first, tag)) {get(TokenID.anchor, No.first, anchor);}
+
+ if(tag !is null){tag = processTag(tag, tagHandleEnd, startMark, tagMark);}
+
+ if(invalidMarks)
+ {
+ startMark = endMark = scanner_.front.startMark;
+ }
+
+ bool implicit = (tag is null || tag == "!");
+
+ if(indentlessSequence && scanner_.front.id == TokenID.blockEntry)
+ {
+ state_ = &parseIndentlessSequenceEntry;
+ return sequenceStartEvent
+ (startMark, scanner_.front.endMark, anchor,
+ tag, implicit, CollectionStyle.block);
+ }
+
+ if(scanner_.front.id == TokenID.scalar)
+ {
+ auto token = scanner_.front;
+ scanner_.popFront();
+ auto value = token.style == ScalarStyle.doubleQuoted
+ ? handleDoubleQuotedScalarEscapes(token.value)
+ : cast(string)token.value;
+
+ implicit = (token.style == ScalarStyle.plain && tag is null) || tag == "!";
+ state_ = popState();
+ return scalarEvent(startMark, token.endMark, anchor, tag,
+ implicit, value, token.style);
+ }
+
+ if(scanner_.front.id == TokenID.flowSequenceStart)
+ {
+ endMark = scanner_.front.endMark;
+ state_ = &parseFlowSequenceEntry!(Yes.first);
+ return sequenceStartEvent(startMark, endMark, anchor, tag,
+ implicit, CollectionStyle.flow);
+ }
+
+ if(scanner_.front.id == TokenID.flowMappingStart)
+ {
+ endMark = scanner_.front.endMark;
+ state_ = &parseFlowMappingKey!(Yes.first);
+ return mappingStartEvent(startMark, endMark, anchor, tag,
+ implicit, CollectionStyle.flow);
+ }
+
+ if(block && scanner_.front.id == TokenID.blockSequenceStart)
+ {
+ endMark = scanner_.front.endMark;
+ state_ = &parseBlockSequenceEntry!(Yes.first);
+ return sequenceStartEvent(startMark, endMark, anchor, tag,
+ implicit, CollectionStyle.block);
+ }
+
+ if(block && scanner_.front.id == TokenID.blockMappingStart)
+ {
+ endMark = scanner_.front.endMark;
+ state_ = &parseBlockMappingKey!(Yes.first);
+ return mappingStartEvent(startMark, endMark, anchor, tag,
+ implicit, CollectionStyle.block);
+ }
+
+ if(anchor !is null || tag !is null)
+ {
+ state_ = popState();
+
+ //PyYAML uses a tuple(implicit, false) for the second last arg here,
+ //but the second bool is never used after that - so we don't use it.
+
+ //Empty scalars are allowed even if a tag or an anchor is specified.
+ return scalarEvent(startMark, endMark, anchor, tag,
+ implicit , "");
+ }
+
+ const token = scanner_.front;
+ throw new ParserException("While parsing a " ~ (block ? "block" : "flow") ~ " node",
+ startMark, "expected node content, but found: "
+ ~ token.idString, token.startMark);
+ }
+
+ /// Handle escape sequences in a double quoted scalar.
+ ///
+ /// Moved here from scanner as it can't always be done in-place with slices.
+ string handleDoubleQuotedScalarEscapes(const(char)[] tokenValue) const @safe
+ {
+ string notInPlace;
+ bool inEscape;
+ auto appender = appender!(string)();
+ for(const(char)[] oldValue = tokenValue; !oldValue.empty();)
+ {
+ const dchar c = oldValue.front();
+ oldValue.popFront();
+
+ if(!inEscape)
+ {
+ if(c != '\\')
+ {
+ if(notInPlace is null) { appender.put(c); }
+ else { notInPlace ~= c; }
+ continue;
+ }
+ // Escape sequence starts with a '\'
+ inEscape = true;
+ continue;
+ }
+
+ import dyaml.escapes;
+ scope(exit) { inEscape = false; }
+
+ // 'Normal' escape sequence.
+ if(c.among!(escapes))
+ {
+ if(notInPlace is null)
+ {
+ // \L and \C can't be handled in place as the expand into
+ // many-byte unicode chars
+ if(c != 'L' && c != 'P')
+ {
+ appender.put(dyaml.escapes.fromEscape(c));
+ continue;
+ }
+ // Need to duplicate as we won't fit into
+ // token.value - which is what appender uses
+ notInPlace = appender.data.dup;
+ notInPlace ~= dyaml.escapes.fromEscape(c);
+ continue;
+ }
+ notInPlace ~= dyaml.escapes.fromEscape(c);
+ continue;
+ }
+
+ // Unicode char written in hexadecimal in an escape sequence.
+ if(c.among!(escapeHexCodeList))
+ {
+ // Scanner has already checked that the hex string is valid.
+
+ const hexLength = dyaml.escapes.escapeHexLength(c);
+ // Any hex digits are 1-byte so this works.
+ const(char)[] hex = oldValue[0 .. hexLength];
+ oldValue = oldValue[hexLength .. $];
+ import std.ascii : isHexDigit;
+ assert(!hex.canFind!(d => !d.isHexDigit),
+ "Scanner must ensure the hex string is valid");
+
+ const decoded = cast(dchar)parse!int(hex, 16u);
+ if(notInPlace is null) { appender.put(decoded); }
+ else { notInPlace ~= decoded; }
+ continue;
+ }
+
+ assert(false, "Scanner must handle unsupported escapes");
+ }
+
+ return notInPlace is null ? appender.data : notInPlace;
+ }
+
+ /**
+ * Process a tag string retrieved from a tag token.
+ *
+ * Params: tag = Tag before processing.
+ * handleEnd = Index in tag where tag handle ends and tag suffix
+ * starts.
+ * startMark = Position of the node the tag belongs to.
+ * tagMark = Position of the tag.
+ */
+ string processTag(const string tag, const uint handleEnd,
+ const Mark startMark, const Mark tagMark)
+ const @safe
+ {
+ const handle = tag[0 .. handleEnd];
+ const suffix = tag[handleEnd .. $];
+
+ if(handle.length > 0)
+ {
+ string replacement;
+ foreach(ref pair; tagDirectives_)
+ {
+ if(pair.handle == handle)
+ {
+ replacement = pair.prefix;
+ break;
+ }
+ }
+ //handle must be in tagDirectives_
+ enforce(replacement !is null,
+ new ParserException("While parsing a node", startMark,
+ "found undefined tag handle: " ~ handle, tagMark));
+ return replacement ~ suffix;
+ }
+ return suffix;
+ }
+
+ ///Wrappers to parse nodes.
+ Event parseBlockNode() @safe {return parseNode(Yes.block);}
+ Event parseFlowNode() @safe {return parseNode(No.block);}
+ Event parseBlockNodeOrIndentlessSequence() @safe {return parseNode(Yes.block, Yes.indentlessSequence);}
+
+ ///block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
+
+ ///Parse an entry of a block sequence. If first is true, this is the first entry.
+ Event parseBlockSequenceEntry(Flag!"first" first)() @safe
+ {
+ static if(first)
+ {
+ pushMark(scanner_.front.startMark);
+ scanner_.popFront();
+ }
+
+ if(scanner_.front.id == TokenID.blockEntry)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+ if(!scanner_.front.id.among!(TokenID.blockEntry, TokenID.blockEnd))
+ {
+ pushState(&parseBlockSequenceEntry!(No.first));
+ return parseBlockNode();
+ }
+
+ state_ = &parseBlockSequenceEntry!(No.first);
+ return processEmptyScalar(token.endMark);
+ }
+
+ if(scanner_.front.id != TokenID.blockEnd)
+ {
+ const token = scanner_.front;
+ throw new ParserException("While parsing a block collection", marks_.data.back,
+ "expected block end, but found " ~ token.idString,
+ token.startMark);
+ }
+
+ state_ = popState();
+ popMark();
+ const token = scanner_.front;
+ scanner_.popFront();
+ return sequenceEndEvent(token.startMark, token.endMark);
+ }
+
+ ///indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+
+ ///Parse an entry of an indentless sequence.
+ Event parseIndentlessSequenceEntry() @safe
+ {
+ if(scanner_.front.id == TokenID.blockEntry)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+
+ if(!scanner_.front.id.among!(TokenID.blockEntry, TokenID.key,
+ TokenID.value, TokenID.blockEnd))
+ {
+ pushState(&parseIndentlessSequenceEntry);
+ return parseBlockNode();
+ }
+
+ state_ = &parseIndentlessSequenceEntry;
+ return processEmptyScalar(token.endMark);
+ }
+
+ state_ = popState();
+ const token = scanner_.front;
+ return sequenceEndEvent(token.startMark, token.endMark);
+ }
+
+ /**
+ * block_mapping ::= BLOCK-MAPPING_START
+ * ((KEY block_node_or_indentless_sequence?)?
+ * (VALUE block_node_or_indentless_sequence?)?)*
+ * BLOCK-END
+ */
+
+ ///Parse a key in a block mapping. If first is true, this is the first key.
+ Event parseBlockMappingKey(Flag!"first" first)() @safe
+ {
+ static if(first)
+ {
+ pushMark(scanner_.front.startMark);
+ scanner_.popFront();
+ }
+
+ if(scanner_.front.id == TokenID.key)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+
+ if(!scanner_.front.id.among!(TokenID.key, TokenID.value, TokenID.blockEnd))
+ {
+ pushState(&parseBlockMappingValue);
+ return parseBlockNodeOrIndentlessSequence();
+ }
+
+ state_ = &parseBlockMappingValue;
+ return processEmptyScalar(token.endMark);
+ }
+
+ if(scanner_.front.id != TokenID.blockEnd)
+ {
+ const token = scanner_.front;
+ throw new ParserException("While parsing a block mapping", marks_.data.back,
+ "expected block end, but found: " ~ token.idString,
+ token.startMark);
+ }
+
+ state_ = popState();
+ popMark();
+ const token = scanner_.front;
+ scanner_.popFront();
+ return mappingEndEvent(token.startMark, token.endMark);
+ }
+
+ ///Parse a value in a block mapping.
+ Event parseBlockMappingValue() @safe
+ {
+ if(scanner_.front.id == TokenID.value)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+
+ if(!scanner_.front.id.among!(TokenID.key, TokenID.value, TokenID.blockEnd))
+ {
+ pushState(&parseBlockMappingKey!(No.first));
+ return parseBlockNodeOrIndentlessSequence();
+ }
+
+ state_ = &parseBlockMappingKey!(No.first);
+ return processEmptyScalar(token.endMark);
+ }
+
+ state_= &parseBlockMappingKey!(No.first);
+ return processEmptyScalar(scanner_.front.startMark);
+ }
+
+ /**
+ * flow_sequence ::= FLOW-SEQUENCE-START
+ * (flow_sequence_entry FLOW-ENTRY)*
+ * flow_sequence_entry?
+ * FLOW-SEQUENCE-END
+ * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ *
+ * Note that while production rules for both flow_sequence_entry and
+ * flow_mapping_entry are equal, their interpretations are different.
+ * For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
+ * generate an inline mapping (set syntax).
+ */
+
+ ///Parse an entry in a flow sequence. If first is true, this is the first entry.
+ Event parseFlowSequenceEntry(Flag!"first" first)() @safe
+ {
+ static if(first)
+ {
+ pushMark(scanner_.front.startMark);
+ scanner_.popFront();
+ }
+
+ if(scanner_.front.id != TokenID.flowSequenceEnd)
+ {
+ static if(!first)
+ {
+ if(scanner_.front.id == TokenID.flowEntry)
+ {
+ scanner_.popFront();
+ }
+ else
+ {
+ const token = scanner_.front;
+ throw new ParserException("While parsing a flow sequence", marks_.data.back,
+ "expected ',' or ']', but got: " ~
+ token.idString, token.startMark);
+ }
+ }
+
+ if(scanner_.front.id == TokenID.key)
+ {
+ const token = scanner_.front;
+ state_ = &parseFlowSequenceEntryMappingKey;
+ return mappingStartEvent(token.startMark, token.endMark,
+ null, null, true, CollectionStyle.flow);
+ }
+ else if(scanner_.front.id != TokenID.flowSequenceEnd)
+ {
+ pushState(&parseFlowSequenceEntry!(No.first));
+ return parseFlowNode();
+ }
+ }
+
+ const token = scanner_.front;
+ scanner_.popFront();
+ state_ = popState();
+ popMark();
+ return sequenceEndEvent(token.startMark, token.endMark);
+ }
+
+ ///Parse a key in flow context.
+ Event parseFlowKey(Event delegate() @safe nextState) @safe
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+
+ if(!scanner_.front.id.among!(TokenID.value, TokenID.flowEntry,
+ TokenID.flowSequenceEnd))
+ {
+ pushState(nextState);
+ return parseFlowNode();
+ }
+
+ state_ = nextState;
+ return processEmptyScalar(token.endMark);
+ }
+
+ ///Parse a mapping key in an entry in a flow sequence.
+ Event parseFlowSequenceEntryMappingKey() @safe
+ {
+ return parseFlowKey(&parseFlowSequenceEntryMappingValue);
+ }
+
+ ///Parse a mapping value in a flow context.
+ Event parseFlowValue(TokenID checkId, Event delegate() @safe nextState)
+ @safe
+ {
+ if(scanner_.front.id == TokenID.value)
+ {
+ const token = scanner_.front;
+ scanner_.popFront();
+ if(!scanner_.front.id.among(TokenID.flowEntry, checkId))
+ {
+ pushState(nextState);
+ return parseFlowNode();
+ }
+
+ state_ = nextState;
+ return processEmptyScalar(token.endMark);
+ }
+
+ state_ = nextState;
+ return processEmptyScalar(scanner_.front.startMark);
+ }
+
+ ///Parse a mapping value in an entry in a flow sequence.
+ Event parseFlowSequenceEntryMappingValue() @safe
+ {
+ return parseFlowValue(TokenID.flowSequenceEnd,
+ &parseFlowSequenceEntryMappingEnd);
+ }
+
+ ///Parse end of a mapping in a flow sequence entry.
+ Event parseFlowSequenceEntryMappingEnd() @safe
+ {
+ state_ = &parseFlowSequenceEntry!(No.first);
+ const token = scanner_.front;
+ return mappingEndEvent(token.startMark, token.startMark);
+ }
+
+ /**
+ * flow_mapping ::= FLOW-MAPPING-START
+ * (flow_mapping_entry FLOW-ENTRY)*
+ * flow_mapping_entry?
+ * FLOW-MAPPING-END
+ * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ */
+
+ ///Parse a key in a flow mapping.
+ Event parseFlowMappingKey(Flag!"first" first)() @safe
+ {
+ static if(first)
+ {
+ pushMark(scanner_.front.startMark);
+ scanner_.popFront();
+ }
+
+ if(scanner_.front.id != TokenID.flowMappingEnd)
+ {
+ static if(!first)
+ {
+ if(scanner_.front.id == TokenID.flowEntry)
+ {
+ scanner_.popFront();
+ }
+ else
+ {
+ const token = scanner_.front;
+ throw new ParserException("While parsing a flow mapping", marks_.data.back,
+ "expected ',' or '}', but got: " ~
+ token.idString, token.startMark);
+ }
+ }
+
+ if(scanner_.front.id == TokenID.key)
+ {
+ return parseFlowKey(&parseFlowMappingValue);
+ }
+
+ if(scanner_.front.id != TokenID.flowMappingEnd)
+ {
+ pushState(&parseFlowMappingEmptyValue);
+ return parseFlowNode();
+ }
+ }
+
+ const token = scanner_.front;
+ scanner_.popFront();
+ state_ = popState();
+ popMark();
+ return mappingEndEvent(token.startMark, token.endMark);
+ }
+
+ ///Parse a value in a flow mapping.
+ Event parseFlowMappingValue() @safe
+ {
+ return parseFlowValue(TokenID.flowMappingEnd, &parseFlowMappingKey!(No.first));
+ }
+
+ ///Parse an empty value in a flow mapping.
+ Event parseFlowMappingEmptyValue() @safe
+ {
+ state_ = &parseFlowMappingKey!(No.first);
+ return processEmptyScalar(scanner_.front.startMark);
+ }
+
+ ///Return an empty scalar.
+ Event processEmptyScalar(const Mark mark) @safe pure nothrow const @nogc
+ {
+ return scalarEvent(mark, mark, null, null, true, "");
+ }
+}