diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 711714d..a564512 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,18 @@ build: script: - mvn package -test: +test_parsing: + image: ubuntu:rolling + stage: test + tags: + - docker + before_script: + - apt update + - apt install -y build-essential openjdk-13-jre-headless maven + script: + - make testJava + +test_compilation: image: ubuntu:rolling stage: test tags: diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000..45d7a1d --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +--add-opens java.base/java.lang=ALL-UNNAMED \ No newline at end of file diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 4fd796d..c46ad2d 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 encoding//target/generated-sources/antlr4=UTF-8 encoding/=UTF-8 diff --git a/makefile b/makefile index b6614a1..7233db6 100644 --- a/makefile +++ b/makefile @@ -13,10 +13,13 @@ eval: code.k target/klang-1.0-jar-with-dependencies.jar build: clean target/klang-1.0-jar-with-dependencies.jar target/klang-1.0-jar-with-dependencies.jar: - mvn package + mvn -Dmaven.test.skip=true package test: ./src/test/test ./src/test/test + +testJava: + mvn test ./src/test/test: ./src/test/test.s gcc -o ./src/test/test ./src/test/test.s ./src/test/**/*.c ./src/test/test.c diff --git a/pom.xml b/pom.xml index bb42969..dd5fd96 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 de.hsrm.compiler @@ -21,6 +20,16 @@ antlr4-runtime 4.7.2 + + org.junit.jupiter + junit-jupiter-api + 5.6.0 + + + org.junit.jupiter + junit-jupiter-engine + 5.6.0 + @@ -74,6 +83,14 @@ + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + \ No newline at end of file diff --git a/src/test/java/AndTest.java b/src/test/java/AndTest.java new file mode 100644 index 0000000..03f4f33 --- /dev/null +++ b/src/test/java/AndTest.java @@ -0,0 +1,20 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class AndTest { + @Test + void onlyForBool() { + ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 && 2; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:30 && is only defined for bool.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/ConstructorCallTest.java b/src/test/java/ConstructorCallTest.java new file mode 100644 index 0000000..99010bc --- /dev/null +++ b/src/test/java/ConstructorCallTest.java @@ -0,0 +1,43 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class ConstructorCallTest { + + @Test + void structNotDefined() { + ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create schwurbel(1); } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:52 Struct with name \"schwurbel\" not defined.", e.getMessage()); + } + + @Test + void numConstructorParameterMissmatch() { + ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:52 Struct \"bar\" defined 1 fields, but got 2 constructor parameters.", e.getMessage()); + } + + @Test + void constructorParameterTypeMismatch() { + ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(false); } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:63 argument 0 Type missmatch: cannot combine bool and int", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/DestroyStatementTest.java b/src/test/java/DestroyStatementTest.java new file mode 100644 index 0000000..f157d2b --- /dev/null +++ b/src/test/java/DestroyStatementTest.java @@ -0,0 +1,21 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + + +public class DestroyStatementTest { + @Test + void variableNotDefined() { + ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { destroy x; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:45 Variable with name \"x\" not defined.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/FieldAssignmentTest.java b/src/test/java/FieldAssignmentTest.java new file mode 100644 index 0000000..879e4c8 --- /dev/null +++ b/src/test/java/FieldAssignmentTest.java @@ -0,0 +1,32 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class FieldAssignmentTest { + + @Test + void variableNotDefined() { + ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { str.a = 1; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:46 Variable with name str not defined.", e.getMessage()); + } + + @Test + void fieldAssignmentOnNonStruct() { + ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; x.a = 0; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:62 Variable must reference a struct but references int.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/FunctionCallTest.java b/src/test/java/FunctionCallTest.java new file mode 100644 index 0000000..d0e5126 --- /dev/null +++ b/src/test/java/FunctionCallTest.java @@ -0,0 +1,43 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class FunctionCallTest { + + @Test + void funcNotDefined() { + ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } bar();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:34 Function with name \"bar\" not defined.", e.getMessage()); + } + + @Test + void numParameterMismatch() { + ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } foo(5);"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:34 Function \"foo\" expects 0 parameters, but got 1.", e.getMessage()); + } + + @Test + void parameterTypeMissmatch() { + ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:40 argument 0 Expected int but got: bool", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java new file mode 100644 index 0000000..442e792 --- /dev/null +++ b/src/test/java/FunctionDefinitionTest.java @@ -0,0 +1,32 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class FunctionDefinitionTest { + + @Test + void typeNotDefined() { + ParseTree tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:0 Type schwurbel not defined.", e.getMessage()); + } + + @Test + void noReturnExpression() { + ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:0 Function foo has to return something of type int.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/Helper.java b/src/test/java/Helper.java new file mode 100644 index 0000000..68b0ff6 --- /dev/null +++ b/src/test/java/Helper.java @@ -0,0 +1,36 @@ +import java.util.*; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.tree.*; + +import de.hsrm.compiler.Klang.*; +import de.hsrm.compiler.Klang.helper.*; +import de.hsrm.compiler.Klang.nodes.*; + +public class Helper { + public static ParseTree prepareParser(String input) { + CharStream inStream = CharStreams.fromString(input); + KlangLexer lexer = new KlangLexer(inStream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + KlangParser parser = new KlangParser(tokens); + return parser.parse(); + } + + public static Map getFuncs(ParseTree tree) { + var functionDefinitions = new HashMap(); + new GetFunctions(functionDefinitions).visit(tree); + return functionDefinitions; + } + + public static Set getStructNames(ParseTree tree) { + var structNames = new HashSet(); + new GetStructNames(structNames).visit(tree); + return structNames; + } + + public static Map getStructs(ParseTree tree) { + var structs = new HashMap(); + new GetStructs(getStructNames(tree), structs).visit(tree); + return structs; + } +} \ No newline at end of file diff --git a/src/test/java/ModuloTest.java b/src/test/java/ModuloTest.java new file mode 100644 index 0000000..c11a87f --- /dev/null +++ b/src/test/java/ModuloTest.java @@ -0,0 +1,20 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class ModuloTest { + @Test + void onlyForInt() { + ParseTree tree = Helper.prepareParser("function foo(): float { return 1.0 % 2.3; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:31 Only integers are allowed for modulo.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/OrTest.java b/src/test/java/OrTest.java new file mode 100644 index 0000000..07dfc33 --- /dev/null +++ b/src/test/java/OrTest.java @@ -0,0 +1,21 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class OrTest { + + @Test + void onlyForBool() { + ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 || 2; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:30 || is only defined for bool.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/ParameterTest.java b/src/test/java/ParameterTest.java new file mode 100644 index 0000000..9342ccc --- /dev/null +++ b/src/test/java/ParameterTest.java @@ -0,0 +1,14 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +public class ParameterTest { + @Test + void typeNotDefined() { + ParseTree tree = Helper.prepareParser("struct test { a: schwurbel; } function foo(): int { return 1; } foo();"); + Exception e = assertThrows(RuntimeException.class, () -> Helper.getStructs(tree)); + assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/StructFieldAccessTest.java b/src/test/java/StructFieldAccessTest.java new file mode 100644 index 0000000..79b9766 --- /dev/null +++ b/src/test/java/StructFieldAccessTest.java @@ -0,0 +1,31 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class StructFieldAccessTest { + @Test + void variableNotDefined() { + ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return str.a; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:53 Variable with name str not defined.", e.getMessage()); + } + + @Test + void fieldAssignmentOnNonStruct() { + ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; return x.a; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:69 Variable must reference a struct but references int.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/VariableAssignmentTest.java b/src/test/java/VariableAssignmentTest.java new file mode 100644 index 0000000..faa338d --- /dev/null +++ b/src/test/java/VariableAssignmentTest.java @@ -0,0 +1,21 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class VariableAssignmentTest { + + @Test + void variableNotDefined() { + ParseTree tree = Helper.prepareParser("function foo(): int { x = 1; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:22 Variable with name \"x\" not defined.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/VariableDeclarationTest.java b/src/test/java/VariableDeclarationTest.java new file mode 100644 index 0000000..a52ea2e --- /dev/null +++ b/src/test/java/VariableDeclarationTest.java @@ -0,0 +1,32 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class VariableDeclarationTest { + @Test + void typeNotDefined() { + ParseTree tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:22 Type unk not defined.", e.getMessage()); + } + + @Test + void variableRedeclaration() + { + ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:34 Redeclaration of variable with name \"x\".", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/VariableTest.java b/src/test/java/VariableTest.java new file mode 100644 index 0000000..763226c --- /dev/null +++ b/src/test/java/VariableTest.java @@ -0,0 +1,32 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import de.hsrm.compiler.Klang.ContextAnalysis; + +public class VariableTest { + + @Test + void variableNotDefined() { + ParseTree tree = Helper.prepareParser("function foo(): int { return x; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:29 Variable with name \"x\" not defined.", e.getMessage()); + } + + @Test + void variableNotInitialized() { + ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; return x; } foo();"); + var funcs = Helper.getFuncs(tree); + var structs = Helper.getStructs(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + + Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:41 Variable with name \"x\" has not been initialized.", e.getMessage()); + } +} \ No newline at end of file