/*******************************************************************************
* Copyright (c) 2014 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.ast;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import java.nio.file.Path;
import melnorme.lang.tooling.ast.util.NodeElementUtil;
import melnorme.lang.tooling.ast_actual.ASTNode;
import melnorme.lang.tooling.context.ISemanticContext;
import melnorme.lang.tooling.engine.IElementSemanticData;
import melnorme.lang.tooling.engine.PickedElement;
import melnorme.lang.tooling.engine.scoping.CommonScopeLookup;
import melnorme.lang.tooling.engine.scoping.CommonScopeLookup.ScopeNameResolution;
import melnorme.lang.tooling.engine.scoping.IScopeElement;
import melnorme.lang.tooling.symbols.INamedElement;
import melnorme.lang.tooling.symbols.PackageNamespace;
import dtool.ast.definitions.EArcheType;
public abstract class CommonLanguageElement implements ILanguageElement {
/** Custom field to store various kinds of data */
private NodeData data = NodeData.CREATED_STATUS;
/** AST node parent, null if the node is the tree root. */
protected CommonLanguageElement parent = null;
public CommonLanguageElement() {
}
/* ------------------------ Node data/status ------------------------ */
@Override
public final boolean isSemanticReady() {
return getData().isSemanticReadyStatus();
}
public final NodeData getData() {
return assertNotNull(data);
}
/** Set the data of this node. Cannot be null. Cannot set data twice without explicitly resetting */
public final void setData(NodeData data) {
assertNotNull(data);
assertTrue(!isSemanticReady()); // can only change data if node has not been made ready
this.data = data;
}
/** Removes the data of this node. Can only remove data if node is in parsed status.
* @return the previous data. */
public NodeData resetData() {
NodeData oldData = getData();
setData(NodeData.CREATED_STATUS);
return oldData;
}
public void setElementReady() {
assertTrue(isSemanticReady() == false);
doSetElementSemanticReady();
getData().setSemanticReady(this);
}
protected abstract void doSetElementSemanticReady();
/* ----------------- lexical parent ----------------- */
@Override
public CommonLanguageElement getLexicalParent() {
return parent;
}
/** Set the parent of this element. Cannot set parent twice without explicitly detaching. */
@Override
public final void setParent(CommonLanguageElement newParent) {
assertTrue(this.parent == null);
if(isSemanticReady()) {
assertTrue(newParent.isSemanticReady());
}
this.parent = newParent;
checkNewParent();
}
protected void checkNewParent() {
// Default implementation: do nothing
// subclasses can implement to check a contract relating to their parent
// (usually, to ensure the parent is of a certain class)
getParent_Concrete();
}
/** Same as {@link #getLexicalParent()}, but allows classes to cast to a more specific parent. */
// Is this extra method really needed instead of just defining getParent as non-final?
// Would the casts make a different in performance?
protected ILanguageElement getParent_Concrete() {
return getLexicalParent();
}
public void detachFromParent_disposeParent() {
assertNotNull(parent);
parent.data = null; // Dispose parent, parent becomes an invalid node.
parent = null;
}
/* ----------------- owner element ----------------- */
public abstract ILanguageElement getOwnerElement();
@Override
public boolean isBuiltinElement() {
return getOwnerElement() == null ? true : getOwnerElement().isBuiltinElement();
}
/* ----------------- isSemanticReady utils ----------------- */
protected static <T extends ILanguageElement> T checkIsSemanticReady(T element) {
assertTrue(element == null || element.isSemanticReady());
return element;
}
public static <T extends Iterable<? extends ILanguageElement>> T checkAreSemanticReady(T elements,
boolean readyPackageNamespace) {
for(ILanguageElement element : elements) {
if(readyPackageNamespace && element instanceof PackageNamespace) {
// PackageNamespace cannot be setCompleted early, so set it completed now
((PackageNamespace) element).setElementReady();
}
checkIsSemanticReady(element);
}
return elements;
}
protected static <T extends Iterable<? extends ILanguageElement>> T checkAllSemanticReady(T elements) {
return checkAreSemanticReady(elements, false);
}
/* ----------------- INamedElement utils ----------------- */
@Override
public String getModuleFullName() {
INamedElement module = getContainingModuleNamespace();
return module == null ? null : module.getFullyQualifiedName();
}
public INamedElement getContainingModuleNamespace() {
INamedElement namedElement = NodeElementUtil.getNearestNamedElement(this);
while(namedElement != null) {
if(namedElement.getArcheType() == EArcheType.Module) {
return namedElement;
}
namedElement = NodeElementUtil.getOuterNamedElement(namedElement);
}
return null;
}
public static String getFullyQualifiedName(INamedElement namedElement) {
INamedElement parentNamespace = namedElement.getParentNamespace();
if(parentNamespace == null) {
return namedElement.getName();
} else {
return parentNamespace.getFullyQualifiedName() + "." + namedElement.getName();
}
}
/* ----------------- ----------------- */
@Override
public IElementSemanticData getSemantics(ISemanticContext parentContext) {
assertTrue(isSemanticReady());
return doGetSemantics(parentContext);
}
public IElementSemanticData doGetSemantics(ISemanticContext parentContext) {
ISemanticContext context = getElementSemanticContext(parentContext);
return context.getSemanticsEntry(this);
}
@Override
public ISemanticContext getElementSemanticContext(ISemanticContext parentContext) {
assertNotNull(parentContext);
return parentContext.getContainingSemanticContext(this);
}
@Override
public Path getSemanticContainerKey() {
return getOwnerElement() == null ? null : getOwnerElement().getSemanticContainerKey();
}
@Override
public final IElementSemanticData createSemantics(PickedElement<?> pickedElement) {
assertTrue(pickedElement.element == this); // Note this precondition!
return doCreateSemantics(pickedElement);
}
@SuppressWarnings("unused")
protected IElementSemanticData doCreateSemantics(PickedElement<?> pickedElement) {
throw assertFail(); // Not valid unless re-implemented.
}
/* ----------------- name lookup ----------------- */
/**
* Perform a name lookup starting in this node.
* The exact mechanism in which the name lookup will be performed will depend on the node,
* but the most common (and default) scenario is to perform a lexical lookup.
*/
public final void performNameLookup(CommonScopeLookup lookup) {
assertTrue(isSemanticReady());
doPerformNameLookup(lookup);
}
protected void doPerformNameLookup(CommonScopeLookup lookup) {
assertTrue(lookup.isSequentialLookup());
assertTrue(lookup.refOffset >= 0);
lookup.evaluateScope(ASTNode.getPrimitivesScope());
if(lookup.isFinished())
return;
doPerformNameLookupInLexicalScope(lookup);
}
protected final void doPerformNameLookupInLexicalScope(CommonScopeLookup lookup) {
if(this instanceof IScopeElement) {
IScopeElement scope = (IScopeElement) this;
lookup.evaluateScope(scope);
}
if(lookup.isFinished())
return;
CommonLanguageElement parent = getLexicalParent();
if(parent != null) {
parent.doPerformNameLookupInLexicalScope(lookup);
}
}
@Override
public void evaluateForScopeLookup(ScopeNameResolution scopeRes, boolean isSecondaryScope,
boolean publicImportsOnly) {
// Default: do nothing.
}
}