mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-04-17 13:49:57 +00:00
Salvage TerraScript codegen code from dead laptop
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen.asm;
|
||||
|
||||
import com.dfsek.terra.api.structure.Structure;
|
||||
|
||||
|
||||
public class DynamicClassLoader extends ClassLoader {
|
||||
public DynamicClassLoader(Class<?> clazz) {
|
||||
super(clazz.getClassLoader());
|
||||
}
|
||||
|
||||
public Class<?> defineClass(String name, byte[] data) {
|
||||
return defineClass(name, data, 0, data.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
return Class.forName(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen.asm;
|
||||
|
||||
public interface TerraScript {
|
||||
|
||||
void execute();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen.asm;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Assignment;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Binary;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Call;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Grouping;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Literal;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Unary;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Variable;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Void;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Block;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Break;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Continue;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Expression;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.For;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.FunctionDeclaration;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.If;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.NoOp;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Return;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.VariableDeclaration;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.While;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.dfsek.terra.addons.terrascript.util.ASMUtil.dynamicName;
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
import static org.objectweb.asm.Opcodes.ALOAD;
|
||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
||||
import static org.objectweb.asm.Opcodes.IOR;
|
||||
import static org.objectweb.asm.Opcodes.RETURN;
|
||||
|
||||
|
||||
public class TerraScriptClassGenerator {
|
||||
|
||||
private static final Class<?> TARGET_CLASS = TerraScript.class;
|
||||
|
||||
private static final boolean DUMP = true;
|
||||
|
||||
private int generationCount = 0;
|
||||
|
||||
private final String debugPath;
|
||||
|
||||
public TerraScriptClassGenerator(String debugPath) {
|
||||
this.debugPath = debugPath;
|
||||
}
|
||||
|
||||
public TerraScript generate(Block root) throws IOException {
|
||||
String targetClassName = dynamicName(TARGET_CLASS);
|
||||
String generatedClassName = targetClassName + "_GENERATED_" + generationCount;
|
||||
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
// Create class
|
||||
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, generatedClassName, null, "java/lang/Object", new String[]{ targetClassName });
|
||||
|
||||
// Generate constructor method
|
||||
MethodVisitor constructor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
constructor.visitCode();
|
||||
constructor.visitVarInsn(ALOAD, 0); // Put this reference on stack
|
||||
constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
||||
constructor.visitInsn(RETURN); // Void return
|
||||
constructor.visitMaxs(0, 0);
|
||||
constructor.visitEnd();
|
||||
|
||||
// Generate execute method
|
||||
String methodName = "execute";
|
||||
// Extract method description
|
||||
MethodExtractor extractor = new MethodExtractor(methodName);
|
||||
new ClassReader(targetClassName).accept(extractor, 0);
|
||||
String description = extractor.methodDescription;
|
||||
MethodVisitor execute = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, description, null, null);
|
||||
execute.visitCode(); // Start method body
|
||||
|
||||
execute.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
new BytecodeGenerator(execute).visitBlockStmt(root);
|
||||
execute.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
|
||||
|
||||
execute.visitInsn(RETURN);
|
||||
execute.visitMaxs(0, 0);
|
||||
execute.visitEnd();
|
||||
|
||||
// Finished generating class
|
||||
classWriter.visitEnd();
|
||||
|
||||
DynamicClassLoader loader = new DynamicClassLoader(TARGET_CLASS); // Instantiate a new loader every time so classes can be GC'ed when they are no longer used. (Classes cannot be GC'ed until their loaders are).
|
||||
|
||||
generationCount++;
|
||||
|
||||
byte[] bytecode = classWriter.toByteArray();
|
||||
|
||||
Class<?> generatedClass = loader.defineClass(generatedClassName.replace('/', '.'), bytecode);
|
||||
|
||||
if (DUMP) {
|
||||
File dump = new File(debugPath + "/" + generatedClass.getSimpleName() + ".class");
|
||||
dump.getParentFile().mkdirs();
|
||||
try(FileOutputStream out = new FileOutputStream(dump)) {
|
||||
out.write(bytecode);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Object instance = generatedClass.getDeclaredConstructor().newInstance();
|
||||
return (TerraScript) instance;
|
||||
} catch(ReflectiveOperationException e) {
|
||||
throw new Error(e); // Should literally never happen
|
||||
}
|
||||
}
|
||||
|
||||
private static class BytecodeGenerator implements Stmt.Visitor<Void>, Expr.Visitor<Void> {
|
||||
|
||||
private final MethodVisitor method;
|
||||
|
||||
public BytecodeGenerator(MethodVisitor method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBinaryExpr(Binary expr) {
|
||||
expr.left.accept(this);
|
||||
expr.right.accept(this);
|
||||
switch(expr.operator) {
|
||||
// TODO - Short circuit binary operators
|
||||
case BOOLEAN_OR -> method.visitInsn(Opcodes.IOR);
|
||||
case BOOLEAN_AND -> method.visitInsn(Opcodes.IAND);
|
||||
// case EQUALS -> null;
|
||||
// case NOT_EQUALS -> null;
|
||||
case GREATER -> {
|
||||
Label falseLabel = new Label();
|
||||
Label finished = new Label();
|
||||
method.visitInsn(Opcodes.DCMPL);
|
||||
method.visitJumpInsn(Opcodes.IFLE, falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_1);
|
||||
method.visitJumpInsn(Opcodes.GOTO, finished);
|
||||
method.visitLabel(falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_0);
|
||||
method.visitLabel(finished);
|
||||
}
|
||||
case GREATER_EQUALS -> {
|
||||
Label falseLabel = new Label();
|
||||
Label finished = new Label();
|
||||
method.visitInsn(Opcodes.DCMPL);
|
||||
method.visitJumpInsn(Opcodes.IFLT, falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_1);
|
||||
method.visitJumpInsn(Opcodes.GOTO, finished);
|
||||
method.visitLabel(falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_0);
|
||||
method.visitLabel(finished);
|
||||
}
|
||||
case LESS -> {
|
||||
Label falseLabel = new Label();
|
||||
Label finished = new Label();
|
||||
method.visitInsn(Opcodes.DCMPG);
|
||||
method.visitJumpInsn(Opcodes.IFGE, falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_1);
|
||||
method.visitJumpInsn(Opcodes.GOTO, finished);
|
||||
method.visitLabel(falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_0);
|
||||
method.visitLabel(finished);
|
||||
}
|
||||
case LESS_EQUALS -> {
|
||||
Label falseLabel = new Label();
|
||||
Label finished = new Label();
|
||||
method.visitInsn(Opcodes.DCMPG);
|
||||
method.visitJumpInsn(Opcodes.IFGT, falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_1);
|
||||
method.visitJumpInsn(Opcodes.GOTO, finished);
|
||||
method.visitLabel(falseLabel);
|
||||
method.visitInsn(Opcodes.ICONST_0);
|
||||
method.visitLabel(finished);
|
||||
}
|
||||
case ADD -> method.visitInsn(Opcodes.DADD);
|
||||
case SUBTRACT -> method.visitInsn(Opcodes.DSUB);
|
||||
case MULTIPLY -> method.visitInsn(Opcodes.DMUL);
|
||||
case DIVIDE -> method.visitInsn(Opcodes.DDIV);
|
||||
// case MODULO ->
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitGroupingExpr(Grouping expr) {
|
||||
expr.expression.accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitLiteralExpr(Literal expr) {
|
||||
method.visitLdcInsn(expr.value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnaryExpr(Unary expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Call expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableExpr(Variable expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitAssignmentExpr(Assignment expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVoidExpr(Void expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExpressionStmt(Expression stmt) {
|
||||
stmt.expression.accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBlockStmt(Block stmt) {
|
||||
stmt.statements.forEach(s -> s.accept(this));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitFunctionDeclarationStmt(FunctionDeclaration stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableDeclarationStmt(VariableDeclaration stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitReturnStmt(Return stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIfStmt(If stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitForStmt(For stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitWhileStmt(While stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitNoOpStmt(NoOp stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBreakStmt(Break stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitContinueStmt(Continue stmt) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodExtractor extends ClassVisitor {
|
||||
|
||||
private final String methodName;
|
||||
private String methodDescription;
|
||||
|
||||
protected MethodExtractor(String methodName) {
|
||||
super(Opcodes.ASM9);
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (name.equals(methodName))
|
||||
methodDescription = descriptor;
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.dfsek.terra.addons.terrascript.util;
|
||||
|
||||
public class ASMUtil {
|
||||
|
||||
/**
|
||||
* Dynamically get name to account for possibility of shading
|
||||
* @param clazz Class instance
|
||||
* @return Internal class name
|
||||
*/
|
||||
public static String dynamicName(Class<?> clazz) {
|
||||
return clazz.getCanonicalName().replace('.', '/');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user