// This file is part of YamlDotNet - A .NET library for YAML. // Copyright (c) Antoine Aubry and contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using YamlDotNet.Core.Tokens; using MappingStyle = YamlDotNet.Core.Events.MappingStyle; using ParsingEvent = YamlDotNet.Core.Events.ParsingEvent; using SequenceStyle = YamlDotNet.Core.Events.SequenceStyle; namespace YamlDotNet.Core { /// /// Parses YAML streams. /// public class Parser : IParser { private readonly Stack states = new Stack(); private readonly TagDirectiveCollection tagDirectives = new TagDirectiveCollection(); private ParserState state; private readonly IScanner scanner; private Token? currentToken; private VersionDirective? version; private Token? GetCurrentToken() { if (currentToken == null) { while (scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (currentToken is Comment commentToken) { pendingEvents.Enqueue(new Events.Comment(commentToken.Value, commentToken.IsInline, commentToken.Start, commentToken.End)); scanner.ConsumeCurrent(); } else { break; } } } return currentToken; } /// /// Initializes a new instance of the class. /// /// The input where the YAML stream is to be read. public Parser(TextReader input) : this(new Scanner(input)) { } /// /// Initializes a new instance of the class. /// public Parser(IScanner scanner) { this.scanner = scanner; } /// /// Gets the current event. /// public ParsingEvent? Current { get; private set; } private readonly EventQueue pendingEvents = new EventQueue(); /// /// Moves to the next event. /// /// Returns true if there are more events available, otherwise returns false. public bool MoveNext() { // No events after the end of the stream or error. if (state == ParserState.StreamEnd) { Current = null; return false; } else if (pendingEvents.Count == 0) { // Generate the next event. pendingEvents.Enqueue(StateMachine()); } Current = pendingEvents.Dequeue(); return true; } private ParsingEvent StateMachine() { switch (state) { case ParserState.StreamStart: return ParseStreamStart(); case ParserState.ImplicitDocumentStart: return ParseDocumentStart(true); case ParserState.DocumentStart: return ParseDocumentStart(false); case ParserState.DocumentContent: return ParseDocumentContent(); case ParserState.DocumentEnd: return ParseDocumentEnd(); case ParserState.BlockNode: return ParseNode(true, false); case ParserState.BlockNodeOrIndentlessSequence: return ParseNode(true, true); case ParserState.FlowNode: return ParseNode(false, false); case ParserState.BlockSequenceFirstEntry: return ParseBlockSequenceEntry(true); case ParserState.BlockSequenceEntry: return ParseBlockSequenceEntry(false); case ParserState.IndentlessSequenceEntry: return ParseIndentlessSequenceEntry(); case ParserState.BlockMappingFirstKey: return ParseBlockMappingKey(true); case ParserState.BlockMappingKey: return ParseBlockMappingKey(false); case ParserState.BlockMappingValue: return ParseBlockMappingValue(); case ParserState.FlowSequenceFirstEntry: return ParseFlowSequenceEntry(true); case ParserState.FlowSequenceEntry: return ParseFlowSequenceEntry(false); case ParserState.FlowSequenceEntryMappingKey: return ParseFlowSequenceEntryMappingKey(); case ParserState.FlowSequenceEntryMappingValue: return ParseFlowSequenceEntryMappingValue(); case ParserState.FlowSequenceEntryMappingEnd: return ParseFlowSequenceEntryMappingEnd(); case ParserState.FlowMappingFirstKey: return ParseFlowMappingKey(true); case ParserState.FlowMappingKey: return ParseFlowMappingKey(false); case ParserState.FlowMappingValue: return ParseFlowMappingValue(false); case ParserState.FlowMappingEmptyValue: return ParseFlowMappingValue(true); default: Debug.Assert(false, "Invalid state"); // Invalid state. throw new InvalidOperationException(); } } private void Skip() { if (currentToken != null) { currentToken = null; scanner.ConsumeCurrent(); } } /// /// Parse the production: /// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END /// ************ /// private ParsingEvent ParseStreamStart() { var current = GetCurrentToken(); if (!(current is StreamStart streamStart)) { throw new SemanticErrorException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "Did not find expected ."); } Skip(); state = ParserState.ImplicitDocumentStart; return new Events.StreamStart(streamStart.Start, streamStart.End); } /// /// Parse the productions: /// implicit_document ::= block_node DOCUMENT-END* /// * /// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* /// ************************* /// private ParsingEvent ParseDocumentStart(bool isImplicit) { if (currentToken is VersionDirective) { // EB22 throw new SyntaxErrorException("While parsing a document start node, could not find document end marker before version directive."); } // Parse extra document end indicators. var current = GetCurrentToken(); if (!isImplicit) { while (current is DocumentEnd) { Skip(); current = GetCurrentToken(); } } if (current == null) { throw new SyntaxErrorException("Reached the end of the stream while parsing a document start."); } if (current is Scalar && (state == ParserState.ImplicitDocumentStart || state == ParserState.DocumentStart)) { isImplicit = true; } // Parse an isImplicit document. if (isImplicit && !(current is VersionDirective || current is TagDirective || current is DocumentStart || current is StreamEnd || current is DocumentEnd) || current is BlockMappingStart) { var directives = new TagDirectiveCollection(); ProcessDirectives(directives); states.Push(ParserState.DocumentEnd); state = ParserState.BlockNode; return new Events.DocumentStart(null, directives, true, current.Start, current.End); } // Parse an explicit document. else if (!(current is StreamEnd || current is DocumentEnd)) { var start = current.Start; var directives = new TagDirectiveCollection(); var versionDirective = ProcessDirectives(directives); current = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document start"); if (!(current is DocumentStart)) { throw new SemanticErrorException(current.Start, current.End, "Did not find expected ."); } states.Push(ParserState.DocumentEnd); state = ParserState.DocumentContent; var end = current.End; Skip(); return new Events.DocumentStart(versionDirective, directives, false, start, end); } // Parse the stream end. else { if (current is DocumentEnd) { Skip(); } state = ParserState.StreamEnd; current = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document start"); var evt = new Events.StreamEnd(current.Start, current.End); // Do not call skip here because that would throw an exception if (scanner.MoveNextWithoutConsuming()) { throw new InvalidOperationException("The scanner should contain no more tokens."); } return evt; } } /// /// Parse directives. /// private VersionDirective? ProcessDirectives(TagDirectiveCollection tags) { var hasOwnDirectives = false; VersionDirective? localVersion = null; while (true) { if (GetCurrentToken() is VersionDirective currentVersion) { if (version != null) { throw new SemanticErrorException(currentVersion.Start, currentVersion.End, "Found duplicate %YAML directive."); } if (currentVersion.Version.Major != Constants.MajorVersion || currentVersion.Version.Minor > Constants.MinorVersion) { throw new SemanticErrorException(currentVersion.Start, currentVersion.End, "Found incompatible YAML document."); } localVersion = version = currentVersion; hasOwnDirectives = true; } else if (GetCurrentToken() is TagDirective tag) { if (tags.Contains(tag.Handle)) { throw new SemanticErrorException(tag.Start, tag.End, "Found duplicate %TAG directive."); } tags.Add(tag); hasOwnDirectives = true; } // Starting from v1.2, it is not permitted to use tag shorthands for multiple documents in a stream. else if (GetCurrentToken() is DocumentStart && (version == null || (version.Version.Major == 1 && version.Version.Minor > 1))) { if (GetCurrentToken() is DocumentStart && (version == null)) { version = new VersionDirective(new Version(1, 2)); } hasOwnDirectives = true; break; } else { break; } Skip(); } AddTagDirectives(tags, Constants.DefaultTagDirectives); if (hasOwnDirectives) { tagDirectives.Clear(); } AddTagDirectives(tagDirectives, tags); return localVersion; } private static void AddTagDirectives(TagDirectiveCollection directives, IEnumerable source) { foreach (var directive in source) { if (!directives.Contains(directive)) { directives.Add(directive); } } } /// /// Parse the productions: /// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* /// *********** /// private ParsingEvent ParseDocumentContent() { if ( GetCurrentToken() is VersionDirective || GetCurrentToken() is TagDirective || GetCurrentToken() is DocumentStart || GetCurrentToken() is DocumentEnd || GetCurrentToken() is StreamEnd ) { state = states.Pop(); return ProcessEmptyScalar(scanner.CurrentPosition); } else { return ParseNode(true, false); } } /// /// Generate an empty scalar event. /// private static ParsingEvent ProcessEmptyScalar(Mark position) { return new Events.Scalar(AnchorName.Empty, TagName.Empty, string.Empty, ScalarStyle.Plain, true, false, position, position); } /// /// Parse the productions: /// 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 /// ****** /// private ParsingEvent ParseNode(bool isBlock, bool isIndentlessSequence) { if (GetCurrentToken() is Error errorToken) { throw new SemanticErrorException(errorToken.Start, errorToken.End, errorToken.Value); } var current = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a node"); if (current is AnchorAlias alias) { state = states.Pop(); ParsingEvent evt = new Events.AnchorAlias(alias.Value, alias.Start, alias.End); Skip(); return evt; } var start = current.Start; var anchorName = AnchorName.Empty; var tagName = TagName.Empty; Anchor? lastAnchor = null; Tag? lastTag = null; // The anchor and the tag can be in any order. This loop repeats at most twice. while (true) { if (anchorName.IsEmpty && current is Anchor anchor) { lastAnchor = anchor; anchorName = anchor.Value; Skip(); } else if (tagName.IsEmpty && current is Tag tag) { lastTag = tag; if (string.IsNullOrEmpty(tag.Handle)) { tagName = new TagName(tag.Suffix); } else if (tagDirectives.Contains(tag.Handle)) { tagName = new TagName(string.Concat(tagDirectives[tag.Handle].Prefix, tag.Suffix)); } else { throw new SemanticErrorException(tag.Start, tag.End, "While parsing a node, found undefined tag handle."); } Skip(); } else if (current is Anchor secondAnchor) { throw new SemanticErrorException(secondAnchor.Start, secondAnchor.End, "While parsing a node, found more than one anchor."); } else if (current is AnchorAlias anchorAlias) { throw new SemanticErrorException(anchorAlias.Start, anchorAlias.End, "While parsing a node, did not find expected token."); } else if (current is Error error) { if (lastTag != null && lastAnchor != null && !anchorName.IsEmpty) { return new Events.Scalar(anchorName, default, string.Empty, default, false, false, lastAnchor.Start, lastAnchor.End); } throw new SemanticErrorException(error.Start, error.End, error.Value); } else { break; } current = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a node"); } var isImplicit = tagName.IsEmpty; if (isIndentlessSequence && GetCurrentToken() is BlockEntry) { state = ParserState.IndentlessSequenceEntry; return new Events.SequenceStart( anchorName, tagName, isImplicit, SequenceStyle.Block, start, current.End ); } else { if (current is Scalar scalar) { var isPlainImplicit = false; var isQuotedImplicit = false; if ((scalar.Style == ScalarStyle.Plain && tagName.IsEmpty) || tagName.IsNonSpecific) { isPlainImplicit = true; } else if (tagName.IsEmpty) { isQuotedImplicit = true; } state = states.Pop(); Skip(); ParsingEvent evt = new Events.Scalar(anchorName, tagName, scalar.Value, scalar.Style, isPlainImplicit, isQuotedImplicit, start, scalar.End); // Read next token to ensure the error case spec test 'CXX2': // "Mapping with anchor on document start line". if (!anchorName.IsEmpty && scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (currentToken is Error) { errorToken = (currentToken as Error)!; throw new SemanticErrorException(errorToken.Start, errorToken.End, errorToken.Value); } } // Read next token to ensure the error case spec test 'T833': // "Flow mapping missing a separating comma". if (state == ParserState.FlowMappingKey && scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (currentToken != null && !(currentToken is FlowEntry) && !(currentToken is FlowMappingEnd)) { throw new SemanticErrorException(currentToken.Start, currentToken.End, "While parsing a flow mapping, did not find expected ',' or '}'."); } } return evt; } if (current is FlowSequenceStart flowSequenceStart) { state = ParserState.FlowSequenceFirstEntry; return new Events.SequenceStart(anchorName, tagName, isImplicit, SequenceStyle.Flow, start, flowSequenceStart.End); } if (current is FlowMappingStart flowMappingStart) { state = ParserState.FlowMappingFirstKey; return new Events.MappingStart(anchorName, tagName, isImplicit, MappingStyle.Flow, start, flowMappingStart.End); } if (isBlock) { if (current is BlockSequenceStart blockSequenceStart) { state = ParserState.BlockSequenceFirstEntry; return new Events.SequenceStart(anchorName, tagName, isImplicit, SequenceStyle.Block, start, blockSequenceStart.End); } if (current is BlockMappingStart blockMappingStart) { state = ParserState.BlockMappingFirstKey; return new Events.MappingStart(anchorName, tagName, isImplicit, MappingStyle.Block, start, blockMappingStart.End); } } if (!anchorName.IsEmpty || !tagName.IsEmpty) { state = states.Pop(); return new Events.Scalar(anchorName, tagName, string.Empty, ScalarStyle.Plain, isImplicit, false, start, current.End); } throw new SemanticErrorException(current.Start, current.End, "While parsing a node, did not find expected node content."); } } /// /// Parse the productions: /// implicit_document ::= block_node DOCUMENT-END* /// ************* /// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* /// ************* /// private ParsingEvent ParseDocumentEnd() { var current = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document end"); var isImplicit = true; var start = current.Start; var end = start; if (current is DocumentEnd) { end = current.End; Skip(); isImplicit = false; } else if (!(currentToken is StreamEnd || currentToken is DocumentStart || currentToken is FlowSequenceEnd || currentToken is VersionDirective || (Current is Events.Scalar && currentToken is Error))) { throw new SemanticErrorException(start, end, "Did not find expected ."); } if (version != null && version.Version.Major == 1 && version.Version.Minor > 1) { version = null; } state = ParserState.DocumentStart; return new Events.DocumentEnd(isImplicit, start, end); } /// /// Parse the productions: /// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END /// ******************** *********** * ********* /// private ParsingEvent ParseBlockSequenceEntry(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } var current = GetCurrentToken(); if (current is BlockEntry blockEntry) { var mark = blockEntry.End; Skip(); current = GetCurrentToken(); if (!(current is BlockEntry || current is BlockEnd)) { states.Push(ParserState.BlockSequenceEntry); return ParseNode(true, false); } else { state = ParserState.BlockSequenceEntry; return ProcessEmptyScalar(mark); } } else if (current is BlockEnd blockEnd) { state = states.Pop(); ParsingEvent evt = new Events.SequenceEnd(blockEnd.Start, blockEnd.End); Skip(); return evt; } else { throw new SemanticErrorException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "While parsing a block collection, did not find expected '-' indicator."); } } /// /// Parse the productions: /// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ /// *********** * /// private ParsingEvent ParseIndentlessSequenceEntry() { var current = GetCurrentToken(); if (current is BlockEntry blockEntry) { var mark = blockEntry.End; Skip(); current = GetCurrentToken(); if (!(current is BlockEntry || current is Key || current is Value || current is BlockEnd)) { states.Push(ParserState.IndentlessSequenceEntry); return ParseNode(true, false); } else { state = ParserState.IndentlessSequenceEntry; return ProcessEmptyScalar(mark); } } else { state = states.Pop(); return new Events.SequenceEnd(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty); } } /// /// Parse the productions: /// block_mapping ::= BLOCK-MAPPING_START /// ******************* /// ((KEY block_node_or_indentless_sequence?)? /// *** * /// (VALUE block_node_or_indentless_sequence?)?)* /// /// BLOCK-END /// ********* /// private ParsingEvent ParseBlockMappingKey(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } var current = GetCurrentToken(); if (current is Key key) { var mark = key.End; Skip(); current = GetCurrentToken(); if (!(current is Key || current is Value || current is BlockEnd)) { states.Push(ParserState.BlockMappingValue); return ParseNode(true, true); } else { state = ParserState.BlockMappingValue; return ProcessEmptyScalar(mark); } } else if (current is Value value) { Skip(); return ProcessEmptyScalar(value.End); } else if (current is AnchorAlias anchorAlias) { Skip(); return new Events.AnchorAlias(anchorAlias.Value, anchorAlias.Start, anchorAlias.End); } else if (current is BlockEnd blockEnd) { state = states.Pop(); ParsingEvent evt = new Events.MappingEnd(blockEnd.Start, blockEnd.End); Skip(); return evt; } else if (GetCurrentToken() is Error error) { throw new SyntaxErrorException(error.Start, error.End, error.Value); } else { throw new SemanticErrorException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "While parsing a block mapping, did not find expected key."); } } /// /// Parse the productions: /// block_mapping ::= BLOCK-MAPPING_START /// /// ((KEY block_node_or_indentless_sequence?)? /// /// (VALUE block_node_or_indentless_sequence?)?)* /// ***** * /// BLOCK-END /// /// private ParsingEvent ParseBlockMappingValue() { var current = GetCurrentToken(); if (current is Value value) { var mark = value.End; Skip(); current = GetCurrentToken(); if (!(current is Key || current is Value || current is BlockEnd)) { states.Push(ParserState.BlockMappingKey); return ParseNode(true, true); } else { state = ParserState.BlockMappingKey; return ProcessEmptyScalar(mark); } } else if (current is Error error) { throw new SemanticErrorException(error.Start, error.End, error.Value); } else { state = ParserState.BlockMappingKey; return ProcessEmptyScalar(current?.Start ?? Mark.Empty); } } /// /// Parse the productions: /// 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?)? /// * /// private ParsingEvent ParseFlowSequenceEntry(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } ParsingEvent evt; var current = GetCurrentToken(); if (!(current is FlowSequenceEnd)) { if (!isFirst) { if (current is FlowEntry) { Skip(); current = GetCurrentToken(); } else { throw new SemanticErrorException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "While parsing a flow sequence, did not find expected ',' or ']'."); } } if (current is Key) { state = ParserState.FlowSequenceEntryMappingKey; evt = new Events.MappingStart(AnchorName.Empty, TagName.Empty, true, MappingStyle.Flow); Skip(); return evt; } else if (!(current is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntry); return ParseNode(false, false); } } state = states.Pop(); evt = new Events.SequenceEnd(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty); Skip(); return evt; } /// /// Parse the productions: /// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? /// *** * /// private ParsingEvent ParseFlowSequenceEntryMappingKey() { var current = GetCurrentToken(); if (!(current is Value || current is FlowEntry || current is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntryMappingValue); return ParseNode(false, false); } else { var mark = current?.End ?? Mark.Empty; Skip(); state = ParserState.FlowSequenceEntryMappingValue; return ProcessEmptyScalar(mark); } } /// /// Parse the productions: /// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? /// ***** * /// private ParsingEvent ParseFlowSequenceEntryMappingValue() { var current = GetCurrentToken(); if (current is Value) { Skip(); current = GetCurrentToken(); if (!(current is FlowEntry || current is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntryMappingEnd); return ParseNode(false, false); } } state = ParserState.FlowSequenceEntryMappingEnd; return ProcessEmptyScalar(current?.Start ?? Mark.Empty); } /// /// Parse the productions: /// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? /// * /// private ParsingEvent ParseFlowSequenceEntryMappingEnd() { state = ParserState.FlowSequenceEntry; var current = GetCurrentToken(); return new Events.MappingEnd(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty); } /// /// Parse the productions: /// 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?)? /// * *** * /// private ParsingEvent ParseFlowMappingKey(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } var current = GetCurrentToken(); if (!(current is FlowMappingEnd)) { if (!isFirst) { if (current is FlowEntry) { Skip(); current = GetCurrentToken(); } else { throw new SemanticErrorException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "While parsing a flow mapping, did not find expected ',' or '}'."); } } if (current is Key) { Skip(); current = GetCurrentToken(); if (!(current is Value || current is FlowEntry || current is FlowMappingEnd)) { states.Push(ParserState.FlowMappingValue); return ParseNode(false, false); } else { state = ParserState.FlowMappingValue; return ProcessEmptyScalar(current?.Start ?? Mark.Empty); } } else if (current is Scalar) { states.Push(ParserState.FlowMappingValue); return ParseNode(false, false); } else if (!(current is FlowMappingEnd)) { states.Push(ParserState.FlowMappingEmptyValue); return ParseNode(false, false); } } state = states.Pop(); Skip(); return new Events.MappingEnd(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty); } /// /// Parse the productions: /// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? /// * ***** * /// private ParsingEvent ParseFlowMappingValue(bool isEmpty) { var current = GetCurrentToken(); if (isEmpty) { state = ParserState.FlowMappingKey; return ProcessEmptyScalar(current?.Start ?? Mark.Empty); } if (current is Value) { Skip(); current = GetCurrentToken(); if (!(current is FlowEntry || current is FlowMappingEnd)) { states.Push(ParserState.FlowMappingKey); return ParseNode(false, false); } } state = ParserState.FlowMappingKey; return ProcessEmptyScalar(current?.Start ?? Mark.Empty); } private class EventQueue { // This class is specialized for our specific use case where there are exactly two priority levels. // If more levels are required, a more generic implementation should be used instead. private readonly Queue highPriorityEvents = new Queue(); private readonly Queue normalPriorityEvents = new Queue(); public void Enqueue(ParsingEvent @event) { switch (@event.Type) { case Events.EventType.StreamStart: case Events.EventType.DocumentStart: highPriorityEvents.Enqueue(@event); break; default: normalPriorityEvents.Enqueue(@event); break; } } public ParsingEvent Dequeue() { return highPriorityEvents.Count > 0 ? highPriorityEvents.Dequeue() : normalPriorityEvents.Dequeue(); } public int Count { get { return highPriorityEvents.Count + normalPriorityEvents.Count; } } } } }