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