diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5090a1a..329ff2a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,10 +26,15 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") testImplementation("org.junit.jupiter:junit-jupiter-params") + implementation("io.arrow-kt:arrow-core:1.2.1") + + testImplementation("io.mockk:mockk:1.13.10") + testImplementation("org.assertj:assertj-core:3.25.1") } tasks { compileJavacc { + dependsOn(compileKotlin) inputDirectory = file("src/main/javacc") outputDirectory = file(layout.buildDirectory.dir("generated/javacc")) } @@ -45,6 +50,9 @@ sourceSets { java { srcDir(file(layout.buildDirectory.dir("generated/javacc"))) } + kotlin { + srcDir("src/main/kotlin") + } } } diff --git a/app/src/main/java/org/example/ast/ClassDeclExtends.java b/app/src/main/java/org/example/ast/ClassDeclExtends.java index ba7a548..1dbbb63 100644 --- a/app/src/main/java/org/example/ast/ClassDeclExtends.java +++ b/app/src/main/java/org/example/ast/ClassDeclExtends.java @@ -12,12 +12,12 @@ @Builder @AllArgsConstructor public class ClassDeclExtends extends ClassDecl { - private Identifier className; - private Identifier parent; + public Identifier className; + public Identifier parent; @Builder.Default - private VarDeclList fields = new VarDeclList(); + public VarDeclList fields = new VarDeclList(); @Builder.Default - private MethodDeclList methods = new MethodDeclList(); + public MethodDeclList methods = new MethodDeclList(); @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/ClassDeclList.java b/app/src/main/java/org/example/ast/ClassDeclList.java index 2df0cb8..52eb5a2 100644 --- a/app/src/main/java/org/example/ast/ClassDeclList.java +++ b/app/src/main/java/org/example/ast/ClassDeclList.java @@ -11,7 +11,7 @@ @EqualsAndHashCode public class ClassDeclList { @Builder.Default - private ArrayList classDecls = new ArrayList<>(); + public ArrayList classDecls = new ArrayList<>(); public void addClassDecl(ClassDecl classDecl) { classDecls.add(classDecl); diff --git a/app/src/main/java/org/example/ast/ClassDeclSimple.java b/app/src/main/java/org/example/ast/ClassDeclSimple.java index 26b0ebc..3e5c851 100644 --- a/app/src/main/java/org/example/ast/ClassDeclSimple.java +++ b/app/src/main/java/org/example/ast/ClassDeclSimple.java @@ -12,11 +12,11 @@ @Builder @AllArgsConstructor public class ClassDeclSimple extends ClassDecl { - private Identifier className; + public Identifier className; @Builder.Default - private VarDeclList fields = new VarDeclList(); + public VarDeclList fields = new VarDeclList(); @Builder.Default - private MethodDeclList methods = new MethodDeclList(); + public MethodDeclList methods = new MethodDeclList(); @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/ExpressionList.java b/app/src/main/java/org/example/ast/ExpressionList.java index 68b8851..100a4d3 100644 --- a/app/src/main/java/org/example/ast/ExpressionList.java +++ b/app/src/main/java/org/example/ast/ExpressionList.java @@ -11,7 +11,7 @@ @AllArgsConstructor @NoArgsConstructor public class ExpressionList { - private ArrayList list = new ArrayList<>(); + public ArrayList list = new ArrayList<>(); public void addExpression(Expression expression) { list.add(expression); diff --git a/app/src/main/java/org/example/ast/Formal.java b/app/src/main/java/org/example/ast/Formal.java index 9239d51..70085b8 100644 --- a/app/src/main/java/org/example/ast/Formal.java +++ b/app/src/main/java/org/example/ast/Formal.java @@ -12,8 +12,8 @@ @Builder @AllArgsConstructor public class Formal extends Node { - private Type type; - private String name; + public Type type; + public String name; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/FormalList.java b/app/src/main/java/org/example/ast/FormalList.java index 092ed33..02d1276 100644 --- a/app/src/main/java/org/example/ast/FormalList.java +++ b/app/src/main/java/org/example/ast/FormalList.java @@ -11,7 +11,7 @@ @EqualsAndHashCode public class FormalList { @Builder.Default - private ArrayList formals = new ArrayList<>(); + public ArrayList formals = new ArrayList<>(); public void addFormal(Formal formal) { formals.add(formal); diff --git a/app/src/main/java/org/example/ast/Identifier.java b/app/src/main/java/org/example/ast/Identifier.java index d0315ec..59fbab8 100644 --- a/app/src/main/java/org/example/ast/Identifier.java +++ b/app/src/main/java/org/example/ast/Identifier.java @@ -1,9 +1,6 @@ package org.example.ast; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; +import lombok.*; import org.example.visitor.ASTVisitor; import org.example.visitor.TypeVisitor; @@ -11,8 +8,9 @@ @ToString @Data @AllArgsConstructor +@Builder public class Identifier extends Expression { - private String s; + public String s; @Override public void accept(ASTVisitor v) { v.visit(this); diff --git a/app/src/main/java/org/example/ast/IdentifierExpression.java b/app/src/main/java/org/example/ast/IdentifierExpression.java index 9c6d807..91e7287 100644 --- a/app/src/main/java/org/example/ast/IdentifierExpression.java +++ b/app/src/main/java/org/example/ast/IdentifierExpression.java @@ -12,7 +12,7 @@ @Builder @AllArgsConstructor public class IdentifierExpression extends Expression { - private String id; + public String id; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/IdentifierType.java b/app/src/main/java/org/example/ast/IdentifierType.java index 1798dbe..9bfe3ed 100644 --- a/app/src/main/java/org/example/ast/IdentifierType.java +++ b/app/src/main/java/org/example/ast/IdentifierType.java @@ -10,7 +10,7 @@ @Data @AllArgsConstructor public class IdentifierType extends Type { - private String s; + public String s; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/IntegerType.java b/app/src/main/java/org/example/ast/IntegerType.java index d71367a..339f9f4 100644 --- a/app/src/main/java/org/example/ast/IntegerType.java +++ b/app/src/main/java/org/example/ast/IntegerType.java @@ -1,5 +1,6 @@ package org.example.ast; +import lombok.Builder; import lombok.EqualsAndHashCode; import org.example.visitor.ASTVisitor; import org.example.visitor.TypeVisitor; diff --git a/app/src/main/java/org/example/ast/MainClass.java b/app/src/main/java/org/example/ast/MainClass.java index 37a8eee..aaab08b 100644 --- a/app/src/main/java/org/example/ast/MainClass.java +++ b/app/src/main/java/org/example/ast/MainClass.java @@ -12,9 +12,9 @@ @Builder @AllArgsConstructor public class MainClass extends Node { - private Identifier className; - private Identifier argsName; - private StatementList statements; + public Identifier className; + public Identifier argsName; + public StatementList statements; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/MethodDecl.java b/app/src/main/java/org/example/ast/MethodDecl.java index 8f10193..a236bee 100644 --- a/app/src/main/java/org/example/ast/MethodDecl.java +++ b/app/src/main/java/org/example/ast/MethodDecl.java @@ -12,12 +12,12 @@ @Builder @AllArgsConstructor public class MethodDecl extends Node { - private Type type; - private String identifier; - private FormalList formals; - private VarDeclList varDecls; - private StatementList statements; - private Expression returnExpression; + public Type type; + public String identifier; + public FormalList formals; + public VarDeclList varDecls; + public StatementList statements; + public Expression returnExpression; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/MethodDeclList.java b/app/src/main/java/org/example/ast/MethodDeclList.java index abb5c83..6408840 100644 --- a/app/src/main/java/org/example/ast/MethodDeclList.java +++ b/app/src/main/java/org/example/ast/MethodDeclList.java @@ -11,7 +11,7 @@ @EqualsAndHashCode public class MethodDeclList { @Builder.Default - private ArrayList methodDecls = new ArrayList<>(); + public ArrayList methodDecls = new ArrayList<>(); public void addMethodDecl(MethodDecl methodDecl) { methodDecls.add(methodDecl); diff --git a/app/src/main/java/org/example/ast/Program.java b/app/src/main/java/org/example/ast/Program.java index d1a4a5f..f6dec84 100644 --- a/app/src/main/java/org/example/ast/Program.java +++ b/app/src/main/java/org/example/ast/Program.java @@ -12,8 +12,8 @@ @Builder @AllArgsConstructor public class Program extends Node { - private MainClass mainClass; - private ClassDeclList classes; + public MainClass mainClass; + public ClassDeclList classes; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/Type.java b/app/src/main/java/org/example/ast/Type.java index dacbe82..7c7ee37 100644 --- a/app/src/main/java/org/example/ast/Type.java +++ b/app/src/main/java/org/example/ast/Type.java @@ -1,4 +1,6 @@ package org.example.ast; +import lombok.Builder; + public abstract class Type extends Node { } diff --git a/app/src/main/java/org/example/ast/VarDecl.java b/app/src/main/java/org/example/ast/VarDecl.java index d7a088f..07d0ce3 100644 --- a/app/src/main/java/org/example/ast/VarDecl.java +++ b/app/src/main/java/org/example/ast/VarDecl.java @@ -12,8 +12,8 @@ @Builder @AllArgsConstructor public class VarDecl extends Node { - private Type type; - private String name; + public Type type; + public String name; @Override public void accept(ASTVisitor v) { diff --git a/app/src/main/java/org/example/ast/VarDeclList.java b/app/src/main/java/org/example/ast/VarDeclList.java index 046d6b3..2609de4 100644 --- a/app/src/main/java/org/example/ast/VarDeclList.java +++ b/app/src/main/java/org/example/ast/VarDeclList.java @@ -13,7 +13,7 @@ @NoArgsConstructor public class VarDeclList { @Builder.Default - private ArrayList varDecls = new ArrayList<>(); + public ArrayList varDecls = new ArrayList<>(); public void addVarDecl(VarDecl varDecl) { varDecls.add(varDecl); diff --git a/app/src/main/java/org/example/visitor/ASTVisitor.java b/app/src/main/java/org/example/visitor/ASTVisitor.java deleted file mode 100644 index e29852c..0000000 --- a/app/src/main/java/org/example/visitor/ASTVisitor.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.example.visitor; - -public interface ASTVisitor extends Visitor{ -} diff --git a/app/src/main/kotlin/org/example/visitor/ClassDeclVisitor.kt b/app/src/main/kotlin/org/example/visitor/ClassDeclVisitor.kt new file mode 100644 index 0000000..1f892e0 --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/ClassDeclVisitor.kt @@ -0,0 +1,62 @@ +package org.example.visitor + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import org.example.ast.* +import org.example.ast2.org.example.visitor.MethodDeclListVisitor +import org.example.visitor.SymbolVisitor.Companion.dispatch + +object ClassDeclListVisitor : SymbolVisitor { + override fun Table.visit(entity: ClassDeclList): Either = + ClassDeclVisitor.fold( + entity.classDecls.toList() + ) { dispatch(it) } +} + +object ClassDeclVisitor : SymbolVisitor { + private fun extractName(entity: ClassDecl): String = + when (entity) { + is ClassDeclSimple -> entity.className.s + is ClassDeclExtends -> entity.className.s + else -> throw IllegalArgumentException( + "ClassDeclVisitor: ClassDecl must be either ClassDeclSimple or ClassDeclExtends" + ) + } + + object ClassDeclSimpleVisitor : SymbolVisitor { + override fun Table.visit(entity: ClassDeclSimple): Either = either { + this@visit + Table( + ClassData( + name = extractName(entity), + fields = dispatch(entity.fields).bind(), + methods = dispatch(entity.methods).bind() + ) + ) + } + } + + object ClassDeclExtendsVisitor : SymbolVisitor { + override fun Table.visit(entity: ClassDeclExtends): Either = either { + this@visit + Table( + ClassData( + name = extractName(entity), + fields = dispatch(entity.fields).bind(), + methods = dispatch(entity.methods).bind() + ) + ) + } + } + + override fun Table.visit(entity: ClassDecl): Either = either { + val name = extractName(entity) + + ensure(!this@visit.contains(name)) { + Error("ClassDeclVisitor: ClassDecl must have a unique name") + } + + val newTable = dispatch(entity, table = this@visit).bind() + + this@visit + newTable + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/example/visitor/MainClassVisitor.kt b/app/src/main/kotlin/org/example/visitor/MainClassVisitor.kt new file mode 100644 index 0000000..396d60c --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/MainClassVisitor.kt @@ -0,0 +1,42 @@ +package org.example.visitor + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import org.example.ast.IntegerLiteral +import org.example.ast.IntegerType +import org.example.ast.MainClass + +object MainClassVisitor : SymbolVisitor { + override fun Table.visit(entity: MainClass): Either = either { + ensure(!contains(entity.className.s)) { + Error("MainClassVisitor: MainClass must have a unique name") + } + ensure(entity.argsName.s == "args") { + Error("MainClassVisitor: MainClass args must be named 'args'") + } + + Table( + ClassData( + name = entity.className.s, + fields = Table(), + methods = Table( + MethodData( + name = entity.className.s, + args = Table( + ParamData( + name = entity.argsName.s, + type = IntegerType() + ) + ), + varDeclList = Table( + + ), + ) + ) + ) + ) + } + + +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/example/visitor/MethodDeclVisitor.kt b/app/src/main/kotlin/org/example/visitor/MethodDeclVisitor.kt new file mode 100644 index 0000000..75364c2 --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/MethodDeclVisitor.kt @@ -0,0 +1,63 @@ +package org.example.ast2.org.example.visitor + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import org.example.ast.Formal +import org.example.ast.FormalList +import org.example.ast.MethodDecl +import org.example.ast.MethodDeclList +import org.example.visitor.* +import org.example.visitor.SymbolVisitor.Companion.dispatch + + +object FormalsListVisitor : SymbolVisitor { + override fun Table.visit(entity: FormalList): Either = + with(FormalsVisitor) { + fold( + entity.formals.toList(), + ::dispatch + ) + } +} + +object FormalsVisitor : SymbolVisitor { + override fun Table.visit(entity: Formal): Either = either { + ensure(!contains(entity.name)) { + Error("FormalsVisitor: Formals must have unique names") + } + + Table( + FormalData( + name = entity.name, + type = entity.type + ) + ) + } +} + +object MethodDeclListVisitor : SymbolVisitor { + override fun Table.visit(entity: MethodDeclList): Either = + with(MethodDeclVisitor) { + fold( + entity.methodDecls.toList(), + ::dispatch + ) + } +} + +object MethodDeclVisitor : SymbolVisitor { + override fun Table.visit(entity: MethodDecl): Either = either { + ensure(!contains(entity.identifier)) { + Error("MethodDeclVisitor: MethodDecl must have a unique name") + } + + Table( + MethodData( + name = entity.identifier, + args = dispatch(entity.formals).bind(), + varDeclList = dispatch(entity.varDecls).bind() + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/example/visitor/ProgramVisitor.kt b/app/src/main/kotlin/org/example/visitor/ProgramVisitor.kt new file mode 100644 index 0000000..0a9ba5d --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/ProgramVisitor.kt @@ -0,0 +1,18 @@ +package org.example.visitor + +import arrow.core.Either +import arrow.core.flatMap +import arrow.core.left +import arrow.core.raise.either +import org.example.ast.Program +import org.example.visitor.SymbolVisitor.Companion.dispatch + +object ProgramVisitor : SymbolVisitor { + override fun Table.visit(entity: Program): Either = either { + val mainClassTable = dispatch(entity.mainClass, this@visit).bind() + + val fullTable = dispatch(entity.classes, mainClassTable).bind() + + fullTable + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/example/visitor/SymbolVisitor.kt b/app/src/main/kotlin/org/example/visitor/SymbolVisitor.kt new file mode 100644 index 0000000..56e87d9 --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/SymbolVisitor.kt @@ -0,0 +1,93 @@ +package org.example.visitor; + +import arrow.core.* +import org.example.ast.* +import org.example.ast2.org.example.visitor.FormalsListVisitor +import org.example.ast2.org.example.visitor.FormalsVisitor +import org.example.ast2.org.example.visitor.MethodDeclListVisitor +import org.example.ast2.org.example.visitor.MethodDeclVisitor +import org.example.visitor.ClassDeclListVisitor.visit + +interface ASTVisitor : Visitor + +@JvmInline +value class Id(val name: String) { + constructor(name: Identifier) : this(name.s) +} + +sealed interface Scope { + val name: String +} + +data class ClassData( + override val name: String, + val fields: Table, + val methods: Table +) : Scope + +data class ParamData( + override val name: String, + val type: Type +) : Scope + +data class MethodData( + override val name: String, + val args: Table, + val varDeclList: Table, + val returnType: String? = null, +) : Scope + +data class FormalData( + override val name: String, + val type: Type +) : Scope + +data class Table(private val map: Map) { + constructor( + vararg pairs: Scope + ) : this(listOf(*pairs).associateBy { it.name }) + + constructor( + list: ArrayList + ) : this(list.associateBy { it.name }) + + operator fun plus(table: Table): Table = Table(map + table.map) + operator fun plus(pair: Scope): Table = Table(map + (pair.name to pair)) + operator fun get(key: String): Scope? = map[key] + fun contains(key: String): Boolean = map.containsKey(key) + + override fun toString(): String = map.entries + .joinToString(prefix = "{ ", separator = ", ", postfix = " }") { (k, v) -> "$k -> $v" } +} + +@JvmInline +value class Error(val message: String) + +interface SymbolVisitor { + companion object { + fun dispatch(entity: T, table: Table = Table()): Either = + when (entity) { + is MainClass -> MainClassVisitor.run { table.visit(entity) } + is ClassDeclSimple -> ClassDeclVisitor.ClassDeclSimpleVisitor.run { table.visit(entity) } + is ClassDeclExtends -> ClassDeclVisitor.ClassDeclExtendsVisitor.run { table.visit(entity) } + is VarDecl -> VarDeclVisitor.run { table.visit(entity) } + is VarDeclList -> VarDeclListVisitor.run { table.visit(entity) } + is MethodDecl -> MethodDeclVisitor.run { table.visit(entity) } + is MethodDeclList -> MethodDeclListVisitor.run { table.visit(entity) } + is FormalList -> FormalsListVisitor.run { table.visit(entity) } + is Formal -> FormalsVisitor.run { table.visit(entity) } + is Program -> ProgramVisitor.run { table.visit(entity) } + else -> throw IllegalArgumentException("SymbolVisitor: Unknown entity type") + } + } + + fun fold(entityList: List, f: (T) -> Either): Either = + entityList.fold(Table().right()) { + acc: Either, entity -> f(entity) + .flatMap { table -> acc.map { it + table } } + } + + fun Table.visit(entity: T): Either +} + + diff --git a/app/src/main/kotlin/org/example/visitor/VarDeclVisitor.kt b/app/src/main/kotlin/org/example/visitor/VarDeclVisitor.kt new file mode 100644 index 0000000..d894764 --- /dev/null +++ b/app/src/main/kotlin/org/example/visitor/VarDeclVisitor.kt @@ -0,0 +1,29 @@ +package org.example.visitor + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import org.example.ast.VarDecl +import org.example.ast.VarDeclList + +object VarDeclListVisitor : SymbolVisitor { + override fun Table.visit(entity: VarDeclList): Either = + with(VarDeclVisitor) { + fold( + entity.varDecls.toList() + ) { visit(it) } + } +} + +object VarDeclVisitor : SymbolVisitor { + override fun Table.visit(entity: VarDecl): Either = either { + ensure(!contains(entity.name)) { + Error("VarDeclVisitor: VarDecl must have a unique name") + } + + this@visit + FormalData( + name = entity.name, + type = entity.type + ) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/org/example/visitor/ClassDeclExtensionTest.kt b/app/src/test/kotlin/org/example/visitor/ClassDeclExtensionTest.kt new file mode 100644 index 0000000..eb75710 --- /dev/null +++ b/app/src/test/kotlin/org/example/visitor/ClassDeclExtensionTest.kt @@ -0,0 +1,96 @@ +package org.example.visitor + +import arrow.core.flatMap +import arrow.core.getOrElse +import arrow.core.right +import io.mockk.mockk +import io.mockk.unmockkAll +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.fail +import org.example.ast.* +import org.example.visitor.SymbolVisitor.Companion.dispatch +import org.junit.jupiter.api.Test + +class ClassDeclExtensionTest { + fun tearDown() { + unmockkAll() + } + + @Test + fun `should visit class decl`(): Unit = ClassDeclVisitor.run { + // Arrange + val table = Table() + val varDeclList = VarDeclList( + ArrayList( + listOf( + VarDecl(IntegerType(), "field1"), + VarDecl(IntegerType(), "field2") + ) + ) + ) + val statementList = StatementList( + ArrayList( + listOf( + Assign( + Identifier("local1"), + IntegerLiteral(1) + ) + ) + ) + ) + val expression = And(True(), False()) + val formalList = FormalList( + ArrayList( + listOf( + Formal(IntegerType(), "arg1") + ) + ) + ) + val methodDeclList = MethodDeclList( + ArrayList( + listOf( + MethodDecl( + IntegerType(), + "method1", + formalList, + varDeclList, + statementList, + expression + ) + ) + ) + ) + val superClass = ClassDeclSimple( + Identifier("Super"), + varDeclList, + methodDeclList + ) + val classDecl = ClassDeclExtends( + Identifier("Main"), + Identifier("Super"), + varDeclList, + methodDeclList + ) + val expectedClass = ClassData( + name = "Main", + fields = dispatch(varDeclList).getOrElse { fail("Should not fail") }, + methods = dispatch(methodDeclList).getOrElse { fail("Should not fail") } + ) + val expectedSuperClass = ClassData( + name = "Super", + fields = dispatch(varDeclList).getOrElse { fail("Should not fail") }, + methods = dispatch(methodDeclList).getOrElse { fail("Should not fail") } + ) + val expectedTable = Table( + expectedClass, + expectedSuperClass + ) + + // Act + val result = dispatch(superClass, table = table) + .flatMap { dispatch(classDecl, table = it) } + + // Assert + Assertions.assertThat(result).isEqualTo(expectedTable.right()) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/org/example/visitor/ClassDeclSimpleVisitorTest.kt b/app/src/test/kotlin/org/example/visitor/ClassDeclSimpleVisitorTest.kt new file mode 100644 index 0000000..6f51f36 --- /dev/null +++ b/app/src/test/kotlin/org/example/visitor/ClassDeclSimpleVisitorTest.kt @@ -0,0 +1,87 @@ +package org.example.visitor + +import arrow.core.getOrElse +import arrow.core.right +import io.mockk.unmockkAll +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.fail +import org.example.ast.* +import org.example.visitor.SymbolVisitor.Companion.dispatch +import org.junit.jupiter.api.Test + +class ClassDeclSimpleVisitorTest { + fun tearDown() { + unmockkAll() + } + + @Test + fun `should visit class decl`(): Unit = ClassDeclVisitor.ClassDeclSimpleVisitor.run { + // Arrange + val table = Table() + val varDeclList = VarDeclList( + ArrayList( + listOf( + VarDecl(IntegerType(), "field1"), + VarDecl(IntegerType(), "field2") + ) + ) + ) + val statementList = StatementList( + ArrayList( + listOf( + Assign( + Identifier("local1"), + IntegerLiteral(1) + ) + ) + ) + ) + val expression = And(True(), False()) + val formalList = FormalList( + ArrayList( + listOf( + Formal(IntegerType(), "arg1") + ) + ) + ) + val methodDeclList = MethodDeclList( + ArrayList( + listOf( + MethodDecl( + IntegerType(), + "method1", + formalList, + varDeclList, + statementList, + expression + ) + ) + ) + ) + val classDecl: ClassDeclSimple = ClassDeclSimple( + Identifier("Main"), + varDeclList, + methodDeclList + ) + val expectedClass = ClassData( + name = "Main", + fields = dispatch(varDeclList).getOrElse { fail("Should not fail") }, + methods = Table( + MethodData( + name = "method1", + args = dispatch(formalList).getOrElse { fail("Should not fail") }, + varDeclList = dispatch(varDeclList).getOrElse { fail("Should not fail") }, + ) + ) + ) + val expectedTable = Table( + expectedClass + ) + + // Act + val result = dispatch(classDecl, table) + + // Assert + Assertions.assertThat(result).isEqualTo(expectedTable.right()) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/org/example/visitor/MainClassVisitorTest.kt b/app/src/test/kotlin/org/example/visitor/MainClassVisitorTest.kt new file mode 100644 index 0000000..960e22e --- /dev/null +++ b/app/src/test/kotlin/org/example/visitor/MainClassVisitorTest.kt @@ -0,0 +1,82 @@ +package org.example.visitor + +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import org.assertj.core.api.Assertions +import org.example.ast.Identifier +import org.example.ast.IntegerType +import org.example.ast.MainClass +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class MainClassVisitorTest { + fun tearDown(): Unit { + unmockkObject(MainClassVisitor) + } + + @Test + fun `should visit main class`(): Unit = MainClassVisitor.run { + // Arrange + val table = Table() + val mainClass = MainClass.builder() + .className(Identifier.builder().s("Main").build()) + .argsName(Identifier.builder().s("args").build()) + .build() + val expectedTable = Companion.defaultMainTable + + // Act + val result = table.visit(mainClass) + + // Assert + result.fold( + ifLeft = { Assertions.fail("Should not throw an error") }, + ifRight = { Assertions.assertThat(it).isEqualTo(expectedTable) } + ) + } + + @Test + fun `Should visit main class with error`(): Unit = MainClassVisitor.run { + // Arrange + val table = Table() + val mainClass = MainClass.builder() + .className(Identifier.builder().s("Main").build()) + .argsName(Identifier.builder().s("wrong").build()) + .build() + + // Act + val result = table.visit(mainClass) + + // Assert + result.fold( + ifLeft = { Assertions.assertThat(it.message).isEqualTo("MainClassVisitor: MainClass args must be named 'args'") }, + ifRight = { Assertions.fail("Should throw an error") } + ) + } + + companion object { + val defaultClassData = + ClassData( + name = "Main", + fields = Table(), + methods = Table( + MethodData( + name = "Main", + args = Table( + ParamData( + name = "args", + type = IntegerType() + ) + ), + varDeclList = Table(), + returnType = null + ) + ) + ) + val defaultMainTable = Table( + defaultClassData + ) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/org/example/visitor/ProgramVisitorTest.kt b/app/src/test/kotlin/org/example/visitor/ProgramVisitorTest.kt new file mode 100644 index 0000000..de2debf --- /dev/null +++ b/app/src/test/kotlin/org/example/visitor/ProgramVisitorTest.kt @@ -0,0 +1,131 @@ +package org.example.visitor + + +import arrow.core.* +import io.mockk.* +import org.assertj.core.api.Assertions.assertThat +import org.example.ast.* +import org.example.visitor.SymbolVisitor.Companion.dispatch +import org.junit.jupiter.api.Test + +//class ClassDeclVisitorTest { +// fun tearDown() { +// unmockkAll() +// } +// +// +// @Test +// fun `should visit class decl`(): Unit = ClassDeclVisitor.run { +// // Arrange +// val table = Table() +// val classDecl = ClassDecl.builder() +// .className(Identifier.builder().s("Main").build()) +// .varDecls(VarDeclList.builder().build()) +// .methodDecls(MethodDeclList.builder().build()) +// .build() +// val expectedTable = Table( +// ClassData( +// name = "Main" +// ) +// ) +// +// // Act +// val result = table.visit(classDecl) +// +// // Assert +// assertThat(result).isEqualTo(expectedTable.right()) +// } +// +// @Test +// fun `should visit class decl with error`(): Unit = ClassDeclVisitor.run { +// // Arrange +// val table = Table() +// val classDecl = ClassDecl.builder() +// .className(Identifier.builder().s("Main").build()) +// .varDecls(VarDeclList.builder().build()) +// .methodDecls(MethodDeclList.builder().build()) +// .build() +// val expectedTable = Table( +// ClassData( +// name = "Main" +// ) +// ) +// val expectedError = Error("ClassDeclVisitor: ClassDecl must have a unique name") +// +// // Act +// val result = table.visit(classDecl) +// +// // Assert +// assertThat(result).isEqualTo(expectedTable.right()) +// } +// +//} + +class ProgramVisitorTest { + fun tearDown() { + unmockkAll() + } + + @Test + fun `should visit program`(): Unit = ProgramVisitor.run { + // Arrange + val expectedMainTable = MainClassVisitorTest.defaultMainTable + val expectedClassTable = expectedMainTable + Table( + MainClassVisitorTest.defaultClassData.copy( + name = "otherClass" + ) + ) + val expectedFullTable = expectedClassTable + Table( + MainClassVisitorTest.defaultClassData, + MainClassVisitorTest.defaultClassData.copy( + name = "otherClass" + ) + ) + val table = Table() + val program = slot() + mockkObject(SymbolVisitor.Companion) { + every { dispatch(any(MainClass::class), table) } returns expectedMainTable.right() + every { dispatch(any(ClassDeclList::class), table) } returns expectedClassTable.right() + + // Act + val result = dispatch(program, table) + + // Assert + assertThat(result).isEqualTo(expectedFullTable.right()) + } + } + + @Test + fun `should visit program with error`(): Unit = ProgramVisitor.run { + // Arrange + val table = Table() + val program = slot() + mockkObject(SymbolVisitor.Companion) { + every { dispatch(any(MainClass::class), table) } returns Error("MainClassVisitor: MainClass must have a unique name").left() + + // Act + val result = dispatch(program, table) + + // Assert + assertThat(result).isEqualTo(Error("MainClassVisitor: MainClass must have a unique name").left()) + } + } + + @Test + fun `should visit program with error in class decl list`(): Unit = ProgramVisitor.run { + // Arrange + val table = Table() + val expectedMainTable = MainClassVisitorTest.defaultMainTable + val program = slot() + mockkObject(SymbolVisitor.Companion) { + every { dispatch(any(MainClass::class), table) } returns expectedMainTable.right() + every { dispatch(any(ClassDeclList::class), table) } returns Error("ClassDeclVisitor: ClassDecl must have a unique name").left() + + // Act + val result = dispatch(program, table) + + // Assert + assertThat(result).isEqualTo(Error("ClassDeclVisitor: ClassDecl must have a unique name").left()) + } + } +} \ No newline at end of file