/* * 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.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.google.template.soy.base.internal.BaseUtils; import com.google.template.soy.base.internal.LegacyInternalSyntaxException; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.exprtree.Operator; import com.google.template.soy.msgs.internal.MsgUtils; import com.google.template.soy.pysrc.internal.MsgFuncGenerator.MsgFuncGeneratorFactory; import com.google.template.soy.pysrc.restricted.PyExpr; import com.google.template.soy.pysrc.restricted.PyExprUtils; import com.google.template.soy.pysrc.restricted.PyStringExpr; import com.google.template.soy.pysrc.restricted.SoyPySrcPrintDirective; import com.google.template.soy.soytree.AbstractSoyNodeVisitor; import com.google.template.soy.soytree.CallNode; import com.google.template.soy.soytree.CallParamContentNode; import com.google.template.soy.soytree.CssNode; 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.MsgFallbackGroupNode; import com.google.template.soy.soytree.PrintDirectiveNode; 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.XidNode; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Visitor for generating Python expressions for parse tree nodes. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public class GenPyExprsVisitor extends AbstractSoyNodeVisitor<List<PyExpr>> { /** Injectable factory for creating an instance of this class. */ public static interface GenPyExprsVisitorFactory { public GenPyExprsVisitor create(LocalVariableStack localVarExprs, ErrorReporter errorReporter); } /** Map of all SoyPySrcPrintDirectives (name to directive). */ private Map<String, SoyPySrcPrintDirective> soyPySrcDirectivesMap; private final IsComputableAsPyExprVisitor isComputableAsPyExprVisitor; private final GenPyExprsVisitorFactory genPyExprsVisitorFactory; private final GenPyCallExprVisitor genPyCallExprVisitor; private final MsgFuncGeneratorFactory msgFuncGeneratorFactory; private final LocalVariableStack localVarExprs; /** List to collect the results. */ private List<PyExpr> pyExprs; private final ErrorReporter errorReporter; /** @param soyPySrcDirectivesMap Map of all SoyPySrcPrintDirectives (name to directive). */ @AssistedInject GenPyExprsVisitor( ImmutableMap<String, SoyPySrcPrintDirective> soyPySrcDirectivesMap, IsComputableAsPyExprVisitor isComputableAsPyExprVisitor, GenPyExprsVisitorFactory genPyExprsVisitorFactory, MsgFuncGeneratorFactory msgFuncGeneratorFactory, GenPyCallExprVisitor genPyCallExprVisitor, @Assisted LocalVariableStack localVarExprs, @Assisted ErrorReporter errorReporter) { this.soyPySrcDirectivesMap = soyPySrcDirectivesMap; this.isComputableAsPyExprVisitor = isComputableAsPyExprVisitor; this.genPyExprsVisitorFactory = genPyExprsVisitorFactory; this.genPyCallExprVisitor = genPyCallExprVisitor; this.msgFuncGeneratorFactory = msgFuncGeneratorFactory; this.localVarExprs = localVarExprs; this.errorReporter = errorReporter; } @Override public List<PyExpr> exec(SoyNode node) { Preconditions.checkArgument(isComputableAsPyExprVisitor.exec(node)); pyExprs = new ArrayList<>(); visit(node); return pyExprs; } /** * Executes this visitor on the children of the given node, without visiting the given node * itself. */ List<PyExpr> execOnChildren(ParentSoyNode<?> node) { Preconditions.checkArgument(isComputableAsPyExprVisitor.execOnChildren(node)); pyExprs = new ArrayList<>(); visitChildren(node); return pyExprs; } // ----------------------------------------------------------------------------------------------- // Implementations for specific nodes. /** * Example: * * <pre> * I'm feeling lucky! * </pre> * * generates * * <pre> * 'I\'m feeling lucky!' * </pre> */ @Override protected void visitRawTextNode(RawTextNode node) { // Escape special characters in the text before writing as a string. String exprText = BaseUtils.escapeToSoyString(node.getRawText(), false); pyExprs.add(new PyStringExpr(exprText)); } /** * Visiting a print node accomplishes 3 basic tasks. It loads data, it performs any operations * needed, and it executes the appropriate print directives. * * <p>TODO(dcphillips): Add support for local variables once LetNode are supported. * * <p>Example: * * <pre> * {$boo |changeNewlineToBr} * {$goo + 5} * </pre> * * might generate * * <pre> * sanitize.change_newline_to_br(data.get('boo')) * data.get('goo') + 5 * </pre> */ @Override protected void visitPrintNode(PrintNode node) { TranslateToPyExprVisitor translator = new TranslateToPyExprVisitor(localVarExprs, errorReporter); PyExpr pyExpr = translator.exec(node.getExpr()); // Process directives. for (PrintDirectiveNode directiveNode : node.getChildren()) { // Get directive. SoyPySrcPrintDirective directive = soyPySrcDirectivesMap.get(directiveNode.getName()); if (directive == null) { throw LegacyInternalSyntaxException.createWithMetaInfo( "Failed to find SoyPySrcPrintDirective with name '" + directiveNode.getName() + "'" + " (tag " + node.toSourceString() + ")", directiveNode.getSourceLocation()); } // Get directive args. List<ExprRootNode> args = directiveNode.getArgs(); if (!directive.getValidArgsSizes().contains(args.size())) { throw LegacyInternalSyntaxException.createWithMetaInfo( "Print directive '" + directiveNode.getName() + "' used with the wrong number of" + " arguments (tag " + node.toSourceString() + ").", directiveNode.getSourceLocation()); } // Translate directive args. List<PyExpr> argsPyExprs = new ArrayList<>(args.size()); for (ExprRootNode arg : args) { argsPyExprs.add(translator.exec(arg)); } // Apply directive. pyExpr = directive.applyForPySrc(pyExpr, argsPyExprs); } pyExprs.add(pyExpr); } @Override protected void visitMsgFallbackGroupNode(MsgFallbackGroupNode node) { PyExpr msg = msgFuncGeneratorFactory.create(node.getMsg(), localVarExprs, errorReporter).getPyExpr(); // MsgFallbackGroupNode could only have one child or two children. See MsgFallbackGroupNode. if (node.hasFallbackMsg()) { StringBuilder pyExprTextSb = new StringBuilder(); PyExpr fallbackMsg = msgFuncGeneratorFactory .create(node.getFallbackMsg(), localVarExprs, errorReporter) .getPyExpr(); // Build Python ternary expression: a if cond else c pyExprTextSb.append(msg.getText()).append(" if "); // The fallback message is only used if the first message is not available, but the fallback // is. So availability of both messages must be tested. long firstId = MsgUtils.computeMsgIdForDualFormat(node.getMsg()); long secondId = MsgUtils.computeMsgIdForDualFormat(node.getFallbackMsg()); pyExprTextSb .append(PyExprUtils.TRANSLATOR_NAME) .append(".is_msg_available(") .append(firstId) .append(")") .append(" or not ") .append(PyExprUtils.TRANSLATOR_NAME) .append(".is_msg_available(") .append(secondId) .append(")"); pyExprTextSb.append(" else ").append(fallbackMsg.getText()); msg = new PyStringExpr( pyExprTextSb.toString(), PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL)); } // Escaping directives apply to messages, especially in attribute context. for (String directiveName : node.getEscapingDirectiveNames()) { SoyPySrcPrintDirective directive = soyPySrcDirectivesMap.get(directiveName); Preconditions.checkNotNull( directive, "Contextual autoescaping produced a bogus directive: %s", directiveName); msg = directive.applyForPySrc(msg, ImmutableList.<PyExpr>of()); } pyExprs.add(msg); } @Override protected void visitXidNode(XidNode node) { // preliminary xid implementation. just uses the css renaming map StringBuilder sb = new StringBuilder("runtime.get_xid_name('").append(node.getText()).append("')"); pyExprs.add(new PyExpr(sb.toString(), Integer.MAX_VALUE)); } @Override protected void visitCssNode(CssNode node) { StringBuilder sb = new StringBuilder("runtime.get_css_name("); ExprRootNode componentNameExpr = node.getComponentNameExpr(); if (componentNameExpr != null) { TranslateToPyExprVisitor translator = new TranslateToPyExprVisitor(localVarExprs, errorReporter); PyExpr basePyExpr = translator.exec(componentNameExpr); sb.append(basePyExpr.getText()).append(", "); } sb.append("'").append(node.getSelectorText()).append("')"); pyExprs.add(new PyExpr(sb.toString(), Integer.MAX_VALUE)); } /** * If all the children are computable as expressions, the IfNode can be written as a ternary * conditional expression. */ @Override protected void visitIfNode(IfNode node) { // Create another instance of this visitor for generating Python expressions from children. GenPyExprsVisitor genPyExprsVisitor = genPyExprsVisitorFactory.create(localVarExprs, errorReporter); TranslateToPyExprVisitor translator = new TranslateToPyExprVisitor(localVarExprs, errorReporter); StringBuilder pyExprTextSb = new StringBuilder(); boolean hasElse = false; for (SoyNode child : node.getChildren()) { if (child instanceof IfCondNode) { IfCondNode icn = (IfCondNode) child; // Python ternary conditional expressions modify the order of the conditional from // <conditional> ? <true> : <false> to // <true> if <conditional> else <false> PyExpr condBlock = PyExprUtils.concatPyExprs(genPyExprsVisitor.exec(icn)).toPyString(); condBlock = PyExprUtils.maybeProtect( condBlock, PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL)); pyExprTextSb.append(condBlock.getText()); // Append the conditional and if/else syntax. PyExpr condPyExpr = translator.exec(icn.getExpr()); pyExprTextSb.append(" if ").append(condPyExpr.getText()).append(" else "); } else if (child instanceof IfElseNode) { hasElse = true; IfElseNode ien = (IfElseNode) child; PyExpr elseBlock = PyExprUtils.concatPyExprs(genPyExprsVisitor.exec(ien)).toPyString(); pyExprTextSb.append(elseBlock.getText()); } else { throw new AssertionError("Unexpected if child node type. Child: " + child); } } if (!hasElse) { pyExprTextSb.append("''"); } // By their nature, inline'd conditionals can only contain output strings, so they can be // treated as a string type with a conditional precedence. pyExprs.add( new PyStringExpr( pyExprTextSb.toString(), PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL))); } @Override protected void visitIfCondNode(IfCondNode node) { visitChildren(node); } @Override protected void visitIfElseNode(IfElseNode node) { visitChildren(node); } @Override protected void visitCallNode(CallNode node) { pyExprs.add(genPyCallExprVisitor.exec(node, localVarExprs, errorReporter)); } @Override protected void visitCallParamContentNode(CallParamContentNode node) { visitChildren(node); } }