
lt.repl.Compiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of latte-compiler Show documentation
Show all versions of latte-compiler Show documentation
The latte-lang compiler project, which contains compiler and runtime required library.
The newest version!
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 KuiGang Wang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package lt.repl;
import lt.compiler.*;
import lt.compiler.Properties;
import lt.compiler.Scanner;
import lt.compiler.lexical.ElementStartNode;
import lt.compiler.semantic.STypeDef;
import lt.compiler.syntactic.Statement;
import lt.runtime.Wrapper;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* a compiler with full functions provided. lt.repl.Compiler is a small set of this compiler
* first record all necessary info
* then creates a ThreadPool to run Scanner and Parser
* then summaries these AST to do semantic analysis in a single thread
* finally creates a ThreadPool to run Code Generation and write files to disk (or store these byte code for loading)
* if requires loading, then load all these generated classes
*/
@SuppressWarnings("unused")
public class Compiler {
private static final int availableProcessors = Runtime.getRuntime().availableProcessors();
public static class Config {
/**
* class-path
*/
public List classpath = new ArrayList();
/**
* configuration about each step use how many threads
*/
public static class Threads {
/**
* thread count for scanners
*/
public int scanner = availableProcessors;
/**
* thread count for parsers
*/
public int parser = availableProcessors;
/**
* thread count for codeGen
*/
public int codeGen = availableProcessors;
}
/**
* thread configuration
*/
public Threads threads = new Threads();
/**
* configuration about coding
*/
public static class Code {
/**
* these imports will be added when doing semantic analysis
*/
public List autoImport = new ArrayList();
/**
* indentation of the source code
*/
public int indentation = 4;
/**
* line base
*/
public int lineBase = 0;
/**
* column base
*/
public int columnBase = 0;
}
/**
* code configuration
*/
public Code code = new Code();
/**
* out configuration
*/
public ErrorManager.Out out = new ErrorManager.Out();
/**
* throw exception immediately when meets a syntax exception
*/
public boolean fastFail = true;
/**
* configuration about the result
*/
public static class Result {
/**
* output directory
*/
public File outputDir = null;
/**
* the jar file name
*/
public String pkg = null;
/**
* main class
*/
public String main = null;
/**
* include Latte runtime libraries
*/
public boolean with_lib = false;
}
/**
* result configuration
*/
public Result result = new Result();
}
public final Config config = new Config();
private final ClassLoader baseLoader;
/**
* construct the compiler
*/
public Compiler() {
baseLoader = Thread.currentThread().getContextClassLoader();
}
/**
* construct the compiler with base class loader
*
* @param baseLoader the base class loader
*/
public Compiler(ClassLoader baseLoader) {
this.baseLoader = baseLoader;
}
/**
* add a jarFile to class-path
*
* @param dirOrJar directory or jar file location
* @return the compiler itself
* @throws IOException exceptions
*/
public Compiler add(String dirOrJar) throws IOException {
return add(new File(dirOrJar));
}
/**
* add a jarFile to class-path
*
* @param file the file representing url
* @return the compiler itself
* @throws IOException exceptions
*/
public Compiler add(File file) throws IOException {
return add(new URL(file.toURI().toString()));
}
/**
* add a jarFile to class-path
*
* @param url the url
* @return the compiler itself
*/
public Compiler add(URL url) {
config.classpath.add(url);
return this;
}
/**
* configure using given config
*
* @param config a map containing configuration
* @return this
* @throws Exception all kinds of exceptions
*/
public Compiler configure(Map config) throws Exception {
if (config != null) {
List classpathToSet = new ArrayList();
Config.Threads threads = new Config.Threads();
Config.Code code = new Config.Code();
ErrorManager.Out out = new ErrorManager.Out();
boolean fastFail = true;
Config.Result result = new Config.Result();
if (config.containsKey("classpath")) {
final String exMsg = "config.classpath should be a list of JarFile/File/String";
Object o = config.get("classpath");
if (o instanceof List) {
for (Object e : (List) o) {
if (e instanceof String) {
addIntoClasspathList(classpathToSet, (String) e);
} else if (e instanceof File) {
addIntoClasspathList(classpathToSet, (File) e);
} else if (e instanceof URL) {
addIntoClasspathList(classpathToSet, (URL) e);
} else throw new IllegalArgumentException(exMsg);
}
} else throw new IllegalArgumentException(exMsg);
}
if (config.containsKey("threads")) {
Object o = config.get("threads");
if (o instanceof Map) {
Map t = (Map) o;
if (t.containsKey("scanner")) {
Object scanner = t.get("scanner");
if (scanner instanceof Integer && ((Integer) scanner) >= 1) {
threads.scanner = (Integer) scanner;
} else
throw new IllegalArgumentException("config.threads.scanner should be Integer and >=1");
}
if (t.containsKey("parser")) {
Object parser = t.get("parser");
if (parser instanceof Integer && ((Integer) parser) >= 1) {
threads.parser = (Integer) parser;
} else
throw new IllegalArgumentException("config.threads.parser should be Integer and >= 1");
}
if (t.containsKey("codeGen")) {
Object codeGen = t.get("codeGen");
if (codeGen instanceof Integer && ((Integer) codeGen) >= 1) {
threads.codeGen = (Integer) codeGen;
} else
throw new IllegalArgumentException("config.threads.codeGen should be Integer and >=1");
}
} else
throw new IllegalArgumentException("config.threads should be {scanner:?, parser:?, codeGen:?}");
}
if (config.containsKey("code")) {
Object o = config.get("code");
if (o instanceof Map) {
Map c = (Map) o;
if (c.containsKey("autoImport")) {
Object ai = c.get("autoImport");
final String exMsg = "config.code.autoImport should be List of strings";
if (ai instanceof List) {
for (Object autoImport : (List) ai) {
if (autoImport instanceof String) {
code.autoImport.add((String) autoImport);
} else throw new IllegalArgumentException(exMsg);
}
} else throw new IllegalArgumentException(exMsg);
}
if (c.containsKey("indentation")) {
Object i = c.get("indentation");
if (i instanceof Integer && ((Integer) i) >= 1) {
code.indentation = (Integer) i;
} else
throw new IllegalArgumentException("config.code.indentation should be Integer and >=1");
}
if (c.containsKey("lineBase")) {
Object l = c.get("lineBase");
if (l instanceof Integer) {
code.lineBase = (Integer) l;
} else
throw new IllegalArgumentException("config.code.lineBase should be Integer");
}
if (c.containsKey("columnBase")) {
Object co = c.get("columnBase");
if (co instanceof Integer) {
code.columnBase = (Integer) co;
} else
throw new IllegalArgumentException("config.code.columnBase should be Integer");
}
} else
throw new IllegalArgumentException("config.code should be {autoImport:?, indentation:?, lineBase:?, columnBase:?}");
}
if (config.containsKey("out")) {
Object o = config.get("out");
if (o instanceof Map) {
Map ou = (Map) o;
if (ou.containsKey("debug")) {
Object debug = ou.get("debug");
if (debug == null || debug instanceof PrintStream) {
out.debug = (PrintStream) debug;
} else
throw new IllegalArgumentException("config.out.debug should be PrintStream");
}
if (ou.containsKey("info")) {
Object info = ou.get("info");
if (info == null || info instanceof PrintStream) {
out.info = (PrintStream) info;
} else
throw new IllegalArgumentException("config.out.info should be PrintStream");
}
if (ou.containsKey("warn")) {
Object warn = ou.get("warn");
if (warn == null || warn instanceof PrintStream) {
out.warn = (PrintStream) warn;
} else
throw new IllegalArgumentException("config.out.warn should be PrintStream");
}
if (ou.containsKey("err")) {
Object error = ou.get("err");
if (error == null || error instanceof PrintStream) {
out.err = (PrintStream) error;
} else
throw new IllegalArgumentException("config.out.err should be PrintStream");
}
} else
throw new IllegalArgumentException("config.out should be {debug:?, info:?, warn:?, err:?}");
}
if (config.containsKey("fastFail")) {
Object f = config.get("fastFail");
if (f instanceof Boolean) {
fastFail = (Boolean) f;
} else throw new IllegalArgumentException("config.fastFail should be Boolean");
}
if (config.containsKey("result")) {
Object r = config.get("result");
if (r instanceof Map) {
Map re = (Map) r;
if (re.containsKey("outputDir")) {
Object o = re.get("outputDir");
if (o instanceof String) {
File f = new File((String) o);
if (!f.exists()) //noinspection ResultOfMethodCallIgnored
f.mkdirs();
if (f.isDirectory()) {
result.outputDir = f;
} else
throw new IllegalArgumentException("config.result.outputDir should be a directory");
} else if (o instanceof File) {
if (((File) o).isDirectory()) {
result.outputDir = (File) o;
} else
throw new IllegalArgumentException("config.result.outputDir should be a directory");
} else
throw new IllegalArgumentException("config.result.outputDir should be File/String");
}
if (re.containsKey("package")) {
Object o = re.get("package");
if (o != null) {
if (o instanceof String) {
result.pkg = (String) o;
} else
throw new IllegalArgumentException("config.result.package should be String");
}
}
if (re.containsKey("main")) {
Object o = re.get("main");
if (o != null) {
if (o instanceof String) {
result.main = (String) o;
} else
throw new IllegalArgumentException("config.result.main should be String");
}
}
if (re.containsKey("with-lib")) {
Object o = re.get("with-lib");
if (o instanceof Boolean) {
result.with_lib = (Boolean) o;
} else
throw new IllegalArgumentException("config.result.with-lib should be bool");
}
} else
throw new IllegalArgumentException("config.result should be {outputDir:?, statistic:?}");
}
this.config.classpath = classpathToSet;
this.config.threads = threads;
this.config.code = code;
this.config.out = out;
this.config.fastFail = fastFail;
this.config.result = result;
}
return this;
}
private void addIntoClasspathList(List list, String fileName) throws IOException {
File f = new File(fileName);
if (f.exists()) {
addIntoClasspathList(list, f);
} else throw new IllegalArgumentException(fileName + " does not exist");
}
private void addIntoClasspathList(List list, File file) throws IOException {
if (file.isDirectory()
|| (file.isFile() && file.getName().endsWith(".jar"))) {
addIntoClasspathList(list, new URL(file.toURI().toString()));
} else throw new IllegalArgumentException("requiring a directory or jarFile, got " + file);
}
private void addIntoClasspathList(List list, URL url) {
list.add(url);
}
/**
* specify the output directory
*
* @param fileDir file directory
* @return the compiler itself
*/
public Compiler shiftRight(String fileDir) {
return shiftRight(new File(fileDir));
}
/**
* specify the output directory
*
* @param file the directory
* @return the compiler itself
*/
public Compiler shiftRight(File file) {
if (!file.isDirectory()) throw new IllegalArgumentException(file + " is not a directory");
config.result.outputDir = file;
return this;
}
/**
* compile the given files
*
* @param fileNameToCode a map containing fileName to source_code/File/InputStream/Reader
* @return the retrieved class loader
* @throws Exception the exception occurred when compiling,
* or lt.runtime.Wrapper whose object field is a List of {@link lt.compiler.ErrorManager.CompilingError}
*/
public ClassLoader compile(Map fileNameToCode) throws Exception {
// validate and transform compile input
Map input = new HashMap();
for (Map.Entry entry : fileNameToCode.entrySet()) {
String name = entry.getKey();
Object v = entry.getValue();
if (v instanceof String) {
input.put(name, new StringReader((String) v));
} else if (v instanceof File) {
if (((File) v).isFile()) {
input.put(name, new FileReader((File) v));
} else throw new IllegalArgumentException(v + " is not a file");
} else if (v instanceof InputStream) {
input.put(name, new InputStreamReader((InputStream) v));
} else if (v instanceof Reader) {
input.put(name, (Reader) v);
} else
throw new IllegalArgumentException("the mapped values should be String/File/InputStream/Reader");
}
// validate configuration
if (config.threads.codeGen < 1) throw new IllegalArgumentException("config.threads.codeGen should >=1");
if (config.threads.parser < 1) throw new IllegalArgumentException("config.threads.parser should >=1");
if (config.threads.scanner < 1) throw new IllegalArgumentException("config.threads.scanner should >=1");
if (config.code.indentation < 1)
throw new IllegalArgumentException("config.code.indentation should >=1");
if (config.result.outputDir != null && config.result.outputDir.exists() && !config.result.outputDir.isDirectory())
throw new IllegalArgumentException("config.result.outputDir should be a directory");
// load jars
ClassPathLoader classPathLoader = new ClassPathLoader(baseLoader);
for (URL url : config.classpath) {
classPathLoader.load(url);
}
// construct thread pool for scanners and parsers
ExecutorService scannerPool = Executors.newFixedThreadPool(config.threads.scanner);
final ExecutorService parserPool = Executors.newFixedThreadPool(config.threads.parser);
final ErrorManager errorManager = new ErrorManager(config.fastFail);
errorManager.out = config.out;
List scans = new ArrayList();
Properties properties = new Properties();
properties._COLUMN_BASE_ = config.code.columnBase;
properties._LINE_BASE_ = config.code.lineBase;
for (Map.Entry entry : input.entrySet()) {
Scan scan = new Scan(entry.getKey(), entry.getValue(), properties, errorManager);
scans.add(scan);
}
List> scanRes = scannerPool.invokeAll(scans);
Map> parseRes = new ConcurrentHashMap>();
final List>>> parseState = new Vector>>>();
final Exception[] caughtException = new Exception[]{null};
for (final Future f : scanRes) {
new Thread(new Runnable() {
@Override
public void run() {
try {
FileRoot root;
try {
root = f.get();
} catch (ExecutionException e) {
caughtException[0] = (Exception) e.getCause();
return;
}
Future
© 2015 - 2025 Weber Informatics LLC | Privacy Policy