diff --git a/common/addons/structure-terrascript-v2/build.gradle.kts b/common/addons/structure-terrascript-v2/build.gradle.kts index 0a5fbeacb..4f8057b94 100644 --- a/common/addons/structure-terrascript-v2/build.gradle.kts +++ b/common/addons/structure-terrascript-v2/build.gradle.kts @@ -30,13 +30,11 @@ data class ASTNode( val name: String, val constructorFields: List>, val mutableFields: List> = 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() diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java index e35ac2b02..2d80cbaa7 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java @@ -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 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 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 getNestedIndexes() { List 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); diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java index e772b522c..296a98512 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java @@ -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 subtypes, List 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 subtypes, List 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 { } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java index 4a4e93f7a..1fdc5e704 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java @@ -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 { diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/DynamicClassLoader.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/DynamicClassLoader.java index 7206d0d58..291b8a07b 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/DynamicClassLoader.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/DynamicClassLoader.java @@ -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); } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java index a55ff8c38..7696594aa 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java @@ -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, "", "()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 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 parameter : stmt.parameters) { + for(Pair 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); } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java index 683edf867..d75605103 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java @@ -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; diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java index 504c1a108..6b8679fdf 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java @@ -26,9 +26,8 @@ import static com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.S public class ScopeAnalyzer implements Visitor, Stmt.Visitor { - private Environment currentScope; - private final ErrorHandler errorHandler; + private Environment currentScope; public ScopeAnalyzer(Environment globalScope, ErrorHandler errorHandler) { diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java index 498f7356b..701f97369 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java @@ -43,16 +43,22 @@ public class TypeChecker implements Visitor, Stmt.Visitor 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, Stmt.Visitor 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, Stmt.Visitor 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, Stmt.Visitor 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, Stmt.Visitor 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, Stmt.Visitor 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, Stmt.Visitor "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, Stmt.Visitor 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, Stmt.Visitor @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> 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, Stmt.Visitor 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, Stmt.Visitor @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)); } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/util/ASMUtil.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/util/ASMUtil.java index c8c6034fd..d59966938 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/util/ASMUtil.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/util/ASMUtil.java @@ -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) { diff --git a/common/addons/structure-terrascript-v2/src/test/java/codegen/CodeGenTest.java b/common/addons/structure-terrascript-v2/src/test/java/codegen/CodeGenTest.java index 047b53cb1..59ea7b053 100644 --- a/common/addons/structure-terrascript-v2/src/test/java/codegen/CodeGenTest.java +++ b/common/addons/structure-terrascript-v2/src/test/java/codegen/CodeGenTest.java @@ -59,7 +59,7 @@ public class CodeGenTest { retNum(); var bln: bool = true; - + print(takesArgs("test", 3, true)); print(retStr()); diff --git a/common/addons/structure-terrascript-v2/src/test/java/lexer/LexerTest.java b/common/addons/structure-terrascript-v2/src/test/java/lexer/LexerTest.java index 07963f108..2809441e9 100644 --- a/common/addons/structure-terrascript-v2/src/test/java/lexer/LexerTest.java +++ b/common/addons/structure-terrascript-v2/src/test/java/lexer/LexerTest.java @@ -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) { diff --git a/common/addons/structure-terrascript-v2/src/test/java/lexer/LookaheadStreamTest.java b/common/addons/structure-terrascript-v2/src/test/java/lexer/LookaheadStreamTest.java index ca6d40a83..575ae098f 100644 --- a/common/addons/structure-terrascript-v2/src/test/java/lexer/LookaheadStreamTest.java +++ b/common/addons/structure-terrascript-v2/src/test/java/lexer/LookaheadStreamTest.java @@ -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)); diff --git a/common/addons/structure-terrascript-v2/src/test/java/semanticanalysis/SemanticAnalyzerTest.java b/common/addons/structure-terrascript-v2/src/test/java/semanticanalysis/SemanticAnalyzerTest.java index 36fd94eca..cf2a1dd3e 100644 --- a/common/addons/structure-terrascript-v2/src/test/java/semanticanalysis/SemanticAnalyzerTest.java +++ b/common/addons/structure-terrascript-v2/src/test/java/semanticanalysis/SemanticAnalyzerTest.java @@ -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);