Search in sources :

Example 1 with RangeArgs

use of com.google.template.soy.shared.RangeArgs in project closure-templates by google.

the class SoyNodeCompiler method visitForNode.

@Override
protected Statement visitForNode(ForNode node) {
    ForNonemptyNode nonEmptyNode = (ForNonemptyNode) node.getChild(0);
    Optional<RangeArgs> exprAsRangeArgs = RangeArgs.createFromNode(node);
    Scope scope = variables.enterScope();
    final Variable indexVar;
    final List<Statement> initializers = new ArrayList<>();
    final Variable sizeVar;
    final Variable itemVar;
    if (exprAsRangeArgs.isPresent()) {
        final CompiledForeachRangeArgs compiledArgs = calculateRangeArgs(node, scope);
        initializers.addAll(compiledArgs.initStatements());
        // The size is just the number of items in the range.  The logic is a little tricky so we
        // implement it in a runtime function: JbcsrcRuntime.rangeLoopLength
        sizeVar = scope.createSynthetic(SyntheticVarName.foreachLoopLength(nonEmptyNode), MethodRef.RUNTIME_RANGE_LOOP_LENGTH.invoke(compiledArgs.start(), compiledArgs.end(), compiledArgs.step()), DERIVED);
        indexVar = scope.createSynthetic(SyntheticVarName.foreachLoopIndex(nonEmptyNode), constant(0), STORE);
        itemVar = scope.create(nonEmptyNode.getVarName(), new Expression(Type.LONG_TYPE, Feature.CHEAP) {

            @Override
            protected void doGen(CodeBuilder adapter) {
                // executes ((long) start + index * step)
                compiledArgs.start().gen(adapter);
                compiledArgs.step().gen(adapter);
                indexVar.local().gen(adapter);
                adapter.visitInsn(Opcodes.IMUL);
                adapter.visitInsn(Opcodes.IADD);
                adapter.cast(Type.INT_TYPE, Type.LONG_TYPE);
            }
        }, DERIVED);
    } else {
        SoyExpression expr = exprCompiler.compile(node.getExpr()).unboxAs(List.class);
        Variable listVar = scope.createSynthetic(SyntheticVarName.foreachLoopList(nonEmptyNode), expr, STORE);
        initializers.add(listVar.initializer());
        sizeVar = scope.createSynthetic(SyntheticVarName.foreachLoopLength(nonEmptyNode), MethodRef.LIST_SIZE.invoke(listVar.local()), DERIVED);
        indexVar = scope.createSynthetic(SyntheticVarName.foreachLoopIndex(nonEmptyNode), constant(0), STORE);
        itemVar = scope.create(nonEmptyNode.getVarName(), MethodRef.LIST_GET.invoke(listVar.local(), indexVar.local()).checkedCast(SOY_VALUE_PROVIDER_TYPE), DERIVED);
    }
    initializers.add(sizeVar.initializer());
    final Statement loopBody = visitChildrenInNewScope(nonEmptyNode);
    final Statement exitScope = scope.exitScope();
    // it important for this to be generated after exitScope is called (or before enterScope)
    final Statement emptyBlock = node.numChildren() == 2 ? visitChildrenInNewScope(node.getChild(1)) : null;
    return new Statement() {

        @Override
        protected void doGen(CodeBuilder adapter) {
            for (Statement initializer : initializers) {
                initializer.gen(adapter);
            }
            sizeVar.local().gen(adapter);
            Label emptyListLabel = new Label();
            adapter.ifZCmp(Opcodes.IFEQ, emptyListLabel);
            indexVar.initializer().gen(adapter);
            Label loopStart = adapter.mark();
            itemVar.initializer().gen(adapter);
            loopBody.gen(adapter);
            // index++
            adapter.iinc(indexVar.local().index(), 1);
            indexVar.local().gen(adapter);
            sizeVar.local().gen(adapter);
            // if index < list.size(), goto loopstart
            adapter.ifICmp(Opcodes.IFLT, loopStart);
            // exit the loop
            exitScope.gen(adapter);
            if (emptyBlock != null) {
                Label skipIfEmptyBlock = new Label();
                adapter.goTo(skipIfEmptyBlock);
                adapter.mark(emptyListLabel);
                emptyBlock.gen(adapter);
                adapter.mark(skipIfEmptyBlock);
            } else {
                adapter.mark(emptyListLabel);
            }
        }
    };
}
Also used : Variable(com.google.template.soy.jbcsrc.TemplateVariableManager.Variable) RangeArgs(com.google.template.soy.shared.RangeArgs) Statement(com.google.template.soy.jbcsrc.restricted.Statement) ArrayList(java.util.ArrayList) Label(org.objectweb.asm.Label) CodeBuilder(com.google.template.soy.jbcsrc.restricted.CodeBuilder) SoyExpression(com.google.template.soy.jbcsrc.restricted.SoyExpression) Scope(com.google.template.soy.jbcsrc.TemplateVariableManager.Scope) SoyExpression(com.google.template.soy.jbcsrc.restricted.SoyExpression) Expression(com.google.template.soy.jbcsrc.restricted.Expression) ForNonemptyNode(com.google.template.soy.soytree.ForNonemptyNode)

