/* * 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.template.soy.jbcsrc.BytecodeUtils.SOY_VALUE_PROVIDER_TYPE; import static com.google.template.soy.jbcsrc.BytecodeUtils.SOY_VALUE_TYPE; import com.google.template.soy.data.SoyValueProvider; import com.google.template.soy.jbcsrc.api.RenderResult; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; /** A helper for generating detach operations in soy expressions. */ interface ExpressionDetacher { interface Factory { /** * Returns a new {@link ExpressionDetacher}. Any given soy expression requires at most one * detacher. */ ExpressionDetacher createExpressionDetacher(Label reattachPoint); } /** * Returns an expression for the SoyValue that is resolved by the given SoyValueProvider, * potentially detaching if it is not {@link SoyValueProvider#status() resolvable}. * * @param soyValueProvider an expression yielding a SoyValueProvider * @return an expression yielding a SoyValue returned by {@link SoyValueProvider#resolve()}. */ Expression resolveSoyValueProvider(Expression soyValueProvider); /** * Given a list of SoyValueProviders, await for all members to be resolved. * * @param soyValueProviderList an expression yielding a list of SoyValueProviders * @return an expression yielding the soyValueProviderList, but it is guaranteed that all items * will be ready to be resolved. */ Expression resolveSoyValueProviderList(Expression soyValueProviderList); /** * An {@link ExpressionDetacher} that simply returns the {@link RenderResult} returned from {@link * SoyValueProvider#status()} if it isn't done. * * <p>Generates code that looks like: * * <pre>{@code * SoyValueProvider expr = ...; * if (!expr.status().isDone()) { * return expr.status(); * } * expr.resolve(); * }</pre> */ static final class BasicDetacher implements ExpressionDetacher { static final BasicDetacher INSTANCE = new BasicDetacher(Statement.NULL_STATEMENT); private final Statement saveOperation; BasicDetacher(Statement saveOperation) { this.saveOperation = saveOperation; } @Override public Expression resolveSoyValueProvider(final Expression soyValueProvider) { soyValueProvider.checkAssignableTo(SOY_VALUE_PROVIDER_TYPE); return new Expression(SOY_VALUE_TYPE) { @Override void doGen(CodeBuilder adapter) { // We use a bunch of dup() operations in order to save extra field reads and method // invocations. This makes it difficult/confusing to use the expression api. So instead // call a bunch of unchecked invocations. // Legend: SVP = SoyValueProvider, RR = RenderResult, Z = boolean, SV = SoyValue soyValueProvider.gen(adapter); // Stack: SVP adapter.dup(); // Stack: SVP, SVP MethodRef.SOY_VALUE_PROVIDER_STATUS.invokeUnchecked(adapter); // Stack: SVP, RR adapter.dup(); // Stack: SVP, RR, RR MethodRef.RENDER_RESULT_IS_DONE.invokeUnchecked(adapter); // Stack: SVP, RR, Z Label end = new Label(); // if isDone goto end adapter.ifZCmp(Opcodes.IFNE, end); // Stack: SVP, RR saveOperation.gen(adapter); adapter.returnValue(); adapter.mark(end); adapter.pop(); // Stack: SVP MethodRef.SOY_VALUE_PROVIDER_RESOLVE.invokeUnchecked(adapter); // Stack: SV } }; } @Override public Expression resolveSoyValueProviderList(final Expression soyValueProviderList) { soyValueProviderList.checkAssignableTo(BytecodeUtils.LIST_TYPE); return new Expression(soyValueProviderList.resultType()) { @Override void doGen(CodeBuilder cb) { // We use a bunch of dup() operations in order to save extra field reads and method // invocations. This makes it difficult/confusing to use the expression api. So instead // call a bunch of unchecked invocations. // Legend: List = SoyValueProviderList, RR = RenderResult, Z = boolean soyValueProviderList.gen(cb); // Stack: List cb.dup(); // Stack: List, List MethodRef.RUNTIME_GET_LIST_STATUS.invokeUnchecked(cb); // Stack: List, RR cb.dup(); // Stack: List, RR, RR MethodRef.RENDER_RESULT_IS_DONE.invokeUnchecked(cb); // Stack: List, RR, Z Label end = new Label(); // if isDone goto end cb.ifZCmp(Opcodes.IFNE, end); // Stack: List, RR saveOperation.gen(cb); cb.returnValue(); cb.mark(end); cb.pop(); // Stack: List } }; } } }