package org.overture.codegen.vdm2java;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.overture.ast.analysis.AnalysisException;
import org.overture.ast.analysis.DepthFirstAnalysisAdaptor;
import org.overture.ast.intf.lex.ILexIdentifierToken;
import org.overture.ast.intf.lex.ILexLocation;
import org.overture.ast.intf.lex.ILexNameToken;
import org.overture.codegen.analysis.vdm.Renaming;
import org.overture.codegen.ir.ITempVarGen;
import org.overture.typechecker.utilities.type.ClassTypeFinder;
public class JavaIdentifierNormaliser extends DepthFirstAnalysisAdaptor
{
private Set<String> allNames;
private Map<String, String> renamingsSoFar;
private ITempVarGen nameGen;
private Set<Renaming> renamings;
public JavaIdentifierNormaliser(Set<String> allNames, ITempVarGen nameGen)
{
this.allNames = allNames;
this.renamingsSoFar = new HashMap<String, String>();
this.nameGen = nameGen;
this.renamings = new HashSet<Renaming>();
}
@Override
public void caseILexIdentifierToken(ILexIdentifierToken node)
throws AnalysisException
{
validateName(node.getName(), node.getLocation(), /* no module */ null);
}
@Override
public void caseILexNameToken(ILexNameToken node) throws AnalysisException
{
validateName(node.getName(), node.getLocation(), node.getModule());
}
private void validateName(String name, ILexLocation location, String module)
{
if (!contains(location))
{
boolean rename = false;
String newName = name;
if (!isImplicitlyNamed(name)
&& !JavaCodeGenUtil.isValidJavaIdentifier(name))
{
newName = getReplacementName(name);
rename = true;
}
String newModule = module;
if (module != null && !isImplicitlyNamed(module)
&& !JavaCodeGenUtil.isValidJavaIdentifier(module))
{
newModule = getReplacementName(module);
rename = true;
}
if (rename)
{
this.renamings.add(new Renaming(location, name, newName, module, newModule));
}
}
}
private boolean isImplicitlyNamed(String name)
{
// "?" is used for implicitly named things
return name.equals("?")
|| name.startsWith(ClassTypeFinder.UNION_CLASS_PREFIX);
}
private boolean contains(ILexLocation loc)
{
for (Renaming r : renamings)
{
if (r.getLoc().equals(loc))
{
return true;
}
}
return false;
}
public Set<Renaming> getRenamings()
{
return renamings;
}
public String getReplacementName(String invalidName)
{
String name = renamingsSoFar.get(invalidName);
if (name != null)
{
// A replacement name has previously been computed for 'invalidName' just use that
return name;
}
String suggestion = "";
if (JavaCodeGenUtil.isJavaKeyword(invalidName))
{
// appending '_' to a Java keyword makes it a valid identifier
suggestion = invalidName + "_";
} else
{
suggestion = patchName(invalidName);
}
// Now it is important that the suggestion does not collide with a name in the model
if (allNames.contains(suggestion))
{
// Okay the name is already used so we need to compute a new one (e.g. <suggestion>_42)
String prefix = suggestion + "_";
suggestion = nameGen.nextVarName(prefix);
while (allNames.contains(suggestion))
{
suggestion = nameGen.nextVarName(prefix);
}
}
// else {the suggestion is valid and does not collide with another name in the model}
// By now we should have computed a name that does not appear in the model
// Register the name we are about to use to replace 'invalidName'
renamingsSoFar.put(invalidName, suggestion);
allNames.add(suggestion);
return suggestion;
}
private String patchName(String invalidName)
{
// Say we have an invalid name such as s'
final String PATCH = "_X_";
List<Integer> correctionIndices = JavaCodeGenUtil.computeJavaIdentifierCorrections(invalidName);
String tmp = "";
char[] chars = invalidName.toCharArray();
for (int i = 0; i < chars.length; i++)
{
if (correctionIndices.contains(i))
{
tmp += PATCH;
} else
{
tmp += chars[i];
}
}
// Return the patch named (e.g. s_X_)
return tmp;
}
}