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);
}
}
};
}
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;
}
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));
}
}
}
}
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));
}
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());
}
Aggregations