/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.compiler;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import com.github.anba.es6draft.ast.AsyncFunctionDefinition;
import com.github.anba.es6draft.ast.AsyncGeneratorDefinition;
import com.github.anba.es6draft.ast.FunctionDefinition;
import com.github.anba.es6draft.ast.FunctionNode;
import com.github.anba.es6draft.ast.GeneratorDefinition;
import com.github.anba.es6draft.ast.Module;
import com.github.anba.es6draft.ast.Script;
import com.github.anba.es6draft.ast.scope.Scope;
import com.github.anba.es6draft.ast.scope.ScriptScope;
import com.github.anba.es6draft.compiler.analyzer.CodeSize;
import com.github.anba.es6draft.compiler.analyzer.CodeSizeException;
import com.github.anba.es6draft.compiler.assembler.ClassSignature;
import com.github.anba.es6draft.compiler.assembler.Code;
import com.github.anba.es6draft.compiler.assembler.Code.ClassCode;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.completion.CompletionValueVisitor;
import com.github.anba.es6draft.runtime.internal.RuntimeContext;
import com.github.anba.es6draft.runtime.modules.SourceTextModuleRecord;
/**
*
*/
public final class Compiler {
public enum Option {
DebugInfo, PrintCode, PrintFullCode, IterationCatchStackOverflow, NoCompletion, NoByteCodeSizeValidation,
NoTailCall, SourceMap
}
private final ExecutorService executor;
private final EnumSet<Option> compilerOptions;
public Compiler(RuntimeContext context, ExecutorService executor) {
this.executor = executor;
this.compilerOptions = context.getCompilerOptions();
}
/**
* Compiles a script node to a Java bytecode.
*
* @param script
* the script node
* @param className
* the class name
* @return the compiled script
* @throws CompilationException
* if the script node could not be compiled
*/
public CompiledScript compile(Script script, String className) throws CompilationException {
if (!isEnabled(Compiler.Option.NoByteCodeSizeValidation)) {
try {
CodeSize.analyze(script);
} catch (CodeSizeException e) {
throw new CompilationException(e.getMessage());
}
}
if (!isEnabled(Compiler.Option.NoCompletion)) {
CompletionValueVisitor.performCompletion(script);
}
Code code = new Code(Modifier.PUBLIC | Modifier.FINAL, className, ClassSignature.NONE,
Types.CompiledScript, Collections.<Type> emptyList(), NodeSourceInfo.create(script,
compilerOptions));
CodeGenerator codegen = new CodeGenerator(code, script, executor, compilerOptions);
codegen.compile(script);
return defineAndLoad(code, className);
}
/**
* Compiles a module node to a Java bytecode.
*
* @param module
* the module node
* @param moduleRecord
* the module record
* @param className
* the class name
* @return the compiled module
* @throws CompilationException
* if the module node could not be compiled
*/
public CompiledModule compile(Module module, SourceTextModuleRecord moduleRecord,
String className) throws CompilationException {
if (!isEnabled(Compiler.Option.NoByteCodeSizeValidation)) {
try {
CodeSize.analyze(module);
} catch (CodeSizeException e) {
throw new CompilationException(e.getMessage());
}
}
Code code = new Code(Modifier.PUBLIC | Modifier.FINAL, className, ClassSignature.NONE,
Types.CompiledModule, Collections.<Type> emptyList(), NodeSourceInfo.create(module,
compilerOptions));
CodeGenerator codegen = new CodeGenerator(code, module, executor, compilerOptions);
codegen.compile(module, moduleRecord);
return defineAndLoad(code, className);
}
/**
* Compiles a function node to a Java bytecode.
*
* @param function
* the function node
* @param className
* the class name
* @return the compiled function
* @throws CompilationException
* if the function node could not be compiled
*/
public CompiledFunction compile(FunctionDefinition function, String className)
throws CompilationException {
return compile((FunctionNode) function, className);
}
/**
* Compiles a generator function node to a Java bytecode.
*
* @param generator
* the generator function node
* @param className
* the class name
* @return the compiled generator function
* @throws CompilationException
* if the generator function node could not be compiled
*/
public CompiledFunction compile(GeneratorDefinition generator, String className)
throws CompilationException {
return compile((FunctionNode) generator, className);
}
/**
* Compiles a async function node to a Java bytecode.
*
* @param asyncFunction
* the async function node
* @param className
* the class name
* @return the compiled async function
* @throws CompilationException
* if the async function node could not be compiled
*/
public CompiledFunction compile(AsyncFunctionDefinition asyncFunction, String className)
throws CompilationException {
return compile((FunctionNode) asyncFunction, className);
}
/**
* Compiles a async generator node to a Java bytecode.
*
* @param asyncGenerator
* the async generator node
* @param className
* the class name
* @return the compiled async generator
* @throws CompilationException
* if the async generator node could not be compiled
*/
public CompiledFunction compile(AsyncGeneratorDefinition asyncGenerator, String className)
throws CompilationException {
return compile((FunctionNode) asyncGenerator, className);
}
private CompiledFunction compile(FunctionNode function, String className) {
Script script = functionScript(function);
if (!isEnabled(Compiler.Option.NoByteCodeSizeValidation)) {
try {
CodeSize.analyze(function);
} catch (CodeSizeException e) {
throw new CompilationException(e.getMessage());
}
}
Code code = new Code(Modifier.PUBLIC | Modifier.FINAL, className, ClassSignature.NONE,
Types.CompiledFunction, Collections.<Type> emptyList(), NodeSourceInfo.create(
function, compilerOptions));
CodeGenerator codegen = new CodeGenerator(code, script, executor, compilerOptions);
codegen.compileFunction(function);
return defineAndLoad(code, className);
}
private static Script functionScript(FunctionNode function) {
Scope enclosingScope = function.getScope().getEnclosingScope();
assert enclosingScope instanceof ScriptScope;
return ((ScriptScope) enclosingScope).getNode();
}
private boolean isEnabled(Compiler.Option option) {
return compilerOptions.contains(option);
}
private <T> T defineAndLoad(Code code, String clazzName) {
boolean printCode = isEnabled(Option.PrintCode);
boolean printSimple = printCode && !isEnabled(Option.PrintFullCode);
boolean debugInfo = isEnabled(Option.DebugInfo);
CodeLoader loader = new CodeLoader();
for (ClassCode classCode : code.getClasses()) {
String className = Type.className(classCode.className);
if (debugInfo) {
classCode.addField(Modifier.PRIVATE | Modifier.STATIC, "classBytes",
Type.of(byte[].class), null);
}
byte[] bytes = classCode.toByteArray();
if (printCode) {
System.out.println(Code.toByteCode(bytes, printSimple));
}
// System.out.printf("define class '%s'%n", className);
Class<?> c = loader.defineClass(className, bytes);
if (debugInfo) {
try {
Field classBytes = c.getDeclaredField("classBytes");
classBytes.setAccessible(true);
classBytes.set(null, bytes);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
try {
Class<?> c = loader.loadClass(Type.className(clazzName));
@SuppressWarnings("unchecked")
T instance = (T) c.newInstance();
return instance;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static final class CodeLoader extends ClassLoader {
public CodeLoader() {
this(ClassLoader.getSystemClassLoader());
}
public CodeLoader(ClassLoader parent) {
super(parent);
}
Class<?> defineClass(String className, byte[] bytes) {
return defineClass(className, bytes, 0, bytes.length);
}
}
}