/* * Copyright 2015 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.pysrc.internal; import com.google.template.soy.shared.internal.ApiCallScope; import com.google.template.soy.soytree.AbstractReturningSoyNodeVisitor; import com.google.template.soy.soytree.CallNode; import com.google.template.soy.soytree.CallParamContentNode; import com.google.template.soy.soytree.CallParamValueNode; import com.google.template.soy.soytree.CssNode; import com.google.template.soy.soytree.DebuggerNode; import com.google.template.soy.soytree.ForNode; import com.google.template.soy.soytree.ForeachNode; import com.google.template.soy.soytree.IfCondNode; import com.google.template.soy.soytree.IfElseNode; import com.google.template.soy.soytree.IfNode; import com.google.template.soy.soytree.LetNode; import com.google.template.soy.soytree.LogNode; import com.google.template.soy.soytree.MsgFallbackGroupNode; import com.google.template.soy.soytree.MsgNode; import com.google.template.soy.soytree.PrintNode; import com.google.template.soy.soytree.RawTextNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.SoyNode.ParentSoyNode; import com.google.template.soy.soytree.SwitchNode; import com.google.template.soy.soytree.XidNode; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; /** * Visitor to determine whether the output string for the subtree rooted at a given node is * computable as the concatenation of one or more Python expressions. If this is false, it means the * generated code for computing the node's output must include one or more full Python statements. * * <p>Important: This class is in {@link ApiCallScope} because it memoizes results that are reusable * for the same parse tree. If we change the parse tree between uses of the scoped instance, then * the results may not be correct. (In that case, we would need to take this class out of {@code * ApiCallScope} and rewrite the code somehow to still take advantage of the memoized results to the * extent that they remain correct.) * */ @ApiCallScope class IsComputableAsPyExprVisitor extends AbstractReturningSoyNodeVisitor<Boolean> { /** The memoized results of past visits to nodes. */ private final Map<SoyNode, Boolean> memoizedResults; @Inject IsComputableAsPyExprVisitor() { memoizedResults = new HashMap<>(); } /** * Executes this visitor on the children of the given node, and returns true if all children are * computable as PyExprs. Ignores whether the given node itself is computable as PyExprs or not. */ public Boolean execOnChildren(ParentSoyNode<?> node) { return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visit(SoyNode node) { if (memoizedResults.containsKey(node)) { return memoizedResults.get(node); } else { Boolean result = super.visit(node); memoizedResults.put(node, result); return result; } } // ----------------------------------------------------------------------------------------------- // Implementations for specific nodes. @Override protected Boolean visitRawTextNode(RawTextNode node) { return true; } @Override protected Boolean visitPrintNode(PrintNode node) { return true; } @Override protected Boolean visitMsgFallbackGroupNode(MsgFallbackGroupNode node) { return true; } @Override protected Boolean visitMsgNode(MsgNode node) { return true; } @Override protected Boolean visitLetNode(LetNode node) { return false; } @Override protected Boolean visitCssNode(CssNode node) { return true; } @Override protected Boolean visitXidNode(XidNode node) { return true; } @Override protected Boolean visitIfNode(IfNode node) { // If all children are computable as Python expressions, then this 'if' statement can be written // as an expression as well, using the ternary conditional operator ("'a' if x else 'b'"). return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visitIfCondNode(IfCondNode node) { return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visitIfElseNode(IfElseNode node) { return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visitSwitchNode(SwitchNode node) { return false; } @Override protected Boolean visitForeachNode(ForeachNode node) { // TODO(dcphillips): Consider using list comprehensions to generate the output of a foreach. return false; } @Override protected Boolean visitForNode(ForNode node) { return false; } @Override protected Boolean visitCallNode(CallNode node) { return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visitCallParamValueNode(CallParamValueNode node) { return true; } @Override protected Boolean visitCallParamContentNode(CallParamContentNode node) { return areChildrenComputableAsPyExprs(node); } @Override protected Boolean visitLogNode(LogNode node) { return false; } @Override protected Boolean visitDebuggerNode(DebuggerNode node) { return false; } // ----------------------------------------------------------------------------------------------- // Private helpers. /** * Private helper to check whether all SoyNode children of a given parent node satisfy * IsComputableAsPyExprVisitor. ExprNode children are assumed to be computable as PyExprs. * * @param node The parent node whose children to check. * @return True if all children satisfy IsComputableAsPyExprVisitor. */ private boolean areChildrenComputableAsPyExprs(ParentSoyNode<?> node) { for (SoyNode child : node.getChildren()) { // Note: Save time by not visiting RawTextNode and PrintNode children. if (!(child instanceof RawTextNode) && !(child instanceof PrintNode)) { if (!visit(child)) { return false; } } } return true; } }