Example 2 with RangeArgs

use of com.google.template.soy.shared.RangeArgs in project closure-templates by google.

the class TemplateAnalysis method isListExpressionEmpty.

// consider moving this to SoyTreeUtils or some similar place.
private static StaticAnalysisResult isListExpressionEmpty(ForNode node) {
    Optional<RangeArgs> rangeArgs = RangeArgs.createFromNode(node);
    if (rangeArgs.isPresent()) {
        return isRangeExpressionEmpty(rangeArgs.get());
    }
    ExprNode expr = node.getExpr().getRoot();
    if (expr instanceof ListLiteralNode) {
        return ((ListLiteralNode) expr).numChildren() > 0 ? StaticAnalysisResult.FALSE : StaticAnalysisResult.TRUE;
    }
    return StaticAnalysisResult.UNKNOWN;
}
Also used : ExprNode(com.google.template.soy.exprtree.ExprNode) ListLiteralNode(com.google.template.soy.exprtree.ListLiteralNode) RangeArgs(com.google.template.soy.shared.RangeArgs)

Example 3 with RangeArgs

use of com.google.template.soy.shared.RangeArgs in project closure-templates by google.

the class RenderVisitor method visitForNode.

@Override
protected void visitForNode(ForNode node) {
    Optional<RangeArgs> exprAsRangeArgs = RangeArgs.createFromNode(node);
    if (exprAsRangeArgs.isPresent()) {
        RangeArgs args = exprAsRangeArgs.get();
        int step = args.increment().isPresent() ? evalRangeArg(node, args.increment().get()) : 1;
        int start = args.start().isPresent() ? evalRangeArg(node, args.start().get()) : 0;
        int end = evalRangeArg(node, args.limit());
        int length = end - start;
        if ((length ^ step) < 0) {
            // handle ifempty, if present
            if (node.numChildren() == 2) {
                visit(node.getChild(1));
            }
        } else {
            ForNonemptyNode child = (ForNonemptyNode) node.getChild(0);
            int size = length / step + (length % step == 0 ? 0 : 1);
            for (int i = 0; i < size; ++i) {
                executeForeachBody(child, i, IntegerData.forValue(start + step * i), size);
            }
        }
    } else {
        SoyValue dataRefValue = eval(node.getExpr(), node);
        if (!(dataRefValue instanceof SoyList)) {
            throw RenderException.createWithSource("In 'foreach' command " + node.toSourceString() + ", the data reference does not " + "resolve to a SoyList " + "(encountered type " + dataRefValue.getClass().getName() + ").", node);
        }
        SoyList foreachList = (SoyList) dataRefValue;
        int listLength = foreachList.length();
        if (listLength > 0) {
            // Case 1: Nonempty list.
            ForNonemptyNode child = (ForNonemptyNode) node.getChild(0);
            for (int i = 0; i < listLength; ++i) {
                executeForeachBody(child, i, foreachList.getProvider(i), listLength);
            }
        } else {
            // Case 2: Empty list. If the 'ifempty' node exists, visit it.
            if (node.numChildren() == 2) {
                visit(node.getChild(1));
            }
        }
    }
}
Also used : RangeArgs(com.google.template.soy.shared.RangeArgs) SoyList(com.google.template.soy.data.SoyList) SoyValue(com.google.template.soy.data.SoyValue) ForNonemptyNode(com.google.template.soy.soytree.ForNonemptyNode)

