package com.github.sommeri.less4j.core.compiler.stages; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.DetachedRuleset; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.ReusableStructure; import com.github.sommeri.less4j.core.ast.RuleSet; import com.github.sommeri.less4j.core.ast.VariableDeclaration; import com.github.sommeri.less4j.core.compiler.scopes.IScope; import com.github.sommeri.less4j.core.compiler.scopes.PlaceholderScope; import com.github.sommeri.less4j.core.compiler.scopes.ScopeFactory; import com.github.sommeri.less4j.core.problems.BugHappened; /** * Splits the input tree into separate scope tree and the trees. Removes * variables, pure mixin and pure namespace declarations from the original * abstract syntax tree. * */ //Variables, mixins and namespaces are valid within the whole scope, even before they have been defined. public class InitialScopeExtractor { private ASTManipulator manipulator = new ASTManipulator(); private IScope currentScope; private List<PlaceholderScope> importsPlaceholders; public InitialScopeExtractor() { } public IScope extractScope(ASTCssNode node) { currentScope = null; importsPlaceholders = new LinkedList<PlaceholderScope>(); IScope result = buildScope(node); return result; } private IScope buildScope(ASTCssNode node) { boolean hasOwnScope = AstLogic.hasOwnScope(node); if (hasOwnScope) increaseScope(node); fillScopeNames(node); List<? extends ASTCssNode> childs = new ArrayList<ASTCssNode>(node.getChilds()); for (ASTCssNode kid : childs) { buildScope(kid); if (kid.getType() == ASTCssNodeType.IMPORT) { importsPlaceholders.add(createPlaceholderScope(kid)); } else if (kid.getType() == ASTCssNodeType.VARIABLE_DECLARATION) { currentScope.registerVariable((VariableDeclaration) kid); manipulator.removeFromBody(kid); } else if (kid.getType() == ASTCssNodeType.REUSABLE_STRUCTURE) { ReusableStructure mixin = (ReusableStructure) kid; IScope bodyScope = currentScope.childByOwners(mixin, mixin.getBody()); currentScope.registerMixin(mixin, bodyScope); bodyScope.removedFromAst(); if (bodyScope.hasParent()) bodyScope.getParent().removedFromAst(); // remove also arguments scope from tree manipulator.removeFromBody(kid); } else if (kid.getType() == ASTCssNodeType.DETACHED_RULESET) { DetachedRuleset detached = (DetachedRuleset) kid; IScope bodyScope = currentScope.childByOwners(detached, detached.getBody()); if (bodyScope.hasParent()) bodyScope.getParent().removedFromAst(); // remove also arguments scope from tree bodyScope.removedFromAst(); detached.setScope(bodyScope); } else if (kid.getType() == ASTCssNodeType.RULE_SET) { RuleSet ruleSet = (RuleSet) kid; if (ruleSet.isUsableAsReusableStructure()) { IScope bodyScope = currentScope.childByOwners(ruleSet, ruleSet.getBody()); currentScope.registerMixin(ruleSet.convertToReusableStructure(), bodyScope); } } else if (kid.getType() == ASTCssNodeType.MIXIN_REFERENCE) { currentScope.createDataPlaceholder(); } else if (kid.getType() == ASTCssNodeType.DETACHED_RULESET_REFERENCE) { currentScope.createDataPlaceholder(); } else if (AstLogic.isExpression(kid)) { Expression value = (Expression) kid; value.setScope(currentScope); } } IScope result = currentScope; if (hasOwnScope) decreaseScope(); return result; } private void fillScopeNames(ASTCssNode node) { switch (node.getType()) { case REUSABLE_STRUCTURE: currentScope.addNames(((ReusableStructure) node).getNamesAsStrings()); break; case RULE_SET: { RuleSet ruleSet = (RuleSet) node; if (ruleSet.isUsableAsReusableStructure()) { currentScope.addNames(ruleSet.extractReusableStructureNames()); } break; } default: } } private void decreaseScope() { currentScope = currentScope.getParent(); } private void increaseScope(ASTCssNode owner) { if (currentScope == null) { currentScope = ScopeFactory.createDefaultScope(owner); } else if (AstLogic.isBodyOwner(owner)) { currentScope = ScopeFactory.createBodyOwnerScope(owner, currentScope); } else { currentScope = ScopeFactory.createScope(owner, currentScope); } return; } private PlaceholderScope createPlaceholderScope(ASTCssNode owner) { if (currentScope == null) { throw new BugHappened("No parent scope available.", owner); } return ScopeFactory.createPlaceholderScope(owner, currentScope); } public List<PlaceholderScope> getImportsPlaceholders() { return importsPlaceholders; } }