Autumn.java
package autumn.lang.compiler;
import autumn.lang.compiler.ast.nodes.Module;
import autumn.lang.compiler.ast.nodes.ModuleDirective;
import autumn.lang.compiler.ast.nodes.Name;
import autumn.lang.compiler.errors.BasicErrorReporter;
import autumn.lang.compiler.errors.IErrorReporter;
import autumn.util.F;
import autumn.util.FileIO;
import autumn.util.test.TestResults;
import autumn.util.test.UnitTester;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
/**
* An instance of this class simplifies the use of Autumn.
*
* <p>
* <b>Warning: This class is still under development therefore, changes may be made in the near future. (TODO: remove)</b>
* </p>
*
* @author Mackenzie High
*/
public final class Autumn
{
private URLClassLoader loader;
/**
* These are the paths that where passed to the class-loader's constructor.
*/
private final List<URL> libraries = Lists.newLinkedList();
/**
* This object is used to issue error-message,
* such as syntax errors and type-checking errors.
*/
private IErrorReporter reporter = new BasicErrorReporter();
/**
* These are the modules that will be compiled.
*/
private final List<Module> modules = Lists.newLinkedList();
/**
* These are additional classes to automatically import inside each module.
* This is a list, because order is important in Autumn imports.
*/
private final List<Class> imported = Lists.newLinkedList();
/**
* This flag is true, if assume-statements are turned on.
*/
private static boolean assume = true;
/**
* Sole Constructor.
*/
public Autumn()
{
loader = new URLClassLoader(new URL[0]);
}
/**
* This method turns the assume-statements on.
*/
public static void enableAssume()
{
assume = true;
}
/**
* This method turns the assume-statements off.
*/
public static void disableAssume()
{
assume = false;
}
/**
* This method determines whether assumptions are turned on.
*
* <p>
* By default, assume-statements and assert-statements are both on.
* Only assume-statements (aka assumptions) can be turned off.
* </p>
*
* @return true, iff assumptions are turned on.
*/
public static boolean isAssumeOn()
{
return assume;
}
/**
* This method sets the error-reporter that is used to report compilation-errors.
*
* <p>
* The error-reporter reports parsing-errors, type-checking errors, etc.
* </p>
*
* @param reporter is the new error-reporter.
* @throws NullPointerException if reporter is null.
*/
public void setErrorReporter(final IErrorReporter reporter)
{
Preconditions.checkNotNull(reporter);
this.reporter = reporter;
}
/**
* This method specifies that a particular class should be imported in every module.
*
* @param klass is the type to import in every module automatically.
* @throws NullPointerException if klass is null.
*/
public void addImport(final Class klass)
{
Preconditions.checkNotNull(klass);
imported.add(klass);
}
/**
* This method causes source-files to be read, parsed, and added to the list of modules.
*
* <p>
* This method skips hidden files and files with extensions
* other than ".leaf" (ignoring case).
* </p>
*
* @param root is the path to the directory containing the source-files.
* @param recur is true, if this method should recurse into sub-directories.
* @return the modules that were successfully parsed.
* @throws IOException if a source-file cannot be read.
*/
public List<Module> srcDir(final File root,
final boolean recur)
throws IOException
{
Preconditions.checkNotNull(root);
final List<Module> result = Lists.newLinkedList();
for (Object path : F.iter(FileIO.filesOf(root, recur)))
{
final File file = (File) path;
final String name = file.getName();
final boolean is_file = file.isFile();
final boolean is_hidden = file.isHidden();
final boolean is_leaf = name.length() > 5
&& name.substring(name.length() - 5, name.length()).equalsIgnoreCase(".leaf");
if (is_file && is_leaf && !is_hidden)
{
srcFile(file);
}
}
return Collections.unmodifiableList(result);
}
/**
* This method causes source-files to be read, parsed, and added to the list of modules.
*
* <p>
* Equivalence:
* <code> srcFile(new File(file)) </code>
* </p>
*
* @param root is the path to the directory containing the source-files.
* @param recur is true, if this method should recurse into sub-directories.
* @return the modules that were successfully parsed.
* @throws IOException if the source-file cannot be read.
*/
public List<Module> srcDir(final String root,
final boolean recur)
throws IOException
{
return srcDir(new File(root), recur);
}
/**
* This method causes a source-file to be read, parsed, and added to the list of modules.
*
* @param file is the path to the source-file.
* @throws IOException if the source-file cannot be read.
*/
public Module srcFile(final File file)
throws IOException
{
Preconditions.checkNotNull(file);
final String code = Files.toString(file, Charset.defaultCharset());
final AutumnParser parser = new AutumnParser(reporter);
final Module module = parser.parse(code, file);
return src(module);
}
/**
* This method causes a source-file to be read, parsed, and added to the list of modules.
*
* <p>
* Equivalence:
* <code> srcFile(new File(file)) </code>
* </p>
*
* @param file is the path to the source-file.
* @throws IOException if the source-file cannot be read.
*/
public Module srcFile(final String file)
throws IOException
{
return srcFile(new File(file));
}
/**
* This method causes a source-file to be read, parsed, and added to the list of modules.
*
* @param file is the path to the source-file.
* @throws IOException if the source-file cannot be read.
*/
public Module srcURL(final URL file)
throws IOException
{
Preconditions.checkNotNull(file);
final String code = Resources.toString(file, Charset.defaultCharset());
final AutumnParser parser = new AutumnParser(reporter);
final Module module = parser.parse(code, file);
return src(module);
}
/**
* This method causes a source-file to be read, parsed, and added to the list of modules.
*
* <p>
* Equivalence:
* <code> srcURL(new URL(file)) </code>
* </p>
*
* @param file is the path to the source-file.
* @return the Abstract-Syntax-Tree representation of the module.
* @throws IOException if the source-file cannot be read.
*/
public Module srcURL(final String file)
throws MalformedURLException,
IOException
{
return srcURL(new URL(file));
}
/**
* This method adds the Abstract-Syntax-Tree representation of a module to the list of modules.
*
* @param node is the AST representation of the module.
* @return node.
*/
public Module src(final Module node)
{
if (reporter.errorCount() > 0)
{
return null;
}
Preconditions.checkNotNull(node);
modules.add(node);
return node;
}
/**
* This method adds a module to the list of modules.
*
* <p>
* This method will parse the code.
* </p>
*
* @param code is the source-code representation of the module.
* @return node.
*/
public Module src(final String code)
{
if (reporter.errorCount() > 0)
{
return null;
}
Preconditions.checkNotNull(code);
final File fake = new File("<script>");
final AutumnParser parser = new AutumnParser(reporter);
final Module node = parser.parse(code, fake);
if (reporter.errorCount() > 0)
{
return null;
}
return src(node);
}
/**
* This method compiles the list of modules to bytecode.
*
* @return the bytecode representation the program.
*/
public CompiledProgram compile()
{
if (reporter.errorCount() > 0)
{
return null;
}
final AutumnCompiler cmp = new AutumnCompiler(reporter, loader);
for (Class type : imported)
{
cmp.addImport(type);
}
final CompiledProgram compiled = cmp.compile(modules);
/**
* If compilation failed, then the compiler returned null.
*/
if (compiled == null)
{
return null;
}
/**
* In case dynamic loading will be performed, include the loaded libraries.
*/
final CompiledProgram program = new CompiledProgram(compiled, libraries);
return program;
}
/**
* This method compiles the list of modules to bytecode.
*
* @param out is the path to write the jar-file to.
* @return the bytecode representation the program.
*/
public CompiledProgram compile(final File out)
throws IOException
{
if (reporter.errorCount() > 0)
{
return null;
}
Preconditions.checkNotNull(out);
final CompiledProgram program = compile();
program.jar(out);
return program;
}
/**
* This method compiles the list of modules to bytecode.
*
* <p>
* Equivalence:
* <code> compile(new File(file)) </code>
* </p>
*
* @param out is the path to write the jar-file to.
* @return the bytecode representation the program.
*/
public CompiledProgram compile(final String out)
throws IOException
{
return compile(new File(out));
}
/**
* This method compiles the program, dynamically loads it, and then runs it.
*
* @param args are the command-line arguments to pass to the main(String[]) method.
*/
public void run(final String[] args)
throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException,
IllegalAccessException
{
Preconditions.checkNotNull(args);
final CompiledProgram program = compile();
if (reporter.errorCount() > 0)
{
return;
}
final DynamicLoader dyn_loader = program.load(loader);
dyn_loader.invokeMain(args);
}
/**
* This method compiles the program, dynamically loads it, and then runs it.
*
* @param args are the command-line arguments to pass to the main(String[]) method.
*/
public void run(final Iterable<String> args)
throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException,
IllegalAccessException
{
Preconditions.checkNotNull(args);
run(Lists.newArrayList(args).toArray(new String[0]));
}
/**
* This method compiles the program, dynamically loads it,
* and then executes the unit-tests contained therein.
*
* <p>
* For more information on Autumn unit-tests, see the (autumn.util.test) package.
* </p>
*
* @return the results of running the unit-tests.
*/
public TestResults test()
{
if (reporter.errorCount() > 0)
{
return null;
}
final UnitTester tester = new UnitTester();
final CompiledProgram program = compile();
final DynamicLoader dyn_loader = program.load(loader);
for (Module module : modules)
{
final String name = nameOf(module);
if (name == null)
{
continue;
}
try
{
final Class clazz = Class.forName(name, false, dyn_loader);
tester.add(clazz);
}
catch (ClassNotFoundException ex)
{
/**
* Technically, this should never happen.
* Compilation was successfully and the modules were successfully loaded.
* As a result, the module's class must exist in the loader.
*/
throw new RuntimeException(ex);
}
}
final TestResults results = tester.run();
return results;
}
/**
* This method computes the fully-qualified name of a module.
*
* @param module is the Abstract-Syntax-Tree representation of the module.
* @return the name of the module; or null, if the module is anonymous.
*/
private String nameOf(final Module module)
{
// The name of the module is specified by its only module-directive.
final ModuleDirective directive = module.getModuleDirectives().asMutableList().get(0);
// The name will consist of a package-part and s simple-name.
final StringBuilder name = new StringBuilder();
// For each part of the package's name:
for (Name part : directive.getNamespace().getNames())
{
name.append(part.getName());
name.append('.');
}
// Append the simple-name of the module.
// This will be a '*' character, if the module is anonymous.
name.append(directive.getName().getName());
// Return null, if the module is anonymous.
return name.toString().contains("*") ? null : name.toString();
}
/**
* This method loads a library jar-file or class-file.
*
* @param path is the path to where the jar-file is located.
*/
public void loadURL(final URL path)
{
Preconditions.checkNotNull(path);
libraries.add(path);
final URL[] array = libraries.toArray(new URL[0]);
loader = new URLClassLoader(array);
}
/**
* This method loads a library jar-file or class-file.
*
* @param path is the path to where the jar-file is located.
*/
public void loadURL(final String path)
throws MalformedURLException
{
Preconditions.checkNotNull(path);
loadURL(new URL(path));
}
/**
* This method loads a library jar-file or class-file.
*
* @param path is the path to where the jar-file is located.
*/
public void loadFile(final File path)
throws MalformedURLException
{
Preconditions.checkNotNull(path);
loadURL(path.toURI().toURL());
}
/**
* This method loads a library jar-file or class-file.
*
* @param path is the path to where the jar-file is located.
*/
public void loadFile(final String path)
throws MalformedURLException
{
Preconditions.checkNotNull(path);
loadFile(path);
}
}