r.freemarker-gae.2.3.30.source-code.FTL.jj Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of freemarker-gae Show documentation
Show all versions of freemarker-gae Show documentation
Google App Engine compliant variation of FreeMarker.
FreeMarker is a "template engine"; a generic tool to generate text output based on templates.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
options
{
STATIC = false;
UNICODE_INPUT = true;
// DEBUG_TOKEN_MANAGER = true;
// DEBUG_PARSER = true;
}
PARSER_BEGIN(FMParser)
package freemarker.core;
import freemarker.core.LocalLambdaExpression.LambdaParameterList;
import freemarker.template.*;
import freemarker.template.utility.*;
import java.io.*;
import java.util.*;
import static freemarker.template.Configuration.*;
/**
* This class is generated by JavaCC from a grammar file.
*/
public class FMParser {
private static final int ITERATOR_BLOCK_KIND_LIST = 0;
private static final int ITERATOR_BLOCK_KIND_FOREACH = 1;
private static final int ITERATOR_BLOCK_KIND_ITEMS = 2;
private static final int ITERATOR_BLOCK_KIND_USER_DIRECTIVE = 3;
private static class ParserIteratorBlockContext {
/**
* loopVarName in <#list ... as loopVarName> or <#items as loopVarName>; null after we left the nested
* block of #list or #items, respectively.
*/
private String loopVarName;
/**
* loopVar1Name in <#list ... as k, loopVar2Name> or <#items as k, loopVar2Name>; null after we left the nested
* block of #list or #items, respectively.
*/
private String loopVar2Name;
/**
* See the ITERATOR_BLOCK_KIND_... costants.
*/
private int kind;
/**
* Is this a key-value pair listing? When there's a nested #items, it's only set there.
*/
private boolean hashListing;
}
private Template template;
private boolean stripWhitespace, stripText, preventStrippings;
private int incompatibleImprovements;
private OutputFormat outputFormat;
private int autoEscapingPolicy;
private boolean autoEscaping;
private ParserConfiguration pCfg;
/** Keeps track of #list and #foreach nesting. */
private List iteratorBlockContexts;
/**
* Keeps track of the nesting depth of directives that support #break.
*/
private int breakableDirectiveNesting;
/**
* Keeps track of the nesting depth of directives that support #continue.
*/
private int continuableDirectiveNesting;
private boolean inMacro, inFunction, requireArgsSpecialVariable;
private LinkedList escapes = new LinkedList();
private int mixedContentNesting; // for stripText
/**
* Create an FM expression parser using a string.
*
* @Deprecated This is an internal API of FreeMarker; can be removed any time.
*/
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);
FMParser parser = new FMParser(token_source);
token_source.setParser(parser);
return parser;
}
/**
* Constructs a new parser object.
*
* @param template
* The template associated with this parser.
* @param reader
* The character stream to use as input
* @param strictSyntaxMode
* Whether FreeMarker directives must start with a #
*
* @Deprecated This is an internal API of FreeMarker; will be removed in 2.4.
*/
public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace) {
this(template, reader, strictSyntaxMode, stripWhitespace, Configuration.AUTO_DETECT_TAG_SYNTAX);
}
/**
* @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
*/
public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace, int tagSyntax) {
this(template, reader, strictSyntaxMode, stripWhitespace, tagSyntax,
Configuration.PARSED_DEFAULT_INCOMPATIBLE_ENHANCEMENTS);
}
/**
* @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
*/
public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean stripWhitespace,
int tagSyntax, int incompatibleImprovements) {
this(template, reader, strictSyntaxMode, stripWhitespace,
tagSyntax, Configuration.AUTO_DETECT_NAMING_CONVENTION, incompatibleImprovements);
}
/**
* @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
*/
public FMParser(String template) {
this(dummyTemplate(),
new StringReader(template), true, true);
}
private static Template dummyTemplate() {
try {
return new Template(null, new StringReader(""), Configuration.getDefaultConfiguration());
} catch (IOException e) {
throw new RuntimeException("Failed to create dummy template", e);
}
}
/**
* @Deprecated This is an internal API of FreeMarker; will be changed in 2.4.
*/
public FMParser(Template template, Reader reader, boolean strictSyntaxMode, boolean whitespaceStripping,
int tagSyntax, int namingConvention, int incompatibleImprovements) {
this(template, reader,
new LegacyConstructorParserConfiguration(
strictSyntaxMode, whitespaceStripping,
tagSyntax, LEGACY_INTERPOLATION_SYNTAX, namingConvention,
template != null ? template.getParserConfiguration().getAutoEscapingPolicy()
: Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,
template != null ? template.getParserConfiguration().getOutputFormat()
: null,
template != null ? template.getParserConfiguration().getRecognizeStandardFileExtensions()
: null,
template != null ? template.getParserConfiguration().getTabSize()
: null,
new Version(incompatibleImprovements),
template != null ? template.getArithmeticEngine() : null));
}
/**
* @Deprecated This is an internal API of FreeMarker; don't call it from outside FreeMarker.
*
* @since 2.3.24
*/
public FMParser(Template template, Reader reader, ParserConfiguration pCfg) {
this(template, true, readerToTokenManager(reader, pCfg), pCfg);
}
private static FMParserTokenManager readerToTokenManager(Reader reader, ParserConfiguration pCfg) {
SimpleCharStream simpleCharStream = new SimpleCharStream(reader, 1, 1);
simpleCharStream.setTabSize(pCfg.getTabSize());
return new FMParserTokenManager(simpleCharStream);
}
/**
* @Deprecated This is an internal API of FreeMarker; don't call it from outside FreeMarker.
*
* @since 2.3.24
*/
public FMParser(Template template, boolean newTemplate, FMParserTokenManager tkMan, ParserConfiguration pCfg) {
this(tkMan);
NullArgumentException.check(pCfg);
this.pCfg = pCfg;
NullArgumentException.check(template);
this.template = template;
// Hack due to legacy public constructors (removed in 2.4):
if (pCfg instanceof LegacyConstructorParserConfiguration) {
LegacyConstructorParserConfiguration lpCfg = (LegacyConstructorParserConfiguration) pCfg;
lpCfg.setArithmeticEngineIfNotSet(template.getArithmeticEngine());
lpCfg.setAutoEscapingPolicyIfNotSet(template.getConfiguration().getAutoEscapingPolicy());
lpCfg.setOutputFormatIfNotSet(template.getOutputFormat());
lpCfg.setRecognizeStandardFileExtensionsIfNotSet(
template.getParserConfiguration().getRecognizeStandardFileExtensions());
lpCfg.setTabSizeIfNotSet(
template.getParserConfiguration().getTabSize());
}
int incompatibleImprovements = pCfg.getIncompatibleImprovements().intValue();
token_source.incompatibleImprovements = incompatibleImprovements;
this.incompatibleImprovements = incompatibleImprovements;
{
OutputFormat outputFormatFromExt;
if (!pCfg.getRecognizeStandardFileExtensions()
|| (outputFormatFromExt = getFormatFromStdFileExt()) == null) {
autoEscapingPolicy = pCfg.getAutoEscapingPolicy();
outputFormat = pCfg.getOutputFormat();
} else {
// Override it
autoEscapingPolicy = Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY;
outputFormat = outputFormatFromExt;
}
}
recalculateAutoEscapingField();
token_source.setParser(this);
token_source.strictSyntaxMode = pCfg.getStrictSyntaxMode();
int tagSyntax = pCfg.getTagSyntax();
switch (tagSyntax) {
case Configuration.AUTO_DETECT_TAG_SYNTAX:
token_source.autodetectTagSyntax = true;
break;
case Configuration.ANGLE_BRACKET_TAG_SYNTAX:
token_source.squBracTagSyntax = false;
break;
case Configuration.SQUARE_BRACKET_TAG_SYNTAX:
token_source.squBracTagSyntax = true;
break;
default:
throw new IllegalArgumentException("Illegal argument for tagSyntax: " + tagSyntax);
}
token_source.interpolationSyntax = pCfg.getInterpolationSyntax();
int namingConvention = pCfg.getNamingConvention();
switch (namingConvention) {
case Configuration.AUTO_DETECT_NAMING_CONVENTION:
case Configuration.CAMEL_CASE_NAMING_CONVENTION:
case Configuration.LEGACY_NAMING_CONVENTION:
token_source.initialNamingConvention = namingConvention;
token_source.namingConvention = namingConvention;
break;
default:
throw new IllegalArgumentException("Illegal argument for namingConvention: " + namingConvention);
}
this.stripWhitespace = pCfg.getWhitespaceStripping();
// If this is a Template under construction, we do the below.
// If this is just the enclosing Template for ?eval or such, we must not modify it.
if (newTemplate) {
_TemplateAPI.setAutoEscaping(template, autoEscaping);
_TemplateAPI.setOutputFormat(template, outputFormat);
}
}
void setupStringLiteralMode(FMParser parentParser, OutputFormat outputFormat) {
FMParserTokenManager parentTokenSource = parentParser.token_source;
token_source.initialNamingConvention = parentTokenSource.initialNamingConvention;
token_source.namingConvention = parentTokenSource.namingConvention;
token_source.namingConventionEstabilisher = parentTokenSource.namingConventionEstabilisher;
token_source.SwitchTo(NO_DIRECTIVE);
this.outputFormat = outputFormat;
recalculateAutoEscapingField();
if (incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_24) {
// Emulate bug, where the string literal parser haven't inherited the IcI:
incompatibleImprovements = _TemplateAPI.VERSION_INT_2_3_0;
}
// So that loop variable built-ins, like ?index, works inside the interpolations in the string literal:
iteratorBlockContexts = parentParser.iteratorBlockContexts;
}
void tearDownStringLiteralMode(FMParser parentParser) {
// If the naming convention was established inside the string literal, it's inherited by the parent:
FMParserTokenManager parentTokenSource = parentParser.token_source;
parentTokenSource.namingConvention = token_source.namingConvention;
parentTokenSource.namingConventionEstabilisher = token_source.namingConventionEstabilisher;
}
/**
* Used when we need to recreate the source code from the AST (such as for the FM2 to FM3 converter).
*/
void setPreventStrippings(boolean preventStrippings) {
this.preventStrippings = preventStrippings;
}
private OutputFormat getFormatFromStdFileExt() {
String sourceName = template.getSourceName();
if (sourceName == null) {
return null; // Not possible anyway...
}
int ln = sourceName.length();
if (ln < 5) return null;
char c = sourceName.charAt(ln - 5);
if (c != '.') return null;
c = sourceName.charAt(ln - 4);
if (c != 'f' && c != 'F') return null;
c = sourceName.charAt(ln - 3);
if (c != 't' && c != 'T') return null;
c = sourceName.charAt(ln - 2);
if (c != 'l' && c != 'L') return null;
c = sourceName.charAt(ln - 1);
try {
// Note: We get the output formats by name, so that custom overrides take effect.
if (c == 'h' || c == 'H') {
return template.getConfiguration().getOutputFormat(HTMLOutputFormat.INSTANCE.getName());
}
if (c == 'x' || c == 'X') {
return template.getConfiguration().getOutputFormat(XMLOutputFormat.INSTANCE.getName());
}
} catch (UnregisteredOutputFormatException e) {
throw new BugException("Unregistered std format", e);
}
return null;
}
/**
* Updates the {@link #autoEscaping} field based on the {@link #autoEscapingPolicy} and {@link #outputFormat} fields.
*/
private void recalculateAutoEscapingField() {
if (outputFormat instanceof MarkupOutputFormat) {
if (autoEscapingPolicy == Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY) {
autoEscaping = ((MarkupOutputFormat) outputFormat).isAutoEscapedByDefault();
} else if (autoEscapingPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY) {
autoEscaping = true;
} else if (autoEscapingPolicy == Configuration.DISABLE_AUTO_ESCAPING_POLICY) {
autoEscaping = false;
} else {
throw new IllegalStateException("Unhandled autoEscaping enum: " + autoEscapingPolicy);
}
} else {
autoEscaping = false;
}
}
MarkupOutputFormat getMarkupOutputFormat() {
return outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null;
}
/**
* Don't use it, unless you are developing FreeMarker itself.
*/
public int _getLastTagSyntax() {
return token_source.squBracTagSyntax
? Configuration.SQUARE_BRACKET_TAG_SYNTAX
: Configuration.ANGLE_BRACKET_TAG_SYNTAX;
}
/**
* Don't use it, unless you are developing FreeMarker itself.
* The naming convention used by this template; if it couldn't be detected so far, it will be the most probable one.
* This could be used for formatting error messages, but not for anything serious.
*/
public int _getLastNamingConvention() {
return token_source.namingConvention;
}
/**
* 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) {
throw new ParseException(
"Found string literal: " + exp + ". Expecting: " + expected,
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) {
throw new ParseException(
"Found number literal: " + exp.getCanonicalForm() + ". Expecting " + expected,
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) {
throw new ParseException("Found: " + exp.getCanonicalForm() + " literal. Expecting " + expected , 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) {
throw new ParseException(
"Found hash literal: " + exp.getCanonicalForm() + ". Expecting " + expected,
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) {
throw new ParseException(
"Found list literal: " + exp.getCanonicalForm() + ". Expecting " + expected,
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, "string");
notListLiteral(exp, "string");
notHashLiteral(exp, "string");
notBooleanLiteral(exp, "string");
}
/**
* 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) throws ParseException {
if (!escapes.isEmpty()) {
return ((EscapeBlock) escapes.getFirst()).doEscape(exp);
} else {
return exp;
}
}
private boolean getBoolean(Expression exp, boolean legacyCompat) throws ParseException {
TemplateModel tm = null;
try {
tm = exp.eval(null);
} catch (Exception e) {
throw new ParseException(e.getMessage()
+ "\nCould not evaluate expression: "
+ exp.getCanonicalForm(),
exp,
e);
}
if (tm instanceof TemplateBooleanModel) {
try {
return ((TemplateBooleanModel) tm).getAsBoolean();
} catch (TemplateModelException tme) {
}
}
if (legacyCompat && tm instanceof TemplateScalarModel) {
try {
return StringUtil.getYesNo(((TemplateScalarModel) tm).getAsString());
} catch (Exception e) {
throw new ParseException(e.getMessage()
+ "\nExpecting boolean (true/false), found: " + exp.getCanonicalForm(),
exp);
}
}
throw new ParseException("Expecting boolean (true/false) parameter", exp);
}
void checkCurrentOutputFormatCanEscape(Token start) throws ParseException {
if (!(outputFormat instanceof MarkupOutputFormat)) {
throw new ParseException("The current output format can't do escaping: " + outputFormat,
template, start);
}
}
private ParserIteratorBlockContext pushIteratorBlockContext() {
if (iteratorBlockContexts == null) {
iteratorBlockContexts = new ArrayList(4);
}
ParserIteratorBlockContext newCtx = new ParserIteratorBlockContext();
iteratorBlockContexts.add(newCtx);
return newCtx;
}
private void popIteratorBlockContext() {
iteratorBlockContexts.remove(iteratorBlockContexts.size() - 1);
}
private ParserIteratorBlockContext peekIteratorBlockContext() {
int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
return size != 0 ? (ParserIteratorBlockContext) iteratorBlockContexts.get(size - 1) : null;
}
private void checkLoopVariableBuiltInLHO(String loopVarName, Expression lhoExp, Token biName)
throws ParseException {
int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
for (int i = size - 1; i >= 0; i--) {
ParserIteratorBlockContext ctx = iteratorBlockContexts.get(i);
if (loopVarName.equals(ctx.loopVarName) || loopVarName.equals(ctx.loopVar2Name)) {
if (ctx.kind == ITERATOR_BLOCK_KIND_USER_DIRECTIVE) {
throw new ParseException(
"The left hand operand of ?" + biName.image
+ " can't be the loop variable of an user defined directive: "
+ loopVarName,
lhoExp);
}
return; // success
}
}
throw new ParseException(
"The left hand operand of ?" + biName.image + " must be a loop variable, "
+ "but there's no loop variable in scope with this name: " + loopVarName,
lhoExp);
}
private String forEachDirectiveSymbol() {
// [2.4] Use camel case as the default
return token_source.namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "#forEach" : "#foreach";
}
}
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:
{
private static final String PLANNED_DIRECTIVE_HINT
= "(If you have seen this directive in use elsewhere, this was a planned directive, "
+ "so maybe you need to upgrade FreeMarker.)";
/**
* 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;
private FMParser parser;
private int postInterpolationLexState = -1;
/**
* 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 curlyBracketNesting;
private int parenthesisNesting;
private int bracketNesting;
private boolean inFTLHeader;
boolean strictSyntaxMode,
squBracTagSyntax,
autodetectTagSyntax,
tagSyntaxEstablished,
inInvocation;
int interpolationSyntax;
int initialNamingConvention;
int namingConvention;
Token namingConventionEstabilisher;
int incompatibleImprovements;
void setParser(FMParser parser) {
this.parser = parser;
}
// This method checks if we are in a strict mode where all
// FreeMarker directives must start with <#. It also handles
// tag syntax detection. If you update this logic, take a look
// at the UNKNOWN_DIRECTIVE token too.
private void handleTagSyntaxAndSwitch(Token tok, int tokenNamingConvention, int newLexState) {
final String image = tok.image;
// Non-strict syntax (deprecated) only supports legacy naming convention.
// We didn't push this on the tokenizer because it made it slow, so we filter here.
if (!strictSyntaxMode
&& (tokenNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION)
&& !isStrictTag(image)) {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
char firstChar = image.charAt(0);
if (autodetectTagSyntax && !tagSyntaxEstablished) {
squBracTagSyntax = (firstChar == '[');
}
if ((firstChar == '[' && !squBracTagSyntax) || (firstChar == '<' && squBracTagSyntax)) {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
if (!strictSyntaxMode) {
// Legacy feature (or bug?): Tag syntax never gets estabilished in non-strict mode.
// We do establish the naming convention though.
checkNamingConvention(tok, tokenNamingConvention);
SwitchTo(newLexState);
return;
}
// For square bracket tags there's no non-strict token, so we are sure that it's an FTL tag.
// But if it's an angle bracket tag, we have to check if it's just static text or and FTL tag, because the
// tokenizer will emit the same kind of token for both.
if (!squBracTagSyntax && !isStrictTag(image)) {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
// We only get here if this is a strict FTL tag.
tagSyntaxEstablished = true;
if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
// For END_xxx tags, as they can't contain expressions, the whole tag is a single token. So this is the only
// chance to check if we got something inconsistent like `#if]`. (We can't do this at the #CLOSE_TAG1 or
// such, as at that point it's possible that the tag syntax is not yet established.)
char lastChar = image.charAt(image.length() - 1);
// Is it an end tag?
if (lastChar == ']' || lastChar == '>') {
if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') {
throw new TokenMgrError(
"The tag shouldn't end with \""+ lastChar + "\".",
TokenMgrError.LEXICAL_ERROR,
tok.beginLine, tok.beginColumn,
tok.endLine, tok.endColumn);
}
} // if end-tag
}
checkNamingConvention(tok, tokenNamingConvention);
SwitchTo(newLexState);
}
void checkNamingConvention(Token tok) {
checkNamingConvention(tok, _CoreStringUtils.getIdentifierNamingConvention(tok.image));
}
void checkNamingConvention(Token tok, int tokenNamingConvention) {
if (tokenNamingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION) {
if (namingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION) {
namingConvention = tokenNamingConvention;
namingConventionEstabilisher = tok;
} else if (namingConvention != tokenNamingConvention) {
throw newNameConventionMismatchException(tok);
}
}
}
private TokenMgrError newNameConventionMismatchException(Token tok) {
return new TokenMgrError(
"Naming convention mismatch. "
+ "Identifiers that are part of the template language (not the user specified ones) "
+ (initialNamingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION
? "must consistently use the same naming convention within the same template. This template uses "
: "must use the configured naming convention, which is the ")
+ (namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
? "camel case naming convention (like: exampleName) "
: (namingConvention == Configuration.LEGACY_NAMING_CONVENTION
? "legacy naming convention (directive (tag) names are like examplename, "
+ "everything else is like example_name) "
: "??? (internal error)"
))
+ (namingConventionEstabilisher != null
? "estabilished by auto-detection at "
+ _MessageUtil.formatPosition(
namingConventionEstabilisher.beginLine, namingConventionEstabilisher.beginColumn)
+ " by token " + StringUtil.jQuote(namingConventionEstabilisher.image.trim())
: "")
+ ", but the problematic token, " + StringUtil.jQuote(tok.image.trim())
+ ", uses a different convention.",
TokenMgrError.LEXICAL_ERROR,
tok.beginLine, tok.beginColumn, tok.endLine, tok.endColumn);
}
/**
* Used for tags whose name isn't affected by naming convention.
*/
private void handleTagSyntaxAndSwitch(Token tok, int newLexState) {
handleTagSyntaxAndSwitch(tok, Configuration.AUTO_DETECT_NAMING_CONVENTION, newLexState);
}
private boolean isStrictTag(String image) {
return image.length() > 2 && (image.charAt(1) == '#' || image.charAt(2) == '#');
}
/**
* Detects the naming convention used, both in start- and end-tag tokens.
*
* @param charIdxInName
* The index of the deciding character relatively to the first letter of the name.
*/
private static int getTagNamingConvention(Token tok, int charIdxInName) {
return _CoreStringUtils.isUpperUSASCII(getTagNameCharAt(tok, charIdxInName))
? Configuration.CAMEL_CASE_NAMING_CONVENTION : Configuration.LEGACY_NAMING_CONVENTION;
}
static char getTagNameCharAt(Token tok, int charIdxInName) {
final String image = tok.image;
// Skip tag delimiter:
int idx = 0;
for (;;) {
final char c = image.charAt(idx);
if (c != '<' && c != '[' && c != '/' && c != '#') {
break;
}
idx++;
}
return image.charAt(idx + charIdxInName);
}
private void unifiedCall(Token tok) {
char firstChar = tok.image.charAt(0);
if (autodetectTagSyntax && !tagSyntaxEstablished) {
squBracTagSyntax = (firstChar == '[');
}
if (squBracTagSyntax && firstChar == '<') {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
if (!squBracTagSyntax && firstChar == '[') {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
tagSyntaxEstablished = true;
SwitchTo(NO_SPACE_EXPRESSION);
}
private void unifiedCallEnd(Token tok) {
char firstChar = tok.image.charAt(0);
if (squBracTagSyntax && firstChar == '<') {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
if (!squBracTagSyntax && firstChar == '[') {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
}
private void startInterpolation(Token tok) {
if (
interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
&& tok.kind == SQUARE_BRACKET_INTERPOLATION_OPENING
|| interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX
&& tok.kind != DOLLAR_INTERPOLATION_OPENING
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX
&& tok.kind != SQUARE_BRACKET_INTERPOLATION_OPENING) {
tok.kind = STATIC_TEXT_NON_WS;
return;
}
if (postInterpolationLexState != -1) {
// This certainly never occurs, as starting an interpolation in expression mode fails earlier.
char c = tok.image.charAt(0);
throw new TokenMgrError(
"You can't start an interpolation (" + tok.image + "..."
+ (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX ? "]" : "}")
+ ") here as you are inside another interpolation.)",
TokenMgrError.LEXICAL_ERROR,
tok.beginLine, tok.beginColumn,
tok.endLine, tok.endColumn);
}
postInterpolationLexState = curLexState;
SwitchTo(FM_EXPRESSION);
}
private void endInterpolation(Token closingTk) {
SwitchTo(postInterpolationLexState);
postInterpolationLexState = -1;
}
private TokenMgrError newUnexpectedClosingTokenException(Token closingTk) {
return new TokenMgrError(
"You can't have an \"" + closingTk.image + "\" here, as there's nothing open that it could close.",
TokenMgrError.LEXICAL_ERROR,
closingTk.beginLine, closingTk.beginColumn,
closingTk.endLine, closingTk.endColumn);
}
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 (!tagSyntaxEstablished) {
squBracTagSyntax = matchedToken.image.charAt(0) == '[';
tagSyntaxEstablished = true;
autodetectTagSyntax = false;
}
String img = matchedToken.image;
char firstChar = img.charAt(0);
char lastChar = img.charAt(img.length() - 1);
if ((firstChar == '[' && !squBracTagSyntax) || (firstChar == '<' && squBracTagSyntax)) {
matchedToken.kind = STATIC_TEXT_NON_WS;
}
if (matchedToken.kind != STATIC_TEXT_NON_WS) {
if (lastChar != '>' && lastChar != ']') {
SwitchTo(FM_EXPRESSION);
inFTLHeader = true;
} else {
eatNewline();
}
}
}
}
TOKEN:
{
<#BLANK : " " | "\t" | "\n" | "\r">
|
<#START_TAG : "<" | "<#" | "[#">
|
<#END_TAG : "" | "#" | "[/#">
|
<#CLOSE_TAG1 : ()* (">" | "]")>
|
<#CLOSE_TAG2 : ()* ("/")? (">" | "]")>
|
/*
* ATTENTION: Update _CoreAPI.*_BUILT_IN_DIRECTIVE_NAMES if you add new directives!
*/
"attempt" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"recover" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"if" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"else" ("i" | "I") "f" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 4), FM_EXPRESSION);
}
|
"list" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"items" ()+ > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"sep" >
|
"for" ("e" | "E") "ach" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 3), FM_EXPRESSION);
}
|
"switch" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"case" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"assign" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"global" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"local" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
<_INCLUDE : "include" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"import" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"function" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"macro" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"transform" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"visit" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"stop" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"return" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"call" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"setting" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"output" ("f"|"F") "ormat" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 6), FM_EXPRESSION);
}
|
"auto" ("e"|"E") "sc" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 4), DEFAULT);
}
|
"no" ("autoe"|"AutoE") "sc" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 2), DEFAULT);
}
|
"compress" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"comment" > {
handleTagSyntaxAndSwitch(matchedToken, NO_PARSE); noparseTag = "comment";
}
|
{ noparseTag = "-->"; handleTagSyntaxAndSwitch(matchedToken, NO_PARSE); }
|
"no" ("p" | "P") "arse" > {
int tagNamingConvention = getTagNamingConvention(matchedToken, 2);
handleTagSyntaxAndSwitch(matchedToken, tagNamingConvention, NO_PARSE);
noparseTag = tagNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "noParse" : "noparse";
}
|
"if" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"list" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"items" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"sep" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"recover" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"attempt" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"for" ("e" | "E") "ach" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 3), DEFAULT);
}
|
"local" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"global" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"assign" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"function" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"macro" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"output" ("f" | "F") "ormat" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 6), DEFAULT);
}
|
"auto" ("e" | "E") "sc" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 4), DEFAULT);
}
|
"no" ("autoe"|"AutoE") "sc" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 2), DEFAULT);
}
|
"compress" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"transform" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"switch" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"else" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"break" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"continue" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"return" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"stop" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"flush" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"t" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"lt" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"rt" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"nt" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"default" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"nested" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"nested" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"recurse" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"recurse" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"fallback" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"escape" > { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
"escape" > { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
"no" ("e" | "E") "scape" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 2), DEFAULT);
}
|
"no" ("e" | "E") "scape" > {
handleTagSyntaxAndSwitch(matchedToken, getTagNamingConvention(matchedToken, 2), DEFAULT);
}
|
{ unifiedCall(matchedToken); }
|
) (".")*)? > { unifiedCallEnd(matchedToken); }
|
> { ftlHeader(matchedToken); }
|
" | "]")> { ftlHeader(matchedToken); }
|
/*
* ATTENTION: Update _CoreAPI.*_BUILT_IN_DIRECTIVE_NAMES if you add new directives!
*/
{
if (!tagSyntaxEstablished && incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_19) {
matchedToken.kind = STATIC_TEXT_NON_WS;
} else {
char firstChar = matchedToken.image.charAt(0);
if (!tagSyntaxEstablished && autodetectTagSyntax) {
squBracTagSyntax = (firstChar == '[');
tagSyntaxEstablished = true;
}
if (firstChar == '<' && squBracTagSyntax) {
matchedToken.kind = STATIC_TEXT_NON_WS;
} else if (firstChar == '[' && !squBracTagSyntax) {
matchedToken.kind = STATIC_TEXT_NON_WS;
} else if (strictSyntaxMode) {
String dn = matchedToken.image;
int index = dn.indexOf('#');
dn = dn.substring(index + 1);
// Until the tokenizer/parser is reworked, we have this quirk where something like <#list>
// doesn't match any directive starter tokens, because that token requires whitespace after the
// name as it should be followed by parameters. For now we work this around so we don't report
// unknown directive:
if (_CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES.contains(dn)) {
throw new TokenMgrError(
"#" + dn + " is an existing directive, but the tag is malformed. "
+ " (See FreeMarker Manual / Directive Reference.)",
TokenMgrError.LEXICAL_ERROR,
matchedToken.beginLine, matchedToken.beginColumn + 1,
matchedToken.endLine, matchedToken.endColumn);
}
String tip = null;
if (dn.equals("set") || dn.equals("var")) {
tip = "Use #assign or #local or #global, depending on the intented scope "
+ "(#assign is template-scope). " + PLANNED_DIRECTIVE_HINT;
} else if (dn.equals("else_if") || dn.equals("elif")) {
tip = "Use #elseif.";
} else if (dn.equals("no_escape")) {
tip = "Use #noescape instead.";
} else if (dn.equals("method")) {
tip = "Use #function instead.";
} else if (dn.equals("head") || dn.equals("template") || dn.equals("fm")) {
tip = "You may meant #ftl.";
} else if (dn.equals("try") || dn.equals("atempt")) {
tip = "You may meant #attempt.";
} else if (dn.equals("for") || dn.equals("each") || dn.equals("iterate") || dn.equals("iterator")) {
tip = "You may meant #list (http://freemarker.org/docs/ref_directive_list.html).";
} else if (dn.equals("prefix")) {
tip = "You may meant #import. " + PLANNED_DIRECTIVE_HINT;
} else if (dn.equals("item") || dn.equals("row") || dn.equals("rows")) {
tip = "You may meant #items.";
} else if (dn.equals("separator") || dn.equals("separate") || dn.equals("separ")) {
tip = "You may meant #sep.";
} else {
tip = "Help (latest version): http://freemarker.org/docs/ref_directive_alphaidx.html; "
+ "you're using FreeMarker " + Configuration.getVersion() + ".";
}
throw new TokenMgrError(
"Unknown directive: #" + dn + (tip != null ? ". " + tip : ""),
TokenMgrError.LEXICAL_ERROR,
matchedToken.beginLine, matchedToken.beginColumn + 1,
matchedToken.endLine, matchedToken.endColumn);
}
}
}
}
TOKEN :
{
|
|
// to handle a lone dollar sign or "<" or "# or <@ with whitespace after"
|
{ startInterpolation(matchedToken); }
|
{ startInterpolation(matchedToken); }
|
{ startInterpolation(matchedToken); }
}
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;
}
|
{
if (bracketNesting > 0) {
--bracketNesting;
} else if (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX && postInterpolationLexState != -1) {
endInterpolation(matchedToken);
} else {
// There's a legacy glitch where you can always close a tag with `]`, like `<#if x]`. We have to keep that
// working for backward compatibility, hence we don't always throw at !squBracTagSyntax:
if (!squBracTagSyntax
&& (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)
|| postInterpolationLexState != -1 /* We're in an interpolation => We aren't in a tag */) {
throw newUnexpectedClosingTokenException(matchedToken);
}
// Close tag, either legally or to emulate legacy glitch:
matchedToken.kind = DIRECTIVE_END;
if (inFTLHeader) {
eatNewline();
inFTLHeader = false;
}
SwitchTo(DEFAULT);
}
}
|
{
++parenthesisNesting;
if (parenthesisNesting == 1) SwitchTo(IN_PAREN);
}
|
{
--parenthesisNesting;
if (parenthesisNesting == 0) {
if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION);
else SwitchTo(FM_EXPRESSION);
}
}
|
{
++curlyBracketNesting;
}
|
{
if (curlyBracketNesting > 0) {
--curlyBracketNesting;
} else if (interpolationSyntax != SQUARE_BRACKET_INTERPOLATION_SYNTAX && postInterpolationLexState != -1) {
endInterpolation(matchedToken);
} else {
throw newUnexpectedClosingTokenException(matchedToken);
}
}
|
|
|
|
(|)*> {
// Remove backslashes from Token.image:
final String s = matchedToken.image;
if (s.indexOf('\\') != -1) {
final int srcLn = s.length();
final char[] newS = new char[srcLn - 1];
int dstIdx = 0;
for (int srcIdx = 0; srcIdx < srcLn; srcIdx++) {
final char c = s.charAt(srcIdx);
if (c != '\\') {
newS[dstIdx++] = c;
}
}
matchedToken.image = new String(newS, 0, dstIdx);
}
}
|
{
if ("".length() == 0) { // prevents unreachabe "break" compilation error in generated Java
char closerC = matchedToken.image.charAt(0) != '[' ? '}' : ']';
throw new TokenMgrError(
"You can't use " + matchedToken.image + "..." + closerC + " (an interpolation) here as you are "
+ "already in FreeMarker-expression-mode. Thus, instead of " + matchedToken.image + "myExpression"
+ closerC + ", just write myExpression. (" + matchedToken.image + "..." + closerC + " is only "
+ "used where otherwise static text is expected, i.e., outside FreeMarker tags and "
+ "interpolations, or inside string literals.)",
TokenMgrError.LEXICAL_ERROR,
matchedToken.beginLine, matchedToken.beginColumn,
matchedToken.endLine, matchedToken.endColumn);
}
}
|
<#NON_ESCAPED_ID_START_CHAR:
[
// This was generated on JDK 1.8.0_20 Win64 with src/main/misc/identifierChars/IdentifierCharGenerator.java
"$",
"@" - "Z",
"_",
"a" - "z",
"\u00AA",
"\u00B5",
"\u00BA",
"\u00C0" - "\u00D6",
"\u00D8" - "\u00F6",
"\u00F8" - "\u1FFF",
"\u2071",
"\u207F",
"\u2090" - "\u209C",
"\u2102",
"\u2107",
"\u210A" - "\u2113",
"\u2115",
"\u2119" - "\u211D",
"\u2124",
"\u2126",
"\u2128",
"\u212A" - "\u212D",
"\u212F" - "\u2139",
"\u213C" - "\u213F",
"\u2145" - "\u2149",
"\u214E",
"\u2183" - "\u2184",
"\u2C00" - "\u2C2E",
"\u2C30" - "\u2C5E",
"\u2C60" - "\u2CE4",
"\u2CEB" - "\u2CEE",
"\u2CF2" - "\u2CF3",
"\u2D00" - "\u2D25",
"\u2D27",
"\u2D2D",
"\u2D30" - "\u2D67",
"\u2D6F",
"\u2D80" - "\u2D96",
"\u2DA0" - "\u2DA6",
"\u2DA8" - "\u2DAE",
"\u2DB0" - "\u2DB6",
"\u2DB8" - "\u2DBE",
"\u2DC0" - "\u2DC6",
"\u2DC8" - "\u2DCE",
"\u2DD0" - "\u2DD6",
"\u2DD8" - "\u2DDE",
"\u2E2F",
"\u3005" - "\u3006",
"\u3031" - "\u3035",
"\u303B" - "\u303C",
"\u3040" - "\u318F",
"\u31A0" - "\u31BA",
"\u31F0" - "\u31FF",
"\u3300" - "\u337F",
"\u3400" - "\u4DB5",
"\u4E00" - "\uA48C",
"\uA4D0" - "\uA4FD",
"\uA500" - "\uA60C",
"\uA610" - "\uA62B",
"\uA640" - "\uA66E",
"\uA67F" - "\uA697",
"\uA6A0" - "\uA6E5",
"\uA717" - "\uA71F",
"\uA722" - "\uA788",
"\uA78B" - "\uA78E",
"\uA790" - "\uA793",
"\uA7A0" - "\uA7AA",
"\uA7F8" - "\uA801",
"\uA803" - "\uA805",
"\uA807" - "\uA80A",
"\uA80C" - "\uA822",
"\uA840" - "\uA873",
"\uA882" - "\uA8B3",
"\uA8D0" - "\uA8D9",
"\uA8F2" - "\uA8F7",
"\uA8FB",
"\uA900" - "\uA925",
"\uA930" - "\uA946",
"\uA960" - "\uA97C",
"\uA984" - "\uA9B2",
"\uA9CF" - "\uA9D9",
"\uAA00" - "\uAA28",
"\uAA40" - "\uAA42",
"\uAA44" - "\uAA4B",
"\uAA50" - "\uAA59",
"\uAA60" - "\uAA76",
"\uAA7A",
"\uAA80" - "\uAAAF",
"\uAAB1",
"\uAAB5" - "\uAAB6",
"\uAAB9" - "\uAABD",
"\uAAC0",
"\uAAC2",
"\uAADB" - "\uAADD",
"\uAAE0" - "\uAAEA",
"\uAAF2" - "\uAAF4",
"\uAB01" - "\uAB06",
"\uAB09" - "\uAB0E",
"\uAB11" - "\uAB16",
"\uAB20" - "\uAB26",
"\uAB28" - "\uAB2E",
"\uABC0" - "\uABE2",
"\uABF0" - "\uABF9",
"\uAC00" - "\uD7A3",
"\uD7B0" - "\uD7C6",
"\uD7CB" - "\uD7FB",
"\uF900" - "\uFB06",
"\uFB13" - "\uFB17",
"\uFB1D",
"\uFB1F" - "\uFB28",
"\uFB2A" - "\uFB36",
"\uFB38" - "\uFB3C",
"\uFB3E",
"\uFB40" - "\uFB41",
"\uFB43" - "\uFB44",
"\uFB46" - "\uFBB1",
"\uFBD3" - "\uFD3D",
"\uFD50" - "\uFD8F",
"\uFD92" - "\uFDC7",
"\uFDF0" - "\uFDFB",
"\uFE70" - "\uFE74",
"\uFE76" - "\uFEFC",
"\uFF10" - "\uFF19",
"\uFF21" - "\uFF3A",
"\uFF41" - "\uFF5A",
"\uFF66" - "\uFFBE",
"\uFFC2" - "\uFFC7",
"\uFFCA" - "\uFFCF",
"\uFFD2" - "\uFFD7",
"\uFFDA" - "\uFFDC"
]
>
|
<#ESCAPED_ID_CHAR: "\\" ("-" | "." | ":")>
|
<#ID_START_CHAR: |>
|
<#ASCII_DIGIT: ["0" - "9"]>
}
TOKEN :
{
">
{
if (inFTLHeader) {
eatNewline();
inFTLHeader = false;
}
if (squBracTagSyntax || postInterpolationLexState != -1 /* We are in an interpolation */) {
matchedToken.kind = NATURAL_GT;
} else {
SwitchTo(DEFAULT);
}
}
|
" | "/]">
{
if (tagSyntaxEstablished && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)) {
String image = matchedToken.image;
char lastChar = image.charAt(image.length() - 1);
if (!squBracTagSyntax && lastChar != '>' || squBracTagSyntax && lastChar != ']') {
throw new TokenMgrError(
"The tag shouldn't end with \"" + lastChar + "\".",
TokenMgrError.LEXICAL_ERROR,
matchedToken.beginLine, matchedToken.beginColumn,
matchedToken.endLine, matchedToken.endColumn);
}
}
if (inFTLHeader) {
eatNewline();
inFTLHeader = false;
}
SwitchTo(DEFAULT);
}
}
TOKEN :
{
">
|
=">
}
TOKEN :
{
: FM_EXPRESSION
}
TOKEN :
{
: FM_EXPRESSION
}
TOKEN :
{
" | "--]">
{
if (noparseTag.equals("-->")) {
boolean squareBracket = matchedToken.image.endsWith("]");
if ((squBracTagSyntax && squareBracket) || (!squBracTagSyntax && !squareBracket)) {
matchedToken.image = matchedToken.image + ";";
SwitchTo(DEFAULT);
}
}
}
|
" | "]")
>
{
StringTokenizer st = new StringTokenizer(image.toString(), " \t\n\r<>[]/#", false);
if (st.nextToken().equals(noparseTag)) {
matchedToken.image = matchedToken.image + ";";
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;
}
}
/**
* Should be called HighestPrecedenceExpression.
* Deals with the operators that have the highest precedence. Also deals with `exp!default` and `exp!`, due to parser
* tricks needed because of the last.
*/
Expression PrimaryExpression() :
{
Expression exp;
}
{
exp = AtomicExpression()
(
exp = DotVariable(exp)
|
exp = DynamicKey(exp)
|
exp = MethodArgs(exp)
|
exp = BuiltIn(exp)
|
exp = DefaultTo(exp)
|
exp = Exists(exp)
)*
{
return exp;
}
}
/**
* Lowest level expression, a literal, a variable,
* or a possibly more complex expression bounded
* by parentheses.
*/
Expression AtomicExpression() :
{
Expression exp;
}
{
(
exp = NumberLiteral()
|
exp = HashLiteral()
|
exp = StringLiteral(true)
|
exp = BooleanLiteral()
|
exp = ListLiteral()
|
exp = Identifier()
|
exp = Parenthesis()
|
exp = BuiltinVariable()
)
{
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;
}
}
/**
* Should be called UnaryPrefixExpression.
* A primary expression preceded by zero or more unary prefix operators.
*/
Expression UnaryExpression() :
{
Expression exp, result;
boolean haveNot = false;
Token t = null, start = null;
}
{
(
result = UnaryPlusMinusExpression()
|
result = NotExpression()
|
result = PrimaryExpression()
)
{
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 < nots.size(); i++) {
result = new NotExpression(exp);
Token tok = (Token) nots.get(nots.size() -i -1);
result.setLocation(template, tok, exp);
exp = result;
}
return result;
}
}
Expression UnaryPlusMinusExpression() :
{
Expression exp, result;
boolean isMinus = false;
Token t;
}
{
(
t =
|
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.TYPE_SUBSTRACTION);
}
result.setLocation(template, lhs, rhs);
lhs = result;
}
)*
{
return result;
}
}
/**
* A unary prefix expression followed by zero or more
* unary prefix expressions with operators in between.
*/
Expression MultiplicativeExpression() :
{
Expression lhs, rhs, result;
int operation = ArithmeticExpression.TYPE_MULTIPLICATION;
}
{
lhs = UnaryExpression() { result = lhs; }
(
LOOKAHEAD(||)
(
(
{ operation = ArithmeticExpression.TYPE_MULTIPLICATION; }
|
{ operation = ArithmeticExpression.TYPE_DIVISION; }
|
{operation = ArithmeticExpression.TYPE_MODULO; }
)
)
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(||