Implement function and variable codegen

This commit is contained in:
Astrash
2023-09-08 11:36:40 +10:00
parent 9a75ee78a1
commit b1bfe00bf3
11 changed files with 370 additions and 97 deletions

View File

@@ -1,10 +1,12 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import java.util.*
version = version("1.1.0")
dependencies {
api("commons-io:commons-io:2.7")
api("org.ow2.asm:asm:9.5")
api("org.ow2.asm:asm-commons:9.5")
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
@@ -18,44 +20,38 @@ tasks.named<ShadowJar>("shadowJar") {
val astSourceSet = buildDir.resolve("generated/ast")
val astPackage = astSourceSet.resolve("com/dfsek/terra/addons/terrascript/ast")
data class ASTClass(val name: String, val imports: List<String>, val nodes: List<ASTNode>)
data class ASTNode(val name: String, val constructorFields: List<Pair<String, String>>, val mutableFields: List<Pair<String, String>> = emptyList())
// Auto generate AST classes rather than writing them by hand
tasks.register("genTerrascriptAstClasses") {
val packageName = astPackage.toRelativeString(astSourceSet).replace('/', '.')
fun generateClass(name: String, imports: List<String>, nodes: List<Pair<String, List<String>>>) {
fun generateClass(clazz: ASTClass) {
val src = StringBuilder()
src.appendLine("package $packageName;\n");
for (imprt in imports) src.appendLine("import $imprt;")
for (imprt in clazz.imports) src.appendLine("import $imprt;")
src.appendLine("""
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
import com.dfsek.terra.addons.terrascript.Environment;
/**
* Auto-generated class via genTerrascriptAstClasses gradle task
*/
public abstract class $name {
public abstract class ${clazz.name} {
public final SourcePosition position;
private Environment environment;
public $name(SourcePosition position) {
public ${clazz.name}(SourcePosition position) {
this.position = position;
}
public Environment getEnvironment() {
if (this.environment == null) throw new RuntimeException("Compilation bug! environment has not been set yet for AST node");
return environment;
}
public void setEnvironment(Environment environment) {
if (this.environment != null) throw new RuntimeException("Compilation bug! environment has already been set for AST node and cannot be changed");
this.environment = environment;
}
public interface Visitor<R> {
""".trimIndent())
for (node in nodes) {
src.appendLine(" R visit${node.first}$name(${node.first} ${name.toLowerCase()});")
for (node in clazz.nodes) {
src.appendLine(" R visit${node.name}${clazz.name}(${node.name} ${clazz.name.toLowerCase()});")
}
src.appendLine("""
@@ -65,79 +61,98 @@ tasks.register("genTerrascriptAstClasses") {
| public abstract <R> R accept(Visitor<R> visitor);
""".trimMargin())
for (node in nodes) {
for (node in clazz.nodes) {
src.appendLine()
// Inner class declaration
src.appendLine(" public static class ${node.first} extends $name {\n")
src.appendLine(" public static class ${node.name} extends ${clazz.name} {\n")
// Add fields
for (field in node.second) {
src.appendLine(" public final $field;")
for (field in node.constructorFields) {
src.appendLine(" public final ${field.second} ${field.first};")
}
for (field in node.mutableFields) {
src.appendLine(" private ${field.second} ${field.first};")
}
src.appendLine()
// Add constructor
src.append(" public ${node.first}(")
for (field in node.second)
src.append("$field, ")
src.append(" public ${node.name}(")
for (field in node.constructorFields)
src.append("${field.second} ${field.first}, ")
src.appendLine("SourcePosition position) {\n super(position);")
for (field in node.second) {
val fieldName = field.split(' ').last()
src.appendLine(" this.$fieldName = $fieldName;")
for (field in node.constructorFields) {
src.appendLine(" this.${field.first} = ${field.first};")
}
src.appendLine(" }")
// Add getters and setters for mutable fields
for (field in node.mutableFields) {
src.appendLine("""
|
| public void set${field.first.capitalize()}(${field.second} value) {
| this.${field.first} = value;
| }
|
| public ${field.second} get${field.first.capitalize()}() {
| if (this.${field.first} == null) throw new RuntimeException("Compilation bug! Field ${field.first} has not been set yet");
| return this.${field.first};
| }
""".trimMargin())
}
src.appendLine("""
| }
|
| @Override
| public <R> R accept(Visitor<R> visitor) {
| return visitor.visit${node.first}$name(this);
| return visitor.visit${node.name}${clazz.name}(this);
| }
| }
""".trimMargin())
}
src.appendLine("}")
val outputFile = astPackage.resolve("$name.java")
val outputFile = astPackage.resolve("${clazz.name}.java")
outputFile.writeText(src.toString())
}
doLast {
astSourceSet.deleteRecursively()
astPackage.mkdirs()
generateClass("Expr", listOf(
generateClass(ASTClass("Expr", listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.addons.terrascript.parser.UnaryOperator",
"com.dfsek.terra.addons.terrascript.parser.BinaryOperator",
"com.dfsek.terra.addons.terrascript.Environment.Symbol",
"java.util.List",
),
listOf(
Pair("Binary", listOf("Expr left", "BinaryOperator operator", "Expr right",)),
Pair("Grouping", listOf("Expr expression")),
Pair("Literal", listOf("Object value", "Type type")),
Pair("Unary", listOf("UnaryOperator operator", "Expr operand")),
Pair("Call", listOf("String identifier", "List<Expr> arguments")),
Pair("Variable", listOf("String identifier")),
Pair("Assignment", listOf("Variable lValue", "Expr rValue")),
Pair("Void", listOf()),
))
generateClass("Stmt", listOf(
ASTNode("Binary", listOf("left" to "Expr", "operator" to "BinaryOperator", "right" to "Expr",), listOf("type" to "Type")),
ASTNode("Grouping", listOf("expression" to "Expr")),
ASTNode("Literal", listOf("value" to "Object", "type" to "Type")),
ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "Expr")),
ASTNode("Call", listOf("identifier" to "String", "arguments" to "List<Expr>"), listOf("symbol" to "Symbol.Function")),
ASTNode("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol.Variable")),
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "Expr")),
ASTNode("Void", listOf()),
)))
generateClass(ASTClass("Stmt", listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.api.util.generic.pair.Pair",
"com.dfsek.terra.addons.terrascript.Environment.Symbol",
"java.util.List",
),
listOf(
Pair("Expression", listOf("Expr expression")),
Pair("Block", listOf("List<Stmt> statements")),
Pair("FunctionDeclaration", listOf("String identifier", "List<Pair<String, Type>> parameters", "Type type", "Block body")),
Pair("VariableDeclaration", listOf("Type type", "String identifier", "Expr value")),
Pair("Return", listOf("Expr value")),
Pair("If", listOf("Expr condition", "Block trueBody", "List<Pair<Expr, Block>> elseIfClauses", "Block elseBody")),
Pair("For", listOf("Stmt initializer", "Expr condition", "Expr incrementer", "Block body")),
Pair("While", listOf("Expr condition", "Block body")),
Pair("NoOp", listOf()),
Pair("Break", listOf()),
Pair("Continue", listOf()),
))
ASTNode("Expression", listOf("expression" to "Expr")),
ASTNode("Block", listOf("statements" to "List<Stmt>")),
ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List<Pair<String, Type>>", "type" to "Type", "body" to "Block"), listOf("symbol" to "Symbol.Function")),
ASTNode("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "Expr")),
ASTNode("Return", listOf("value" to "Expr"), listOf("type" to "Type")),
ASTNode("If", listOf("condition" to "Expr", "trueBody" to "Block", "elseIfClauses" to "List<Pair<Expr, Block>>", "elseBody" to "Block")),
ASTNode("For", listOf("initializer" to "Stmt", "condition" to "Expr", "incrementer" to "Expr", "body" to "Block")),
ASTNode("While", listOf("condition" to "Expr", "body" to "Block")),
ASTNode("NoOp", listOf()),
ASTNode("Break", listOf()),
ASTNode("Continue", listOf()),
)))
}
}

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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);
}
}};
}

View File

@@ -1,7 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen.asm;
public interface TerraScript {
void execute();
}

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}