ProgramCompiler.java
package com.mackenziehigh.autumn.lang.compiler.compilers;
import autumn.lang.compiler.ClassFile;
import autumn.lang.compiler.CompiledProgram;
import autumn.lang.compiler.ast.nodes.Module;
import autumn.lang.compiler.errors.IErrorReporter;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.lang.compiler.utils.Utils;
import java.util.List;
/**
* An instance of this class controls the compilation of an entire program.
*
* @author Mackenzie High
*/
public final class ProgramCompiler
implements ICompiler
{
public final TypeSystem typesystem;
public final SymbolTable symbols = new SymbolTable();
public final StaticChecker checker;
public final IErrorReporter reporter;
public final List<Class> imported;
private final List<ModuleCompiler> modules = Lists.newLinkedList();
/**
* Sole Constructor.
*
* @param reporter is used to report errors.
* @param loader is the class-loader used to find previously loaded types.
* @param imported are types to automatically import into every module.
* @param mules are the modules that will be compiled.
*/
private ProgramCompiler(final IErrorReporter reporter,
final ClassLoader loader,
final List<Class> imported,
final List<Module> mules)
{
Preconditions.checkNotNull(reporter);
Preconditions.checkNotNull(mules);
this.reporter = reporter;
this.typesystem = new TypeSystem(loader);
this.checker = new StaticChecker(this);
this.imported = ImmutableList.copyOf(imported);
/**
* Create a ModuleCompiler for each module AST node.
*/
for (Module m : mules)
{
if (ModuleCompiler.isEmpty(m) == false)
{
this.modules.add(new ModuleCompiler(this, m));
}
}
}
/**
* This method generates the the compiled-program that can be written to a JAR file.
*
* @return a jar-able representation of the program.
*/
private CompiledProgram build()
{
/**
* The generated class-files will be added to this list.
*/
final List<ClassFile> classes = Lists.newLinkedList();
/**
* Add all the class-files, which represent modules, to the list.
*/
for (ModuleCompiler m : modules)
{
classes.addAll(m.build());
}
/**
* Add all the class-files, which represent lambdas, to the list.
*/
for (LambdaCompiler x : symbols.lambdas.values())
{
classes.add(x.build());
}
/**
* Find all of the entry-point functions, if any.
*/
final List<FunctionCompiler> starts = this.findStart();
/**
* There should only be one entry-point function.
*/
if (starts.size() > 1)
{
checker.reportTooManyStarts(starts.get(0).node);
}
/**
* Compute the name of the module that contains the entry-point function.
*/
final String main_class = starts.size() == 1
? Utils.sourceName(starts.get(0).type.getOwner())
: null;
/**
* Create an object representation of the compiled program.
*/
final CompiledProgram result = new CompiledProgram(main_class, classes);
return result;
}
/**
* This method finds all the functions being compiled that are marked as the entry-point.
*
* <p>
* If the returned list should not have more than one element,
* because only one function can be the program's entry-point.
* </p>
*
* @return the functions that may be the program's entry-point.
*/
private List<FunctionCompiler> findStart()
{
final List<FunctionCompiler> result = Lists.newLinkedList();
for (ModuleCompiler module : modules)
{
for (FunctionCompiler function : module.functions)
{
if (function.isAnnotationPresent(typesystem.utils.START))
{
result.add(function);
}
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void performTypeDeclaration()
{
for (ModuleCompiler m : modules)
{
m.performTypeDeclaration();
}
}
/**
* {@inheritDoc}
*/
@Override
public void performTypeInitialization()
{
for (ModuleCompiler m : modules)
{
m.performTypeInitialization();
}
for (ExceptionCompiler ecmp : symbols.exceptions.values())
{
ecmp.inferConstructors();
}
}
/**
* {@inheritDoc}
*/
@Override
public void performTypeStructureChecking()
{
/**
* A compilation-unit can only contain a single start-function.
*/
final List<FunctionCompiler> starts = this.findStart();
if (starts.size() > 1)
{
checker.reportTooManyStarts(starts.get(0).node);
}
/**
* Perform type-structure-checking on all the modules, etc.
*/
for (ModuleCompiler m : modules)
{
m.performTypeStructureChecking();
}
}
/**
* {@inheritDoc}
*/
@Override
public void performTypeUsageChecking()
{
for (ModuleCompiler m : modules)
{
m.performTypeUsageChecking();
}
}
/**
* This method compiles a program to its bytecode representation.
*
* @param input are the modules in the program that will be compiled.
* @param reporter is used to report errors.
* @param loader is the class-loader used to find previously loaded types.
* @param imported are types to automatically import into every module.
* @return the bytecode representation of the compiled program; or null, if compilation fails.
*/
public static CompiledProgram compile(final List<Module> input,
final IErrorReporter reporter,
final ClassLoader loader,
final List<Class> imported)
{
try
{
final ProgramCompiler compiler = new ProgramCompiler(reporter, loader, imported, input);
compiler.performTypeDeclaration();
if (reporter.errorCount() > 0)
{
return null;
}
compiler.performTypeInitialization();
if (reporter.errorCount() > 0)
{
return null;
}
compiler.performTypeStructureChecking();
if (reporter.errorCount() > 0)
{
return null;
}
compiler.performTypeUsageChecking();
if (reporter.errorCount() > 0)
{
return null;
}
final CompiledProgram program = compiler.build();
return program;
}
catch (TypeCheckFailed ex)
{
// Pass, because the error will be reported via the error-reporter object.
}
catch (Exception ex)
{
// TODO: should a generalized exception handler go here, in case of compiler bugs?
throw new RuntimeException(ex);
}
return null;
}
}