Skip to content

Commit

Permalink
feature: introduce the concept of lexical scope (interface LexicalSco…
Browse files Browse the repository at this point in the history
…pe) (#2813)
  • Loading branch information
pvojtechovsky authored and nharrand committed Jan 23, 2019
1 parent 8b5f0c5 commit a16f436
Show file tree
Hide file tree
Showing 6 changed files with 632 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/main/java/spoon/reflect/visitor/LexicalScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.reflect.visitor;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.support.Experimental;

import java.util.function.Function;

/**
* Represents that a lexical scope in the language
*
* Note that scopes are changing after variable declaration. For example:
*
* void draw() {
* //scope1
* int a;
* //scope2
* int b;
* //scope3
* }
*
* See https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping
*/
@Experimental
public interface LexicalScope {
/** adds an element to the scope */
LexicalScope addNamedElement(CtNamedElement element);

/**
* @return the {@link CtElement} which represents the current scope
*/
CtElement getScopeElement();

/**
* @param name to be searched simple name
* @param fnc is called for each named element with same simple name, which is defined in this or parent {@link LexicalScope}.
* Function `fnc` is called as long as there are some matching elements and `fnc` returns null.
* If `fnc` returns not null value then searching is stopped and that value is a returned
* @return the value returned by `fnc` or null
*/
<T> T forEachElementByName(String name, Function<? super CtNamedElement, T> fnc);

}
107 changes: 107 additions & 0 deletions src/main/java/spoon/reflect/visitor/LexicalScopeScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package spoon.reflect.visitor;

import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.declaration.CtAnnotationType;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtEnum;
import spoon.reflect.declaration.CtInterface;
import spoon.reflect.declaration.CtMethod;

import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Deque;

/**
* Responsible for building lexical scopes.
*
*/
public class LexicalScopeScanner extends EarlyTerminatingScanner<Object> {
private final Deque<LexicalScope> scopes = new ArrayDeque<>();
protected void enter(spoon.reflect.declaration.CtElement e) {
LexicalScope newFinder = onElement(scopes.peek(), e);
if (newFinder != null) {
scopes.push(newFinder);
}
}
protected void exit(spoon.reflect.declaration.CtElement e) {
LexicalScope topFinder = scopes.peek();
if (topFinder != null && topFinder.getScopeElement() == e) {
//we are living scope of this ConflictFinder. Pop it
scopes.pop();
}
}
private static NameScopeImpl EMPTY = new NameScopeImpl(null, null);
/**
* @return {@link LexicalScope} of actually scanned element. The {@link LexicalScope#forEachElementByName(String, java.util.function.Function)} can be used
* to get all {@link CtElement}s which are mapped to that simple name
*/
public LexicalScope getCurrentNameScope() {
LexicalScope ns = scopes.peek();
return ns == null ? EMPTY : ns;
}

/**
* Call it for each visited CtElement
* @param parent the parent ConflictFinder
* @param target an element
* @return new {@link NameScopeImpl} if `target` element declares new naming scope or null if there is no new scope
*/
private NameScopeImpl onElement(LexicalScope parent, CtElement target) {
class Visitor extends CtAbstractVisitor {
NameScopeImpl finder = null;
@Override
public void visitCtCompilationUnit(CtCompilationUnit compilationUnit) {
//compilation unit items are added in TypeNameScope, because they depend on the inhertance hierarchy of the type itself
}
@Override
public <T> void visitCtClass(CtClass<T> ctClass) {
finder = new TypeNameScope(parent, ctClass);
}
@Override
public <T> void visitCtInterface(CtInterface<T> intrface) {
finder = new TypeNameScope(parent, intrface);
}
@Override
public <T extends Enum<?>> void visitCtEnum(CtEnum<T> ctEnum) {
finder = new TypeNameScope(parent, ctEnum);
}
@Override
public <A extends Annotation> void visitCtAnnotationType(CtAnnotationType<A> annotationType) {
finder = new TypeNameScope(parent, annotationType);
}
@Override
public <T> void visitCtMethod(CtMethod<T> m) {
finder = new NameScopeImpl(parent, m, m.getParameters());
}
@Override
public <T> void visitCtConstructor(CtConstructor<T> c) {
finder = new NameScopeImpl(parent, c, c.getParameters());
}
@Override
public <T> void visitCtLambda(CtLambda<T> lambda) {
finder = new NameScopeImpl(parent, lambda, lambda.getParameters());
}
@Override
public void visitCtCatch(CtCatch catchBlock) {
finder = new NameScopeImpl(parent, catchBlock).addNamedElement(catchBlock.getParameter());
}
@Override
public <R> void visitCtBlock(CtBlock<R> block) {
finder = new NameScopeImpl(parent, block);
}
@Override
public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
parent.addNamedElement(localVariable);
}
};
Visitor scanner = new Visitor();
target.accept(scanner);
return scanner.finder;
}
}
104 changes: 104 additions & 0 deletions src/main/java/spoon/reflect/visitor/NameScopeImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.reflect.visitor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtParameter;

/**
* Maps names to CtElements, which are visible at current scanning place
*/
class NameScopeImpl implements LexicalScope {

private final LexicalScope parent;
private final CtElement scopeElement;

public Map<String, CtNamedElement> getElementsByName() {
return elementsByName;
}

private final Map<String, CtNamedElement> elementsByName = new HashMap<>();

NameScopeImpl(LexicalScope parent, CtElement scopeElement, List<CtParameter<?>> parameters) {
this(parent, scopeElement);
for (CtParameter<?> parameter : parameters) {
addNamedElement(parameter);
}
}

protected NameScopeImpl(LexicalScope parent, CtElement scopeElement) {
this.parent = parent;
this.scopeElement = scopeElement;
}

@Override
public NameScopeImpl addNamedElement(CtNamedElement element) {
elementsByName.put(element.getSimpleName(), element);
return this;
}

/**
* @return the {@link CtElement} which represents the current scope
*/
@Override
public final CtElement getScopeElement() {
return scopeElement;
}

public final Optional<LexicalScope> getParent() {
return Optional.ofNullable(parent);
}

/**
* @param name to be searched simple name
* @param consumer is called for each named element with same name which are accessible from this {@link NameScopeImpl}
* as long as there are some elements and consumer returns null. If `consumer` return not null value then it is returned
* @return the value returned by `consumer` or null
*/
@Override
public <T> T forEachElementByName(String name, Function<? super CtNamedElement, T> consumer) {
T r = forEachByName(elementsByName, name, consumer);
if (scopeElement instanceof CtNamedElement) {
CtNamedElement named = (CtNamedElement) scopeElement;
if (name.equals(named.getSimpleName())) {
r = consumer.apply(named);
if (r != null) {
return r;
}
}
}
if (parent != null) {
return parent.forEachElementByName(name, consumer);
}
return null;
}

protected static <T> T forEachByName(Map<String, CtNamedElement> map, String name, Function<? super CtNamedElement, T> consumer) {
CtNamedElement named = map.get(name);
if (named != null) {
return consumer.apply(named);
}
return null;
}
}
Loading

0 comments on commit a16f436

Please sign in to comment.