/*
* Copyright 2010 CoreMedia AG
*
* 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 net.jangaroo.jooc;
import net.jangaroo.utils.AS3Type;
import net.jangaroo.jooc.ast.AstNode;
import net.jangaroo.jooc.ast.ClassDeclaration;
import net.jangaroo.jooc.ast.CompilationUnit;
import net.jangaroo.jooc.ast.FunctionDeclaration;
import net.jangaroo.jooc.ast.FunctionExpr;
import net.jangaroo.jooc.ast.Ide;
import net.jangaroo.jooc.ast.IdeDeclaration;
import net.jangaroo.jooc.ast.ImportDirective;
import net.jangaroo.jooc.ast.PackageDeclaration;
import net.jangaroo.jooc.ast.QualifiedIde;
import net.jangaroo.jooc.ast.VariableDeclaration;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author Andreas Gawecki
*/
public class DeclarationScope extends ScopeImplBase {
private static final Pattern AUX_VAR_NAME_PATTERN = Pattern.compile("\\$([0-9]+)");
private AstNode definingNode;
private Set<String> packages = new HashSet<String>();
private Map<String, IdeDeclaration> ides = new HashMap<String, IdeDeclaration>();
private Map<String, List<ImportDirective>> importsByName = new HashMap<String, List<ImportDirective>>();
private Map<String, ImportDirective> importsByQualifiedName = new HashMap<String, ImportDirective>();
private boolean isInstanceScope = false;
public boolean isPackage(String fullyQualifiedName) {
return packages.contains(fullyQualifiedName) || super.isPackage(fullyQualifiedName);
}
public DeclarationScope(AstNode definingNode, Scope parent) {
super(parent);
this.definingNode = definingNode;
}
@Override
public AstNode getDefiningNode() {
return definingNode;
}
@Override
public void addImport(final ImportDirective importDirective) {
Ide ide = importDirective.getIde();
String name = ide.getName();
Ide packageIde = ide.getQualifier();
String packageName = "";
final CompilationUnit compilationUnit = getCompilationUnit();
if (packageIde != null) {
packageName = packageIde.getQualifiedNameStr();
packages.add(packageName);
}
if (AS3Type.ANY.toString().equals(name)) {
final List<String> packageIdes = compilationUnit.getCompiler().getPackageIdes(packageName);
for (String typeToImport : packageIdes) {
ImportDirective implicitImport = new ImportDirective(packageIde, typeToImport);
implicitImport.scope(this);
}
} else {
if (importsByName.containsKey(name)) {
final List<ImportDirective> directiveList = importsByName.get(name);
if (isImportAlreadyAdded(directiveList, importDirective)) {
return;
}
directiveList.add(importDirective);
} else {
List<ImportDirective> list = new LinkedList<ImportDirective>();
list.add(importDirective);
importsByName.put(name, list);
}
if (ides.containsKey(name)) {
// name clash with value ide - error according to adobe
throw new CompilerError(importDirective.getIde().getSymbol(), "attempt to redefine identifier " + name + " by import");
}
// define the fully qualified name if not (might be the same string for top level imports):
final String qualifiedName = ide.getQualifiedNameStr();
importsByQualifiedName.put(qualifiedName, importDirective);
}
}
private boolean isImportAlreadyAdded(final List<ImportDirective> directiveList, final ImportDirective importDirective) {
final String qname = importDirective.getQualifiedName();
for (ImportDirective directive : directiveList) {
if (directive.getQualifiedName().equals(qname)) {
return true;
}
}
return false;
}
@Override
public IdeDeclaration declareIde(IdeDeclaration decl) {
final Ide ide = decl.getIde();
final String name = ide.getName();
if (importsByName.containsKey(name)) {
throw new CompilerError(ide.getSymbol(), "attempt to redefine an imported identifier " + name);
}
if (AUX_VAR_NAME_PATTERN.matcher(name).matches()) {
DeclarationScope packageDeclarationScope = getPackageDeclarationScope();
if (packageDeclarationScope != null && packageDeclarationScope != this) {
// also declare local auxiliary vars in package scope to reserve them so they are not used for package names:
new VariableDeclaration(null, new Ide(name), null).scope(packageDeclarationScope);
}
}
return ides.put(name, decl);
}
@Override
public IdeDeclaration lookupDeclaration(Ide ide) {
IdeDeclaration decl = null;
if (ide instanceof QualifiedIde) {
String qname = ide.getQualifiedNameStr();
if (importsByQualifiedName.containsKey(qname)) {
return resolveImport(importsByQualifiedName.get(qname));
}
if (ide.isQualifiedByThis()) {
return getClassDeclaration().resolvePropertyDeclaration(ide.getName());
}
if (ide.isQualifiedBySuper()) {
final IdeDeclaration superTypeDeclaration = getClassDeclaration().getSuperTypeDeclaration();
return superTypeDeclaration == null ? null : superTypeDeclaration.resolvePropertyDeclaration(ide.getName());
}
} else {
final String name = ide.getName();
final List<ImportDirective> importsOfThisIde = importsByName.get(name);
if (importsOfThisIde != null) {
if (importsOfThisIde.size() > 1) {
ambigousImport(ide, importsOfThisIde);
}
return resolveImport(importsOfThisIde.get(0));
}
decl = ides.get(ide.getName());
if (decl == null && getDefiningNode() != null && getClassDeclaration() == getDefiningNode()) {
decl = getClassDeclaration().resolvePropertyDeclaration(ide.getName());
if (decl != null && !isInstanceScope && !decl.isStatic()) {
decl = null;
}
}
}
return decl != null ? decl : super.lookupDeclaration(ide);
}
private IdeDeclaration resolveImport(final ImportDirective importDirective) {
return getCompilationUnit().getCompiler().resolveImport(importDirective);
}
private void ambigousImport(Ide ide, Collection<ImportDirective> importsOfThisIde) {
boolean isFirst = true;
StringBuilder msg = new StringBuilder();
msg.append("Can not resolve a multiname reference unambiguously: ");
for (ImportDirective importDirective : importsOfThisIde) {
if (!isFirst) {
msg.append(" and ");
}
isFirst = false;
msg.append(importDirective.getQualifiedName());
JooSymbol importedIdeSymbol = resolveImport(importDirective).getSymbol();
msg.append("(").append(importedIdeSymbol.getFileName()).append(":").append(importedIdeSymbol.getLine()).append(",").append(importedIdeSymbol.getColumn());
}
msg.append(" are available.");
throw new CompilerError(ide.getSymbol(), msg.toString());
}
public boolean isDeclared(Ide ide) {
return ides.containsKey(ide.getQualifiedNameStr()) || super.isDeclared(ide);
}
@Override
public Ide findFreeAuxVar() {
int i = 1;
while (true) {
String auxVarName = "$" + i;
Ide auxVar = new Ide(new JooSymbol(auxVarName));
if (lookupDeclaration(auxVar) == null) {
return auxVar;
}
++i;
}
}
@Override
public Ide createAuxVar(Scope lookupScope) {
Ide auxVar = findFreeAuxVar();
new VariableDeclaration(null, auxVar, null).scope(this);
return auxVar;
}
@Override
public CompilationUnit getCompilationUnit() {
if (definingNode instanceof CompilationUnit) {
return (CompilationUnit) definingNode;
}
return super.getCompilationUnit();
}
@Override
public PackageDeclaration getPackageDeclaration() {
if (definingNode instanceof PackageDeclaration) {
return (PackageDeclaration) definingNode;
}
return super.getPackageDeclaration();
}
@Override
public ClassDeclaration getClassDeclaration() {
if (definingNode instanceof ClassDeclaration) {
return (ClassDeclaration) definingNode;
}
return super.getClassDeclaration();
}
@Override
public DeclarationScope getPackageDeclarationScope() {
return definingNode instanceof PackageDeclaration ? this : super.getPackageDeclarationScope();
}
@Override
public FunctionDeclaration getMethodDeclaration() {
if (definingNode instanceof FunctionDeclaration) {
final FunctionDeclaration functionDeclaration = (FunctionDeclaration) definingNode;
if (functionDeclaration.isClassMember()) {
return functionDeclaration;
}
}
return super.getMethodDeclaration();
}
@Override
public FunctionExpr getFunctionExpr() {
if (definingNode instanceof FunctionExpr) {
return (FunctionExpr) definingNode;
}
return super.getFunctionExpr();
}
public void setIsInstanceScope(boolean b) {
isInstanceScope = true;
}
}