Example 4 with RangeArgs

use of com.google.template.soy.shared.RangeArgs in project closure-templates by google.

the class GenJsCodeVisitor method visitForNode.

/**
 * Example:
 *
 * <pre>
 *   {for $foo in $boo.foos}
 *     ...
 *   {ifempty}
 *     ...
 *   {/for}
 * </pre>
 *
 * might generate
 *
 * <pre>
 *   var foo2List = opt_data.boo.foos;
 *   var foo2ListLen = foo2List.length;
 *   if (foo2ListLen > 0) {
 *     ...
 *   } else {
 *     ...
 *   }
 * </pre>
 */
@Override
protected void visitForNode(ForNode node) {
    boolean hasIfempty = (node.numChildren() == 2);
    // NOTE: below we call id(varName) on a number of variables instead of using
    // VariableDeclaration.ref(),  this is because the refs() might be referenced on the other side
    // of a call to visitChildrenReturningCodeChunk.
    // That will break the normal behavior of FormattingContext being able to tell whether or not an
    // initial statement has already been generated because it eagerly coerces the value to a
    // string.  This will lead to redundant variable declarations.
    // When visitChildrenReturningCodeChunk is gone, this can be cleaned up, but for now we have to
    // manually decide where to declare the variables.
    List<CodeChunk> statements = new ArrayList<>();
    // Build some local variable names.
    ForNonemptyNode nonEmptyNode = (ForNonemptyNode) node.getChild(0);
    String varPrefix = nonEmptyNode.getVarName() + node.getId();
    // TODO(user): A more consistent pattern for local variable management.
    String limitName = varPrefix + "ListLen";
    CodeChunk.WithValue limitInitializer;
    Optional<RangeArgs> args = RangeArgs.createFromNode(node);
    Function<CodeChunk.WithValue, CodeChunk.WithValue> getDataItemFunction;
    if (args.isPresent()) {
        RangeArgs range = args.get();
        // if any of the expressions are too expensive, allocate local variables for them
        final CodeChunk.WithValue start = maybeStashInLocal(range.start().isPresent() ? translateExpr(range.start().get()) : CodeChunk.number(0), varPrefix + "_RangeStart", statements);
        final CodeChunk.WithValue end = maybeStashInLocal(translateExpr(range.limit()), varPrefix + "_RangeEnd", statements);
        final CodeChunk.WithValue step = maybeStashInLocal(range.increment().isPresent() ? translateExpr(range.increment().get()) : CodeChunk.number(1), varPrefix + "_RangeStep", statements);
        // the logic we want is
        // step * (end-start) < 0 ? 0 : ( (end-start)/step + ((end-start) % step == 0 ? 0 : 1));
        // but given that all javascript numbers are doubles we can simplify this somewhat.
        // Math.max(0, Match.ceil((end - start)/step))
        // should yield identical results.
        limitInitializer = CodeChunk.dottedIdNoRequire("Math.max").call(number(0), dottedIdNoRequire("Math.ceil").call(end.minus(start).divideBy(step)));
        // optimize for foreach over a range
        getDataItemFunction = new Function<CodeChunk.WithValue, CodeChunk.WithValue>() {

            @Override
            public CodeChunk.WithValue apply(CodeChunk.WithValue index) {
                return start.plus(index.times(step));
            }
        };
    } else {
        // Define list var and list-len var.
        CodeChunk.WithValue dataRef = translateExpr(node.getExpr());
        final String listVarName = varPrefix + "List";
        CodeChunk.WithValue listVar = VariableDeclaration.builder(listVarName).setRhs(dataRef).build().ref();
        // does it make sense to store this in a variable?
        limitInitializer = listVar.dotAccess("length");
        getDataItemFunction = new Function<CodeChunk.WithValue, CodeChunk.WithValue>() {

            @Override
            public CodeChunk.WithValue apply(CodeChunk.WithValue index) {
                return id(listVarName).bracketAccess(index);
            }
        };
    }
    // Generate the foreach body as a CodeChunk.
    CodeChunk.WithValue limit = id(limitName);
    statements.add(VariableDeclaration.builder(limitName).setRhs(limitInitializer).build());
    CodeChunk foreachBody = handleForeachLoop(nonEmptyNode, limit, getDataItemFunction);
    if (hasIfempty) {
        // If there is an ifempty node, wrap the foreach body in an if statement and append the
        // ifempty body as the else clause.
        CodeChunk ifemptyBody = visitChildrenReturningCodeChunk(node.getChild(1));
        CodeChunk.WithValue limitCheck = limit.op(Operator.GREATER_THAN, number(0));
        foreachBody = ifStatement(limitCheck, foreachBody).else_(ifemptyBody).build();
    }
    statements.add(foreachBody);
    jsCodeBuilder.append(CodeChunk.statements(statements));
}
Also used : CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) RangeArgs(com.google.template.soy.shared.RangeArgs) ArrayList(java.util.ArrayList) ForNonemptyNode(com.google.template.soy.soytree.ForNonemptyNode)

