/*
* 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.
*/
/*
* LocalNameRenamer.java
* Created: Sep 24, 2007
* By: Joseph Wong
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openquark.cal.compiler.IdentifierOccurrence.Binding;
import org.openquark.cal.compiler.IdentifierOccurrence.Reference;
import org.openquark.cal.compiler.IdentifierOccurrenceFinder.FinderState;
import org.openquark.cal.compiler.IdentifierResolver.SymbolTable;
import org.openquark.cal.compiler.IdentifierResolver.VisitorArgument;
import org.openquark.cal.compiler.IdentifierResolver.SymbolTable.LocalScope;
import org.openquark.cal.compiler.SourceModel.FunctionDefn;
/**
* This class encapsulates the renaming logic for handling locally-bound names
* (let-bound functions and local pattern match variables, case-bound patterns, lambda-bound parameters,
* and parameters of top-level and local functions).
* <p>
* The renaming logic works in two passes:
* <ol>
* <li>
* The first pass gathers the following information:
* <ul>
* <li>which algebraic function contains the local variable
* <li>what are the other names defined locally within that algebraic function
* <li>among the local definitions that are <i>shadowed</i> by the renamed definition, which ones
* must be renamed in order to avoid a change in semantics.
* <p>
* For example:
* <pre>
* let
* foo = 3.0;
* in
* let
* bar = 4.0;
* in
* foo + bar;
* </pre>
* In the above, if bar is renamed to foo, the existing foo reference must be renamed, because
* the reference to foo in 'foo + bar' is in the scope of the bar definition to be renamed.
* On the other hand, given:
* <pre>
* let
* foo = 3.0;
* baz =
* let
* bar = 4.0;
* in
* bar;
* in
* foo + baz;
* </pre>
* If bar is renamed to foo here, the original foo does not need to be renamed, because there
* are no references to it within the scope defining bar.
* </ul>
* <li>
* The second pass takes the information from the first pass, and calculates:
* <ul>
* <li>which local definitions <i>shadow</i> the renamed definition. These are to be renamed so as to
* not conflict with the renamed definition.
* <p>
* For example:
* <pre>
* let
* foo = 3.0;
* in
* let
* bar = 4.0;
* in
* foo + bar;
* </pre>
* If foo is renamed to bar in the above, then bar needs to be renamed so as to preserve semantics.
* <li>where are all the occurrences of the variable to be renamed
* <li>which unqualified names refer to top-level functions or class methods and would be shadowed
* by the renamed definition - these will be qualified to avoid conflicts
* <li>which occurrences correspond to the affected shadowed definitions and the shadowing definitions
* - these will need to be renamed to something else to avoid conflicts (these are termed "collateral damage")
* </ul>
* </ol>
* The renaming algorithm takes the information gathered by the two passes to produce a set of
* required source modifications.
*
* @author Joseph Wong
*/
/*
* @history
*
* The local name renaming logic is based on the work in CALSourceGenerator.ParameterRenamer,
* which handles the renaming of lambda parameters.
*/
final class LocalNameRenamer {
/**
* The first pass gathers the following information:
* <ul>
* <li>which algebraic function contains the local variable
* <li>what are the other names defined locally within that algebraic function
* <li>among the local definitions that are <i>shadowed</i> by the renamed definition, which
* ones must be renamed in order to avoid a change in semantics.
* </ul>
*
* @author Joseph Wong
*/
private static final class FirstPass extends IdentifierOccurrenceFinder<Void> {
/**
* The target to be renamed.
*/
private final IdentifierInfo.Local target;
/**
* The new name for the target.
*/
private final String newName;
/**
* The current algebraic function - it is non-null only if an algebraic function is being visited.
*/
private FunctionDefn.Algebraic currentAlgebraicFunction;
/**
* The set of local names defined in the current algebraic function.
*/
private final Set<String> localNamesInCurrentAlgebraicFunction = new HashSet<String>();
/**
* The algebraic function containing the target - this is null until the target is found.
*/
private FunctionDefn.Algebraic algebraicFunctionContainingTarget;
/**
* The set of local names defined in the algebraic function containing the target - this is empty until
* the target is found.
*/
private final Set<String> localNamesInAlgebraicFunctionContainingTarget = new HashSet<String>();
/**
* A map of all local definitions that are shadowed by the new name for the target.
*/
private final Map<IdentifierInfo.Local, Binding<IdentifierInfo.Local>> shadowedLocalDefinitions =
new HashMap<IdentifierInfo.Local, Binding<IdentifierInfo.Local>>();
/**
* A submap of {@link #shadowedLocalDefinitions} containing only those shadowed local definitions
* that <i>must</i> be renamed to avoid conflicts.
*/
private final Map<IdentifierInfo.Local, Binding<IdentifierInfo.Local>> affectedShadowedLocalDefinitions =
new HashMap<IdentifierInfo.Local, Binding<IdentifierInfo.Local>>();
/**
* The scope that defines the target - this is null until the target is found.
*/
private LocalScope definingScopeOfTarget;
/**
* Construct an instance of this class.
* @param currentModuleName the name of the module associated with the source being visited.
* @param target the target to be renamed.
* @param newName the new name for the target.
*/
FirstPass(final ModuleName currentModuleName, final IdentifierInfo.Local target, final String newName) {
super(currentModuleName);
if (target == null || newName == null) {
throw new NullPointerException();
}
this.target = target;
this.newName = newName;
}
/**
* @return the algebraic function containing the target - this is null until the target is found.
*/
FunctionDefn.Algebraic getAlgebraicFunctionContainingTarget() {
return algebraicFunctionContainingTarget;
}
/**
* @return the set of local names defined in the algebraic function containing the target - this is null until the target is found.
*/
Set<String> getLocalNamesInAlgebraicFunctionContainingTarget() {
return localNamesInAlgebraicFunctionContainingTarget;
}
/**
* @return a map containing only those shadowed local definitions that must be renamed to avoid conflicts.
*/
Map<IdentifierInfo.Local, Binding<IdentifierInfo.Local>> getAffectedShadowedLocalDefinitions() {
return affectedShadowedLocalDefinitions;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<FinderState> arg) {
// optimization: if already found the function containing the target, just return
if (algebraicFunctionContainingTarget != null) {
return null;
}
currentAlgebraicFunction = algebraic;
localNamesInCurrentAlgebraicFunction.clear();
super.visit_FunctionDefn_Algebraic(algebraic, arg);
if (algebraicFunctionContainingTarget == algebraic) {
// we have found the right function, so copy so the local names
localNamesInAlgebraicFunctionContainingTarget.addAll(localNamesInCurrentAlgebraicFunction);
}
// reset the state
currentAlgebraicFunction = null;
localNamesInCurrentAlgebraicFunction.clear();
return null;
}
/**
* {@inheritDoc}
*/
@Override
protected void handleNewLocalScope(final LocalScope scope) {
// first loop: set the definingScopeOfTarget if the target is indeed defined in this scope
for (final Binding<IdentifierInfo.Local> binding : scope.getBindings()) {
if (target.equals(binding.getIdentifierInfo())) {
algebraicFunctionContainingTarget = currentAlgebraicFunction;
definingScopeOfTarget = scope;
}
}
// second loop - done by superclass implementation:
super.handleNewLocalScope(scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleLocalVariableBinding(final Binding<IdentifierInfo.Local> binding, final LocalScope scope) {
if (target.equals(binding.getIdentifierInfo())) {
// find all the local definitions that are shadowed
SymbolTable scopeToCheck = scope;
while (scopeToCheck instanceof LocalScope) {
final LocalScope localAncestorScope = (LocalScope)scopeToCheck;
for (final Binding<IdentifierInfo.Local> bindingInAncestor : localAncestorScope.getBindings()) {
if (newName.equals(bindingInAncestor.getIdentifierInfo().getVarName())) {
shadowedLocalDefinitions.put(bindingInAncestor.getIdentifierInfo(), bindingInAncestor);
}
}
if (scopeToCheck instanceof LocalScope) {
scopeToCheck = ((LocalScope)scopeToCheck).getParent();
} else {
break;
}
}
}
localNamesInCurrentAlgebraicFunction.add(binding.getIdentifierInfo().getVarName());
super.handleLocalVariableBinding(binding, scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleVarNameReference(final Reference<? extends IdentifierInfo> reference, final Binding<? extends IdentifierInfo> binding, final SymbolTable scope) {
if (scope instanceof LocalScope) {
if (isSameOrDescendantScope((LocalScope)scope, definingScopeOfTarget)) {
if (binding.getIdentifierInfo() instanceof IdentifierInfo.Local) {
// If the reference appears in the scope of the target, and the definition is shadowed,
// then the definition is affected.
final Binding<IdentifierInfo.Local> shadowedLocalDefinition = shadowedLocalDefinitions.get(binding.getIdentifierInfo());
if (shadowedLocalDefinition != null) {
affectedShadowedLocalDefinitions.put(shadowedLocalDefinition.getIdentifierInfo(), shadowedLocalDefinition);
}
}
}
}
super.handleVarNameReference(reference, binding, scope);
}
}
/**
* The second pass takes the information from the first pass, and calculates:
* <ul>
* <li>which local definitions <i>shadows</i> the renamed definition. These are to be renamed
* so as to not conflict with the renamed definition.
* <li>where are all the occurrences of the variable to be renamed
* <li>which unqualified names refer to top-level functions or class methods and would be
* shadowed by the renamed definition - these will be qualified to avoid conflicts
* <li>which occurrences correspond to the affected shadowed definitions and the shadowing
* definitions - these will need to be renamed to something else to avoid conflicts (these are
* termed "collateral damage")
* </ul>
*
* @author Joseph Wong
*/
private static final class SecondPass extends IdentifierOccurrenceFinder<Void> {
/**
* The target to be renamed.
*/
private final IdentifierInfo.Local target;
/**
* The new name for the target.
*/
private final String newName;
/**
* A map containing only those shadowed local definitions that <i>must</i> be renamed to avoid conflicts.
* Can *not* be null.
*/
private final Map<IdentifierInfo.Local, Binding<IdentifierInfo.Local>> affectedShadowedLocalDefinitions;
/**
* The scope that defines the target. Can *not* be null.
*/
private LocalScope definingScopeOfTarget;
/**
* A set for collecting the local definitions that would shadow the renamed target.
*/
private final Set<IdentifierInfo.Local> shadowingLocalDefinitions = new HashSet<IdentifierInfo.Local>();
/**
* The algebraic function containing the target. Can *not* be null.
*/
private final FunctionDefn.Algebraic algebraicFunctionContainingTarget;
/**
* A list of the occurrences of the target to be renamed.
*/
private final List<IdentifierOccurrence<?>> occurrencesToRename = new ArrayList<IdentifierOccurrence<?>>();
/**
* A list of the unqualified name occurrences referring to top-level functions or class
* methods that would be shadowed by the renamed target.
*/
private final List<Reference.Qualifiable<?>> occurrencesToQualify = new ArrayList<Reference.Qualifiable<?>>();
/**
* A list of the occurrences corresponding to the affected shadowed definitions and the
* shadowing definitions - these will need to be renamed to something else to avoid
* conflicts (these are termed "collateral damage").
*/
private final List<IdentifierOccurrence<?>> collaterallyDamagedLocalNameOccurrences = new ArrayList<IdentifierOccurrence<?>>();
/**
* Constructs an instance of this class.
* @param currentModuleName the name of the module associated with the source being visited.
* @param target the target to be renamed.
* @param newName the new name for the target.
* @param algebraicFunctionContainingTarget the algebraic function containing the target. Can *not* be null.
* @param affectedShadowedLocalDefinitions a map containing only those shadowed local definitions that must be renamed to avoid conflicts. Can *not* be null.
*/
SecondPass(final ModuleName currentModuleName, final IdentifierInfo.Local target, final String newName, final FunctionDefn.Algebraic algebraicFunctionContainingTarget, final Map<IdentifierInfo.Local, Binding<IdentifierInfo.Local>> affectedShadowedLocalDefinitions) {
super(currentModuleName);
if (target == null || newName == null || algebraicFunctionContainingTarget == null || affectedShadowedLocalDefinitions == null) {
throw new NullPointerException();
}
this.target = target;
this.newName = newName;
this.algebraicFunctionContainingTarget = algebraicFunctionContainingTarget;
this.affectedShadowedLocalDefinitions = affectedShadowedLocalDefinitions;
}
/**
* @return a list of the occurrences of the target to be renamed.
*/
List<IdentifierOccurrence<?>> getOccurrencesToRename() {
return occurrencesToRename;
}
/**
* @return a list of the unqualified name occurrences referring to top-level functions or
* class methods that would be shadowed by the renamed target.
*/
List<Reference.Qualifiable<?>> getOccurrencesToQualify() {
return occurrencesToQualify;
}
/**
* @return a list of the occurrences corresponding to the affected shadowed definitions and
* the shadowing definitions - these will need to be renamed to something else to
* avoid conflicts (these are termed "collateral damage").
*/
List<IdentifierOccurrence<?>> getCollaterallyDamagedLocalNameOccurrences() {
return collaterallyDamagedLocalNameOccurrences;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<FinderState> arg) {
// optimization: skip everything except the function containing the target
if (algebraic != algebraicFunctionContainingTarget) {
return null;
}
return super.visit_FunctionDefn_Algebraic(algebraic, arg);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleNewLocalScope(final LocalScope scope) {
// first loop: set the definingScopeOfTarget if the target is indeed defined in this scope
for (final Binding<IdentifierInfo.Local> binding : scope.getBindings()) {
if (target.equals(binding.getIdentifierInfo())) {
definingScopeOfTarget = scope;
}
}
// second loop - done by superclass implementation:
// determining if the binding corresponds to the one needing to be renamed
// or to one of the "collaterally damaged" names that need to be renamed to something else
super.handleNewLocalScope(scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleLocalVariableBinding(final Binding<IdentifierInfo.Local> binding, final LocalScope scope) {
if (target.equals(binding.getIdentifierInfo())) {
occurrencesToRename.add(binding);
} else if (affectedShadowedLocalDefinitions.containsKey(binding.getIdentifierInfo())) {
collaterallyDamagedLocalNameOccurrences.add(binding);
} else if (isSameOrDescendantScope(scope, definingScopeOfTarget) && newName.equals(binding.getIdentifierInfo().getVarName())) {
// check to see if this definition shadows the target
shadowingLocalDefinitions.add(binding.getIdentifierInfo());
collaterallyDamagedLocalNameOccurrences.add(binding);
}
super.handleLocalVariableBinding(binding, scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleVarNameReference(final Reference<? extends IdentifierInfo> reference, final Binding<? extends IdentifierInfo> binding, final SymbolTable scope) {
if (target.equals(reference.getIdentifierInfo())) {
occurrencesToRename.add(reference);
} else if (shadowingLocalDefinitions.contains(reference.getIdentifierInfo())) {
collaterallyDamagedLocalNameOccurrences.add(reference);
} else if (affectedShadowedLocalDefinitions.containsKey(reference.getIdentifierInfo())) {
collaterallyDamagedLocalNameOccurrences.add(reference);
} else if (reference instanceof Reference.Qualifiable<?>) {
final Reference.Qualifiable<?> qualifiableReference = (Reference.Qualifiable<?>)reference;
if (qualifiableReference.getModuleNameOccurrence() == null) {
if (newName.equals(qualifiableReference.getIdentifierInfo().getResolvedName().getUnqualifiedName())) {
// unqualified occurrence to a top-level entity with the same name as the new name, so need
// to qualify it
occurrencesToQualify.add(qualifiableReference);
}
}
}
super.handleVarNameReference(reference, binding, scope);
}
}
/** Private constructor. */
private LocalNameRenamer() {}
/**
* Helper method for determining whether a scope is the same, or is a descendant of, another scope.
* @param potentialDescendantScope the potential descendant scope.
* @param potentialAncestorScope the potential ancestor scope.
* @return true if the first scope is the same, or a descendant of, the second scope.
*/
private static boolean isSameOrDescendantScope(final LocalScope potentialDescendantScope, final LocalScope potentialAncestorScope) {
if (potentialAncestorScope == null) {
return false;
}
if (potentialDescendantScope == potentialAncestorScope) {
return true;
}
SymbolTable parent = potentialDescendantScope.getParent();
while (parent instanceof LocalScope) {
if (parent == potentialAncestorScope) {
return true;
}
if (parent instanceof LocalScope) {
parent = ((LocalScope)parent).getParent();
} else {
break;
}
}
return false;
}
/**
* Runs the renaming logic for a local variable and returns the necessary modifications in
* a {@link SourceModifier}.
*
* @param moduleTypeInfo ModuleTypeInfo for the module to process
* @param oldName the identifier being renamed
* @param newName the name name
* @param messageLogger CompilerMessageLogger for logging failures
* @return a SourceModifier that will apply the renaming to the source.
*/
static SourceModifier getSourceModifier(final ModuleTypeInfo moduleTypeInfo, final String sourceText, final IdentifierInfo.Local oldName, final String newName, final CompilerMessageLogger messageLogger) {
final SourceModifier sourceModifier = new SourceModifier();
// if the module is a sourceless module, then there is not much we can do with it.
if (sourceText.length() == 0) {
return sourceModifier;
}
final ModuleName moduleName = moduleTypeInfo.getModuleName();
final SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger);
if (moduleDefn == null) {
return sourceModifier;
}
final FirstPass firstPass = new FirstPass(moduleName, oldName, newName);
moduleDefn.accept(firstPass, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make()));
final Set<String> localNamesInAlgebraicFunctionContainingTarget = firstPass.getLocalNamesInAlgebraicFunctionContainingTarget();
final SecondPass secondPass = new SecondPass(moduleName, oldName, newName, firstPass.getAlgebraicFunctionContainingTarget(), firstPass.getAffectedShadowedLocalDefinitions());
moduleDefn.accept(secondPass, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make()));
for (final IdentifierOccurrence<?> occurrenceToRename : secondPass.getOccurrencesToRename()) {
addLocalNameRenameModification(sourceModifier, sourceText, occurrenceToRename, newName);
}
for (final Reference.Qualifiable<?> occurrenceToQualify : secondPass.getOccurrencesToQualify()) {
final QualifiedName fullyQualifiedName = occurrenceToQualify.getIdentifierInfo().getResolvedName();
final ModuleName moduleNameOfReference = fullyQualifiedName.getModuleName();
final ModuleName minimallyQualifiedModuleNameOfReference = moduleTypeInfo.getModuleNameResolver().getMinimallyQualifiedModuleName(moduleNameOfReference);
final QualifiedName qualifiedName = QualifiedName.make(minimallyQualifiedModuleNameOfReference, fullyQualifiedName.getUnqualifiedName());
sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToQualify.getSourceRange(), qualifiedName.toSourceText()));
}
if (!secondPass.getCollaterallyDamagedLocalNameOccurrences().isEmpty()) {
final String newNameForCollateralDamage = getNewNameForCollateralDamage(newName, localNamesInAlgebraicFunctionContainingTarget);
for (final IdentifierOccurrence<?> collateralDamage : secondPass.getCollaterallyDamagedLocalNameOccurrences()) {
addLocalNameRenameModification(sourceModifier, sourceText, collateralDamage, newNameForCollateralDamage);
}
}
return sourceModifier;
}
/**
* Produces a non-conflicting name for definitions deemed "collateral damage".
* @param newName the new name of the target.
* @param localNamesInAlgebraicFunctionContainingTarget the other names in the algebraic function containing the target <i>to be avoided</i>.
* @return a non-conflicting name.
*/
private static String getNewNameForCollateralDamage(final String newName, final Set<String> localNamesInAlgebraicFunctionContainingTarget) {
String newNameForCollateralDamage;
int disambiguator = 1;
do {
newNameForCollateralDamage = newName + "_" + disambiguator;
disambiguator++;
} while (localNamesInAlgebraicFunctionContainingTarget.contains(newNameForCollateralDamage));
return newNameForCollateralDamage;
}
/**
* Adds a source modification corresponding to a local name renaming.
* @param sourceModifier the source modifier to add to.
* @param sourceText the source text.
* @param occurrenceToRename the occurrence to rename.
* @param newName the new name.
*/
private static void addLocalNameRenameModification(final SourceModifier sourceModifier, final String sourceText, final IdentifierOccurrence<?> occurrenceToRename, final String newName) {
if (occurrenceToRename instanceof Binding.PunnedTextualDataConsFieldName<?>) {
final Binding.PunnedTextualDataConsFieldName<?> punnedBinding = (Binding.PunnedTextualDataConsFieldName<?>)occurrenceToRename;
// need to unpun the pattern
sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), punnedBinding.getIdentifierInfo().getVarName() + " = " + newName));
} else if (occurrenceToRename instanceof Binding.PunnedTextualRecordFieldName<?>) {
final Binding.PunnedTextualRecordFieldName<?> punnedBinding = (Binding.PunnedTextualRecordFieldName<?>)occurrenceToRename;
// need to unpun the pattern
sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), punnedBinding.getIdentifierInfo().getVarName() + " = " + newName));
} else {
sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), newName));
}
}
}