blob: 105a060943c256d4c3e830947779cdadf6acddee [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
/*
* NOTE : please see documentation at bottom of this file. (It was placed there its tiring
* to always have to page past it... :)
*/
options
{
/** The default package for this parser kit */
NODE_PACKAGE="org.apache.velocity.runtime.parser";
/** A source file will be generated for each non-terminal */
MULTI=true;
/**
* Each node will have access to the parser, I did this so
* some global information can be shared via the parser. I
* think this will come in handly keeping track of
* context, and being able to push changes back into
* the context when nodes make modifications to the
* context by setting properties, variables and
* what not.
*/
NODE_USES_PARSER=true;
/**
* The parser must be non-static in order for the
* above option to work, otherwise the parser value
* is passed in as null, which isn't all the useful ;)
*/
STATIC=false;
/**
* Enables the use of a visitor that each of nodes
* will accept. This way we can separate the logic
* of node processing in a visitor and out of the
* nodes themselves. If processing changes then
* the nothing has to change in the node code.
*/
VISITOR=true;
/**
* This option is used as one of the steps
* required to allow the use of an "#include"
* type behaviour. In this case the directive
* is "#parse". See the TOKEN_MGR_DECLS section
* below for details on how the TokenManager is
* modified to allow this behaviour.
*/
COMMON_TOKEN_ACTION=true;
DEBUG_PARSER=false;
DEBUG_TOKEN_MANAGER=false;
}
PARSER_BEGIN(Parser)
package org.apache.velocity.runtime.parser;
import java.io.*;
import java.util.*;
import org.apache.velocity.runtime.Runtime;
import org.apache.velocity.runtime.parser.node.*;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.util.StringUtils;
/**
* This class is responsible for parsing a Velocity
* template. This class was generated by JavaCC using
* the JJTree extension to produce an Abstract
* Syntax Tree (AST) of the template.
*
* Please look at the Parser.jjt file which is
* what controls the generation of this class.
*
* @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
* @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
* @version $Id: Parser.jjt,v 1.33 2000/11/22 02:17:25 geirm Exp $
*/
public class Parser
{
/**
* This Hashtable contains a list of all of the dynamic directives.
*/
private Hashtable directives = new Hashtable(0);
/** This was added to allow the parser to be associated
* with a particular syntax. JavaCC doesn't generate
* a constructor without parameters. The normal constructor
* takes a single argument which an InputStream. But in
* order to make the parser dynamically loadable this
* constructor had to be added. This also allows us to
* create a single instance of a parser and reuse
* it over and over.
*/
public Parser()
{
this(new ByteArrayInputStream("\n".getBytes()));
}
/** This was also added to allow parsers to be dynamically
* loadable.
*
* Taken from the generated constructor in Parser.java.
* Just be watchful when you change the grammar because
* the generated method changes when the grammar changes
* WRT to adding new token types. So you have to
* occasionally do some cutting and pasting :-)
*
* It would be A LOT better it you could subclass grammars
* and override particular methods but that's not
* possible with JavaCC. I believe that you can do
* this with ANTLR though.
*/
public SimpleNode parse(InputStream stream) throws ParseException
{
SimpleNode sn = null;
try
{
token_source.clearStateVars();
ReInit(stream);
sn = process();
}
catch (ParseException pe)
{
Runtime.error ("Parser Exception: " + StringUtils.stackTrace(pe));
throw new ParseException (pe.currentToken,
pe.expectedTokenSequences, pe.tokenImage);
}
catch (Exception e)
{
Runtime.error ("Parser Error: " + StringUtils.stackTrace(e));
}
return sn;
}
/**
* This method sets the directives Hashtable
*/
public void setDirectives(Hashtable directives)
{
this.directives = directives;
}
/**
* This method gets the directives Hashtable
*/
public Directive getDirective(String directive)
{
return (Directive) directives.get(directive);
}
/**
* This method finds out of the directive exists in the directives
* Hashtable.
*/
public boolean isDirective(String directive)
{
if (directives.containsKey(directive))
return true;
else
return false;
}
/**
* Produces a processed output for an escaped control or pluggable directive
*/
private String escapedDirective( String strImage )
{
int iLast = strImage.lastIndexOf("\\");
String strDirective = strImage.substring(iLast + 1);
boolean bRecognizedDirective = false;
/*
* is this a PD or a control directive?
*/
if ( isDirective( strDirective.substring(1)))
{
bRecognizedDirective = true;
}
else if (Runtime.isVelocimacro( strDirective.substring(1)))
{
bRecognizedDirective = true;
}
else
{
/* order for speed? */
if ( strDirective.substring(1).equals("if")
|| strDirective.substring(1).equals("end")
|| strDirective.substring(1).equals("set")
|| strDirective.substring(1).equals("else")
|| strDirective.substring(1).equals("elseif")
|| strDirective.substring(1).equals("stop")
)
{
bRecognizedDirective = true;
}
}
/*
* if so, make the proper prefix string (let the escapes do their thing..)
* otherwise, just return what it is..
*/
if (bRecognizedDirective)
return ( strImage.substring(0,iLast/2) + strDirective);
else
return ( strImage );
}
}
PARSER_END(Parser)
/**
* This gets inserted into the ParserMacroTokenManager
* and is being used here strictly for the #parse
* directive: an #include type behaviour. We have
* to save the state the stream currently being
* parsed and we have to save the state of the
* lexer (TokenManager class) then we create
* a new stream from the file named in the
* #parse directive then we ReInit the lexer.
* Whatever it parses will get placed
* into the AST.
*
* I need a simple way to detect circular
* inclusions so this thing doesn't go wild
* and drag down the VM.
*/
TOKEN_MGR_DECLS:
{
private int fileDepth = 0;
private int lparen = 0;
private int rparen = 0;
Stack stateStack = new Stack();
public boolean bDebugPrint_ = false;
private boolean inReference;
public boolean inDirective;
private boolean inComment;
private boolean inSet;
Stack streams = new Stack();
Stack states = new Stack();
/**
* Retrieve the oldStream and oldState and
* continue processing the input.
*/
void popFile()
{
ReInit((ASCII_CharStream) streams.pop(), ((Integer) states.pop()).intValue());
fileDepth--;
}
private boolean AtParent()
{
if (fileDepth == 0)
return true;
else
return false;
}
void CommonTokenAction(Token t)
{
if (t.kind == EOF && ! AtParent())
{
Token new_t;
popFile();
new_t = getNextToken();
t.kind = new_t.kind;
t.beginLine = new_t.beginLine;
t.beginColumn = new_t.beginColumn;
t.endLine = new_t.endLine;
t.endColumn = new_t.endColumn;
t.image = new_t.image;
t.next = new_t.next;
t.specialToken = new_t.specialToken;
}
}
/**
* pushes the current state onto the 'state stack',
* and maintains the parens counts
* public because we need it in PD & VM handling
*
* @return boolean : success. It can fail if the state machine
* gets messed up.
*/
public boolean stateStackPop()
{
Hashtable hStack;
try
{
hStack = (Hashtable) stateStack.pop();
}
catch( EmptyStackException e)
{
lparen=0;
SwitchTo(DEFAULT);
return false;
}
if( bDebugPrint_ )
System.out.println(" stack pop (" + stateStack.size() + ") : lparen=" + ( (Integer) hStack.get("lparen")).intValue()
+ " newstate=" + ( (Integer) hStack.get("lexstate")).intValue() );
lparen = ( (Integer) hStack.get("lparen")).intValue();
rparen = ( (Integer) hStack.get("rparen")).intValue();
SwitchTo( ( (Integer) hStack.get("lexstate")).intValue() );
return true;
}
/**
* pops a state off the stack, and restores paren counts
*
* @return boolean : success of operation
*/
public boolean stateStackPush()
{
if( bDebugPrint_ )
System.out.println(" (" + stateStack.size() + ") pushing cur state : " + curLexState );
Hashtable hStack = new Hashtable();
hStack.put("lexstate", new Integer( curLexState ) );
hStack.put("lparen", new Integer( lparen ));
hStack.put("rparen", new Integer( rparen ));
lparen = 0;
stateStack.push( hStack );
return true;
}
/**
* Clears all state variables, resets to
* start values, clears stateStack. Call
* before parsing.
* @return void
*/
public void clearStateVars()
{
stateStack.clear();
lparen = 0;
rparen = 0;
inReference = false;
inDirective = false;
inComment = false;
inSet = false;
return;
}
/**
* handles the dropdown logic when encountering a RPAREN
*/
private void RPARENHandler()
{
/*
* Ultimately, we want to drop down to the state below the one that has an open (
* if we hit bottom (DEFAULT), that's fine. It's just text schmoo.
*
*/
boolean bClosed = false;
if (inComment)
bClosed = true;
while( !bClosed)
{
/*
* look at current state. If we haven't seen a lparen in this state
* then we drop a state, because this lparen clearly closes our state
*/
if( lparen > 0)
{
/*
* if rparen + 1 == lparen, then this state is closed. Otherwise, increment
* and keep parsing
*/
if( lparen == rparen + 1)
{
stateStackPop();
}
else
{
rparen++;
}
bClosed = true;
}
else
{
/*
* now, drop a state
*/
if(!stateStackPop())
break;
}
}
}
}
/* ------------------------------------------------------------------------
*
* Tokens
*
* Note : we now have another state, REFMODIFIER. This is sort of a
* type of REFERENCE state, simply use to use the DIRECTIVE token
* set when we are processing a $foo.bar() construct
*
* ------------------------------------------------------------------------- */
<DIRECTIVE,REFMODIFIER,REFMOD2>
TOKEN:
{
<LBRACKET: "[">
| <RBRACKET: "]">
}
<DIRECTIVE,REFMOD2>
TOKEN:
{
<COMMA:",">
}
<DIRECTIVE,REFMODIFIER>
TOKEN:
{
<LPAREN: "(">
{
if (!inComment)
lparen++;
/*
* if in REFERENCE and we have seen the dot, then move to REFMOD2 -> Modifier()
*/
if (curLexState == REFMODIFIER )
SwitchTo( REFMOD2 );
}
}
/*
* we never will see a ')' in anything but DIRECTIVE and REFMOD2. Each have their own
*/
<DIRECTIVE>
TOKEN:
{
/*
* We will eat any whitespace upto and including a newline for directives
*/
<RPAREN: ")" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ))?>
{
RPARENHandler();
}
}
<REFMOD2>
TOKEN:
{
/*
* in REFMOD2, we don't want to bind the whitespace and \n like we do when closing a directive.
*/
<REFMOD2_RPAREN: ")">
{
/*
* need to simply switch back to REFERENCE, not drop down the stack
* because we can (infinitely) chain, ala $foo.bar().blargh().woogie().doogie()
*/
SwitchTo( REFERENCE );
}
}
/*----------------------------------------------
*
* escape "\\" handling for the built-in directives
*
*--------------------------------------------- */
TOKEN:
{
/*
* We have to do this, because we want these to be a Text node, and
* whatever follows to be peer to this text in the tree.
*
* We need to touch the ASTs for these, because we want an even # of \'s
* to render properly in front of the block
*
* This is really simplistic. I actually would prefer to find them in
* grammatical context, but I am neither smart nor rested, a receipe
* for disaster, another long night with Mr. Parser, or both.
*/
<ESCAPE_DIRECTIVE : (<DOUBLE_ESCAPE>)* "\\#" <WORD> >
}
/*
* needed because #set is so wacky in it's desired behavior. We want set to eat any preceeding whitespace
* so it is invisible in formatting. (As it should be.) If this works well, I am going to chuck the whole MORE:
* token abomination.
*/
TOKEN:
{
<SET_DIRECTIVE: (" "|"\t")* "#set" >
{
if (! inComment)
{
inDirective = true;
if ( bDebugPrint_ )
System.out.print("#set : going to " + DIRECTIVE );
stateStackPush();
inSet = true;
SwitchTo(DIRECTIVE);
}
}
}
<*>
MORE :
{
/*
* Note : DOLLARBANG is a duplicate of DOLLAR. They must be identical.
*/
<DOLLAR: ("\\")* "$">
{
if (! inComment)
{
inReference = true;
if ( bDebugPrint_ )
System.out.print( "$ : going to " + REFERENCE );
stateStackPush();
SwitchTo(REFERENCE);
}
}
| <DOLLARBANG: "$!">
{
if (! inComment)
{
inReference = true;
if ( bDebugPrint_ )
System.out.print( "$! : going to " + REFERENCE );
stateStackPush();
SwitchTo(REFERENCE);
}
}
| "##"
{
if (!inComment)
{
inComment = true;
stateStackPush();
SwitchTo(IN_SINGLE_LINE_COMMENT);
}
}
| <"#**" ~["#"]>
{
input_stream.backup(1);
inComment = true;
stateStackPush();
SwitchTo( IN_FORMAL_COMMENT);
}
| "#*"
{
inComment=true;
stateStackPush();
SwitchTo( IN_MULTI_LINE_COMMENT );
}
| <HASH : "#" >
{
if (! inComment)
{
/*
* We can have the situation where #if($foo)$foo#end. We need to transition out of
* REFERENCE before going to DIRECTIVE. I don't really like this, but I can't think of
* a legal way you are going into DIRECTIVE while in REFERENCE. -gmj
*/
if (curLexState == REFERENCE)
{
inReference = false;
stateStackPop();
}
inDirective = true;
if ( bDebugPrint_ )
System.out.print("# : going to " + DIRECTIVE );
stateStackPush();
SwitchTo(PRE_DIRECTIVE);
}
}
}
TOKEN :
{
<DOUBLE_ESCAPE : "\\\\">
| <ESCAPE: "\\" >
| <TEXT: (~["$", "#", "\\"])+ >
}
/* -----------------------------------------------------------------------
*
* *_COMMENT Lexical tokens
*
*-----------------------------------------------------------------------*/
<IN_SINGLE_LINE_COMMENT>
TOKEN :
{
<SINGLE_LINE_COMMENT: "\n" | "\r" | "\r\n" >
{
inComment = false;
stateStackPop();
}
}
<IN_FORMAL_COMMENT>
TOKEN :
{
<FORMAL_COMMENT: "*#" >
{
inComment = false;
stateStackPop();
}
}
<IN_MULTI_LINE_COMMENT>
TOKEN :
{
<MULTI_LINE_COMMENT: "*#" >
{
inComment = false;
stateStackPop();
}
}
<IN_SINGLE_LINE_COMMENT,IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
MORE :
{
< ~[] >
}
/* -----------------------------------------------------------------------
*
* DIRECTIVE Lexical State (some of it, anyway)
*
* ---------------------------------------------------------------------- */
<DIRECTIVE,REFMOD2>
TOKEN:
{
<WHITESPACE : ([" ","\t"])+ >
}
<REFERENCE,DIRECTIVE,REFMODIFIER,REFMOD2>
TOKEN :
{
<STRING_LITERAL: ( "\"" ( ~["\"","\n","\r"] )* "\"" ) >
{
/*
* - if we are in REFERENCE || REFMODIFIER then " is an ender
* - if we are in DIRECTIVE and haven't seen ( yet, then also drop out.
* don't forget to account for the beloved yet wierd #set
* - finally, if we are in REFMOD2 (remember : $foo.bar( ) then " is ok!
*/
if (curLexState == REFERENCE || curLexState == REFMODIFIER)
stateStackPop();
else if( curLexState == DIRECTIVE && !inSet && lparen == 0)
stateStackPop();
}
| <TRUE: "true">
| <FALSE: "false">
}
<DIRECTIVE>
TOKEN :
{
<NEWLINE: "\n" | "\r" | "\r\n" >
{
if ( bDebugPrint_ )
System.out.println(" NEWLINE :");
stateStackPop();
if (inSet)
inSet = false;
if (inDirective)
inDirective = false;
}
}
<DIRECTIVE>
TOKEN :
{
<MINUS: "-">
| <PLUS: "+">
| <MULTIPLY: "*">
| <DIVIDE: "/">
| <MODULUS: "%">
| <LOGICAL_AND: "&&">
| <LOGICAL_OR: "||">
| <LOGICAL_LT: "<">
| <LOGICAL_LE: "<=">
| <LOGICAL_GT: ">">
| <LOGICAL_GE: ">=">
| <LOGICAL_EQUALS: "==">
| <LOGICAL_NOT_EQUALS: "!=">
| <LOGICAL_NOT: "!">
| <EQUALS: "=" >
}
<PRE_DIRECTIVE>
TOKEN :
{
<END: "end" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? >
{
inDirective = false;
stateStackPop();
}
| <IF_DIRECTIVE: "if">
{
SwitchTo(DIRECTIVE);
}
| <ELSEIF_DIRECTIVE: "elseif">
{
SwitchTo(DIRECTIVE);
}
| <ELSE_DIRECTIVE: "else">
{
inDirective = false;
stateStackPop();
}
| <STOP_DIRECTIVE: "stop">
{
matchedToken.kind = EOF;
fileDepth = 0;
}
}
<PRE_DIRECTIVE,DIRECTIVE,REFMOD2>
TOKEN:
{
<#DIGIT: [ "0"-"9" ] >
| <NUMBER_LITERAL: (<DIGIT>)+ >
{
/*
* check to see if we are in set
* ex. #set $foo = $foo + 3
* because we want to handle the \n after
*/
if ( lparen == 0 && !inSet && curLexState != REFMOD2)
stateStackPop();
}
}
<PRE_DIRECTIVE,DIRECTIVE>
TOKEN:
{
<#LETTER: [ "a"-"z", "A" - "Z" ] >
| <WORD: (<LETTER>)+ >
}
/* -----------------------------------------------------------------------
*
* REFERENCE Lexical States
*
* This is more than a single state, because of the structure of
* the VTL references. We use three states because the set of tokens
* for each state can be different.
*
* $foo.bar( "arg" )
* ^ ^ ^
* | | |
* ----------- > REFERENCE : state initiated by the '$' character. Continues
* | | until end of the reference, or the . character.
* |------ > REFMODIFIER : state switched to when the <DOT> is encountered.
* | note that this is a switch, not a push. See notes at bottom
* | re stateStack.
* |-- > REFMOD2 : state switch to when the LPAREN is encountered.
* again, this is a switch, not a push.
*
* ---------------------------------------------------------------------------- */
<REFERENCE,REFMODIFIER,REFMOD2>
TOKEN :
{
<#ALPHA_CHAR: ["a"-"z", "A"-"Z"] >
| <#ALPHANUM_CHAR: [ "a"-"z", "A"-"Z", "0"-"9" ] >
| <#IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "-", "_" ] >
| <IDENTIFIER: <ALPHA_CHAR> (<IDENTIFIER_CHAR>)* >
| <DOT: "." <ALPHA_CHAR>>
{
/*
* push the alpha char back into the stream so the following identifier is complete
*/
input_stream.backup(1);
/*
* and munge the <DOT> so we just get a . when we have normal text that looks like a ref.ident
*/
matchedToken.image = ".";
if ( bDebugPrint_ )
System.out.print("DOT : switching to " + REFMODIFIER);
SwitchTo(REFMODIFIER);
}
| <LCURLY: "{">
| <RCURLY: "}">
{
stateStackPop();
}
}
<REFERENCE,REFMODIFIER,REFMOD2>
SPECIAL_TOKEN :
{
<REFERENCE_TERMINATOR: ~[] >
{
/*
* push every terminator character back into the stream
*/
input_stream.backup(1);
inReference = false;
if ( bDebugPrint_ )
System.out.print("REF_TERM :");
stateStackPop();
}
}
<PRE_DIRECTIVE>
SPECIAL_TOKEN :
{
<DIRECTIVE_TERMINATOR: ~[] >
{
if ( bDebugPrint_ )
System.out.print("DIRECTIVE_TERM :");
input_stream.backup(1);
inDirective = false;
stateStackPop();
}
}
/**
* This method is what starts the whole parsing
* process. After the parsing is complete and
* the template has been turned into an AST,
* this method returns the root of AST which
* can subsequently be traversed by a visitor
* which implements the ParserVisitor interface
* which is generated automatically by JavaCC
*/
SimpleNode process() : {}
{
( Statement() )* <EOF>
{ return jjtThis; }
}
/**
* These are the types of statements that
* are acceptable in Velocity templates.
*/
void Statement() #void : {}
{
IfStatement()
| StopStatement()
| Reference()
| Comment()
| SetDirective()
| EscapedDirective()
| Escape()
| Directive()
| Text()
}
/**
* used to separate the notion of a valid directive that has been
* escaped, versus something that looks like a directive and
* is just schmoo. This is important to do as a separate production
* that creates a node, because we want this, in either case, to stop
* the further parsing of the Directive() tree.
*/
void EscapedDirective() : {}
{
{
Token t = null;
}
t = <ESCAPE_DIRECTIVE>
{
/*
* churn and burn..
*/
t.image = escapedDirective( t.image );
}
}
/**
* Used to catch and process escape sequences in grammatical constructs
* as escapes outside of VTL are just characters. Right now we hav both
* this and the EscapeDirective() construction because in the EscapeDirective()
* case, we want to suck in the #<directive> and here we don't. We just want
* the escapes to render correctly
*/
void Escape() : {}
{
{
Token t = null;
int iCount = 0;
boolean bControl = false;
}
( LOOKAHEAD(2) t = <DOUBLE_ESCAPE>
{
iCount++;
}
)+
{
/*
* first, check to see if we have a control directive
*/
switch(t.next.kind ) {
case IF_DIRECTIVE :
case ELSE_DIRECTIVE :
case ELSEIF_DIRECTIVE :
case END :
case STOP_DIRECTIVE :
bControl = true;
break;
}
/*
* if that failed, lets lookahead to see if we matched a PD or a VM
*/
if ( isDirective( t.next.image.substring(1)))
bControl = true;
else if ( Runtime.isVelocimacro( t.next.image.substring(1)))
bControl = true;
t.image = "";
for( int i = 0; i < iCount; i++)
t.image += (bControl ? "\\" : "\\\\");
}
}
void Comment() : {}
{
<SINGLE_LINE_COMMENT>
| <MULTI_LINE_COMMENT>
| <FORMAL_COMMENT>
}
void NumberLiteral() : {}
{
<NUMBER_LITERAL>
}
void StringLiteral() : {}
{
<STRING_LITERAL>
}
/**
* This method corresponds to variable
* references in Velocity templates.
* The following are examples of variable
* references that may be found in a
* template:
*
* $foo
* $bar
*
*/
void Identifier() : {}
{
<IDENTIFIER>
}
void Word() : {}
{
<WORD>
}
/**
* Supports the arguments for the Pluggable Directives
* We add whitespace in here as a token so the VMs can
* easily reconstruct a macro body from the token stream
* @see Directive()
*/
void DirectiveArg() #void : {}
{
Reference()
| Word()
| StringLiteral()
| NumberLiteral()
| ObjectArray()
| <WHITESPACE>
}
/**
* Supports the Pluggable Directives
* #foo( arg+ )
*/
SimpleNode Directive() :
{
Token t = null;
Directive d;
boolean bDoItNow = false;
}
{
/*
* note that if we were escaped, that is now handled by EscapedDirective()
*/
t = <WORD>
{
String strDirectiveName = t.image.substring(1);
d = (Directive) directives.get( strDirectiveName );
/*
* Velocimacro support : if the directive is macro directive
* then set the flag so after the block parsing, we add the VM
* right then. (So available if used w/in the current template )
*/
if ( strDirectiveName.equals("macro"))
{
bDoItNow = true;
}
/*
* set the directive name from here. No reason for the thing to know about parser tokens
*/
jjtThis.setDirectiveName( strDirectiveName );
if ( d == null)
{
/*
* if null, then not a real directive, but maybe a Velocimacro
*/
d = (Directive) Runtime.getVelocimacro( strDirectiveName );
if (d == null)
{
token_source.stateStackPop();
token_source.inDirective = false;
return jjtThis;
}
}
/*
* now, switch us out of PRE_DIRECTIVE
*/
token_source.SwitchTo(DIRECTIVE);
}
/*
* if this is indeed a token, match the #foo ( arg ) pattern
*/
[<WHITESPACE>] <LPAREN> ( DirectiveArg() )* <RPAREN>
{
if (d.getType() == Directive.LINE)
return jjtThis;
}
/*
* and the following block if the PD needs it
*/
( Statement() )+ #Block
<END>
{
/*
* VM : if we are processing a #macro directive, we need to
* process the block. In truth, I can just register the name
* and do the work later when init-ing. That would work
* as long as things were always defined before use. This way
* we don't have to worry about forward references and such...
*/
if (bDoItNow)
{
Macro m = new Macro();
m.processAndRegister( jjtThis );
}
/*
* VM : end
*/
return jjtThis;
}
}
void ObjectArray() : {}
{
<LBRACKET> [ Parameter() ( <COMMA> Parameter() )* ] <RBRACKET>
}
/**
* This method has yet to be fully implemented
* but will allow arbitrarily nested method
* calls
*/
void Parameter() #void: {}
{
[<WHITESPACE>]
(
StringLiteral()
| ObjectArray()
| True()
| False()
| Reference()
| NumberLiteral()
)
[<WHITESPACE>]
}
/**
* This method has yet to be fully implemented
* but will allow arbitrarily nested method
* calls
*/
void Method() : {}
{
Identifier() <LPAREN> [ Parameter() ( <COMMA> Parameter() )* ] <REFMOD2_RPAREN>
}
void Reference() : {}
{
// This should be changed to Indentifier() now. Make
// it easier to walk the AST.
[<LCURLY>]
<IDENTIFIER>
[<RCURLY>]
(LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) [<RCURLY>] )*
}
void True() : {}
{
<TRUE>
}
void False() : {}
{
<FALSE>
}
/**
* This method is responsible for allowing
* all non-grammar text to pass through
* unscathed.
*/
void Text() : {}
{
<TEXT>
| <DOT>
| <RPAREN>
| <LPAREN>
| <NUMBER_LITERAL>
| <STRING_LITERAL>
| <ESCAPE>
}
/* -----------------------------------------------------------------------
*
* Defined Directive Syntax
*
* ----------------------------------------------------------------------*/
void IfStatement() : {}
{
<IF_DIRECTIVE> [<WHITESPACE>] <LPAREN> Expression() <RPAREN>
( Statement() )+ #Block
[ LOOKAHEAD(1) ( ElseIfStatement() )+ ]
[ LOOKAHEAD(1) ElseStatement() ]
<END>
}
void ElseStatement() : {}
{
<ELSE_DIRECTIVE>
( Statement() )+ #Block
}
void ElseIfStatement() : {}
{
<ELSEIF_DIRECTIVE> [<WHITESPACE>]
<LPAREN> Expression() <RPAREN>
( Statement() )+ #Block
}
void SetDirective() : {}
{
( <SET_DIRECTIVE> Expression() [<NEWLINE>] )
}
/**
* This method corresponds to the #stop
* directive which just simulates and EOF
* so that parsing stops. The #stop directive
* is useful for end-user debugging
* purposes.
*/
void StopStatement() #void: {}
{
<STOP_DIRECTIVE>
}
/* -----------------------------------------------------------------------
*
* Expression Syntax
*
* ----------------------------------------------------------------------*/
void Expression() : {}
{
LOOKAHEAD( PrimaryExpression() <EQUALS> ) Assignment()
| ConditionalOrExpression()
}
void Assignment() #Assignment(2) : {}
{
PrimaryExpression() <EQUALS> Expression()
}
void ConditionalOrExpression() #void : {}
{
ConditionalAndExpression()
( <LOGICAL_OR> ConditionalAndExpression() #OrNode(2) )*
}
void ConditionalAndExpression() #void : {}
{
EqualityExpression()
( <LOGICAL_AND> EqualityExpression() #AndNode(2) )*
}
void EqualityExpression() #void : {}
{
RelationalExpression()
(
<LOGICAL_EQUALS> RelationalExpression() #EQNode(2)
| <LOGICAL_NOT_EQUALS> RelationalExpression() #NENode(2)
)*
}
void RelationalExpression() #void : {}
{
AdditiveExpression()
(
<LOGICAL_LT> AdditiveExpression() #LTNode(2)
| <LOGICAL_GT> AdditiveExpression() #GTNode(2)
| <LOGICAL_LE> AdditiveExpression() #LENode(2)
| <LOGICAL_GE> AdditiveExpression() #GENode(2)
)*
}
void AdditiveExpression() #void : {}
{
MultiplicativeExpression()
(
<PLUS> MultiplicativeExpression() #AddNode(2)
| <MINUS> MultiplicativeExpression() #SubtractNode(2)
)*
}
void MultiplicativeExpression() #void : {}
{
UnaryExpression()
(
<MULTIPLY> UnaryExpression() #MulNode(2)
| <DIVIDE> UnaryExpression() #DivNode(2)
| <MODULUS> UnaryExpression() #ModNode(2)
)*
}
void UnaryExpression() #void : {}
{
<LOGICAL_NOT> UnaryExpression() #NotNode(1)
| PrimaryExpression()
}
void PrimaryExpression() #void : {}
{
[<WHITESPACE>]
(
StringLiteral()
| NumberLiteral()
| Reference()
| ObjectArray()
| True()
| False()
| <LPAREN> Expression() <RPAREN>
)
[<WHITESPACE>]
}
/* ======================================================================
Notes
-----
template == the input stream for this parser, contains 'VTL' mixed in with 'schmoo'
VTL == Velocity Template Language : the references, directives, etc
shmoo == the non-VTL component of a template
reference == VTL entity that represents data within the context. ex. $foo
directive == VTL entity that denotes 'action' (#set, #foreach, #if )
defined directive (DD) == VTL directive entity that is expressed explicitly w/in this grammar
pluggable directive (PD) == VTL directive entity that is defined outside of the grammar. PD's
allow VTL to be easily expandable w/o parser modification.
The problem with parsing VTL is that an input stream consists generally of little
bits of VTL mixed in with 'other stuff, referred to as 'schmoo'. Unlike
other languages, like C or Java, where the parser can punt whenever it encounters
input that doesn't conform to the grammar, the VTL parser can't do that. It must simply
output the schmoo and keep going.
There are a few things that we do here :
- define a set of parser states (DEFAULT, DIRECTIVE, REFERENCE, etc)
- define for each parser state a set of tokens for each state
- define the VTL grammar, expressed (mostly) in the productions such as Text(), SetStatement(), etc.
It is clear that this expression of the VTL grammer (the contents of this .jjt file) is maturing and
evolving as we learn more about how to parse VTL ( and as I learn about parsing...), so in the event
this documentation is in disagreement w/ the source, the source takes precedence. :)
Parser States
-------------
DEFAULT : This is the base or starting state, and strangely enough, the default state.
PRE_DIRECTIVE : State immediately following '#' before we figure out which defined or pluggable
directive (or neither) we are working with.
DIRECTIVE : This state is triggered by the a match of a DD or a PD.
REFERENCE : Triggered by '$'. Analagous to PRE_DIRECTIVE.
REFMODIFIER : Triggered by .<alpha> when in REFERENCE.
REFMOD2 : Triggered by ( when in REFMODIFIER
(cont)
Escape Sequences
----------------
The escape processing in VTL is very simple. The '\' character acts only as an escape when :
1) On or more touch a VTL element.
A VTL element is either :
1) It preceeds a reference that is in the context.
2) It preceeds a defined directive (#set, #if, #end, etc) or a valid pluggable directive,
such as #foreach
In all other cases the '\' is just another piece of text. The purpose of this is to allow the non-VTL
parts of a template (the 'schmoo') to not have to be altered for processing by Velocity.
So if in the context $foo and $bar were defined and $woogie was not
\$foo \$bar \$woogie
would output
$foo $bar \$woogie
Further, you can stack them and they affect left to right, just like convention escape characters in other languages.
\$foo = $foo
\\$foo = \<foo>
\\\$foo = \$foo
What You Expect
---------------
The recent versions of the parser are trying to support precise output to support general template use.
The directives do not render trailing whitespace and newlines if followed by a newline. They will render preceeding whitespace.
The only exception is #set, which also eats preceeding whitespace.
So, with a template :
------
#set $foo="foo"
#if($foo)
\$foo = $foo
#end
------
it will render precisely :
------
$foo = foo
------
*/