Skip to content

Commit

Permalink
feat: multitask inline code
Browse files Browse the repository at this point in the history
  • Loading branch information
Eddie authored and Eddie committed Jun 9, 2024
1 parent c0896eb commit 91ac078
Show file tree
Hide file tree
Showing 28 changed files with 368 additions and 231 deletions.
Binary file modified out/artifacts/jungle_jar/jungle.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.4</version>
<version>9.7</version>
</dependency>
</dependencies>

Expand Down
3 changes: 0 additions & 3 deletions programs/ideas/multitask-inline.source

This file was deleted.

11 changes: 11 additions & 0 deletions programs/multitask-inline.ast
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SEQUENCE
MULTITASK
LITERAL_STRING com.example.RunnableTask
BLOCK
SEQUENCE
PRINT
LITERAL_STRING Inline thread!
;
;
;
;
12 changes: 12 additions & 0 deletions programs/multitask-inline.source
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
multitask "com.example.RunnableTaskOne" : {
# Wait a little
x = 999999
loop (greaterThan x 0) {
x = - x 1
}
print "thread!\n"
}

multitask "com.example.RunnableTaskTwo" : {
print "Inline..."
}
19 changes: 19 additions & 0 deletions programs/multitask-inline.tokens
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
1 1 KEYWORD multitask
1 10 SPACE
1 11 TEXT com.example.RunnableTask
1 37 SPACE
1 38 COLON
1 39 SPACE
1 40 BRACKET_CURLY_OPEN
1 41 NEWLINE
2 1 SPACE
2 2 SPACE
2 3 SPACE
2 4 SPACE
2 5 KEYWORD print
2 10 SPACE
2 11 TEXT Inline thread!
2 27 NEWLINE
3 1 BRACKET_CURLY_CLOSE
3 2 NEWLINE
3 3 TERMINAL
4 changes: 2 additions & 2 deletions src/main/java/com/jungle/JungleCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected static void compileCommand(@NotNull CommandLine cli) {
String outputFileName = cli.getOptionValue("output", "Entrypoint");
String junglePath = getJunglePath(cli);
Compiler compiler = new Compiler();
compiler.compile(outputFileName, new MainVisitor(junglePath), ast);
compiler.compileMain(outputFileName, new MainVisitor(junglePath), ast);
}

