com.google.common.css.compiler.passes.ResolveCustomFunctionNodes Maven / Gradle / Ivy
Show all versions of closure-stylesheets Show documentation
/*
* Copyright 2009 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.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssCustomFunctionNode;
import com.google.common.css.compiler.ast.CssFunctionNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
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.GssFunction;
import com.google.common.css.compiler.ast.GssFunctionException;
import com.google.common.css.compiler.ast.MutatingVisitController;
import com.google.common.css.compiler.ast.Proxiable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This compiler pass replaces {@link CssCustomFunctionNode} instances with the
* list of nodes returned by the GssFunction.
*
*/
public class ResolveCustomFunctionNodes extends DefaultTreeVisitor
implements CssCompilerPass {
private final MutatingVisitController visitController;
protected Map functionMap;
private final ErrorManager errorManager;
private final boolean allowUnknownFunctions;
private final Set allowedNonStandardFunctions;
/**
* Constructs the pass.
*
* @param visitController The visit controller
* @param errorManager The error manager
* @param functionMap The map from function names to resolve to GSS functions
*/
public ResolveCustomFunctionNodes(MutatingVisitController visitController,
ErrorManager errorManager,
Map functionMap) {
this(visitController, errorManager, functionMap,
false /* allowUnknownFunctions */);
}
/**
* Constructs the pass.
*
* @param visitController The visit controller
* @param errorManager The error manager
* @param functionMap The map from function names to resolve to GSS functions
* @param allowUnknownFunctions Whether to allow unknown function calls,
* leaving them as is, instead of reporting an error
*/
public ResolveCustomFunctionNodes(MutatingVisitController visitController,
ErrorManager errorManager,
Map functionMap,
boolean allowUnknownFunctions) {
this(visitController, errorManager, functionMap, allowUnknownFunctions,
ImmutableSet.of() /* allowedNonStandardFunctions */);
}
/**
* Constructs the pass.
*
* @param visitController The visit controller
* @param errorManager The error manager
* @param functionMap The map from function names to resolve to GSS functions
* @param allowUnknownFunctions Whether to allow unknown function calls,
* leaving them as is, instead of reporting an error
* @param allowedNonStandardFunctions functions that should not yield a
* warning if they appear in a stylesheet
*/
public ResolveCustomFunctionNodes(MutatingVisitController visitController,
ErrorManager errorManager,
Map functionMap,
boolean allowUnknownFunctions,
Set allowedNonStandardFunctions) {
Preconditions.checkNotNull(functionMap);
this.visitController = visitController;
this.errorManager = errorManager;
this.functionMap = functionMap;
this.allowUnknownFunctions = allowUnknownFunctions;
this.allowedNonStandardFunctions = ImmutableSet.copyOf(
allowedNonStandardFunctions);
}
@Override
public void leaveFunctionNode(CssFunctionNode functionNode) {
if (!(functionNode instanceof Proxiable)) {
return;
}
CssCustomFunctionNode node = (CssCustomFunctionNode) functionNode;
List functionResult = node.getResult();
if (functionResult == null) {
// Look up the function's name in the map.
String functionName = node.getFunctionName();
GssFunction function = functionMap.get(functionName);
if (function == null) {
if (!allowUnknownFunctions && !allowedNonStandardFunctions.contains(
functionName)) {
errorManager.report(new GssError(
String.format("Unknown function \"%s\"", functionName),
node.getSourceCodeLocation()));
visitController.removeCurrentNode();
}
return;
}
// Removes the commas from the arguments and groups arguments separated
// by commas, e.g.:
// verticalGradient(#fff, #f2f2f2, url(foo) left top)
// has 7 initial arguments (2 commas, 5 words).
// After the cleanup we end up with the 3 arguments separated by commas.
// TODO(user): Clean this up once we get rid of the comma-as-argument hack.
List arguments = Lists.newArrayList();
CssValueNode lastArg = null;
for (CssValueNode value : node.getArguments().childIterable()) {
if (value.getValue() != null && value.getValue().equals(",")) {
lastArg = null;
} else {
if (lastArg == null) {
arguments.add(value);
lastArg = value;
} else {
if (value instanceof CssLiteralNode
&& " ".equals(value.getValue())) {
// Here we are building a parse subtree that, when
// printed by most output passes, looks like the subtree
// we got as input, but actually consists of just a
// CssLiteralNode. This requires us to add some
// whitespace to the printed representations of the
// nodes we are merging, corresponding to the whitespace
// eaten by the parser between lexemes and generated by
// printing passes when flattening a tree back to a
// string. But functions are special: the parse tree
// includes CssLiteralNode(" ") to explicitly represent
// function argument delimiters. We need to avoid adding
// extra space here, just as printing passes do.
} else if (lastArg.getValue() == null) {
arguments.remove(lastArg);
lastArg = new CssLiteralNode(lastArg.toString() + " "
+ value.toString());
arguments.add(lastArg);
} else {
lastArg.setValue(lastArg.getValue() + " " + value.toString());
}
}
}
}
Integer expArgNumber = function.getNumExpectedArguments();
int argNumber = arguments.size();
if (expArgNumber != null && expArgNumber.intValue() != argNumber) {
errorManager.report(new GssError("Function expects " + expArgNumber
+ " arguments but has " + argNumber,
node.getSourceCodeLocation()));
visitController.removeCurrentNode();
return;
}
try {
functionResult = evaluateFunction(node, function, arguments, errorManager);
} catch (GssFunctionException e) {
visitController.removeCurrentNode();
return;
} catch (RuntimeException e) {
errorManager.report(
new GssError(
Throwables.getStackTraceAsString(e),
node.getSourceCodeLocation()));
visitController.removeCurrentNode();
return;
}
}
visitController.replaceCurrentBlockChildWith(functionResult, false);
}
/**
* Evaluates the given function node.
*
* Subclasses may sublcass this method to change the evaluation
* process in some circumstances.
*
* @param node the function node to evaluate
* @param function the GSS function matching this node
* @param arguments the arguments of this node
* @param errorManager the error manager passed into the GSS function call
* @return the result of the evaluation as a list of value nodes
* @throws GssFunctionException if the function call is invalid
* @throws RuntimeException if the function call fails to complete
*/
protected List evaluateFunction(
CssCustomFunctionNode node,
GssFunction function,
List arguments,
ErrorManager errorManager) throws GssFunctionException {
List functionResult =
function.getCallResultNodes(arguments, errorManager);
node.setResult(functionResult);
return functionResult;
}
@Override
public void runPass() {
visitController.startVisit(this);
}
}