1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20/* 21 * NOTE : please see documentation at bottom of this file. (It was placed there its tiring 22 * to always have to page past it... :) 23 */ 24options 25{ 26 /** The default package for this parser kit. This is now done from Maven. 27 NODE_PACKAGE="org.apache.velocity.runtime.parser"; 28 */ 29 30 /** A source file will be generated for each non-terminal */ 31 MULTI=true; 32 33 /** 34 * Each node will have access to the parser, I did this so 35 * some global information can be shared via the parser. I 36 * think this will come in handly keeping track of 37 * context, and being able to push changes back into 38 * the context when nodes make modifications to the 39 * context by setting properties, variables and 40 * what not. 41 */ 42 NODE_USES_PARSER=true; 43 44 /** 45 * The parser must be non-static in order for the 46 * above option to work, otherwise the parser value 47 * is passed in as null, which isn't all the useful ;) 48 */ 49 STATIC=false; 50 51 /** 52 * Enables the use of a visitor that each of nodes 53 * will accept. This way we can separate the logic 54 * of node processing in a visitor and out of the 55 * nodes themselves. If processing changes then 56 * the nothing has to change in the node code. 57 */ 58 VISITOR=true; 59 60 /** 61 * Declare that we are accepting unicode input and 62 * that we are using a custom character stream class 63 * Note that the char stream class is really a slightly 64 * modified ASCII_CharStream, as it appears we are safe 65 * because we only deal with pre-encoding-converted 66 * Readers rather than raw input streams. 67 */ 68 UNICODE_INPUT=true; 69 USER_CHAR_STREAM=true; 70 71 /** 72 * for debugging purposes. Those are now handled from within javacc-maven-plugin debugging flags in pom.xml 73 DEBUG_PARSER = true; 74 DEBUG_LOOKAHEAD = true; 75 DEBUG_TOKEN_MANAGER = true; 76 */ 77 78 79} 80 81PARSER_BEGIN(${parser.basename}Parser) 82package ${parser.package}; 83 84import java.io.*; 85import java.util.*; 86import org.apache.velocity.Template; 87import org.apache.velocity.exception.VelocityException; 88import org.apache.velocity.runtime.RuntimeServices; 89import org.apache.velocity.runtime.parser.*; 90import org.apache.velocity.runtime.parser.node.*; 91import org.apache.velocity.runtime.directive.*; 92import org.apache.velocity.runtime.directive.MacroParseException; 93import org.apache.velocity.runtime.RuntimeConstants; 94import static org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; 95 96import org.slf4j.Logger; 97 98/** 99 * This class is responsible for parsing a Velocity 100 * template. This class was generated by JavaCC using 101 * the JJTree extension to produce an Abstract 102 * Syntax Tree (AST) of the template. 103 * 104 * Please look at the Parser.jjt file which is 105 * what controls the generation of this class. 106 * 107 * @author <a href="mailto:[email protected]">Jason van Zyl</a> 108 * @author <a href="mailto:[email protected]">Geir Magnusson Jr.</a> 109 * @author <a href="[email protected]">Henning P. Schmiedehausen</a> 110 * @version $Id$ 111*/ 112public class ${parser.basename}Parser implements Parser 113{ 114 /** 115 * Parser debugging flag. 116 * When debug is active, javacc Parser will contain (among other things) 117 * a trace_call() method. So we use the presence of this method to 118 * initialize our flag. 119 */ 120 private static boolean debugParser; 121 static 122 { 123 try 124 { 125 ${parser.basename}Parser.class.getDeclaredMethod("trace_call", String.class); 126 debugParser = true; 127 } 128 catch(NoSuchMethodException nsfe) 129 { 130 debugParser = false; 131 } 132 } 133 134 /** 135 * Our own trace method. Use sparsingly in production, since each 136 * and every call will introduce an execution branch and slow down parsing. 137 */ 138 public static void trace(String message) 139 { 140 if (debugParser) System.out.println(message); 141 } 142 143 /** 144 * Keep track of defined macros, used for escape processing 145 */ 146 private Map macroNames = new HashMap(); 147 148 /** 149 * Current template we are parsing. Passed to us in parse() 150 */ 151 public Template currentTemplate = null; 152 153 /** 154 * Set to true if the property 155 * RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE is set to true 156 */ 157 public boolean strictEscape = false; 158 159 /** 160 * Set to true if the propoerty 161 * RuntimeConstants.PARSER_HYPHEN_ALLOWED is set to true 162 */ 163 public boolean hyphenAllowedInIdentifiers = false; 164 165 VelocityCharStream velcharstream = null; 166 167 private RuntimeServices rsvc = null; 168 169 @Override 170 public RuntimeServices getRuntimeServices() 171 { 172 return rsvc; 173 } 174 175 private Logger log = null; 176 177 /** 178 * This constructor was added to allow the re-use of parsers. 179 * The normal constructor takes a single argument which 180 * an InputStream. This simply creates a re-usable parser 181 * object, we satisfy the requirement of an InputStream 182 * by using a newline character as an input stream. 183 */ 184 public ${parser.basename}Parser( RuntimeServices rs) 185 { 186 /* 187 * need to call the CTOR first thing. 188 */ 189 190 this( new VelocityCharStream( 191 new ByteArrayInputStream("\n".getBytes()), 1, 1 )); 192 193 /* 194 * then initialize logger 195 */ 196 197 log = rs.getLog("parser"); 198 199 200 /* 201 * now setup a VCS for later use 202 */ 203 velcharstream = new VelocityCharStream( 204 new ByteArrayInputStream("\n".getBytes()), 1, 1 ); 205 206 207 strictEscape = 208 rs.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, false); 209 210 hyphenAllowedInIdentifiers = 211 rs.getBoolean(RuntimeConstants.PARSER_HYPHEN_ALLOWED, false); 212 213 /* 214 * and save the RuntimeServices 215 */ 216 rsvc = rs; 217 218 /* 219 * then initialize customizable characters 220 */ 221 dollar = '${parser.char.dollar}'; 222 hash = '${parser.char.hash}'; 223 at = '${parser.char.at}'; 224 asterisk = '${parser.char.asterisk}'; 225 } 226 227 /** 228 * This was also added to allow parsers to be 229 * re-usable. Normal JavaCC use entails passing an 230 * input stream to the constructor and the parsing 231 * process is carried out once. We want to be able 232 * to re-use parsers: we do this by adding this 233 * method and re-initializing the lexer with 234 * the new stream that we want parsed. 235 */ 236 @Override 237 public SimpleNode parse( Reader reader, Template template ) 238 throws ParseException 239 { 240 SimpleNode sn = null; 241 242 currentTemplate = template; 243 244 try 245 { 246 token_source.clearStateVars(); 247 248 /* 249 * reinitialize the VelocityCharStream 250 * with the new reader 251 */ 252 velcharstream.ReInit( reader, 1, 1 ); 253 254 /* 255 * now reinit the Parser with this CharStream 256 */ 257 ReInit( velcharstream ); 258 259 /* 260 * do that voodoo... 261 */ 262 sn = process(); 263 } 264 catch (MacroParseException mee) 265 { 266 /* 267 * thrown by the Macro class when something is amiss in the 268 * Macro specification 269 */ 270 log.error("{}: {}", template.getName(), mee.getMessage(), mee); 271 throw mee; 272 } 273 catch (ParseException pe) 274 { 275 log.error("{}: {}", currentTemplate.getName(), pe.getMessage()); 276 throw new TemplateParseException (pe.currentToken, 277 pe.expectedTokenSequences, pe.tokenImage, currentTemplate.getName()); 278 } 279 catch (TokenMgrError tme) 280 { 281 throw new ParseException("Lexical error: " + tme.toString()); 282 } 283 catch (Exception e) 284 { 285 String msg = template.getName() + ": " + e.getMessage(); 286 log.error(msg, e); 287 throw new VelocityException(msg, e, getRuntimeServices().getLogContext().getStackTrace()); 288 } 289 290 currentTemplate = null; 291 292 return sn; 293 } 294 295 /** 296 * This method gets a Directive from the directives Hashtable 297 */ 298 @Override 299 public Directive getDirective(String directive) 300 { 301 return (Directive) rsvc.getDirective(directive); 302 } 303 304 /** 305 * This method finds out of the directive exists in the directives Map. 306 */ 307 @Override 308 public boolean isDirective(String directive) 309 { 310 return rsvc.getDirective(directive) != null; 311 } 312 313 314 /** 315 * Produces a processed output for an escaped control or 316 * pluggable directive 317 */ 318 private String escapedDirective( String strImage ) 319 { 320 int iLast = strImage.lastIndexOf("\\"); 321 322 String strDirective = strImage.substring(iLast + 1); 323 324 boolean bRecognizedDirective = false; 325 326 // we don't have to call substring method all the time in this method 327 String dirTag = strDirective.substring(1); 328 if (dirTag.charAt(0) == '{') 329 { 330 dirTag = dirTag.substring(1, dirTag.length() - 1); 331 } 332 333 /* 334 * If this is a predefined derective or if we detect 335 * a macro definition (this is aproximate at best) then 336 * we absorb the forward slash. If in strict reference 337 * mode then we always absord the forward slash regardless 338 * if the derective is defined or not. 339 */ 340 341 if (strictEscape 342 || isDirective(dirTag) 343 || macroNames.containsKey(dirTag) 344 || rsvc.isVelocimacro(dirTag, currentTemplate)) 345 { 346 bRecognizedDirective = true; 347 } 348 else 349 { 350 /* order for speed? */ 351 352 if ( dirTag.equals("if") 353 || dirTag.equals("end") 354 || dirTag.equals("set") 355 || dirTag.equals("else") 356 || dirTag.equals("elseif") 357 ) 358 { 359 bRecognizedDirective = true; 360 } 361 } 362 363 /* 364 * if so, make the proper prefix string (let the escapes do their thing..) 365 * otherwise, just return what it is.. 366 */ 367 368 if (bRecognizedDirective) 369 return ( strImage.substring(0,iLast/2) + strDirective); 370 else 371 return ( strImage ); 372 } 373 374 /** 375 * Check whether there is a left parenthesis with leading optional 376 * whitespaces. This method is used in the semantic look ahead of 377 * Directive method. This is done in code instead of as a production 378 * for simplicity and efficiency. 379 */ 380 private boolean isLeftParenthesis() 381 { 382 char c; 383 int no = 0; 384 try { 385 while(true) 386 { 387 /** 388 * Read a character 389 */ 390 c = velcharstream.readChar(); 391 no++; 392 if (c == '(') 393 { 394 return true; 395 } 396 /** 397 * if not a white space return 398 */ 399 else if (c != ' ' && c != '\n' && c != '\r' && c != '\t') 400 { 401 return false; 402 } 403 } 404 } 405 catch(IOException e) 406 { 407 } 408 finally 409 { 410 /** 411 * Backup the stream to the initial state 412 */ 413 velcharstream.backup(no); 414 } 415 return false; 416 } 417 418 /** 419 * Check whether there is a right parenthesis with leading optional 420 * whitespaces. This method is used in the semantic look ahead of 421 * Directive method. This is done in code instead of as a production 422 * for simplicity and efficiency. 423 */ 424 private boolean isRightParenthesis() 425 { 426 char c; 427 int no = -1; 428 try { 429 while(true) 430 { 431 /** 432 * Read a character 433 */ 434 if (no == -1) 435 { 436 switch (getToken(1).kind) 437 { 438 case RPAREN: 439 return true; 440 case WHITESPACE: 441 case NEWLINE: 442 no = 0; 443 break; 444 default: 445 return false; 446 } 447 } 448 c = velcharstream.readChar(); 449 no++; 450 if (c == ')') 451 { 452 return true; 453 } 454 /** 455 * if not a white space return 456 */ 457 else if (c != ' ' && c != '\n' && c != '\r' && c != '\t') 458 { 459 return false; 460 } 461 } 462 } 463 catch(IOException e) 464 { 465 } 466 finally 467 { 468 /** 469 * Backup the stream to the initial state 470 */ 471 if (no > 0) velcharstream.backup(no); 472 } 473 return false; 474 } 475 476 /** 477 * We use this method in a lookahead to determine if we are in a macro 478 * default value assignment. The standard lookahead is not smart enough. 479 * here we look for the equals after the reference. 480 */ 481 private boolean isAssignment() 482 { 483 // Basically if the last character read was not '$' then false 484 if (token_source.getCurrentLexicalState() != REFERENCE) return false; 485 486 char c = ' '; 487 int backup = 0; 488 try 489 { 490 // Read through any white space 491 while(Character.isWhitespace(c)) 492 { 493 c = velcharstream.readChar(); 494 backup++; 495 } 496 497 // This is what we are ultimately looking for 498 if (c != '=') return false; 499 } 500 catch (IOException e) 501 { 502 } 503 finally 504 { 505 velcharstream.backup(backup); 506 } 507 508 return true; 509 } 510 511 @Override 512 public Template getCurrentTemplate() 513 { 514 return currentTemplate; 515 } 516 517 @Override 518 public void resetCurrentTemplate() 519 { 520 currentTemplate = null; 521 } 522 523 @Override 524 public char dollar() 525 { 526 return dollar; 527 } 528 529 @Override 530 public char hash() 531 { 532 return hash; 533 } 534 535 @Override 536 public char at() 537 { 538 return at; 539 } 540 541 @Override 542 public char asterisk() 543 { 544 return asterisk; 545 } 546 547 private char dollar = '$'; 548 private char hash = '#'; 549 private char at = '@'; 550 private char asterisk = '*'; 551} 552 553PARSER_END(${parser.basename}Parser) 554 555TOKEN_MGR_DECLS: 556{ 557 private int fileDepth = 0; 558 559 private int lparen = 0; 560 private int rparen = 0; 561 private int curlyLevel = 0; 562 List stateStack = new ArrayList(50); 563 564 private boolean inComment; 565 private boolean inSet; 566 567 /** 568 * Our own trace method. Use sparsingly in production, since each 569 * and every call will introduce an execution branch and slow down parsing. 570 */ 571 public static void trace(String message) 572 { 573 ${parser.basename}Parser.trace(message); 574 } 575 576 /** 577 * Switches to a new state (add some log to the default method) 578 */ 579 public void switchTo(int lexState) 580 { 581 trace(" switch to " + lexStateNames[lexState]); 582 SwitchTo(lexState); 583 } 584 585 public int getCurrentLexicalState() 586 { 587 return curLexState; 588 } 589 590 /** 591 * pops a state off the stack, and restores paren counts 592 * 593 * @return boolean : success of operation 594 */ 595 public boolean stateStackPop() 596 { 597 ParserState s; 598 try 599 { 600 s = (ParserState) stateStack.remove(stateStack.size() - 1); // stack.pop 601 } 602 catch(IndexOutOfBoundsException e) 603 { 604 // empty stack 605 lparen=0; 606 switchTo(DEFAULT); 607 return false; 608 } 609 610 trace(" stack pop (" + stateStack.size() + ")"); 611 lparen = s.lparen; 612 rparen = s.rparen; 613 curlyLevel = s.curlyLevel; 614 615 switchTo(s.lexstate); 616 617 return true; 618 } 619 620 /** 621 * pushes the current state onto the 'state stack', 622 * and maintains the parens counts 623 * public because we need it in PD & VM handling 624 * 625 * @return boolean : success. It can fail if the state machine 626 * gets messed up (do don't mess it up :) 627 */ 628 public boolean stateStackPush() 629 { 630 trace(" (" + stateStack.size() + ") pushing cur state : " + lexStateNames[curLexState] ); 631 632 ParserState s = new ParserState(); 633 s.lparen = lparen; 634 s.rparen = rparen; 635 s.curlyLevel = curlyLevel; 636 s.lexstate = curLexState; 637 638 stateStack.add(s); // stack.push 639 640 lparen = 0; 641 curlyLevel = 0; 642 643 return true; 644 } 645 646 /** 647 * Clears all state variables, resets to 648 * start values, clears stateStack. Call 649 * before parsing. 650 */ 651 public void clearStateVars() 652 { 653 stateStack.clear(); 654 655 lparen = 0; 656 rparen = 0; 657 curlyLevel = 0; 658 inComment = false; 659 inSet = false; 660 661 return; 662 } 663 664 public void setInSet(boolean value) 665 { 666 inSet = value; 667 } 668 669 public boolean isInSet() 670 { 671 return inSet; 672 } 673 674 /** 675 * Holds the state of the parsing process. 676 */ 677 private static class ParserState 678 { 679 int lparen; 680 int rparen; 681 int curlyLevel; 682 int lexstate; 683 } 684 685 /** 686 * handles the dropdown logic when encountering a RPAREN 687 */ 688 private void RPARENHandler() 689 { 690 /* 691 * Ultimately, we want to drop down to the state below 692 * the one that has an open (if we hit bottom (DEFAULT), 693 * that's fine. It's just text schmoo. 694 */ 695 696 boolean closed = false; 697 698 if (inComment) 699 closed = true; 700 701 while( !closed ) 702 { 703 /* 704 * look at current state. If we haven't seen a lparen 705 * in this state then we drop a state, because this 706 * lparen clearly closes our state 707 */ 708 709 if( lparen > 0) 710 { 711 /* 712 * if rparen + 1 == lparen, then this state is closed. 713 * Otherwise, increment and keep parsing 714 */ 715 716 if( lparen == rparen + 1) 717 { 718 stateStackPop(); 719 } 720 else 721 { 722 rparen++; 723 } 724 725 closed = true; 726 } 727 else 728 { 729 /* 730 * now, drop a state 731 */ 732 733 if(!stateStackPop()) 734 break; 735 } 736 } 737 } 738} 739 740/* ------------------------------------------------------------------------ 741 * 742 * Tokens 743 * 744 * ------------------------------------------------------------------------- */ 745 746/* The VelocityCharStream will send a zero-width whitespace 747 just before EOF to let us accept a terminal $ or # 748*/ 749<PRE_DIRECTIVE,PRE_REFERENCE,PRE_OLD_REFERENCE> 750TOKEN : 751{ 752 <LONE_SYMBOL: "\u001C" > 753 { 754 stateStackPop(); 755 } 756} 757 758/* In all other states, keep the zero-width whitespace for now */ 759<REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD3,REFINDEX,DIRECTIVE,REFMOD2,DEFAULT,REFMOD,IN_TEXTBLOCK,IN_MULTILINE_COMMENT,IN_FORMAL_COMMENT,IN_SINGLE_LINE_COMMENT> 760TOKEN : 761{ 762 <ZERO_WIDTH_WHITESPACE: "\u001C"> 763} 764 765<REFERENCE, REFMODIFIER, OLD_REFMODIFIER, REFMOD3> 766TOKEN: 767{ 768 <INDEX_LBRACKET: "["> 769 { 770 stateStackPush(); 771 switchTo(REFINDEX); 772 } 773 | 774 /* we need to give precedence to the logical 'or' here, it's a hack to avoid multiplying parsing modes */ 775 <LOGICAL_OR_2: "||"> 776 { 777 stateStackPop(); 778 } 779 | 780 <PIPE: "|"> 781 { 782 if (curlyLevel == 1) 783 { 784 switchTo(ALT_VAL); 785 } 786 else 787 { 788 stateStackPop(); 789 } 790 } 791} 792 793<REFINDEX> 794TOKEN: 795{ 796 <INDEX_RBRACKET: "]"> 797 { 798 stateStackPop(); 799 } 800} 801 802 803<DIRECTIVE,REFMOD2,ALT_VAL> 804TOKEN: 805{ 806 <LBRACKET: "["> 807| <RBRACKET: "]"> 808| <COMMA:","> 809} 810 811<DIRECTIVE,REFMOD2,ALT_VAL> 812TOKEN: 813{ 814 <DOUBLEDOT : ".." > 815} 816 817<DIRECTIVE, REFMOD2,ALT_VAL> 818TOKEN: 819{ 820 <COLON : ":" > 821} 822 823<DIRECTIVE, REFMOD2, ALT_VAL> 824TOKEN : 825{ 826 <LEFT_CURLEY : "{" > 827 { 828 ++curlyLevel; 829 } 830 | 831 <RIGHT_CURLEY : "}" > 832 { 833 --curlyLevel; 834 if (curLexState == ALT_VAL && curlyLevel == 0) 835 { 836 stateStackPop(); 837 } 838 } 839} 840 841<DIRECTIVE,REFMODIFIER,OLD_REFMODIFIER> 842TOKEN: 843{ 844 <LPAREN: "("> 845 { 846 if (!inComment) 847 lparen++; 848 849 /* 850 * If in REFERENCE and we have seen the dot, then move 851 * to REFMOD2 -> Modifier() 852 */ 853 854 if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER ) 855 switchTo( REFMOD2 ); 856 } 857} 858 859/* 860 * we never will see a ')' in anything but DIRECTIVE and REFMOD2. 861 * Each have their own 862 */ 863<DIRECTIVE> 864TOKEN: 865{ 866 <RPAREN: ")"> 867 { 868 RPARENHandler(); 869 } 870} 871 872 873<REFMOD2> 874TOKEN: 875{ 876 /* 877 * in REFMOD2, we don't want to bind the whitespace and \n like we 878 * do when closing a directive. 879 */ 880 <REFMOD2_RPAREN: ")"> 881 { 882 /* 883 * need to simply switch back to REFERENCE, not drop down the stack 884 * because we can (infinitely) chain, ala 885 * $foo.bar().blargh().woogie().doogie() 886 */ 887 888 switchTo( REFMOD3 ); 889 } 890} 891 892/*---------------------------------------------- 893 * 894 * escape "\\" handling for the built-in directives 895 * 896 *--------------------------------------------- */ 897TOKEN: 898{ 899 /* 900 * We have to do this, because we want these to be a Text node, and 901 * whatever follows to be peer to this text in the tree. 902 * 903 * We need to touch the ASTs for these, because we want an even # of \'s 904 * to render properly in front of the block 905 * 906 * This is really simplistic. I actually would prefer to find them in 907 * grammatical context, but I am neither smart nor rested, a receipe 908 * for disaster, another long night with Mr. Parser, or both. 909 */ 910 911 <ESCAPE_DIRECTIVE : (<DOUBLE_ESCAPE>)* "\\${parser.char.hash}" (<WORD> | <BRACKETED_WORD>) > 912} 913 914 915/* 916 * We added the lexical states REFERENCE, REFMODIFIER, REFMOD2 to 917 * address JIRA issue VELOCITY-631. With SET_DIRECTIVE only in the 918 * DEFAULT lexical state the following VTL fails "$a#set($b = 1)" 919 * because the Reference token uses LOOKAHEAD(2) combined with the 920 * fact that we explicity set the lex state to REFERENCE with the $ 921 * token, which means we would never evaluate this token during the 922 * look ahead. This general issue is disscussed here: 923 * 924 * http://www.engr.mun.ca/~theo/JavaCC-FAQ/javacc-faq-ie.htm#tth_sEc3.12 925 * 926 */ 927<DEFAULT, PRE_REFERENCE, PRE_OLD_REFERENCE, REFERENCE, REFMODIFIER, OLD_REFMODIFIER, REFMOD2, REFMOD3> 928TOKEN: 929{ 930 <SET_DIRECTIVE: ("${parser.char.hash}set" | "${parser.char.hash}{set}") (" "|"\t")* "("> 931 { 932 if (! inComment) 933 { 934 trace(" #set : going to DIRECTIVE" ); 935 936 stateStackPush(); 937 setInSet(true); 938 switchTo(DIRECTIVE); 939 } 940 941 /* 942 * need the LPAREN action 943 */ 944 945 if (!inComment) 946 { 947 lparen++; 948 949 /* 950 * If in REFERENCE and we have seen the dot, then move 951 * to REFMOD2 -> Modifier() 952 */ 953 954 if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER ) 955 switchTo( REFMOD2 ); 956 } 957 } 958} 959 960<*> 961MORE : 962{ 963 /* 964 * Note : DOLLARBANG is a duplicate of DOLLAR. They must be identical. 965 */ 966 967 <DOLLAR: ("\\")* "${parser.char.dollar}"> 968 { 969 if (! inComment) 970 { 971 /* 972 * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down 973 * to end the previous ref 974 */ 975 976 if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE) 977 { 978 stateStackPop(); 979 } 980 981 int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE; 982 983 trace( " $ : going to " + lexStateNames[preReferenceState]); 984 985 /* do not push PRE states */ 986 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 987 { 988 stateStackPush(); 989 } 990 switchTo(preReferenceState); 991 } 992 } 993 994| <DOLLARBANG: ("\\")* "${parser.char.dollar}" ("\\")* "!"> 995 { 996 if (! inComment) 997 { 998 /* 999 * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down 1000 * to end the previous ref 1001 */ 1002 1003 if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE) 1004 { 1005 stateStackPop(); 1006 } 1007 1008 int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE; 1009 1010 trace( " $ : going to " + lexStateNames[preReferenceState]); 1011 1012 /* do not push PRE states */ 1013 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 1014 { 1015 stateStackPush(); 1016 } 1017 switchTo(preReferenceState); 1018 } 1019 } 1020 1021| "${parser.char.hash}[[" 1022 { 1023 if (!inComment) 1024 { 1025 inComment = true; 1026 /* do not push PRE states */ 1027 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 1028 { 1029 stateStackPush(); 1030 } 1031 switchTo( IN_TEXTBLOCK ); 1032 } 1033 } 1034 1035| <"${parser.char.hash}${parser.char.asterisk}${parser.char.asterisk}" ~["${parser.char.hash}","\u001C"]> 1036 { 1037 if (!inComment) 1038 { 1039 input_stream.backup(1); 1040 inComment = true; 1041 /* do not push PRE states */ 1042 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 1043 { 1044 stateStackPush(); 1045 } 1046 switchTo( IN_FORMAL_COMMENT); 1047 } 1048 } 1049 1050| "${parser.char.hash}${parser.char.asterisk}" 1051 { 1052 if (!inComment) 1053 { 1054 inComment=true; 1055 /* do not push PRE states */ 1056 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 1057 { 1058 stateStackPush(); 1059 } 1060 switchTo( IN_MULTI_LINE_COMMENT ); 1061 } 1062 } 1063 1064| <HASH : "${parser.char.hash}" > 1065 { 1066 if (! inComment) 1067 { 1068 /* 1069 * We can have the situation where #if($foo)$foo#end. 1070 * We need to transition out of REFERENCE before going to DIRECTIVE. 1071 * I don't really like this, but I can't think of a legal way 1072 * you are going into DIRECTIVE while in REFERENCE. -gmj 1073 */ 1074 1075 if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE || curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER ) 1076 { 1077 stateStackPop(); 1078 } 1079 1080 trace(" # : going to PRE_DIRECTIVE" ); 1081 1082 /* do not push PRE states */ 1083 if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE) 1084 { 1085 stateStackPush(); 1086 } 1087 switchTo(PRE_DIRECTIVE); 1088 } 1089 } 1090} 1091 1092// treat the single line comment case separately 1093// to avoid ##<EOF> errors 1094<DEFAULT,PRE_DIRECTIVE,DIRECTIVE,REFERENCE,PRE_REFERENCE,PRE_OLD_REFERENCE,REFMOD2,REFMOD3,REFMODIFIER,OLD_REFMODIFIER> 1095TOKEN : 1096{ 1097 <SINGLE_LINE_COMMENT_START: "${parser.char.hash}${parser.char.hash}"> 1098 { 1099 if (!inComment) 1100 { 1101 if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE) 1102 { 1103 stateStackPop(); 1104 } 1105 1106 inComment = true; 1107 stateStackPush(); 1108 switchTo(IN_SINGLE_LINE_COMMENT); 1109 } 1110 } 1111} 1112 1113/* ----------------------------------------------------------------------- 1114 * 1115 * *_COMMENT Lexical tokens 1116 * 1117 *-----------------------------------------------------------------------*/ 1118<IN_SINGLE_LINE_COMMENT> 1119TOKEN : 1120{ 1121 <SINGLE_LINE_COMMENT: "\n" | "\r" | "\r\n"> 1122 { 1123 inComment = false; 1124 stateStackPop(); 1125 if (curLexState == REFERENCE || curLexState == REFMOD3) 1126 { 1127 // end of reference: pop again 1128 stateStackPop(); 1129 } 1130 } 1131 1132} 1133 1134<IN_FORMAL_COMMENT> 1135TOKEN : 1136{ 1137 <FORMAL_COMMENT: "${parser.char.asterisk}${parser.char.hash}" > 1138 { 1139 inComment = false; 1140 stateStackPop(); 1141 if (curLexState == REFERENCE || curLexState == REFMOD3) 1142 { 1143 // end of reference: pop again 1144 stateStackPop(); 1145 } 1146 } 1147} 1148 1149<IN_MULTI_LINE_COMMENT> 1150TOKEN : 1151{ 1152 <MULTI_LINE_COMMENT: "${parser.char.asterisk}${parser.char.hash}" > 1153 { 1154 inComment = false; 1155 stateStackPop(); 1156 if (curLexState == REFERENCE || curLexState == REFMOD3) 1157 { 1158 // end of reference: pop again 1159 stateStackPop(); 1160 } 1161 } 1162} 1163 1164<IN_TEXTBLOCK> 1165TOKEN : 1166{ 1167 <TEXTBLOCK: "]]${parser.char.hash}" > 1168 { 1169 inComment = false; 1170 stateStackPop(); 1171 } 1172} 1173 1174<IN_SINGLE_LINE_COMMENT,IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT> 1175SKIP : 1176{ 1177 < ~[] > 1178} 1179 1180<IN_TEXTBLOCK> 1181MORE : 1182{ 1183 < ~["\u001C"] > 1184} 1185 1186/* ----------------------------------------------------------------------- 1187 * 1188 * DIRECTIVE Lexical State (some of it, anyway) 1189 * 1190 * ---------------------------------------------------------------------- */ 1191 1192<DEFAULT,REFINDEX,REFMOD2,DIRECTIVE,ALT_VAL> 1193TOKEN: 1194{ 1195 <WHITESPACE : ([" ","\t"])+> 1196| <NEWLINE : ("\n" | "\r" | "\r\n") > 1197 { 1198 trace(" NEWLINE :"); 1199 1200 /* if (isInSet()) */ 1201 setInSet(false); 1202 } 1203} 1204 1205/* needed for stuff like #foo() followed by ( '$' | '#' )* followed by ( <WHITESPACE> | <ENDLINE> ) 1206 so that directive postfix doesn't eat the '$'s and '#'s 1207*/ 1208<PRE_DIRECTIVE, PRE_REFERENCE, PRE_OLD_REFERENCE> 1209TOKEN: 1210{ 1211 <SUFFIX: ([" ","\t"])* ("\n" | "\r" | "\r\n")> 1212 { 1213 stateStackPop(); 1214 } 1215} 1216 1217<DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL> 1218TOKEN : 1219{ 1220// <STRING_LITERAL: ( "\"" ( ~["\"","\n","\r"] )* "\"" ) | ( "'" ( ~["'","\n","\r"] )* "'" ) > 1221 < STRING_LITERAL: 1222 ("\"" 1223 ( (~["\"","\u001C"]) 1224 | ("\\" 1225 ( ["n","t","b","r","f"] 1226 | ["0"-"7"] ( ["0"-"7"] )? 1227 | ["0"-"3"] ["0"-"7"] ["0"-"7"] 1228 | "u" ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"] 1229 ) 1230 ) 1231 | ("\"\"") 1232 | ( "\\" (" ")* "\n") 1233 )* 1234 "\"" 1235 ) 1236 | 1237 ("\'" 1238 ( (~["\'","\u001C"]) 1239 | ("''") 1240 | ( "\\" (" ")* "\n") 1241 )* 1242 "\'" 1243 ) 1244 > 1245 1246 { 1247 /* 1248 * - if we are in DIRECTIVE and haven't seen ( yet, then also drop out. 1249 * don't forget to account for the beloved yet wierd #set 1250 * - finally, if we are in REFMOD2 (remember : $foo.bar( ) then " is ok! 1251 */ 1252 1253 if( curLexState == DIRECTIVE && !isInSet() && lparen == 0) 1254 stateStackPop(); 1255 } 1256} 1257 1258<REFERENCE,DIRECTIVE,REFMODIFIER,OLD_REFMODIFIER,REFMOD2,REFINDEX,ALT_VAL> 1259TOKEN: 1260{ 1261 <TRUE: "true"> 1262| <FALSE: "false"> 1263} 1264 1265<DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL> 1266TOKEN : 1267{ 1268 <MINUS: "-"> 1269| <PLUS: "+"> 1270| <MULTIPLY: "*"> 1271| <DIVIDE: "/"> 1272| <MODULUS: "%"> 1273| <LOGICAL_AND: "&&" | "and" > 1274| <LOGICAL_OR: "||" | "or" > 1275| <LOGICAL_LT: "<" | "lt" > 1276| <LOGICAL_LE: "<=" | "le" > 1277| <LOGICAL_GT: ">" | "gt" > 1278| <LOGICAL_GE: ">=" | "ge" > 1279| <LOGICAL_EQUALS: "==" | "eq" > 1280| <LOGICAL_NOT_EQUALS: "!=" | "ne" > 1281| <LOGICAL_NOT: "!" | "not" > 1282| <EQUALS: "=" > 1283} 1284 1285<PRE_DIRECTIVE> 1286TOKEN : 1287{ 1288 <END: ( "end" | "{end}" )> 1289 { 1290 stateStackPop(); 1291 } 1292 1293| <IF_DIRECTIVE: "if" | "{if}"> 1294 { 1295 switchTo(DIRECTIVE); 1296 } 1297 1298| <ELSEIF: "elseif" | "{elseif}"> 1299 { 1300 switchTo(DIRECTIVE); 1301 } 1302 1303| <ELSE: "else" | "{else}"> 1304 { 1305 stateStackPop(); 1306 } 1307} 1308 1309<PRE_DIRECTIVE,DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL> 1310TOKEN: 1311{ 1312 <#DIGIT: [ "0"-"9" ] > 1313 1314 /* 1315 * treat FLOATING_POINT_LITERAL and INTEGER_LITERAL differently as a range can only handle integers. 1316 */ 1317 1318 /** 1319 * Note -- we also define an integer as ending with a double period, 1320 * in order to avoid 1..3 being defined as floating point (1.) then a period, then a integer 1321 */ 1322| <INTEGER_LITERAL: ("-")? (<DIGIT>)+ ("..")? > 1323 { 1324 1325 /* 1326 * Remove the double period if it is there 1327 */ 1328 if (matchedToken.image.endsWith("..")) { 1329 input_stream.backup(2); 1330 matchedToken.image = matchedToken.image.substring(0,matchedToken.image.length()-2); 1331 } 1332 1333 /* 1334 * check to see if we are in set 1335 * ex. #set($foo = $foo + 3) 1336 * because we want to handle the \n after 1337 */ 1338 1339 if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != REFINDEX && curLexState != ALT_VAL) 1340 { 1341 stateStackPop(); 1342 } 1343 } 1344 1345| <FLOATING_POINT_LITERAL: 1346 ("-")? (<DIGIT>)+ "." (<DIGIT>)* (<EXPONENT>)? 1347 | ("-")? "." (<DIGIT>)+ (<EXPONENT>)? 1348 | ("-")? (<DIGIT>)+ <EXPONENT> 1349 > 1350 { 1351 /* 1352 * check to see if we are in set 1353 * ex. #set $foo = $foo + 3 1354 * because we want to handle the \n after 1355 */ 1356 1357 if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != ALT_VAL) 1358 { 1359 stateStackPop(); 1360 } 1361} 1362| 1363 <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > 1364 1365} 1366 1367/** 1368 * TODO, the "@" symbol for block macros to be correct really should prefix WORD 1369 * and BRACKETED_WORD, e.g., <WORD ["@"] ( <LETTER... etc... 1370 * However, having the conditional character at the beginning screws up 1371 * Macro parse. As it is now you can have #@1234 defined as a macro 1372 * Which is not correct. 1373 */ 1374 1375<PRE_DIRECTIVE,DIRECTIVE> 1376TOKEN: 1377{ 1378 <#LETTER: [ "a"-"z", "A"-"Z" ] > 1379| <#DIRECTIVE_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_" ] > 1380| <WORD: ( <LETTER> | ["_"] | ["${parser.char.at}"]) (<DIRECTIVE_CHAR>)* > 1381| <BRACKETED_WORD: "{" ( <LETTER> | ["_"] | ["${parser.char.at}"]) (<DIRECTIVE_CHAR>)* "}" > 1382} 1383 1384/* ----------------------------------------------------------------------- 1385 * 1386 * REFERENCE Lexical States 1387 * 1388 * This is more than a single state, because of the structure of 1389 * the VTL references. We use three states because the set of tokens 1390 * for each state can be different. 1391 * 1392 * $foo.bar( "arg" ) 1393 * ^ ^ ^ ^ ^ 1394 * | | | | | 1395 * |_________________ > PRE_REFERENCE : state initiated by the '$' character. 1396 * | | | | (or PRE_OLD_REFERENCE if '-' is allowed in identifiers) 1397 * |________________> REFERENCE : state initiated by the identifier. Continues 1398 * | | | until end of the reference, or the . character. 1399 * |_____________ > REFMODIFIER : state switched to when the <DOT> is encountered. 1400 * | | (or OLD_REFMODIFIER if '-' is allowed in identifiers) 1401 * | | note that this is a switch, not a push. See notes at bottom. 1402 * |_________ > REFMOD2 : state switch to when the LPAREN is encountered. 1403 * | again, this is a switch, not a push. 1404 * |_ > REFMOD3 : state only checking for a possible '.' or '[' continuation. 1405 * 1406 * During the REFERENCE, REFMODIFIER or REFMOD3 lex states we will switch to: 1407 * - REFINDEX if a bracket '[' is encountered: $foo[1], $foo.bar[1], $foo.bar( "arg" )[1] 1408 * - ALT_VAL if a pipe '|' is encountered (only for formal references): ${foo|'foo'} 1409 * ---------------------------------------------------------------------------- */ 1410 1411<PRE_REFERENCE,REFMODIFIER,REFMOD2> 1412TOKEN : 1413{ 1414 <#ALPHA_CHAR: ["a"-"z", "A"-"Z", "_"] > 1415| <#IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_" ] > 1416| <IDENTIFIER: ( <ALPHA_CHAR> ) (<IDENTIFIER_CHAR>)* > 1417 { 1418 if (curLexState == PRE_REFERENCE) 1419 { 1420 switchTo(REFERENCE); 1421 } 1422 } 1423} 1424 1425<PRE_OLD_REFERENCE,OLD_REFMODIFIER> 1426TOKEN : 1427{ 1428 <#OLD_ALPHA_CHAR: ["a"-"z", "A"-"Z", "_"] > 1429| <#OLD_IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_", "-" ] > 1430| <OLD_IDENTIFIER: ( <OLD_ALPHA_CHAR> ) (<OLD_IDENTIFIER_CHAR>)* > 1431 { 1432 if (curLexState == PRE_OLD_REFERENCE) 1433 { 1434 switchTo(REFERENCE); 1435 } 1436 } 1437} 1438 1439 1440<REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD2,REFMOD3> 1441TOKEN: 1442{ 1443 <DOT: "." <ALPHA_CHAR>> 1444 { 1445 /* 1446 * push the alpha char back into the stream so the following identifier 1447 * is complete 1448 */ 1449 1450 input_stream.backup(1); 1451 1452 /* 1453 * and munge the <DOT> so we just get a . when we have normal text that 1454 * looks like a ref.ident 1455 */ 1456 1457 matchedToken.image = "."; 1458 1459 int refModifierState = parser.hyphenAllowedInIdentifiers ? OLD_REFMODIFIER : REFMODIFIER; 1460 1461 trace("DOT : switching to " + lexStateNames[refModifierState]); 1462 switchTo(refModifierState); 1463 1464 } 1465} 1466 1467<PRE_REFERENCE,PRE_OLD_REFERENCE,REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD3> 1468TOKEN : 1469{ 1470 <LCURLY: "{"> 1471 { 1472 ++curlyLevel; 1473 } 1474| <RCURLY: "}"> 1475 { 1476 /* maybe it wasn't for our state */ 1477 while (curlyLevel == 0 && curLexState != DEFAULT) 1478 { 1479 stateStackPop(); 1480 } 1481 /* At this point, here are all the possible states: 1482 * - DEFAULT, which means the '}' is schmoo 1483 * - DIRECTIVE or REFMOD2, which means the '}' is a closing map curly 1484 * - one of the other REFERENCE states or ALT_VAL, which means the '}' ends the reference 1485 * If we're in the last case, pop up state. 1486 */ 1487 if (curLexState != DEFAULT && curLexState != DIRECTIVE && curLexState != REFMOD2) 1488 { 1489 stateStackPop(); 1490 } 1491 } 1492} 1493 1494<PRE_REFERENCE,PRE_OLD_REFERENCE,REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD,REFMOD3> 1495SPECIAL_TOKEN : 1496{ 1497 <REFERENCE_TERMINATOR: ~[] > 1498 { 1499 /* 1500 * push every terminator character back into the stream 1501 */ 1502 1503 input_stream.backup(1); 1504 1505 trace("REF_TERM :"); 1506 1507 stateStackPop(); 1508 } 1509} 1510 1511<PRE_DIRECTIVE> 1512SPECIAL_TOKEN : 1513{ 1514 <DIRECTIVE_TERMINATOR: ~[] > 1515 { 1516 trace("DIRECTIVE_TERM :"); 1517 1518 input_stream.backup(1); 1519 stateStackPop(); 1520 } 1521} 1522 1523/* TEXT must end with a newline, and contain at least one non-whitespace character in the first line, 1524 so that the <WHITESPACE> <NEWLINE> sequence is not read as a TEXT (needed for space gobbling) 1525*/ 1526TOKEN : 1527{ 1528 <DOUBLE_ESCAPE : "\\\\"> 1529| <ESCAPE: "\\" > 1530| <TEXT: (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n", " ", "\t","\u001C"])+ (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* <NEWLINE> ((~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* <NEWLINE>)* > 1531} 1532 1533TOKEN : 1534{ 1535 <INLINE_TEXT: (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])+ > 1536} 1537 1538/** 1539 * This method is what starts the whole parsing 1540 * process. After the parsing is complete and 1541 * the template has been turned into an AST, 1542 * this method returns the root of AST which 1543 * can subsequently be traversed by a visitor 1544 * which implements the ParserVisitor interface 1545 * which is generated automatically by JavaCC 1546 */ 1547SimpleNode process() : 1548{ 1549 boolean afterNewline = true; 1550} 1551{ 1552 ( LOOKAHEAD({ getToken(1).kind != EOF }) afterNewline = Statement(afterNewline) )* <EOF> 1553 { return jjtThis; } 1554} 1555 1556/** 1557 * These are the types of statements that 1558 * are acceptable in Velocity templates. 1559 */ 1560boolean Statement(boolean afterNewline) #void : 1561{ 1562} 1563{ 1564 LOOKAHEAD( { getToken(1).kind == IF_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == IF_DIRECTIVE } ) afterNewline = IfStatement(afterNewline) { return afterNewline; } 1565| LOOKAHEAD(2) Reference() { return false; } 1566| LOOKAHEAD(2) afterNewline = Comment() { return afterNewline; } 1567| Textblock() { return false; } 1568| LOOKAHEAD( { getToken(1).kind == SET_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == SET_DIRECTIVE } ) afterNewline = SetDirective(afterNewline) { return afterNewline; } 1569| EscapedDirective() { return false; } 1570| Escape() { return false; } 1571| LOOKAHEAD( { getToken(1).kind == WORD || getToken(1).kind == BRACKETED_WORD || afterNewline && getToken(1).kind == WHITESPACE && ( getToken(2).kind == WORD || getToken(2).kind == BRACKETED_WORD ) } ) afterNewline = Directive(afterNewline) { return afterNewline; } 1572| afterNewline = Text() { return afterNewline; } 1573| (<NEWLINE>) #Text { return true; } 1574| (((<INLINE_TEXT>) { afterNewline = false; } ) ((<TEXT>) { afterNewline = true; })? ) #Text { return afterNewline; } 1575| (<WHITESPACE>) #Text { return false; } 1576| (<SUFFIX>) #Text { return true; } 1577| LOOKAHEAD(2) EndingZeroWidthWhitespace() { return afterNewline; } 1578| (<LOGICAL_OR_2>) #Text { return afterNewline; } // needed here since it can be triggered in <REFERENCE> mode out of any boolean evaluation 1579| (<ZERO_WIDTH_WHITESPACE>) #Text { afterNewline = !afterNewline; return false; } 1580} 1581 1582void EndingZeroWidthWhitespace() #void : {} 1583{ 1584 <ZERO_WIDTH_WHITESPACE> <EOF> { } 1585} 1586 1587/** 1588 * used to separate the notion of a valid directive that has been 1589 * escaped, versus something that looks like a directive and 1590 * is just schmoo. This is important to do as a separate production 1591 * that creates a node, because we want this, in either case, to stop 1592 * the further parsing of the Directive() tree. 1593 */ 1594void EscapedDirective() : {} 1595{ 1596 { 1597 Token t = null; 1598 } 1599 1600 t = <ESCAPE_DIRECTIVE> 1601 { 1602 /* 1603 * churn and burn.. 1604 */ 1605 t.image = escapedDirective( t.image ); 1606 } 1607} 1608 1609/** 1610 * Used to catch and process escape sequences in grammatical constructs 1611 * as escapes outside of VTL are just characters. Right now we have both 1612 * this and the EscapeDirective() construction because in the EscapeDirective() 1613 * case, we want to suck in the #<directive> and here we don't. We just want 1614 * the escapes to render correctly 1615 */ 1616void Escape() : {} 1617{ 1618 { 1619 Token t = null; 1620 int count = 0; 1621 boolean control = false; 1622 } 1623 1624 ( LOOKAHEAD(2) t = <DOUBLE_ESCAPE> 1625 { 1626 count++; 1627 } 1628 )+ 1629 { 1630 /* 1631 * first, check to see if we have a control directive 1632 */ 1633 switch(t.next.kind ) { 1634 case IF_DIRECTIVE : 1635 case ELSE : 1636 case ELSEIF : 1637 case END : 1638 control = true; 1639 break; 1640 } 1641 1642 /* 1643 * if that failed, lets lookahead to see if we matched a PD or a VM 1644 */ 1645 String nTag = t.next.image.substring(1); 1646 if (strictEscape 1647 || isDirective(nTag) 1648 || macroNames.containsKey(nTag) 1649 || rsvc.isVelocimacro(nTag, currentTemplate)) 1650 { 1651 control = true; 1652 } 1653 1654 jjtThis.val = ""; 1655 1656 for( int i = 0; i < count; i++) 1657 jjtThis.val += ( control ? "\\" : "\\\\"); 1658 } 1659 1660} 1661 1662boolean Comment() : {} 1663{ 1664 <SINGLE_LINE_COMMENT_START> ( <SINGLE_LINE_COMMENT> ) ? { return true; } 1665| <MULTI_LINE_COMMENT> { return false; } 1666| <FORMAL_COMMENT> { return false; } 1667} 1668 1669void Textblock() : {} 1670{ 1671 <TEXTBLOCK> 1672} 1673 1674void FloatingPointLiteral() : {} 1675{ 1676 <FLOATING_POINT_LITERAL> 1677} 1678 1679void IntegerLiteral() : {} 1680{ 1681 <INTEGER_LITERAL> 1682} 1683 1684void StringLiteral() : {} 1685{ 1686 <STRING_LITERAL> 1687} 1688 1689/** 1690 * This method corresponds to variable 1691 * references in Velocity templates. 1692 * The following are examples of variable 1693 * references that may be found in a 1694 * template: 1695 * 1696 * $foo 1697 * $bar 1698 * 1699 */ 1700void Identifier() : {} 1701{ 1702 <IDENTIFIER> | <OLD_IDENTIFIER> 1703} 1704 1705void Word() : {} 1706{ 1707 <WORD> 1708} 1709 1710/** 1711 * Supports the arguments for the Pluggable Directives 1712 */ 1713int DirectiveArg() #void : {} 1714{ 1715 Reference() 1716 { 1717 return ParserTreeConstants.JJTREFERENCE; 1718 } 1719| Word() 1720 { 1721 return ParserTreeConstants.JJTWORD; 1722 } 1723| StringLiteral() 1724 { 1725 return ParserTreeConstants.JJTSTRINGLITERAL; 1726 } 1727 1728| IntegerLiteral() 1729 { 1730 return ParserTreeConstants.JJTINTEGERLITERAL; 1731 } 1732 /* 1733 * Need to put this before the floating point expansion 1734 */ 1735| LOOKAHEAD( <LBRACKET> (<WHITESPACE> | <NEWLINE>)* ( Reference() | IntegerLiteral()) (<WHITESPACE> | <NEWLINE>)* <DOUBLEDOT> ) IntegerRange() 1736 { 1737 return ParserTreeConstants.JJTINTEGERRANGE; 1738 } 1739| FloatingPointLiteral() 1740 { 1741 return ParserTreeConstants.JJTFLOATINGPOINTLITERAL; 1742 } 1743| Map() 1744 { 1745 return ParserTreeConstants.JJTMAP; 1746 } 1747| ObjectArray() 1748 { 1749 return ParserTreeConstants.JJTOBJECTARRAY; 1750 } 1751| True() 1752 { 1753 return ParserTreeConstants.JJTTRUE; 1754 } 1755| False() 1756 { 1757 return ParserTreeConstants.JJTFALSE; 1758 } 1759} 1760 1761void DirectiveAssign() : {} 1762{ 1763 Reference() 1764} 1765 1766 1767/** 1768 * Supports the Pluggable Directives 1769 * #foo( arg+ ) 1770 * @return true if ends with a newline 1771 */ 1772boolean Directive(boolean afterNewline) : 1773{ 1774 Token id = null, t = null, u = null, end = null, _else = null; 1775 int argType; 1776 int argPos = 0; 1777 Directive d; 1778 int directiveType; 1779 boolean isVM = false; 1780 boolean isMacro = false; 1781 ArrayList argtypes = new ArrayList(4); 1782 String blockPrefix = ""; 1783 ASTBlock block = null, elseBlock = null; 1784 boolean hasParentheses = false; 1785 boolean newlineAtStart = afterNewline; 1786} 1787{ 1788 [ 1789 (t = <WHITESPACE>) 1790 { 1791 // only possible if not after new line 1792 jjtThis.setPrefix(t.image); 1793 t = null; 1794 } 1795 ] 1796 /* 1797 * note that if we were escaped, that is now handled by 1798 * EscapedDirective() 1799 */ 1800 ((id = <WORD>) | (id = <BRACKETED_WORD>)) 1801 { 1802 String directiveName; 1803 int p = id.image.lastIndexOf(hash); 1804 if (id.kind == StandardParserConstants.BRACKETED_WORD) 1805 { 1806 directiveName = id.image.substring(p + 2, id.image.length() - 1); 1807 } 1808 else 1809 { 1810 directiveName = id.image.substring(p + 1); 1811 } 1812 1813 d = getDirective(directiveName); 1814 1815 /* 1816 * Velocimacro support : if the directive is macro directive 1817 * then set the flag so after the block parsing, we add the VM 1818 * right then. (So available if used w/in the current template ) 1819 */ 1820 1821 if (directiveName.equals("macro")) 1822 { 1823 isMacro = true; 1824 } 1825 1826 /* 1827 * set the directive name from here. No reason for the thing to know 1828 * about parser tokens 1829 */ 1830 1831 jjtThis.setDirectiveName(directiveName); 1832 1833 if ( d == null) 1834 { 1835 if( directiveName.charAt(0) == at ) 1836 { 1837 // block macro call of type: #@foobar($arg1 $arg2) astBody #end 1838 directiveType = Directive.BLOCK; 1839 } 1840 else 1841 { 1842 /* 1843 * if null, then not a real directive, but maybe a Velocimacro 1844 */ 1845 isVM = rsvc.isVelocimacro(directiveName, currentTemplate); 1846 1847 directiveType = Directive.LINE; 1848 } 1849 } 1850 else 1851 { 1852 directiveType = d.getType(); 1853 } 1854 1855 /* 1856 * now, switch us out of PRE_DIRECTIVE 1857 */ 1858 1859 token_source.switchTo(DIRECTIVE); 1860 argPos = 0; 1861 } 1862 1863 1864 /** 1865 * Look for the pattern [WHITESPACE] <LPAREN> 1866 */ 1867 ( 1868 LOOKAHEAD( { isLeftParenthesis() } ) 1869 /* 1870 * if this is indeed a token, match the #foo ( arg, arg... ) pattern 1871 */ 1872 ( 1873 (<WHITESPACE> | <NEWLINE>)* <LPAREN> 1874 ( 1875 LOOKAHEAD({ !isRightParenthesis() }) (<WHITESPACE> | <NEWLINE>)* [<COMMA> (<WHITESPACE> | <NEWLINE>)*] 1876 ( 1877 [ 1878 LOOKAHEAD( { isMacro && isAssignment() }) 1879 DirectiveAssign() (<WHITESPACE> | <NEWLINE>)* <EQUALS> ( <WHITESPACE> | <NEWLINE> )* 1880 { 1881 argtypes.add(ParserTreeConstants.JJTDIRECTIVEASSIGN); 1882 } 1883 ] 1884 LOOKAHEAD( { !isRightParenthesis() } ) 1885 ( 1886 argType = DirectiveArg() 1887 { 1888 argtypes.add(argType); 1889 if (d == null && argType == ParserTreeConstants.JJTWORD) 1890 { 1891 if (isVM) 1892 { 1893 throw new MacroParseException("Invalid argument " 1894 + (argPos+1) + " in macro call " + id.image, currentTemplate.getName(), id); 1895 } 1896 } 1897 argPos++; 1898 } 1899 ) 1900 | 1901 { 1902 if (!isMacro) 1903 { 1904 // We only allow line comments in macro definitions for now 1905 throw new MacroParseException("A Line comment is not allowed in " + id.image 1906 + " arguments", currentTemplate.getName(), id); 1907 } 1908 } 1909 <SINGLE_LINE_COMMENT_START> [<SINGLE_LINE_COMMENT>] 1910 ) 1911 )* (<WHITESPACE> | <NEWLINE>)* <RPAREN> 1912 { hasParentheses = true; } 1913 ) 1914 | 1915 { 1916 token_source.stateStackPop(); 1917 } 1918 ) 1919 { afterNewline = false; } 1920 [ 1921 // Conditions where whitespace and newline postfix is eaten by space gobbling at this point: 1922 // - block directive 1923 // - new line before directive without backward compatibility mode 1924 // - backward compatibility mode *with parentheses* 1925 // - #include() or #parse() 1926 LOOKAHEAD(2, { directiveType != Directive.LINE || newlineAtStart && rsvc.getSpaceGobbling() != SpaceGobbling.BC || rsvc.getSpaceGobbling() == SpaceGobbling.BC && hasParentheses || d != null && (d instanceof Include || d instanceof Parse) }) 1927 ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 1928 { 1929 afterNewline = true; 1930 if (directiveType == Directive.LINE) 1931 { 1932 jjtThis.setPostfix(t == null ? u.image : t.image + u.image); 1933 } 1934 else 1935 { 1936 blockPrefix = (t == null ? u.image : t.image + u.image); 1937 } 1938 t = u = null; 1939 } 1940 ] 1941 { 1942 if (d != null) 1943 { 1944 d.checkArgs(argtypes, id, currentTemplate.getName()); 1945 } 1946 if (directiveType == Directive.LINE) 1947 { 1948 return afterNewline; 1949 } 1950 } 1951 /* 1952 * and the following block if the PD needs it 1953 */ 1954 ( 1955 ( 1956 ( 1957 LOOKAHEAD( { getToken(1).kind != END && getToken(1).kind != ELSE && ( !afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END && getToken(2).kind != ELSE ) }) afterNewline = Statement(afterNewline) 1958 )* 1959 { 1960 block = jjtThis; 1961 block.setPrefix(blockPrefix); 1962 blockPrefix = ""; 1963 } 1964 ) 1965 #Block 1966 ) 1967 [ 1968 LOOKAHEAD( 1, { afterNewline }) 1969 (t = <WHITESPACE>) 1970 { 1971 block.setPostfix(t.image); 1972 t = null; 1973 } 1974 ] 1975 /* 1976 * then an optional #else for the #foreach directive 1977 */ 1978 ( 1979 [ 1980 LOOKAHEAD( { d != null && (d instanceof Foreach) && getToken(1).kind == ELSE } ) 1981 ( 1982 (_else = <ELSE>) 1983 ( 1984 [ 1985 LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 1986 { 1987 jjtThis.setPrefix(t == null ? u.image : t.image + u.image); 1988 t = u = null; 1989 afterNewline = true; 1990 } 1991 ] 1992 ( 1993 LOOKAHEAD( { getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END) }) 1994 afterNewline = Statement(afterNewline) 1995 )* 1996 { 1997 elseBlock = jjtThis; 1998 } 1999 ) 2000 #Block 2001 { 2002 int pos = _else.image.lastIndexOf(hash); 2003 if (pos > 0) 2004 { 2005 block.setMorePostfix(_else.image.substring(0, pos)); 2006 } 2007 block = elseBlock; 2008 } 2009 ) 2010 ] 2011 ) 2012 [ 2013 LOOKAHEAD( 1, { afterNewline }) 2014 (t = <WHITESPACE>) 2015 { 2016 block.setPostfix(t.image); 2017 t = null; 2018 afterNewline = false; 2019 } 2020 ] 2021 ( 2022 (end = <END>) 2023 { afterNewline = false; } 2024 [ 2025 LOOKAHEAD(2, { newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC }) 2026 ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2027 { 2028 jjtThis.setPostfix(t == null ? u.image : t.image + u.image); 2029 t = u = null; 2030 afterNewline = true; 2031 } 2032 ] 2033 { 2034 int pos = end.image.lastIndexOf(hash); 2035 if (pos > 0) 2036 { 2037 block.setMorePostfix(end.image.substring(0, pos)); 2038 } 2039 } 2040 ) 2041 { 2042 /* 2043 * VM : if we are processing a #macro directive, we need to 2044 * process the block. In truth, I can just register the name 2045 * and do the work later when init-ing. That would work 2046 * as long as things were always defined before use. This way 2047 * we don't have to worry about forward references and such... 2048 */ 2049 if (isMacro) 2050 { 2051 // Add the macro name so that we can peform escape processing 2052 // on defined macros 2053 String macroName = jjtThis.jjtGetChild(0).getFirstToken().image; 2054 macroNames.put(macroName, macroName); 2055 } 2056 if (d != null) 2057 { 2058 d.checkArgs(argtypes, id, currentTemplate.getName()); 2059 } 2060 /* 2061 * VM : end 2062 */ 2063 return afterNewline; 2064 } 2065} 2066 2067/** 2068 * for creating a map in a #set 2069 * 2070 * #set($foo = {$foo : $bar, $blargh : $thingy}) 2071 */ 2072void Map() : {} 2073{ 2074 <LEFT_CURLEY> 2075 ( 2076 LOOKAHEAD(( <WHITESPACE> | <NEWLINE> )* Parameter() <COLON>) ( Parameter() <COLON> Parameter() (<COMMA> Parameter() <COLON> Parameter() )* ) 2077 | 2078 ( <WHITESPACE> | <NEWLINE> )* 2079 ) 2080 2081 /** note: need both tokens as they are generated in different states **/ 2082 ( <RIGHT_CURLEY> | <RCURLY> ) 2083} 2084 2085void ObjectArray() : {} 2086{ 2087 <LBRACKET> [ Parameter() ( <COMMA> Parameter() )* ] <RBRACKET> 2088} 2089 2090 2091/** 2092 * supports the [n..m] vector generator for use in 2093 * the #foreach() to generate measured ranges w/o 2094 * needing explicit support from the app/servlet 2095 */ 2096void IntegerRange() : {} 2097{ 2098 <LBRACKET> (<WHITESPACE> | <NEWLINE>)* 2099 ( Reference() | IntegerLiteral()) 2100 (<WHITESPACE>|<NEWLINE>)* <DOUBLEDOT> (<WHITESPACE>|<NEWLINE>)* 2101 (Reference() | IntegerLiteral()) 2102 (<WHITESPACE>|<NEWLINE>)* <RBRACKET> 2103} 2104 2105 2106/** 2107 * A Simplified parameter more suitable for an index position: $foo[$index] 2108 */ 2109void IndexParameter() #void: {} 2110{ 2111 (<WHITESPACE>|<NEWLINE>)* 2112 ( 2113 Expression() 2114 ) 2115 (<WHITESPACE>|<NEWLINE>)* 2116} 2117 2118 2119/** 2120 * This method has yet to be fully implemented 2121 * but will allow arbitrarily nested method 2122 * calls 2123 */ 2124void Parameter() #void: {} 2125{ 2126 (<WHITESPACE>|<NEWLINE>)* 2127 ( 2128 StringLiteral() 2129 | IntegerLiteral() 2130 | LOOKAHEAD( <LBRACKET> ( <WHITESPACE> | <NEWLINE> )* ( Reference() | IntegerLiteral()) ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) IntegerRange() 2131 | Map() 2132 | ObjectArray() 2133 | True() 2134 | False() 2135 | Reference() 2136 | FloatingPointLiteral() 2137 ) 2138 (<WHITESPACE>|<NEWLINE>)* 2139} 2140 2141/** 2142 * This method has yet to be fully implemented 2143 * but will allow arbitrarily nested method 2144 * calls 2145 */ 2146void Method() : {} 2147{ 2148 Identifier() <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <REFMOD2_RPAREN> 2149} 2150 2151 2152void Index() : {} 2153{ 2154 <INDEX_LBRACKET> IndexParameter() <INDEX_RBRACKET> 2155} 2156 2157void Reference() : {} 2158{ 2159 /* 2160 * A reference is either $<FOO> or ${<FOO>} or ${<FOO>'|'<ALTERNATE_VALUE>) 2161 */ 2162 2163 ( 2164 ( <IDENTIFIER> | <OLD_IDENTIFIER> ) (Index())* 2165 (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )* 2166 ) 2167 | 2168 ( 2169 <LCURLY> 2170 ( <IDENTIFIER> | <OLD_IDENTIFIER> ) (Index())* 2171 (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )* 2172 [ <PIPE> Expression() ] 2173 ( <RCURLY> | <RIGHT_CURLEY> ) 2174 ) 2175} 2176 2177void True() : {} 2178{ 2179 <TRUE> 2180} 2181 2182void False() : {} 2183{ 2184 <FALSE> 2185} 2186 2187 2188/** 2189 * This is somewhat of a kludge, the problem is that the parser picks 2190 * up on '$[' , or '$![' as being a Reference, and does not dismiss it even though 2191 * there is no <Identifier> between $ and [, This has something to do 2192 * with the LOOKAHEAD in Reference, but I never found a way to resolve 2193 * it in a more fashionable way.. 2194 */ 2195<DEFAULT,PRE_REFERENCE,PRE_OLD_REFERENCE> 2196TOKEN : 2197{ 2198 <EMPTY_INDEX : ("$[" | "$![" | "$\\![" | "$.")> 2199} 2200 2201 2202/** 2203 * This method is responsible for allowing 2204 * all non-grammar text to pass through 2205 * unscathed. 2206 * @return true if last read token was a newline 2207 */ 2208boolean Text() : 2209{ 2210 Token t = null; 2211} 2212{ 2213 <TEXT> { return true; } 2214 | <DOT> { return false; } 2215 | <RPAREN> { return false; } 2216 | <LPAREN> { return false; } 2217 | <INTEGER_LITERAL> { return false; } 2218 | <FLOATING_POINT_LITERAL> { return false; } 2219 | <STRING_LITERAL> { return false; } 2220 | <ESCAPE> { return false; } 2221 | <LCURLY> { return false; } 2222 | <RCURLY> { return false; } 2223 | <EMPTY_INDEX> { return false; } 2224 | <PIPE> { return false; } 2225 | t=<LONE_SYMBOL> 2226 { 2227 /* Drop the ending zero-width whitespace */ 2228 t.image = t.image.substring(0, t.image.length() - 1); return false; 2229 } 2230} 2231 2232/* ----------------------------------------------------------------------- 2233 * 2234 * Defined Directive Syntax 2235 * 2236 * ----------------------------------------------------------------------*/ 2237 2238boolean IfStatement(boolean afterNewline) : 2239{ 2240 Token t = null, u = null, end = null; 2241 ASTBlock lastBlock = null; 2242 boolean newlineAtStart = afterNewline; 2243} 2244{ 2245 [ ( t = <WHITESPACE> ) 2246 { 2247 // only possible if not after new line 2248 jjtThis.setPrefix(t.image); 2249 t = null; 2250 } 2251 ] 2252 <IF_DIRECTIVE> ( <WHITESPACE> | <NEWLINE> )* <LPAREN> Expression() <RPAREN> 2253 ( 2254 [ 2255 LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2256 { 2257 jjtThis.setPrefix(t == null ? u.image : t.image + u.image); 2258 t = u = null; 2259 afterNewline = true; 2260 } 2261 ] 2262 ( LOOKAHEAD( 2263 { 2264 (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) && 2265 (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END)) 2266 }) 2267 afterNewline = Statement(afterNewline) )* 2268 { 2269 lastBlock = jjtThis; 2270 } 2271 ) #Block 2272 [ LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) }) 2273 ( LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) }) ( lastBlock = ElseIfStatement(lastBlock, afterNewline) { afterNewline = lastBlock.endsWithNewline; } ))+ ] 2274 [ LOOKAHEAD( { getToken(1).kind == ELSE || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSE) } ) lastBlock = ElseStatement(lastBlock, afterNewline) { afterNewline = lastBlock.endsWithNewline; } ] 2275 [ LOOKAHEAD( 1, { afterNewline } ) ( t = <WHITESPACE> ) 2276 { 2277 lastBlock.setPostfix(t.image); 2278 t = null; 2279 } 2280 ] 2281 (end = <END>) 2282 { afterNewline = false; } 2283 [ 2284 LOOKAHEAD(2, { newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC } ) 2285 ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2286 { 2287 jjtThis.setPostfix(t == null ? u.image : t.image + u.image); 2288 afterNewline = true; 2289 } 2290 ] 2291 { 2292 int pos = end.image.lastIndexOf(hash); 2293 if (pos > 0) 2294 { 2295 lastBlock.setMorePostfix(end.image.substring(0, pos)); 2296 } 2297 return afterNewline; 2298 } 2299} 2300 2301ASTBlock ElseStatement(ASTBlock previousBlock, boolean afterNewline) : 2302{ 2303 Token t = null, u = null, _else = null; 2304 ASTBlock block = null; 2305} 2306{ 2307 [ ( t = <WHITESPACE> ) 2308 { 2309 previousBlock.setPostfix(t.image); 2310 t = null; 2311 } 2312 ] 2313 (_else = <ELSE>) 2314 ( 2315 [ 2316 LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2317 { 2318 jjtThis.setPrefix(t == null ? u.image : t.image + u.image); 2319 t = u = null; 2320 afterNewline = true; 2321 } 2322 ] 2323 ( LOOKAHEAD( { getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END) }) afterNewline = Statement(afterNewline) )* 2324 { 2325 block = jjtThis; 2326 block.endsWithNewline = afterNewline; 2327 } 2328 ) 2329 #Block 2330 { 2331 int pos = _else.image.lastIndexOf(hash); 2332 if (pos > 0) 2333 { 2334 previousBlock.setMorePostfix(_else.image.substring(0, pos)); 2335 } 2336 return block; 2337 } 2338} 2339 2340ASTBlock ElseIfStatement(ASTBlock previousBlock, boolean afterNewline) : 2341{ 2342 Token t = null, u = null, elseif = null; 2343 ASTBlock block = null; 2344} 2345{ 2346 [ ( t = <WHITESPACE> ) 2347 { 2348 previousBlock.setPostfix(t.image); 2349 t = null; 2350 } 2351 ] 2352 (elseif = <ELSEIF>) ( <WHITESPACE> | <NEWLINE> )* 2353 <LPAREN> Expression() <RPAREN> 2354 ( 2355 [ 2356 LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2357 { 2358 jjtThis.setPrefix(t == null ? u.image : t.image + u.image); 2359 t = u = null; 2360 afterNewline = true; 2361 } 2362 ] 2363 ( LOOKAHEAD( { (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) && (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END)) }) afterNewline = Statement(afterNewline) )* 2364 { 2365 block = jjtThis; 2366 block.endsWithNewline = afterNewline; 2367 } 2368 ) 2369 #Block 2370 { 2371 int pos = elseif.image.lastIndexOf(hash); 2372 if (pos > 0) 2373 { 2374 previousBlock.setMorePostfix(elseif.image.substring(0, pos)); 2375 } 2376 return block; 2377 } 2378} 2379 2380/** 2381 * Currently support both types of set : 2382 * #set( expr ) 2383 * #set expr 2384 */ 2385boolean SetDirective(boolean afterNewline) : 2386{ 2387 Token t = null, u = null; 2388 boolean endsWithNewline = false; 2389} 2390{ 2391 [ ( t = <WHITESPACE> ) 2392 { 2393 // only possible after new line 2394 jjtThis.setPrefix(t.image); 2395 t = null; 2396 } 2397 ] 2398 <SET_DIRECTIVE>(( <WHITESPACE> | <NEWLINE> )* Reference() ( <WHITESPACE> | <NEWLINE> )* <EQUALS> Expression() <RPAREN> 2399 { 2400 /* 2401 * ensure that inSet is false. Leads to some amusing bugs... 2402 */ 2403 2404 token_source.setInSet(false); 2405 } 2406 [ 2407 LOOKAHEAD(2, { afterNewline || rsvc.getSpaceGobbling() == SpaceGobbling.BC } ) 2408 ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) ) 2409 { 2410 jjtThis.setPostfix(t == null ? u.image : t.image + u.image); 2411 endsWithNewline = true; 2412 } 2413 ] ) 2414 { 2415 return endsWithNewline; 2416 } 2417} 2418 2419/* ----------------------------------------------------------------------- 2420 * 2421 * Expression Syntax 2422 * 2423 * ----------------------------------------------------------------------*/ 2424 2425void Expression() : {} 2426{ 2427// LOOKAHEAD( PrimaryExpression() <EQUALS> ) Assignment() 2428//| 2429ConditionalOrExpression() 2430} 2431 2432void Assignment() #Assignment(2) : {} 2433{ 2434 PrimaryExpression() <EQUALS> Expression() 2435} 2436 2437void ConditionalOrExpression() #void : {} 2438{ 2439 ConditionalAndExpression() 2440 ( ( <LOGICAL_OR> | <LOGICAL_OR_2> ) ConditionalAndExpression() #OrNode(2) )* 2441} 2442 2443 2444void ConditionalAndExpression() #void : {} 2445{ 2446 EqualityExpression() 2447 ( <LOGICAL_AND> EqualityExpression() #AndNode(2) )* 2448} 2449 2450void EqualityExpression() #void : {} 2451{ 2452 RelationalExpression() 2453 ( 2454 <LOGICAL_EQUALS> RelationalExpression() #EQNode(2) 2455 | <LOGICAL_NOT_EQUALS> RelationalExpression() #NENode(2) 2456 )* 2457} 2458 2459void RelationalExpression() #void : {} 2460{ 2461 AdditiveExpression() 2462 ( 2463 <LOGICAL_LT> AdditiveExpression() #LTNode(2) 2464 | <LOGICAL_GT> AdditiveExpression() #GTNode(2) 2465 | <LOGICAL_LE> AdditiveExpression() #LENode(2) 2466 | <LOGICAL_GE> AdditiveExpression() #GENode(2) 2467 )* 2468} 2469 2470void AdditiveExpression() #void : {} 2471{ 2472 MultiplicativeExpression() 2473 ( 2474 <PLUS> MultiplicativeExpression() #AddNode(2) 2475 | <MINUS> MultiplicativeExpression() #SubtractNode(2) 2476 )* 2477} 2478 2479void MultiplicativeExpression() #void : {} 2480{ 2481 UnaryExpression() 2482 ( 2483 <MULTIPLY> UnaryExpression() #MulNode(2) 2484 | <DIVIDE> UnaryExpression() #DivNode(2) 2485 | <MODULUS> UnaryExpression() #ModNode(2) 2486 )* 2487} 2488 2489void UnaryExpression() #void : {} 2490{ 2491 ( <WHITESPACE> | <NEWLINE> )* 2492 ( 2493 <LOGICAL_NOT> UnaryExpression() #NotNode(1) 2494 | <MINUS> PrimaryExpression() #NegateNode(1) 2495 | PrimaryExpression() 2496 ) 2497} 2498 2499void PrimaryExpression() #void : {} 2500{ 2501 ( <WHITESPACE> | <NEWLINE> )* 2502 ( 2503 StringLiteral() 2504 | Reference() 2505 | IntegerLiteral() 2506 | LOOKAHEAD( <LBRACKET> ( <WHITESPACE> | <NEWLINE> )* ( Reference() | IntegerLiteral()) ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) IntegerRange() 2507 | FloatingPointLiteral() 2508 | Map() 2509 | ObjectArray() 2510 | True() 2511 | False() 2512 | <LPAREN> Expression() <RPAREN> 2513 ) 2514 ( <WHITESPACE> | <NEWLINE> )* 2515} 2516 2517/* ====================================================================== 2518 2519 Notes 2520 ----- 2521 2522 template == the input stream for this parser, contains 'VTL' 2523 mixed in with 'schmoo' 2524 2525 VTL == Velocity Template Language : the references, directives, etc 2526 2527 schmoo == the non-VTL component of a template 2528 2529 reference == VTL entity that represents data within the context. ex. $foo 2530 2531 directive == VTL entity that denotes 'action' (#set, #foreach, #if ) 2532 2533 defined directive (DD) == VTL directive entity that is expressed 2534 explicitly w/in this grammar 2535 2536 pluggable directive (PD) == VTL directive entity that is defined outside of the 2537 grammar. PD's allow VTL to be easily expandable w/o parser modification. 2538 2539 The problem with parsing VTL is that an input stream consists generally of 2540 little bits of VTL mixed in with 'other stuff, referred to as 'schmoo'. 2541 Unlike other languages, like C or Java, where the parser can punt whenever 2542 it encounters input that doesn't conform to the grammar, the VTL parser can't do 2543 that. It must simply output the schmoo and keep going. 2544 2545 There are a few things that we do here : 2546 - define a set of parser states (DEFAULT, DIRECTIVE, REFERENCE, etc) 2547 - define for each parser state a set of tokens for each state 2548 - define the VTL grammar, expressed (mostly) in the productions such as Text(), 2549 SetStatement(), etc. 2550 2551 It is clear that this expression of the VTL grammar (the contents 2552 of this .jjt file) is maturing and evolving as we learn more about 2553 how to parse VTL ( and as I learn about parsing...), so in the event 2554 this documentation is in disagreement w/ the source, the source 2555 takes precedence. :) 2556 2557 Parser States 2558 ------------- 2559 DEFAULT : This is the base or starting state, and strangely enough, the 2560 default state. 2561 2562 PRE_DIRECTIVE : State immediately following '#' before we figure out which 2563 defined or pluggable directive (or neither) we are working with. 2564 2565 DIRECTIVE : This state is triggered by the a match of a DD or a PD. 2566 2567 PRE_REFERENCE : Triggered by '$'. Analagous to PRE_DIRECTIVE. When '-' is 2568 allowed in identifiers, this state is called PRE_OLD_REFERENCE. 2569 2570 REFERENCE : Triggered by the <IDENTIFIER> 2571 2572 REFMODIFIER : Triggered by .<alpha> when in REFERENCE, REFMODIFIER or REFMOD3. When '-' 2573 is allowed in identifiers, this state is called OLD_REFMODIFIER. 2574 2575 REFMOD2 : Triggered by '(' when in REFMODIFIER 2576 2577 REFMOD3 : Triggered by the corresponding ')' 2578 2579 REFINDEX : Array index. Triggered by '[' in REFERENCE, REFMODIFIER, REFMOD3. 2580 2581 ALT_VAL : Alternate value. Triggered by '|' in REFERENCE, REFMODIFIER, REFMOD3. 2582 2583 (cont) 2584 2585 Escape Sequences 2586 ---------------- 2587 The escape processing in VTL is very simple. The '\' character acts 2588 only as an escape when : 2589 2590 1) On or more touch a VTL element. 2591 2592 A VTL element is either : 2593 2594 1) It preceeds a reference that is in the context. 2595 2596 2) It preceeds a defined directive (#set, #if, #end, etc) or a valid 2597 pluggable directive, such as #foreach 2598 2599 In all other cases the '\' is just another piece of text. The purpose of this 2600 is to allow the non-VTL parts of a template (the 'schmoo') to not have to be 2601 altered for processing by Velocity. 2602 2603 So if in the context $foo and $bar were defined and $woogie was not 2604 2605 \$foo \$bar \$woogie 2606 2607 would output 2608 2609 $foo $bar \$woogie 2610 2611 Further, you can stack them and they affect left to right, just like convention 2612 escape characters in other languages. 2613 2614 \$foo = $foo 2615 \\$foo = \<foo> 2616 \\\$foo = \$foo 2617 2618 2619 What You Expect 2620 --------------- 2621 The recent versions of the parser are trying to support precise output to 2622 support general template use. The directives do not render trailing 2623 whitespace and newlines if followed by a newline. They will render 2624 preceeding whitespace. The only exception is #set, which also eats 2625 preceeding whitespace. 2626 2627 So, with a template : 2628 2629 ------ 2630 #set($foo="foo") 2631 #if($foo) 2632 \$foo = $foo 2633 #end 2634 ------ 2635 2636 it will render precisely : 2637 2638 ------ 2639 $foo = foo 2640 ------ 2641 2642*/ 2643