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