Files
CraftTweaker/ZenScript/src/main/java/stanhebben/zenscript/ZenModule.java
T

341 lines
11 KiB
Java
Raw Normal View History

package stanhebben.zenscript;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
2016-05-05 01:30:36 +02:00
import stanhebben.zenscript.compiler.*;
import stanhebben.zenscript.definitions.ParsedFunction;
import stanhebben.zenscript.definitions.ParsedFunctionArgument;
import stanhebben.zenscript.statements.Statement;
import stanhebben.zenscript.statements.StatementReturn;
import stanhebben.zenscript.symbols.SymbolArgument;
import stanhebben.zenscript.symbols.SymbolZenStaticMethod;
import stanhebben.zenscript.type.ZenType;
import stanhebben.zenscript.util.MethodOutput;
2016-05-05 01:30:36 +02:00
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static stanhebben.zenscript.util.ZenTypeUtil.internal;
/**
2014-06-10 21:12:55 +02:00
* Main module class. Contains a compiled module. The static methods of this
* class can be used to compile a specific file or files.
*
2015-08-31 19:08:46 +02:00
* Modules may contain statements in their source, or define functions.
* Functions in different scripts within the same module are accessible to each
* other, but not to other modules.
2014-06-10 21:12:55 +02:00
*
* @author Stan Hebben
*/
public class ZenModule {
2014-06-10 21:12:55 +02:00
/**
* Compiles a set of parsed files into a module.
*
* @param mainFileName main filename (used for debug info)
* @param scripts scripts to compile
* @param environmentGlobal global compile environment
* @param debug enable debug mode (outputs classes to generated directory)
2014-06-10 21:12:55 +02:00
*/
public static void compileScripts(String mainFileName, List<ZenParsedFile> scripts, IEnvironmentGlobal environmentGlobal, boolean debug) {
2014-07-04 22:03:03 +02:00
ClassWriter clsMain = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
2014-06-10 21:12:55 +02:00
clsMain.visitSource(mainFileName, null);
2015-08-31 19:08:46 +02:00
clsMain.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "__ZenMain__", null, internal(Object.class), new String[] { internal(Runnable.class) });
MethodOutput mainRun = new MethodOutput(clsMain, Opcodes.ACC_PUBLIC, "run", "()V", null, null);
mainRun.start();
2015-08-31 19:08:46 +02:00
for (ZenParsedFile script : scripts) {
for (Map.Entry<String, ParsedFunction> function : script.getFunctions().entrySet()) {
ParsedFunction fn = function.getValue();
environmentGlobal.putValue(function.getKey(), new SymbolZenStaticMethod(
script.getClassName(),
fn.getName(),
fn.getSignature(),
fn.getArgumentTypes(),
fn.getReturnType()), fn.getPosition());
}
}
2015-08-31 19:08:46 +02:00
for (ZenParsedFile script : scripts) {
2014-07-04 22:03:03 +02:00
ClassWriter clsScript = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
clsScript.visitSource(script.getFileName(), null);
EnvironmentClass environmentScript = new EnvironmentClass(clsScript, script.getEnvironment());
2015-08-31 19:08:46 +02:00
clsScript.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, script.getClassName().replace('.', '/'), null, internal(Object.class), new String[] { internal(Runnable.class) });
for (Map.Entry<String, ParsedFunction> function : script.getFunctions().entrySet()) {
ParsedFunction fn = function.getValue();
2015-08-31 19:08:46 +02:00
String signature = fn.getSignature();
MethodOutput methodOutput = new MethodOutput(clsScript, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, function.getKey(), signature, null, null);
EnvironmentMethod methodEnvironment = new EnvironmentMethod(methodOutput, environmentScript);
List<ParsedFunctionArgument> arguments = function.getValue().getArguments();
for (int i = 0; i < arguments.size(); i++) {
ParsedFunctionArgument argument = arguments.get(i);
methodEnvironment.putValue(argument.getName(), new SymbolArgument(i, argument.getType()), fn.getPosition());
}
methodOutput.start();
Statement[] statements = fn.getStatements();
for (Statement statement : statements) {
statement.compile(methodEnvironment);
}
if (function.getValue().getReturnType() != ZenType.VOID) {
if (statements[statements.length - 1] instanceof StatementReturn) {
if (((StatementReturn) statements[statements.length - 1]).getExpression() != null) {
fn.getReturnType()
2015-08-31 19:08:46 +02:00
.defaultValue(fn.getPosition())
.compile(true, methodEnvironment);
methodOutput.returnType(fn.getReturnType().toASMType());
}
} else {
fn.getReturnType()
2015-08-31 19:08:46 +02:00
.defaultValue(fn.getPosition())
.compile(true, methodEnvironment);
methodOutput.returnType(fn.getReturnType().toASMType());
}
}
methodOutput.end();
}
if (script.getStatements().size() > 0) {
MethodOutput scriptOutput = new MethodOutput(clsScript, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "__script__", "()V", null, null);
IEnvironmentMethod functionMethod = new EnvironmentMethod(scriptOutput, environmentScript);
2015-08-31 19:08:46 +02:00
// scriptOutput.enableDebug();
scriptOutput.start();
for (Statement statement : script.getStatements()) {
statement.compile(functionMethod);
}
scriptOutput.ret();
scriptOutput.end();
2015-08-31 19:08:46 +02:00
2014-08-31 11:52:25 +02:00
mainRun.invokeStatic(script.getClassName().replace('.', '/'), "__script__", "()V");
}
2015-08-31 19:08:46 +02:00
clsScript.visitEnd();
environmentGlobal.putClass(script.getClassName(), clsScript.toByteArray());
}
2015-08-31 19:08:46 +02:00
mainRun.ret();
mainRun.end();
clsMain.visitEnd();
2015-08-31 19:08:46 +02:00
MethodVisitor constructor = clsMain.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0);
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
constructor.visitInsn(Opcodes.RETURN);
constructor.visitMaxs(0, 0);
constructor.visitEnd();
2015-08-31 19:08:46 +02:00
// debug: output classes
if (debug) {
try {
File outputDir = new File("generated");
2016-05-05 01:30:36 +02:00
outputDir.mkdir();
for (String className : environmentGlobal.getClassNames()) {
File outputFile = new File(outputDir, className.replace('.', '/') + ".class");
2015-08-31 19:08:46 +02:00
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
2015-08-31 19:08:46 +02:00
FileOutputStream output = new FileOutputStream(outputFile);
output.write(environmentGlobal.getClass(className));
output.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
2015-08-31 19:08:46 +02:00
environmentGlobal.putClass("__ZenMain__", clsMain.toByteArray());
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
* Compiles a single script file.
*
* @param single file to be compiled
* @param environment compile environment
2014-07-04 22:03:03 +02:00
* @param baseClassLoader
2014-06-10 21:12:55 +02:00
* @return compiled module
* @throws IOException if the file could not be read
*/
public static ZenModule compileScriptFile(File single, IZenCompileEnvironment environment, ClassLoader baseClassLoader) throws IOException {
Map<String, byte[]> classes = new HashMap<String, byte[]>();
ClassNameGenerator nameGen = new ClassNameGenerator();
EnvironmentGlobal environmentGlobal = new EnvironmentGlobal(
environment,
classes,
nameGen
2015-08-31 19:08:46 +02:00
);
String filename = single.getName();
String className = extractClassName(filename);
2015-08-31 19:08:46 +02:00
FileInputStream input = new FileInputStream(single);
Reader reader = new InputStreamReader(new BufferedInputStream(input));
2014-06-10 21:12:55 +02:00
ZenTokener parser = new ZenTokener(reader, environment);
ZenParsedFile file = new ZenParsedFile(filename, className, parser, environmentGlobal);
reader.close();
2015-08-31 19:08:46 +02:00
List<ZenParsedFile> files = new ArrayList<ZenParsedFile>();
files.add(file);
2015-08-31 19:08:46 +02:00
compileScripts(filename, files, environmentGlobal, false);
2015-08-31 19:08:46 +02:00
// debug: output classes
File outputDir = new File("generated");
outputDir.mkdir();
2015-08-31 19:08:46 +02:00
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
File outputFile = new File(outputDir, entry.getKey().replace('.', '/') + ".class");
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
FileOutputStream output = new FileOutputStream(outputFile);
output.write(entry.getValue());
output.close();
}
2015-08-31 19:08:46 +02:00
return new ZenModule(classes, baseClassLoader);
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
* Compiles a zip file as module. All containing files (inside the given
* subdirectory) will be compiled.
*
* @param file zip file
* @param subdir subdirectory (use empty string to compile all)
* @param environment compile environment
* @return compiled module
* @throws IOException if the file could not be read properly
*/
public static ZenModule compileZip(
File file,
String subdir,
IZenCompileEnvironment environment,
ClassLoader baseClassLoader) throws IOException {
Map<String, byte[]> classes = new HashMap<String, byte[]>();
ClassNameGenerator nameGen = new ClassNameGenerator();
EnvironmentGlobal environmentGlobal = new EnvironmentGlobal(
environment,
classes,
nameGen
2015-08-31 19:08:46 +02:00
);
List<ZenParsedFile> files = new ArrayList<ZenParsedFile>();
2015-08-31 19:08:46 +02:00
ZipFile zipFile = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
2015-08-31 19:08:46 +02:00
if (entry.getName().startsWith(subdir) && !entry.getName().equals(subdir)) {
String filename = entry.getName().substring(subdir.length());
String className = extractClassName(filename);
2015-08-31 19:08:46 +02:00
Reader reader = new InputStreamReader(new BufferedInputStream(zipFile.getInputStream(entry)));
2014-06-10 21:12:55 +02:00
ZenTokener parser = new ZenTokener(reader, environment);
ZenParsedFile pfile = new ZenParsedFile(filename, className, parser, environmentGlobal);
files.add(pfile);
reader.close();
}
}
2015-08-31 19:08:46 +02:00
String filename = file.getName();
compileScripts(filename, files, environmentGlobal, true);
2015-08-31 19:08:46 +02:00
return new ZenModule(classes, baseClassLoader);
}
2015-08-31 19:08:46 +02:00
private final Map<String, byte[]> classes;
private final MyClassLoader classLoader;
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
* Constructs a module for the given set of classes. Mostly intended for
* internal use.
*
2015-08-31 19:08:46 +02:00
* @param classes
2014-06-10 21:12:55 +02:00
*/
public ZenModule(Map<String, byte[]> classes, ClassLoader baseClassLoader) {
this.classes = classes;
classLoader = new MyClassLoader(baseClassLoader);
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
2015-08-31 19:08:46 +02:00
* Retrieves the main runnable. Running this runnable will execute the
* content of the given module.
2014-06-10 21:12:55 +02:00
*
* @return main runnable
*/
public Runnable getMain() {
try {
2016-05-05 01:30:36 +02:00
return (Runnable) classLoader.loadClass("__ZenMain__").newInstance();
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
return null;
} catch (ClassNotFoundException ex) {
return null;
}
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
// ######################
// ### Static methods ###
// ######################
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
* Converts a filename into a class name.
*
* @param filename filename to convert
* @return class name
*/
public static String extractClassName(String filename) {
filename = filename.replace('\\', '/');
2015-08-31 19:08:46 +02:00
if (filename.startsWith("/"))
filename = filename.substring(1);
int lastSlash = filename.lastIndexOf('/');
int lastDot = filename.lastIndexOf('.');
2015-08-31 19:08:46 +02:00
if (lastDot > lastSlash) {
filename = filename.substring(0, lastDot);
}
if (lastSlash > 0) {
String dir = filename.substring(0, lastSlash);
String name = filename.substring(lastSlash + 1, lastSlash + 2).toUpperCase() + filename.substring(lastSlash + 2);
return dir + '.' + name;
} else {
return filename.substring(0, 1).toUpperCase() + filename.substring(1);
}
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
// #############################
// ### Private inner classes ###
// #############################
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
/**
* Custom class loader. Loads classes from this module.
*/
private class MyClassLoader extends ClassLoader {
private MyClassLoader(ClassLoader baseClassLoader) {
super(baseClassLoader);
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
if (classes.containsKey(name)) {
return defineClass(name, classes.get(name), 0, classes.get(name).length);
2014-06-10 21:12:55 +02:00
}
2015-08-31 19:08:46 +02:00
2014-06-10 21:12:55 +02:00
return super.findClass(name);
}
}
}