/*
* Copyright 2008 Google Inc.
*
* Licensed 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.
*/
package com.google.template.soy.javasrc.internal;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.NULL_DATA_INSTANCE;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.UTILS_LIB;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genBinaryOp;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genCoerceBoolean;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genCoerceString;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genFloatValue;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genFunctionCall;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genIntegerValue;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genMaybeCast;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genMaybeProtect;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewBooleanData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewFloatData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewIntegerData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewListData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewMapData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNewStringData;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genNumberValue;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.genUnaryOp;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysAtLeastOneFloat;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysAtLeastOneString;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysFloat;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysInteger;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysTwoFloatsOrOneFloatOneInteger;
import static com.google.template.soy.javasrc.restricted.JavaCodeUtils.isAlwaysTwoIntegers;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.data.SoyData;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.data.restricted.BooleanData;
import com.google.template.soy.data.restricted.FloatData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NullData;
import com.google.template.soy.data.restricted.NumberData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.exprtree.AbstractReturningExprNodeVisitor;
import com.google.template.soy.exprtree.BooleanNode;
import com.google.template.soy.exprtree.DataRefAccessIndexNode;
import com.google.template.soy.exprtree.DataRefAccessKeyNode;
import com.google.template.soy.exprtree.DataRefAccessNode;
import com.google.template.soy.exprtree.DataRefNode;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprNode.OperatorNode;
import com.google.template.soy.exprtree.ExprNode.ParentExprNode;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.exprtree.FloatNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.GlobalNode;
import com.google.template.soy.exprtree.IntegerNode;
import com.google.template.soy.exprtree.ListLiteralNode;
import com.google.template.soy.exprtree.MapLiteralNode;
import com.google.template.soy.exprtree.NullNode;
import com.google.template.soy.exprtree.Operator;
import com.google.template.soy.exprtree.OperatorNodes.AndOpNode;
import com.google.template.soy.exprtree.OperatorNodes.ConditionalOpNode;
import com.google.template.soy.exprtree.OperatorNodes.DivideByOpNode;
import com.google.template.soy.exprtree.OperatorNodes.EqualOpNode;
import com.google.template.soy.exprtree.OperatorNodes.GreaterThanOpNode;
import com.google.template.soy.exprtree.OperatorNodes.GreaterThanOrEqualOpNode;
import com.google.template.soy.exprtree.OperatorNodes.LessThanOpNode;
import com.google.template.soy.exprtree.OperatorNodes.LessThanOrEqualOpNode;
import com.google.template.soy.exprtree.OperatorNodes.MinusOpNode;
import com.google.template.soy.exprtree.OperatorNodes.ModOpNode;
import com.google.template.soy.exprtree.OperatorNodes.NegativeOpNode;
import com.google.template.soy.exprtree.OperatorNodes.NotEqualOpNode;
import com.google.template.soy.exprtree.OperatorNodes.NotOpNode;
import com.google.template.soy.exprtree.OperatorNodes.OrOpNode;
import com.google.template.soy.exprtree.OperatorNodes.PlusOpNode;
import com.google.template.soy.exprtree.OperatorNodes.TimesOpNode;
import com.google.template.soy.exprtree.StringNode;
import com.google.template.soy.internal.base.CharEscapers;
import com.google.template.soy.javasrc.restricted.JavaExpr;
import com.google.template.soy.javasrc.restricted.SoyJavaSrcFunction;
import com.google.template.soy.shared.internal.NonpluginFunction;
import java.util.Deque;
import java.util.List;
import java.util.Map;
/**
* Visitor for translating a Soy expression (in the form of an {@code ExprNode}) into an
* equivalent Java expression.
*
* <p> Important: Do not use outside of Soy code (treat as superpackage-private).
*
* @author Kai Huang
*/
public class TranslateToJavaExprVisitor extends AbstractReturningExprNodeVisitor<JavaExpr> {
/**
* Injectable factory for creating an instance of this class.
*/
public static interface TranslateToJavaExprVisitorFactory {
/**
* @param localVarTranslations The current stack of replacement Java expressions for the local
* variables (and foreach-loop special functions) current in scope.
*/
public TranslateToJavaExprVisitor create(Deque<Map<String, JavaExpr>> localVarTranslations);
}
/** Map of all SoyJavaSrcFunctions (name to function). */
private final Map<String, SoyJavaSrcFunction> soyJavaSrcFunctionsMap;
/** The current stack of replacement Java expressions for the local variables (and foreach-loop
* special functions) current in scope. */
private final Deque<Map<String, JavaExpr>> localVarTranslations;
/**
* @param soyJavaSrcFunctionsMap Map of all SoyJavaSrcFunctions (name to function).
* @param localVarTranslations The current stack of replacement Java expressions for the local
* variables (and foreach-loop special functions) current in scope.
*/
@AssistedInject
TranslateToJavaExprVisitor(
Map<String, SoyJavaSrcFunction> soyJavaSrcFunctionsMap,
@Assisted Deque<Map<String, JavaExpr>> localVarTranslations) {
this.soyJavaSrcFunctionsMap = soyJavaSrcFunctionsMap;
this.localVarTranslations = localVarTranslations;
}
// -----------------------------------------------------------------------------------------------
// Implementation for a dummy root node.
@Override protected JavaExpr visitExprRootNode(ExprRootNode<?> node) {
return visit(node.getChild(0));
}
// -----------------------------------------------------------------------------------------------
// Implementations for primitives.
@Override protected JavaExpr visitNullNode(NullNode node) {
return new JavaExpr(NULL_DATA_INSTANCE, NullData.class, Integer.MAX_VALUE);
}
@Override protected JavaExpr visitBooleanNode(BooleanNode node) {
// Soy boolean literals have same form as Java 'boolean' literals.
return convertBooleanResult(genNewBooleanData(node.toSourceString()));
}
@Override protected JavaExpr visitIntegerNode(IntegerNode node) {
// Soy integer literals have same form as Java 'int' literals.
return convertIntegerResult(genNewIntegerData(node.toSourceString()));
}
@Override protected JavaExpr visitFloatNode(FloatNode node) {
// Soy float literals have same form as Java 'double' literals.
return convertFloatResult(genNewFloatData(node.toSourceString()));
}
@Override protected JavaExpr visitStringNode(StringNode node) {
return convertStringResult(genNewStringData(
'"' + CharEscapers.javaStringEscaper().escape(node.getValue()) + '"'));
}
// -----------------------------------------------------------------------------------------------
// Implementations for collections.
@Override protected JavaExpr visitListLiteralNode(ListLiteralNode node) {
return convertListResult(genNewListData(buildCommaSepChildrenListHelper(node)));
}
@Override protected JavaExpr visitMapLiteralNode(MapLiteralNode node) {
return convertMapResult(genNewMapData(buildCommaSepChildrenListHelper(node)));
}
/**
* Private helper for visitListLiteralNode() and visitMapLiteralNode() to build a
* comma-separated list of children expression texts.
* @param node The parent node whose children should be visited and then the resulting expression
* texts joined into a comma-separated list.
* @return A comma-separated list of children expression texts.
*/
private String buildCommaSepChildrenListHelper(ParentExprNode node) {
StringBuilder resultSb = new StringBuilder();
boolean isFirst = true;
for (ExprNode child : node.getChildren()) {
if (isFirst) {
isFirst = false;
} else {
resultSb.append(", ");
}
resultSb.append(visit(child).getText());
}
return resultSb.toString();
}
// -----------------------------------------------------------------------------------------------
// Implementations for data references.
@Override protected JavaExpr visitDataRefNode(DataRefNode node) {
// Note: Using String instead of StringBuilder for readability. No performance concern here.
String nullSafetyPrefix = "";
String refText;
// ------ Translate first key, which may reference a variable, data, or injected data. ------
String firstKey = node.getFirstKey();
if (node.isIjDataRef()) {
// Case 1: Injected data reference.
refText = genGetDataSingleCallWithKey("this.$$ijData", firstKey);
if (node.isNullSafeIjDataRef()) {
nullSafetyPrefix = "(this.$$ijData == null) ? " + NULL_DATA_INSTANCE + " : ";
}
} else {
JavaExpr translation = getLocalVarTranslation(firstKey);
if (translation != null) {
// Case 2: In-scope local var.
refText = (translation.getPrecedence() == Integer.MAX_VALUE) ?
translation.getText() : "(" + translation.getText() + ")";
} else {
// Case 3: Data reference.
refText = genGetDataSingleCallWithKey("data", firstKey);
}
}
// ------ Translate the rest of the keys, if any. ------
for (ExprNode child : node.getChildren()) {
DataRefAccessNode accessNode = (DataRefAccessNode) child;
if (accessNode.isNullSafe()) {
nullSafetyPrefix +=
"(! " + genFunctionCall(UTILS_LIB + ".$$isNonnull", refText) + ") ? " +
NULL_DATA_INSTANCE + " : ";
}
switch (accessNode.getKind()) {
case DATA_REF_ACCESS_KEY_NODE:
refText = genGetDataSingleCallWithKey(
refText, ((DataRefAccessKeyNode) accessNode).getKey());
break;
case DATA_REF_ACCESS_INDEX_NODE:
refText = genGetDataSingleCallWithKey(
refText,
Integer.toString(((DataRefAccessIndexNode) accessNode).getIndex()));
break;
case DATA_REF_ACCESS_EXPR_NODE:
JavaExpr keyExpr = visit(accessNode.getChild(0));
refText = genGetDataSingleCallWithKeyExpr(refText, keyExpr);
break;
default:
throw new AssertionError();
}
}
if (nullSafetyPrefix.length() == 0) {
return new JavaExpr(refText, SoyData.class, Integer.MAX_VALUE);
} else {
return new JavaExpr(
nullSafetyPrefix + refText, SoyData.class, Operator.CONDITIONAL.getPrecedence());
}
}
private static String genGetDataSingleCallWithKey(String dataExprText, String key) {
return genFunctionCall(
UTILS_LIB + ".$$getDataSingle", dataExprText,
"\"" + CharEscapers.javaStringEscaper().escape(key) + "\"");
}
private static String genGetDataSingleCallWithKeyExpr(String dataExprText, JavaExpr keyExpr) {
return genFunctionCall(
UTILS_LIB + ".$$getDataSingle", dataExprText,
genMaybeProtect(keyExpr, Integer.MAX_VALUE) + ".toString()");
}
@Override protected JavaExpr visitGlobalNode(GlobalNode node) {
throw new UnsupportedOperationException();
}
// -----------------------------------------------------------------------------------------------
// Implementations for operators.
@Override protected JavaExpr visitNegativeOpNode(NegativeOpNode node) {
JavaExpr operand = visit(node.getChild(0));
String integerComputation = genNewIntegerData(genUnaryOp("-", genIntegerValue(operand)));
String floatComputation = genNewFloatData(genUnaryOp("-", genFloatValue(operand)));
if (isAlwaysInteger(operand)) {
return convertIntegerResult(integerComputation);
} else if (isAlwaysFloat(operand)) {
return convertFloatResult(floatComputation);
} else {
return convertNumberResult(genFunctionCall(
UTILS_LIB + ".$$negative", genMaybeCast(operand, NumberData.class)));
}
}
@Override protected JavaExpr visitNotOpNode(NotOpNode node) {
JavaExpr operand = visit(node.getChild(0));
return convertBooleanResult(genNewBooleanData(genUnaryOp("!", genCoerceBoolean(operand))));
}
@Override protected JavaExpr visitTimesOpNode(TimesOpNode node) {
return visitNumberToNumberBinaryOpHelper(node, "*", "$$times");
}
@Override protected JavaExpr visitDivideByOpNode(DivideByOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
// Note: Soy always performs floating-point division, even on two integers (like JavaScript).
return convertFloatResult(genNewFloatData(genBinaryOp(
"/", genNumberValue(operand0), genNumberValue(operand1))));
}
@Override protected JavaExpr visitModOpNode(ModOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
return convertIntegerResult(genNewIntegerData(genBinaryOp(
"%", genIntegerValue(operand0), genIntegerValue(operand1))));
}
@Override protected JavaExpr visitPlusOpNode(PlusOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
String stringComputation = genNewStringData(genBinaryOp(
"+", genCoerceString(operand0), genCoerceString(operand1)));
String integerComputation = genNewIntegerData(genBinaryOp(
"+", genIntegerValue(operand0), genIntegerValue(operand1)));
String floatComputation = genNewFloatData(genBinaryOp(
"+", genNumberValue(operand0), genNumberValue(operand1)));
if (isAlwaysTwoIntegers(operand0, operand1)) {
return convertIntegerResult(integerComputation);
} else if (isAlwaysAtLeastOneString(operand0, operand1)) {
return convertStringResult(stringComputation);
} else if (isAlwaysTwoFloatsOrOneFloatOneInteger(operand0, operand1)) {
return convertFloatResult(floatComputation);
} else {
return convertUnknownResult(genFunctionCall(
UTILS_LIB + ".$$plus", operand0.getText(), operand1.getText()));
}
}
@Override protected JavaExpr visitMinusOpNode(MinusOpNode node) {
return visitNumberToNumberBinaryOpHelper(node, "-", "$$minus");
}
@Override protected JavaExpr visitLessThanOpNode(LessThanOpNode node) {
return visitNumberToBooleanBinaryOpHelper(node, "<", "$$lessThan");
}
@Override protected JavaExpr visitGreaterThanOpNode(GreaterThanOpNode node) {
return visitNumberToBooleanBinaryOpHelper(node, ">", "$$greaterThan");
}
@Override protected JavaExpr visitLessThanOrEqualOpNode(LessThanOrEqualOpNode node) {
return visitNumberToBooleanBinaryOpHelper(node, "<=", "$$lessThanOrEqual");
}
@Override protected JavaExpr visitGreaterThanOrEqualOpNode(GreaterThanOrEqualOpNode node) {
return visitNumberToBooleanBinaryOpHelper(node, ">=", "$$greaterThanOrEqual");
}
@Override protected JavaExpr visitEqualOpNode(EqualOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
return convertBooleanResult(genNewBooleanData(
genMaybeProtect(operand0, Integer.MAX_VALUE) + ".equals(" + operand1.getText() + ")"));
}
@Override protected JavaExpr visitNotEqualOpNode(NotEqualOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
return convertBooleanResult(genNewBooleanData(
"! " + genMaybeProtect(operand0, Integer.MAX_VALUE) + ".equals(" +
operand1.getText() + ")"));
}
@Override protected JavaExpr visitAndOpNode(AndOpNode node) {
return visitBooleanToBooleanBinaryOpHelper(node, "&&");
}
@Override protected JavaExpr visitOrOpNode(OrOpNode node) {
return visitBooleanToBooleanBinaryOpHelper(node, "||");
}
@Override protected JavaExpr visitConditionalOpNode(ConditionalOpNode node) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
JavaExpr operand2 = visit(node.getChild(2));
Class<?> type1 = operand1.getType();
Class<?> type2 = operand2.getType();
// Set result type to nearest common ancestor of type1 and type2.
Class<?> resultType = null;
for (Class<?> type = type1; type != null; type = type.getSuperclass()) {
if (type.isAssignableFrom(type2)) {
resultType = type;
break;
}
}
if (resultType == null) {
throw new AssertionError();
}
return new JavaExpr(
genCoerceBoolean(operand0) + " ? " +
genMaybeProtect(operand1, Operator.CONDITIONAL.getPrecedence() + 1) + " : " +
genMaybeProtect(operand2, Operator.CONDITIONAL.getPrecedence() + 1),
resultType, Operator.CONDITIONAL.getPrecedence());
}
// -----------------------------------------------------------------------------------------------
// Implementation for functions.
@Override protected JavaExpr visitFunctionNode(FunctionNode node) {
String fnName = node.getFunctionName();
int numArgs = node.numChildren();
// Handle nonplugin functions.
NonpluginFunction nonpluginFn = NonpluginFunction.forFunctionName(fnName);
if (nonpluginFn != null) {
if (numArgs != nonpluginFn.getNumArgs()) {
throw SoySyntaxException.createWithoutMetaInfo(
"Function '" + fnName + "' called with the wrong number of arguments" +
" (function call \"" + node.toSourceString() + "\").");
}
switch (nonpluginFn) {
case IS_FIRST:
return visitIsFirstFunction(node);
case IS_LAST:
return visitIsLastFunction(node);
case INDEX:
return visitIndexFunction(node);
case QUOTE_KEYS_IF_JS:
return visitMapLiteralNode((MapLiteralNode) node.getChild(0));
default:
throw new AssertionError();
}
}
// Handle plugin functions.
SoyJavaSrcFunction fn = soyJavaSrcFunctionsMap.get(fnName);
if (fn != null) {
if (! fn.getValidArgsSizes().contains(numArgs)) {
throw SoySyntaxException.createWithoutMetaInfo(
"Function '" + fnName + "' called with the wrong number of arguments" +
" (function call \"" + node.toSourceString() + "\").");
}
List<JavaExpr> args = visitChildren(node);
try {
return fn.computeForJavaSrc(args);
} catch (Exception e) {
throw SoySyntaxException.createCausedWithoutMetaInfo(
"Error in function call \"" + node.toSourceString() + "\": " + e.getMessage(), e);
}
}
throw SoySyntaxException.createWithoutMetaInfo(
"Failed to find SoyJavaSrcFunction with name '" + fnName + "'" +
" (function call \"" + node.toSourceString() + "\").");
}
private JavaExpr visitIsFirstFunction(FunctionNode node) {
String varName = ((DataRefNode) node.getChild(0)).getFirstKey();
return getLocalVarTranslation(varName + "__isFirst");
}
private JavaExpr visitIsLastFunction(FunctionNode node) {
String varName = ((DataRefNode) node.getChild(0)).getFirstKey();
return getLocalVarTranslation(varName + "__isLast");
}
private JavaExpr visitIndexFunction(FunctionNode node) {
String varName = ((DataRefNode) node.getChild(0)).getFirstKey();
return getLocalVarTranslation(varName + "__index");
}
// -----------------------------------------------------------------------------------------------
// Private helpers.
private JavaExpr convertBooleanResult(String exprText) {
return new JavaExpr(exprText, BooleanData.class, Integer.MAX_VALUE);
}
private JavaExpr convertIntegerResult(String exprText) {
return new JavaExpr(exprText, IntegerData.class, Integer.MAX_VALUE);
}
private JavaExpr convertFloatResult(String exprText) {
return new JavaExpr(exprText, FloatData.class, Integer.MAX_VALUE);
}
private JavaExpr convertNumberResult(String exprText) {
return new JavaExpr(exprText, NumberData.class, Integer.MAX_VALUE);
}
private JavaExpr convertStringResult(String exprText) {
return new JavaExpr(exprText, StringData.class, Integer.MAX_VALUE);
}
private JavaExpr convertListResult(String exprText) {
return new JavaExpr(exprText, SoyListData.class, Integer.MAX_VALUE);
}
private JavaExpr convertMapResult(String exprText) {
return new JavaExpr(exprText, SoyMapData.class, Integer.MAX_VALUE);
}
private JavaExpr convertUnknownResult(String exprText) {
return new JavaExpr(exprText, SoyData.class, Integer.MAX_VALUE);
}
private JavaExpr visitBooleanToBooleanBinaryOpHelper(OperatorNode node, String javaOpToken) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
return convertBooleanResult(genNewBooleanData(genBinaryOp(
javaOpToken, genCoerceBoolean(operand0), genCoerceBoolean(operand1))));
}
private JavaExpr visitNumberToNumberBinaryOpHelper(
OperatorNode node, String javaOpToken, String utilsLibFnName) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
String integerComputation = genNewIntegerData(genBinaryOp(
javaOpToken, genIntegerValue(operand0), genIntegerValue(operand1)));
String floatComputation = genNewFloatData(genBinaryOp(
javaOpToken, genNumberValue(operand0), genNumberValue(operand1)));
if (isAlwaysTwoIntegers(operand0, operand1)) {
return convertIntegerResult(integerComputation);
} else if (isAlwaysAtLeastOneFloat(operand0, operand1)) {
return convertFloatResult(floatComputation);
} else {
return convertNumberResult(genFunctionCall(
UTILS_LIB + "." + utilsLibFnName,
genMaybeCast(operand0, NumberData.class), genMaybeCast(operand1, NumberData.class)));
}
}
private JavaExpr visitNumberToBooleanBinaryOpHelper(
OperatorNode node, String javaOpToken, String utilsLibFnName) {
JavaExpr operand0 = visit(node.getChild(0));
JavaExpr operand1 = visit(node.getChild(1));
String integerComputation = genNewBooleanData(genBinaryOp(
javaOpToken, genIntegerValue(operand0), genIntegerValue(operand1)));
String floatComputation = genNewBooleanData(genBinaryOp(
javaOpToken, genNumberValue(operand0), genNumberValue(operand1)));
if (isAlwaysTwoIntegers(operand0, operand1)) {
return convertBooleanResult(integerComputation);
} else if (isAlwaysAtLeastOneFloat(operand0, operand1)) {
return convertBooleanResult(floatComputation);
} else {
return convertBooleanResult(genFunctionCall(
UTILS_LIB + "." + utilsLibFnName,
genMaybeCast(operand0, NumberData.class), genMaybeCast(operand1, NumberData.class)));
}
}
/**
* Gets the translated expression for an in-scope local variable (or special "variable" derived
* from a foreach-loop var), or null if not found.
* @param ident The Soy local variable to translate.
* @return The translated expression for the given variable, or null if not found.
*/
private JavaExpr getLocalVarTranslation(String ident) {
for (Map<String, JavaExpr> localVarTranslationsFrame : localVarTranslations) {
JavaExpr translation = localVarTranslationsFrame.get(ident);
if (translation != null) {
return translation;
}
}
return null;
}
}