/*
* 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.jbcsrc;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.template.soy.jbcsrc.BytecodeUtils.constant;
import com.google.common.base.Optional;
import com.google.template.soy.data.SoyValueProvider;
import com.google.template.soy.exprtree.DataAccessNode;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.exprtree.NullNode;
import com.google.template.soy.exprtree.OperatorNodes.ConditionalOpNode;
import com.google.template.soy.exprtree.OperatorNodes.NullCoalescingOpNode;
import com.google.template.soy.exprtree.VarRefNode;
import com.google.template.soy.jbcsrc.ExpressionCompiler.BasicExpressionCompiler;
import com.google.template.soy.soytree.defn.InjectedParam;
import com.google.template.soy.soytree.defn.LocalVar;
import com.google.template.soy.soytree.defn.TemplateParam;
import javax.annotation.Nullable;
import org.objectweb.asm.Label;
/**
* Attempts to compile an {@link ExprNode} to an {@link Expression} for a {@link SoyValueProvider}
* in order to preserve laziness.
*
* <p>There are two ways to use this depending on the specific requirements of the caller
*
* <ul>
* <li>{@link #compileAvoidingBoxing(ExprNode, Label)} attempts to compile the expression to a
* {@link SoyValueProvider} but without introducing any unnecessary boxing operations.
* Generating detach logic is OK. This case is for print operations, where callers may want to
* call {@link SoyValueProvider#renderAndResolve} to incrementally print the value. However,
* this is only desirable if the expression is naturally a {@link SoyValueProvider}.
* <li>{@link #compileAvoidingDetaches(ExprNode)} attempts to compile the expression to a {@link
* SoyValueProvider} with no detach logic. This is for passing data to templates or defining
* variables with {@code let} statements. In these cases boxing operations are fine (because
* the alternative is to use the {@link LazyClosureCompiler} which necessarily boxes the
* expression into a custom SoyValueProvider.
* </ul>
*
* <p>This is used as a basic optimization and as a necessary tool to implement template
* transclusions. If a template has a parameter {@code foo} then we want to be able to render it via
* {@link SoyValueProvider#renderAndResolve} so that we can render it incrementally.
*/
final class ExpressionToSoyValueProviderCompiler {
/**
* Create an expression compiler that can implement complex detaching logic with the given {@link
* ExpressionDetacher.Factory}
*/
static ExpressionToSoyValueProviderCompiler create(
ExpressionCompiler exprCompiler, TemplateParameterLookup variables) {
return new ExpressionToSoyValueProviderCompiler(exprCompiler, variables);
}
private final TemplateParameterLookup variables;
private final ExpressionCompiler exprCompiler;
private ExpressionToSoyValueProviderCompiler(
ExpressionCompiler exprCompiler, TemplateParameterLookup variables) {
this.exprCompiler = exprCompiler;
this.variables = variables;
}
/**
* Compiles the given expression tree to a sequence of bytecode in the current method visitor.
*
* <p>If successful, the generated bytecode will resolve to a {@link SoyValueProvider} if it can
* be done without introducing unnecessary boxing operations. This is intended for situations
* (like print operations) where calling {@link SoyValueProvider#renderAndResolve} would be better
* than calling {@link #toString()} and passing directly to the output.
*/
Optional<Expression> compileAvoidingBoxing(ExprNode node, Label reattachPoint) {
checkNotNull(node);
return new CompilerVisitor(variables, null, exprCompiler.asBasicCompiler(reattachPoint))
.exec(node);
}
/**
* Compiles the given expression tree to a sequence of bytecode in the current method visitor.
*
* <p>If successful, the generated bytecode will resolve to a {@link SoyValueProvider} if it can
* be done without introducing any detach operations. This is intended for situations where we
* need to model the expression as a SoyValueProvider to satisfy a contract (e.g. let nodes and
* params), but we also want to preserve any laziness. So boxing is fine, but detaches are not.
*/
Optional<Expression> compileAvoidingDetaches(ExprNode node) {
checkNotNull(node);
return new CompilerVisitor(variables, exprCompiler, null).exec(node);
}
private static final class CompilerVisitor
extends EnhancedAbstractExprNodeVisitor<Optional<Expression>> {
final TemplateParameterLookup variables;
// depending on the mode one or the other of these will be null
@Nullable final ExpressionCompiler exprCompiler;
@Nullable final BasicExpressionCompiler detachingExprCompiler;
CompilerVisitor(
TemplateParameterLookup variables,
ExpressionCompiler exprCompiler,
BasicExpressionCompiler detachingExprCompiler) {
this.variables = variables;
checkArgument((exprCompiler == null) != (detachingExprCompiler == null));
this.exprCompiler = exprCompiler;
this.detachingExprCompiler = detachingExprCompiler;
}
private boolean allowsBoxing() {
return exprCompiler != null;
}
private boolean allowsDetaches() {
return detachingExprCompiler != null;
}
@Override
protected final Optional<Expression> visitExprRootNode(ExprRootNode node) {
return visit(node.getRoot());
}
// Primitive value constants
@Override
protected Optional<Expression> visitNullNode(NullNode node) {
// unlike other primitives, this doesn't really count as boxing, just a read of a static
// constant field. so we always do it
return Optional.of(FieldRef.NULL_PROVIDER.accessor());
}
@Override
protected Optional<Expression> visitNullCoalescingOpNode(NullCoalescingOpNode node) {
// All non-trivial ?: will require detaches for the left hand side.
if (allowsDetaches()) {
// for '$foo ?: $bar' we always have to eagerly evaluate $foo but $bar could be lazy.
// of course if $foo is not null, then we will need to box it, however the current support
// for ?: in ExpressionCompiler also always boxes everything so we aren't really losing
// anything here.
Optional<Expression> right = visit(node.getRightChild());
if (!right.isPresent()) {
return Optional.absent();
}
Expression left =
detachingExprCompiler
.compile(node.getLeftChild())
.box()
.checkedCast(SoyValueProvider.class);
return Optional.of(BytecodeUtils.firstNonNull(left, right.get()));
}
return visitExprNode(node);
}
@Override
protected final Optional<Expression> visitConditionalOpNode(ConditionalOpNode node) {
if (allowsDetaches()) {
Optional<Expression> trueBranch = visit(node.getChild(1));
Optional<Expression> falseBranch = visit(node.getChild(2));
// We only compile as an SVP if both branches are able to be compiled as such. Technically,
// we could also support cases where only one branch is compilable to an SVP, but i doubt
// that would be that important.
if (!trueBranch.isPresent() || !falseBranch.isPresent()) {
return Optional.absent();
}
Expression condition = detachingExprCompiler.compile(node.getChild(0)).coerceToBoolean();
return Optional.of(BytecodeUtils.ternary(condition, trueBranch.get(), falseBranch.get()));
}
return visitExprNode(node);
}
@Override
Optional<Expression> visitForeachLoopVar(VarRefNode varRef, LocalVar local) {
return Optional.of(variables.getLocal(local));
}
@Override
Optional<Expression> visitParam(VarRefNode varRef, TemplateParam param) {
return Optional.of(variables.getParam(param));
}
@Override
Optional<Expression> visitIjParam(VarRefNode node, InjectedParam ij) {
return Optional.of(
MethodRef.RUNTIME_GET_FIELD_PROVIDER.invoke(
variables.getIjRecord(), constant(ij.name())));
}
@Override
Optional<Expression> visitLetNodeVar(VarRefNode varRef, LocalVar local) {
return Optional.of(variables.getLocal(local));
}
@Override
protected Optional<Expression> visitDataAccessNode(DataAccessNode node) {
// TODO(lukes): implement special case for allowsDetaches(). The complex part will be sharing
// null safety access logic with the ExpressionCompiler
return visitExprNode(node);
}
@Override
protected final Optional<Expression> visitExprNode(ExprNode node) {
if (allowsBoxing()) {
Optional<SoyExpression> compileWithNoDetaches = exprCompiler.compileWithNoDetaches(node);
if (compileWithNoDetaches.isPresent()) {
return Optional.<Expression>of(compileWithNoDetaches.get().box());
}
}
return Optional.absent();
}
}
}