package org.overture.codegen.ir;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.apache.velocity.app.Velocity;
import org.overture.ast.analysis.AnalysisException;
import org.overture.ast.analysis.DepthFirstAnalysisAdaptor;
import org.overture.ast.definitions.PDefinition;
import org.overture.ast.definitions.SClassDefinition;
import org.overture.ast.definitions.SFunctionDefinition;
import org.overture.ast.definitions.SOperationDefinition;
import org.overture.ast.expressions.ANewExp;
import org.overture.ast.expressions.ANotYetSpecifiedExp;
import org.overture.ast.lex.Dialect;
import org.overture.ast.modules.AModuleModules;
import org.overture.ast.node.INode;
import org.overture.ast.statements.ANotYetSpecifiedStm;
import org.overture.ast.util.modules.ModuleList;
import org.overture.codegen.analysis.vdm.UnreachableStmRemover;
import org.overture.codegen.assistant.DeclAssistantIR;
import org.overture.codegen.ir.statements.ABlockStmIR;
import org.overture.codegen.merging.MergeVisitor;
import org.overture.codegen.trans.BlockCleanupTrans;
import org.overture.codegen.trans.OldNameRenamer;
import org.overture.codegen.trans.assistants.TransAssistantIR;
import org.overture.codegen.utils.Generated;
import org.overture.codegen.utils.GeneratedData;
import org.overture.codegen.utils.GeneratedModule;
import org.overture.config.Settings;
/**
* This class is a base class implementation of a code generator that is developed on top of the Code Generation
* Platform (CGP). Code generators that want to translate VDM models into some target language can inherit from this
* class.
*
* @author pvj
*/
abstract public class CodeGenBase implements IREventCoordinator
{
/**
* The {@link #generator} field is used for constructing and transforming the Intermediate Representation (IR)
* generated from the VDM AST.
*/
protected IRGenerator generator;
/**
* The {@link #transAssistant} provides functionality that is convenient when implementing transformations.
*/
protected TransAssistantIR transAssistant;
/**
* The {@link #irObserver} can receive and manipulate the initial and final versions of the IR via the
* {@link #initialIrEvent(List)} and {@link #finalIrEvent(List)} methods.
*/
protected IREventObserver irObserver;
/**
* Used to log information during the code generation process.
*/
protected static Logger log = Logger.getLogger(CodeGenBase.class.getName());
/**
* Constructs this code generator by initializing the template engine and constructing the {@link #generator}.
*/
protected CodeGenBase()
{
super();
initVelocity();
this.irObserver = null;
this.generator = new IRGenerator();
this.transAssistant = new TransAssistantIR(generator.getIRInfo());
}
/**
* This method clears the state of the {@link #generator}, which forms the first step of the code generation
* process. That is, this method is invoked upon entering {@link #generate(List)}.
*/
protected void clear()
{
generator.clear();
}
/**
* The entry point of this class. This method translates a VDM AST into target language code. Use of the template
* method design pattern ensures that the state of this code generator is properly initialized and that the VDM AST
* is pre-processed in accordance with the {@link #preProcessAst(List)} method.
*
* @param ast
* The VDM AST, which can be either VDM modules or classes.
* @return The data generated from the VDM AST.
* @throws AnalysisException
* If a problem occurs during the construction of the IR.
*/
public GeneratedData generate(List<INode> ast) throws AnalysisException
{
clear();
if (Settings.dialect == Dialect.VDM_SL)
{
ModuleList moduleList = new ModuleList(getModules(ast));
moduleList.combineDefaults();
ast = getNodes(moduleList);
}
preProcessAst(ast);
List<IRStatus<PIR>> statuses = new LinkedList<>();
for (INode node : ast)
{
genIrStatus(statuses, node);
}
return genVdmToTargetLang(statuses);
}
/**
* This method translates a VDM node into an IR status.
*
* @param statuses
* A list of previously generated IR statuses. The generated IR status will be added to this list.
* @param node
* The VDM node from which we generate an IR status
* @throws AnalysisException
* If something goes wrong during the construction of the IR status.
*/
protected void genIrStatus(List<IRStatus<PIR>> statuses, INode node)
throws AnalysisException
{
IRStatus<PIR> status = generator.generateFrom(node);
if (status != null)
{
statuses.add(status);
}
}
/**
* Translates a list of IR statuses into target language code. This method must be implemented by all code
* generators that inherit from this class. This method is invoked by {@link #generate(List)}.
*
* @param statuses
* The IR statuses holding the nodes to be code generated.
* @return The generated code as well as information about the code generation process.
* @throws AnalysisException
* If something goes wrong during the code generation process.
*/
abstract protected GeneratedData genVdmToTargetLang(
List<IRStatus<PIR>> statuses) throws AnalysisException;
/**
* This method pre-processes the VDM AST by<br>
* (1) computing a definition table that can be used during the analysis of the IR to determine if a VDM identifier
* state designator is local or not.<br>
* (2) Removing unreachable statements from the VDM AST <br>
* (3) Normalizing old names by replacing tilde signs '~' with underscores '_' <br>
* (4) Simplifying the standard libraries by cutting nodes that are not likely to be meaningful during the analysis
* of the VDM AST. Library classes are processed using {@link #simplifyLibrary(INode)}, whereas non-library modules
* or classes are processed using {@link #preProcessVdmUserClass(INode)}.
*
* @param ast
* The VDM AST subject to pre-processing.
* @throws AnalysisException
* In case something goes wrong during the VDM AST analysis.
*/
protected void preProcessAst(List<INode> ast) throws AnalysisException
{
generator.computeDefTable(getUserModules(ast));
removeUnreachableStms(ast);
handleOldNames(ast);
for (INode node : ast)
{
if (getInfo().getAssistantManager().getDeclAssistant().isLibrary(node))
{
simplifyLibrary(node);
} else
{
preProcessVdmUserClass(node);
}
}
}
/**
* Initializes the Apache Velocity template engine.
*/
protected void initVelocity()
{
Velocity.setProperty("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.NullLogSystem");
Velocity.init();
}
/**
* Formats the generated code. By default, this method simply returns the input unmodified.
*
* @param code
* The unformatted code.
* @return The formatted code.
*/
protected String formatCode(StringWriter code)
{
return code.toString();
}
/**
* Convenience method for extracting the VDM module definitions from a list of VDM nodes.
*
* @param ast
* A list of VDM nodes.
* @return The module definitions extracted from <code>ast</code>.
*/
public static List<AModuleModules> getModules(List<INode> ast)
{
List<AModuleModules> modules = new LinkedList<>();
for (INode n : ast)
{
if (n instanceof AModuleModules)
{
modules.add((AModuleModules) n);
}
}
return modules;
}
/**
* Convenience method for extracting the VDM class definitions from a list of VDM nodes.
*
* @param ast
* A list of VDM nodes.
* @return The class definitions extracted from <code>ast</code>.
*/
public static List<SClassDefinition> getClasses(List<INode> ast)
{
List<SClassDefinition> classes = new LinkedList<>();
for (INode n : ast)
{
if (n instanceof SClassDefinition)
{
classes.add((SClassDefinition) n);
}
}
return classes;
}
/**
* Convenience method for converting a VDM AST into a list of nodes.
*
* @param ast
* The VDM AST.
* @return The VDM AST as a list of nodes.
*/
public static List<INode> getNodes(List<? extends INode> ast)
{
List<INode> nodes = new LinkedList<>();
nodes.addAll(ast);
return nodes;
}
/**
* Applies the {@link #cleanup(IRStatus)} method to all the IR statuses.
*
* @param statuses
* The IR statuses
*/
public void cleanup(List<IRStatus<PIR>> statuses)
{
for (IRStatus<PIR> s : statuses)
{
cleanup(s);
}
}
/**
* Cleans up an IR status by removing redundant {@link ABlockStmIR} nodes using the {@link BlockCleanupTrans}
* transformation.
*
* @param status
* The IR status
*/
public void cleanup(IRStatus<PIR> status)
{
if (status != null && status.getIrNode() != null)
{
try
{
status.getIrNode().apply(new BlockCleanupTrans());
} catch (org.overture.codegen.ir.analysis.AnalysisException e)
{
e.printStackTrace();
log.error("Problem encountered when trying to clean up blocks");
}
}
}
/**
* Given an IR status this method determines if it represents a test case or not.
*
* @param status
* The IR status.
* @return True if the <code>status</code> represents a test case - false otherwise.
*/
protected boolean isTestCase(IRStatus<? extends PIR> status)
{
return getInfo().getDeclAssistant().isTestCase(status.getIrNode().getSourceNode().getVdmNode());
}
/**
* This method extracts the user modules or classes from a VDM AST.
*
* @param ast
* The VDM AST.
* @return A list of user modules or classes.
*/
protected List<INode> getUserModules(List<? extends INode> ast)
{
List<INode> userModules = new LinkedList<INode>();
for (INode node : ast)
{
if (!getInfo().getDeclAssistant().isLibrary(node))
{
userModules.add(node);
}
}
return userModules;
}
/**
* This method removes unreachable statements from the VDM AST.
*
* @param ast
* The VDM AST subject to processing.
* @throws AnalysisException
* If something goes wrong when trying to remove unreachable statements from the <code>ast</code>.
*/
protected void removeUnreachableStms(List<? extends INode> ast)
throws AnalysisException
{
UnreachableStmRemover remover = new UnreachableStmRemover();
for (INode node : ast)
{
node.apply(remover);
}
}
/**
* Simplifies a VDM standard library class or module by removing sub-nodes that are likely not to be of interest
* during the generation of the IR.
*
* @param module
* A VDM class or module
*/
protected void simplifyLibrary(INode module)
{
List<PDefinition> defs = null;
if (module instanceof SClassDefinition)
{
defs = ((SClassDefinition) module).getDefinitions();
} else if (module instanceof AModuleModules)
{
defs = ((AModuleModules) module).getDefs();
} else
{
// Nothing to do
return;
}
for (PDefinition def : defs)
{
if (def instanceof SOperationDefinition)
{
SOperationDefinition op = (SOperationDefinition) def;
op.setBody(new ANotYetSpecifiedStm());
op.setPrecondition(null);
op.setPostcondition(null);
} else if (def instanceof SFunctionDefinition)
{
SFunctionDefinition func = (SFunctionDefinition) def;
func.setBody(new ANotYetSpecifiedExp());
func.setPrecondition(null);
func.setPostcondition(null);
}
}
}
/**
* Processes old names by replacing the tilde sign '~' with an underscore.
*
* @param vdmAst
* The VDM AST subject to processing.
* @throws AnalysisException
* If something goes wrong during the renaming process.
*/
protected void handleOldNames(List<? extends INode> vdmAst)
throws AnalysisException
{
OldNameRenamer oldNameRenamer = new OldNameRenamer();
for (INode module : vdmAst)
{
module.apply(oldNameRenamer);
}
}
/**
* Given a list of IR statuses this method retrieves the names of the user-defined test cases.
*
* @param statuses
* The list of IR statuses from which the test case names are retrieved
* @return The names of the user-defined test cases.
*/
protected List<String> getUserTestCases(List<IRStatus<PIR>> statuses)
{
List<String> userTestCases = new LinkedList<>();
for (IRStatus<PIR> s : statuses)
{
if (getInfo().getDeclAssistant().isTestCase(s.getVdmNode()))
{
userTestCases.add(getInfo().getDeclAssistant().getNodeName(s.getVdmNode()));
}
}
return userTestCases;
}
/**
* Pre-processing of a user class. This method is invoked by {@link #preProcessAst(List)}.
*
* @param vdmModule
* The user module or class.
*/
protected void preProcessVdmUserClass(INode vdmModule)
{
final Set<String> instantiatedClasses = new HashSet<>();
try {
vdmModule.apply(new DepthFirstAnalysisAdaptor() {
@Override
public void caseANewExp(ANewExp node) throws AnalysisException {
super.caseANewExp(node);
instantiatedClasses.add(node.getClassName().getName());
}
});
} catch (AnalysisException e) {
log.error("Got unexpected error when trying to find classes that are instantiated: " + e.getMessage());
e.printStackTrace();
}
this.generator.getIRInfo().getInstantiatedClasses().addAll(instantiatedClasses);
}
/**
* Determines whether a VDM module or class should be code generated or not.
*
* @param vdmNode
* The node to be checked.
* @return True if <code>node</code> should be code generated - false otherwise.
*/
protected boolean shouldGenerateVdmNode(INode vdmNode)
{
DeclAssistantIR declAssistant = getInfo().getDeclAssistant();
if (declAssistant.isLibrary(vdmNode))
{
return false;
} else
{
return true;
}
}
/**
* This method translates an IR module or class into target language code.
*
* @param mergeVisitor
* The visitor that translates the IR module or class into target language code.
* @param status
* The IR status that holds the IR node that we want to code generate.
* @return The generated code and data about what has been generated.
* @throws org.overture.codegen.ir.analysis.AnalysisException
* If something goes wrong during the code generation process.
*/
protected GeneratedModule genIrModule(MergeVisitor mergeVisitor,
IRStatus<? extends PIR> status)
throws org.overture.codegen.ir.analysis.AnalysisException
{
if (status.canBeGenerated())
{
mergeVisitor.init();
StringWriter writer = new StringWriter();
status.getIrNode().apply(mergeVisitor, writer);
boolean isTestCase = isTestCase(status);
GeneratedModule generatedModule;
if (mergeVisitor.hasMergeErrors())
{
generatedModule = new GeneratedModule(status.getIrNodeName(), status.getIrNode(), mergeVisitor.getMergeErrors(), isTestCase);
} else if (mergeVisitor.hasUnsupportedTargLangNodes())
{
generatedModule = new GeneratedModule(status.getIrNodeName(), new HashSet<VdmNodeInfo>(), mergeVisitor.getUnsupportedInTargLang(), isTestCase);
} else
{
generatedModule = new GeneratedModule(status.getIrNodeName(), status.getIrNode(), formatCode(writer), isTestCase);
generatedModule.setTransformationWarnings(status.getTransformationWarnings());
}
return generatedModule;
} else
{
return new GeneratedModule(status.getIrNodeName(), status.getUnsupportedInIr(), new HashSet<IrNodeInfo>(), isTestCase(status));
}
}
/**
* Translates an IR expression into target language code.
*
* @param expStatus
* The IR status that holds the expressions that we want to code generate.
* @param mergeVisitor
* The visitor that translates the IR expression into target language code.
* @return The generated code and data about what has been generated.
* @throws org.overture.codegen.ir.analysis.AnalysisException
* If something goes wrong during the code generation process.
*/
protected Generated genIrExp(IRStatus<SExpIR> expStatus,
MergeVisitor mergeVisitor)
throws org.overture.codegen.ir.analysis.AnalysisException
{
StringWriter writer = new StringWriter();
SExpIR expCg = expStatus.getIrNode();
if (expStatus.canBeGenerated())
{
mergeVisitor.init();
expCg.apply(mergeVisitor, writer);
if (mergeVisitor.hasMergeErrors())
{
return new Generated(mergeVisitor.getMergeErrors());
} else if (mergeVisitor.hasUnsupportedTargLangNodes())
{
return new Generated(new HashSet<VdmNodeInfo>(), mergeVisitor.getUnsupportedInTargLang());
} else
{
String code = writer.toString();
return new Generated(code);
}
} else
{
return new Generated(expStatus.getUnsupportedInIr(), new HashSet<IrNodeInfo>());
}
}
/**
* Emits generated code to a file. The file will be encoded using UTF-8.
*
* @param outputFolder
* The output folder that will store the generated code.
* @param fileName
* The name of the file that will store the generated code.
* @param code
* The generated code.
*/
public static void emitCode(File outputFolder, String fileName, String code)
{
emitCode(outputFolder, fileName, code, "UTF-8");
}
/**
* Emits generated code to a file.
*
* @param outputFolder
* outputFolder The output folder that will store the generated code.
* @param fileName
* The name of the file that will store the generated code.
* @param code
* The generated code.
* @param encoding
* The encoding to use for the generated code.
*/
public static void emitCode(File outputFolder, String fileName, String code,
String encoding)
{
try
{
File javaFile = new File(outputFolder, File.separator + fileName);
javaFile.getParentFile().mkdirs();
javaFile.createNewFile();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(javaFile, false), encoding));
BufferedWriter out = new BufferedWriter(writer);
out.write(code);
out.close();
} catch (IOException e)
{
log.error("Error when saving class file: " + fileName);
e.printStackTrace();
}
}
/**
* Registration of the {@link #irObserver}.
*/
@Override
public void registerIrObs(IREventObserver obs)
{
if (obs != null && irObserver == null)
{
irObserver = obs;
}
}
/**
* Unregistration of the {@link #irObserver}.
*/
@Override
public void unregisterIrObs(IREventObserver obs)
{
if (obs != null && irObserver == obs)
{
irObserver = null;
}
}
/**
* Notifies the {@link #irObserver} when the initial version of the IR has been constructed. This method allows the
* {@link #irObserver} to modify the initial version of the IR.
*
* @param ast
* The initial version of the IR.
* @return A possibly modified version of the initial IR.
*/
public List<IRStatus<PIR>> initialIrEvent(List<IRStatus<PIR>> ast)
{
if (irObserver != null)
{
return irObserver.initialIRConstructed(ast, getInfo());
}
return ast;
}
/**
* Notifies the {@link #irObserver} when the final version of the IR has been constructed. This method allows the
* {@link #irObserver} to modify the IR.
*
* @param ast
* The final version of the IR.
* @return A possibly modified version of the IR
*/
public List<IRStatus<PIR>> finalIrEvent(List<IRStatus<PIR>> ast)
{
if (irObserver != null)
{
return irObserver.finalIRConstructed(ast, getInfo());
}
return ast;
}
public void setIRGenerator(IRGenerator generator)
{
this.generator = generator;
}
public IRGenerator getIRGenerator()
{
return generator;
}
public void setSettings(IRSettings settings)
{
generator.getIRInfo().setSettings(settings);
}
public IRSettings getSettings()
{
return generator.getIRInfo().getSettings();
}
public IRInfo getInfo()
{
return generator.getIRInfo();
}
public void setTransAssistant(TransAssistantIR transAssistant)
{
this.transAssistant = transAssistant;
}
public TransAssistantIR getTransAssistant()
{
return transAssistant;
}
}