📄 fmparser.jj
字号:
/*
* 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 visigoths@visigoths.org.
*
* 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;
// 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 "<if" and then, when you hit the matching ">"
* 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 <noparse> and <comment>. This variable
tells us what the closing tag should be, and when
we hit that, we resume parsing. Note that with this
scheme, <comment> and <noparse> 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;
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 : (<BLANK>)* (">" | "]")>
|
<#CLOSE_TAG2 : (<BLANK>)* ("/")? (">" | "]")>
|
<ATTEMPT : <START_TAG> "attempt" <CLOSE_TAG1>> {strictSyntaxCheck(matchedToken, DEFAULT);}
|
<RECOVER : <START_TAG> "recover" <CLOSE_TAG1>> {strictSyntaxCheck(matchedToken, DEFAULT);}
|
<IF : <START_TAG> "if" <BLANK>> {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -