1 
2 //          Copyright Ferdinand Majerech 2011-2014.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 /**
8  * YAML parser.
9  * Code based on PyYAML: http://www.pyyaml.org
10  */
11 module dyaml.parser;
12 
13 
14 import std.algorithm;
15 import std.array;
16 import std.conv;
17 import std.exception;
18 import std.typecons;
19 
20 import dyaml.event;
21 import dyaml.exception;
22 import dyaml.scanner;
23 import dyaml.style;
24 import dyaml.token;
25 import dyaml.tagdirective;
26 
27 
28 /**
29  * The following YAML grammar is LL(1) and is parsed by a recursive descent
30  * parser.
31  *
32  * stream            ::= STREAM-START implicit_document? explicit_document* STREAM-END
33  * implicit_document ::= block_node DOCUMENT-END*
34  * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
35  * block_node_or_indentless_sequence ::=
36  *                       ALIAS
37  *                       | properties (block_content | indentless_block_sequence)?
38  *                       | block_content
39  *                       | indentless_block_sequence
40  * block_node        ::= ALIAS
41  *                       | properties block_content?
42  *                       | block_content
43  * flow_node         ::= ALIAS
44  *                       | properties flow_content?
45  *                       | flow_content
46  * properties        ::= TAG ANCHOR? | ANCHOR TAG?
47  * block_content     ::= block_collection | flow_collection | SCALAR
48  * flow_content      ::= flow_collection | SCALAR
49  * block_collection  ::= block_sequence | block_mapping
50  * flow_collection   ::= flow_sequence | flow_mapping
51  * block_sequence    ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
52  * indentless_sequence   ::= (BLOCK-ENTRY block_node?)+
53  * block_mapping     ::= BLOCK-MAPPING_START
54  *                       ((KEY block_node_or_indentless_sequence?)?
55  *                       (VALUE block_node_or_indentless_sequence?)?)*
56  *                       BLOCK-END
57  * flow_sequence     ::= FLOW-SEQUENCE-START
58  *                       (flow_sequence_entry FLOW-ENTRY)*
59  *                       flow_sequence_entry?
60  *                       FLOW-SEQUENCE-END
61  * flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
62  * flow_mapping      ::= FLOW-MAPPING-START
63  *                       (flow_mapping_entry FLOW-ENTRY)*
64  *                       flow_mapping_entry?
65  *                       FLOW-MAPPING-END
66  * flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
67  *
68  * FIRST sets:
69  *
70  * stream: { STREAM-START }
71  * explicit_document: { DIRECTIVE DOCUMENT-START }
72  * implicit_document: FIRST(block_node)
73  * block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
74  * flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
75  * block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
76  * flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
77  * block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
78  * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
79  * block_sequence: { BLOCK-SEQUENCE-START }
80  * block_mapping: { BLOCK-MAPPING-START }
81  * block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
82  * indentless_sequence: { ENTRY }
83  * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
84  * flow_sequence: { FLOW-SEQUENCE-START }
85  * flow_mapping: { FLOW-MAPPING-START }
86  * flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
87  * flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
88  */
89 
90 
91 package:
92 /// Generates events from tokens provided by a Scanner.
93 ///
94 /// While Parser receives tokens with non-const character slices, the events it
95 /// produces are immutable strings, which are usually the same slices, cast to string.
96 /// Parser is the last layer of D:YAML that may possibly do any modifications to these
97 /// slices.
98 final class Parser
99 {
100     private:
101         ///Default tag handle shortcuts and replacements.
102         static TagDirective[] defaultTagDirectives_ =
103             [TagDirective("!", "!"), TagDirective("!!", "tag:yaml.org,2002:")];
104 
105         ///Scanner providing YAML tokens.
106         Scanner scanner_;
107 
108         ///Event produced by the most recent state.
109         Event currentEvent_;
110 
111         ///YAML version string.
112         string YAMLVersion_ = null;
113         ///Tag handle shortcuts and replacements.
114         TagDirective[] tagDirectives_;
115 
116         ///Stack of states.
117         Appender!(Event delegate() @safe[]) states_;
118         ///Stack of marks used to keep track of extents of e.g. YAML collections.
119         Appender!(Mark[]) marks_;
120 
121         ///Current state.
122         Event delegate() @safe state_;
123 
124     public:
125         ///Construct a Parser using specified Scanner.
126         this(Scanner scanner) @safe
127         {
128             state_ = &parseStreamStart;
129             scanner_ = scanner;
130             states_.reserve(32);
131             marks_.reserve(32);
132         }
133 
134         /**
135          * Check if any events are left. May have side effects in some cases.
136          */
137         bool empty() @safe
138         {
139             ensureState();
140             return currentEvent_.isNull;
141         }
142 
143         /**
144          * Return the current event.
145          *
146          * Must not be called if there are no events left.
147          */
148         Event front() @safe
149         {
150             ensureState();
151             assert(!currentEvent_.isNull, "No event left to peek");
152             return currentEvent_;
153         }
154 
155         /**
156          * Skip to the next event.
157          *
158          * Must not be called if there are no events left.
159          */
160         void popFront() @safe
161         {
162             currentEvent_.id = EventID.invalid;
163             ensureState();
164         }
165 
166         /// Set file name.
167         ref inout(string) name() inout @safe return pure nothrow @nogc
168         {
169             return scanner_.name;
170         }
171         /// Get a mark from the current reader position
172         Mark mark() const @safe pure nothrow @nogc
173         {
174             return scanner_.mark;
175         }
176 
177     private:
178         /// If current event is invalid, load the next valid one if possible.
179         void ensureState() @safe
180         {
181             if(currentEvent_.isNull && state_ !is null)
182             {
183                 currentEvent_ = state_();
184             }
185         }
186         ///Pop and return the newest state in states_.
187         Event delegate() @safe popState() @safe
188         {
189             enforce(states_.data.length > 0,
190                     new YAMLException("Parser: Need to pop state but no states left to pop"));
191             const result = states_.data.back;
192             states_.shrinkTo(states_.data.length - 1);
193             return result;
194         }
195 
196         ///Pop and return the newest mark in marks_.
197         Mark popMark() @safe
198         {
199             enforce(marks_.data.length > 0,
200                     new YAMLException("Parser: Need to pop mark but no marks left to pop"));
201             const result = marks_.data.back;
202             marks_.shrinkTo(marks_.data.length - 1);
203             return result;
204         }
205 
206         /// Push a state on the stack
207         void pushState(Event delegate() @safe state) @safe
208         {
209             states_ ~= state;
210         }
211         /// Push a mark on the stack
212         void pushMark(Mark mark) @safe
213         {
214             marks_ ~= mark;
215         }
216 
217         /**
218          * stream    ::= STREAM-START implicit_document? explicit_document* STREAM-END
219          * implicit_document ::= block_node DOCUMENT-END*
220          * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
221          */
222 
223         ///Parse stream start.
224         Event parseStreamStart() @safe
225         {
226             const token = scanner_.front;
227             scanner_.popFront();
228             state_ = &parseImplicitDocumentStart;
229             return streamStartEvent(token.startMark, token.endMark);
230         }
231 
232         /// Parse implicit document start, unless explicit detected: if so, parse explicit.
233         Event parseImplicitDocumentStart() @safe
234         {
235             // Parse an implicit document.
236             if(!scanner_.front.id.among!(TokenID.directive, TokenID.documentStart,
237                                     TokenID.streamEnd))
238             {
239                 tagDirectives_  = defaultTagDirectives_;
240                 const token = scanner_.front;
241 
242                 pushState(&parseDocumentEnd);
243                 state_ = &parseBlockNode;
244 
245                 return documentStartEvent(token.startMark, token.endMark, false, null, null);
246             }
247             return parseDocumentStart();
248         }
249 
250         ///Parse explicit document start.
251         Event parseDocumentStart() @safe
252         {
253             //Parse any extra document end indicators.
254             while(scanner_.front.id == TokenID.documentEnd)
255             {
256                 scanner_.popFront();
257             }
258 
259             //Parse an explicit document.
260             if(scanner_.front.id != TokenID.streamEnd)
261             {
262                 const startMark = scanner_.front.startMark;
263 
264                 auto tagDirectives = processDirectives();
265                 enforce(scanner_.front.id == TokenID.documentStart,
266                         new ParserException("Expected document start but found " ~
267                                   scanner_.front.idString,
268                                   scanner_.front.startMark));
269 
270                 const endMark = scanner_.front.endMark;
271                 scanner_.popFront();
272                 pushState(&parseDocumentEnd);
273                 state_ = &parseDocumentContent;
274                 return documentStartEvent(startMark, endMark, true, YAMLVersion_, tagDirectives);
275             }
276             else
277             {
278                 //Parse the end of the stream.
279                 const token = scanner_.front;
280                 scanner_.popFront();
281                 assert(states_.data.length == 0);
282                 assert(marks_.data.length == 0);
283                 state_ = null;
284                 return streamEndEvent(token.startMark, token.endMark);
285             }
286         }
287 
288         ///Parse document end (explicit or implicit).
289         Event parseDocumentEnd() @safe
290         {
291             Mark startMark = scanner_.front.startMark;
292             const bool explicit = scanner_.front.id == TokenID.documentEnd;
293             Mark endMark = startMark;
294             if (explicit)
295             {
296                 endMark = scanner_.front.endMark;
297                 scanner_.popFront();
298             }
299 
300             state_ = &parseDocumentStart;
301 
302             return documentEndEvent(startMark, endMark, explicit);
303         }
304 
305         ///Parse document content.
306         Event parseDocumentContent() @safe
307         {
308             if(scanner_.front.id.among!(TokenID.directive,   TokenID.documentStart,
309                                    TokenID.documentEnd, TokenID.streamEnd))
310             {
311                 state_ = popState();
312                 return processEmptyScalar(scanner_.front.startMark);
313             }
314             return parseBlockNode();
315         }
316 
317         /// Process directives at the beginning of a document.
318         TagDirective[] processDirectives() @safe
319         {
320             // Destroy version and tag handles from previous document.
321             YAMLVersion_ = null;
322             tagDirectives_.length = 0;
323 
324             // Process directives.
325             while(scanner_.front.id == TokenID.directive)
326             {
327                 const token = scanner_.front;
328                 scanner_.popFront();
329                 string value = token.value.idup;
330                 if(token.directive == DirectiveType.yaml)
331                 {
332                     enforce(YAMLVersion_ is null,
333                             new ParserException("Duplicate YAML directive", token.startMark));
334                     const minor = value.split(".")[0];
335                     enforce(minor == "1",
336                             new ParserException("Incompatible document (version 1.x is required)",
337                                       token.startMark));
338                     YAMLVersion_ = value;
339                 }
340                 else if(token.directive == DirectiveType.tag)
341                 {
342                     auto handle = value[0 .. token.valueDivider];
343 
344                     foreach(ref pair; tagDirectives_)
345                     {
346                         // handle
347                         const h = pair.handle;
348                         enforce(h != handle, new ParserException("Duplicate tag handle: " ~ handle,
349                                                        token.startMark));
350                     }
351                     tagDirectives_ ~=
352                         TagDirective(handle, value[token.valueDivider .. $]);
353                 }
354                 // Any other directive type is ignored (only YAML and TAG are in YAML
355                 // 1.1/1.2, any other directives are "reserved")
356             }
357 
358             TagDirective[] value = tagDirectives_;
359 
360             //Add any default tag handles that haven't been overridden.
361             foreach(ref defaultPair; defaultTagDirectives_)
362             {
363                 bool found;
364                 foreach(ref pair; tagDirectives_) if(defaultPair.handle == pair.handle)
365                 {
366                     found = true;
367                     break;
368                 }
369                 if(!found) {tagDirectives_ ~= defaultPair; }
370             }
371 
372             return value;
373         }
374 
375         /**
376          * block_node_or_indentless_sequence ::= ALIAS
377          *               | properties (block_content | indentless_block_sequence)?
378          *               | block_content
379          *               | indentless_block_sequence
380          * block_node    ::= ALIAS
381          *                   | properties block_content?
382          *                   | block_content
383          * flow_node     ::= ALIAS
384          *                   | properties flow_content?
385          *                   | flow_content
386          * properties    ::= TAG ANCHOR? | ANCHOR TAG?
387          * block_content     ::= block_collection | flow_collection | SCALAR
388          * flow_content      ::= flow_collection | SCALAR
389          * block_collection  ::= block_sequence | block_mapping
390          * flow_collection   ::= flow_sequence | flow_mapping
391          */
392 
393         ///Parse a node.
394         Event parseNode(const Flag!"block" block,
395                         const Flag!"indentlessSequence" indentlessSequence = No.indentlessSequence)
396             @trusted
397         {
398             if(scanner_.front.id == TokenID.alias_)
399             {
400                 const token = scanner_.front;
401                 scanner_.popFront();
402                 state_ = popState();
403                 return aliasEvent(token.startMark, token.endMark,
404                                   cast(string)token.value);
405             }
406 
407             string anchor;
408             string tag;
409             Mark startMark, endMark, tagMark;
410             bool invalidMarks = true;
411             // The index in the tag string where tag handle ends and tag suffix starts.
412             uint tagHandleEnd;
413 
414             //Get anchor/tag if detected. Return false otherwise.
415             bool get(const TokenID id, const Flag!"first" first, ref string target) @safe
416             {
417                 if(scanner_.front.id != id){return false;}
418                 invalidMarks = false;
419                 const token = scanner_.front;
420                 scanner_.popFront();
421                 if(first){startMark = token.startMark;}
422                 if(id == TokenID.tag)
423                 {
424                     tagMark = token.startMark;
425                     tagHandleEnd = token.valueDivider;
426                 }
427                 endMark = token.endMark;
428                 target  = token.value.idup;
429                 return true;
430             }
431 
432             //Anchor and/or tag can be in any order.
433             if(get(TokenID.anchor, Yes.first, anchor)){get(TokenID.tag, No.first, tag);}
434             else if(get(TokenID.tag, Yes.first, tag)) {get(TokenID.anchor, No.first, anchor);}
435 
436             if(tag !is null){tag = processTag(tag, tagHandleEnd, startMark, tagMark);}
437 
438             if(invalidMarks)
439             {
440                 startMark = endMark = scanner_.front.startMark;
441             }
442 
443             bool implicit = (tag is null || tag == "!");
444 
445             if(indentlessSequence && scanner_.front.id == TokenID.blockEntry)
446             {
447                 state_ = &parseIndentlessSequenceEntry;
448                 return sequenceStartEvent
449                     (startMark, scanner_.front.endMark, anchor,
450                      tag, implicit, CollectionStyle.block);
451             }
452 
453             if(scanner_.front.id == TokenID.scalar)
454             {
455                 auto token = scanner_.front;
456                 scanner_.popFront();
457                 auto value = token.style == ScalarStyle.doubleQuoted
458                            ? handleDoubleQuotedScalarEscapes(token.value)
459                            : cast(string)token.value;
460 
461                 implicit = (token.style == ScalarStyle.plain && tag is null) || tag == "!";
462                 state_ = popState();
463                 return scalarEvent(startMark, token.endMark, anchor, tag,
464                                    implicit, value, token.style);
465             }
466 
467             if(scanner_.front.id == TokenID.flowSequenceStart)
468             {
469                 endMark = scanner_.front.endMark;
470                 state_ = &parseFlowSequenceEntry!(Yes.first);
471                 return sequenceStartEvent(startMark, endMark, anchor, tag,
472                                           implicit, CollectionStyle.flow);
473             }
474 
475             if(scanner_.front.id == TokenID.flowMappingStart)
476             {
477                 endMark = scanner_.front.endMark;
478                 state_ = &parseFlowMappingKey!(Yes.first);
479                 return mappingStartEvent(startMark, endMark, anchor, tag,
480                                          implicit, CollectionStyle.flow);
481             }
482 
483             if(block && scanner_.front.id == TokenID.blockSequenceStart)
484             {
485                 endMark = scanner_.front.endMark;
486                 state_ = &parseBlockSequenceEntry!(Yes.first);
487                 return sequenceStartEvent(startMark, endMark, anchor, tag,
488                                           implicit, CollectionStyle.block);
489             }
490 
491             if(block && scanner_.front.id == TokenID.blockMappingStart)
492             {
493                 endMark = scanner_.front.endMark;
494                 state_ = &parseBlockMappingKey!(Yes.first);
495                 return mappingStartEvent(startMark, endMark, anchor, tag,
496                                          implicit, CollectionStyle.block);
497             }
498 
499             if(anchor !is null || tag !is null)
500             {
501                 state_ = popState();
502 
503                 //PyYAML uses a tuple(implicit, false) for the second last arg here,
504                 //but the second bool is never used after that - so we don't use it.
505 
506                 //Empty scalars are allowed even if a tag or an anchor is specified.
507                 return scalarEvent(startMark, endMark, anchor, tag,
508                                    implicit , "");
509             }
510 
511             const token = scanner_.front;
512             throw new ParserException("While parsing a " ~ (block ? "block" : "flow")
513                 ~ " node, expected node content, but found: " ~ token.idString,
514                 token.startMark, "node started here", startMark);
515         }
516 
517         /// Handle escape sequences in a double quoted scalar.
518         ///
519         /// Moved here from scanner as it can't always be done in-place with slices.
520         string handleDoubleQuotedScalarEscapes(const(char)[] tokenValue) const @safe
521         {
522             string notInPlace;
523             bool inEscape;
524             auto appender = appender!(string)();
525             for(const(char)[] oldValue = tokenValue; !oldValue.empty();)
526             {
527                 const dchar c = oldValue.front();
528                 oldValue.popFront();
529 
530                 if(!inEscape)
531                 {
532                     if(c != '\\')
533                     {
534                         if(notInPlace is null) { appender.put(c); }
535                         else                   { notInPlace ~= c; }
536                         continue;
537                     }
538                     // Escape sequence starts with a '\'
539                     inEscape = true;
540                     continue;
541                 }
542 
543                 import dyaml.escapes;
544                 scope(exit) { inEscape = false; }
545 
546                 // 'Normal' escape sequence.
547                 if(c.among!(escapes))
548                 {
549                     if(notInPlace is null)
550                     {
551                         // \L and \C can't be handled in place as the expand into
552                         // many-byte unicode chars
553                         if(c != 'L' && c != 'P')
554                         {
555                             appender.put(dyaml.escapes.fromEscape(c));
556                             continue;
557                         }
558                         // Need to duplicate as we won't fit into
559                         // token.value - which is what appender uses
560                         notInPlace = appender.data.dup;
561                         notInPlace ~= dyaml.escapes.fromEscape(c);
562                         continue;
563                     }
564                     notInPlace ~= dyaml.escapes.fromEscape(c);
565                     continue;
566                 }
567 
568                 // Unicode char written in hexadecimal in an escape sequence.
569                 if(c.among!(escapeHexCodeList))
570                 {
571                     // Scanner has already checked that the hex string is valid.
572 
573                     const hexLength = dyaml.escapes.escapeHexLength(c);
574                     // Any hex digits are 1-byte so this works.
575                     const(char)[] hex = oldValue[0 .. hexLength];
576                     oldValue = oldValue[hexLength .. $];
577                     import std.ascii : isHexDigit;
578                     assert(!hex.canFind!(d => !d.isHexDigit),
579                             "Scanner must ensure the hex string is valid");
580 
581                     const decoded = cast(dchar)parse!int(hex, 16u);
582                     if(notInPlace is null) { appender.put(decoded); }
583                     else                   { notInPlace ~= decoded; }
584                     continue;
585                 }
586 
587                 assert(false, "Scanner must handle unsupported escapes");
588             }
589 
590             return notInPlace is null ? appender.data : notInPlace;
591         }
592 
593         /**
594          * Process a tag string retrieved from a tag token.
595          *
596          * Params:  tag       = Tag before processing.
597          *          handleEnd = Index in tag where tag handle ends and tag suffix
598          *                      starts.
599          *          startMark = Position of the node the tag belongs to.
600          *          tagMark   = Position of the tag.
601          */
602         string processTag(const string tag, const uint handleEnd,
603                           const Mark startMark, const Mark tagMark)
604             const @safe
605         {
606             const handle = tag[0 .. handleEnd];
607             const suffix = tag[handleEnd .. $];
608 
609             if(handle.length > 0)
610             {
611                 string replacement;
612                 foreach(ref pair; tagDirectives_)
613                 {
614                     if(pair.handle == handle)
615                     {
616                         replacement = pair.prefix;
617                         break;
618                     }
619                 }
620                 //handle must be in tagDirectives_
621                 enforce(replacement !is null,
622                     new ParserException("While parsing a node, found undefined tag handle: "
623                         ~ handle, tagMark, "node started here", startMark));
624                 return replacement ~ suffix;
625             }
626             return suffix;
627         }
628 
629         ///Wrappers to parse nodes.
630         Event parseBlockNode() @safe {return parseNode(Yes.block);}
631         Event parseFlowNode() @safe {return parseNode(No.block);}
632         Event parseBlockNodeOrIndentlessSequence() @safe {return parseNode(Yes.block, Yes.indentlessSequence);}
633 
634         ///block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
635 
636         ///Parse an entry of a block sequence. If first is true, this is the first entry.
637         Event parseBlockSequenceEntry(Flag!"first" first)() @safe
638         {
639             static if(first)
640             {
641                 pushMark(scanner_.front.startMark);
642                 scanner_.popFront();
643             }
644 
645             if(scanner_.front.id == TokenID.blockEntry)
646             {
647                 const token = scanner_.front;
648                 scanner_.popFront();
649                 if(!scanner_.front.id.among!(TokenID.blockEntry, TokenID.blockEnd))
650                 {
651                     pushState(&parseBlockSequenceEntry!(No.first));
652                     return parseBlockNode();
653                 }
654 
655                 state_ = &parseBlockSequenceEntry!(No.first);
656                 return processEmptyScalar(token.endMark);
657             }
658 
659             if(scanner_.front.id != TokenID.blockEnd)
660             {
661                 const token = scanner_.front;
662                 throw new ParserException("While parsing a block sequence, expected block end, but found: "
663                     ~ token.idString, token.startMark, "sequence started here", marks_.data.back);
664             }
665 
666             state_ = popState();
667             popMark();
668             const token = scanner_.front;
669             scanner_.popFront();
670             return sequenceEndEvent(token.startMark, token.endMark);
671         }
672 
673         ///indentless_sequence ::= (BLOCK-ENTRY block_node?)+
674 
675         ///Parse an entry of an indentless sequence.
676         Event parseIndentlessSequenceEntry() @safe
677         {
678             if(scanner_.front.id == TokenID.blockEntry)
679             {
680                 const token = scanner_.front;
681                 scanner_.popFront();
682 
683                 if(!scanner_.front.id.among!(TokenID.blockEntry, TokenID.key,
684                                         TokenID.value, TokenID.blockEnd))
685                 {
686                     pushState(&parseIndentlessSequenceEntry);
687                     return parseBlockNode();
688                 }
689 
690                 state_ = &parseIndentlessSequenceEntry;
691                 return processEmptyScalar(token.endMark);
692             }
693 
694             state_ = popState();
695             const token = scanner_.front;
696             return sequenceEndEvent(token.startMark, token.endMark);
697         }
698 
699         /**
700          * block_mapping     ::= BLOCK-MAPPING_START
701          *                       ((KEY block_node_or_indentless_sequence?)?
702          *                       (VALUE block_node_or_indentless_sequence?)?)*
703          *                       BLOCK-END
704          */
705 
706         ///Parse a key in a block mapping. If first is true, this is the first key.
707         Event parseBlockMappingKey(Flag!"first" first)() @safe
708         {
709             static if(first)
710             {
711                 pushMark(scanner_.front.startMark);
712                 scanner_.popFront();
713             }
714 
715             if(scanner_.front.id == TokenID.key)
716             {
717                 const token = scanner_.front;
718                 scanner_.popFront();
719 
720                 if(!scanner_.front.id.among!(TokenID.key, TokenID.value, TokenID.blockEnd))
721                 {
722                     pushState(&parseBlockMappingValue);
723                     return parseBlockNodeOrIndentlessSequence();
724                 }
725 
726                 state_ = &parseBlockMappingValue;
727                 return processEmptyScalar(token.endMark);
728             }
729 
730             if(scanner_.front.id != TokenID.blockEnd)
731             {
732                 const token = scanner_.front;
733                 throw new ParserException("While parsing a block mapping, expected block end, but found: "
734                     ~ token.idString, token.startMark, "mapping started here", marks_.data.back);
735             }
736 
737             state_ = popState();
738             popMark();
739             const token = scanner_.front;
740             scanner_.popFront();
741             return mappingEndEvent(token.startMark, token.endMark);
742         }
743 
744         ///Parse a value in a block mapping.
745         Event parseBlockMappingValue() @safe
746         {
747             if(scanner_.front.id == TokenID.value)
748             {
749                 const token = scanner_.front;
750                 scanner_.popFront();
751 
752                 if(!scanner_.front.id.among!(TokenID.key, TokenID.value, TokenID.blockEnd))
753                 {
754                     pushState(&parseBlockMappingKey!(No.first));
755                     return parseBlockNodeOrIndentlessSequence();
756                 }
757 
758                 state_ = &parseBlockMappingKey!(No.first);
759                 return processEmptyScalar(token.endMark);
760             }
761 
762             state_= &parseBlockMappingKey!(No.first);
763             return processEmptyScalar(scanner_.front.startMark);
764         }
765 
766         /**
767          * flow_sequence     ::= FLOW-SEQUENCE-START
768          *                       (flow_sequence_entry FLOW-ENTRY)*
769          *                       flow_sequence_entry?
770          *                       FLOW-SEQUENCE-END
771          * flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
772          *
773          * Note that while production rules for both flow_sequence_entry and
774          * flow_mapping_entry are equal, their interpretations are different.
775          * For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
776          * generate an inline mapping (set syntax).
777          */
778 
779         ///Parse an entry in a flow sequence. If first is true, this is the first entry.
780         Event parseFlowSequenceEntry(Flag!"first" first)() @safe
781         {
782             static if(first)
783             {
784                 pushMark(scanner_.front.startMark);
785                 scanner_.popFront();
786             }
787 
788             if(scanner_.front.id != TokenID.flowSequenceEnd)
789             {
790                 static if(!first)
791                 {
792                     if(scanner_.front.id == TokenID.flowEntry)
793                     {
794                         scanner_.popFront();
795                     }
796                     else
797                     {
798                         const token = scanner_.front;
799                         throw new ParserException("While parsing a flow sequence, expected ',' or ']', but got: " ~
800                             token.idString, token.startMark, "sequence started here", marks_.data.back);
801                     }
802                 }
803 
804                 if(scanner_.front.id == TokenID.key)
805                 {
806                     const token = scanner_.front;
807                     state_ = &parseFlowSequenceEntryMappingKey;
808                     return mappingStartEvent(token.startMark, token.endMark,
809                                              null, null, true, CollectionStyle.flow);
810                 }
811                 else if(scanner_.front.id != TokenID.flowSequenceEnd)
812                 {
813                     pushState(&parseFlowSequenceEntry!(No.first));
814                     return parseFlowNode();
815                 }
816             }
817 
818             const token = scanner_.front;
819             scanner_.popFront();
820             state_ = popState();
821             popMark();
822             return sequenceEndEvent(token.startMark, token.endMark);
823         }
824 
825         ///Parse a key in flow context.
826         Event parseFlowKey(Event delegate() @safe nextState) @safe
827         {
828             const token = scanner_.front;
829             scanner_.popFront();
830 
831             if(!scanner_.front.id.among!(TokenID.value, TokenID.flowEntry,
832                                     TokenID.flowSequenceEnd))
833             {
834                 pushState(nextState);
835                 return parseFlowNode();
836             }
837 
838             state_ = nextState;
839             return processEmptyScalar(token.endMark);
840         }
841 
842         ///Parse a mapping key in an entry in a flow sequence.
843         Event parseFlowSequenceEntryMappingKey() @safe
844         {
845             return parseFlowKey(&parseFlowSequenceEntryMappingValue);
846         }
847 
848         ///Parse a mapping value in a flow context.
849         Event parseFlowValue(TokenID checkId, Event delegate() @safe nextState)
850             @safe
851         {
852             if(scanner_.front.id == TokenID.value)
853             {
854                 const token = scanner_.front;
855                 scanner_.popFront();
856                 if(!scanner_.front.id.among(TokenID.flowEntry, checkId))
857                 {
858                     pushState(nextState);
859                     return parseFlowNode();
860                 }
861 
862                 state_ = nextState;
863                 return processEmptyScalar(token.endMark);
864             }
865 
866             state_ = nextState;
867             return processEmptyScalar(scanner_.front.startMark);
868         }
869 
870         ///Parse a mapping value in an entry in a flow sequence.
871         Event parseFlowSequenceEntryMappingValue() @safe
872         {
873             return parseFlowValue(TokenID.flowSequenceEnd,
874                                   &parseFlowSequenceEntryMappingEnd);
875         }
876 
877         ///Parse end of a mapping in a flow sequence entry.
878         Event parseFlowSequenceEntryMappingEnd() @safe
879         {
880             state_ = &parseFlowSequenceEntry!(No.first);
881             const token = scanner_.front;
882             return mappingEndEvent(token.startMark, token.startMark);
883         }
884 
885         /**
886          * flow_mapping  ::= FLOW-MAPPING-START
887          *                   (flow_mapping_entry FLOW-ENTRY)*
888          *                   flow_mapping_entry?
889          *                   FLOW-MAPPING-END
890          * flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
891          */
892 
893         ///Parse a key in a flow mapping.
894         Event parseFlowMappingKey(Flag!"first" first)() @safe
895         {
896             static if(first)
897             {
898                 pushMark(scanner_.front.startMark);
899                 scanner_.popFront();
900             }
901 
902             if(scanner_.front.id != TokenID.flowMappingEnd)
903             {
904                 static if(!first)
905                 {
906                     if(scanner_.front.id == TokenID.flowEntry)
907                     {
908                         scanner_.popFront();
909                     }
910                     else
911                     {
912                         const token = scanner_.front;
913                         throw new ParserException("While parsing a flow mapping, expected ',' or '}', but got: "
914                             ~ token.idString, token.startMark, "mapping started here", marks_.data.back);
915                     }
916                 }
917 
918                 if(scanner_.front.id == TokenID.key)
919                 {
920                     return parseFlowKey(&parseFlowMappingValue);
921                 }
922 
923                 if(scanner_.front.id != TokenID.flowMappingEnd)
924                 {
925                     pushState(&parseFlowMappingEmptyValue);
926                     return parseFlowNode();
927                 }
928             }
929 
930             const token = scanner_.front;
931             scanner_.popFront();
932             state_ = popState();
933             popMark();
934             return mappingEndEvent(token.startMark, token.endMark);
935         }
936 
937         ///Parse a value in a flow mapping.
938         Event parseFlowMappingValue()  @safe
939         {
940             return parseFlowValue(TokenID.flowMappingEnd, &parseFlowMappingKey!(No.first));
941         }
942 
943         ///Parse an empty value in a flow mapping.
944         Event parseFlowMappingEmptyValue() @safe
945         {
946             state_ = &parseFlowMappingKey!(No.first);
947             return processEmptyScalar(scanner_.front.startMark);
948         }
949 
950         ///Return an empty scalar.
951         Event processEmptyScalar(const Mark mark) @safe pure nothrow const @nogc
952         {
953             return scalarEvent(mark, mark, null, null, true, "");
954         }
955 }
956 
957 // Provide good error message for bad block mapping
958 @safe unittest
959 {
960     import dyaml.loader : Loader;
961 
962     const str = `[`;
963 
964     const exc = collectException!LoaderException(Loader.fromString(str).load());
965     assert(exc);
966     assert(exc.message() ==
967        "Unable to load <unknown>: While parsing a flow node, expected node content, but found: streamEnd\n" ~
968        "<unknown>:1,2\nnode started here: <unknown>:1,2");
969 }
970 
971 // Provide good error message for bad block mapping
972 @safe unittest
973 {
974     import dyaml.loader : Loader;
975 
976     const str = `&anchor !foo!bar value`;
977 
978     const exc = collectException!LoaderException(Loader.fromString(str).load());
979     assert(exc);
980     assert(exc.message() ==
981        "Unable to load <unknown>: While parsing a node, found undefined tag handle: !foo!\n" ~
982        "<unknown>:1,9\nnode started here: <unknown>:1,1");
983 }
984 
985 // Provide good error message for bad block mapping
986 @safe unittest
987 {
988     import dyaml.loader : Loader;
989 
990     const str = `- a
991 ,`;
992 
993     const exc = collectException!LoaderException(Loader.fromString(str).load());
994     assert(exc);
995     assert(exc.message() ==
996        "Unable to load <unknown>: While parsing a block sequence, expected block end, but found: flowEntry\n" ~
997        "<unknown>:2,1\nsequence started here: <unknown>:1,1");
998 }
999 
1000 // Provide good error message for bad block mapping
1001 @safe unittest
1002 {
1003     import dyaml.loader : Loader;
1004 
1005     const str = `a: b
1006 ,`;
1007 
1008     const exc = collectException!LoaderException(Loader.fromString(str).load());
1009     assert(exc);
1010     assert(exc.message() ==
1011        "Unable to load <unknown>: While parsing a block mapping, expected block end, but found: flowEntry\n" ~
1012        "<unknown>:2,1\nmapping started here: <unknown>:1,1");
1013 }
1014 
1015 // Provide good error message for bad flow sequence
1016 @safe unittest
1017 {
1018     import dyaml.loader : Loader;
1019 
1020     const str = `[a,b,c`;
1021 
1022     const exc = collectException!LoaderException(Loader.fromString(str).load());
1023     assert(exc);
1024     assert(exc.message() ==
1025        "Unable to load <unknown>: While parsing a flow sequence, expected ',' or ']', but got: streamEnd\n" ~
1026        "<unknown>:1,7\nsequence started here: <unknown>:1,1");
1027 }
1028 
1029 // Provide good error message for bad flow mapping
1030 @safe unittest
1031 {
1032     import dyaml.loader : Loader;
1033 
1034     const str = `{a,b,c`;
1035 
1036     const exc = collectException!LoaderException(Loader.fromString(str).load());
1037     assert(exc);
1038     assert(exc.message() ==
1039        "Unable to load <unknown>: While parsing a flow mapping, expected ',' or '}', but got: streamEnd\n" ~
1040        "<unknown>:1,7\nmapping started here: <unknown>:1,1");
1041 }