1 module dietc.parser; 2 3 import dietc.lexer; 4 5 import std.algorithm; 6 import std.conv : to, text; 7 import std.meta : AliasSeq; 8 9 alias ASTClasses = AliasSeq!(Document, HiddenComment, Comment, DStatement, DietFilter, TagNode, TagNode.AttributeAST, 10 RawAssignment, Assignment, StringTagContents, TextLine, XMLNode, PipeText, Expression, TextLine.PartAST); 11 12 interface AST 13 { 14 Token token() @property; 15 void accept(ASTVisitor visitor); 16 } 17 18 abstract class ASTVisitor 19 { 20 static foreach (T; ASTClasses) 21 void visit(T ast) in (ast !is null) 22 { 23 ast.accept(this); 24 } 25 26 void visit(AST ast) in (ast !is null) 27 { 28 static foreach (T; ASTClasses) 29 if (cast(T) ast) 30 return visit(cast(T) ast); 31 throw new Exception("Unknown ast passed?!"); 32 } 33 } 34 35 enum VisitResult 36 { 37 continue_, 38 recurse, 39 return_ 40 } 41 42 alias VisitorDelegate = VisitResult delegate(AST node, AST parent); 43 44 void traverse(AST node, VisitorDelegate callback) 45 { 46 static class VisitorImpl : ASTVisitor 47 { 48 static foreach (T; ASTClasses) 49 override void visit(T ast) 50 { 51 if (result == VisitResult.return_) 52 return; 53 54 result = callback(ast, parents[$ - 1]); 55 if (result == VisitResult.return_) 56 return; 57 58 if (result == VisitResult.recurse) 59 { 60 parents ~= ast; 61 scope (exit) 62 parents.length--; 63 ast.accept(this); 64 } 65 } 66 67 alias visit = ASTVisitor.visit; 68 69 VisitResult result; 70 AST[] parents; 71 VisitorDelegate callback; 72 73 this(AST node, VisitorDelegate callback) 74 { 75 parents = [node]; 76 this.callback = callback; 77 } 78 } 79 80 node.accept(new VisitorImpl(node, callback)); 81 } 82 83 class Document : AST, Node 84 { 85 Node[] _children; 86 Token _token; 87 88 Node[] children() @property 89 { 90 return _children; 91 } 92 93 void addChild(Node child) 94 { 95 _token.range[1] = child.token.range[1]; 96 _children ~= child; 97 } 98 99 Token token() @property 100 { 101 return _token; 102 } 103 104 void accept(ASTVisitor visitor) 105 { 106 foreach (child; _children) 107 if (child) 108 visitor.visit(child); 109 } 110 111 this(Token token) 112 { 113 _token = token; 114 } 115 116 override string toString() const 117 { 118 string ret = "Document("; 119 foreach (child; _children) 120 ret ~= "\n" ~ child.to!string.indent; 121 return ret ~= ")"; 122 } 123 } 124 125 interface Node : AST 126 { 127 Node[] children() @property; 128 void addChild(Node); 129 } 130 131 interface INamed 132 { 133 string name() @property; 134 } 135 136 interface IStringContainer : AST 137 { 138 string content() @property; 139 } 140 141 abstract class StringNode : Node, IStringContainer 142 { 143 Token _token; 144 string _content; 145 Node[] _children; 146 147 Node[] children() @property 148 { 149 return _children; 150 } 151 152 void addChild(Node child) 153 { 154 _token.range[1] = child.token.range[1]; 155 _children ~= child; 156 } 157 158 Token token() @property 159 { 160 return _token; 161 } 162 163 void accept(ASTVisitor visitor) 164 { 165 foreach (child; _children) 166 if (child) 167 visitor.visit(child); 168 } 169 170 string content() @property 171 { 172 return _content; 173 } 174 175 this(Token token, string content) 176 { 177 _token = token; 178 _content = content; 179 } 180 181 override string toString() 182 { 183 import std.array : join; 184 185 string ret = text('(', (cast(Object)this).classinfo.name, `) "`, content, '"'); 186 foreach (child; children) 187 ret ~= "\n" ~ child.to!string.indent; 188 return ret; 189 } 190 } 191 192 class Comment : StringNode 193 { 194 this(Token token, string content) 195 { 196 super(token, content); 197 } 198 } 199 200 class HiddenComment : Comment 201 { 202 this(Token token, string content) 203 { 204 super(token, content); 205 } 206 } 207 208 class DStatement : StringNode 209 { 210 this(Token token, string content) 211 { 212 super(token, content); 213 } 214 } 215 216 class DietFilter : StringNode, INamed 217 { 218 string _name; 219 220 string name() @property 221 { 222 return _name; 223 } 224 225 this(Token token, string name, string content) 226 { 227 super(token, content); 228 _name = name; 229 } 230 } 231 232 interface NestedTags : Node, TagContents 233 { 234 } 235 236 class TagNode : NestedTags, INamed 237 { 238 // NormalTagStart (TextBlock | NestedStart NestedTags | TagContents) 239 // NormalTagStart: TAG_IDENTIFIER? ( NodeID | NodeClass )* Attributes? ( FitInside? FitOutside? | FitOutside FitInside ) Translated? 240 // TagContents: RawAssignment | Assignment | ' '? TextLine | EOL 241 242 struct Attribute 243 { 244 Token name; 245 Expression expr; 246 247 string toString() 248 { 249 return name.content ~ name.range.to!string ~ (expr ? "='" ~ expr.content ~ "'" : "×"); 250 } 251 } 252 253 /// Wrapper class around Attribute used for visiting 254 class AttributeAST : AST, INamed 255 { 256 Attribute attribute; 257 258 this(Attribute attribute) 259 { 260 this.attribute = attribute; 261 } 262 263 Token token() @property 264 { 265 auto tok = attribute.name; 266 if (attribute.expr) 267 tok.range[1] = attribute.expr.token.range[1]; 268 return tok; 269 } 270 271 string name() @property 272 { 273 return attribute.name.content; 274 } 275 276 void accept(ASTVisitor visitor) 277 { 278 if (attribute.expr) 279 visitor.visit(attribute.expr); 280 } 281 282 override string toString() 283 { 284 return attribute.toString(); 285 } 286 } 287 288 Token[] directIDs, directClasses; 289 Attribute[] attributes; 290 Token _fitInside, _fitOutside, _translated; 291 Token _token, _tag; 292 TagContents _contents; 293 Node[] _children; 294 size_t[2] attributesRange; 295 296 Node[] children() @property 297 { 298 return _children; 299 } 300 301 void accept(ASTVisitor visitor) 302 { 303 foreach (attr; attributes) 304 visitor.visit(new AttributeAST(attr)); 305 if (_contents !is null) 306 visitor.visit(_contents); 307 foreach (child; _children) 308 if (child) 309 visitor.visit(child); 310 } 311 312 void addChild(Node child) 313 { 314 _token.range[1] = child.token.range[1]; 315 _children ~= child; 316 } 317 318 Token tag() @property 319 { 320 return _tag; 321 } 322 323 Token token() @property 324 { 325 return _token; 326 } 327 328 /// Returns: the tag name. 329 string name() @property 330 { 331 return _tag.content; 332 } 333 334 /// Returns: true if the `<` whitespace modifier token is present 335 bool fitInside() @property 336 { 337 return _fitInside != Token.init; 338 } 339 340 /// Returns: true if the `>` whitespace modifier token is present 341 bool fitOutside() @property 342 { 343 return _fitOutside != Token.init; 344 } 345 346 /// Returns: true if the `&` token is present 347 bool translated() @property 348 { 349 return _translated != Token.init; 350 } 351 352 /// Content of the tag (nullable) 353 TagContents contents() @property 354 { 355 return _contents; 356 } 357 358 this(Token tag) 359 { 360 this(tag, null); 361 } 362 363 this(Token tag, TagContents contents) 364 { 365 _tag = _token = tag; 366 _contents = contents; 367 } 368 369 override string toString() 370 { 371 import std.array : join; 372 373 string ret = "tag" ~ token.range.to!string ~ "<" ~ _tag.range.to!string ~ name ~ ">"; 374 foreach (c; directClasses) 375 ret ~= c.content; 376 foreach (id; directIDs) 377 ret ~= id.content; 378 ret ~= "("; 379 ret ~= attributes.to!(string[]).join(", "); 380 ret ~= ")" ~ attributesRange.to!string; 381 if (fitOutside) 382 ret ~= ">"; 383 if (fitInside) 384 ret ~= "<"; 385 if (translated) 386 ret ~= "&"; 387 if (_contents !is null) 388 ret ~= " = " ~ contents.to!string; 389 foreach (child; children) 390 ret ~= "\n" ~ child.to!string.indent; 391 return ret; 392 } 393 } 394 395 interface TagContents : AST 396 { 397 } 398 399 class StringTagContents : TagContents, IStringContainer 400 { 401 Token _token; 402 string _content; 403 404 Token token() @property 405 { 406 return _token; 407 } 408 409 void accept(ASTVisitor) 410 { 411 } 412 413 string content() @property 414 { 415 return _content; 416 } 417 418 this(Token token, string content) 419 { 420 _token = token; 421 _content = content; 422 } 423 424 override string toString() 425 { 426 import std.array : join; 427 428 string ret = text('(', (cast(Object)this).classinfo.name, `) "`, content, '"'); 429 return ret; 430 } 431 } 432 433 class Assignment : StringTagContents 434 { 435 this(Token token, string content) 436 { 437 super(token, content); 438 } 439 } 440 441 class RawAssignment : Assignment 442 { 443 this(Token token, string content) 444 { 445 super(token, content); 446 } 447 } 448 449 class TextLine : TagContents 450 { 451 struct Part 452 { 453 Token token; 454 string raw; 455 Expression inlineExpr; 456 NestedTags inlineTag; 457 bool escapeInlineExpr; 458 459 string toString() const 460 { 461 string pre = token.range.to!string; 462 if (raw.length) 463 return pre ~ raw; 464 else if (inlineExpr !is null) 465 return pre ~ (escapeInlineExpr ? "#" : "!") ~ "{" ~ inlineExpr.to!string ~ "}"; 466 else if (inlineTag !is null) 467 return pre ~ "#[" ~ inlineTag.to!string ~ "]"; 468 else 469 return pre; 470 } 471 } 472 473 /// Wrapper class around Part used for visiting 474 class PartAST : AST 475 { 476 Part part; 477 478 this(Part part) 479 { 480 this.part = part; 481 } 482 483 Token token() @property 484 { 485 return part.token; 486 } 487 488 void accept(ASTVisitor visitor) 489 { 490 if (part.inlineExpr) 491 visitor.visit(part.inlineExpr); 492 if (part.inlineTag) 493 visitor.visit(part.inlineTag); 494 } 495 496 override string toString() const 497 { 498 return part.toString(); 499 } 500 } 501 502 Token _token; 503 Part[] _parts; 504 505 void accept(ASTVisitor visitor) 506 { 507 foreach (part; _parts) 508 visitor.visit(new PartAST(part)); 509 } 510 511 Token token() @property 512 { 513 return _token; 514 } 515 516 this(Token token, Part[] parts) 517 { 518 _token = token; 519 _parts = parts; 520 } 521 522 override string toString() 523 { 524 string ret = _token.range.to!string ~ "'"; 525 foreach (part; _parts) 526 ret ~= '{' ~ part.toString ~ '}'; 527 return ret ~ "'"; 528 } 529 } 530 531 class XMLNode : NestedTags 532 { 533 TextLine _line; 534 Node[] _children; 535 536 Node[] children() @property 537 { 538 return _children; 539 } 540 541 void accept(ASTVisitor visitor) 542 { 543 foreach (child; _children) 544 if (child) 545 visitor.visit(child); 546 } 547 548 void addChild(Node child) 549 { 550 _line.token.range[1] = child.token.range[1]; 551 _children ~= child; 552 } 553 554 Token token() @property 555 { 556 return _line.token; 557 } 558 559 this(TextLine line) 560 { 561 _line = line; 562 } 563 } 564 565 class PipeText : NestedTags 566 { 567 Token _token; 568 Token _translated; 569 TagContents _content; 570 Node[] _children; 571 572 Node[] children() @property 573 { 574 return _children; 575 } 576 577 void accept(ASTVisitor visitor) 578 { 579 if (_content) 580 visitor.visit(_content); 581 foreach (child; _children) 582 if (child) 583 visitor.visit(child); 584 } 585 586 void addChild(Node child) 587 { 588 _token.range[1] = child.token.range[1]; 589 _children ~= child; 590 } 591 592 Token token() @property 593 { 594 return _token; 595 } 596 597 bool translated() @property 598 { 599 return _translated != Token.init; 600 } 601 602 TagContents content() @property 603 { 604 return _content; 605 } 606 607 this(Token token, Token translated, TagContents content) 608 { 609 _token = token; 610 _translated = translated; 611 _content = content; 612 } 613 } 614 615 class Expression : AST 616 { 617 Token _token; 618 string _content; 619 620 Token token() @property 621 { 622 return _token; 623 } 624 625 void accept(ASTVisitor) 626 { 627 } 628 629 string content() @property 630 { 631 return _content; 632 } 633 634 this(Token token, string content) 635 { 636 _token = token; 637 _content = content; 638 } 639 640 override string toString() 641 { 642 return _content; 643 } 644 } 645 646 struct ASTParser 647 { 648 DietInput input; 649 Document root; 650 651 void parseDocument() 652 { 653 root = new Document(input.front); 654 while (true) 655 { 656 auto n = parseNode(); 657 if (!n) 658 break; 659 root.addChild(n); 660 } 661 input.expect(TokenType.eof); 662 } 663 664 Node parseNode() 665 { 666 auto past = input.save(); 667 auto val = parseNodeValue(); 668 if (val is null) 669 { 670 input = past; 671 return null; 672 } 673 if (input.skipAll(TokenType.newline) == 0 && input.front.type != TokenType.eof) 674 { 675 input = past; 676 return null; 677 } 678 if (input.peek(TokenType.indent)) 679 { 680 input.popFront(); 681 while (true) 682 { 683 auto n = parseNode(); 684 if (!n) 685 break; 686 val.addChild(n); 687 } 688 input.expect(TokenType.detent); 689 } 690 return val; 691 } 692 693 Node parseNodeValue() 694 { 695 if (auto comment = parseComment()) 696 return comment; 697 if (auto statement = parseDStatement()) 698 return statement; 699 if (auto filter = parseFilter()) 700 return filter; 701 if (auto nested = parseNestedTags()) 702 return nested; 703 return null; 704 } 705 706 string parseText(bool multiline, bool allowIndent) 707 { 708 string ret; 709 size_t indentation = 1; 710 bool lastNewline; 711 while (true) 712 { 713 auto v = input.front; 714 if (lastNewline && v.type != TokenType.indent) 715 break; 716 if (v.type == TokenType.eof) 717 break; 718 if (v.type == TokenType.newline && (indentation == 1 || !multiline)) 719 { 720 if (multiline) 721 { 722 lastNewline = true; 723 input.popFront; 724 } 725 else 726 break; 727 } 728 else 729 { 730 input.popFront; 731 if (v.type == TokenType.indent) 732 { 733 if (indentation > 1 && !allowIndent) 734 input.errors.error(input, v.range[0], 735 "Can't indent here because it is already indented."); 736 indentation++; 737 } 738 else if (v.type == TokenType.detent) 739 { 740 indentation--; 741 if (indentation == 0) 742 break; 743 } 744 else 745 { 746 ret ~= v.content; 747 } 748 } 749 } 750 return ret; 751 } 752 753 Comment parseComment() 754 { 755 auto tok = input.front; 756 if (input.matchText("//-")) 757 { 758 string content = parseText(true, true); 759 tok.range[1] = input.front.range[0]; 760 return new HiddenComment(tok, content); 761 } 762 else if (input.matchText("//")) 763 { 764 string content = parseText(true, true); 765 tok.range[1] = input.front.range[0]; 766 return new Comment(tok, content); 767 } 768 return null; 769 } 770 771 DStatement parseDStatement() 772 { 773 auto tok = input.front; 774 if (input.matchText("-")) 775 { 776 string content = parseText(false, false); 777 tok.range[1] = input.front.range[0]; 778 return new DStatement(tok, content); 779 } 780 return null; 781 } 782 783 DietFilter parseFilter() 784 { 785 auto startTok = input.front; 786 if (input.matchText(":")) 787 { 788 auto tok = input.front; 789 string name; 790 if (input.expect(TokenType.identifier)) 791 { 792 name = tok.content; 793 if (!name.validateIdentifierAlpha) 794 input.errors.expect(input, tok.range[0], "identifier of type [-_a-zA-Z][-_0-9a-zA-Z]*"); 795 } 796 input.match(TokenType.whitespace); 797 string text = parseText(true, true); 798 return new DietFilter(startTok, name, text); 799 } 800 return null; 801 } 802 803 NestedTags parseNestedTags() 804 { 805 if (auto comment = parseDoctype()) 806 return comment; 807 if (auto statement = parseXML()) 808 return statement; 809 if (auto filter = parsePipeText()) 810 return filter; 811 if (auto nested = parseTag(true)) 812 return nested; 813 return null; 814 } 815 816 NestedTags parseSingleTag() 817 { 818 if (auto comment = parseDoctype()) 819 return comment; 820 if (auto statement = parseXML()) 821 return statement; 822 if (auto filter = parsePipeText()) 823 return filter; 824 if (auto nested = parseTag(false)) 825 return nested; 826 return null; 827 } 828 829 TextLine parseTextLine() 830 { 831 DietInput fastForward(string inc, string dec) 832 { 833 auto c = input.save; 834 int depth = 0; 835 do 836 { 837 if (c.front.content == inc) 838 depth++; 839 else if (c.front.content == dec) 840 depth--; 841 c.popFront; 842 } 843 while (depth > 0 && c.front.type != TokenType.eof); 844 return c; 845 } 846 847 Token tok = input.front; 848 TextLine.Part[] parts; 849 string raw; 850 size_t[2] rawRange = input.index; 851 immutable size_t startIndex = input.front.range[0]; 852 853 void flushRaw() 854 { 855 if (raw.length) 856 parts ~= TextLine.Part(Token(TokenType.code, raw, rawRange), raw); 857 raw = ""; 858 } 859 860 while (!input.front.type.among!(TokenType.eof, TokenType.newline)) 861 { 862 auto v = input.front; 863 input.popFront; 864 if (v.content == "\\") 865 { 866 v = input.front; 867 input.popFront; 868 if (!v.content.among!("!", "\\", "#")) 869 input.errors.expect(input, v.range[0], "escaped !, \\ or #"); 870 if (!raw.length) 871 rawRange[0] = v.range[0]; 872 raw ~= v.content; 873 rawRange[1] = input.index; 874 } 875 else if (v.content == "!") 876 { 877 if (input.front.content == "{") 878 { 879 flushRaw(); 880 auto load = fastForward("{", "}"); 881 input.code.length = load.index; 882 auto start = v.range[0]; 883 input.popFront(); 884 auto expr = parseExpression; 885 auto end = input.index; 886 v.range = [start, end]; 887 parts ~= TextLine.Part(v, null, expr, null, true); 888 input = load; 889 } 890 else 891 { 892 if (!raw.length) 893 rawRange[0] = v.range[0]; 894 raw ~= v.content; 895 rawRange[1] = input.index; 896 } 897 } 898 else if (v.content == "#") 899 { 900 if (input.front.content == "{") 901 { 902 flushRaw(); 903 auto load = fastForward("{", "}"); 904 input.code.length = load.index; 905 auto start = v.range[0]; 906 input.popFront(); 907 auto expr = parseExpression; 908 auto end = input.index; 909 v.range = [start, end]; 910 parts ~= TextLine.Part(v, null, expr, null, true); 911 input = load; 912 } 913 else if (input.front.content == "[") 914 { 915 flushRaw(); 916 auto load = fastForward("[", "]"); 917 input.code.length = load.index; 918 auto start = v.range[0]; 919 input.popFront(); 920 auto tag = parseSingleTag; 921 auto end = input.index; 922 v.range = [start, end]; 923 parts ~= TextLine.Part(v, null, null, tag); 924 input = load; 925 } 926 else 927 { 928 if (!raw.length) 929 rawRange[0] = v.range[0]; 930 raw ~= v.content; 931 rawRange[1] = input.index; 932 } 933 } 934 else if (v.type != TokenType.newline) 935 { 936 if (!raw.length) 937 rawRange[0] = v.range[0]; 938 raw ~= v.content; 939 rawRange[1] = input.index; 940 } 941 else 942 break; 943 tok.range[1] = input.index; 944 } 945 flushRaw(); 946 if (!parts.length && startIndex == input.front.range[0]) 947 return null; 948 return new TextLine(tok, parts); 949 } 950 951 TagNode parseDoctype() 952 { 953 auto tok = input.front; 954 if (input.matchText("!!!")) 955 { 956 tok.content = "doctype"; 957 auto text = parseTextLine(); 958 return new TagNode(tok, text); 959 } 960 return null; 961 } 962 963 XMLNode parseXML() 964 { 965 if (input.front.content == "<") 966 { 967 auto v = parseTextLine(); 968 return new XMLNode(v); 969 } 970 return null; 971 } 972 973 PipeText parsePipeText() 974 { 975 auto tok = input.front; 976 if (tok.content == "|") 977 { 978 input.popFront; 979 Token translated; 980 if (input.front.content == "&") 981 { 982 translated = input.front; 983 input.popFront; 984 } 985 return new PipeText(tok, translated, parseTagContents()); 986 } 987 return null; 988 } 989 990 Token parseAttributeIdentifier() 991 { 992 import std.array : array; 993 import std.range : retro, chain; 994 995 Token tok; 996 tok.type = TokenType.code; 997 tok.range = input.front.range; 998 string ret; 999 char[] stack; 1000 Loop: while (!input.empty) 1001 { 1002 auto front = input.front; 1003 if (front.type != TokenType.identifier) 1004 { 1005 switch (front.content) 1006 { 1007 case ",": 1008 case "=": 1009 if (stack.length == 0) 1010 break Loop; 1011 break; 1012 case ")": 1013 case "]": 1014 case "}": 1015 if (stack.length && stack[$ - 1] != front.content[0]) 1016 { 1017 input.errors.expect(input, front.range[0], 1018 "'" ~ (cast(char[])(cast(ubyte[]) stack).retro.chain.array).idup ~ "' before ')'"); 1019 stack.length = 0; 1020 } 1021 if (stack.length == 0) 1022 break Loop; 1023 stack.length--; 1024 break; 1025 case "(": 1026 stack ~= ')'; 1027 break; 1028 case "[": 1029 stack ~= ']'; 1030 break; 1031 case "{": 1032 stack ~= '}'; 1033 break; 1034 case "\"": 1035 if (stack.length && stack[$ - 1] == '"') 1036 { 1037 stack.length--; 1038 } 1039 else 1040 { 1041 stack ~= '"'; 1042 } 1043 break; 1044 default: 1045 break; 1046 } 1047 } 1048 ret ~= front.content; 1049 input.popFront; 1050 } 1051 tok.range[1] = input.front.range[0]; 1052 tok.content = ret; 1053 return tok; 1054 } 1055 1056 Expression parseExpression() 1057 { 1058 import std.array : array; 1059 import std.range : retro, chain; 1060 1061 auto save = input.save; 1062 auto tok = input.front; 1063 string ret; 1064 char[] stack; 1065 bool escape = false; 1066 int level = 0; 1067 Loop: while (!input.empty) 1068 { 1069 auto front = input.front; 1070 if (front.type == TokenType.indent) 1071 level++; 1072 else if (front.type == TokenType.detent) 1073 level--; 1074 if (level < 0) 1075 { 1076 input = save; 1077 return null; 1078 } 1079 if (front.type == TokenType.raw) 1080 { 1081 if (stack.length && stack[$ - 1].among!('"', '\'')) 1082 { 1083 if (escape) 1084 { 1085 escape = false; 1086 } 1087 else 1088 { 1089 if (front.content == "\\") 1090 escape = true; 1091 else if (front.content.length == 1 && front.content[0] == stack[$ - 1]) 1092 stack.length--; 1093 } 1094 } 1095 else 1096 { 1097 switch (front.content) 1098 { 1099 case ",": 1100 if (stack.length == 0) 1101 break Loop; 1102 break; 1103 case ")": 1104 case "]": 1105 case "}": 1106 if (stack.length && stack[$ - 1] != front.content[0]) 1107 { 1108 input.errors.expect(input, front.range[0], 1109 "'" ~ (cast(char[])(cast(ubyte[]) stack).retro.chain.array).idup ~ "' before ')'"); 1110 stack.length = 0; 1111 } 1112 if (stack.length == 0) 1113 break Loop; 1114 stack.length--; 1115 break; 1116 case "(": 1117 stack ~= ')'; 1118 break; 1119 case "[": 1120 stack ~= ']'; 1121 break; 1122 case "{": 1123 stack ~= '}'; 1124 break; 1125 case "\"": 1126 stack ~= '"'; 1127 break; 1128 case "'": 1129 stack ~= '\''; 1130 break; 1131 default: 1132 break; 1133 } 1134 } 1135 } 1136 else 1137 escape = false; 1138 ret ~= front.content; 1139 input.popFront; 1140 } 1141 if (stack.length) 1142 input.errors.expect(input, input.front.range[0], 1143 "'" ~ (cast(char[])(cast(ubyte[]) stack).retro.chain.array).idup ~ "' before ')'"); 1144 tok.range[1] = input.front.range[0]; 1145 return new Expression(tok, ret); 1146 } 1147 1148 TagNode parseTag(bool allowNested = true) 1149 { 1150 auto save = input.save; 1151 1152 auto tok = input.front; 1153 input.popFront; 1154 1155 Token[] classes, ids; 1156 1157 bool parseClassOrID() 1158 { 1159 Token combineIdentifier() 1160 { 1161 Token start = input.front; 1162 input.popFront; 1163 auto next = input.front; 1164 if (input.expect(TokenType.identifier)) 1165 { 1166 if (!validateIdentifier(next.content)) 1167 input.errors.expect(input, next.range[0], "identifier of type [-_0-9a-zA-Z]+"); 1168 start.content ~= next.content; 1169 start.range[1] = next.range[1]; 1170 } 1171 return start; 1172 } 1173 1174 if (input.front.content == ".") 1175 { 1176 classes ~= combineIdentifier(); 1177 return true; 1178 } 1179 else if (input.front.content == "#") 1180 { 1181 ids ~= combineIdentifier(); 1182 return true; 1183 } 1184 else 1185 return false; 1186 } 1187 1188 Token tag; 1189 bool match; 1190 if (input.front.content == "." || input.front.content == "#") 1191 { 1192 tag = tok; 1193 tag.range[1] = tag.range[0]; 1194 tag.content = "div"; 1195 while (parseClassOrID()) 1196 { 1197 } 1198 match = classes.length > 0 || ids.length > 0; 1199 } 1200 else if (tok.type == TokenType.identifier) 1201 { 1202 tag = tok; 1203 if (!tok.content.validateTagIdentifier) 1204 input.errors.expect(input, tok.range[0], "identifier of type [-:_0-9a-zA-Z]+"); 1205 while (parseClassOrID()) 1206 { 1207 } 1208 match = true; 1209 } 1210 1211 if (match) 1212 { 1213 auto ret = new TagNode(tag); 1214 ret.directIDs = ids; 1215 ret.directClasses = classes; 1216 ret.attributesRange[] = input.front.range[0]; 1217 if (input.matchText("(")) 1218 { 1219 ret.attributesRange[0]++; 1220 ret.attributesRange[1]++; 1221 auto lastValid = input.save; 1222 // don't store in lastValid after detent happened 1223 bool detented = false; 1224 TagNode.Attribute[] lastNonDetented; 1225 bool lastNonDetentedHadValue; 1226 if (input.peek(TokenType.identifier)) 1227 { 1228 lastValid = input.save; 1229 bool errored; 1230 while (input.front.content != ")") 1231 { 1232 input.skipAllWhiteGetDetent(detented); 1233 const wasDetented = detented; 1234 auto identifier = parseAttributeIdentifier(); 1235 Expression value; 1236 bool validKey; 1237 if (!identifier.content.length) 1238 errored = true; 1239 else if (!detented) 1240 { 1241 lastValid = input.save; 1242 validKey = true; 1243 } 1244 input.skipAllWhiteGetDetent(detented); 1245 bool empty; 1246 if (input.matchText("=")) 1247 { 1248 if (!detented) 1249 lastValid = input.save; 1250 input.skipAllWhiteGetDetent(detented); 1251 value = parseExpression(); 1252 if (value !is null) 1253 { 1254 if (!detented) 1255 lastValid = input.save; 1256 } 1257 else 1258 errored = true; 1259 } 1260 else if (input.matchText(",")) 1261 { 1262 if (!detented) 1263 lastValid = input.save; 1264 input.skipAllWhiteGetDetent(detented); 1265 } 1266 else 1267 { 1268 if (!identifier.content.length) 1269 break; 1270 empty = true; 1271 } 1272 1273 if (!errored) 1274 { 1275 if (!detented) 1276 lastValid = input.save; 1277 input.skipAllWhiteGetDetent(detented); 1278 } 1279 1280 ret.attributes ~= TagNode.Attribute(identifier, value); 1281 if (!detented) 1282 lastNonDetented = ret.attributes; 1283 1284 if (!wasDetented && detented && validKey) 1285 { 1286 lastNonDetented = ret.attributes; 1287 lastNonDetentedHadValue = false; 1288 } 1289 1290 if (empty && !input.front.content.among!(")", ",")) 1291 { 1292 errored = true; 1293 break; 1294 } 1295 1296 if (input.front.content == ",") 1297 input.popFront(); 1298 } 1299 ret.attributesRange[1] = input.front.range[0]; 1300 if (errored) 1301 { 1302 lastValid.errors = input.errors; 1303 ret.attributes = lastNonDetented; 1304 input = lastValid; 1305 } 1306 } 1307 if (!input.expect(TokenType.raw, ")") && detented) 1308 { 1309 lastValid.errors = input.errors; 1310 ret.attributes = lastNonDetented; 1311 input = lastValid; 1312 } 1313 if (detented && !lastNonDetentedHadValue && ret.attributes.length) 1314 ret.attributes[$ - 1].expr = null; 1315 } 1316 if (input.front.content == "<") 1317 { 1318 ret._fitInside = input.front; 1319 input.popFront; 1320 } 1321 else if (input.front.content == ">") 1322 { 1323 auto tmp = input.front; 1324 ret._fitOutside = tmp; 1325 input.popFront; 1326 if (input.front.content == "<") 1327 { 1328 tmp = input.front; 1329 input.popFront; 1330 ret._fitInside = tmp; 1331 } 1332 } 1333 if (input.front.content == "&") 1334 { 1335 ret._translated = input.front; 1336 input.popFront; 1337 } 1338 1339 if (input.front.content == ".") 1340 { 1341 ret._contents = parseTextBlock(); 1342 } 1343 else if (allowNested && input.front.content == ":") 1344 { 1345 input.popFront; 1346 input.skipAll(TokenType.whitespace); 1347 ret._contents = parseNestedTags(); 1348 } 1349 else 1350 { 1351 ret._contents = parseTagContents(); 1352 } 1353 1354 ret._token.range[1] = input.index; 1355 return ret; 1356 } 1357 else 1358 { 1359 input = save; 1360 return null; 1361 } 1362 } 1363 1364 StringTagContents parseTextBlock() 1365 { 1366 Token front = input.front; 1367 if (front.content == ".") 1368 { 1369 input.popFront; 1370 string content; 1371 input.skipAll(TokenType.identifier, TokenType.raw, TokenType.whitespace); 1372 if (input.expect(TokenType.newline)) 1373 { 1374 input.skipAll(TokenType.newline); 1375 if (input.expect(TokenType.indent)) 1376 { 1377 content = parseText(true, true); 1378 input.expect(TokenType.detent); 1379 } 1380 } 1381 front.range[1] = input.index; 1382 return new StringTagContents(front, content); 1383 } 1384 return null; 1385 } 1386 1387 TagContents parseTagContents() 1388 { 1389 if (auto assignment = parseAssignment()) 1390 return assignment; 1391 if (input.peek(TokenType.newline)) 1392 return null; 1393 input.match(TokenType.whitespace); 1394 return parseTextLine(); 1395 } 1396 1397 Assignment parseAssignment() 1398 { 1399 auto tok = input.front; 1400 if (input.matchText("!=")) 1401 { 1402 string content = parseText(false, false); 1403 tok.content = "!="; 1404 tok.range[1] = input.front.range[0]; 1405 return new RawAssignment(tok, content); 1406 } 1407 else if (input.matchText("=")) 1408 { 1409 string content = parseText(false, false); 1410 tok.range[1] = input.front.range[0]; 1411 return new Assignment(tok, content); 1412 } 1413 return null; 1414 } 1415 1416 /// Searches for a path of AST nodes lying within the specified offset. 1417 /// Params: 1418 /// offset = The cursor position to search AST nodes in. 1419 /// inclusiveStart = true if an AST [1 .. 3] should be matched for index 1. 1420 /// inclusiveEnd = true if an AST [1 .. 3] should be matched for index 3. 1421 /// Returns: A path of AST nodes starting at the broadest object (Document) down to the finest object. 1422 AST[] searchAST(size_t offset, bool inclusiveStart = true, bool inclusiveEnd = true) 1423 out (r; r.length > 0) 1424 { 1425 AST[] ret = [root]; 1426 1427 root.traverse((AST node, AST parent) { 1428 if (ret[$ - 1] != parent) 1429 return VisitResult.return_; 1430 auto range = node.token.range; 1431 if (!offset.withinRange(range, inclusiveStart, inclusiveEnd)) 1432 return VisitResult.continue_; 1433 ret ~= node; 1434 return VisitResult.recurse; 1435 }); 1436 1437 return ret; 1438 } 1439 } 1440 1441 bool withinRange(size_t offset, size_t[2] range, bool inclusiveStart = true, bool inclusiveEnd = true) 1442 { 1443 if (inclusiveStart && inclusiveEnd) 1444 return offset >= range[0] && offset <= range[1]; 1445 else if (inclusiveStart) 1446 return offset >= range[0] && offset < range[1]; 1447 else if (inclusiveEnd) 1448 return offset > range[0] && offset <= range[1]; 1449 else 1450 return offset > range[0] && offset < range[1]; 1451 } 1452 1453 void skipAllWhiteGetDetent(ref DietInput input, ref bool detented) 1454 { 1455 auto c = input.skipAllCount(TokenType.whitespace, TokenType.detent, 1456 TokenType.indent, TokenType.newline); 1457 if (c[1]) 1458 detented = true; 1459 }