package me.tomassetti.turin.compiler;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.google.common.collect.ImmutableList;
import me.tomassetti.turin.classloading.ClassFileDefinition;
import me.tomassetti.turin.compiler.errorhandling.ErrorCollector;
import me.tomassetti.turin.parser.TurinFileWithSource;
import me.tomassetti.turin.resolvers.*;
import me.tomassetti.turin.resolvers.compiled.JarTypeResolver;
import me.tomassetti.turin.resolvers.jdk.JdkTypeResolver;
import me.tomassetti.turin.parser.ast.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import me.tomassetti.turin.parser.Parser;
public class Compiler {
private static String VERSION = "0.1 (Crocetta)";
private SymbolResolver resolver;
private Options options;
public Compiler(SymbolResolver resolver, Options options) {
this.resolver = resolver;
this.options = options;
}
public List<ClassFileDefinition> compile(TurinFile turinFile, ErrorCollector errorCollector) {
ResolverRegistry.INSTANCE.record(turinFile, resolver);
return new Compilation(resolver, errorCollector).compile(turinFile);
}
public static class Options {
public String getDestinationDir() {
return destinationDir;
}
public void setDestinationDir(String destinationDir) {
this.destinationDir = destinationDir;
}
public List<String> getClassPathElements() {
return classPathElements;
}
public void setClassPathElements(List<String> classPathElements) {
this.classPathElements = classPathElements;
}
public boolean isVerbose() {
return verbose;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public boolean isHelp() {
return help;
}
public void setHelp(boolean help) {
this.help = help;
}
public List<String> getSources() {
return sources;
}
public void setSources(List<String> sources) {
this.sources = sources;
}
@Parameter(names = {"-o", "--output"})
private String destinationDir = "turin_classes";
@Parameter(names = {"-cp", "--classpath"}, variableArity = true)
private List<String> classPathElements = new ArrayList<>();
@Parameter(names = {"-v", "--verbose"})
private boolean verbose = false;
@Parameter(names = {"-d", "--debug"})
private boolean debug = false;
@Parameter(names = {"-h", "--help"})
private boolean help = false;
@Parameter(description = "Files or directories to compile")
private List<String> sources = new ArrayList<>();
}
private static SymbolResolver getResolver(List<String> sources, List<String> classPathElements, List<TurinFile> turinFiles) {
TypeResolver typeResolver = new ComposedTypeResolver(ImmutableList.<TypeResolver>builder()
.add(JdkTypeResolver.getInstance())
.addAll(classPathElements.stream().map((cp) -> toTypeResolver(cp)).collect(Collectors.toList()))
.build());
return new ComposedSymbolResolver(ImmutableList.of(new InFileSymbolResolver(typeResolver), new SrcSymbolResolver(turinFiles)));
}
private static TypeResolver toTypeResolver(String classPathElement) {
File file = new File(classPathElement);
if (file.exists() && file.isFile() && classPathElement.endsWith(".jar")) {
try {
return new JarTypeResolver(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalArgumentException(classPathElement);
}
}
private void compile(File file) throws IOException {
if (file.isDirectory()) {
compileDir(file);
} else {
compileFile(file);
}
}
private static class ErrorPrinter implements ErrorCollector {
private String fileDescription;
public ErrorPrinter(String fileDescription) {
this.fileDescription = fileDescription;
}
@Override
public void recordSemanticError(Position position, String description) {
System.err.println(fileDescription + " at " + position + ": (semantic error) " + description);
}
}
private void compileFile(File file) throws IOException {
TurinFile turinFile = new Parser().parse(new FileInputStream(file));
for (ClassFileDefinition classFileDefinition : compile(turinFile, new ErrorPrinter(file.getPath()))) {
if (options.verbose) {
System.out.println(" Writing [" + classFileDefinition.getName() + "]");
}
File classFile = new File(options.destinationDir +"/" + classFileDefinition.getName().replaceAll("\\.", "/") + ".class");
classFile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(classFile);
fos.write(classFileDefinition.getBytecode());
fos.close();
}
}
private void compileDir(File file) throws IOException {
for (File child : file.listFiles()) {
compile(child);
}
}
public static void main(String[] args) throws IOException {
System.out.println("--------------------------------------------------");
System.out.println(" Turin Compiler - version " + VERSION);
System.out.println("--------------------------------------------------\n");
Options options = new Options();
JCommander commander = null;
try {
commander = new JCommander(options, args);
} catch (Throwable t) {
System.err.println("Problem parsing options: " + t.getMessage());
System.exit(1);
return;
}
if (options.help) {
System.out.println("Help demanded - printing usage");
commander.usage();
return;
}
if (options.sources.isEmpty()) {
System.err.println("No sources specified");
commander.usage();
return;
}
Parser parser = new Parser();
// First we collect all TurinFiles and we pass it to the resolver
List<TurinFileWithSource> turinFiles = new ArrayList<>();
for (String source : options.sources) {
try {
turinFiles.addAll(parser.parseAllIn(new File(source)));
} catch (FileNotFoundException e){
System.err.println("Error: " + e.getMessage());
System.exit(1);
return;
}
}
SymbolResolver resolver = getResolver(options.sources, options.classPathElements, turinFiles.stream().map(TurinFileWithSource::getTurinFile).collect(Collectors.toList()));
// Then we compile all files
Compiler instance = new Compiler(resolver, options);
for (TurinFileWithSource turinFile : turinFiles) {
for (ClassFileDefinition classFileDefinition : instance.compile(turinFile.getTurinFile(), new ErrorPrinter(turinFile.getSource().getPath()))) {
saveClassFile(classFileDefinition, options);
}
}
}
private static void saveClassFile(ClassFileDefinition classFileDefinition, Options options) {
File output = null;
try {
output = new File(new File(options.destinationDir).getAbsolutePath() + "/" + classFileDefinition.getName().replaceAll("\\.", "/") + ".class");
if (options.verbose) {
System.out.println(" [saving "+output.getPath()+"]");
}
output.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(output);
fos.write(classFileDefinition.getBytecode());
} catch (IOException e) {
System.err.println("Problem writing file "+output+": "+ e.getMessage());
System.exit(3);
}
}
}