/*
* Copyright 2009 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.jssrc.internal;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.basetree.SyntaxVersion;
import com.google.template.soy.coredirectives.CoreDirectiveUtils;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.internal.i18n.BidiGlobalDir;
import com.google.template.soy.passes.CombineConsecutiveRawTextNodesVisitor;
import com.google.template.soy.shared.restricted.SoyFunction;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
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.SoyFileSetNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.MsgBlockNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
import javax.inject.Inject;
/**
* Visitor for replacing any {@code PrintNode} whose expression is a single call to {@code
* bidiMark()}, {@code bidiStartEdge()}, or {@code bidiEndEdge()} with an equivalent {@code
* RawTextNode}. If these replacements cause the resulting tree to contain any sequences of
* consecutive {@code RawTextNodes}, they will each be simplified to a single {@code RawTextNode}.
*
* <p>This pass does not replace {@code PrintNode}s containing directives other than {@code |id},
* {@code |noAutoescape}, and {@code |escapeHtml}.
*
* <p>{@link #exec} must be called on a full parse tree.
*
*/
public class OptimizeBidiCodeGenVisitor extends AbstractSoyNodeVisitor<Void> {
// Names of the bidi functions for which we optimize code generation.
private static final String BIDI_MARK_FN_NAME = "bidiMark";
private static final String BIDI_START_EDGE_FN_NAME = "bidiStartEdge";
private static final String BIDI_END_EDGE_FN_NAME = "bidiEndEdge";
/** The bidi global directionality. */
private BidiGlobalDir bidiGlobalDir;
/** The node id generator for the parse tree. Retrieved from the root SoyFileSetNode. */
private IdGenerator nodeIdGen;
/** Whether a replacement was made. */
boolean madeReplacement;
/** @param bidiGlobalDir The bidi global directionality. */
@Inject
public OptimizeBidiCodeGenVisitor(BidiGlobalDir bidiGlobalDir) {
this.bidiGlobalDir = bidiGlobalDir;
}
// -----------------------------------------------------------------------------------------------
// Implementations for specific nodes.
@Override
protected void visitSoyFileSetNode(SoyFileSetNode node) {
// if don't have a static value, skip
if (!bidiGlobalDir.isStaticValue()) {
return;
}
// Retrieve the node id generator.
nodeIdGen = node.getNodeIdGenerator();
// Run the pass.
madeReplacement = false;
visitChildren(node);
// If we made any replacements, we may have created consecutive RawTextNodes, so clean them up.
if (madeReplacement) {
new CombineConsecutiveRawTextNodesVisitor(node.getNodeIdGenerator()).exec(node);
}
}
@Override
protected void visitPrintNode(PrintNode node) {
// We replace this node if and only if it:
// (a) is in V2 syntax,
// (b) is not a child of a MsgBlockNode,
// (c) has a single call to bidiMark(), bidiStartEdge(), or bidiEndEdge() as its expression and
// the global directionality is static,
// (d) doesn't have directives other than "|id", "|noAutoescape", and "|escapeHtml".
if (!node.couldHaveSyntaxVersionAtLeast(SyntaxVersion.V2_0)) {
return;
}
ParentSoyNode<StandaloneNode> parent = node.getParent();
if (parent instanceof MsgBlockNode) {
return; // don't replace this node
}
ExprNode expr = node.getExpr().getRoot();
if (!(expr instanceof FunctionNode)) {
return; // don't replace this node
}
SoyFunction fn = ((FunctionNode) expr).getSoyFunction();
if (fn == null) {
return;
}
String rawText;
switch (fn.getName()) {
case BIDI_MARK_FN_NAME:
rawText = (bidiGlobalDir.getStaticValue() < 0) ? "\\u200F" /*RLM*/ : "\\u200E" /*LRM*/;
break;
case BIDI_START_EDGE_FN_NAME:
rawText = (bidiGlobalDir.getStaticValue() < 0) ? "right" : "left";
break;
case BIDI_END_EDGE_FN_NAME:
rawText = (bidiGlobalDir.getStaticValue() < 0) ? "left" : "right";
break;
default:
return; // don't replace this node
}
for (PrintDirectiveNode directiveNode : node.getChildren()) {
if (!CoreDirectiveUtils.isCoreDirective(directiveNode)) {
return; // don't replace this node
}
}
// Replace this node with a RawTextNode.
parent.replaceChild(
node, new RawTextNode(nodeIdGen.genId(), rawText, node.getSourceLocation()));
madeReplacement = true;
}
// -----------------------------------------------------------------------------------------------
// Fallback implementation.
@Override
protected void visitSoyNode(SoyNode node) {
if (node instanceof ParentSoyNode<?>) {
visitChildrenAllowingConcurrentModification((ParentSoyNode<?>) node);
}
}
}