Autoformat

This commit is contained in:
Astrash 2023-10-27 14:24:42 +11:00
parent 9a94b26126
commit 5f3a2bb579
14 changed files with 260 additions and 231 deletions

View File

@ -30,13 +30,11 @@ data class ASTNode(
val name: String,
val constructorFields: List<Pair<String, String>>,
val mutableFields: List<Pair<String, String>> = emptyList() // TODO - Remove mutability from AST nodes
)
// Auto generate AST classes rather than writing them by hand
tasks.register("genTerrascriptAstClasses") {
val packageName = astPackage.toRelativeString(astSourceSet).replace('/', '.')
fun generateClass(clazz: ASTClass) {
val src = StringBuilder()

View File

@ -13,19 +13,13 @@ import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction;
public class Environment {
private final Environment outer;
private final boolean canAccessOuterVariables;
private final Map<String, Symbol> symbolTable = new HashMap<>();
private final boolean inLoop;
private final int index;
private int innerCount = 0;
public final String name;
private final Environment outer;
private final boolean canAccessOuterVariables;
private final Map<String, Symbol> symbolTable = new HashMap<>();
private final boolean inLoop;
private final int index;
private int innerCount = 0;
private Environment(@Nullable Environment outer, boolean canAccessOuterVariables, boolean inLoop, int index) {
this.outer = outer;
@ -35,8 +29,11 @@ public class Environment {
this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList());
// Populate global scope with built-in Java implemented methods
// TODO - Replace with AST import nodes
if (index == 0) NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) ->
symbolTable.put(name, new Symbol.Variable(new Type.Function.Native(function.getReturnType(), function.getParameterTypes(), name, this, function))));
if(index == 0) NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) ->
symbolTable.put(name, new Symbol.Variable(
new Type.Function.Native(function.getReturnType(),
function.getParameterTypes(), name,
this, function))));
}
public static Environment global() {
@ -57,7 +54,7 @@ public class Environment {
private List<Integer> getNestedIndexes() {
List<Integer> idxs = new ArrayList<>();
for (Environment env = this; env.outer != null; env = env.outer) {
for(Environment env = this; env.outer != null; env = env.outer) {
idxs.add(0, env.index);
}
return idxs;
@ -78,7 +75,7 @@ public class Environment {
*
* @return variable symbol table entry
*
* @throws NonexistentSymbolException if symbol is not declared in symbol table
* @throws NonexistentSymbolException if symbol is not declared in symbol table
*/
public Symbol getVariable(String id) throws NonexistentSymbolException {
Symbol symbol = symbolTable.get(id);

View File

@ -12,6 +12,89 @@ import com.dfsek.terra.api.util.generic.pair.Pair;
public interface Type {
Type NUMBER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return double.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.DOUBLE;
}
@Override
public String toString() {
return "num";
}
};
Type INTEGER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return int.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.INTEGER;
}
@Override
public String toString() {
return "int";
}
};
Type STRING = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return String.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.STRING;
}
@Override
public String toString() {
return "str";
}
};
Type BOOLEAN = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return boolean.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.BOOLEAN;
}
@Override
public String toString() {
return "bool";
}
};
Type VOID = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return void.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.VOID;
}
@Override
public String toString() {
return "()";
}
};
static Type fromString(String lexeme) throws TypeException {
return switch(lexeme) {
case "num" -> NUMBER;
@ -31,6 +114,7 @@ public interface Type {
CodegenType getCodegenType();
class Function implements Type {
private final Type returnType;
@ -43,6 +127,11 @@ public interface Type {
this.id = identifier == null ? "ANONYMOUS" : identifier + declarationScope.name;
}
private static boolean paramsAreSubtypes(List<Type> subtypes, List<Type> superTypes) {
if(subtypes.size() != superTypes.size()) return false;
return Streams.zip(subtypes.stream(), superTypes.stream(), Pair::of).allMatch(p -> p.getLeft().typeOf(p.getRight()));
}
public Type getReturnType() {
return returnType;
}
@ -66,11 +155,6 @@ public interface Type {
return CodegenType.OBJECT;
}
private static boolean paramsAreSubtypes(List<Type> subtypes, List<Type> superTypes) {
if(subtypes.size() != superTypes.size()) return false;
return Streams.zip(subtypes.stream(), superTypes.stream(), Pair::of).allMatch(p -> p.getLeft().typeOf(p.getRight()));
}
@Override
public java.lang.reflect.Type javaType() {
return Function.class;
@ -96,92 +180,6 @@ public interface Type {
}
}
Type NUMBER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return double.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.DOUBLE;
}
@Override
public String toString() {
return "num";
}
};
Type INTEGER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return int.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.INTEGER;
}
@Override
public String toString() {
return "int";
}
};
Type STRING = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return String.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.STRING;
}
@Override
public String toString() {
return "str";
}
};
Type BOOLEAN = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return boolean.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.BOOLEAN;
}
@Override
public String toString() {
return "bool";
}
};
Type VOID = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return void.class;
}
@Override
public CodegenType getCodegenType() {
return CodegenType.VOID;
}
@Override
public String toString() {
return "()";
}
};
class TypeException extends Exception {
}

