/* * 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.collect.Lists; import com.google.common.css.SourceCodeLocation; import com.google.common.css.compiler.ast.CssAbstractBlockNode; import com.google.common.css.compiler.ast.CssAtRuleNode; import com.google.common.css.compiler.ast.CssBooleanExpressionNode; import com.google.common.css.compiler.ast.CssCompilerPass; import com.google.common.css.compiler.ast.CssConditionalBlockNode; import com.google.common.css.compiler.ast.CssConditionalRuleNode; import com.google.common.css.compiler.ast.CssDefinitionNode; import com.google.common.css.compiler.ast.CssNode; import com.google.common.css.compiler.ast.CssRulesetNode; import com.google.common.css.compiler.ast.CssUnknownAtRuleNode; 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.List; import java.util.Stack; /** * A compiler pass that replaces each {@code @if}, {@code @elseif}, and * {@code @else} {@link CssUnknownAtRuleNode} with appropriate conditional * nodes ({@link CssConditionalBlockNode} and {@link CssConditionalRuleNode}). * */ public class CreateConditionalNodes extends DefaultTreeVisitor implements CssCompilerPass { private final MutatingVisitController visitController; private final ErrorManager errorManager; private Stack<CssConditionalBlockNode> stack = new Stack<CssConditionalBlockNode>(); private CssConditionalBlockNode activeBlockNode = null; private static final String ifName = CssAtRuleNode.Type.IF.getCanonicalName(); private static final String elseifName = CssAtRuleNode.Type.ELSEIF.getCanonicalName(); private static final String elseName = CssAtRuleNode.Type.ELSE.getCanonicalName(); public CreateConditionalNodes(MutatingVisitController visitController, ErrorManager errorManager) { this.visitController = visitController; this.errorManager = errorManager; } @Override public boolean enterUnknownAtRule(CssUnknownAtRuleNode node) { String name = node.getName().getValue(); if (name.equals(ifName)) { CssConditionalBlockNode condBlock = new CssConditionalBlockNode(node.getComments()); condBlock.setSourceCodeLocation(node.getSourceCodeLocation()); stack.push(condBlock); } else if (name.equals(elseifName) || name.equals(elseName)) { if (activeBlockNode == null) { errorManager.report(new GssError("@" + name + " without previous @" + ifName, node.getSourceCodeLocation())); visitController.removeCurrentNode(); return false; } stack.push(activeBlockNode); } activeBlockNode = null; return true; } @Override public void leaveUnknownAtRule(CssUnknownAtRuleNode node) { String name = node.getName().getValue(); if (name.equals(ifName) || name.equals(elseifName) || name.equals(elseName)) { activeBlockNode = stack.pop(); CssConditionalRuleNode conditionalNode = createConditionalRuleNode(node, name); activeBlockNode.addChildToBack(conditionalNode); updateLocation(activeBlockNode); if (name.equals(ifName)) { visitController.replaceCurrentBlockChildWith( Lists.newArrayList((CssNode) activeBlockNode), false); } else { visitController.removeCurrentNode(); if (name.equals(elseName)) { activeBlockNode = null; } } } } @Override public boolean enterRuleset(CssRulesetNode node) { activeBlockNode = null; return true; } @Override public boolean enterDefinition(CssDefinitionNode node) { activeBlockNode = null; return true; } private CssConditionalRuleNode createConditionalRuleNode( CssUnknownAtRuleNode node, String name) { CssAbstractBlockNode block = node.getBlock(); if (block == null) { errorManager.report(new GssError("@" + name + " without block", node.getSourceCodeLocation())); } List<CssValueNode> params = node.getParameters(); CssBooleanExpressionNode condition = null; if (!name.equals(elseName)) { if (!params.isEmpty()) { if (params.size() > 1) { errorManager.report(new GssError("@" + name + " with too many parameters", node.getSourceCodeLocation())); } CssValueNode param = params.get(0); if (param instanceof CssBooleanExpressionNode) { condition = (CssBooleanExpressionNode) param; } else { condition = new CssBooleanExpressionNode(CssBooleanExpressionNode.Type.CONSTANT, param.getValue(), param.getSourceCodeLocation()); } } else { errorManager.report(new GssError("@" + name + " without condition", node.getSourceCodeLocation())); } } else { if (!params.isEmpty()) { errorManager.report(new GssError("@" + elseName + " with too many parameters", node.getSourceCodeLocation())); } } CssConditionalRuleNode rulenode = new CssConditionalRuleNode( CssAtRuleNode.Type.valueOf(name.toUpperCase()), node.getName(), condition, block); rulenode.setComments(node.getComments()); rulenode.setSourceCodeLocation(node.getSourceCodeLocation()); return rulenode; } /** * Assigns a new SourceCodeLocation to the BlockNode which starts at the * BlockNode's first child's beginning location point and ends at the * last child's ending location point. */ private void updateLocation(CssConditionalBlockNode blockNode) { SourceCodeLocation firstLocation = blockNode.getSourceCodeLocation(); SourceCodeLocation lastLocation = blockNode.getLastChild().getSourceCodeLocation(); SourceCodeLocation mergedLocation = new SourceCodeLocation( firstLocation.getSourceCode(), firstLocation.getBeginCharacterIndex(), firstLocation.getBeginLineNumber(), firstLocation.getBeginIndexInLine(), lastLocation.getEndCharacterIndex(), lastLocation.getEndLineNumber(), lastLocation.getEndIndexInLine()); blockNode.setSourceCodeLocation(mergedLocation); } @Override public void runPass() { visitController.startVisit(this); } }