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 }