View File

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

View File

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

View File

@ -50,24 +50,24 @@ import com.dfsek.terra.api.util.generic.pair.Pair;
import static com.dfsek.terra.addons.terrascript.v2.util.ASMUtil.dynamicName;
public class TerraScriptClassGenerator {
private static final Class<?> TARGET_CLASS = TerraScript.class;
private static final boolean DUMP = true;
private int generationCount = 0;
private final String debugPath;
private int generationCount = 0;
public TerraScriptClassGenerator(String debugPath) {
this.debugPath = debugPath;
}
/**
*
* @param root Assumed to be semantically correct
*
* @return Generated TerraScript instance
*
* @throws IOException
*/
public TerraScript generate(Block root) throws IOException {
@ -78,7 +78,7 @@ public class TerraScriptClassGenerator {
// Create class
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, generatedClassName, null, "java/lang/Object", new String[]{ targetClassName });
// Generate constructor method
MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
@ -97,7 +97,8 @@ public class TerraScriptClassGenerator {
int exeAcc = Opcodes.ACC_PUBLIC;
MethodVisitor executeMethod = classWriter.visitMethod(exeAcc, methodName, description, null, null);
executeMethod.visitCode(); // Start method body
new MethodBytecodeGenerator(classWriter, generatedClassName, executeMethod, exeAcc, description).generate(root); // Generate bytecode
new MethodBytecodeGenerator(classWriter, generatedClassName, executeMethod, exeAcc, description).generate(
root); // Generate bytecode
// Finish up method
executeMethod.visitInsn(Opcodes.RETURN);
executeMethod.visitMaxs(0, 0);
@ -106,7 +107,9 @@ public class TerraScriptClassGenerator {
// Finished generating class
classWriter.visitEnd();
DynamicClassLoader loader = new DynamicClassLoader(TARGET_CLASS); // Instantiate a new loader every time so classes can be GC'ed when they are no longer used. (Classes cannot be GC'ed until their loaders are).
DynamicClassLoader loader = new DynamicClassLoader(
TARGET_CLASS); // Instantiate a new loader every time so classes can be GC'ed when they are no longer used. (Classes
// cannot be GC'ed until their loaders are).
generationCount++;
@ -114,7 +117,7 @@ public class TerraScriptClassGenerator {
Class<?> generatedClass = loader.defineClass(generatedClassName.replace('/', '.'), bytecode);
if (DUMP) {
if(DUMP) {
File dump = new File(debugPath + "/" + generatedClass.getSimpleName() + ".class");
dump.getParentFile().mkdirs();
try(FileOutputStream out = new FileOutputStream(dump)) {
@ -156,6 +159,13 @@ public class TerraScriptClassGenerator {
this.lvs = new LocalVariablesSorter(access, descriptor, method);
}
private static boolean exprTypesEqual(Type type, TypedExpr... exprs) {
for(TypedExpr expr : exprs) {
if(expr.type != type) return false;
}
return true;
}
public void generate(Block root) {
this.visitBlockTypedStmt(root);
}
@ -169,9 +179,10 @@ public class TerraScriptClassGenerator {
CodegenType codegenType = expr.type.getCodegenType();
if(codegenType.bytecodeType() == InstructionType.DOUBLE)
method.visitInsn(Opcodes.DADD);
else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
else if(Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
// TODO - Optimize string concatenation
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat",
"(Ljava/lang/String;)Ljava/lang/String;", false);
else throw new RuntimeException("Could not generate bytecode for ADD binary operator returning type " + expr.type);
}
case SUBTRACT -> binaryInsn(expr, Opcodes.DSUB);
@ -191,7 +202,8 @@ public class TerraScriptClassGenerator {
@Override
public Void visitLiteralTypedExpr(Literal expr) {
if(expr.type.getCodegenType() == CodegenType.BOOLEAN)
if ((boolean) expr.value) pushTrue(); else pushFalse();
if((boolean) expr.value) pushTrue();
else pushFalse();
else method.visitLdcInsn(expr.value);
return null;
}
@ -199,7 +211,7 @@ public class TerraScriptClassGenerator {
@Override
public Void visitUnaryTypedExpr(Unary expr) {
expr.operand.accept(this);
switch (expr.operator) {
switch(expr.operator) {
case NOT -> invertBool();
case NEGATE -> method.visitInsn(Opcodes.DNEG);
}
@ -209,7 +221,7 @@ public class TerraScriptClassGenerator {
@Override
public Void visitCallTypedExpr(Call expr) {
// TODO - Remove specific handling of native functions
if (expr.callee.type instanceof Type.Function.Native nativeFunction) {
if(expr.callee.type instanceof Type.Function.Native nativeFunction) {
NativeFunction function = nativeFunction.getNativeFunction();
function.pushInstance(method);
expr.arguments.forEach(a -> a.accept(this));
@ -219,7 +231,8 @@ public class TerraScriptClassGenerator {
// TODO - Add support for invokevirtual
expr.arguments.forEach(a -> a.accept(this));
List<Type> parameters = expr.arguments.stream().map(e -> e.type).toList();
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, ((Type.Function) expr.callee.type).getId(), getFunctionDescriptor(parameters, expr.type), false);
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, ((Type.Function) expr.callee.type).getId(),
getFunctionDescriptor(parameters, expr.type), false);
return null;
}
@ -257,7 +270,9 @@ public class TerraScriptClassGenerator {
/**
* Writes function as a private static method of the current class
*
* @param stmt
*
* @return
*/
@Override
@ -266,7 +281,8 @@ public class TerraScriptClassGenerator {
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
MethodVisitor method = classWriter.visitMethod(access, stmt.scopedIdentifier, getFunctionDescriptor(parameterTypes, stmt.returnType), null, null);
MethodVisitor method = classWriter.visitMethod(access, stmt.scopedIdentifier,
getFunctionDescriptor(parameterTypes, stmt.returnType), null, null);
method.visitCode(); // Start method body
@ -274,7 +290,7 @@ public class TerraScriptClassGenerator {
// Add local variable indexes for each parameter
int lvidx = 0;
for (Pair<String, Type> parameter : stmt.parameters) {
for(Pair<String, Type> parameter : stmt.parameters) {
funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
lvidx += parameter.getRight().getCodegenType().bytecodeType().slotSize(); // Increment by how many slots data type takes
}
@ -370,13 +386,6 @@ public class TerraScriptClassGenerator {
return exprTypesEqual(type, expr.left, expr.right);
}
private static boolean exprTypesEqual(Type type, TypedExpr... exprs) {
for(TypedExpr expr : exprs) {
if (expr.type != type) return false;
}
return true;
}
/**
* Inverts a boolean on the stack
*/
@ -406,6 +415,7 @@ public class TerraScriptClassGenerator {
/**
* Pushes boolean on to the stack based on comparison result
*
* @param condition
*/
private void pushComparisonBool(TypedExpr condition) {
@ -417,6 +427,7 @@ public class TerraScriptClassGenerator {
/**
* Executes a statement then jumps to the exit label if the condition is true, jumps over the statement if false
*
* @param condition
* @param stmt
* @param exit
@ -447,7 +458,7 @@ public class TerraScriptClassGenerator {
private void conditionalRunnable(TypedExpr condition, Runnable trueBlock, Label trueFinished) {
Label exit = new Label(); // If the first conditional is false, jump over statement and don't execute it
if (condition instanceof Binary binaryCondition) {
if(condition instanceof Binary binaryCondition) {
switch(binaryCondition.operator) {
case BOOLEAN_AND -> {
// Operands assumed booleans
@ -466,30 +477,30 @@ public class TerraScriptClassGenerator {
label(skipRight);
}
case EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
if(binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition);
jumpIf(OpcodeAlias.INTEGERS_NOT_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
} else if(binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
binaryInsn(binaryCondition, Opcodes.DCMPG);
jumpIf(OpcodeAlias.CMP_NOT_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) {
} else if(binaryOperandsSameType(Type.STRING, binaryCondition)) {
pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
jumpIf(OpcodeAlias.BOOL_FALSE, exit);
} else throw new CompilerBugException();
}
case NOT_EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
if(binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition);
jumpIf(OpcodeAlias.INTEGERS_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
} else if(binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
binaryInsn(binaryCondition, Opcodes.DCMPG);
jumpIf(OpcodeAlias.CMP_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
} else if(binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
invertBool();
@ -533,6 +544,7 @@ public class TerraScriptClassGenerator {
}
}
private static class MethodExtractor extends ClassVisitor {
private final String methodName;
@ -545,7 +557,7 @@ public class TerraScriptClassGenerator {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals(methodName))
if(name.equals(methodName))
methodDescription = descriptor;
return super.visitMethod(access, name, descriptor, signature, exceptions);
}

View File

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

View File

@ -26,9 +26,8 @@ import static com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.S
public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
private Environment currentScope;
private final ErrorHandler errorHandler;
private Environment currentScope;
public ScopeAnalyzer(Environment globalScope, ErrorHandler errorHandler) {

View File

@ -43,16 +43,22 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
Type type = switch(expr.operator) {
case BOOLEAN_OR, BOOLEAN_AND -> {
if(!leftType.typeOf(Type.BOOLEAN) || !rightType.typeOf(Type.BOOLEAN))
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.BOOLEAN + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
errorHandler.add(new InvalidTypeException(
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.BOOLEAN + "', found types '" +
leftType + "' and '" + rightType + "'", expr.position));
yield Type.BOOLEAN;
}
case EQUALS, NOT_EQUALS -> {
if(!leftType.typeOf(rightType)) errorHandler.add(new InvalidTypeException("Both operands of equality operator (==) must be of the same type, found mismatched types '" + leftType + "' and '" + rightType + "'", expr.position));
if(!leftType.typeOf(rightType)) errorHandler.add(new InvalidTypeException(
"Both operands of equality operator (==) must be of the same type, found mismatched types '" + leftType +
"' and '" + rightType + "'", expr.position));
yield Type.BOOLEAN;
}
case GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> {
if(!leftType.typeOf(Type.NUMBER) || !rightType.typeOf(Type.NUMBER))
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
errorHandler.add(new InvalidTypeException(
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" +
leftType + "' and '" + rightType + "'", expr.position));
yield Type.BOOLEAN;
}
case ADD -> {
@ -62,12 +68,16 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
if(leftType.typeOf(Type.STRING) || rightType.typeOf(Type.STRING)) {
yield Type.STRING;
}
errorHandler.add(new InvalidTypeException("Addition operands must be either both of type '" + Type.NUMBER + "', or one of type '" + Type.STRING + "'", expr.position));
errorHandler.add(new InvalidTypeException(
"Addition operands must be either both of type '" + Type.NUMBER + "', or one of type '" + Type.STRING + "'",
expr.position));
yield Type.VOID;
}
case SUBTRACT, MULTIPLY, DIVIDE, MODULO -> {
if(!leftType.typeOf(Type.NUMBER) || !rightType.typeOf(Type.NUMBER))
errorHandler.add(new InvalidTypeException("Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" + leftType + "' and '" + rightType + "'", expr.position));
errorHandler.add(new InvalidTypeException(
"Both operands of '" + expr.operator + "' operator must be of type '" + Type.NUMBER + "', found types '" +
leftType + "' and '" + rightType + "'", expr.position));
yield Type.NUMBER;
}
};
@ -106,7 +116,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
TypedExpr function = expr.callee.accept(this);
if(!(function.type instanceof Type.Function functionType)) {
errorHandler.add(new InvalidCalleeException("Cannot call type '" + function.type + "', only functions can be called", expr.position));
errorHandler.add(
new InvalidCalleeException("Cannot call type '" + function.type + "', only functions can be called", expr.position));
return new TypedExpr.Void(Type.VOID);
}
@ -115,7 +126,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
if(arguments.size() != parameters.size())
errorHandler.add(new InvalidArgumentsException(
"Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments", expr.position));
"Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments",
expr.position));
for(int i = 0; i < parameters.size(); i++) {
Type expectedType = parameters.get(i);
@ -142,7 +154,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
String id = expr.lValue.identifier;
if(!right.type.typeOf(expected))
errorHandler.add(new InvalidTypeException(
"Cannot assign variable '" + id + "' to value of type '" + right.type + "', '" + id + "' is declared with type '" + expected + "'",
"Cannot assign variable '" + id + "' to value of type '" + right.type + "', '" + id + "' is declared with type '" +
expected + "'",
expr.position));
return new TypedExpr.Assignment(left, right, right.type);
}
@ -171,7 +184,8 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
new InvalidFunctionDeclarationException("Function body for '" + stmt.identifier + "' does not contain return statement",
stmt.position));
}
return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body, ((Type.Function) stmt.getSymbol().type).getId());
return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body,
((Type.Function) stmt.getSymbol().type).getId());
}
private boolean alwaysReturns(TypedStmt stmt, Stmt.FunctionDeclaration function) {
@ -181,11 +195,12 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
"Return statement must match function's return type. Function '" + function.identifier + "' expects " +
function.returnType + ", found " + ret.value.type + " instead", function.position));
return true;
} else if (stmt instanceof TypedStmt.If ifStmt) {
} else if(stmt instanceof TypedStmt.If ifStmt) {
return alwaysReturns(ifStmt.trueBody, function) &&
ifStmt.elseIfClauses.stream().map(Pair::getRight).allMatch(s -> alwaysReturns(s, function)) &&
ifStmt.elseBody.map(body -> alwaysReturns(body, function)).orElse(false); // If else body is not defined then statement does not always return
} else if (stmt instanceof TypedStmt.Block block) {
ifStmt.elseBody.map(body -> alwaysReturns(body, function)).orElse(
false); // If else body is not defined then statement does not always return
} else if(stmt instanceof TypedStmt.Block block) {
return block.statements.stream().anyMatch(s -> alwaysReturns(s, function));
}
return false;
@ -196,8 +211,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
TypedExpr value = stmt.value.accept(this);
if(!stmt.type.typeOf(value.type))
errorHandler.add(new InvalidTypeException(
"Type of value assigned to variable '" + stmt.identifier + "' does not match variable's declared type. Expected type '" +
stmt.type + "', found '" + value.type +"' instead", stmt.position));
"Type of value assigned to variable '" + stmt.identifier +
"' does not match variable's declared type. Expected type '" +
stmt.type + "', found '" + value.type + "' instead", stmt.position));
return new TypedStmt.VariableDeclaration(stmt.type, stmt.identifier, value);
}
@ -209,12 +225,15 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
@Override
public TypedStmt visitIfStmt(Stmt.If stmt) {
TypedExpr condition = stmt.condition.accept(this);
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("If statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
"If statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
TypedStmt.Block trueBody = (TypedStmt.Block) stmt.trueBody.accept(this);
List<Pair<TypedExpr, TypedStmt.Block>> elseIfClauses = stmt.elseIfClauses.stream().map(c -> {
TypedExpr clauseCondition = c.getLeft().accept(this);
if (!clauseCondition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("Else if clause conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
if(!clauseCondition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
"Else if clause conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
stmt.position));
return Pair.of(clauseCondition, (TypedStmt.Block) c.getRight().accept(this));
}).toList();
@ -227,7 +246,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
public TypedStmt visitForStmt(Stmt.For stmt) {
TypedStmt initializer = stmt.initializer.accept(this);
TypedExpr condition = stmt.condition.accept(this);
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("For statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
"For statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
stmt.position));
TypedExpr incrementer = stmt.incrementer.accept(this);
return new TypedStmt.For(initializer, condition, incrementer, (TypedStmt.Block) stmt.body.accept(this));
}
@ -235,7 +256,9 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
@Override
public TypedStmt visitWhileStmt(Stmt.While stmt) {
TypedExpr condition = stmt.condition.accept(this);
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException("While statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.", stmt.position));
if(!condition.type.typeOf(Type.BOOLEAN)) errorHandler.add(new InvalidTypeException(
"While statement conditional must be of type '" + Type.BOOLEAN + "', found '" + condition.type + "' instead.",
stmt.position));
return new TypedStmt.While(condition, (TypedStmt.Block) stmt.body.accept(this));
}

View File

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

View File

@ -59,7 +59,7 @@ public class CodeGenTest {
retNum();
var bln: bool = true;
print(takesArgs("test", 3, true));
print(retStr());

View File

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

View File

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

View File

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