All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wyil.transform.NameResolution Maven / Gradle / Ivy

// Copyright 2011 The Whiley Project Developers
//
// 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 wyil.transform;

import wycc.lang.Syntactic;
import wycc.util.AbstractCompilationUnit.Identifier;
import wycc.util.AbstractCompilationUnit.Name;
import wycc.util.AbstractCompilationUnit.Ref;
import wycc.util.AbstractHeap;
import wyc.util.ErrorMessages;
import wyil.lang.WyilFile;
import wyil.lang.WyilFile.*;

import static wyil.lang.WyilFile.*;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import wycc.util.ArrayUtils;
import wyil.util.AbstractConsumer;

/**
 * 

* Responsible for resolving a name which occurs at some position in a * compilation unit. This takes into account the context and, if necessary, will * traverse import statements to resolve the query. For example, consider a * compilation unit entitled "file": *

* *
 * import std::ascii::*
 *
 * function f(T1 x, T2 y) -> (int r):
 *    return g(x,y)
 * 
* *

* Here the name "g" is not fully qualified. Depending on which * file the matching declaration of g occurs will depend on what * its fully qualified name is. For example, if g is declared in * the current compilation unit then it's fully quaified name would be * test.g. However, it could well be declared in a compilation unit * matching the import whiley.lang.*. *

* *

* An important challenge faced is the linking of external declarations. In * particular, such external declarations must be brought into the current * WyilFile. Such external declarations may themselves require additional * external declarations be imported and we must transitively import them all. *

* * @author David J. Pearce * */ public class NameResolution { private final WyilFile target; /** * The resolver identifies unresolved names and produces patches based on them. */ private final Resolver resolver; private final SymbolTable symbolTable; /** * If enabled, import all referenced symbols in full (i.e. rather than stubs). */ private boolean linking = false; private boolean status = true; public NameResolution(List dependencies, WyilFile target, boolean link) throws IOException { this.target = target; this.symbolTable = new SymbolTable(target,dependencies); this.resolver = new Resolver(); this.linking = link; } /** * Apply this name resolver to a given WyilFile. * * @param wf */ public boolean apply() { // FIXME: need to make this incremental checkImports(target); // Create initial set of patches. List patches = resolver.apply(target); // Keep iterating until all patches are resolved while (patches.size() > 0) { // Create importer Importer importer = new Importer(target, !linking); // Now continue importing until patches all resolved. for (int i = 0; i != patches.size(); ++i) { // Import and link the given patch patches.get(i).apply(importer); } // Switch over to the next set of patches patches = importer.getPatches(); } // Consolidate any imported declarations as externals. symbolTable.consolidate(); // return status; } /** * Sanity check that import statements make sense. Specifically. that: (1) the * module being imported from exists; (2) any names being imported exist. * * @param target */ private void checkImports(WyilFile target) { for(Decl.Unit unit : target.getModule().getUnits()) { for(Decl d : unit.getDeclarations()) { if(d instanceof Decl.Import) { // Found one to check! Decl.Import imp = (Decl.Import) d; Tuple path = imp.getPath(); // Sanity check module exists Name name = new Name(path.toArray(Identifier.class)); // Sanity check imported module if(!name.toString().contains("*") && !symbolTable.contains(name)) { // Cannot identify name syntaxError(path.get(path.size()-1), RESOLUTION_ERROR); } else if(imp.hasFrom() || imp.hasWith()) { Tuple names = imp.getNames(); for (int i = 0; i != names.size(); ++i) { Identifier id = names.get(i); if (!id.get().equals("*") && !symbolTable.contains(new QualifiedName(name, id))) { // Sanity check imported names (if applicable) syntaxError(id, RESOLUTION_ERROR); } } } } } } } /** * Responsible for identifying unresolved names which remain to be resolved to * their corresponding declarations. This is achieved by traversing from a given * declaration and identifying all static variables and callables which are * referred to. In each case, a "patch" is created which identifies a location * within the module which needs to be resolved. Whilst in some cases we could * resolve immediately, for external symbols we cannot. Therefore, patches can * be thought of as "lazy" resolution which works for both local and non-local * names. * * @author David J. Pearce * */ private class Resolver extends AbstractConsumer> { /** * The list of patches being constructed */ private ArrayList patches = new ArrayList<>(); /** * Cache of previously resolved names. */ private HashMap cache = new HashMap<>(); /** * Used to indicate when resolving something which is exposed (and, hence, * relevant members should themselves be exposed). */ private boolean isVisible = false; public List apply(WyilFile module) { super.visitModule(module, null); return patches; } @Override public void visitUnit(Decl.Unit unit, List unused) { // Reset cache as this is necessary when moving between compilation units! cache.clear(); // Create an initially empty list of import statements. super.visitUnit(unit, new ArrayList<>()); } @Override public void visitExternalUnit(Decl.Unit unit, List unused) { // NOTE: we override this to prevent unnecessarily traversing units } @Override public void visitType(Decl.Type decl, List unused) { isVisible = isPublic(decl); super.visitType(decl, unused); isVisible = false; } @Override public void visitProperty(Decl.Property decl, List unused) { isVisible = isPublic(decl); super.visitProperty(decl, unused); isVisible = false; } @Override public void visitFunction(Decl.Function decl, List unused) { isVisible = isPublic(decl); visitVariables(decl.getParameters(), unused); visitVariables(decl.getReturns(), unused); visitExpressions(decl.getRequires(), unused); visitExpressions(decl.getEnsures(), unused); isVisible = false; visitStatement(decl.getBody(), unused); } @Override public void visitMethod(Decl.Method decl, List unused) { isVisible = isPublic(decl); visitVariables(decl.getParameters(), unused); visitVariables(decl.getReturns(), unused); visitExpressions(decl.getRequires(), unused); visitExpressions(decl.getEnsures(), unused); isVisible = false; visitStatement(decl.getBody(), unused); } @Override public void visitImport(Decl.Import decl, List imports) { // Reset cache as an import statement can have non-trivial effects. Ideally, it // would be nice to incrementally update the cache. cache.clear(); // super.visitImport(decl, imports); // Add this import statements to list of visible imports imports.add(decl); } @Override public void visitLambdaAccess(Expr.LambdaAccess expr, List imports) { super.visitLambdaAccess(expr, imports); // Resolve to qualified name QualifiedName name = resolveAs(expr.getLink(), imports); // Sanity check result if(name != null) { // Create patch patches.add(new Patch(isVisible, name, null, expr)); } } @Override public void visitStaticVariableAccess(Expr.StaticVariableAccess expr, List imports) { super.visitStaticVariableAccess(expr, imports); // Resolve to qualified name QualifiedName name = resolveAs(expr.getLink(), imports); // Sanity check result if(name != null) { // Create patch patches.add(new Patch(isVisible, name, null, expr)); } } @Override public void visitInvoke(Expr.Invoke expr, List imports) { super.visitInvoke(expr, imports); // Resolve to qualified name QualifiedName name = resolveAs(expr.getLink(), imports); // Sanity check result if(name != null) { // Create patch patches.add(new Patch(isVisible, name, null, expr)); } } @Override public void visitTypeNominal(Type.Nominal type, List imports) { super.visitTypeNominal(type, imports); // Resolve to qualified name QualifiedName name = resolveAs(type.getLink(), imports); // Sanity check result if (name != null) { // Create patch patches.add(new Patch(isVisible, name, null, type)); } } /** * Resolve a given name in a given compilation Unit to all corresponding * (callable) declarations. If the name is already fully qualified then this * amounts to checking that the name exists and finding its declaration(s); * otherwise, we have to process the list of important statements for this * compilation unit in an effort to qualify the name. * * @param name * The name to be resolved * @param enclosing * The enclosing declaration in which this name is contained. * @return */ private QualifiedName resolveAs(Decl.Link link, List imports) { QualifiedName r; Name name = link.getName(); QualifiedName qualified = cache.get(name); if(qualified != null) { // Have previously resolved this particular name in this particular context. return qualified; } else { // Resolve unqualified name to qualified name switch (name.size()) { case 1: qualified = unqualifiedResolveAs(name.get(0), imports); break; case 2: qualified = partialResolveAs(name.get(0), name.get(1), imports); break; default: // NOTE: don't put these into cache as not necessary! return new QualifiedName(name.getPath(), name.getLast()); } // Cache resolution for performance cache.put(name, qualified); // Done return qualified; } } /** * Resolve a name which is completely unqualified (e.g. to_string). * That is, it's just an identifier. This could be a name in the current unit, * or an explicitly imported name/ * * @param name * @param imports * @return */ private QualifiedName unqualifiedResolveAs(Identifier name, List imports) { // Attempt to local resolve Decl.Unit unit = name.getAncestor(Decl.Unit.class); QualifiedName localName = new QualifiedName(unit.getName(), name); if (symbolTable.contains(localName)) { // Yes, matching local name return localName; } else { // No, attempt to non-local resolve for (int i = imports.size() - 1; i >= 0; --i) { Decl.Import imp = imports.get(i); if (imp.hasFrom() || imp.hasWith()) { Tuple names = imp.getNames(); for (int j = 0; j != names.size(); ++j) { Identifier with = names.get(j); if (with.get().equals("*") || name.equals(with)) { return new QualifiedName(imp.getPath(), name); } } } } // No dice. syntaxError(name, RESOLUTION_ERROR); return null; } } /** * Resolve a name which is partially qualified (e.g. * ascii::to_string). This consists of an unqualified unit and a * name. * * @param unit * @param name * @param kind * @param imports * @return */ private QualifiedName partialResolveAs(Identifier unit, Identifier name, List imports) { Decl.Unit enclosing = name.getAncestor(Decl.Unit.class); if (unit.equals(enclosing.getName().getLast())) { // A local lookup on the enclosing compilation unit. return unqualifiedResolveAs(name, imports); } else { for (int i = imports.size() - 1; i >= 0; --i) { Decl.Import imp = imports.get(i); Tuple path = imp.getPath(); Identifier last = path.get(path.size() - 1); // if (!imp.hasFrom() && last.equals(unit)) { // Resolving partially qualified names requires no "from". QualifiedName qualified = new QualifiedName(path, name); if (symbolTable.contains(qualified)) { return qualified; } } } // No dice. syntaxError(name, RESOLUTION_ERROR); return null; } } } /** * Records information about a name which needs to be "patched" with its * corresponding declaration in a given expression. * * @author David J. Pearce * */ public class Patch { public final boolean isPublic; public final QualifiedName name; public final Type type; private final Syntactic.Item target; private Patch parent; public Patch(boolean isPublic, QualifiedName name, Type type, Syntactic.Item target) { if(name == null || target == null) { throw new IllegalArgumentException("name cannot be null"); } this.isPublic = isPublic; this.name = name; this.type = type; this.target = target; } /** * Set the parent of this patch. * * @param parent */ public void setParent(Patch parent) { this.parent = parent; } /** * Get qualified name being resolved at the top-level. * * @return */ public QualifiedName getRootName() { if(parent == null) { return name; } else { return parent.getRootName(); } } /** * Import and link a given patch. The import process is only necessary for * external symbols. For these, their declarations must be imported lazily as * stubs from the relevant declaration. * * @param importer * --- The importer to use for importing. */ public void apply(Importer importer) { // Import imPort(importer); // And, link. link(); } private void imPort(Importer importer) { // Import external declarations as necessary if (!symbolTable.isAvailable(name)) { // FIXME: want to optimise so don't bring in the whole type unless we are doing // link-time analysis or generating a single binary. ArrayList imported = new ArrayList<>(); for (Decl.Named d : symbolTable.getRegisteredDeclarations(name)) { // Sanity check import imported.add((Decl.Named) importer.allocate(d, this)); } symbolTable.addAvailable(name, imported); } else { for (Decl.Named d : symbolTable.getRegisteredDeclarations(name)) { // Sanity check local declarations if(isPublic && !isPublic(d)) { syntaxError(target,EXPOSING_HIDDEN_DECLARATION); } } } } /** * Connect the given syntactic item with its target declaration (which may have * just been imported). */ private void link() { // Apply patch to target expression switch (target.getOpcode()) { case EXPR_staticvariable: { Expr.StaticVariableAccess e = (Expr.StaticVariableAccess) target; Decl.StaticVariable d = select(Decl.StaticVariable.class); if(d != null) { e.getLink().resolve(d); } break; } case EXPR_invoke: { Expr.Invoke e = (Expr.Invoke) target; Decl.Link link = e.getLink(); if(type != null) { // do nothing? Decl.Callable resolved = select(type, Decl.Callable.class); if (resolved != null) { e.getLink().resolve(resolved); } } else { Decl.Callable[] resolved = selectAll(Decl.Callable.class); if (resolved != null) { e.getLink().resolve(resolved); } } break; } case EXPR_lambdaaccess: { Expr.LambdaAccess e = (Expr.LambdaAccess) target; Decl.Callable[] resolved = selectAll(Decl.Callable.class); if(resolved != null) { e.getLink().resolve(filterParameters(e.getParameterTypes().size(), resolved)); } break; } default: case TYPE_nominal: { Type.Nominal e = (Type.Nominal) target; Decl.Type d = select(Decl.Type.class); if(d != null && e.getParameters().size() != d.getTemplate().size()) { if(e.getParameters().size() > d.getTemplate().size()) { syntaxError(e.getLink().getName(), TOOMANY_TEMPLATE_PARAMETERS); } else { syntaxError(e.getLink().getName(), MISSING_TEMPLATE_PARAMETERS); } } else if(d != null){ e.getLink().resolve(d); } else { // NOTE: if we get here, then we have a public member referring to a hidden // member. For now, this is prohibited further upstream. } break; } } } /** * Resolve a name which is fully qualified (e.g. * std::ascii::to_string) to first matching declaration. This * consists of a qualified unit and a name. * * @param name * Fully qualified name * @param kind * Declaration kind we are resolving. * @return */ private T select(Class kind) { List declarations = symbolTable.getAvailableDeclarations(name); Identifier id = name.getName(); for (int i = 0; i != declarations.size(); ++i) { Decl.Named d = declarations.get(i); // if (kind.isInstance(d)) { // Found direct instance return (T) d; } } if(parent == null) { syntaxError(name.getName(), RESOLUTION_ERROR); } else { syntaxError(getRootName().getName(), INTERNAL_RESOLUTION_ERROR, name.getUnit(), name.getName()); } return null; } /** * Resolve a name which is fully qualified (e.g. * std::array::equals) and includes a distinguishing type signature. * * @param name * Fully qualified name * @param kind * Declaration kind we are resolving. * @return */ private T select(Type type, Class kind) { List declarations = symbolTable.getAvailableDeclarations(name); Identifier id = name.getName(); for (int i = 0; i != declarations.size(); ++i) { Decl.Named d = declarations.get(i); // if (kind.isInstance(d) && d.getType().equals(type)) { // Found direct instance return (T) d; } } if(parent == null) { syntaxError(name.getName(), RESOLUTION_ERROR); } else { syntaxError(getRootName().getName(), INTERNAL_RESOLUTION_ERROR, name.getUnit(), name.getName()); } return null; } /** * Resolve a name which is fully qualified (e.g. * std::ascii::to_string) to all matching declarations. This * consists of a qualified unit and a name. * * @param name * Fully qualified name * @param kind * Declaration kind we are resolving. * @return */ private T[] selectAll(Class kind) { List declarations = symbolTable.getAvailableDeclarations(name); Identifier id = name.getName(); // Determine how many matches int count = 0; for (int i = 0; i != declarations.size(); ++i) { Decl.Named d = declarations.get(i); if (kind.isInstance(d)) { count++; } } // Create the array @SuppressWarnings("unchecked") T[] matches = (T[]) Array.newInstance(kind, count); // Populate the array for (int i = 0, j = 0; i != declarations.size(); ++i) { Decl.Named d = declarations.get(i); if (kind.isInstance(d)) { matches[j++] = (T) d; } } // Check for resolution error if (matches.length == 0) { if(parent == null) { syntaxError(name.getName(), RESOLUTION_ERROR); } else { syntaxError(getRootName().getName(), INTERNAL_RESOLUTION_ERROR, name.getUnit(), name.getName()); } return null; } else { return matches; } } /** * Filter the given callable declarations based on their parameter count. * * @param parameters * @param resolved * @return */ private Decl.Callable[] filterParameters(int parameters, Decl.Callable[] resolved) { // Remove any with incorrect number of parameters for (int i = 0; i != resolved.length; ++i) { Decl.Callable c = resolved[i]; if (parameters > 0 && c.getParameters().size() != parameters) { resolved[i] = null; } } return ArrayUtils.removeAll(resolved, null); } @Override public String toString() { return "<" + name + "," + target + ">"; } } private static Ref REF_UNKNOWN_DECL = new Ref(new Decl.Unknown()); /** * Responsible for importing an item from one syntactic heap into another. This * relies on all items externally referenced by the imported item having been * already resolved. * * @author David J. Pearce * */ private class Importer extends AbstractHeap.Allocator { /** * Signals whether or not to only import stubs. */ private final boolean stubsOnly; /** * List of patches generated during imports by this importer. */ private final ArrayList patches = new ArrayList<>(); private final Syntactic.Item REF_UNKNOWN; public Importer(AbstractHeap heap, boolean stubsOnly) { super(heap); this.stubsOnly = stubsOnly; this.REF_UNKNOWN = super.allocate(REF_UNKNOWN_DECL); } /** * Import a given item as a result of a given patch. * * @param item * @param parent * @return */ public Syntactic.Item allocate(Syntactic.Item item, Patch parent) { int index = patches.size(); // Allocate the item Syntactic.Item r = allocate(item); // Set parent for patches for(int i=index;i ref = (Ref) item; Syntactic.Item referent = ref.get(); if (referent.getHeap() != heap && referent instanceof Decl.Named) { // This is a deference to a named declaration in a different module. This will // need to be patched. return REF_UNKNOWN; } else { return super.allocate(item); } case DECL_function: case DECL_method: { if (stubsOnly) { // drop the body from the function, making it a stub return allocateStub((Decl.FunctionOrMethod) item); } else { return super.allocate(item); } } // The linkable items require patching case EXPR_staticvariable: case EXPR_invoke: case EXPR_lambdaaccess: case TYPE_nominal: { Linkable linkable = (Linkable) item; Decl.Link link = linkable.getLink(); item = super.allocate(item); // Register patch patches.add(new Patch(false, link.getTarget().getQualifiedName(), link.getTarget().getType(), item)); // Done return item; } case DECL_property: // NOTE: properties are included here because we always import the full "body". default: return super.allocate(item); } // } public List getPatches() { return patches; } /** * Allocate a stub into the underlying heap. * * @param fm * @return */ private Syntactic.Item allocateStub(Decl.FunctionOrMethod fm) { Syntactic.Item item = map.get(fm); if (item != null) { // item is already allocated as a stub, therefore must return that. return item; } Tuple template = (Tuple) super.allocate(fm.getTemplate()); Tuple params = (Tuple) super.allocate(fm.getParameters()); Tuple returns = (Tuple) super.allocate(fm.getReturns()); Tuple requires = (Tuple) super.allocate(fm.getRequires()); Tuple ensures = (Tuple) super.allocate(fm.getEnsures()); if (fm instanceof Decl.Function) { // Create function stub Decl.Function f = (Decl.Function) fm; item = new Decl.Function(f.getModifiers(), f.getName(), template, params, returns, requires, ensures, new Stmt.Block()); } else { // Create method stub Decl.Method m = (Decl.Method) fm; item = new Decl.Method(m.getModifiers(), m.getName(), template, params, returns, requires, ensures, new Stmt.Block()); } // Allocate new item using underlying allocator. This will recursively allocate // child nodes. item = super.allocate(item); // Store item in map to ensure we don't allocate it more than once. map.put(fm, item); // Done return item; } } /** * Check whether named declaration is public or not. * * @param decl * @return */ private boolean isPublic(Decl.Named decl) { return decl.getModifiers().match(Modifier.Public.class) != null; } private void syntaxError(Syntactic.Item e, int code, Syntactic.Item... context) { status = false; ErrorMessages.syntaxError(e, code, context); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy