1 module lexer; 2 3 import std.experimental.logger; 4 import std.format : format; 5 import std.stdio; 6 7 import tokenmodule; 8 9 struct Lexer { 10 string input; 11 size_t stringPos; 12 13 size_t line; 14 size_t column; 15 16 Token cur; 17 18 this(string input) @safe { 19 this.input = input; 20 this.stringPos = 0; 21 this.line = 1; 22 this.column = 1; 23 this.buildToken(); 24 } 25 26 private bool isTokenStop() const @safe { 27 return this.stringPos >= this.input.length 28 || this.isTokenStop(this.input[this.stringPos]); 29 } 30 31 private bool isTokenStop(const(char) c) const @safe { 32 return 33 c == ' ' || c == '\t' || c == '\n' || c == ';' || c == '(' 34 || c == ')' || c == '{' || c == '}' || c == '!' || c == '=' 35 || c == '|' || c == '*' || c == '/' || c == '[' || c == ':' 36 || c == ']' || c == ',' || c == '@' || c == '#' || c == '$'; 37 } 38 39 private void eatComment() @safe { 40 if(this.stringPos < this.input.length && 41 this.input[this.stringPos] == '#') 42 { 43 while(this.stringPos < this.input.length && 44 this.input[this.stringPos] != '\n') 45 { 46 ++this.stringPos; 47 } 48 ++this.stringPos; 49 ++this.line; 50 } 51 } 52 53 private void eatWhitespace() @safe { 54 import std.ascii : isWhite; 55 while(this.stringPos < this.input.length) { 56 this.eatComment(); 57 if(this.input[this.stringPos] == ' ') { 58 ++this.column; 59 } else if(this.input[this.stringPos] == '\t') { 60 ++this.column; 61 } else if(this.input[this.stringPos] == '\n') { 62 this.column = 1; 63 ++this.line; 64 } else { 65 break; 66 } 67 ++this.stringPos; 68 } 69 } 70 71 private void buildToken() @safe { 72 import std.ascii : isAlphaNum; 73 this.eatWhitespace(); 74 75 if(this.stringPos >= this.input.length) { 76 this.cur = Token(TokenType.undefined); 77 return; 78 } 79 80 if(this.input[this.stringPos] == ')') { 81 this.cur = Token(TokenType.rparen, this.line, this.column); 82 ++this.column; 83 ++this.stringPos; 84 } else if(this.input[this.stringPos] == '(') { 85 this.cur = Token(TokenType.lparen, this.line, this.column); 86 ++this.column; 87 ++this.stringPos; 88 } else if(this.input[this.stringPos] == ']') { 89 this.cur = Token(TokenType.rbrack, this.line, this.column); 90 ++this.column; 91 ++this.stringPos; 92 } else if(this.input[this.stringPos] == '[') { 93 this.cur = Token(TokenType.lbrack, this.line, this.column); 94 ++this.column; 95 ++this.stringPos; 96 } else if(this.input[this.stringPos] == '}') { 97 this.cur = Token(TokenType.rcurly, this.line, this.column); 98 ++this.column; 99 ++this.stringPos; 100 } else if(this.input[this.stringPos] == '$') { 101 this.cur = Token(TokenType.dollar, this.line, this.column); 102 ++this.column; 103 ++this.stringPos; 104 } else if(this.input[this.stringPos] == '!') { 105 this.cur = Token(TokenType.exclamation, this.line, this.column); 106 ++this.column; 107 ++this.stringPos; 108 } else if(this.input[this.stringPos] == '{') { 109 this.cur = Token(TokenType.lcurly, this.line, this.column); 110 ++this.column; 111 ++this.stringPos; 112 } else if(this.input[this.stringPos] == '|') { 113 this.cur = Token(TokenType.pipe, this.line, this.column); 114 ++this.column; 115 ++this.stringPos; 116 } else if(this.input[this.stringPos] == '@') { 117 this.cur = Token(TokenType.at, this.line, this.column); 118 ++this.column; 119 ++this.stringPos; 120 } else if(this.input[this.stringPos] == ',') { 121 this.cur = Token(TokenType.comma, this.line, this.column); 122 ++this.column; 123 ++this.stringPos; 124 } else if(this.input[this.stringPos] == '=') { 125 this.cur = Token(TokenType.equal, this.line, this.column); 126 ++this.column; 127 ++this.stringPos; 128 } else if(this.input[this.stringPos] == ':') { 129 this.cur = Token(TokenType.colon, this.line, this.column); 130 ++this.column; 131 ++this.stringPos; 132 } else { 133 size_t b = this.stringPos; 134 size_t e = this.stringPos; 135 switch(this.input[this.stringPos]) { 136 case 'm': 137 ++this.stringPos; 138 ++this.column; 139 ++e; 140 if(this.testCharAndInc('u', e)) { 141 if(this.testCharAndInc('t', e)) { 142 if(this.testCharAndInc('a', e)) { 143 if(this.testCharAndInc('t', e)) { 144 if(this.testCharAndInc('i', e)) { 145 if(this.testCharAndInc('o', e)) { 146 if(this.testCharAndInc('n', e)) { 147 if(this.isTokenStop()) { 148 this.cur = 149 Token(TokenType.mutation, 150 this.line, 151 this.column); 152 return; 153 } 154 } 155 } 156 } 157 } 158 } 159 } 160 } 161 goto default; 162 case '_': 163 ++this.stringPos; 164 ++this.column; 165 ++e; 166 if(this.testCharAndInc('_', e)) { 167 if(this.testCharAndInc('t', e)) { 168 if(this.testCharAndInc('y', e)) { 169 if(this.testCharAndInc('p', e)) { 170 if(this.testCharAndInc('e', e)) { 171 if(this.isTokenStop()) { 172 this.cur = Token(TokenType.type, 173 this.line, this.column); 174 return; 175 } 176 } 177 } 178 } 179 } else if(this.testCharAndInc('s', e)) { 180 if(this.testCharAndInc('c', e)) { 181 if(this.testCharAndInc('h', e)) { 182 if(this.testCharAndInc('e', e)) { 183 if(this.testCharAndInc('m', e)) { 184 if(this.testCharAndInc('a', e)) { 185 if(this.isTokenStop()) { 186 this.cur = 187 Token(TokenType.schema__, 188 this.line, 189 this.column); 190 return; 191 } 192 } 193 } 194 } 195 } 196 } 197 } 198 } 199 goto default; 200 case 's': 201 ++this.stringPos; 202 ++this.column; 203 ++e; 204 if(this.testCharAndInc('u', e)) { 205 if(this.testCharAndInc('b', e)) { 206 if(this.testCharAndInc('s', e)) { 207 if(this.testCharAndInc('c', e)) { 208 if(this.testCharAndInc('r', e)) { 209 if(this.testCharAndInc('i', e)) { 210 if(this.testCharAndInc('p', e)) { 211 if(this.testCharAndInc('t', e)) { 212 if(this.testCharAndInc('i', e)) { 213 if(this.testCharAndInc('o', e)) { 214 if(this.testCharAndInc('n', e)) { 215 if(this.isTokenStop()) { 216 this.cur = 217 Token(TokenType.subscription, 218 this.line, 219 this.column); 220 return; 221 } 222 } 223 } 224 } 225 } 226 } 227 } 228 } 229 } 230 } 231 } 232 } else if(this.testCharAndInc('c', e)) { 233 if(this.testCharAndInc('a', e)) { 234 if(this.testCharAndInc('l', e)) { 235 if(this.testCharAndInc('a', e)) { 236 if(this.testCharAndInc('r', e)) { 237 if(this.isTokenStop()) { 238 this.cur = Token(TokenType.scalar, this.line, this.column); 239 return; 240 } 241 } 242 } 243 } 244 } else if(this.testCharAndInc('h', e)) { 245 if(this.testCharAndInc('e', e)) { 246 if(this.testCharAndInc('m', e)) { 247 if(this.testCharAndInc('a', e)) { 248 if(this.isTokenStop()) { 249 this.cur = Token(TokenType.schema, this.line, this.column); 250 return; 251 } 252 } 253 } 254 } 255 } 256 } 257 goto default; 258 case 'o': 259 ++this.stringPos; 260 ++this.column; 261 ++e; 262 if(this.testCharAndInc('n', e)) { 263 if(this.isTokenStop()) { 264 this.cur = Token(TokenType.on_, this.line, 265 this.column); 266 return; 267 } 268 } 269 goto default; 270 case 'd': 271 ++this.stringPos; 272 ++this.column; 273 ++e; 274 if(this.testCharAndInc('i', e)) { 275 if(this.testCharAndInc('r', e)) { 276 if(this.testCharAndInc('e', e)) { 277 if(this.testCharAndInc('t', e)) { 278 if(this.testCharAndInc('i', e)) { 279 if(this.testCharAndInc('v', e)) { 280 if(this.testCharAndInc('e', e)) { 281 if(this.isTokenStop()) { 282 this.cur = Token(TokenType.directive, 283 this.line, this.column); 284 return; 285 } 286 } 287 } 288 } 289 } 290 } 291 } 292 } 293 goto default; 294 case 'e': 295 ++this.stringPos; 296 ++this.column; 297 ++e; 298 if(this.testCharAndInc('n', e)) { 299 if(this.testCharAndInc('u', e)) { 300 if(this.testCharAndInc('m', e)) { 301 if(this.isTokenStop()) { 302 this.cur = Token(TokenType.enum_, 303 this.line, this.column); 304 return; 305 } 306 } 307 } 308 } else if(this.testCharAndInc('x', e)) { 309 if(this.testCharAndInc('t', e)) { 310 if(this.testCharAndInc('e', e)) { 311 if(this.testCharAndInc('n', e)) { 312 if(this.testCharAndInc('d', e)) { 313 if(this.isTokenStop()) { 314 this.cur = Token(TokenType.extend, 315 this.line, this.column); 316 return; 317 } 318 } 319 } 320 } 321 } 322 } 323 goto default; 324 case 'i': 325 ++this.stringPos; 326 ++this.column; 327 ++e; 328 if(this.testCharAndInc('n', e)) { 329 if(this.testCharAndInc('p', e)) { 330 if(this.testCharAndInc('u', e)) { 331 if(this.testCharAndInc('t', e)) { 332 if(this.isTokenStop()) { 333 this.cur = Token(TokenType.input, 334 this.line, this.column); 335 return; 336 } 337 } 338 } 339 } else if(this.testCharAndInc('t', e)) { 340 if(this.testCharAndInc('e', e)) { 341 if(this.testCharAndInc('r', e)) { 342 if(this.testCharAndInc('f', e)) { 343 if(this.testCharAndInc('a', e)) { 344 if(this.testCharAndInc('c', e)) { 345 if(this.testCharAndInc('e', e)) { 346 if(this.isTokenStop()) { 347 this.cur = Token(TokenType.interface_, 348 this.line, this.column); 349 return; 350 } 351 } 352 } 353 } 354 } 355 } 356 } 357 } 358 } else if(this.testCharAndInc('m', e)) { 359 if(this.testCharAndInc('p', e)) { 360 if(this.testCharAndInc('l', e)) { 361 if(this.testCharAndInc('e', e)) { 362 if(this.testCharAndInc('m', e)) { 363 if(this.testCharAndInc('e', e)) { 364 if(this.testCharAndInc('n', e)) { 365 if(this.testCharAndInc('t', e)) { 366 if(this.testCharAndInc('s', e)) { 367 if(this.isTokenStop()) { 368 this.cur = Token(TokenType.implements, 369 this.line, this.column); 370 return; 371 } 372 } 373 } 374 } 375 } 376 } 377 } 378 } 379 } 380 } 381 382 goto default; 383 case 'f': 384 ++this.stringPos; 385 ++this.column; 386 ++e; 387 if(this.testCharAndInc('a', e)) { 388 if(this.testCharAndInc('l', e)) { 389 if(this.testCharAndInc('s', e)) { 390 if(this.testCharAndInc('e', e)) { 391 if(this.isTokenStop()) { 392 this.cur = Token(TokenType.false_, 393 this.line, this.column); 394 return; 395 } 396 } 397 } 398 } 399 } else if(this.testCharAndInc('r', e)) { 400 if(this.testCharAndInc('a', e)) { 401 if(this.testCharAndInc('g', e)) { 402 if(this.testCharAndInc('m', e)) { 403 if(this.testCharAndInc('e', e)) { 404 if(this.testCharAndInc('n', e)) { 405 if(this.testCharAndInc('t', e)) { 406 if(this.isTokenStop()) { 407 this.cur = 408 Token(TokenType.fragment, 409 this.line, 410 this.column); 411 return; 412 } 413 } 414 } 415 } 416 } 417 } 418 } 419 } 420 goto default; 421 /*case '@': 422 ++this.stringPos; 423 ++this.column; 424 ++e; 425 if(isTokenStop()) { 426 this.cur = Token(TokenType.at, this.line, this.column); 427 return; 428 } 429 goto default;*/ 430 case 'q': 431 ++this.stringPos; 432 ++this.column; 433 ++e; 434 if(this.testCharAndInc('u', e)) { 435 if(this.testCharAndInc('e', e)) { 436 if(this.testCharAndInc('r', e)) { 437 if(this.testCharAndInc('y', e)) { 438 if(this.isTokenStop()) { 439 this.cur = Token(TokenType.query, 440 this.line, this.column); 441 return; 442 } 443 } 444 } 445 } 446 } 447 goto default; 448 case 't': 449 ++this.stringPos; 450 ++this.column; 451 ++e; 452 if(this.testCharAndInc('r', e)) { 453 if(this.testCharAndInc('u', e)) { 454 if(this.testCharAndInc('e', e)) { 455 if(this.isTokenStop()) { 456 this.cur = Token(TokenType.true_, 457 this.line, this.column); 458 return; 459 } 460 } 461 } 462 } else if(this.testCharAndInc('y', e)) { 463 if(this.testCharAndInc('p', e)) { 464 if(this.testCharAndInc('e', e)) { 465 if(this.isTokenStop()) { 466 this.cur = Token(TokenType.type, 467 this.line, this.column); 468 return; 469 } 470 } 471 } 472 } 473 goto default; 474 case 'n': 475 ++this.stringPos; 476 ++this.column; 477 ++e; 478 if(this.testCharAndInc('u', e)) { 479 if(this.testCharAndInc('l', e)) { 480 if(this.testCharAndInc('l', e)) { 481 if(this.isTokenStop()) { 482 this.cur = Token(TokenType.null_, 483 this.line, this.column); 484 return; 485 } 486 } 487 } 488 } 489 goto default; 490 case 'u': 491 ++this.stringPos; 492 ++this.column; 493 ++e; 494 if(this.testCharAndInc('n', e)) { 495 if(this.testCharAndInc('i', e)) { 496 if(this.testCharAndInc('o', e)) { 497 if(this.testCharAndInc('n', e)) { 498 if(this.isTokenStop()) { 499 this.cur = Token(TokenType.union_, 500 this.line, this.column); 501 return; 502 } 503 } 504 } 505 } 506 } 507 goto default; 508 case '.': 509 ++this.stringPos; 510 ++this.column; 511 ++e; 512 if(this.testCharAndInc('.', e)) { 513 if(this.testCharAndInc('.', e)) { 514 //if(this.stringPos < this.input.length 515 // && isAlphaNum(this.input[this.stringPos])) 516 if(this.isTokenStop() 517 || (this.stringPos < this.input.length 518 && isAlphaNum(this.input[this.stringPos]) 519 ) 520 ) 521 { 522 this.cur = Token(TokenType.dots, this.line, 523 this.column); 524 return; 525 } 526 } 527 } 528 throw new Exception(format( 529 "failed to parse \"...\" at line %s column %s", 530 this.line, this.column 531 )); 532 case '-': 533 ++this.stringPos; 534 ++this.column; 535 ++e; 536 goto case '0'; 537 case '+': 538 ++this.stringPos; 539 ++this.column; 540 ++e; 541 goto case '0'; 542 case '0': .. case '9': 543 do { 544 ++this.stringPos; 545 ++this.column; 546 ++e; 547 } while(this.stringPos < this.input.length 548 && this.input[this.stringPos] >= '0' 549 && this.input[this.stringPos] <= '9'); 550 551 if(this.stringPos >= this.input.length 552 || this.input[this.stringPos] != '.') 553 { 554 this.cur = Token(TokenType.intValue, this.input[b .. 555 e], this.line, this.column); 556 return; 557 } else if(this.stringPos < this.input.length 558 && this.input[this.stringPos] == '.') 559 { 560 do { 561 ++this.stringPos; 562 ++this.column; 563 ++e; 564 } while(this.stringPos < this.input.length 565 && this.input[this.stringPos] >= '0' 566 && this.input[this.stringPos] <= '9'); 567 568 this.cur = Token(TokenType.floatValue, this.input[b .. 569 e], this.line, this.column); 570 return; 571 } 572 goto default; 573 case '"': 574 ++this.stringPos; 575 ++this.column; 576 ++e; 577 while(this.stringPos < this.input.length 578 && (this.input[this.stringPos] != '"' 579 || (this.input[this.stringPos] == '"' 580 && this.input[this.stringPos - 1U] == '\\') 581 ) 582 ) 583 { 584 ++this.stringPos; 585 ++this.column; 586 ++e; 587 } 588 ++this.stringPos; 589 ++this.column; 590 this.cur = Token(TokenType.stringValue, this.input[b + 1 591 .. e], this.line, this.column); 592 break; 593 default: 594 while(!this.isTokenStop()) { 595 //writefln("455 '%s'", this.input[this.stringPos]); 596 ++this.stringPos; 597 ++this.column; 598 ++e; 599 } 600 //writefln("%s %s %s '%s'", b, e, this.stringPos, this.input[b .. e]); 601 //do { 602 // writefln("'%s'", this.input[this.stringPos]); 603 // ++this.stringPos; 604 // ++this.column; 605 // ++e; 606 //} while(!this.isTokenStop()); 607 //writefln("%s %s", TokenType.name, this.input[b .. e]); 608 this.cur = Token(TokenType.name, this.input[b .. e], 609 this.line, this.column 610 ); 611 break; 612 } 613 } 614 } 615 616 bool testCharAndInc(const(char) c, ref size_t e) @safe { 617 if(this.stringPos < this.input.length 618 && this.input[this.stringPos] == c) 619 { 620 ++this.column; 621 ++this.stringPos; 622 ++e; 623 return true; 624 } else { 625 return false; 626 } 627 } 628 629 @property bool empty() const @safe { 630 return this.stringPos >= this.input.length 631 && this.cur.type == TokenType.undefined; 632 } 633 634 Token front() @property @safe { 635 return this.cur; 636 } 637 638 @property Token front() const @safe @nogc pure { 639 return this.cur; 640 } 641 642 void popFront() @safe { 643 this.buildToken(); 644 } 645 } 646 647 unittest { 648 string f = "f "; 649 auto l = Lexer(f); 650 assert(!l.empty); 651 assert(l.front.type == TokenType.name); 652 assert(l.front.value == "f", format("'%s'", l.front.value)); 653 } 654 655 unittest { 656 string f = "... "; 657 658 auto l = Lexer(f); 659 assert(!l.empty); 660 assert(l.front.type == TokenType.dots); 661 l.popFront(); 662 assert(l.empty); 663 } 664 665 unittest { 666 string f = "name! "; 667 auto l = Lexer(f); 668 assert(!l.empty); 669 assert(l.front.type == TokenType.name); 670 assert(l.front.value == "name", format("'%s'", l.front.value)); 671 l.popFront(); 672 assert(!l.empty); 673 assert(l.front.type == TokenType.exclamation); 674 l.popFront(); 675 assert(l.empty); 676 } 677 678 unittest { 679 string f = "fragment"; 680 auto l = Lexer(f); 681 assert(!l.empty); 682 assert(l.front.type == TokenType.fragment); 683 } 684 685 unittest { 686 string f = ` 687 mutation { 688 likeStory(storyID: 12345) { 689 story { 690 likeCount 691 } 692 } 693 }`; 694 auto l = Lexer(f); 695 assert(!l.empty); 696 assert(l.front.type == TokenType.mutation); 697 l.popFront(); 698 assert(!l.empty); 699 assert(l.front.type == TokenType.lcurly); 700 l.popFront(); 701 assert(!l.empty); 702 assert(l.front.type == TokenType.name); 703 l.popFront(); 704 assert(!l.empty); 705 assert(l.front.type == TokenType.lparen); 706 l.popFront(); 707 assert(!l.empty); 708 assert(l.front.type == TokenType.name); 709 l.popFront(); 710 assert(!l.empty); 711 assert(l.front.type == TokenType.colon, format("%s", l.front.type)); 712 l.popFront(); 713 assert(!l.empty); 714 assert(l.front.type == TokenType.intValue); 715 l.popFront(); 716 assert(!l.empty); 717 assert(l.front.type == TokenType.rparen); 718 l.popFront(); 719 assert(!l.empty); 720 assert(l.front.type == TokenType.lcurly); 721 l.popFront(); 722 assert(!l.empty); 723 assert(l.front.type == TokenType.name); 724 l.popFront(); 725 assert(!l.empty); 726 assert(l.front.type == TokenType.lcurly); 727 l.popFront(); 728 assert(!l.empty); 729 assert(l.front.type == TokenType.name); 730 l.popFront(); 731 assert(!l.empty); 732 assert(l.front.type == TokenType.rcurly); 733 l.popFront(); 734 assert(!l.empty); 735 assert(l.front.type == TokenType.rcurly); 736 l.popFront(); 737 assert(!l.empty); 738 assert(l.front.type == TokenType.rcurly); 739 l.popFront(); 740 assert(l.empty); 741 } 742 743 unittest { 744 string f = ` 745 query withFragments { 746 user(id: +4) { 747 # super cool comment 748 friends(first: -10.3) { 749 ...friendFields 750 null false true 751 } 752 mutualFriends(first: 10) { 753 ...friendFields 754 } 755 } 756 } 757 758 fragment friendFields on User { 759 id 760 name 761 profilePic(size: 50) 762 }`; 763 auto l = Lexer(f); 764 assert(!l.empty); 765 assert(l.front.type == TokenType.query); 766 l.popFront(); 767 assert(!l.empty); 768 assert(l.front.type == TokenType.name); 769 assert(l.front.value == "withFragments"); 770 l.popFront(); 771 l.popFront(); 772 assert(!l.empty); 773 assert(l.front.type == TokenType.name); 774 assert(l.front.value == "user"); 775 l.popFront(); 776 l.popFront(); 777 assert(!l.empty); 778 assert(l.front.type == TokenType.name); 779 assert(l.front.value == "id", l.front.value); 780 l.popFront(); 781 assert(!l.empty); 782 assert(l.front.type == TokenType.colon); 783 l.popFront(); 784 assert(!l.empty); 785 assert(l.front.type == TokenType.intValue); 786 assert(l.front.value == "+4"); 787 l.popFront(); 788 assert(!l.empty); 789 assert(l.front.type == TokenType.rparen); 790 l.popFront(); 791 l.popFront(); 792 assert(!l.empty); 793 assert(l.front.type == TokenType.name); 794 assert(l.front.value == "friends"); 795 l.popFront(); 796 assert(!l.empty); 797 assert(l.front.type == TokenType.lparen); 798 l.popFront(); 799 assert(!l.empty); 800 assert(l.front.type == TokenType.name); 801 assert(l.front.value == "first"); 802 l.popFront(); 803 l.popFront(); 804 assert(!l.empty); 805 assert(l.front.type == TokenType.floatValue, format("%s", l.front.type)); 806 assert(l.front.value == "-10.3", l.front.value); 807 l.popFront(); 808 l.popFront(); 809 l.popFront(); 810 assert(!l.empty); 811 assert(l.front.type == TokenType.dots, format("%s", l.front.type)); 812 l.popFront(); 813 assert(!l.empty); 814 assert(l.front.type == TokenType.name, format("%s", l.front.type)); 815 assert(l.front.value == "friendFields"); 816 l.popFront(); 817 assert(!l.empty); 818 assert(l.front.type == TokenType.null_, format("%s", l.front.type)); 819 l.popFront(); 820 assert(!l.empty); 821 assert(l.front.type == TokenType.false_, format("%s", l.front.type)); 822 l.popFront(); 823 assert(!l.empty); 824 assert(l.front.type == TokenType.true_, format("%s", l.front.type)); 825 while(!l.empty) { 826 l.popFront(); 827 } 828 } 829 830 unittest { 831 string f = ` 832 query withFragments { 833 user(id: "hello") { 834 } 835 }`; 836 837 auto l = Lexer(f); 838 assert(!l.empty); 839 assert(l.front.type == TokenType.query); 840 l.popFront(); 841 assert(!l.empty); 842 assert(l.front.type == TokenType.name); 843 assert(l.front.value == "withFragments"); 844 l.popFront(); 845 l.popFront(); 846 assert(!l.empty); 847 assert(l.front.type == TokenType.name); 848 assert(l.front.value == "user"); 849 l.popFront(); 850 l.popFront(); 851 assert(!l.empty); 852 assert(l.front.type == TokenType.name); 853 assert(l.front.value == "id", l.front.value); 854 l.popFront(); 855 assert(!l.empty); 856 assert(l.front.type == TokenType.colon); 857 l.popFront(); 858 assert(!l.empty); 859 assert(l.front.type == TokenType.stringValue); 860 assert(l.front.value == "hello", format("'%s' '%s'", l.front.value, "hello")); 861 l.popFront(); 862 assert(!l.empty); 863 assert(l.front.type == TokenType.rparen); 864 }