• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2008, SnakeYAML
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package org.yaml.snakeyaml.parser;
15 
16 import java.util.HashMap;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Map;
20 import org.yaml.snakeyaml.DumperOptions;
21 import org.yaml.snakeyaml.DumperOptions.Version;
22 import org.yaml.snakeyaml.LoaderOptions;
23 import org.yaml.snakeyaml.comments.CommentType;
24 import org.yaml.snakeyaml.error.Mark;
25 import org.yaml.snakeyaml.error.YAMLException;
26 import org.yaml.snakeyaml.events.AliasEvent;
27 import org.yaml.snakeyaml.events.CommentEvent;
28 import org.yaml.snakeyaml.events.DocumentEndEvent;
29 import org.yaml.snakeyaml.events.DocumentStartEvent;
30 import org.yaml.snakeyaml.events.Event;
31 import org.yaml.snakeyaml.events.ImplicitTuple;
32 import org.yaml.snakeyaml.events.MappingEndEvent;
33 import org.yaml.snakeyaml.events.MappingStartEvent;
34 import org.yaml.snakeyaml.events.ScalarEvent;
35 import org.yaml.snakeyaml.events.SequenceEndEvent;
36 import org.yaml.snakeyaml.events.SequenceStartEvent;
37 import org.yaml.snakeyaml.events.StreamEndEvent;
38 import org.yaml.snakeyaml.events.StreamStartEvent;
39 import org.yaml.snakeyaml.nodes.Tag;
40 import org.yaml.snakeyaml.reader.StreamReader;
41 import org.yaml.snakeyaml.scanner.Scanner;
42 import org.yaml.snakeyaml.scanner.ScannerImpl;
43 import org.yaml.snakeyaml.tokens.AliasToken;
44 import org.yaml.snakeyaml.tokens.AnchorToken;
45 import org.yaml.snakeyaml.tokens.BlockEntryToken;
46 import org.yaml.snakeyaml.tokens.CommentToken;
47 import org.yaml.snakeyaml.tokens.DirectiveToken;
48 import org.yaml.snakeyaml.tokens.ScalarToken;
49 import org.yaml.snakeyaml.tokens.StreamEndToken;
50 import org.yaml.snakeyaml.tokens.StreamStartToken;
51 import org.yaml.snakeyaml.tokens.TagToken;
52 import org.yaml.snakeyaml.tokens.TagTuple;
53 import org.yaml.snakeyaml.tokens.Token;
54 import org.yaml.snakeyaml.util.ArrayStack;
55 
56 /**
57  * <pre>
58  * # The following YAML grammar is LL(1) and is parsed by a recursive descent
59  * parser.
60  * stream            ::= STREAM-START implicit_document? explicit_document* STREAM-END
61  * implicit_document ::= block_node DOCUMENT-END*
62  * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
63  * block_node_or_indentless_sequence ::=
64  *                       ALIAS
65  *                       | properties (block_content | indentless_block_sequence)?
66  *                       | block_content
67  *                       | indentless_block_sequence
68  * block_node        ::= ALIAS
69  *                       | properties block_content?
70  *                       | block_content
71  * flow_node         ::= ALIAS
72  *                       | properties flow_content?
73  *                       | flow_content
74  * properties        ::= TAG ANCHOR? | ANCHOR TAG?
75  * block_content     ::= block_collection | flow_collection | SCALAR
76  * flow_content      ::= flow_collection | SCALAR
77  * block_collection  ::= block_sequence | block_mapping
78  * flow_collection   ::= flow_sequence | flow_mapping
79  * block_sequence    ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
80  * indentless_sequence   ::= (BLOCK-ENTRY block_node?)+
81  * block_mapping     ::= BLOCK-MAPPING_START
82  *                       ((KEY block_node_or_indentless_sequence?)?
83  *                       (VALUE block_node_or_indentless_sequence?)?)*
84  *                       BLOCK-END
85  * flow_sequence     ::= FLOW-SEQUENCE-START
86  *                       (flow_sequence_entry FLOW-ENTRY)*
87  *                       flow_sequence_entry?
88  *                       FLOW-SEQUENCE-END
89  * flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
90  * flow_mapping      ::= FLOW-MAPPING-START
91  *                       (flow_mapping_entry FLOW-ENTRY)*
92  *                       flow_mapping_entry?
93  *                       FLOW-MAPPING-END
94  * flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
95  * FIRST sets:
96  * stream: { STREAM-START }
97  * explicit_document: { DIRECTIVE DOCUMENT-START }
98  * implicit_document: FIRST(block_node)
99  * block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
100  * flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
101  * block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
102  * flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
103  * block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
104  * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
105  * block_sequence: { BLOCK-SEQUENCE-START }
106  * block_mapping: { BLOCK-MAPPING-START }
107  * block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
108  * indentless_sequence: { ENTRY }
109  * flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
110  * flow_sequence: { FLOW-SEQUENCE-START }
111  * flow_mapping: { FLOW-MAPPING-START }
112  * flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
113  * flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
114  * </pre>
115  *
116  * Since writing a recursive-descendant parser is a straightforward task, we do not give many
117  * comments here.
118  */
119 public class ParserImpl implements Parser {
120 
121   private static final Map<String, String> DEFAULT_TAGS = new HashMap<String, String>();
122 
123   static {
124     DEFAULT_TAGS.put("!", "!");
125     DEFAULT_TAGS.put("!!", Tag.PREFIX);
126   }
127 
128   protected final Scanner scanner;
129   private Event currentEvent;
130   private final ArrayStack<Production> states;
131   private final ArrayStack<Mark> marks;
132   private Production state;
133   private VersionTagsTuple directives;
134 
ParserImpl(StreamReader reader)135   public ParserImpl(StreamReader reader) {
136     this(new ScannerImpl(reader));
137   }
138 
139   @Deprecated
ParserImpl(StreamReader reader, boolean parseComments)140   public ParserImpl(StreamReader reader, boolean parseComments) {
141     this(new ScannerImpl(reader, new LoaderOptions().setProcessComments(parseComments)));
142   }
143 
ParserImpl(StreamReader reader, LoaderOptions options)144   public ParserImpl(StreamReader reader, LoaderOptions options) {
145     this(new ScannerImpl(reader, options));
146   }
147 
ParserImpl(Scanner scanner)148   public ParserImpl(Scanner scanner) {
149     this.scanner = scanner;
150     currentEvent = null;
151     directives = new VersionTagsTuple(null, new HashMap<String, String>(DEFAULT_TAGS));
152     states = new ArrayStack<Production>(100);
153     marks = new ArrayStack<Mark>(10);
154     state = new ParseStreamStart();
155   }
156 
157   /**
158    * Check the type of the next event.
159    */
checkEvent(Event.ID choice)160   public boolean checkEvent(Event.ID choice) {
161     peekEvent();
162     return currentEvent != null && currentEvent.is(choice);
163   }
164 
165   /**
166    * Get the next event.
167    */
peekEvent()168   public Event peekEvent() {
169     if (currentEvent == null) {
170       if (state != null) {
171         currentEvent = state.produce();
172       }
173     }
174     return currentEvent;
175   }
176 
177   /**
178    * Get the next event and proceed further.
179    */
getEvent()180   public Event getEvent() {
181     peekEvent();
182     Event value = currentEvent;
183     currentEvent = null;
184     return value;
185   }
186 
produceCommentEvent(CommentToken token)187   private CommentEvent produceCommentEvent(CommentToken token) {
188     Mark startMark = token.getStartMark();
189     Mark endMark = token.getEndMark();
190     String value = token.getValue();
191     CommentType type = token.getCommentType();
192 
193     // state = state, that no change in state
194 
195     return new CommentEvent(type, value, startMark, endMark);
196   }
197 
198   /**
199    * <pre>
200    * stream    ::= STREAM-START implicit_document? explicit_document* STREAM-END
201    * implicit_document ::= block_node DOCUMENT-END*
202    * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
203    * </pre>
204    */
205   private class ParseStreamStart implements Production {
206 
produce()207     public Event produce() {
208       // Parse the stream start.
209       StreamStartToken token = (StreamStartToken) scanner.getToken();
210       Event event = new StreamStartEvent(token.getStartMark(), token.getEndMark());
211       // Prepare the next state.
212       state = new ParseImplicitDocumentStart();
213       return event;
214     }
215   }
216 
217   private class ParseImplicitDocumentStart implements Production {
218 
produce()219     public Event produce() {
220       // Parse an implicit document.
221       if (scanner.checkToken(Token.ID.Comment)) {
222         state = new ParseImplicitDocumentStart();
223         return produceCommentEvent((CommentToken) scanner.getToken());
224       }
225       if (!scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart, Token.ID.StreamEnd)) {
226         Token token = scanner.peekToken();
227         Mark startMark = token.getStartMark();
228         Mark endMark = startMark;
229         Event event = new DocumentStartEvent(startMark, endMark, false, null, null);
230         // Prepare the next state.
231         states.push(new ParseDocumentEnd());
232         state = new ParseBlockNode();
233         return event;
234       }
235       return new ParseDocumentStart().produce();
236     }
237   }
238 
239   private class ParseDocumentStart implements Production {
240 
produce()241     public Event produce() {
242       // Parse any extra document end indicators.
243       while (scanner.checkToken(Token.ID.DocumentEnd)) {
244         scanner.getToken();
245       }
246       // Parse an explicit document.
247       Event event;
248       if (!scanner.checkToken(Token.ID.StreamEnd)) {
249         Token token = scanner.peekToken();
250         Mark startMark = token.getStartMark();
251         VersionTagsTuple tuple = processDirectives();
252         while (scanner.checkToken(Token.ID.Comment)) {
253           // TODO: till we figure out what todo with the comments
254           scanner.getToken();
255         }
256         if (!scanner.checkToken(Token.ID.StreamEnd)) {
257           if (!scanner.checkToken(Token.ID.DocumentStart)) {
258             throw new ParserException(null, null,
259                 "expected '<document start>', but found '" + scanner.peekToken().getTokenId() + "'",
260                 scanner.peekToken().getStartMark());
261           }
262           token = scanner.getToken();
263           Mark endMark = token.getEndMark();
264           event =
265               new DocumentStartEvent(startMark, endMark, true, tuple.getVersion(), tuple.getTags());
266           states.push(new ParseDocumentEnd());
267           state = new ParseDocumentContent();
268           return event;
269         }
270       }
271       // Parse the end of the stream.
272       StreamEndToken token = (StreamEndToken) scanner.getToken();
273       event = new StreamEndEvent(token.getStartMark(), token.getEndMark());
274       if (!states.isEmpty()) {
275         throw new YAMLException("Unexpected end of stream. States left: " + states);
276       }
277       if (!marks.isEmpty()) {
278         throw new YAMLException("Unexpected end of stream. Marks left: " + marks);
279       }
280       state = null;
281       return event;
282     }
283   }
284 
285   private class ParseDocumentEnd implements Production {
286 
produce()287     public Event produce() {
288       // Parse the document end.
289       Token token = scanner.peekToken();
290       Mark startMark = token.getStartMark();
291       Mark endMark = startMark;
292       boolean explicit = false;
293       if (scanner.checkToken(Token.ID.DocumentEnd)) {
294         token = scanner.getToken();
295         endMark = token.getEndMark();
296         explicit = true;
297       }
298       Event event = new DocumentEndEvent(startMark, endMark, explicit);
299       // Prepare the next state.
300       state = new ParseDocumentStart();
301       return event;
302     }
303   }
304 
305   private class ParseDocumentContent implements Production {
306 
produce()307     public Event produce() {
308       if (scanner.checkToken(Token.ID.Comment)) {
309         state = new ParseDocumentContent();
310         return produceCommentEvent((CommentToken) scanner.getToken());
311       }
312       if (scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart, Token.ID.DocumentEnd,
313           Token.ID.StreamEnd)) {
314         Event event = processEmptyScalar(scanner.peekToken().getStartMark());
315         state = states.pop();
316         return event;
317       }
318       return new ParseBlockNode().produce();
319     }
320   }
321 
322   /**
323    * https://yaml.org/spec/1.1/#id898785 says "If the document specifies no directives, it is parsed
324    * using the same settings as the previous document. If the document does specify any directives,
325    * all directives of previous documents, if any, are ignored." TODO the last statement is not
326    * respected (as in PyYAML, to work the same)
327    *
328    * @return directives to be applied for the current document
329    */
330   @SuppressWarnings("unchecked")
processDirectives()331   private VersionTagsTuple processDirectives() {
332     HashMap<String, String> tagHandles = new HashMap<String, String>(directives.getTags());
333     for (String key : DEFAULT_TAGS.keySet()) {
334       tagHandles.remove(key);
335     }
336     // keep only added tag handlers
337     directives = new VersionTagsTuple(null, tagHandles);
338     while (scanner.checkToken(Token.ID.Directive)) {
339       @SuppressWarnings("rawtypes")
340       DirectiveToken token = (DirectiveToken) scanner.getToken();
341       if (token.getName().equals("YAML")) {
342         if (directives.getVersion() != null) {
343           throw new ParserException(null, null, "found duplicate YAML directive",
344               token.getStartMark());
345         }
346         List<Integer> value = (List<Integer>) token.getValue();
347         Integer major = value.get(0);
348         if (major != 1) {
349           throw new ParserException(null, null,
350               "found incompatible YAML document (version 1.* is required)", token.getStartMark());
351         }
352         Integer minor = value.get(1);
353         // TODO refactor with ternary
354         if (minor == 0) {
355           directives = new VersionTagsTuple(Version.V1_0, tagHandles);
356         } else {
357           directives = new VersionTagsTuple(Version.V1_1, tagHandles);
358         }
359       } else if (token.getName().equals("TAG")) {
360         List<String> value = (List<String>) token.getValue();
361         String handle = value.get(0);
362         String prefix = value.get(1);
363         if (tagHandles.containsKey(handle)) {
364           throw new ParserException(null, null, "duplicate tag handle " + handle,
365               token.getStartMark());
366         }
367         tagHandles.put(handle, prefix);
368       }
369     }
370     HashMap<String, String> detectedTagHandles = new HashMap<String, String>();
371     if (!tagHandles.isEmpty()) {
372       // copy from tagHandles
373       detectedTagHandles = new HashMap<String, String>(tagHandles);
374     }
375     // add default tag handlers to resolve tags
376     for (String key : DEFAULT_TAGS.keySet()) {
377       // do not overwrite re-defined tags
378       if (!tagHandles.containsKey(key)) {
379         tagHandles.put(key, DEFAULT_TAGS.get(key));
380       }
381     }
382     // data for the events (no default tags added)
383     return new VersionTagsTuple(directives.getVersion(), detectedTagHandles);
384   }
385 
386   /**
387    * <pre>
388    *  block_node_or_indentless_sequence ::= ALIAS
389    *                | properties (block_content | indentless_block_sequence)?
390    *                | block_content
391    *                | indentless_block_sequence
392    *  block_node    ::= ALIAS
393    *                    | properties block_content?
394    *                    | block_content
395    *  flow_node     ::= ALIAS
396    *                    | properties flow_content?
397    *                    | flow_content
398    *  properties    ::= TAG ANCHOR? | ANCHOR TAG?
399    *  block_content     ::= block_collection | flow_collection | SCALAR
400    *  flow_content      ::= flow_collection | SCALAR
401    *  block_collection  ::= block_sequence | block_mapping
402    *  flow_collection   ::= flow_sequence | flow_mapping
403    * </pre>
404    */
405 
406   private class ParseBlockNode implements Production {
407 
produce()408     public Event produce() {
409       return parseNode(true, false);
410     }
411   }
412 
parseFlowNode()413   private Event parseFlowNode() {
414     return parseNode(false, false);
415   }
416 
parseBlockNodeOrIndentlessSequence()417   private Event parseBlockNodeOrIndentlessSequence() {
418     return parseNode(true, true);
419   }
420 
parseNode(boolean block, boolean indentlessSequence)421   private Event parseNode(boolean block, boolean indentlessSequence) {
422     Event event;
423     Mark startMark = null;
424     Mark endMark = null;
425     Mark tagMark = null;
426     if (scanner.checkToken(Token.ID.Alias)) {
427       AliasToken token = (AliasToken) scanner.getToken();
428       event = new AliasEvent(token.getValue(), token.getStartMark(), token.getEndMark());
429       state = states.pop();
430     } else {
431       String anchor = null;
432       TagTuple tagTokenTag = null;
433       if (scanner.checkToken(Token.ID.Anchor)) {
434         AnchorToken token = (AnchorToken) scanner.getToken();
435         startMark = token.getStartMark();
436         endMark = token.getEndMark();
437         anchor = token.getValue();
438         if (scanner.checkToken(Token.ID.Tag)) {
439           TagToken tagToken = (TagToken) scanner.getToken();
440           tagMark = tagToken.getStartMark();
441           endMark = tagToken.getEndMark();
442           tagTokenTag = tagToken.getValue();
443         }
444       } else if (scanner.checkToken(Token.ID.Tag)) {
445         TagToken tagToken = (TagToken) scanner.getToken();
446         startMark = tagToken.getStartMark();
447         tagMark = startMark;
448         endMark = tagToken.getEndMark();
449         tagTokenTag = tagToken.getValue();
450         if (scanner.checkToken(Token.ID.Anchor)) {
451           AnchorToken token = (AnchorToken) scanner.getToken();
452           endMark = token.getEndMark();
453           anchor = token.getValue();
454         }
455       }
456       String tag = null;
457       if (tagTokenTag != null) {
458         String handle = tagTokenTag.getHandle();
459         String suffix = tagTokenTag.getSuffix();
460         if (handle != null) {
461           if (!directives.getTags().containsKey(handle)) {
462             throw new ParserException("while parsing a node", startMark,
463                 "found undefined tag handle " + handle, tagMark);
464           }
465           tag = directives.getTags().get(handle) + suffix;
466         } else {
467           tag = suffix;
468         }
469       }
470       if (startMark == null) {
471         startMark = scanner.peekToken().getStartMark();
472         endMark = startMark;
473       }
474       event = null;
475       boolean implicit = tag == null || tag.equals("!");
476       if (indentlessSequence && scanner.checkToken(Token.ID.BlockEntry)) {
477         endMark = scanner.peekToken().getEndMark();
478         event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
479             DumperOptions.FlowStyle.BLOCK);
480         state = new ParseIndentlessSequenceEntryKey();
481       } else {
482         if (scanner.checkToken(Token.ID.Scalar)) {
483           ScalarToken token = (ScalarToken) scanner.getToken();
484           endMark = token.getEndMark();
485           ImplicitTuple implicitValues;
486           if ((token.getPlain() && tag == null) || "!".equals(tag)) {
487             implicitValues = new ImplicitTuple(true, false);
488           } else if (tag == null) {
489             implicitValues = new ImplicitTuple(false, true);
490           } else {
491             implicitValues = new ImplicitTuple(false, false);
492           }
493           event = new ScalarEvent(anchor, tag, implicitValues, token.getValue(), startMark, endMark,
494               token.getStyle());
495           state = states.pop();
496         } else if (scanner.checkToken(Token.ID.FlowSequenceStart)) {
497           endMark = scanner.peekToken().getEndMark();
498           event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
499               DumperOptions.FlowStyle.FLOW);
500           state = new ParseFlowSequenceFirstEntry();
501         } else if (scanner.checkToken(Token.ID.FlowMappingStart)) {
502           endMark = scanner.peekToken().getEndMark();
503           event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
504               DumperOptions.FlowStyle.FLOW);
505           state = new ParseFlowMappingFirstKey();
506         } else if (block && scanner.checkToken(Token.ID.BlockSequenceStart)) {
507           endMark = scanner.peekToken().getStartMark();
508           event = new SequenceStartEvent(anchor, tag, implicit, startMark, endMark,
509               DumperOptions.FlowStyle.BLOCK);
510           state = new ParseBlockSequenceFirstEntry();
511         } else if (block && scanner.checkToken(Token.ID.BlockMappingStart)) {
512           endMark = scanner.peekToken().getStartMark();
513           event = new MappingStartEvent(anchor, tag, implicit, startMark, endMark,
514               DumperOptions.FlowStyle.BLOCK);
515           state = new ParseBlockMappingFirstKey();
516         } else if (anchor != null || tag != null) {
517           // Empty scalars are allowed even if a tag or an anchor is
518           // specified.
519           event = new ScalarEvent(anchor, tag, new ImplicitTuple(implicit, false), "", startMark,
520               endMark, DumperOptions.ScalarStyle.PLAIN);
521           state = states.pop();
522         } else {
523           Token token = scanner.peekToken();
524           throw new ParserException("while parsing a " + (block ? "block" : "flow") + " node",
525               startMark, "expected the node content, but found '" + token.getTokenId() + "'",
526               token.getStartMark());
527         }
528       }
529     }
530     return event;
531   }
532 
533   // block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
534   // BLOCK-END
535 
536   private class ParseBlockSequenceFirstEntry implements Production {
537 
produce()538     public Event produce() {
539       Token token = scanner.getToken();
540       marks.push(token.getStartMark());
541       return new ParseBlockSequenceEntryKey().produce();
542     }
543   }
544 
545   private class ParseBlockSequenceEntryKey implements Production {
546 
produce()547     public Event produce() {
548       if (scanner.checkToken(Token.ID.Comment)) {
549         state = new ParseBlockSequenceEntryKey();
550         return produceCommentEvent((CommentToken) scanner.getToken());
551       }
552       if (scanner.checkToken(Token.ID.BlockEntry)) {
553         BlockEntryToken token = (BlockEntryToken) scanner.getToken();
554         return new ParseBlockSequenceEntryValue(token).produce();
555       }
556       if (!scanner.checkToken(Token.ID.BlockEnd)) {
557         Token token = scanner.peekToken();
558         throw new ParserException("while parsing a block collection", marks.pop(),
559             "expected <block end>, but found '" + token.getTokenId() + "'", token.getStartMark());
560       }
561       Token token = scanner.getToken();
562       Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
563       state = states.pop();
564       marks.pop();
565       return event;
566     }
567   }
568 
569   private class ParseBlockSequenceEntryValue implements Production {
570 
571     BlockEntryToken token;
572 
ParseBlockSequenceEntryValue(final BlockEntryToken token)573     public ParseBlockSequenceEntryValue(final BlockEntryToken token) {
574       this.token = token;
575     }
576 
produce()577     public Event produce() {
578       if (scanner.checkToken(Token.ID.Comment)) {
579         state = new ParseBlockSequenceEntryValue(token);
580         return produceCommentEvent((CommentToken) scanner.getToken());
581       }
582       if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.BlockEnd)) {
583         states.push(new ParseBlockSequenceEntryKey());
584         return new ParseBlockNode().produce();
585       } else {
586         state = new ParseBlockSequenceEntryKey();
587         return processEmptyScalar(token.getEndMark());
588       }
589     }
590   }
591 
592   // indentless_sequence ::= (BLOCK-ENTRY block_node?)+
593 
594   private class ParseIndentlessSequenceEntryKey implements Production {
595 
produce()596     public Event produce() {
597       if (scanner.checkToken(Token.ID.Comment)) {
598         state = new ParseIndentlessSequenceEntryKey();
599         return produceCommentEvent((CommentToken) scanner.getToken());
600       }
601       if (scanner.checkToken(Token.ID.BlockEntry)) {
602         BlockEntryToken token = (BlockEntryToken) scanner.getToken();
603         return new ParseIndentlessSequenceEntryValue(token).produce();
604       }
605       Token token = scanner.peekToken();
606       Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
607       state = states.pop();
608       return event;
609     }
610   }
611 
612   private class ParseIndentlessSequenceEntryValue implements Production {
613 
614     BlockEntryToken token;
615 
ParseIndentlessSequenceEntryValue(final BlockEntryToken token)616     public ParseIndentlessSequenceEntryValue(final BlockEntryToken token) {
617       this.token = token;
618     }
619 
produce()620     public Event produce() {
621       if (scanner.checkToken(Token.ID.Comment)) {
622         state = new ParseIndentlessSequenceEntryValue(token);
623         return produceCommentEvent((CommentToken) scanner.getToken());
624       }
625       if (!scanner.checkToken(Token.ID.BlockEntry, Token.ID.Key, Token.ID.Value,
626           Token.ID.BlockEnd)) {
627         states.push(new ParseIndentlessSequenceEntryKey());
628         return new ParseBlockNode().produce();
629       } else {
630         state = new ParseIndentlessSequenceEntryKey();
631         return processEmptyScalar(token.getEndMark());
632       }
633     }
634   }
635 
636   private class ParseBlockMappingFirstKey implements Production {
637 
produce()638     public Event produce() {
639       Token token = scanner.getToken();
640       marks.push(token.getStartMark());
641       return new ParseBlockMappingKey().produce();
642     }
643   }
644 
645   private class ParseBlockMappingKey implements Production {
646 
produce()647     public Event produce() {
648       if (scanner.checkToken(Token.ID.Comment)) {
649         state = new ParseBlockMappingKey();
650         return produceCommentEvent((CommentToken) scanner.getToken());
651       }
652       if (scanner.checkToken(Token.ID.Key)) {
653         Token token = scanner.getToken();
654         if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
655           states.push(new ParseBlockMappingValue());
656           return parseBlockNodeOrIndentlessSequence();
657         } else {
658           state = new ParseBlockMappingValue();
659           return processEmptyScalar(token.getEndMark());
660         }
661       }
662       if (!scanner.checkToken(Token.ID.BlockEnd)) {
663         Token token = scanner.peekToken();
664         throw new ParserException("while parsing a block mapping", marks.pop(),
665             "expected <block end>, but found '" + token.getTokenId() + "'", token.getStartMark());
666       }
667       Token token = scanner.getToken();
668       Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
669       state = states.pop();
670       marks.pop();
671       return event;
672     }
673   }
674 
675   private class ParseBlockMappingValue implements Production {
676 
produce()677     public Event produce() {
678       if (scanner.checkToken(Token.ID.Value)) {
679         Token token = scanner.getToken();
680         if (scanner.checkToken(Token.ID.Comment)) {
681           state = new ParseBlockMappingValueComment();
682           return state.produce();
683         } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
684           states.push(new ParseBlockMappingKey());
685           return parseBlockNodeOrIndentlessSequence();
686         } else {
687           state = new ParseBlockMappingKey();
688           return processEmptyScalar(token.getEndMark());
689         }
690       } else if (scanner.checkToken(Token.ID.Scalar)) {
691         states.push(new ParseBlockMappingKey());
692         return parseBlockNodeOrIndentlessSequence();
693       }
694       state = new ParseBlockMappingKey();
695       Token token = scanner.peekToken();
696       return processEmptyScalar(token.getStartMark());
697     }
698   }
699 
700   private class ParseBlockMappingValueComment implements Production {
701 
702     List<CommentToken> tokens = new LinkedList<>();
703 
produce()704     public Event produce() {
705       if (scanner.checkToken(Token.ID.Comment)) {
706         tokens.add((CommentToken) scanner.getToken());
707         return produce();
708       } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) {
709         if (!tokens.isEmpty()) {
710           return produceCommentEvent(tokens.remove(0));
711         }
712         states.push(new ParseBlockMappingKey());
713         return parseBlockNodeOrIndentlessSequence();
714       } else {
715         state = new ParseBlockMappingValueCommentList(tokens);
716         return processEmptyScalar(scanner.peekToken().getStartMark());
717       }
718     }
719   }
720 
721   private class ParseBlockMappingValueCommentList implements Production {
722 
723     List<CommentToken> tokens;
724 
ParseBlockMappingValueCommentList(final List<CommentToken> tokens)725     public ParseBlockMappingValueCommentList(final List<CommentToken> tokens) {
726       this.tokens = tokens;
727     }
728 
produce()729     public Event produce() {
730       if (!tokens.isEmpty()) {
731         return produceCommentEvent(tokens.remove(0));
732       }
733       return new ParseBlockMappingKey().produce();
734     }
735   }
736 
737   /**
738    * <pre>
739    * flow_sequence     ::= FLOW-SEQUENCE-START
740    *                       (flow_sequence_entry FLOW-ENTRY)*
741    *                       flow_sequence_entry?
742    *                       FLOW-SEQUENCE-END
743    * flow_sequence_entry   ::= flow_node | KEY flow_node? (VALUE flow_node?)?
744    * Note that while production rules for both flow_sequence_entry and
745    * flow_mapping_entry are equal, their interpretations are different.
746    * For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
747    * generate an inline mapping (set syntax).
748    * </pre>
749    */
750   private class ParseFlowSequenceFirstEntry implements Production {
751 
produce()752     public Event produce() {
753       Token token = scanner.getToken();
754       marks.push(token.getStartMark());
755       return new ParseFlowSequenceEntry(true).produce();
756     }
757   }
758 
759   private class ParseFlowSequenceEntry implements Production {
760 
761     private final boolean first;
762 
ParseFlowSequenceEntry(boolean first)763     public ParseFlowSequenceEntry(boolean first) {
764       this.first = first;
765     }
766 
produce()767     public Event produce() {
768       if (scanner.checkToken(Token.ID.Comment)) {
769         state = new ParseFlowSequenceEntry(first);
770         return produceCommentEvent((CommentToken) scanner.getToken());
771       }
772       if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
773         if (!first) {
774           if (scanner.checkToken(Token.ID.FlowEntry)) {
775             scanner.getToken();
776             if (scanner.checkToken(Token.ID.Comment)) {
777               state = new ParseFlowSequenceEntry(true);
778               return produceCommentEvent((CommentToken) scanner.getToken());
779             }
780           } else {
781             Token token = scanner.peekToken();
782             throw new ParserException("while parsing a flow sequence", marks.pop(),
783                 "expected ',' or ']', but got " + token.getTokenId(), token.getStartMark());
784           }
785         }
786         if (scanner.checkToken(Token.ID.Key)) {
787           Token token = scanner.peekToken();
788           Event event = new MappingStartEvent(null, null, true, token.getStartMark(),
789               token.getEndMark(), DumperOptions.FlowStyle.FLOW);
790           state = new ParseFlowSequenceEntryMappingKey();
791           return event;
792         } else if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
793           states.push(new ParseFlowSequenceEntry(false));
794           return parseFlowNode();
795         }
796       }
797       Token token = scanner.getToken();
798       Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark());
799       if (!scanner.checkToken(Token.ID.Comment)) {
800         state = states.pop();
801       } else {
802         state = new ParseFlowEndComment();
803       }
804       marks.pop();
805       return event;
806     }
807   }
808 
809   private class ParseFlowEndComment implements Production {
810 
produce()811     public Event produce() {
812       Event event = produceCommentEvent((CommentToken) scanner.getToken());
813       if (!scanner.checkToken(Token.ID.Comment)) {
814         state = states.pop();
815       }
816       return event;
817     }
818   }
819 
820   private class ParseFlowSequenceEntryMappingKey implements Production {
821 
produce()822     public Event produce() {
823       Token token = scanner.getToken();
824       if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
825         states.push(new ParseFlowSequenceEntryMappingValue());
826         return parseFlowNode();
827       } else {
828         state = new ParseFlowSequenceEntryMappingValue();
829         return processEmptyScalar(token.getEndMark());
830       }
831     }
832   }
833 
834   private class ParseFlowSequenceEntryMappingValue implements Production {
835 
produce()836     public Event produce() {
837       if (scanner.checkToken(Token.ID.Value)) {
838         Token token = scanner.getToken();
839         if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) {
840           states.push(new ParseFlowSequenceEntryMappingEnd());
841           return parseFlowNode();
842         } else {
843           state = new ParseFlowSequenceEntryMappingEnd();
844           return processEmptyScalar(token.getEndMark());
845         }
846       } else {
847         state = new ParseFlowSequenceEntryMappingEnd();
848         Token token = scanner.peekToken();
849         return processEmptyScalar(token.getStartMark());
850       }
851     }
852   }
853 
854   private class ParseFlowSequenceEntryMappingEnd implements Production {
855 
produce()856     public Event produce() {
857       state = new ParseFlowSequenceEntry(false);
858       Token token = scanner.peekToken();
859       return new MappingEndEvent(token.getStartMark(), token.getEndMark());
860     }
861   }
862 
863   /**
864    * <pre>
865    *   flow_mapping  ::= FLOW-MAPPING-START
866    *          (flow_mapping_entry FLOW-ENTRY)*
867    *          flow_mapping_entry?
868    *          FLOW-MAPPING-END
869    *   flow_mapping_entry    ::= flow_node | KEY flow_node? (VALUE flow_node?)?
870    * </pre>
871    */
872   private class ParseFlowMappingFirstKey implements Production {
873 
produce()874     public Event produce() {
875       Token token = scanner.getToken();
876       marks.push(token.getStartMark());
877       return new ParseFlowMappingKey(true).produce();
878     }
879   }
880 
881   private class ParseFlowMappingKey implements Production {
882 
883     private final boolean first;
884 
ParseFlowMappingKey(boolean first)885     public ParseFlowMappingKey(boolean first) {
886       this.first = first;
887     }
888 
produce()889     public Event produce() {
890       if (scanner.checkToken(Token.ID.Comment)) {
891         state = new ParseFlowMappingKey(first);
892         return produceCommentEvent((CommentToken) scanner.getToken());
893       }
894       if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
895         if (!first) {
896           if (scanner.checkToken(Token.ID.FlowEntry)) {
897             scanner.getToken();
898             if (scanner.checkToken(Token.ID.Comment)) {
899               state = new ParseFlowMappingKey(true);
900               return produceCommentEvent((CommentToken) scanner.getToken());
901             }
902           } else {
903             Token token = scanner.peekToken();
904             throw new ParserException("while parsing a flow mapping", marks.pop(),
905                 "expected ',' or '}', but got " + token.getTokenId(), token.getStartMark());
906           }
907         }
908         if (scanner.checkToken(Token.ID.Key)) {
909           Token token = scanner.getToken();
910           if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) {
911             states.push(new ParseFlowMappingValue());
912             return parseFlowNode();
913           } else {
914             state = new ParseFlowMappingValue();
915             return processEmptyScalar(token.getEndMark());
916           }
917         } else if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
918           states.push(new ParseFlowMappingEmptyValue());
919           return parseFlowNode();
920         }
921       }
922       Token token = scanner.getToken();
923       Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark());
924       marks.pop();
925       if (!scanner.checkToken(Token.ID.Comment)) {
926         state = states.pop();
927       } else {
928         state = new ParseFlowEndComment();
929       }
930       return event;
931     }
932   }
933 
934   private class ParseFlowMappingValue implements Production {
935 
produce()936     public Event produce() {
937       if (scanner.checkToken(Token.ID.Value)) {
938         Token token = scanner.getToken();
939         if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) {
940           states.push(new ParseFlowMappingKey(false));
941           return parseFlowNode();
942         } else {
943           state = new ParseFlowMappingKey(false);
944           return processEmptyScalar(token.getEndMark());
945         }
946       } else {
947         state = new ParseFlowMappingKey(false);
948         Token token = scanner.peekToken();
949         return processEmptyScalar(token.getStartMark());
950       }
951     }
952   }
953 
954   private class ParseFlowMappingEmptyValue implements Production {
955 
produce()956     public Event produce() {
957       state = new ParseFlowMappingKey(false);
958       return processEmptyScalar(scanner.peekToken().getStartMark());
959     }
960   }
961 
962   /**
963    * <pre>
964    * block_mapping     ::= BLOCK-MAPPING_START
965    *           ((KEY block_node_or_indentless_sequence?)?
966    *           (VALUE block_node_or_indentless_sequence?)?)*
967    *           BLOCK-END
968    * </pre>
969    */
processEmptyScalar(Mark mark)970   private Event processEmptyScalar(Mark mark) {
971     return new ScalarEvent(null, null, new ImplicitTuple(true, false), "", mark, mark,
972         DumperOptions.ScalarStyle.PLAIN);
973   }
974 }
975