protected static void runCommand(@NotNull CommandLine cli) {
Expand All @@ -139,7 +139,7 @@ protected static void runCommand(@NotNull CommandLine cli) {
String entrypointClassName = cli.getOptionValue("output", "Entrypoint");
String junglePath = getJunglePath(cli);
Compiler compiler = new Compiler();
compiler.compile(entrypointClassName, new MainVisitor(junglePath), ast);
compiler.compileMain(entrypointClassName, new MainVisitor(junglePath), ast);
// Run...
/* Problem:
* Loading the new class using reflection and invoking main appears to work in some cases.
Expand Down
118 changes: 95 additions & 23 deletions src/main/java/com/jungle/compiler/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static org.objectweb.asm.Opcodes.*;

Expand All @@ -24,10 +23,34 @@ public class Compiler {

// region Helpers

public static void writeClassFile(@NotNull String className, byte[] classData) {
Path classPath = Paths.get(className + ".class");
@NotNull
public static String asInternalClassName(@NotNull String className) {
return className.replace('.', '/');
}

public static void writeClassFile(@NotNull String fullClassName, byte[] classData) {
// TODO: new class files should be placed in a target directory
// ...
String classFileString = fullClassName.replace('.', '/') + ".class";
logger.debug(String.format("write class file - %s", classFileString));
File classFile = new File(classFileString);
File classParentFile = classFile.getParentFile();
if (classParentFile != null) {
// Ensure folder path exists
classParentFile.mkdir();
}
try {
boolean hasCreatedNewFile = classFile.createNewFile();
if (!hasCreatedNewFile) {
logger.debug(String.format("class already exists - %s", fullClassName));
}
} catch (IOException e) {
String message = "failed to create class file";
logger.error(message, e);
throw new CompilerError(message);
}
try {
Files.write(classPath, classData);
Files.write(classFile.toPath(), classData);
} catch (IOException e) {
String message = "failed to write to class file";
logger.error(message, e);
Expand All @@ -37,22 +60,38 @@ public static void writeClassFile(@NotNull String className, byte[] classData) {

// endregion

public void compile(@NotNull String mainClassName, @NotNull IVisitor mainVisitor, @Nullable INode ast) {
// TODO: handle multi-class
public void compileMain(@NotNull String mainClassName, @NotNull IVisitor languageVisitor, @Nullable INode ast) {
ClassWriter initialClassWriter = visitMainClass(mainClassName); // template
ClassWriter classWriter = visitEntrypoint("main", initialClassWriter, languageVisitor, ast);
writeClassFile(mainClassName, classWriter.toByteArray());
}

public void compileRunnable(@NotNull String runnableClassName, @NotNull IVisitor languageVisitor, @Nullable INode ast) {
ClassWriter initialClassWriter = visitRunnableClass(runnableClassName); // template
ClassWriter classWriter = visitEntrypoint("run", initialClassWriter, languageVisitor, ast);
writeClassFile(runnableClassName, classWriter.toByteArray());
}

@NotNull
public ClassWriter visitEntrypoint(
@NotNull String targetMethodName,
@NotNull ClassWriter initialClassWriter,
@NotNull IVisitor languageVisitor,
@Nullable INode ast
) {
if (ast == null) {
String message = "AST is null - source files must contain code";
logger.error(message);
throw new CompilerError(message);
}
logger.debug("generating entrypoint class from template");
ClassWriter initialClassWriter = visitMainClass(mainClassName); // template
logger.debug("generating entrypoint class from initial class");
ClassReader classReader = new ClassReader(initialClassWriter.toByteArray());
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
ClassVisitor entrypoint = new MainClassVisitor(classWriter, mainVisitor, ast);
ClassVisitor entrypoint = new JungleClassVisitor(targetMethodName, classWriter, languageVisitor, ast);
logger.debug("traversing AST");
classReader.accept(entrypoint, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
logger.debug("writing class file");
writeClassFile(mainClassName, classWriter.toByteArray());
return classWriter;
}

// region Emit Classes - ClassWriter factories
Expand All @@ -66,21 +105,41 @@ protected ClassWriter visitMainClass(@NotNull String className) {
cw.visit(
V1_8,
ACC_PUBLIC + ACC_SUPER,
className,
asInternalClassName(className),
null,
"java/lang/Object",
null);
null
);
visitDefaultConstructor(cw);
visitMainMethod(cw);
cw.visitEnd();
return cw;
}

@NotNull
public static ClassWriter visitRunnableClass(@NotNull String className) {
logger.debug("visit runnable class");
int flags = ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES;
ClassWriter cw = new ClassWriter(flags);
cw.visit(
V1_8,
ACC_PUBLIC + ACC_SUPER,
asInternalClassName(className),
null,
"java/lang/Object",
new String[]{"java/lang/Runnable"}
);
visitDefaultConstructor(cw);
visitRunnableRunMethod(cw);
cw.visitEnd();
return cw;
}

// endregion

// region Emit Methods - MethodVisitor factories
@NotNull
public static MethodVisitor visitDefaultConstructor(@NotNull ClassWriter cw) {

public static void visitDefaultConstructor(@NotNull ClassWriter cw) {
// public ClassConstructor() { Object::super(); return; }
logger.debug("visit default constructor");
MethodVisitor mv = cw.visitMethod(
Expand All @@ -90,35 +149,48 @@ public static MethodVisitor visitDefaultConstructor(@NotNull ClassWriter cw) {
null,
null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(
mv.visitMethodInsn( // super()
INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()V",
false);
// Code should be added here using a MethodVisitor...
// Use a MethodVisitor to emit code before the return statement
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
return mv;
}

@NotNull
public static MethodVisitor visitMainMethod(@NotNull ClassWriter cw) {
public static void visitMainMethod(@NotNull ClassWriter cw) {
// public static void main(String[]) { return; }
logger.debug("visit main method");
MethodVisitor mv = cw.visitMethod(
ACC_PUBLIC + ACC_STATIC,
MainMethodVisitor.MAIN_METHOD_NAME,
"main",
"([Ljava/lang/String;)V",
null,
null);
mv.visitCode();
// Code should be added here using a MethodVisitor...
// Use a MethodVisitor to emit code before the return statement
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}

public static void visitRunnableRunMethod(@NotNull ClassWriter cw) {
// public void run() { return; }
logger.debug("visit runnable run method");
MethodVisitor mv = cw.visitMethod(
ACC_PUBLIC,
"run",
"()V",
null,
null);
mv.visitCode();
// Use a MethodVisitor to emit code before the return statement
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
return mv;
}

//endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,38 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MainClassVisitor extends ClassVisitor {
public class JungleClassVisitor extends ClassVisitor {
@NotNull
private static final FileLogger logger = new FileLogger(MainClassVisitor.class.getSimpleName());
private static final FileLogger logger = new FileLogger(JungleClassVisitor.class.getSimpleName());

@NotNull
private final String targetMethodName;

@NotNull
private final IVisitor mainVisitor;

@NotNull
private final INode ast;

public MainClassVisitor(ClassVisitor classVisitor, @NotNull IVisitor mainVisitor, @NotNull INode ast) {
public JungleClassVisitor(
@NotNull String targetMethodName,
@NotNull ClassVisitor classVisitor,
@NotNull IVisitor mainVisitor,
@NotNull INode ast
) {
super(Opcodes.ASM5, classVisitor);
this.targetMethodName = targetMethodName;
this.mainVisitor = mainVisitor;
this.ast = ast;
}

@Override
public MethodVisitor visitMethod(int flags, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(flags, name, desc, signature, exceptions);
boolean isMainMethod = MainMethodVisitor.MAIN_METHOD_NAME.equals(name);
if (isMainMethod) {
logger.debug("emit custom instructions in main class");
return new MainMethodVisitor(mv, mainVisitor, ast);
boolean isTargetMethod = targetMethodName.equals(name);
if (isTargetMethod) {
logger.debug("emit custom instructions into class method");
return new JungleMethodVisitor(mv, mainVisitor, ast);
}
return mv;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
package com.jungle.compiler;

import com.jungle.ast.INode;
import com.jungle.compiler.operand.OperandStackContext;
import com.jungle.compiler.visitor.IVisitor;
import com.jungle.logger.FileLogger;

import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MainMethodVisitor extends MethodVisitor {
public class JungleMethodVisitor extends MethodVisitor {
@NotNull
private static final FileLogger logger = new FileLogger(MainMethodVisitor.class.getSimpleName());

public static final String MAIN_METHOD_NAME = "main";
private static final FileLogger logger = new FileLogger(JungleMethodVisitor.class.getSimpleName());

@NotNull
private final IVisitor mainVisitor;

@NotNull
private final INode ast;

public MainMethodVisitor(MethodVisitor mv, @NotNull IVisitor mainVisitor, @NotNull INode ast) {
@NotNull
private final OperandStackContext context;

public JungleMethodVisitor(MethodVisitor mv, @NotNull IVisitor mainVisitor, @NotNull INode ast) {
super(Opcodes.ASM5, mv);
this.mainVisitor = mainVisitor;
this.ast = ast;
this.context = new OperandStackContext();
}

@Override
public void visitInsn(final int opcode) {
if (opcode == Opcodes.RETURN) {
logger.debug("emit instructions just before existing RETURN operation");
mainVisitor.visit(this, ast);
mainVisitor.visit(this, ast, context);
}
super.visitInsn(opcode);
}
Expand Down
13 changes: 0 additions & 13 deletions src/main/java/com/jungle/compiler/operand/OperandStackContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.jungle.logger.FileLogger;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.MethodVisitor;

import java.util.Stack;
Expand All @@ -15,18 +14,6 @@ public class OperandStackContext {
@NotNull
private static final FileLogger logger = new FileLogger(OperandStackContext.class.getName());

// region Singleton Factory
@Nullable
private static OperandStackContext operandStackContext = null;
@NotNull
public static OperandStackContext getInstance() {
if (operandStackContext == null) {
operandStackContext = new OperandStackContext();
}
return operandStackContext;
}
// endregion

// Track the node type that goes onto the jvm stack to catch semantic errors before they are runtime errors
// When the jvm instruction adds to the stack, add the node type to this compile-time stack
// When the jvm instruction removes from the stack, remove the type from this compile-time stack
Expand Down
Loading

0 comments on commit 91ac078

Please sign in to comment.