com.google.common.css.compiler.passes.UnrollLoops Maven / Gradle / Ivy
Show all versions of closure-stylesheets Show documentation
/*
* 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.common.css.compiler.passes;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.css.compiler.ast.CssBlockNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssDefinitionNode;
import com.google.common.css.compiler.ast.CssForLoopRuleNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssLoopVariableNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssNumericNode;
import com.google.common.css.compiler.ast.CssRootNode;
import com.google.common.css.compiler.ast.CssTree;
import com.google.common.css.compiler.ast.CssValueNode;
import com.google.common.css.compiler.ast.DefaultTreeVisitor;
import com.google.common.css.compiler.ast.ErrorManager;
import com.google.common.css.compiler.ast.GssError;
import com.google.common.css.compiler.ast.MutatingVisitController;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A compiler pass that unrolls loops.
*
* For every loop, we iterate over the different values of the loop variables, create a copy of
* the loop block and invoke {@link LoopVariableReplacementPass} to replace all references of the
* loop variable with the current iteration value. So for a loop with N iterations, we'll replace
* the loop block with N blocks which only differ in the replacement to the loop variable value.
*
*
NOTE: There is special treatment for {@code @def}s in this pass. Since
* definition references might appear before declaration, it's easier for
* {@link LoopVariableReplacementPass} to know all definitions declarations beforehand (the latter
* pass needs to do name replacement for definitions declared inside the loop). That's why this pass
* scrapes them prior to calling {@link LoopVariableReplacementPass}.
*/
public class UnrollLoops extends DefaultTreeVisitor implements CssCompilerPass {
@VisibleForTesting
static final String UNKNOWN_CONSTANT = "Unknown constant used in for loop.";
@VisibleForTesting
static final String UNKNOWN_VARIABLE = "For loop variable is used before it was evaluated.";
private final MutatingVisitController visitController;
private final ErrorManager errorManager;
public UnrollLoops(MutatingVisitController visitController, ErrorManager errorManager) {
this.visitController = visitController;
this.errorManager = errorManager;
}
@Override
public boolean enterForLoop(CssForLoopRuleNode node) {
GatherLoopDefinitions definitionsGatherer = new GatherLoopDefinitions();
node.getVisitController().startVisit(definitionsGatherer);
Set definitions = definitionsGatherer.getLoopDefinitions();
Integer from = getNumberValue(node.getFrom());
Integer to = getNumberValue(node.getTo());
Integer step = getNumberValue(node.getStep());
if (from == null || to == null || step == null) {
// If any of the loop parameters are illegal, stop processing the node.
// NOTE(user): getNumberValue already reported an error in this case.
visitController.removeCurrentNode();
return false;
}
List blocks = Lists.newArrayListWithCapacity((to - from + step) / step);
for (int i = from; i <= to; i += step) {
blocks.addAll(
makeBlock(node, i, definitions, node.getLoopId()).getChildren());
}
visitController.replaceCurrentBlockChildWith(blocks, true /* visitTheReplacementNodes */);
return true;
}
/**
* Copies the node's block and replaces appearances of the loop variable with the given value.
*/
private CssBlockNode makeBlock(
CssForLoopRuleNode node, int value, Set definitions, int loopId) {
CssBlockNode newBlock = new CssBlockNode(false, node.getBlock().deepCopy().getChildren());
newBlock.setSourceCodeLocation(node.getSourceCodeLocation());
CssTree tree = new CssTree(null, new CssRootNode(newBlock));
new LoopVariableReplacementPass(
node.getVariableName(), value, definitions, tree.getMutatingVisitController(), loopId)
.runPass();
return newBlock;
}
@Nullable
private Integer getNumberValue(CssValueNode node) {
if (node instanceof CssNumericNode) {
return Integer.parseInt(((CssNumericNode) node).getNumericPart());
} else if (node instanceof CssLoopVariableNode) {
reportError(UNKNOWN_VARIABLE, node);
} else if (node instanceof CssLiteralNode) {
reportError(UNKNOWN_CONSTANT, node);
} else {
throw new RuntimeException("Unsupported value type for loop variable: " + node.getClass());
}
return null;
}
private void reportError(String message, CssNode node) {
errorManager.report(new GssError(message, node.getSourceCodeLocation()));
}
/**
* A visitor gathers all the definitions ({@code @def}) inside the loop.
* That is needed to properly add the iteration suffix.
*/
private class GatherLoopDefinitions extends DefaultTreeVisitor {
private final Set loopDefinitions = new HashSet<>();
public Set getLoopDefinitions() {
return loopDefinitions;
}
@Override
public boolean enterDefinition(CssDefinitionNode node) {
loopDefinitions.add(node.getName().getValue());
return true;
}
}
@Override
public void runPass() {
visitController.startVisit(this);
}
}