/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* TypeNamingPolicy.java
* Created: Aug 21, 2003
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
/**
* A helper class used to determine how a scoped name (such as Maybe) should be displayed.
* For example, it could be displayed always fully qualified, or always unqualified, or
* fully qualified only when it is ambiguous within the context of the current module.
*
* @author Bo Ilic
*/
abstract public class ScopedEntityNamingPolicy {
static public final ScopedEntityNamingPolicy UNQUALIFIED = new Unqualified();
static public final ScopedEntityNamingPolicy FULLY_QUALIFIED = new FullyQualified();
private ScopedEntityNamingPolicy() {
}
public String getName(ScopedEntity entity) {
ModuleName moduleName = getModuleNameForScopedEntity(entity);
if(moduleName != null) {
return QualifiedName.make(moduleName, entity.getName().getUnqualifiedName()).getQualifiedName();
} else {
return entity.getName().getUnqualifiedName();
}
}
/**
* @return the module name to be used for the entity. Null is returned if the entity's name is to be unqualified.
*/
abstract ModuleName getModuleNameForScopedEntity(ScopedEntity entity);
/**
* Use the unqualified name e.g. Maybe rather than Prelude.Maybe.
* @author Bo Ilic
*/
static final class Unqualified extends ScopedEntityNamingPolicy {
private Unqualified() {
}
/** {@inheritDoc} */
@Override
ModuleName getModuleNameForScopedEntity(ScopedEntity entity) {
return null;
}
}
/**
* Use the fully-qualified name (including the full name of the module) e.g. Prelude.Maybe and not Maybe.
* @author Bo Ilic
*/
static final class FullyQualified extends ScopedEntityNamingPolicy {
private FullyQualified() {
}
/** {@inheritDoc} */
@Override
ModuleName getModuleNameForScopedEntity(ScopedEntity entity) {
return entity.getName().getModuleName();
}
}
/**
* Use the unqualified name unless there is ambiguity.
* <p>
* More precisely, if the context module is M:
* <ul>
* <li>
* If the entity is not visible in M, then use the qualified name.
* <li>
* If the entity is visible in M and there
* is another module in which an entity in the same namespace exists with the same name,
* and this other entity is defined and visible in M, then use a qualified name with
* a minimally qualified module name (e.g. Prelude.id, rather than Cal.Core.Prelude.id).
* </ul>
* Note: a type class name and a type constructor name live in different namespaces so they
* will never be ambiguous by the criterion of this class even if they have the same names.
* <p>
* Note also that this is not quite the same rule that is used when resolving names in CAL source.
* For example, if you are working in module Foo and define a sin function there, then
* you can refer to Foo.sin as sin, since Foo's sin will mask Prelude.sin. This is
* because dependent modules should not break when new public functions are introduced.
* However, from the point of view of this naming policy, Foo.sin would be displayed
* fully qualified, since this appears to be less confusing from the point of view
* of UI clients.
*
* @author Bo Ilic
*/
public static final class UnqualifiedUnlessAmbiguous extends ScopedEntityNamingPolicy {
private ModuleTypeInfo contextModuleTypeInfo;
public UnqualifiedUnlessAmbiguous(ModuleTypeInfo contextModuleTypeInfo) {
if (contextModuleTypeInfo == null) {
throw new NullPointerException();
}
this.contextModuleTypeInfo = contextModuleTypeInfo;
}
/**
* @return true if entity's name should be fully-qualified, or false if it should be unqualified.
* Note: It's possible for a naming policy to return a name that is neither qualified nor unqualified,
* but rather in some custom string format (eg, localized type names, strings
* like "List of Int" for [Int], etc.). For such cases, override getName as well as useQualifiedName.
*/
private boolean useQualifiedName(ScopedEntity entity) {
//if the entity is not visible in the context module, then return the qualified name
if (!isVisible(entity)) {
return true;
}
//we are now looking to see if there are 2 visible entities having the same unqualified names.
int sameNameCount = 0;
if (getOtherEntity(contextModuleTypeInfo, entity) != null) {
++sameNameCount;
}
for (int i = 0, nImportedModules = contextModuleTypeInfo.getNImportedModules(); i < nImportedModules; ++i) {
ModuleTypeInfo moduleTypeInfo = contextModuleTypeInfo.getNthImportedModule(i);
ScopedEntity otherEntity = getOtherEntity(moduleTypeInfo, entity);
if (otherEntity != null && contextModuleTypeInfo.isEntityVisible(otherEntity)) {
++sameNameCount;
if (sameNameCount > 1) {
return true;
}
}
}
return false;
}
/** {@inheritDoc} */
@Override
ModuleName getModuleNameForScopedEntity(ScopedEntity entity) {
if (useQualifiedName(entity)) {
return contextModuleTypeInfo.getModuleNameResolver().getMinimallyQualifiedModuleName(entity.getName().getModuleName());
} else {
return null;
}
}
/**
* boolean true if the entity is visible within the context module
*/
private boolean isVisible(ScopedEntity entity) {
//an entity is visible within its own context module
ModuleName entityModuleName = entity.getName().getModuleName();
if (entityModuleName.equals(contextModuleTypeInfo.getModuleName())) {
return true;
}
//a private entity is not visible outside its context module
if (entity.getScope() == Scope.PRIVATE) {
return false;
}
//check if the context module imports the entity's module.
return contextModuleTypeInfo.getImportedModule(entityModuleName) != null;
}
/**
* A helper function that attempts to look up an entity living in the same namespace
* and with the same unqualified name as the given entity.
* @param moduleTypeInfo module in which to look up the other entity in
* @param entity
* @return ScopedEntity
*/
private ScopedEntity getOtherEntity(ModuleTypeInfo moduleTypeInfo, ScopedEntity entity) {
String entityName = entity.getName().getUnqualifiedName();
ScopedEntity otherEntity;
if (entity instanceof TypeConstructor) {
otherEntity = moduleTypeInfo.getTypeConstructor(entityName);
} else if (entity instanceof Function || entity instanceof ClassMethod) {
otherEntity = moduleTypeInfo.getFunctionOrClassMethod(entityName);
} else if (entity instanceof TypeClass) {
otherEntity = moduleTypeInfo.getTypeClass(entityName);
} else if (entity instanceof DataConstructor) {
otherEntity = moduleTypeInfo.getDataConstructor(entityName);
} else {
throw new UnsupportedOperationException();
}
return otherEntity;
}
}
/**
* Qualifies names based on the module that the policy is constructed with.
* For a policy constructed with module M:
* <ul>
* <li>entities in M will not be qualified
* <li>entities imported in a using clause in M will not be qualified
* <li>all other entities will be qualified
* <li>all module names will have minimal qualifiers (Prelude rather than Cal.Prelude if Prelude is unambiguous)
* </ul>
*
* @author James Wright
*/
public static final class UnqualifiedIfUsingOrSameModule extends ScopedEntityNamingPolicy {
private final ModuleTypeInfo moduleTypeInfo;
public UnqualifiedIfUsingOrSameModule(ModuleTypeInfo moduleTypeInfo) {
if(moduleTypeInfo == null) {
throw new NullPointerException();
}
this.moduleTypeInfo = moduleTypeInfo;
}
/**
* @return true if entity's name should be fully-qualified, or false if it should be unqualified.
* Note: It's possible for a naming policy to return a name that is neither qualified nor unqualified,
* but rather in some custom string format (eg, localized type names, strings
* like "List of Int" for [Int], etc.). For such cases, override getName as well as useQualifiedName.
*/
private boolean useQualifiedName(ScopedEntity entity) {
QualifiedName qualifiedName = entity.getName();
ModuleName moduleName = qualifiedName.getModuleName();
String unqualifiedName = qualifiedName.getUnqualifiedName();
// In the same module, don't fully qualify
if (moduleName.equals(moduleTypeInfo.getModuleName())) {
return false;
}
// For names that are in a using clause, don't fully qualify
if ((entity instanceof Function || entity instanceof ClassMethod) &&
moduleName.equals(moduleTypeInfo.getModuleOfUsingFunctionOrClassMethod(unqualifiedName))) {
return false;
}
if (entity instanceof TypeClass &&
moduleName.equals(moduleTypeInfo.getModuleOfUsingTypeClass(unqualifiedName))) {
return false;
}
if (entity instanceof TypeConstructor &&
moduleName.equals(moduleTypeInfo.getModuleOfUsingTypeConstructor(unqualifiedName))) {
return false;
}
if (entity instanceof DataConstructor &&
moduleName.equals(moduleTypeInfo.getModuleOfUsingDataConstructor(unqualifiedName))) {
return false;
}
// Otherwise, qualify it
return true;
}
/** {@inheritDoc} */
@Override
ModuleName getModuleNameForScopedEntity(ScopedEntity entity) {
if (useQualifiedName(entity)) {
ModuleName fullModuleName = entity.getName().getModuleName();
return moduleTypeInfo.getModuleNameResolver().getMinimallyQualifiedModuleName(fullModuleName);
} else {
return null;
}
}
}
/**
* This policy produces unqualified names for entities defined in the current module, and Prelude entities
* (if their names are not also defined in the current module). For qualified names, it produces fully qualified
* module names.
* <p>
* This policy is mainly intended to produce user documentation where the only contextual information is the
* current module, and the produced names are not further hyperlinked or described by tooltips with their
* fully qualified names.
*
* @author Joseph Wong
*/
public static final class UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous extends ScopedEntityNamingPolicy {
private final ModuleTypeInfo moduleTypeInfo;
public UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous(ModuleTypeInfo moduleTypeInfo) {
if(moduleTypeInfo == null) {
throw new NullPointerException();
}
this.moduleTypeInfo = moduleTypeInfo;
}
/**
* @return true if entity's name should be fully-qualified, or false if it should be unqualified.
* Note: It's possible for a naming policy to return a name that is neither qualified nor unqualified,
* but rather in some custom string format (eg, localized type names, strings
* like "List of Int" for [Int], etc.). For such cases, override getName as well as useQualifiedName.
*/
private boolean useQualifiedName(IdentifierInfo.TopLevel topLevelIdentifier) {
QualifiedName qualifiedName = topLevelIdentifier.getResolvedName();
ModuleName moduleName = qualifiedName.getModuleName();
String unqualifiedName = qualifiedName.getUnqualifiedName();
// In the current module, don't fully qualify
if (moduleName.equals(moduleTypeInfo.getModuleName())) {
return false;
}
// In Prelude, so check to see if the current module also defines an entity with that name
if (moduleName.equals(CAL_Prelude.MODULE_NAME)) {
if (topLevelIdentifier instanceof IdentifierInfo.TopLevel.FunctionOrClassMethod &&
moduleTypeInfo.getFunctionOrClassMethod(unqualifiedName) != null) {
return true;
}
if (topLevelIdentifier instanceof IdentifierInfo.TopLevel.TypeClass &&
moduleTypeInfo.getTypeClass(unqualifiedName) != null) {
return true;
}
if (topLevelIdentifier instanceof IdentifierInfo.TopLevel.TypeCons &&
moduleTypeInfo.getTypeConstructor(unqualifiedName) != null) {
return true;
}
if (topLevelIdentifier instanceof IdentifierInfo.TopLevel.DataCons &&
moduleTypeInfo.getDataConstructor(unqualifiedName) != null) {
return true;
}
// does not conflict with anything in the current module, so use unqualified name
return false;
}
// Otherwise, qualify it
return true;
}
/** {@inheritDoc} */
@Override
ModuleName getModuleNameForScopedEntity(ScopedEntity entity) {
if (entity instanceof Function || entity instanceof ClassMethod) {
return getModuleNameForTopLevelIdentifier(new IdentifierInfo.TopLevel.FunctionOrClassMethod(entity.getName()));
}
if (entity instanceof TypeClass) {
return getModuleNameForTopLevelIdentifier(new IdentifierInfo.TopLevel.TypeClass(entity.getName()));
}
if (entity instanceof TypeConstructor) {
return getModuleNameForTopLevelIdentifier(new IdentifierInfo.TopLevel.TypeCons(entity.getName()));
}
if (entity instanceof DataConstructor) {
return getModuleNameForTopLevelIdentifier(new IdentifierInfo.TopLevel.DataCons(entity.getName()));
}
return null;
}
/**
* @param topLevelIdentifier
* @return the module name to be used for the entity named by the identifier. Null is returned if the entity's name is to be unqualified.
*/
ModuleName getModuleNameForTopLevelIdentifier(IdentifierInfo.TopLevel topLevelIdentifier) {
if (useQualifiedName(topLevelIdentifier)) {
return topLevelIdentifier.getResolvedName().getModuleName();
} else {
return null;
}
}
/** {@inheritDoc} */
@Override
public String getName(ScopedEntity entity) {
ModuleName moduleName = getModuleNameForScopedEntity(entity);
if (moduleName != null) {
return QualifiedName.make(moduleName, entity.getName().getUnqualifiedName()).getQualifiedName();
} else {
return entity.getName().getUnqualifiedName();
}
}
/**
* @param topLevelIdentifier
* @return the display name to be used for the entity named by the identifier.
*/
public String getName(IdentifierInfo.TopLevel topLevelIdentifier) {
ModuleName moduleName = getModuleNameForTopLevelIdentifier(topLevelIdentifier);
if (moduleName != null) {
return QualifiedName.make(moduleName, topLevelIdentifier.getResolvedName().getUnqualifiedName()).getQualifiedName();
} else {
return topLevelIdentifier.getResolvedName().getUnqualifiedName();
}
}
/**
* @return the current module name.
*/
public ModuleName getCurrentModuleName() {
return moduleTypeInfo.getModuleName();
}
}
}