freemarker.core.FMParser.jj Maven / Gradle / Ivy
Go to download
Google App Engine compliant variation of FreeMarker.
FreeMarker is a "template engine"; a generic tool to generate text output based on templates.
/*
* Copyright (c) 2003 The Visigoth Software Society. 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 acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [email protected].
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* 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 VISIGOTH SOFTWARE SOCIETY 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 Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
options
{
STATIC=false;
UNICODE_INPUT=true;
LEXER_CLASS="FMParserTokenManager";
CONSTANTS_CLASS="FMParserConstants";
// DEBUG_TOKEN_MANAGER=true;
// DEBUG_PARSER=true;
}
PARSER_BEGIN(FMParser)
package freemarker.core;
import freemarker.template.*;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.DeepUnwrap;
import java.io.*;
import java.util.*;
/**
* This class is generated by JavaCC from a grammar file.
*/
public class FMParser {
// Necessary for adding macros and setting location info.
Template template;
private String templateName;
// variables that keep track of whether we are in a loop or a switch.
private int loopNesting, switchNesting;
private boolean inMacro, inFunction, stripWhitespace, stripText;
private LinkedList escapes = new LinkedList();
private int contentNesting; // for stripText
/**
* Create an FM expression parser using a string.
*/
static public FMParser createExpressionParser(String s) {
SimpleCharStream scs = new SimpleCharStream(new StringReader(s), 1, 1, s.length());
FMParserTokenManager token_source = new FMParserTokenManager(scs);
token_source.SwitchTo(FMParserConstants.FM_EXPRESSION);
return new FMParser(token_source);
}
/**
* Constructs a new parser object.
* @param template The template associated with this parser.
* @param reader The character stream to use as input
* @param strictEscapeSyntax Whether FreeMarker directives must start with a #
*/
public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace) {
this(reader);
this.template = template;
token_source.strictEscapeSyntax = strictEscapeSyntax;
this.templateName = template != null ? template.getName() : "";
token_source.templateName = templateName;
this.stripWhitespace = stripWhitespace;
}
public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace, int tagSyntax) {
this(template, reader, strictEscapeSyntax, stripWhitespace);
switch (tagSyntax) {
case Configuration.AUTO_DETECT_TAG_SYNTAX :
token_source.autodetectTagSyntax = true;
break;
case Configuration.ANGLE_BRACKET_TAG_SYNTAX :
token_source.altDirectiveSyntax = false;
break;
case Configuration.SQUARE_BRACKET_TAG_SYNTAX :
token_source.altDirectiveSyntax = true;
break;
default : throw new IllegalArgumentException("Illegal argument for tagSyntax");
}
}
public FMParser(String template) {
this(null, new StringReader(template), true, true);
}
private String getErrorStart(Token t) {
return "Error in template: " + template.getName()
+ "\non line " + t.beginLine + ", column " + t.beginColumn;
}
/**
* Throw an exception if the expression passed in is a String
* Literal
*/
private void notStringLiteral(Expression exp, String expected) throws ParseException {
if (exp instanceof StringLiteral) {
String msg = "Error " + exp.getStartLocation()
+ "\nFound string literal: " + exp
+ "\nExpecting: " + expected;
throw new ParseException(msg, exp);
}
}
/**
* Throw an exception if the expression passed in is a Number
* Literal
*/
private void notNumberLiteral(Expression exp, String expected) throws ParseException {
if (exp instanceof NumberLiteral) {
String msg = "Error " + exp.getStartLocation()
+ "\nFound number literal: " + exp.getCanonicalForm()
+ "\nExpecting " + expected;
throw new ParseException(msg, exp);
}
}
/**
* Throw an exception if the expression passed in is a boolean
* Literal
*/
private void notBooleanLiteral(Expression exp, String expected) throws ParseException {
if (exp instanceof BooleanLiteral) {
String msg = "Error " + exp.getStartLocation()
+ "\nFound: " + exp.getCanonicalForm()
+ "\nExpecting " + expected;
throw new ParseException(msg, exp);
}
}
/**
* Throw an exception if the expression passed in is a Hash
* Literal
*/
private void notHashLiteral(Expression exp, String expected) throws ParseException {
if (exp instanceof HashLiteral) {
String msg = "Error " + exp.getStartLocation()
+ "\nFound hash literal: " + exp.getCanonicalForm()
+ "\nExpecting " + expected;
throw new ParseException(msg, exp);
}
}
/**
* Throw an exception if the expression passed in is a List
* Literal
*/
private void notListLiteral(Expression exp, String expected)
throws ParseException
{
if (exp instanceof ListLiteral) {
String msg = "Error " + exp.getStartLocation()
+ "\nFound list literal: " + exp.getCanonicalForm()
+ "\nExpecting " + expected;
throw new ParseException(msg, exp);
}
}
/**
* Throw an exception if the expression passed in is a literal
* other than of the numerical type
*/
private void numberLiteralOnly(Expression exp) throws ParseException {
notStringLiteral(exp, "number");
notListLiteral(exp, "number");
notHashLiteral(exp, "number");
notBooleanLiteral(exp, "number");
}
/**
* Throw an exception if the expression passed in is
* not a string.
*/
private void stringLiteralOnly(Expression exp) throws ParseException {
notNumberLiteral(exp, "number");
notListLiteral(exp, "number");
notHashLiteral(exp, "number");
notBooleanLiteral(exp, "number");
}
/**
* Throw an exception if the expression passed in is a literal
* other than of the boolean type
*/
private void booleanLiteralOnly(Expression exp) throws ParseException {
notStringLiteral(exp, "boolean (true/false)");
notListLiteral(exp, "boolean (true/false)");
notHashLiteral(exp, "boolean (true/false)");
notNumberLiteral(exp, "boolean (true/false)");
}
private Expression escapedExpression(Expression exp) {
if(!escapes.isEmpty()) {
return ((EscapeBlock)escapes.getFirst()).doEscape(exp);
}
return exp;
}
private boolean getBoolean(Expression exp) throws ParseException {
TemplateModel tm = null;
try {
tm = exp.getAsTemplateModel(null);
} catch (Exception e) {
throw new ParseException(e.getMessage()
+ "\nCould not evaluate expression: "
+ exp.getCanonicalForm()
+ exp.getStartLocation(), exp);
}
if (tm instanceof TemplateBooleanModel) {
try {
return ((TemplateBooleanModel) tm).getAsBoolean();
} catch (TemplateModelException tme) {
}
}
if (tm instanceof TemplateScalarModel) {
try {
return StringUtil.getYesNo(((TemplateScalarModel) tm).getAsString());
} catch (Exception e) {
throw new ParseException(e.getMessage()
+ "\nExpecting yes/no, found: " + exp.getCanonicalForm()
+ exp.getStartLocation(), exp);
}
}
throw new ParseException("Expecting boolean (yes/no) parameter" + exp.getStartLocation(), exp);
}
}
PARSER_END(FMParser)
/**
* The lexer portion defines 5 lexical states:
* DEFAULT, FM_EXPRESSION, IN_PAREN, NO_PARSE, and EXPRESSION_COMMENT.
* The DEFAULT state is when you are parsing
* text but are not inside a FreeMarker expression.
* FM_EXPRESSION is the state you are in
* when the parser wants a FreeMarker expression.
* IN_PAREN is almost identical really. The difference
* is that you are in this state when you are within
* FreeMarker expression and also within (...).
* This is a necessary subtlety because the
* ">" and ">=" symbols can only be used
* within parentheses because otherwise, it would
* be ambiguous with the end of a directive.
* So, for example, you enter the FM_EXPRESSION state
* right after a ${ and leave it after the matching }.
* Or, you enter the FM_EXPRESSION state right after
* an ""
* that ends the if directive,
* you go back to DEFAULT lexical state.
* If, within the FM_EXPRESSION state, you enter a
* parenthetical expression, you enter the IN_PAREN
* state.
* Note that whitespace is ignored in the
* FM_EXPRESSION and IN_PAREN states
* but is passed through to the parser as PCDATA in the DEFAULT state.
* NO_PARSE and EXPRESSION_COMMENT are extremely simple
* lexical states. NO_PARSE is when you are in a comment
* block and EXPRESSION_COMMENT is when you are in a comment
* that is within an FTL expression.
*/
TOKEN_MGR_DECLS :
{
/**
The noparseTag is set when we enter
a block of text that the parser more or less ignores.
These are and . This variable
tells us what the closing tag should be, and when
we hit that, we resume parsing. Note that with this
scheme, and tags cannot nest
recursively, but it is not clear how important that is.
*/
String noparseTag;
/**
Keeps track of how deeply nested
we have the hash literals.
This is necessary since we need to be
able to distinguish the } used to close
a hash literal and the one used to
close a ${
*/
private int hashLiteralNesting;
private int parenthesisNesting;
private int bracketNesting;
private boolean inFTLHeader;
boolean strictEscapeSyntax,
onlyTextOutput,
altDirectiveSyntax,
autodetectTagSyntax,
directiveSyntaxEstablished,
inInvocation;
String templateName;
// This method checks if we are in a strict mode where all
// FreeMarker directives must start with <#
private void strictSyntaxCheck(Token tok, int newLexState) {
if (onlyTextOutput) {
tok.kind = PRINTABLE_CHARS;
return;
}
char firstChar = tok.image.charAt(0);
if (autodetectTagSyntax && !directiveSyntaxEstablished) {
altDirectiveSyntax = (firstChar == '[');
}
if ((firstChar == '[' && !altDirectiveSyntax) || (firstChar == '<' && altDirectiveSyntax)) {
tok.kind = PRINTABLE_CHARS;
return;
}
if (!strictEscapeSyntax) {
SwitchTo(newLexState);
return;
}
if (!altDirectiveSyntax) {
if (!tok.image.startsWith("<#") && !tok.image.startsWith("#")) {
tok.kind = PRINTABLE_CHARS;
return;
}
}
directiveSyntaxEstablished = true;
SwitchTo(newLexState);
}
private void unifiedCall(Token tok) {
char firstChar = tok.image.charAt(0);
if (autodetectTagSyntax && !directiveSyntaxEstablished) {
altDirectiveSyntax = (firstChar == '[');
}
if (altDirectiveSyntax && firstChar == '<') {
tok.kind = PRINTABLE_CHARS;
return;
}
if (!altDirectiveSyntax && firstChar == '[') {
tok.kind = PRINTABLE_CHARS;
return;
}
directiveSyntaxEstablished = true;
SwitchTo(NO_SPACE_EXPRESSION);
}
private void unifiedCallEnd(Token tok) {
char firstChar = tok.image.charAt(0);
if (altDirectiveSyntax && firstChar == '<') {
tok.kind = PRINTABLE_CHARS;
return;
}
if (!altDirectiveSyntax && firstChar == '[') {
tok.kind = PRINTABLE_CHARS;
return;
}
}
private void closeBracket(Token tok) {
if (bracketNesting >0) {
--bracketNesting;
} else {
tok.kind=DIRECTIVE_END;
if (inFTLHeader) {
eatNewline();
inFTLHeader = false;
}
SwitchTo(DEFAULT);
}
}
private void eatNewline() {
int charsRead = 0;
try {
while (true) {
char c = input_stream.readChar();
++charsRead;
if (!Character.isWhitespace(c)) {
input_stream.backup(charsRead);
return;
} else if (c=='\r') {
char next = input_stream.readChar();
++charsRead;
if (next != '\n') {
input_stream.backup(1);
}
return;
} else if (c=='\n') {
return;
}
}
} catch (IOException ioe) {
input_stream.backup(charsRead);
}
}
private void ftlHeader(Token matchedToken) {
if (!directiveSyntaxEstablished) {
altDirectiveSyntax = matchedToken.image.charAt(0) == '[';
directiveSyntaxEstablished = true;
autodetectTagSyntax = false;
}
String img = matchedToken.image;
char firstChar = img.charAt(0);
char lastChar = img.charAt(img.length() -1);
if ((firstChar == '[' && !altDirectiveSyntax) || (firstChar == '<' && altDirectiveSyntax)) {
matchedToken.kind = PRINTABLE_CHARS;
}
if (matchedToken.kind != PRINTABLE_CHARS) {
if (lastChar != '>' && lastChar != ']') {
SwitchTo(FM_EXPRESSION);
inFTLHeader = true;
} else {
eatNewline();
}
}
}
}
TOKEN:
{
<#BLANK : [" ", "\t", "\n", "\r"]>
|
<#START_TAG : "<" | "<#" | "[#">
|
<#END_TAG : "" | "#" | "[/#">
|
<#CLOSE_TAG1 : ()* (">" | "]")>
|
<#CLOSE_TAG2 : ()* ("/")? (">" | "]")>
|
"attempt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"recover" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"if" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"elseif" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"list" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"foreach" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"switch" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"case" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"assign" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"global" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"local" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
<_INCLUDE : "include" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"import" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"function" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"macro" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"transform" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"visit" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"stop" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"return" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"call" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"setting" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"compress" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"comment" > {strictSyntaxCheck(matchedToken, NO_PARSE); noparseTag="comment";}
|
{noparseTag = "-->"; strictSyntaxCheck(matchedToken, NO_PARSE); }
|
"noparse" > {strictSyntaxCheck(matchedToken, NO_PARSE); noparseTag="noparse";}
|
"if" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"list" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"recover" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"attempt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"foreach" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"local" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"global" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"assign" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"function" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"macro" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"compress" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"transform" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"switch" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"else" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"break" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"return" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"stop" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"flush" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"t" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"lt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"rt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"nt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"default" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"nested" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"nested" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"recurse" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"recurse" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"fallback" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"escape" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
|
"escape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"noescape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
"noescape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
|
{unifiedCall(matchedToken);}
|
) (".")*)? > {unifiedCallEnd(matchedToken);}
|
> {ftlHeader(matchedToken);}
|
" | "]")> {ftlHeader(matchedToken);}
|
{
if (!directiveSyntaxEstablished) {
matchedToken.kind = PRINTABLE_CHARS;
} else {
char firstChar = matchedToken.image.charAt(0);
if (firstChar == '<' && altDirectiveSyntax) {
matchedToken.kind = PRINTABLE_CHARS;
} else if (firstChar == '[' && !altDirectiveSyntax) {
matchedToken.kind = PRINTABLE_CHARS;
}
else if (strictEscapeSyntax) {
String s = matchedToken.image;
int index = s.indexOf('#');
s = s.substring(index);
String msg = "Unknown directive: "
+ s
+ " on line: " + matchedToken.beginLine
+ ", column: " + matchedToken.beginColumn +1
+ ", in template: " + templateName;
throw new TokenMgrError(msg, TokenMgrError.LEXICAL_ERROR);
}
}
}
}
TOKEN :
{
|
|
// to handle a lone dollar sign or "<" or "# or <@ with whitespace after"
|
: FM_EXPRESSION
|
: FM_EXPRESSION
}
SKIP :
{
< ( " " | "\t" | "\n" | "\r" )+ >
|
< ["<", "["] ["#", "!"] "--"> : EXPRESSION_COMMENT
}
SKIP:
{
< (~["-", ">", "]"])+ >
|
< ">">
|
< "]">
|
< "-">
|
< "-->" | "--]"> {if (parenthesisNesting >0) SwitchTo(IN_PAREN); else if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION); else SwitchTo(FM_EXPRESSION);}
}
TOKEN :
{
<#ESCAPED_CHAR : "\\"
(
["n","t","r","f","b","g","l","a","\\","'","\"","$","{"]
|
("x" ["0"-"9","A"-"F","a"-"f"])
)
>
|
)*
"\"")
|
("'"
((~["'","\\"]) | )*
"'"
)
>
|
|
|
|
|
"." >
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{++bracketNesting;}
|
{closeBracket(matchedToken);}
|
{
++parenthesisNesting;
if (parenthesisNesting == 1)
SwitchTo(IN_PAREN);
}
|
{
--parenthesisNesting;
if (parenthesisNesting == 0) {
if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION);
else SwitchTo(FM_EXPRESSION);
}
}
|
{++hashLiteralNesting;}
|
{
if (hashLiteralNesting == 0)
SwitchTo(DEFAULT);
else
--hashLiteralNesting;
}
|
< IN : "in">
|
< AS : "as">
|
< USING : "using">
|
< ID: (|)* >
|
< #LETTER:
[
"\u0024",
"\u0040"-"\u005a",
"\u005f",
"\u0061"-"\u007a",
"\u00c0"-"\u00d6",
"\u00d8"-"\u00f6",
"\u00f8"-"\u00ff",
"\u0100"-"\u1fff",
"\u3040"-"\u318f",
"\u3300"-"\u337f",
"\u3400"-"\u3d2d",
"\u4e00"-"\u9fff",
"\uf900"-"\ufaff"
]
>
|
< #DIGIT:
[
"\u0030"-"\u0039",
"\u0660"-"\u0669",
"\u06f0"-"\u06f9",
"\u0966"-"\u096f",
"\u09e6"-"\u09ef",
"\u0a66"-"\u0a6f",
"\u0ae6"-"\u0aef",
"\u0b66"-"\u0b6f",
"\u0be7"-"\u0bef",
"\u0c66"-"\u0c6f",
"\u0ce6"-"\u0cef",
"\u0d66"-"\u0d6f",
"\u0e50"-"\u0e59",
"\u0ed0"-"\u0ed9",
"\u1040"-"\u1049"
]
>
}
TOKEN :
{
">
{
if (inFTLHeader) eatNewline();
inFTLHeader = false;
if (altDirectiveSyntax) {
matchedToken.kind = NATURAL_GT;
} else {
SwitchTo(DEFAULT);
}
}
|
" | "/]"> {if (inFTLHeader) eatNewline(); inFTLHeader = false; SwitchTo(DEFAULT);}
}
TOKEN :
{
">
|
=">
}
TOKEN :
{
: FM_EXPRESSION
}
TOKEN :
{
: FM_EXPRESSION
}
TOKEN :
{
" | "--]">
{
if (noparseTag.equals("-->")) {
boolean squareBracket = matchedToken.image.endsWith("]");
if ((altDirectiveSyntax && squareBracket) || (!altDirectiveSyntax && !squareBracket))
SwitchTo(DEFAULT);
}
}
|
" | "]")
>
{
StringTokenizer st = new StringTokenizer(image.toString(),
" \t\n\r<>[]/#",
false);
if (st.nextToken().equals(noparseTag)) {
SwitchTo(DEFAULT);
}
}
|
|
}
// Now the actual parsing code, starting
// with the productions for FreeMarker's
// expression syntax.
/**
* This is the same as OrExpression, since
* the OR is the operator with the lowest
* precedence.
*/
Expression Expression() :
{
Expression exp;
}
{
exp=OrExpression()
{
return exp;
}
}
/**
* Lowest level expression, a literal, a variable,
* or a possibly more complex expression bounded
* by parentheses.
*/
Expression PrimaryExpression() :
{
Expression exp;
}
{
(
exp=NumberLiteral()
|
exp=HashLiteral()
|
exp=StringLiteral(true)
|
exp=BooleanLiteral()
|
exp=ListLiteral()
|
exp=Identifier()
|
exp=Parenthesis()
|
exp=BuiltinVariable()
)
(
LOOKAHEAD(
|
|
|
|
|
|)
exp=AddSubExpression(exp)
)*
{
return exp;
}
}
Expression Parenthesis() :
{
Expression exp, result;
Token start, end;
}
{
start=
exp=Expression()
end=
{
result = new ParentheticalExpression(exp);
result.setLocation(template, start, end);
return result;
}
}
/**
* A primary expression preceded by zero or
* more unary operators. (The only unary operator we
* currently have is the NOT.)
*/
Expression UnaryExpression() :
{
Expression exp, result;
boolean haveNot = false;
Token t = null, start=null;
}
{
(
result=UnaryPlusMinusExpression()
|
result=NotExpression()
|
result=PrimaryExpression()
)
{
return result;
}
/*
(
t=
{
haveNot = !haveNot;
if (start == null)
start = t;
}
)*
exp=PrimaryExpression()
{
result = exp;
if (haveNot) {
booleanLiteralOnly(exp);
result = new NotExpression(exp);
result.setLocation(template, start, exp);
}
return result;
}
*/
}
Expression NotExpression() :
{
Token t;
Expression exp, result=null;
ArrayList nots = new ArrayList();
}
{
(
t= {nots.add(t);}
)+
exp=PrimaryExpression()
{
for (int i=0; i
|
t= {isMinus = true;}
)
exp=PrimaryExpression()
{
result = new UnaryPlusMinusExpression(exp, isMinus);
result.setLocation(template, t, exp);
return result;
}
}
Expression AdditiveExpression() :
{
Expression lhs, rhs, result;
boolean plus;
}
{
lhs=MultiplicativeExpression() {result = lhs;}
(
LOOKAHEAD(|)
(
(
{plus = true;}
|
{plus = false;}
)
)
rhs=MultiplicativeExpression()
{
if (plus) {
// plus is treated separately, since it is also
// used for concatenation.
result = new AddConcatExpression(lhs, rhs);
}
else {
numberLiteralOnly(lhs);
numberLiteralOnly(rhs);
result = new ArithmeticExpression(lhs,
rhs,
ArithmeticExpression.SUBSTRACTION);
}
result.setLocation(template, lhs, rhs);
lhs = result;
}
)*
{
return result;
}
}
/**
* A unary expression followed by zero or more
* unary expressions with operators in between.
*/
Expression MultiplicativeExpression() :
{
Expression lhs, rhs, result;
int operation = ArithmeticExpression.MULTIPLICATION;
}
{
lhs=UnaryExpression() {result = lhs;}
(
LOOKAHEAD(||)
(
(
{operation = ArithmeticExpression.MULTIPLICATION;}
|
{operation = ArithmeticExpression.DIVISION;}
|
{operation = ArithmeticExpression.MODULUS;}
)
)
rhs=UnaryExpression()
{
numberLiteralOnly(lhs);
numberLiteralOnly(rhs);
result = new ArithmeticExpression(lhs, rhs, operation);
result.setLocation(template, lhs, rhs);
lhs = result;
}
)*
{
return result;
}
}
Expression EqualityExpression() :
{
Expression lhs, rhs, result;
Token t;
}
{
lhs=RelationalExpression() {result = lhs;}
[
LOOKAHEAD(||)
(
t=
|
t=
|
t=
)
rhs=RelationalExpression()
{
notHashLiteral(lhs, "scalar");
notHashLiteral(rhs, "scalar");
notListLiteral(lhs, "scalar");
notListLiteral(rhs, "scalar");
result = new ComparisonExpression(lhs, rhs, t.image);
result.setLocation(template, lhs, rhs);
}
]
{
return result;
}
}
Expression RelationalExpression() :
{
Expression lhs, rhs, result;
Token t;
}
{
lhs=RangeExpression() {result = lhs;}
[
LOOKAHEAD(||||||)
(
t=
|
t=
|
t=
|
t=
|
t=
|
t=
)
rhs=RangeExpression()
{
notHashLiteral(lhs, "scalar");
notHashLiteral(rhs, "scalar");
notListLiteral(lhs, "scalar");
notListLiteral(rhs, "scalar");
notStringLiteral(lhs, "number");
notStringLiteral(rhs, "number");
result = new ComparisonExpression(lhs, rhs, t.image);
result.setLocation(template, lhs, rhs);
}
]
{
return result;
}
}
Expression RangeExpression() :
{
Expression lhs, rhs=null, result;
}
{
lhs=AdditiveExpression() {result = lhs;}
[
LOOKAHEAD()
[
LOOKAHEAD(Expression())
rhs=AdditiveExpression()
]
{
numberLiteralOnly(lhs);
if (rhs != null) {
numberLiteralOnly(rhs);
}
Range range = new Range(lhs, rhs);
if (rhs != null) {
range.setLocation(template, lhs, rhs);
} else {
range.setLocation(template, lhs, lhs);
}
result = range;
}
]
{
return result;
}
}
Expression AndExpression() :
{
Expression lhs, rhs, result;
}
{
lhs=EqualityExpression() {result = lhs;}
(
LOOKAHEAD()
rhs=EqualityExpression()
{
booleanLiteralOnly(lhs);
booleanLiteralOnly(rhs);
result = new AndExpression(lhs, rhs);
result.setLocation(template, lhs, rhs);
lhs = result;
}
)*
{
return result;
}
}
Expression OrExpression() :
{
Expression lhs, rhs, result;
}
{
lhs=AndExpression() {result = lhs;}
(
LOOKAHEAD()
rhs=AndExpression()
{
booleanLiteralOnly(lhs);
booleanLiteralOnly(rhs);
result = new OrExpression(lhs, rhs);
result.setLocation(template, lhs, rhs);
lhs = result;
}
)*
{
return result;
}
}
ListLiteral ListLiteral() :
{
ArrayList values = new ArrayList();
Token begin, end;
}
{
begin=
values=PositionalArgs()
end=
{
ListLiteral result = new ListLiteral(values);
result.setLocation(template, begin, end);
return result;
}
}
Expression NumberLiteral() :
{
Token op = null, t;
}
{
(
t=
|
t=
)
{
String s = t.image;
Expression result = new NumberLiteral(template.getArithmeticEngine().toNumber(s));
Token startToken = (op != null) ? op : t;
result.setLocation(template, startToken, t);
return result;
}
}
Identifier Identifier() :
{
Token t;
}
{
t=
{
Identifier id = new Identifier(t.image);
id.setLocation(template, t, t);
return id;
}
}
Expression IdentifierOrStringLiteral() :
{
Expression exp;
}
{
(
exp=Identifier()
|
exp=StringLiteral(false)
)
{
return exp;
}
}
BuiltinVariable BuiltinVariable() :
{
Token dot, name;
}
{
dot=
name=
{
BuiltinVariable result = null;
try {
result = new BuiltinVariable(name.image);
} catch (ParseException pe) {
pe.lineNumber = dot.beginLine;
pe.columnNumber = dot.beginColumn;
throw pe;
}
result.setLocation(template, dot, name);
return result;
}
}
/**
* Production that builds up an expression
* using the dot or dynamic key name
* or the args list if this is a method invocation.
*/
Expression AddSubExpression(Expression exp) :
{
Expression result = null;
}
{
(
result=DotVariable(exp)
|
result=DynamicKey(exp)
|
result=MethodArgs(exp)
|
result=BuiltIn(exp)
|
result=DefaultTo(exp)
|
result=Exists(exp)
)
{
return result;
}
}
Expression DefaultTo(Expression exp) :
{
Expression rhs = null;
Token t;
}
{
(
t=
|
(
t=
[
LOOKAHEAD(Expression())
rhs=Expression()
]
)
)
{
DefaultToExpression result = new DefaultToExpression(exp, rhs);
if (rhs ==null) {
result.setLocation(template, exp, t);
}
else {
result.setLocation(template, exp, rhs);
}
return result;
}
}
Expression Exists(Expression exp) :
{
Token t;
}
{
t=
{
ExistsExpression result = new ExistsExpression(exp);
result.setLocation(template, exp, t);
return result;
}
}
Expression BuiltIn(Expression exp) :
{
Token t=null;
}
{
t=
{
BuiltIn result = null;
try {
result = BuiltIn.newBuiltIn(exp, t.image, t, templateName);
} catch (ParseException pe) {
pe.lineNumber = t.beginLine;
pe.columnNumber = t.beginColumn;
throw pe;
}
result.setLocation(template, exp, t);
return result;
}
}
/**
* production for when a key is specified by