mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-04-17 13:49:57 +00:00
Implement function and variable codegen
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
package com.dfsek.terra.addons.terrascript;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.NonexistentSymbolException;
|
||||
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.SymbolAlreadyExistsException;
|
||||
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.SymbolTypeMismatchException;
|
||||
import com.dfsek.terra.addons.terrascript.Environment.Symbol.Function;
|
||||
import com.dfsek.terra.addons.terrascript.Environment.Symbol.Variable;
|
||||
import com.dfsek.terra.addons.terrascript.codegen.TerraScript;
|
||||
import com.dfsek.terra.api.util.generic.pair.Pair;
|
||||
|
||||
|
||||
@@ -18,7 +27,7 @@ public class Environment {
|
||||
|
||||
private final boolean canAccessOuterVariables;
|
||||
|
||||
private final HashMap<String, Symbol> symbolTable = new HashMap<>();
|
||||
private final Map<String, Symbol> symbolTable = new HashMap<>();
|
||||
|
||||
private final boolean inLoop;
|
||||
|
||||
@@ -26,6 +35,13 @@ public class Environment {
|
||||
this.outer = outer;
|
||||
this.canAccessOuterVariables = canAccessOuterVariables;
|
||||
this.inLoop = inLoop;
|
||||
// Populate symbol tables with built-in Java implemented methods
|
||||
TerraScript.BUILTIN_FUNCTIONS.forEach((name, method) -> symbolTable
|
||||
.put(name,
|
||||
new Function(
|
||||
Type.from(method.getReturnType()).orElseThrow(() -> new RuntimeException("")),
|
||||
// Map Java classes to TerraScript types
|
||||
IntStream.range(0, method.getParameterCount()).mapToObj(i -> Pair.of("param" + i, Type.from(method.getParameterTypes()[i]).orElseThrow(() -> new RuntimeException("")))).toList())));
|
||||
}
|
||||
|
||||
public static Environment global() {
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
package com.dfsek.terra.addons.terrascript;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
// TODO - Make not enum
|
||||
public enum Type {
|
||||
NUMBER,
|
||||
STRING,
|
||||
BOOLEAN,
|
||||
VOID,
|
||||
VOID;
|
||||
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return switch(this) {
|
||||
case NUMBER -> double.class;
|
||||
case STRING -> String.class;
|
||||
case BOOLEAN -> boolean.class;
|
||||
case VOID -> void.class;
|
||||
};
|
||||
}
|
||||
|
||||
public static Optional<Type> from(Class<?> clazz) {
|
||||
return Optional.ofNullable(
|
||||
clazz == double.class ? NUMBER :
|
||||
clazz == String.class ? STRING :
|
||||
clazz == boolean.class ? BOOLEAN :
|
||||
clazz == void.class ? VOID :
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public interface TerraScript {
|
||||
|
||||
void execute();
|
||||
|
||||
Map<String, Method> BUILTIN_FUNCTIONS = new HashMap<>() {{
|
||||
try {
|
||||
put("print", System.out.getClass().getMethod("println", String.class));
|
||||
} catch(NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen.asm;
|
||||
|
||||
public interface TerraScript {
|
||||
|
||||
void execute();
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen.asm;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.Environment.Symbol;
|
||||
import com.dfsek.terra.addons.terrascript.Type;
|
||||
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;
|
||||
@@ -22,24 +24,26 @@ 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 com.dfsek.terra.addons.terrascript.codegen.TerraScript;
|
||||
import com.dfsek.terra.addons.terrascript.util.ASMUtil;
|
||||
import com.dfsek.terra.api.util.generic.pair.Pair;
|
||||
|
||||
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 org.objectweb.asm.commons.LocalVariablesSorter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -55,6 +59,12 @@ public class TerraScriptClassGenerator {
|
||||
this.debugPath = debugPath;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param root Assumed to be semantically correct
|
||||
* @return Generated TerraScript instance
|
||||
* @throws IOException
|
||||
*/
|
||||
public TerraScript generate(Block root) throws IOException {
|
||||
String targetClassName = dynamicName(TARGET_CLASS);
|
||||
String generatedClassName = targetClassName + "_GENERATED_" + generationCount;
|
||||
@@ -65,11 +75,11 @@ public class TerraScriptClassGenerator {
|
||||
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);
|
||||
MethodVisitor constructor = classWriter.visitMethod(Opcodes.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.visitVarInsn(Opcodes.ALOAD, 0); // Put this reference on stack
|
||||
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
||||
constructor.visitInsn(Opcodes.RETURN); // Void return
|
||||
constructor.visitMaxs(0, 0);
|
||||
constructor.visitEnd();
|
||||
|
||||
@@ -79,16 +89,14 @@ public class TerraScriptClassGenerator {
|
||||
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();
|
||||
int exeAcc = Opcodes.ACC_PUBLIC;
|
||||
MethodVisitor executeMethod = classWriter.visitMethod(exeAcc, methodName, description, null, null);
|
||||
executeMethod.visitCode(); // Start method body
|
||||
new MethodBytecodeGenerator(classWriter, generatedClassName, executeMethod, exeAcc, description).generate(root); // Generate bytecode
|
||||
// Finish up method
|
||||
executeMethod.visitInsn(Opcodes.RETURN);
|
||||
executeMethod.visitMaxs(0, 0);
|
||||
executeMethod.visitEnd();
|
||||
|
||||
// Finished generating class
|
||||
classWriter.visitEnd();
|
||||
@@ -119,12 +127,31 @@ public class TerraScriptClassGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private static class BytecodeGenerator implements Stmt.Visitor<Void>, Expr.Visitor<Void> {
|
||||
private static class MethodBytecodeGenerator implements Stmt.Visitor<Void>, Expr.Visitor<Void> {
|
||||
|
||||
private final ClassWriter classWriter;
|
||||
|
||||
private final String className;
|
||||
|
||||
private final MethodVisitor method;
|
||||
|
||||
public BytecodeGenerator(MethodVisitor method) {
|
||||
private final String descriptor;
|
||||
|
||||
private final LocalVariablesSorter lvs;
|
||||
|
||||
private final Map<String, Integer> lvTable = new HashMap<>();
|
||||
|
||||
public MethodBytecodeGenerator(ClassWriter classWriter, String className, MethodVisitor method, int access, String descriptor) {
|
||||
this.classWriter = classWriter;
|
||||
this.className = className;
|
||||
this.method = method;
|
||||
this.descriptor = descriptor;
|
||||
this.lvs = new LocalVariablesSorter(access, descriptor, method);
|
||||
}
|
||||
|
||||
public void generate(Block root) {
|
||||
this.visitBlockStmt(root);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,7 +208,14 @@ public class TerraScriptClassGenerator {
|
||||
method.visitInsn(Opcodes.ICONST_0);
|
||||
method.visitLabel(finished);
|
||||
}
|
||||
case ADD -> method.visitInsn(Opcodes.DADD);
|
||||
case ADD -> {
|
||||
switch(expr.getType()) {
|
||||
case NUMBER -> method.visitInsn(Opcodes.DADD);
|
||||
// TODO - Optimize string concatenation
|
||||
case STRING -> method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);
|
||||
default -> throw new RuntimeException("Could not generate bytecode for ADD binary operator returning type " + expr.getType());
|
||||
}
|
||||
}
|
||||
case SUBTRACT -> method.visitInsn(Opcodes.DSUB);
|
||||
case MULTIPLY -> method.visitInsn(Opcodes.DMUL);
|
||||
case DIVIDE -> method.visitInsn(Opcodes.DDIV);
|
||||
@@ -209,16 +243,43 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Call expr) {
|
||||
Symbol.Function function = expr.getSymbol();
|
||||
if (TerraScript.BUILTIN_FUNCTIONS.containsKey(expr.identifier)) {
|
||||
if (expr.identifier.equals("print")) { // TODO - remove quick dirty print function call
|
||||
method.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
expr.arguments.get(0).accept(this);
|
||||
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
expr.arguments.forEach(a -> a.accept(this));
|
||||
List<Type> parameters = function.parameters.stream().map(Pair::getRight).toList();
|
||||
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, expr.identifier, getFunctionDescriptor(parameters, function.type), false);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableExpr(Variable expr) {
|
||||
Type varType = expr.getSymbol().type;
|
||||
method.visitVarInsn(switch(varType) {
|
||||
case NUMBER -> Opcodes.DLOAD;
|
||||
case STRING -> Opcodes.ALOAD;
|
||||
case BOOLEAN -> Opcodes.ILOAD;
|
||||
default -> throw new RuntimeException("Unable to load local variable, unknown parameter type '" + varType + "'");
|
||||
}, lvTable.get(expr.identifier));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitAssignmentExpr(Assignment expr) {
|
||||
expr.rValue.accept(this);
|
||||
Type type = expr.lValue.getSymbol().type;
|
||||
method.visitVarInsn(switch(type) {
|
||||
case NUMBER -> Opcodes.DSTORE;
|
||||
case STRING -> Opcodes.ASTORE;
|
||||
case BOOLEAN -> Opcodes.ISTORE;
|
||||
default -> throw new RuntimeException("Unable to assign local variable, unknown parameter type '" + type + "'");
|
||||
}, lvTable.get(expr.lValue.identifier));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -239,18 +300,77 @@ public class TerraScriptClassGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getFunctionDescriptor(List<Type> parameters, Type returnType) {
|
||||
StringBuilder sb = new StringBuilder().append("(");
|
||||
parameters.stream().map(p -> switch (p) {
|
||||
case NUMBER -> "D";
|
||||
case STRING -> "Ljava/lang/String;";
|
||||
case BOOLEAN -> "Z";
|
||||
default -> throw new RuntimeException("Unable to generate method descriptor, unknown parameter type '" + p + "'");
|
||||
}).forEach(sb::append);
|
||||
sb.append(")");
|
||||
sb.append(switch (returnType) {
|
||||
case NUMBER -> "D";
|
||||
case STRING -> "Ljava/lang/String;";
|
||||
case BOOLEAN -> "Z";
|
||||
case VOID -> "V";
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitFunctionDeclarationStmt(FunctionDeclaration stmt) {
|
||||
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
List<Type> parameterTypes = stmt.parameters.stream().map(Pair::getRight).toList();
|
||||
MethodVisitor method = classWriter.visitMethod(access, stmt.identifier, getFunctionDescriptor(parameterTypes, stmt.type), null, null);
|
||||
|
||||
method.visitCode(); // Start method body
|
||||
|
||||
MethodBytecodeGenerator funcGenerator = new MethodBytecodeGenerator(classWriter, className, method, access, descriptor);
|
||||
|
||||
// Add local variable indexes for each parameter
|
||||
int lvidx = 0;
|
||||
for (Pair<String, Type> parameter : stmt.parameters) {
|
||||
funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
|
||||
lvidx += switch(parameter.getRight()) {
|
||||
case NUMBER -> 2;
|
||||
case STRING, BOOLEAN -> 1;
|
||||
default -> throw new RuntimeException("Unable to register local variable index for parameter, unknown parameter type '" + parameter.getRight() + "'");
|
||||
};
|
||||
}
|
||||
|
||||
// Generate method bytecode
|
||||
funcGenerator.generate(stmt.body);
|
||||
|
||||
// Finish up
|
||||
method.visitInsn(Opcodes.RETURN);
|
||||
method.visitMaxs(0, 0);
|
||||
method.visitEnd();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableDeclarationStmt(VariableDeclaration stmt) {
|
||||
stmt.value.accept(this);
|
||||
lvTable.put(stmt.identifier, lvs.newLocal(ASMUtil.tsTypeToAsmType(stmt.type)));
|
||||
method.visitVarInsn(switch(stmt.type) {
|
||||
case NUMBER -> Opcodes.DSTORE;
|
||||
case STRING -> Opcodes.ASTORE;
|
||||
case BOOLEAN -> Opcodes.ISTORE;
|
||||
default -> throw new RuntimeException("Unable to declare local variable, unknown parameter type '" + stmt.type + "'");
|
||||
}, lvTable.get(stmt.identifier));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitReturnStmt(Return stmt) {
|
||||
stmt.value.accept(this);
|
||||
switch(stmt.getType()) {
|
||||
case NUMBER -> method.visitInsn(Opcodes.DRETURN);
|
||||
case STRING -> method.visitInsn(Opcodes.ARETURN);
|
||||
case BOOLEAN -> method.visitInsn(Opcodes.IRETURN);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visit
|
||||
public Void visitCallExpr(Call expr) {
|
||||
String id = expr.identifier;
|
||||
try {
|
||||
expr.getEnvironment().getFunction(id);
|
||||
expr.getSymbol();
|
||||
} catch(NonexistentSymbolException e) {
|
||||
errorHandler.add(
|
||||
new UndefinedReferenceException("No function by the name '" + id + "' is defined in this scope", expr.position));
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Call expr) {
|
||||
expr.setEnvironment(currentScope);
|
||||
expr.setSymbol(currentScope.getFunction(expr.identifier));
|
||||
expr.arguments.forEach(e -> e.accept(this));
|
||||
return null;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
} catch(SymbolTypeMismatchException e) {
|
||||
errorHandler.add(new ParseException("Identifier '" + id + "' is not defined as a variable", expr.position));
|
||||
}
|
||||
expr.setEnvironment(currentScope);
|
||||
expr.setSymbol(currentScope.getVariable(expr.identifier));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
errorHandler.add(new IdentifierAlreadyDeclaredException("Name '" + stmt.identifier + "' is already defined in this scope",
|
||||
stmt.position));
|
||||
}
|
||||
stmt.setEnvironment(currentScope);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,14 @@ public class TypeChecker implements Visitor<Type>, Stmt.Visitor<Type> {
|
||||
yield Type.BOOLEAN;
|
||||
}
|
||||
case ADD -> {
|
||||
if(left == Type.NUMBER && right == Type.NUMBER) yield Type.NUMBER;
|
||||
if(left == Type.STRING || right == Type.STRING) yield Type.STRING;
|
||||
if(left == Type.NUMBER && right == Type.NUMBER) {
|
||||
expr.setType(Type.NUMBER);
|
||||
yield Type.NUMBER;
|
||||
}
|
||||
if(left == Type.STRING || right == Type.STRING) {
|
||||
expr.setType(Type.STRING);
|
||||
yield Type.STRING;
|
||||
}
|
||||
throw new RuntimeException("Addition operands must be either both numbers, or one of type string");
|
||||
}
|
||||
case SUBTRACT, MULTIPLY, DIVIDE, MODULO -> {
|
||||
@@ -92,10 +98,10 @@ public class TypeChecker implements Visitor<Type>, Stmt.Visitor<Type> {
|
||||
public Type visitCallExpr(Call expr) {
|
||||
String id = expr.identifier;
|
||||
|
||||
Environment.Symbol.Function signature = expr.getEnvironment().getFunction(id);
|
||||
Environment.Symbol.Function signature = expr.getSymbol();
|
||||
|
||||
List<Type> argumentTypes = expr.arguments.stream().map(a -> a.accept(this)).toList();
|
||||
List<Pair<String, Type>> parameters = signature.parameters;
|
||||
List<Type> parameters = signature.parameters.stream().map(Pair::getRight).toList();
|
||||
|
||||
if(argumentTypes.size() != parameters.size())
|
||||
errorHandler.add(new ParseException(
|
||||
@@ -103,7 +109,7 @@ public class TypeChecker implements Visitor<Type>, Stmt.Visitor<Type> {
|
||||
" arguments", expr.position));
|
||||
|
||||
for(int i = 0; i < parameters.size(); i++) {
|
||||
Type expectedType = parameters.get(i).getRight();
|
||||
Type expectedType = parameters.get(i);
|
||||
Type providedType = argumentTypes.get(i);
|
||||
if(expectedType != providedType)
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
@@ -116,7 +122,7 @@ public class TypeChecker implements Visitor<Type>, Stmt.Visitor<Type> {
|
||||
|
||||
@Override
|
||||
public Type visitVariableExpr(Variable expr) {
|
||||
return expr.getEnvironment().getVariable(expr.identifier).type;
|
||||
return expr.getSymbol().type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,7 +188,7 @@ public class TypeChecker implements Visitor<Type>, Stmt.Visitor<Type> {
|
||||
|
||||
@Override
|
||||
public Type visitReturnStmt(Stmt.Return stmt) {
|
||||
stmt.value.accept(this);
|
||||
stmt.setType(stmt.value.accept(this));
|
||||
return Type.VOID;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.dfsek.terra.addons.terrascript.util;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.Type;
|
||||
|
||||
|
||||
public class ASMUtil {
|
||||
|
||||
/**
|
||||
@@ -10,4 +13,8 @@ public class ASMUtil {
|
||||
public static String dynamicName(Class<?> clazz) {
|
||||
return clazz.getCanonicalName().replace('.', '/');
|
||||
}
|
||||
|
||||
public static org.objectweb.asm.Type tsTypeToAsmType(Type type) {
|
||||
return org.objectweb.asm.Type.getType((Class<?>) type.javaType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package codegen;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.ErrorHandler;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Block;
|
||||
import com.dfsek.terra.addons.terrascript.codegen.TerraScript;
|
||||
import com.dfsek.terra.addons.terrascript.codegen.asm.TerraScriptClassGenerator;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.Lexer;
|
||||
import com.dfsek.terra.addons.terrascript.parser.Parser;
|
||||
import com.dfsek.terra.addons.terrascript.semanticanalysis.SemanticAnalyzer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CodeGenTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
testValid("""
|
||||
fun retNum(): num {
|
||||
return 3 + 3;
|
||||
}
|
||||
|
||||
fun retBool(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
fun concatThree(a: str, b: str, c: str): str {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
fun retStr(): str {
|
||||
fun concatTwo(a: str, b: str): str {
|
||||
return a + b;
|
||||
}
|
||||
str hello = "Hell";
|
||||
hello = concatTwo(hello, "o");
|
||||
str world = "world!";
|
||||
return concatThree(hello, " ", world);
|
||||
}
|
||||
|
||||
fun takesArgs(a: str, b: num, c: bool): str {
|
||||
return a;
|
||||
}
|
||||
|
||||
fun doStuff(a: str, b: str, c: bool) {
|
||||
print("Doing stuff");
|
||||
if (c) {
|
||||
print(concatThree(a, " ", b));
|
||||
} else {
|
||||
print("c is false");
|
||||
}
|
||||
}
|
||||
|
||||
num a = 1;
|
||||
num b = 2;
|
||||
str e = "test";
|
||||
|
||||
retNum();
|
||||
bool bln = true;
|
||||
|
||||
print(takesArgs("test", 3, true));
|
||||
print(retStr());
|
||||
|
||||
doStuff("Ay0o", "world", true);
|
||||
""");
|
||||
}
|
||||
|
||||
private void testValid(String validSource) {
|
||||
try {
|
||||
Block script = Parser.parse(new Lexer(validSource).analyze());
|
||||
SemanticAnalyzer.analyze(script, new ErrorHandler());
|
||||
TerraScript ts = new TerraScriptClassGenerator("./build/codegentest").generate(script);
|
||||
ts.execute();
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user