Example 5 with RangeArgs

use of com.google.template.soy.shared.RangeArgs in project closure-templates by google.

the class SoyNodeCompiler method calculateRangeArgs.

/**
 * Interprets the given expressions as the arguments of a {@code range(...)} expression in a
 * {@code foreach} loop.
 */
private CompiledForeachRangeArgs calculateRangeArgs(ForNode forNode, Scope scope) {
    RangeArgs rangeArgs = RangeArgs.createFromNode(forNode).get();
    ForNonemptyNode nonEmptyNode = (ForNonemptyNode) forNode.getChild(0);
    ImmutableList.Builder<Statement> initStatements = ImmutableList.builder();
    Expression startExpression = computeRangeValue(SyntheticVarName.foreachLoopRangeStart(nonEmptyNode), rangeArgs.start(), 0, scope, initStatements);
    Expression stepExpression = computeRangeValue(SyntheticVarName.foreachLoopRangeStep(nonEmptyNode), rangeArgs.increment(), 1, scope, initStatements);
    Expression endExpression = computeRangeValue(SyntheticVarName.foreachLoopRangeEnd(nonEmptyNode), Optional.of(rangeArgs.limit()), Integer.MAX_VALUE, scope, initStatements);
    return new AutoValue_SoyNodeCompiler_CompiledForeachRangeArgs(startExpression, endExpression, stepExpression, initStatements.build());
}
Also used : SoyExpression(com.google.template.soy.jbcsrc.restricted.SoyExpression) Expression(com.google.template.soy.jbcsrc.restricted.Expression) RangeArgs(com.google.template.soy.shared.RangeArgs) ImmutableList(com.google.common.collect.ImmutableList) Statement(com.google.template.soy.jbcsrc.restricted.Statement) ForNonemptyNode(com.google.template.soy.soytree.ForNonemptyNode)

Aggregations

RangeArgs (com.google.template.soy.shared.RangeArgs)5 ForNonemptyNode (com.google.template.soy.soytree.ForNonemptyNode)4 Expression (com.google.template.soy.jbcsrc.restricted.Expression)2 SoyExpression (com.google.template.soy.jbcsrc.restricted.SoyExpression)2 Statement (com.google.template.soy.jbcsrc.restricted.Statement)2 ArrayList (java.util.ArrayList)2 ImmutableList (com.google.common.collect.ImmutableList)1 SoyList (com.google.template.soy.data.SoyList)1 SoyValue (com.google.template.soy.data.SoyValue)1 ExprNode (com.google.template.soy.exprtree.ExprNode)1 ListLiteralNode (com.google.template.soy.exprtree.ListLiteralNode)1 Scope (com.google.template.soy.jbcsrc.TemplateVariableManager.Scope)1 Variable (com.google.template.soy.jbcsrc.TemplateVariableManager.Variable)1 CodeBuilder (com.google.template.soy.jbcsrc.restricted.CodeBuilder)1 CodeChunk (com.google.template.soy.jssrc.dsl.CodeChunk)1 Label (org.objectweb.asm.Label)1