/*******************************************************************************
* Copyright (c) 2011 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package melnorme.lang.tooling.engine.scoping;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import java.util.HashSet;
import java.util.Set;
import melnorme.lang.tooling.ast.ILanguageElement;
import melnorme.lang.tooling.context.ISemanticContext;
import melnorme.lang.tooling.context.ModuleFullName;
import melnorme.lang.tooling.context.ModuleSourceException;
import melnorme.lang.tooling.engine.ErrorElement;
import melnorme.lang.tooling.symbols.IConcreteNamedElement;
import melnorme.lang.tooling.symbols.INamedElement;
import melnorme.lang.tooling.symbols.PackageNamespace;
import melnorme.lang.tooling.symbols.SymbolTable;
import melnorme.utilbox.collections.Collection2;
import java.util.function.Function;
import melnorme.utilbox.misc.StringUtil;
public abstract class CommonScopeLookup {
public final ISemanticContext context;
/** The offset of the reference.
* Used to check availability in statement scopes. */
public final int refOffset;
protected final SymbolTable matches = new SymbolTable();
/** The scopes that have already been searched */
protected final HashSet<IScopeElement> searchedScopes = new HashSet<>(4);
/** Named elements for which evaluateInMembersScope() has been called for. */
protected final HashSet<INamedElement> resolvedElementsForMemberScopes = new HashSet<>(4);;
public CommonScopeLookup(int refOffset, ISemanticContext context) {
this.refOffset = refOffset;
this.context = assertNotNull(context);
}
public boolean isSequentialLookup() {
return refOffset >= 0;
}
public Set<IScopeElement> getSearchedScopes() {
return searchedScopes;
}
public void addSymbolsToMatches(SymbolTable symbolTable) {
matches.addSymbols(symbolTable);
}
public Collection2<INamedElement> getMatchedElements() {
completeSearchMatches();
return matches.getElements();
}
public void completeSearchMatches() {
for (INamedElement namedElement : matches.getElements()) {
if(namedElement instanceof PackageNamespace) {
PackageNamespace packageNamespace = (PackageNamespace) namedElement;
if(!packageNamespace.isSemanticReady()) {
packageNamespace.setElementReady();
}
}
}
}
/* ----------------- ----------------- */
@Override
public String toString() {
return getClass().getName() + " ---\n" + toString_matches();
}
public String toString_matches() {
return StringUtil.toString(matches.getElements(), "\n", new Function<INamedElement, String>() {
@Override
public String apply(INamedElement obj) {
return obj.getFullyQualifiedName();
}
});
}
/* ----------------- module lookup helpers ----------------- */
public static IConcreteNamedElement resolveModule(ISemanticContext context, ILanguageElement refElement,
String moduleFullName) {
return resolveModule(context, refElement, new ModuleFullName(moduleFullName));
}
public static IConcreteNamedElement resolveModule(ISemanticContext context, ILanguageElement refElement,
ModuleFullName moduleName) {
try {
return context.findModule(moduleName);
} catch (ModuleSourceException pse) {
return new ErrorElement(moduleName.getLastSegment(), refElement,
ErrorElement.quoteDoc(pse.toString()));
}
}
public abstract Set<String> findMatchingModules();
/* ----------------- ----------------- */
/** Return whether the search has found all matches. */
public abstract boolean isFinished();
/** Returns whether this search matches the given name or not. */
public abstract boolean matchesName(String name);
/* ----------------- ----------------- */
public void evaluateInMembersScope(INamedElement nameElement) {
if(isFinished() || nameElement == null)
return;
IConcreteNamedElement concreteElement = nameElement.resolveConcreteElement(context);
evaluateInMembersScope(concreteElement);
}
protected void evaluateInMembersScope(IConcreteNamedElement concreteElement) {
if(concreteElement == null)
return;
// since evaluateInMembersScope() can call evaluateInMembersScope() of other elements,
// we need to add loop detection. The visited scopes hash is not enough to prevent this
// XXX: Perhaps this could be fixed instead by modifying how var nodes do evaluateInMembersScope
if(resolvedElementsForMemberScopes.contains(concreteElement))
return;
assertTrue(concreteElement.isSemanticReady());
resolvedElementsForMemberScopes.add(concreteElement);
concreteElement.resolveSearchInMembersScope(this);
}
/* ----------------- ----------------- */
/**
* Evaluate a scope (a collection of nodes with named elements) for this name lookup search.
*/
public void evaluateScope(IScopeElement scope) {
SymbolTable scopeNames = resolveScopeSymbols(scope);
if(scopeNames == null)
return;
matches.addVisibleSymbols(scopeNames);
if(scope != null) {
// Warning: potential infinite loop problems here ?
scope.getScopeTraverser().evaluateSuperScopes(this);
}
}
public SymbolTable resolveScopeSymbols(IScopeElement scope) {
if(scope == null)
return null;
if(isFinished())
return null;
if(searchedScopes.contains(scope))
return null;
searchedScopes.add(scope);
return doResolveScopeSymbols(scope);
}
protected SymbolTable doResolveScopeSymbols(IScopeElement scope) {
ScopeTraverser scopeTraverser = scope.getScopeTraverser();
SymbolTable names = scopeTraverser.evaluateScope(new ScopeNameResolution(this), refOffset, false);
SymbolTable importedNames = scopeTraverser.evaluateScope(new ScopeNameResolution(this), refOffset, true);
names.addVisibleSymbols(importedNames);
names.setCompleted();
return names;
}
public static class ScopeNameResolution {
protected final CommonScopeLookup lookup;
protected SymbolTable names = new SymbolTable();
public ScopeNameResolution(CommonScopeLookup lookup) {
this.lookup = lookup;
}
public SymbolTable getNames() {
return names;
}
public CommonScopeLookup getLookup() {
return lookup;
}
public ISemanticContext getContext() {
return getLookup().context;
}
public void visitNamedElement(INamedElement namedElement) {
if(namedElement == null)
return;
String name = namedElement.getNameInRegularNamespace();
if(name == null || name.isEmpty()) {
// Never match an element with missing name;
return;
}
if(!getLookup().matchesName(name)) {
return;
}
assertNotNull(namedElement);
names.addSymbol(namedElement);
}
}
}