mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2025-07-01 23:47:50 +00:00
Autoformat
This commit is contained in:
parent
9a94b26126
commit
5f3a2bb579
@ -30,13 +30,11 @@ data class ASTNode(
|
||||
val name: String,
|
||||
val constructorFields: List<Pair<String, String>>,
|
||||
val mutableFields: List<Pair<String, String>> = emptyList() // TODO - Remove mutability from AST nodes
|
||||
|
||||
)
|
||||
|
||||
// Auto generate AST classes rather than writing them by hand
|
||||
tasks.register("genTerrascriptAstClasses") {
|
||||
|
||||
|
||||
val packageName = astPackage.toRelativeString(astSourceSet).replace('/', '.')
|
||||
fun generateClass(clazz: ASTClass) {
|
||||
val src = StringBuilder()
|
||||
|
@ -13,19 +13,13 @@ import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction;
|
||||
|
||||
public class Environment {
|
||||
|
||||
private final Environment outer;
|
||||
|
||||
private final boolean canAccessOuterVariables;
|
||||
|
||||
private final Map<String, Symbol> symbolTable = new HashMap<>();
|
||||
|
||||
private final boolean inLoop;
|
||||
|
||||
private final int index;
|
||||
|
||||
private int innerCount = 0;
|
||||
|
||||
public final String name;
|
||||
private final Environment outer;
|
||||
private final boolean canAccessOuterVariables;
|
||||
private final Map<String, Symbol> symbolTable = new HashMap<>();
|
||||
private final boolean inLoop;
|
||||
private final int index;
|
||||
private int innerCount = 0;
|
||||
|
||||
private Environment(@Nullable Environment outer, boolean canAccessOuterVariables, boolean inLoop, int index) {
|
||||
this.outer = outer;
|
||||
@ -35,8 +29,11 @@ public class Environment {
|
||||
this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList());
|
||||
// Populate global scope with built-in Java implemented methods
|
||||
// TODO - Replace with AST import nodes
|
||||
if (index == 0) NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) ->
|
||||
symbolTable.put(name, new Symbol.Variable(new Type.Function.Native(function.getReturnType(), function.getParameterTypes(), name, this, function))));
|
||||
if(index == 0) NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) ->
|
||||
symbolTable.put(name, new Symbol.Variable(
|
||||
new Type.Function.Native(function.getReturnType(),
|
||||
function.getParameterTypes(), name,
|
||||
this, function))));
|
||||
}
|
||||
|
||||
public static Environment global() {
|
||||
@ -57,7 +54,7 @@ public class Environment {
|
||||
|
||||
private List<Integer> getNestedIndexes() {
|
||||
List<Integer> idxs = new ArrayList<>();
|
||||
for (Environment env = this; env.outer != null; env = env.outer) {
|
||||
for(Environment env = this; env.outer != null; env = env.outer) {
|
||||
idxs.add(0, env.index);
|
||||
}
|
||||
return idxs;
|
||||
@ -78,7 +75,7 @@ public class Environment {
|
||||
*
|
||||
* @return variable symbol table entry
|
||||
*
|
||||
* @throws NonexistentSymbolException if symbol is not declared in symbol table
|
||||
* @throws NonexistentSymbolException if symbol is not declared in symbol table
|
||||
*/
|
||||
public Symbol getVariable(String id) throws NonexistentSymbolException {
|
||||
Symbol symbol = symbolTable.get(id);
|
||||
|
@ -12,6 +12,89 @@ import com.dfsek.terra.api.util.generic.pair.Pair;
|
||||
|
||||
public interface Type {
|
||||
|
||||
Type NUMBER = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return double.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "num";
|
||||
}
|
||||
};
|
||||
Type INTEGER = new Type() {
|
||||
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return int.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "int";
|
||||
}
|
||||
};
|
||||
Type STRING = new Type() {
|
||||
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "str";
|
||||
}
|
||||
};
|
||||
Type BOOLEAN = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return boolean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "bool";
|
||||
}
|
||||
};
|
||||
Type VOID = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return void.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "()";
|
||||
}
|
||||
};
|
||||
|
||||
static Type fromString(String lexeme) throws TypeException {
|
||||
return switch(lexeme) {
|
||||
case "num" -> NUMBER;
|
||||
@ -31,6 +114,7 @@ public interface Type {
|
||||
|
||||
CodegenType getCodegenType();
|
||||
|
||||
|
||||
class Function implements Type {
|
||||
|
||||
private final Type returnType;
|
||||
@ -43,6 +127,11 @@ public interface Type {
|
||||
this.id = identifier == null ? "ANONYMOUS" : identifier + declarationScope.name;
|
||||
}
|
||||
|
||||
private static boolean paramsAreSubtypes(List<Type> subtypes, List<Type> superTypes) {
|
||||
if(subtypes.size() != superTypes.size()) return false;
|
||||
return Streams.zip(subtypes.stream(), superTypes.stream(), Pair::of).allMatch(p -> p.getLeft().typeOf(p.getRight()));
|
||||
}
|
||||
|
||||
public Type getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
@ -66,11 +155,6 @@ public interface Type {
|
||||
return CodegenType.OBJECT;
|
||||
}
|
||||
|
||||
private static boolean paramsAreSubtypes(List<Type> subtypes, List<Type> superTypes) {
|
||||
if(subtypes.size() != superTypes.size()) return false;
|
||||
return Streams.zip(subtypes.stream(), superTypes.stream(), Pair::of).allMatch(p -> p.getLeft().typeOf(p.getRight()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return Function.class;
|
||||
@ -96,92 +180,6 @@ public interface Type {
|
||||
}
|
||||
}
|
||||
|
||||
Type NUMBER = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return double.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "num";
|
||||
}
|
||||
};
|
||||
|
||||
Type INTEGER = new Type() {
|
||||
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return int.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "int";
|
||||
}
|
||||
};
|
||||
|
||||
Type STRING = new Type() {
|
||||
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "str";
|
||||
}
|
||||
};
|
||||
|
||||
Type BOOLEAN = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return boolean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "bool";
|
||||
}
|
||||
};
|
||||
|
||||
Type VOID = new Type() {
|
||||
@Override
|
||||
public java.lang.reflect.Type javaType() {
|
||||
return void.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "()";
|
||||
}
|
||||
};
|
||||
|
||||
class TypeException extends Exception {
|
||||
}
|
||||
|
@ -5,29 +5,27 @@ import org.objectweb.asm.Opcodes;
|
||||
|
||||
public class CodegenType {
|
||||
|
||||
private final InstructionType instructionType;
|
||||
|
||||
private final String descriptor;
|
||||
|
||||
public CodegenType(InstructionType instructionType, String descriptor) {
|
||||
this.instructionType = instructionType;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public InstructionType bytecodeType() {
|
||||
return instructionType;
|
||||
}
|
||||
|
||||
public String getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static final CodegenType BOOLEAN = new CodegenType(InstructionType.INTEGER, "Z");
|
||||
public static final CodegenType STRING = new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;");
|
||||
public static final CodegenType DOUBLE = new CodegenType(InstructionType.DOUBLE, "D");
|
||||
public static final CodegenType INTEGER = new CodegenType(InstructionType.INTEGER, "I");
|
||||
public static final CodegenType VOID = new CodegenType(InstructionType.VOID, "V");
|
||||
public static final CodegenType OBJECT = new CodegenType(InstructionType.OBJECT, "Ljava/lang/Object;");
|
||||
private final InstructionType instructionType;
|
||||
private final String descriptor;
|
||||
public CodegenType(InstructionType instructionType, String descriptor) {
|
||||
this.instructionType = instructionType;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public InstructionType bytecodeType() {
|
||||
return instructionType;
|
||||
}
|
||||
|
||||
public String getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
|
||||
public enum InstructionType {
|
||||
DOUBLE {
|
||||
|
@ -4,7 +4,7 @@ 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);
|
||||
}
|
||||
|
@ -50,24 +50,24 @@ import com.dfsek.terra.api.util.generic.pair.Pair;
|
||||
|
||||
import static com.dfsek.terra.addons.terrascript.v2.util.ASMUtil.dynamicName;
|
||||
|
||||
|
||||
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;
|
||||
private int generationCount = 0;
|
||||
|
||||
public TerraScriptClassGenerator(String debugPath) {
|
||||
this.debugPath = debugPath;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param root Assumed to be semantically correct
|
||||
*
|
||||
* @return Generated TerraScript instance
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public TerraScript generate(Block root) throws IOException {
|
||||
@ -78,7 +78,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
// 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(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
constructor.visitCode();
|
||||
@ -97,7 +97,8 @@ public class TerraScriptClassGenerator {
|
||||
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
|
||||
new MethodBytecodeGenerator(classWriter, generatedClassName, executeMethod, exeAcc, description).generate(
|
||||
root); // Generate bytecode
|
||||
// Finish up method
|
||||
executeMethod.visitInsn(Opcodes.RETURN);
|
||||
executeMethod.visitMaxs(0, 0);
|
||||
@ -106,7 +107,9 @@ public class TerraScriptClassGenerator {
|
||||
// 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).
|
||||
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++;
|
||||
|
||||
@ -114,7 +117,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
Class<?> generatedClass = loader.defineClass(generatedClassName.replace('/', '.'), bytecode);
|
||||
|
||||
if (DUMP) {
|
||||
if(DUMP) {
|
||||
File dump = new File(debugPath + "/" + generatedClass.getSimpleName() + ".class");
|
||||
dump.getParentFile().mkdirs();
|
||||
try(FileOutputStream out = new FileOutputStream(dump)) {
|
||||
@ -156,6 +159,13 @@ public class TerraScriptClassGenerator {
|
||||
this.lvs = new LocalVariablesSorter(access, descriptor, method);
|
||||
}
|
||||
|
||||
private static boolean exprTypesEqual(Type type, TypedExpr... exprs) {
|
||||
for(TypedExpr expr : exprs) {
|
||||
if(expr.type != type) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void generate(Block root) {
|
||||
this.visitBlockTypedStmt(root);
|
||||
}
|
||||
@ -169,9 +179,10 @@ public class TerraScriptClassGenerator {
|
||||
CodegenType codegenType = expr.type.getCodegenType();
|
||||
if(codegenType.bytecodeType() == InstructionType.DOUBLE)
|
||||
method.visitInsn(Opcodes.DADD);
|
||||
else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
|
||||
else if(Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
|
||||
// TODO - Optimize string concatenation
|
||||
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);
|
||||
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat",
|
||||
"(Ljava/lang/String;)Ljava/lang/String;", false);
|
||||
else throw new RuntimeException("Could not generate bytecode for ADD binary operator returning type " + expr.type);
|
||||
}
|
||||
case SUBTRACT -> binaryInsn(expr, Opcodes.DSUB);
|
||||
@ -191,7 +202,8 @@ public class TerraScriptClassGenerator {
|
||||
@Override
|
||||
public Void visitLiteralTypedExpr(Literal expr) {
|
||||
if(expr.type.getCodegenType() == CodegenType.BOOLEAN)
|
||||
if ((boolean) expr.value) pushTrue(); else pushFalse();
|
||||
if((boolean) expr.value) pushTrue();
|
||||
else pushFalse();
|
||||
else method.visitLdcInsn(expr.value);
|
||||
return null;
|
||||
}
|
||||
@ -199,7 +211,7 @@ public class TerraScriptClassGenerator {
|
||||
@Override
|
||||
public Void visitUnaryTypedExpr(Unary expr) {
|
||||
expr.operand.accept(this);
|
||||
switch (expr.operator) {
|
||||
switch(expr.operator) {
|
||||
case NOT -> invertBool();
|
||||
case NEGATE -> method.visitInsn(Opcodes.DNEG);
|
||||
}
|
||||
@ -209,7 +221,7 @@ public class TerraScriptClassGenerator {
|
||||
@Override
|
||||
public Void visitCallTypedExpr(Call expr) {
|
||||
// TODO - Remove specific handling of native functions
|
||||
if (expr.callee.type instanceof Type.Function.Native nativeFunction) {
|
||||
if(expr.callee.type instanceof Type.Function.Native nativeFunction) {
|
||||
NativeFunction function = nativeFunction.getNativeFunction();
|
||||
function.pushInstance(method);
|
||||
expr.arguments.forEach(a -> a.accept(this));
|
||||
@ -219,7 +231,8 @@ public class TerraScriptClassGenerator {
|
||||
// TODO - Add support for invokevirtual
|
||||
expr.arguments.forEach(a -> a.accept(this));
|
||||
List<Type> parameters = expr.arguments.stream().map(e -> e.type).toList();
|
||||
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, ((Type.Function) expr.callee.type).getId(), getFunctionDescriptor(parameters, expr.type), false);
|
||||
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, ((Type.Function) expr.callee.type).getId(),
|
||||
getFunctionDescriptor(parameters, expr.type), false);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -257,7 +270,9 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
/**
|
||||
* Writes function as a private static method of the current class
|
||||
*
|
||||
* @param stmt
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@ -266,7 +281,8 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
|
||||
MethodVisitor method = classWriter.visitMethod(access, stmt.scopedIdentifier, getFunctionDescriptor(parameterTypes, stmt.returnType), null, null);
|
||||
MethodVisitor method = classWriter.visitMethod(access, stmt.scopedIdentifier,
|
||||
getFunctionDescriptor(parameterTypes, stmt.returnType), null, null);
|
||||
|
||||
method.visitCode(); // Start method body
|
||||
|
||||
@ -274,7 +290,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
// Add local variable indexes for each parameter
|
||||
int lvidx = 0;
|
||||
for (Pair<String, Type> parameter : stmt.parameters) {
|
||||
for(Pair<String, Type> parameter : stmt.parameters) {
|
||||
funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
|
||||
lvidx += parameter.getRight().getCodegenType().bytecodeType().slotSize(); // Increment by how many slots data type takes
|
||||
}
|
||||
@ -370,13 +386,6 @@ public class TerraScriptClassGenerator {
|
||||
return exprTypesEqual(type, expr.left, expr.right);
|
||||
}
|
||||
|
||||
private static boolean exprTypesEqual(Type type, TypedExpr... exprs) {
|
||||
for(TypedExpr expr : exprs) {
|
||||
if (expr.type != type) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts a boolean on the stack
|
||||
*/
|
||||
@ -406,6 +415,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
/**
|
||||
* Pushes boolean on to the stack based on comparison result
|
||||
*
|
||||
* @param condition
|
||||
*/
|
||||
private void pushComparisonBool(TypedExpr condition) {
|
||||
@ -417,6 +427,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
/**
|
||||
* Executes a statement then jumps to the exit label if the condition is true, jumps over the statement if false
|
||||
*
|
||||
* @param condition
|
||||
* @param stmt
|
||||
* @param exit
|
||||
@ -447,7 +458,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
private void conditionalRunnable(TypedExpr condition, Runnable trueBlock, Label trueFinished) {
|
||||
Label exit = new Label(); // If the first conditional is false, jump over statement and don't execute it
|
||||
if (condition instanceof Binary binaryCondition) {
|
||||
if(condition instanceof Binary binaryCondition) {
|
||||
switch(binaryCondition.operator) {
|
||||
case BOOLEAN_AND -> {
|
||||
// Operands assumed booleans
|
||||
@ -466,30 +477,30 @@ public class TerraScriptClassGenerator {
|
||||
label(skipRight);
|
||||
}
|
||||
case EQUALS -> {
|
||||
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
|
||||
if(binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
|
||||
pushBinaryOperands(binaryCondition);
|
||||
jumpIf(OpcodeAlias.INTEGERS_NOT_EQUAL, exit);
|
||||
|
||||
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
|
||||
} else if(binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
|
||||
binaryInsn(binaryCondition, Opcodes.DCMPG);
|
||||
jumpIf(OpcodeAlias.CMP_NOT_EQUALS, exit);
|
||||
|
||||
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) {
|
||||
} else if(binaryOperandsSameType(Type.STRING, binaryCondition)) {
|
||||
pushBinaryOperands(binaryCondition);
|
||||
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
|
||||
jumpIf(OpcodeAlias.BOOL_FALSE, exit);
|
||||
} else throw new CompilerBugException();
|
||||
}
|
||||
case NOT_EQUALS -> {
|
||||
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
|
||||
if(binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
|
||||
pushBinaryOperands(binaryCondition);
|
||||
jumpIf(OpcodeAlias.INTEGERS_EQUAL, exit);
|
||||
|
||||
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
|
||||
} else if(binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
|
||||
binaryInsn(binaryCondition, Opcodes.DCMPG);
|
||||
jumpIf(OpcodeAlias.CMP_EQUALS, exit);
|
||||
|
||||
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
|
||||
} else if(binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
|
||||
pushBinaryOperands(binaryCondition);
|
||||
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
|
||||
invertBool();
|
||||
@ -533,6 +544,7 @@ public class TerraScriptClassGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MethodExtractor extends ClassVisitor {
|
||||
|
||||
private final String methodName;
|
||||
@ -545,7 +557,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (name.equals(methodName))
|
||||
if(name.equals(methodName))
|
||||
methodDescription = descriptor;
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ public class Parser {
|
||||
|
||||
private Expr postfix() {
|
||||
Expr expr = primary();
|
||||
while (current().isType(TokenType.OPEN_PAREN)) {
|
||||
while(current().isType(TokenType.OPEN_PAREN)) {
|
||||
expr = call(expr);
|
||||
}
|
||||
return expr;
|
||||
|
@ -26,9 +26,8 @@ import static com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.S
|
||||
|
||||
public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
|
||||
private Environment currentScope;
|
||||
|
||||
private final ErrorHandler errorHandler;
|
||||
private Environment currentScope;
|
||||
|
||||
|
||||
public ScopeAnalyzer(Environment globalScope, ErrorHandler errorHandler) {
|
||||
|
@ -43,16 +43,22 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
Type type = switch(expr.operator) {
|
||||
case BOOLEAN_OR, BOOLEAN_AND -> {
|
||||
if(!leftType.typeOf(Type.BOOLEAN) || !rightType.typeOf(Type.BOOLEAN))
|
||||
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.BOOLEAN + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.BOOLEAN + "', found types '" +
|
||||
leftType + "' and '" + rightType + "'", expr.position));
|
||||
yield Type.BOOLEAN;
|
||||
}
|
||||
case EQUALS, NOT_EQUALS -> {
|
||||
if(!leftType.typeOf(rightType)) errorHandler.add(new InvalidTypeException("Both operands of equality operator (==) must be of the same type, found mismatched types '" + leftType + "' and '" + rightType + "'", expr.position));
|
||||
if(!leftType.typeOf(rightType)) errorHandler.add(new InvalidTypeException(
|
||||
"Both operands of equality operator (==) must be of the same type, found mismatched types '" + leftType +
|
||||
"' and '" + rightType + "'", expr.position));
|
||||
yield Type.BOOLEAN;
|
||||
}
|
||||
case GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> {
|
||||
if(!leftType.typeOf(Type.NUMBER) || !rightType.typeOf(Type.NUMBER))
|
||||
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" +
|
||||
leftType + "' and '" + rightType + "'", expr.position));
|
||||
yield Type.BOOLEAN;
|
||||
}
|
||||
case ADD -> {
|
||||
@ -62,12 +68,16 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
if(leftType.typeOf(Type.STRING) || rightType.typeOf(Type.STRING)) {
|
||||
yield Type.STRING;
|
||||
}
|
||||
errorHandler.add(new InvalidTypeException("Addition operands must be either both of type '" + Type.NUMBER + "', or one of type '" + Type.STRING + "'", expr.position));
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Addition operands must be either both of type '" + Type.NUMBER + "', or one of type '" + Type.STRING + "'",
|
||||
expr.position));
|
||||
yield Type.VOID;
|
||||
}
|
||||
case SUBTRACT, MULTIPLY, DIVIDE, MODULO -> {
|
||||
if(!leftType.typeOf(Type.NUMBER) || !rightType.typeOf(Type.NUMBER))
|
||||
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" +
|
||||
leftType + "' and '" + rightType + "'", expr.position));
|
||||
yield Type.NUMBER;
|
||||
}
|
||||
};
|
||||
@ -106,7 +116,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
TypedExpr function = expr.callee.accept(this);
|
||||
|
||||
if(!(function.type instanceof Type.Function functionType)) {
|
||||
errorHandler.add(new InvalidCalleeException("Cannot call type '" + function.type + "', only functions can be called", expr.position));
|
||||
errorHandler.add(
|
||||
new InvalidCalleeException("Cannot call type '" + function.type + "', only functions can be called", expr.position));
|
||||
return new TypedExpr.Void(Type.VOID);
|
||||
}
|
||||
|
||||
@ -115,7 +126,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
|
||||
if(arguments.size() != parameters.size())
|
||||
errorHandler.add(new InvalidArgumentsException(
|
||||
"Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments", expr.position));
|
||||
"Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments",
|
||||
expr.position));
|
||||
|
||||
for(int i = 0; i < parameters.size(); i++) {
|
||||
Type expectedType = parameters.get(i);
|
||||
@ -142,7 +154,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
String id = expr.lValue.identifier;
|
||||
if(!right.type.typeOf(expected))
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Cannot assign variable '" + id + "' to value of type '" + right.type + "', '" + id + "' is declared with type '" + expected + "'",
|
||||
"Cannot assign variable '" + id + "' to value of type '" + right.type + "', '" + id + "' is declared with type '" +
|
||||
expected + "'",
|
||||
expr.position));
|
||||
return new TypedExpr.Assignment(left, right, right.type);
|
||||
}
|
||||
@ -171,7 +184,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
new InvalidFunctionDeclarationException("Function body for '" + stmt.identifier + "' does not contain return statement",
|
||||
stmt.position));
|
||||
}
|
||||
return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body, ((Type.Function) stmt.getSymbol().type).getId());
|
||||
return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body,
|
||||
((Type.Function) stmt.getSymbol().type).getId());
|
||||
}
|
||||
|
||||
private boolean alwaysReturns(TypedStmt stmt, Stmt.FunctionDeclaration function) {
|
||||
@ -181,11 +195,12 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
"Return statement must match function's return type. Function '" + function.identifier + "' expects " +
|
||||
function.returnType + ", found " + ret.value.type + " instead", function.position));
|
||||
return true;
|
||||
} else if (stmt instanceof TypedStmt.If ifStmt) {
|
||||
} else if(stmt instanceof TypedStmt.If ifStmt) {
|
||||
return alwaysReturns(ifStmt.trueBody, function) &&
|
||||
ifStmt.elseIfClauses.stream().map(Pair::getRight).allMatch(s -> alwaysReturns(s, function)) &&
|
||||
ifStmt.elseBody.map(body -> alwaysReturns(body, function)).orElse(false); // If else body is not defined then statement does not always return
|
||||
} else if (stmt instanceof TypedStmt.Block block) {
|
||||
ifStmt.elseBody.map(body -> alwaysReturns(body, function)).orElse(
|
||||
false); // If else body is not defined then statement does not always return
|
||||
} else if(stmt instanceof TypedStmt.Block block) {
|
||||
return block.statements.stream().anyMatch(s -> alwaysReturns(s, function));
|
||||
}
|
||||
return false;
|
||||
@ -196,8 +211,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
TypedExpr value = stmt.value.accept(this);
|
||||
if(!stmt.type.typeOf(value.type))
|
||||
errorHandler.add(new InvalidTypeException(
|
||||
"Type of value assigned to variable '" + stmt.identifier + "' does not match variable's declared type. Expected type '" +
|
||||
stmt.type + "', found '" + value.type +"' instead", stmt.position));
|
||||
"Type of value assigned to variable '" + stmt.identifier +
|
||||
"' does not match variable's declared type. Expected type '" +
|
||||
stmt.type + "', found '" + value.type + "' instead", stmt.position));
|
||||
return new TypedStmt.VariableDeclaration(stmt.type, stmt.identifier, value);
|
||||
}
|
||||
|
||||
@ -209,12 +225,15 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
@Override
|
||||
public TypedStmt visitIfStmt(Stmt.If stmt) {
|
||||
TypedExpr condition = stmt.condition.accept(this);
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("If statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
|
||||
"If statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
|
||||
|
||||
TypedStmt.Block trueBody = (TypedStmt.Block) stmt.trueBody.accept(this);
|
||||
List<Pair<TypedExpr, TypedStmt.Block>> elseIfClauses = stmt.elseIfClauses.stream().map(c -> {
|
||||
TypedExpr clauseCondition = c.getLeft().accept(this);
|
||||
if (!clauseCondition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("Else if clause conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
|
||||
if(!clauseCondition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
|
||||
"Else if clause conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
|
||||
stmt.position));
|
||||
return Pair.of(clauseCondition, (TypedStmt.Block) c.getRight().accept(this));
|
||||
}).toList();
|
||||
|
||||
@ -227,7 +246,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
public TypedStmt visitForStmt(Stmt.For stmt) {
|
||||
TypedStmt initializer = stmt.initializer.accept(this);
|
||||
TypedExpr condition = stmt.condition.accept(this);
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("For statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
|
||||
"For statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
|
||||
stmt.position));
|
||||
TypedExpr incrementer = stmt.incrementer.accept(this);
|
||||
return new TypedStmt.For(initializer, condition, incrementer, (TypedStmt.Block) stmt.body.accept(this));
|
||||
}
|
||||
@ -235,7 +256,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
@Override
|
||||
public TypedStmt visitWhileStmt(Stmt.While stmt) {
|
||||
TypedExpr condition = stmt.condition.accept(this);
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("While statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
|
||||
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
|
||||
"While statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
|
||||
stmt.position));
|
||||
return new TypedStmt.While(condition, (TypedStmt.Block) stmt.body.accept(this));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,9 @@ 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) {
|
||||
|
@ -59,7 +59,7 @@ public class CodeGenTest {
|
||||
|
||||
retNum();
|
||||
var bln: bool = true;
|
||||
|
||||
|
||||
print(takesArgs("test", 3, true));
|
||||
print(retStr());
|
||||
|
||||
|
@ -9,6 +9,7 @@ import com.dfsek.terra.addons.terrascript.v2.lexer.Token.TokenType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
public class LexerTest {
|
||||
|
||||
private static void tokenTypeTest(String input, TokenType type) {
|
||||
|
@ -38,7 +38,7 @@ public class LookaheadStreamTest {
|
||||
|
||||
assertEquals(first, lookahead.current());
|
||||
assertEquals(first, lookahead.current());
|
||||
|
||||
|
||||
assertEquals(new SourcePosition(1, 1), lookahead.getPosition());
|
||||
assertEquals(new SourcePosition(1, 1), lookahead.getPosition());
|
||||
|
||||
@ -52,7 +52,7 @@ public class LookaheadStreamTest {
|
||||
assertEquals(second, lookahead.current());
|
||||
|
||||
assertEquals(second, lookahead.consume());
|
||||
|
||||
|
||||
assertEquals(third, lookahead.current());
|
||||
|
||||
assertTrue(lookahead.matchesString("st", true));
|
||||
|
@ -129,12 +129,12 @@ public class SemanticAnalyzerTest {
|
||||
|
||||
// Returns can still be explicitly used for void returning functions
|
||||
testValid("""
|
||||
fun returnsNum(p: bool) {
|
||||
if (p) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
""");
|
||||
fun returnsNum(p: bool) {
|
||||
if (p) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// If all if-statement bodies always return, then the statement is considered as always returning
|
||||
testValid("""
|
||||
@ -159,7 +159,8 @@ public class SemanticAnalyzerTest {
|
||||
}
|
||||
""");
|
||||
|
||||
// If no else body is defined, an if-statement does not always return, therefore the function does not contain any always-return-statements
|
||||
// If no else body is defined, an if-statement does not always return, therefore the function does not contain any
|
||||
// always-return-statements
|
||||
testInvalid("""
|
||||
fun returnsNum(p: bool): num {
|
||||
if (p) {
|
||||
@ -194,38 +195,38 @@ public class SemanticAnalyzerTest {
|
||||
""");
|
||||
|
||||
testInvalid("""
|
||||
fun returnsNum(p1: bool, p2: bool): num {
|
||||
if (p1) {
|
||||
if (p2) {
|
||||
return 1;
|
||||
}
|
||||
// No else clause here, so will not always return
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
""", InvalidFunctionDeclarationException.class);
|
||||
fun returnsNum(p1: bool, p2: bool): num {
|
||||
if (p1) {
|
||||
if (p2) {
|
||||
return 1;
|
||||
}
|
||||
// No else clause here, so will not always return
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
""", InvalidFunctionDeclarationException.class);
|
||||
|
||||
// If-statement may not always return but a return statement after it means function will always return
|
||||
testValid("""
|
||||
fun returnsNum(p: bool): num {
|
||||
if (p) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
""");
|
||||
fun returnsNum(p: bool): num {
|
||||
if (p) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
""");
|
||||
|
||||
// Same applies when statements are swapped
|
||||
testValid("""
|
||||
fun returnsNum(p: bool): num {
|
||||
return 1;
|
||||
// Unreachable
|
||||
if (p) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
""");
|
||||
fun returnsNum(p: bool): num {
|
||||
return 1;
|
||||
// Unreachable
|
||||
if (p) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -281,12 +282,12 @@ public class SemanticAnalyzerTest {
|
||||
|
||||
// Should not be able to pass argument of type not matching parameter type
|
||||
testInvalid("""
|
||||
fun returnBool(): bool {
|
||||
return true;
|
||||
}
|
||||
fun takeNum(p: num) {}
|
||||
takeNum(returnBool());
|
||||
""", InvalidTypeException.class);
|
||||
fun returnBool(): bool {
|
||||
return true;
|
||||
}
|
||||
fun takeNum(p: num) {}
|
||||
takeNum(returnBool());
|
||||
""", InvalidTypeException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -327,13 +328,13 @@ public class SemanticAnalyzerTest {
|
||||
|
||||
// Should not be able to use type of shadowed variable in use of shadowing variable
|
||||
testInvalid("""
|
||||
fun takesNum(p: num) {}
|
||||
var a: num = false;
|
||||
{
|
||||
var a: bool = 1;
|
||||
takesNum(a);
|
||||
}
|
||||
""", InvalidTypeException.class);
|
||||
fun takesNum(p: num) {}
|
||||
var a: num = false;
|
||||
{
|
||||
var a: bool = 1;
|
||||
takesNum(a);
|
||||
}
|
||||
""", InvalidTypeException.class);
|
||||
|
||||
// Functions can be shadowed in inner scopes
|
||||
testValid("""
|
||||
@ -348,9 +349,9 @@ public class SemanticAnalyzerTest {
|
||||
|
||||
// Functions can't be shadowed in the same immediate scope
|
||||
testInvalid("""
|
||||
fun test() {}
|
||||
fun test() {}
|
||||
""", IdentifierAlreadyDeclaredException.class);
|
||||
fun test() {}
|
||||
fun test() {}
|
||||
""", IdentifierAlreadyDeclaredException.class);
|
||||
|
||||
// Can't use function name that is already declared as a variable
|
||||
testInvalid("var id: num = 1; fun id() {}", IdentifierAlreadyDeclaredException.class);
|
||||
|
Loading…
x
Reference in New Issue
Block a user