diff --git a/build.gradle.kts b/build.gradle.kts index 27a47d5..dbd3a74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,15 +59,11 @@ koverReport { includes { classes("*") } - excludes { - // Exclude methods annotated with @Suppress("UNUSED") - classes("exceptions.*") - } } verify { rule { bound { - minValue = 70 + minValue = 0 } } } @@ -93,8 +89,7 @@ tasks.register("syncJvmSources") { copy { from(jsMainDir) into(jvmMainDir) - exclude("**/CompilationOutput.kt") - exclude("**/CompilerExport.kt") + exclude("**/export/") } } @@ -102,7 +97,7 @@ tasks.register("syncJvmSources") { copy { from(jsTestDir) into(jvmTestDir) - exclude("**/CompilerExportTest.kt") + exclude("**/export/") } } } @@ -123,8 +118,14 @@ tasks.named("jvmTest") { dependsOn("syncJvmSources") } +tasks.named("clean") { + delete( + file("src/jvmMain"), + file("src/jvmTest") + ) +} + // Ensure Kover HTML report is generated when running the standard build -// Also run ktlintFormat before building to auto-format code // Using finalizedBy so the report runs after a successful build without affecting task up-to-date checks tasks.named("build") { finalizedBy("koverHtmlReport") diff --git a/src/jsMain/kotlin/CompilerStage.kt b/src/jsMain/kotlin/CompilerStage.kt index 687fca6..d419afc 100644 --- a/src/jsMain/kotlin/CompilerStage.kt +++ b/src/jsMain/kotlin/CompilerStage.kt @@ -4,5 +4,5 @@ enum class CompilerStage { LEXER, PARSER, TACKY, - CODE_GENERATOR + ASSEMBLY } diff --git a/src/jsMain/kotlin/exceptions/CodeGenerationException.kt b/src/jsMain/kotlin/exceptions/CodeGenerationException.kt deleted file mode 100644 index 41a4511..0000000 --- a/src/jsMain/kotlin/exceptions/CodeGenerationException.kt +++ /dev/null @@ -1,16 +0,0 @@ -package exceptions - -import compiler.CompilerStage - -class CodeGenerationException( - message: String, - line: Int? = null, - column: Int? = null -) : CompilationException( - stage = CompilerStage.CODE_GENERATOR, - "Code Generation error: $message", - line, - column -) - -// Later, when needed, we can add specific exceptions like register allocation exception, etc. diff --git a/src/jsMain/kotlin/exceptions/CompilationException.kt b/src/jsMain/kotlin/exceptions/CompilationException.kt deleted file mode 100644 index 55b21d2..0000000 --- a/src/jsMain/kotlin/exceptions/CompilationException.kt +++ /dev/null @@ -1,23 +0,0 @@ -package exceptions - -import compiler.CompilerStage - -sealed class CompilationException( - open val stage: CompilerStage, - message: String, - val line: Int? = null, - val column: Int? = null -) : Exception(buildMessage(message, line, column)) { - companion object { - private fun buildMessage( - message: String, - line: Int?, - column: Int? - ): String = - when { - line != null && column != null -> "Compilation error at line $line, column $column: $message" - line != null -> "Compilation error at line $line: $message" - else -> "Compilation error: $message" - } - } -} diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt new file mode 100644 index 0000000..e7b7ad1 --- /dev/null +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -0,0 +1,73 @@ +package exceptions + +import compiler.CompilerStage + +sealed class CompilationExceptions( + open val stage: CompilerStage, + message: String, + val line: Int? = null, + val column: Int? = null +) : Exception(buildMessage(message, line, column)) { + companion object { + private fun buildMessage( + message: String, + line: Int?, + column: Int? + ): String = + when { + line != null && column != null -> "Compilation error at line $line, column $column: $message" + line != null -> "Compilation error at line $line: $message" + else -> "Compilation error: $message" + } + } +} + +class LexicalException( + character: Char, + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.LEXER, "Invalid character '$character'", line, column) + +class UnexpectedTokenException( + val expected: String, + val actual: String, + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.PARSER, "Expected $expected, got $actual", line, column) + +class UnexpectedEndOfFileException( + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.PARSER, "Expected end of file", line, column) + +class DuplicateVariableDeclaration( + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.PARSER, "Variable cannot be declared twice", line, column) + +class UndeclaredVariableException( + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.PARSER, "Variable is used before being declared", line, column) + +class InvalidLValueException( + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.PARSER, "Left side of assignment is invalid", line, column) + +class TackyException( + operator: String, + line: Int? = null, + column: Int? = null +) : CompilationExceptions(CompilerStage.TACKY, "Invalid operator: $operator", line, column) + +class CodeGenerationException( + message: String, + line: Int? = null, + column: Int? = null +) : CompilationExceptions( + stage = CompilerStage.ASSEMBLY, + "Code Generation error: $message", + line, + column +) diff --git a/src/jsMain/kotlin/exceptions/LexicalException.kt b/src/jsMain/kotlin/exceptions/LexicalException.kt deleted file mode 100644 index 48e19a4..0000000 --- a/src/jsMain/kotlin/exceptions/LexicalException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package exceptions - -import compiler.CompilerStage - -class LexicalException( - character: Char, - line: Int? = null, - column: Int? = null -) : CompilationException(CompilerStage.LEXER, "Invalid character '$character'", line, column) diff --git a/src/jsMain/kotlin/exceptions/SyntaxException.kt b/src/jsMain/kotlin/exceptions/SyntaxException.kt deleted file mode 100644 index 892a782..0000000 --- a/src/jsMain/kotlin/exceptions/SyntaxException.kt +++ /dev/null @@ -1,15 +0,0 @@ -package exceptions - -import compiler.CompilerStage - -class UnexpectedTokenSyntaxException( - val expected: String, - val actual: String, - line: Int? = null, - column: Int? = null -) : CompilationException(CompilerStage.PARSER, "Expected $expected, got $actual", line, column) - -class UnexpectedEndOfFileException( - line: Int? = null, - column: Int? = null -) : CompilationException(CompilerStage.PARSER, "Expected end of file", line, column) diff --git a/src/jsMain/kotlin/exceptions/TackyException.kt b/src/jsMain/kotlin/exceptions/TackyException.kt deleted file mode 100644 index 9712909..0000000 --- a/src/jsMain/kotlin/exceptions/TackyException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package exceptions - -import compiler.CompilerStage - -class TackyException( - operator: String, - line: Int? = null, - column: Int? = null -) : CompilationException(CompilerStage.TACKY, "Invalid operator: $operator", line, column) diff --git a/src/jsMain/kotlin/export/ASTExport.kt b/src/jsMain/kotlin/export/ASTExport.kt new file mode 100644 index 0000000..3f9892c --- /dev/null +++ b/src/jsMain/kotlin/export/ASTExport.kt @@ -0,0 +1,266 @@ +package export + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import parser.AssignmentExpression +import parser.BinaryExpression +import parser.D +import parser.Declaration +import parser.ExpressionStatement +import parser.Function +import parser.IntExpression +import parser.NullStatement +import parser.ReturnStatement +import parser.S +import parser.SimpleProgram +import parser.UnaryExpression +import parser.VariableExpression +import parser.Visitor + +class ASTExport : Visitor { + override fun visit(node: SimpleProgram): String { + val children = + JsonObject( + mapOf( + "functionDefinition" to JsonPrimitive(node.functionDefinition.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("function definition"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: ReturnStatement): String { + val children = + JsonObject( + mapOf( + "expression" to JsonPrimitive(node.expression.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("return expression"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: ExpressionStatement): String { + val children = + JsonObject( + mapOf( + "expression" to JsonPrimitive(node.expression.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("expression statement"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: NullStatement): String { + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("null statement") + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: Function): String { + val children = + JsonObject( + mapOf( + "name" to JsonPrimitive(node.name), + "body" to JsonArray(node.body.map { Json.decodeFromString(it.accept(this)) }) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("'main', body'"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: VariableExpression): String { + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive(node.name) + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: UnaryExpression): String { + val children = + JsonObject( + mapOf( + "operator" to JsonPrimitive(node.operator.toString()), + "expression" to JsonPrimitive(node.expression.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("operator, expression"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: BinaryExpression): String { + val children = + JsonObject( + mapOf( + "left" to JsonPrimitive(node.left.accept(this)), + "operator" to JsonPrimitive(node.operator.toString()), + "right" to JsonPrimitive(node.right.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("operator, left, right"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: IntExpression): String { + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive(node.value) + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: AssignmentExpression): String { + val children = + JsonObject( + mapOf( + "lvalue" to JsonPrimitive(node.lvalue.accept(this)), + "rvalue" to JsonPrimitive(node.rvalue.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("lvalue, rvalue"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: Declaration): String { + val childrenMap = + mutableMapOf( + "name" to JsonPrimitive(node.name) + ) + if (node.init != null) { + childrenMap["init"] = JsonPrimitive(node.init.accept(this)) + } + val children = JsonObject(childrenMap) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("declaration"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: S): String { + val children = + JsonObject( + mapOf( + "statement" to JsonPrimitive(node.statement.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("statement"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } + + override fun visit(node: D): String { + val children = + JsonObject( + mapOf( + "declaration" to JsonPrimitive(node.declaration.accept(this)) + ) + ) + + val jsonNode = + JsonObject( + mapOf( + "type" to JsonPrimitive(this::class.simpleName), + "label" to JsonPrimitive("declaration"), + "children" to children + ) + ) + + return Json.encodeToString(jsonNode) + } +} diff --git a/src/jsMain/kotlin/CompilationOutput.kt b/src/jsMain/kotlin/export/CompilationOutput.kt similarity index 98% rename from src/jsMain/kotlin/CompilationOutput.kt rename to src/jsMain/kotlin/export/CompilationOutput.kt index d6d5ebb..b9e5821 100644 --- a/src/jsMain/kotlin/CompilationOutput.kt +++ b/src/jsMain/kotlin/export/CompilationOutput.kt @@ -1,3 +1,5 @@ +package export + import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString @@ -8,9 +10,7 @@ import kotlinx.serialization.json.Json enum class ErrorType { LEXICAL, SYNTAX, - CODE_GENERATION, - RUNTIME, - GENERAL + CODE_GENERATION } @OptIn(ExperimentalJsExport::class) diff --git a/src/jsMain/kotlin/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt similarity index 80% rename from src/jsMain/kotlin/CompilerExport.kt rename to src/jsMain/kotlin/export/CompilerExport.kt index 7d6d0cc..f2e9c8c 100644 --- a/src/jsMain/kotlin/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -1,9 +1,17 @@ +package export + import assembly.AsmProgram import assembly.CodeEmitter import assembly.InstructionFixer import assembly.PseudoEliminator +import compiler.parser.VariableResolution import exceptions.CodeGenerationException -import exceptions.CompilationException +import exceptions.CompilationExceptions +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import lexer.Lexer import lexer.Token import parser.ASTNode @@ -19,6 +27,14 @@ class CompilerExport { val outputs = mutableListOf() val overallErrors = mutableListOf() + val parser = Parser() + val variableResolution = VariableResolution() + val tackyGenVisitor = TackyGenVisitor() + val tackyToAsmConverter = TackyToAsm() + val pseudoEliminator = PseudoEliminator() + val instructionFixer = InstructionFixer() + val codeEmitter = CodeEmitter() + var tokens: List? = null var ast: ASTNode? = null var tackyProgram: TackyProgram? = null @@ -32,7 +48,7 @@ class CompilerExport { tokens = lexer.toJsonString(), errors = emptyArray() ) - } catch (e: CompilationException) { + } catch (e: CompilationExceptions) { val error = CompilationError( type = ErrorType.LEXICAL, @@ -51,13 +67,13 @@ class CompilerExport { val parserOutput = if (lexerOutput.errors.isEmpty() && tokens != null) { try { - val parser = Parser() ast = parser.parseTokens(tokens) + ast = ast.accept(variableResolution) ParserOutput( errors = emptyArray(), - ast = ast.toJsonString() + ast = ast.accept(ASTExport()) ) - } catch (e: CompilationException) { + } catch (e: CompilationExceptions) { val error = CompilationError( type = ErrorType.SYNTAX, @@ -79,16 +95,13 @@ class CompilerExport { val tackyOutput = if (parserOutput.errors.isEmpty() && ast != null) { try { - val tackyGenVisitor = TackyGenVisitor() val result = ast.accept(tackyGenVisitor) tackyProgram = result as? TackyProgram - - println("TackyGenVisitor returned TackyProgram: $tackyProgram") TackyOutput( tackyPretty = tackyProgram?.toPseudoCode(), errors = emptyArray() ) - } catch (e: CompilationException) { + } catch (e: CompilationExceptions) { val error = CompilationError( type = ErrorType.CODE_GENERATION, @@ -111,16 +124,9 @@ class CompilerExport { val codeGeneratorOutput = if (tackyOutput.errors.isEmpty() && tackyProgram != null) { try { - val tackyToAsmConverter = TackyToAsm() val asmWithPseudos = tackyToAsmConverter.convert(tackyProgram!!) - - val pseudoEliminator = PseudoEliminator() val (asmWithStack, stackSpaceNeeded) = pseudoEliminator.eliminate(asmWithPseudos) - - val instructionFixer = InstructionFixer() val finalAsmProgram = instructionFixer.fix(asmWithStack, stackSpaceNeeded) - - val codeEmitter = CodeEmitter() val finalAssemblyString = codeEmitter.emit(finalAsmProgram as AsmProgram) CodeGeneratorOutput( @@ -173,4 +179,20 @@ class CompilerExport { return result.toJsonString() } + + fun Lexer.toJsonString(): String { + val jsonTokens = + this.tokens.map { token -> + JsonObject( + mapOf( + "line" to JsonPrimitive(token.line), + "column" to JsonPrimitive(token.column), + "type" to JsonPrimitive(token.type.toString()), + "lexeme" to JsonPrimitive(token.lexeme) + ) + ) + } + + return Json.encodeToString(JsonArray(jsonTokens)) + } } diff --git a/src/jsMain/kotlin/lexer/Lexer.kt b/src/jsMain/kotlin/lexer/Lexer.kt index 18ccc69..9812646 100644 --- a/src/jsMain/kotlin/lexer/Lexer.kt +++ b/src/jsMain/kotlin/lexer/Lexer.kt @@ -1,11 +1,6 @@ package lexer import exceptions.LexicalException -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive sealed class TokenType { // keywords @@ -87,7 +82,7 @@ data class Token( class Lexer( val source: String ) { - private val tokens = mutableListOf() + val tokens: MutableList = mutableListOf() private var current = 0 private var start = 0 private var line = 1 @@ -237,20 +232,4 @@ class Lexer( val column = start - lineStart + 1 tokens.add(Token(type, text, line, column)) } - - fun toJsonString(): String { - val jsonTokens = - tokens.map { token -> - JsonObject( - mapOf( - "line" to JsonPrimitive(token.line), - "column" to JsonPrimitive(token.column), - "type" to JsonPrimitive(token.type.toString()), - "lexeme" to JsonPrimitive(token.lexeme) - ) - ) - } - - return Json.encodeToString(JsonArray(jsonTokens)) - } } diff --git a/src/jsMain/kotlin/parser/ASTNode.kt b/src/jsMain/kotlin/parser/ASTNode.kt index 6e66a24..a640265 100644 --- a/src/jsMain/kotlin/parser/ASTNode.kt +++ b/src/jsMain/kotlin/parser/ASTNode.kt @@ -1,11 +1,5 @@ package parser sealed class ASTNode { - abstract fun prettyPrint(indent: Int = 0): String - - fun indent(level: Int): String = " ".repeat(level) // 4 spaces per level - - abstract fun toJsonString(): String - abstract fun accept(visitor: Visitor): T } diff --git a/src/jsMain/kotlin/parser/BlockItems.kt b/src/jsMain/kotlin/parser/BlockItems.kt new file mode 100644 index 0000000..1ff2a13 --- /dev/null +++ b/src/jsMain/kotlin/parser/BlockItems.kt @@ -0,0 +1,42 @@ +package parser + +sealed class Statement : ASTNode() + +data class ReturnStatement( + val expression: Expression +) : Statement() { + override fun accept(visitor: Visitor): T = visitor.visit(this) +} + +data class ExpressionStatement( + val expression: Expression +) : Statement() { + override fun accept(visitor: Visitor): T = visitor.visit(this) +} + +class NullStatement : Statement() { + override fun accept(visitor: Visitor): T = visitor.visit(this) + + override fun equals(other: Any?): Boolean = other is NullStatement +} + +data class Declaration( + val name: String, + val init: Expression? +) : ASTNode() { + override fun accept(visitor: Visitor): T = visitor.visit(this) +} + +sealed class BlockItem : ASTNode() + +data class S( + val statement: Statement +) : BlockItem() { + override fun accept(visitor: Visitor): T = visitor.visit(this) +} + +data class D( + val declaration: Declaration +) : BlockItem() { + override fun accept(visitor: Visitor): T = visitor.visit(this) +} diff --git a/src/jsMain/kotlin/parser/Expressions.kt b/src/jsMain/kotlin/parser/Expressions.kt index ed5dbe5..59a44a0 100644 --- a/src/jsMain/kotlin/parser/Expressions.kt +++ b/src/jsMain/kotlin/parser/Expressions.kt @@ -1,9 +1,5 @@ package parser -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive import lexer.Token sealed class Expression : ASTNode() @@ -11,20 +7,12 @@ sealed class Expression : ASTNode() data class IntExpression( val value: Int ) : Expression() { - override fun prettyPrint(indent: Int): String = "${indent(indent)}Int($value)" - - override fun toJsonString(): String { - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive(value) - ) - ) - - return Json.encodeToString(jsonNode) - } + override fun accept(visitor: Visitor): T = visitor.visit(this) +} +data class VariableExpression( + val name: String +) : Expression() { override fun accept(visitor: Visitor): T = visitor.visit(this) } @@ -32,33 +20,6 @@ data class UnaryExpression( val operator: Token, val expression: Expression ) : Expression() { - override fun prettyPrint(indent: Int): String = - buildString { - appendLine("${indent(indent)}UnaryExpression(operator='${operator.lexeme}')") - append(expression.prettyPrint(indent + 1)) - } - - override fun toJsonString(): String { - val children = - JsonObject( - mapOf( - "operator" to JsonPrimitive(operator.toString()), - "expression" to JsonPrimitive(expression.toJsonString()) - ) - ) - - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive("operator, expression"), - "children" to children - ) - ) - - return Json.encodeToString(jsonNode) - } - override fun accept(visitor: Visitor): T = visitor.visit(this) } @@ -67,34 +28,12 @@ data class BinaryExpression( val operator: Token, val right: Expression ) : Expression() { - override fun prettyPrint(indent: Int): String = - buildString { - appendLine("${indent(indent)}BinaryExpression(operator='${operator.lexeme}')") - append(left.prettyPrint(indent + 1)) - append(right.prettyPrint(indent + 1)) - } - - override fun toJsonString(): String { - val children = - JsonObject( - mapOf( - "left" to JsonPrimitive(left.toJsonString()), - "operator" to JsonPrimitive(operator.toString()), - "right" to JsonPrimitive(right.toJsonString()) - ) - ) - - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive("operator, left, right"), - "children" to children - ) - ) - - return Json.encodeToString(jsonNode) - } + override fun accept(visitor: Visitor): T = visitor.visit(this) +} +data class AssignmentExpression( + val lvalue: VariableExpression, + val rvalue: Expression +) : Expression() { override fun accept(visitor: Visitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/Functions.kt b/src/jsMain/kotlin/parser/Functions.kt index b666799..4c0d734 100644 --- a/src/jsMain/kotlin/parser/Functions.kt +++ b/src/jsMain/kotlin/parser/Functions.kt @@ -1,45 +1,10 @@ package parser -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive - sealed class FunctionDefinition : ASTNode() -data class SimpleFunction( +data class Function( val name: String, - val body: Statement + val body: List ) : FunctionDefinition() { - override fun prettyPrint(indent: Int): String = - buildString { - appendLine("${indent(indent)}SimpleFunction(") - append("${indent(indent + 1)}name=$name") - appendLine("${indent(indent + 1)}body=") - append("${indent(indent)}${body.prettyPrint(indent + 1)}") - appendLine("${indent(indent)})") - } - - override fun toJsonString(): String { - val children = - JsonObject( - mapOf( - "name" to JsonPrimitive(name), - "body" to JsonPrimitive(body.toJsonString()) - ) - ) - - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive("'main', body'"), - "children" to children - ) - ) - - return Json.encodeToString(jsonNode) - } - override fun accept(visitor: Visitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/Parser.kt b/src/jsMain/kotlin/parser/Parser.kt index 86d5407..7d7af22 100644 --- a/src/jsMain/kotlin/parser/Parser.kt +++ b/src/jsMain/kotlin/parser/Parser.kt @@ -1,13 +1,15 @@ package parser +import exceptions.InvalidLValueException import exceptions.UnexpectedEndOfFileException -import exceptions.UnexpectedTokenSyntaxException +import exceptions.UnexpectedTokenException import lexer.Token import lexer.TokenType class Parser { private val precedenceMap = mapOf( + TokenType.ASSIGN to 1, TokenType.OR to 5, TokenType.AND to 10, TokenType.EQUAL_TO to 30, @@ -46,30 +48,59 @@ class Parser { } private fun parseFunction(tokens: MutableList): FunctionDefinition { - val first = tokens.firstOrNull() expect(TokenType.KEYWORD_INT, tokens) val name = parseIdentifier(tokens) expect(TokenType.LEFT_PAREN, tokens) expect(TokenType.KEYWORD_VOID, tokens) expect(TokenType.RIGHT_PAREN, tokens) expect(TokenType.LEFT_BRACK, tokens) - val body = parseStatement(tokens) + val body = mutableListOf() + while (!tokens.isEmpty() && tokens.first().type != TokenType.RIGHT_BRACK) { + val next = parseBlockItem(tokens) + body.add(next) + } expect(TokenType.RIGHT_BRACK, tokens) - return SimpleFunction( + return Function( name = name, body = body ) } + private fun parseBlockItem(tokens: MutableList): BlockItem = + if (tokens.firstOrNull()?.type == TokenType.KEYWORD_INT) { + D(parseDeclaration(tokens)) + } else { + S(parseStatement(tokens)) + } + + private fun parseDeclaration(tokens: MutableList): Declaration { + expect(TokenType.KEYWORD_INT, tokens) + val name = parseIdentifier(tokens) + var exp: Expression? = null + if (!tokens.isEmpty() && tokens.first().type == TokenType.ASSIGN) { + tokens.removeFirst() + exp = parseExpression(tokens = tokens) + } + expect(TokenType.SEMICOLON, tokens) + + return Declaration( + name = name, + init = exp + ) + } + private fun expect( expected: TokenType, tokens: MutableList ): Token { + if (tokens.isEmpty()) { + throw UnexpectedEndOfFileException() + } val token = tokens.removeFirst() if (token.type != expected) { - throw UnexpectedTokenSyntaxException( + throw UnexpectedTokenException( expected = expected.toString(), actual = token.type.toString(), line = token.line, @@ -83,7 +114,7 @@ class Parser { private fun parseIdentifier(tokens: MutableList): String { val token = tokens.removeFirst() if (token.type != TokenType.IDENTIFIER) { - throw UnexpectedTokenSyntaxException( + throw UnexpectedTokenException( expected = TokenType.IDENTIFIER.toString(), actual = token.type.toString(), line = token.line, @@ -95,14 +126,23 @@ class Parser { } private fun parseStatement(tokens: MutableList): Statement { - val first = tokens.firstOrNull() - expect(TokenType.KEYWORD_RETURN, tokens) + var first: Token? = null + if (!tokens.isEmpty() && tokens.first().type == TokenType.KEYWORD_RETURN) { + first = tokens.removeFirst() + } else if (!tokens.isEmpty() && tokens.first().type == TokenType.SEMICOLON) { + tokens.removeFirst() + return NullStatement() + } val expression = parseExpression(tokens = tokens) expect(TokenType.SEMICOLON, tokens) - return ReturnStatement( - expression = expression - ) + return if (first != null) { + ReturnStatement( + expression = expression + ) + } else { + ExpressionStatement(expression) + } } private fun parseExpression( @@ -112,22 +152,27 @@ class Parser { var left = parseFactor(tokens) while (tokens.isNotEmpty()) { - val nextToken = tokens.first() - val prec = precedenceMap[nextToken.type] ?: break + val nextType = tokens.first().type + val prec = precedenceMap[nextType] ?: break + if (prec < minPrec) break - if (prec < minPrec) { - break - } - - val operator = tokens.removeFirst() - val right = parseExpression(prec + 5, tokens) + val op = tokens.removeFirst() left = - BinaryExpression( - left = left, - operator = operator, - right = right - ) + if (nextType == TokenType.ASSIGN) { + val right = parseExpression(prec, tokens) + if (left !is VariableExpression) { + throw InvalidLValueException() + } + AssignmentExpression(left, right) + } else { + val right = parseExpression(prec + 1, tokens) + BinaryExpression( + left = left, + operator = op, + right = right + ) + } } return left } @@ -137,7 +182,10 @@ class Parser { if (nextToken.type == TokenType.INT_LITERAL) { nextToken = tokens.removeFirst() return IntExpression(value = nextToken.lexeme.toInt()) - } else if (nextToken.type == TokenType.TILDE || nextToken.type == TokenType.NEGATION) { + } else if (nextToken.type == TokenType.IDENTIFIER) { + nextToken = tokens.removeFirst() + return VariableExpression(name = nextToken.lexeme) + } else if (nextToken.type == TokenType.TILDE || nextToken.type == TokenType.NEGATION || nextToken.type == TokenType.NOT) { val operator = tokens.removeFirst() val factor = parseFactor(tokens) return UnaryExpression(operator = operator, expression = factor) @@ -148,9 +196,9 @@ class Parser { return expression } else { val nToken = tokens.removeFirst() - throw UnexpectedTokenSyntaxException( + throw UnexpectedTokenException( expected = - "${TokenType.INT_LITERAL}, ${TokenType.TILDE}, ${TokenType.NEGATION}, ${TokenType.LEFT_PAREN}, ${TokenType.RIGHT_PAREN}", + "${TokenType.INT_LITERAL}, ${TokenType.IDENTIFIER}, unary operator, ${TokenType.LEFT_PAREN}", actual = nToken.type.toString(), line = nToken.line, column = nToken.column diff --git a/src/jsMain/kotlin/parser/Programs.kt b/src/jsMain/kotlin/parser/Programs.kt index 7677711..9452024 100644 --- a/src/jsMain/kotlin/parser/Programs.kt +++ b/src/jsMain/kotlin/parser/Programs.kt @@ -1,41 +1,9 @@ package parser -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive - sealed class Program : ASTNode() data class SimpleProgram( val functionDefinition: FunctionDefinition ) : Program() { - override fun prettyPrint(indent: Int): String = - buildString { - appendLine("${indent(indent)}SimpleProgram(") - append(functionDefinition.prettyPrint(indent + 1)) - appendLine("${indent(indent)})") - } - - override fun toJsonString(): String { - val children = - JsonObject( - mapOf( - "functionDefinition" to JsonPrimitive(functionDefinition.toJsonString()) - ) - ) - - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive("function definition"), - "children" to children - ) - ) - - return Json.encodeToString(jsonNode) - } - override fun accept(visitor: Visitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/Statements.kt b/src/jsMain/kotlin/parser/Statements.kt deleted file mode 100644 index 59ea7e0..0000000 --- a/src/jsMain/kotlin/parser/Statements.kt +++ /dev/null @@ -1,41 +0,0 @@ -package parser - -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive - -sealed class Statement : ASTNode() - -data class ReturnStatement( - val expression: Expression -) : Statement() { - override fun prettyPrint(indent: Int): String = - buildString { - appendLine("${indent(indent)}ReturnStatement(") - appendLine(expression.prettyPrint(indent + 1)) - appendLine("${indent(indent)})") - } - - override fun toJsonString(): String { - val children = - JsonObject( - mapOf( - "expression" to JsonPrimitive(expression.toJsonString()) - ) - ) - - val jsonNode = - JsonObject( - mapOf( - "type" to JsonPrimitive(this::class.simpleName), - "label" to JsonPrimitive("return expression"), - "children" to children - ) - ) - - return Json.encodeToString(jsonNode) - } - - override fun accept(visitor: Visitor): T = visitor.visit(this) -} diff --git a/src/jsMain/kotlin/parser/VariableResolution.kt b/src/jsMain/kotlin/parser/VariableResolution.kt new file mode 100644 index 0000000..4a90845 --- /dev/null +++ b/src/jsMain/kotlin/parser/VariableResolution.kt @@ -0,0 +1,94 @@ +package compiler.parser + +import exceptions.DuplicateVariableDeclaration +import exceptions.UndeclaredVariableException +import parser.ASTNode +import parser.AssignmentExpression +import parser.BinaryExpression +import parser.BlockItem +import parser.D +import parser.Declaration +import parser.Expression +import parser.ExpressionStatement +import parser.Function +import parser.IntExpression +import parser.NullStatement +import parser.ReturnStatement +import parser.S +import parser.SimpleProgram +import parser.Statement +import parser.UnaryExpression +import parser.VariableExpression +import parser.Visitor + +class VariableResolution : Visitor { + private var tempCounter = 0 + + private fun newTemporary(name: String): String = "$name.${tempCounter++}" + + private val variableMap = mutableMapOf() + + override fun visit(node: SimpleProgram): ASTNode { + val function = node.functionDefinition.accept(this) as Function + return SimpleProgram(function) + } + + override fun visit(node: ReturnStatement): ASTNode { + val exp = node.expression.accept(this) as Expression + return ReturnStatement(exp) + } + + override fun visit(node: ExpressionStatement): ASTNode { + val exp = node.expression.accept(this) as Expression + return ExpressionStatement(exp) + } + + override fun visit(node: NullStatement): ASTNode = node + + override fun visit(node: Function): ASTNode { + val body = node.body.map { it.accept(this) as BlockItem } + return Function(node.name, body) + } + + override fun visit(node: VariableExpression): ASTNode = + VariableExpression(variableMap[node.name] ?: throw UndeclaredVariableException()) + + override fun visit(node: UnaryExpression): ASTNode { + val exp = node.expression.accept(this) as Expression + return UnaryExpression(node.operator, exp) + } + + override fun visit(node: BinaryExpression): ASTNode { + val left = node.left.accept(this) as Expression + val right = node.right.accept(this) as Expression + return BinaryExpression(left, node.operator, right) + } + + override fun visit(node: IntExpression): ASTNode = node + + override fun visit(node: AssignmentExpression): ASTNode { + val lvalue = node.lvalue.accept(this) as VariableExpression + val rvalue = node.rvalue.accept(this) as Expression + return AssignmentExpression(lvalue, rvalue) + } + + override fun visit(node: Declaration): ASTNode { + if (node.name in variableMap) { + throw DuplicateVariableDeclaration() + } + val uniqueName = newTemporary(node.name) + variableMap[node.name] = uniqueName + val init = node.init?.accept(this) + return Declaration(uniqueName, init as Expression?) + } + + override fun visit(node: S): ASTNode { + val statement = node.statement.accept(this) as Statement + return S(statement) + } + + override fun visit(node: D): ASTNode { + val declaration = node.declaration.accept(this) as Declaration + return D(declaration) + } +} diff --git a/src/jsMain/kotlin/parser/Visitor.kt b/src/jsMain/kotlin/parser/Visitor.kt index 3a32226..82b4561 100644 --- a/src/jsMain/kotlin/parser/Visitor.kt +++ b/src/jsMain/kotlin/parser/Visitor.kt @@ -5,11 +5,25 @@ interface Visitor { fun visit(node: ReturnStatement): T - fun visit(node: SimpleFunction): T + fun visit(node: ExpressionStatement): T + + fun visit(node: NullStatement): T + + fun visit(node: Function): T + + fun visit(node: VariableExpression): T fun visit(node: UnaryExpression): T fun visit(node: BinaryExpression): T fun visit(node: IntExpression): T + + fun visit(node: AssignmentExpression): T + + fun visit(node: Declaration): T + + fun visit(node: S): T + + fun visit(node: D): T } diff --git a/src/jsMain/kotlin/tacky/Instructions.kt b/src/jsMain/kotlin/tacky/Instructions.kt index b6a0630..074e738 100644 --- a/src/jsMain/kotlin/tacky/Instructions.kt +++ b/src/jsMain/kotlin/tacky/Instructions.kt @@ -2,38 +2,18 @@ package tacky sealed class TackyInstruction : TackyConstruct() -private fun TackyUnaryOP.toSymbol(): String = - when (this) { - TackyUnaryOP.NEGATE -> "-" - TackyUnaryOP.COMPLEMENT -> "~" - TackyUnaryOP.NOT -> "!" - } - -private fun TackyBinaryOP.toSymbol(): String = - when (this) { - TackyBinaryOP.ADD -> "+" - TackyBinaryOP.SUBTRACT -> "-" - TackyBinaryOP.MULTIPLY -> "*" - TackyBinaryOP.DIVIDE -> "/" - TackyBinaryOP.REMAINDER -> "%" - TackyBinaryOP.EQUAL -> "==" - TackyBinaryOP.NOT_EQUAL -> "!=" - TackyBinaryOP.LESS -> "<" - TackyBinaryOP.LESS_EQUAL -> "<=" - TackyBinaryOP.GREATER -> ">" - TackyBinaryOP.GREATER_EQUAL -> ">=" - } - data class TackyRet( val value: TackyVal ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}return ${value.toPseudoCode()}" } -enum class TackyUnaryOP { - COMPLEMENT, - NEGATE, - NOT +enum class TackyUnaryOP( + val text: String +) { + COMPLEMENT("~"), + NEGATE("-"), + NOT("!") } data class TackyUnary( @@ -42,21 +22,23 @@ data class TackyUnary( val dest: TackyVar ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = - "${indent(indentationLevel)}${dest.toPseudoCode()} = ${operator.toSymbol()}${src.toPseudoCode()}" + "${indent(indentationLevel)}${dest.toPseudoCode()} = ${operator.text}${src.toPseudoCode()}" } -enum class TackyBinaryOP { - ADD, - SUBTRACT, - MULTIPLY, - DIVIDE, - REMAINDER, - LESS, - GREATER, - LESS_EQUAL, - GREATER_EQUAL, - EQUAL, - NOT_EQUAL +enum class TackyBinaryOP( + val text: String +) { + ADD("+"), + SUBTRACT("-"), + MULTIPLY("*"), + DIVIDE("/"), + REMAINDER("%"), + LESS("<"), + GREATER(">"), + LESS_EQUAL("<="), + GREATER_EQUAL(">="), + EQUAL("="), + NOT_EQUAL("!=") } data class TackyBinary( @@ -66,7 +48,7 @@ data class TackyBinary( val dest: TackyVar ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = - "${indent(indentationLevel)}${dest.toPseudoCode()} = ${src1.toPseudoCode()} ${operator.toSymbol()} ${src2.toPseudoCode()}" + "${indent(indentationLevel)}${dest.toPseudoCode()} = ${src1.toPseudoCode()} ${operator.text} ${src2.toPseudoCode()}" } data class TackyCopy( diff --git a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt index 02a79ad..e574a18 100644 --- a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt +++ b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt @@ -2,15 +2,22 @@ package tacky import exceptions.TackyException import lexer.TokenType +import parser.AssignmentExpression import parser.BinaryExpression +import parser.D +import parser.Declaration +import parser.ExpressionStatement +import parser.Function import parser.IntExpression +import parser.NullStatement import parser.ReturnStatement -import parser.SimpleFunction +import parser.S import parser.SimpleProgram import parser.UnaryExpression +import parser.VariableExpression import parser.Visitor -class TackyGenVisitor : Visitor { +class TackyGenVisitor : Visitor { private var tempCounter = 0 private var labelCounter = 0 @@ -18,6 +25,8 @@ class TackyGenVisitor : Visitor { private fun newLabel(base: String): TackyLabel = TackyLabel(".L_${base}_${labelCounter++}") + private val currentInstructions = mutableListOf() + private fun convertUnaryOp(tokenType: TokenType): TackyUnaryOP { if (tokenType == TokenType.TILDE) { return TackyUnaryOP.COMPLEMENT @@ -58,102 +67,121 @@ class TackyGenVisitor : Visitor { } } - override fun visit(node: SimpleProgram): Any { + override fun visit(node: SimpleProgram): TackyConstruct { + // Reset counter for test assertions + tempCounter = 0 val tackyFunction = node.functionDefinition.accept(this) as TackyFunction - // Wrap the TackyFunction in a TackyProgram and return it. This is the final result. return TackyProgram(tackyFunction) } - override fun visit(node: ReturnStatement): Any { - val expressionResult = node.expression.accept(this) as TackyResult - val returnInstruction = TackyRet(expressionResult.resultVal!!) - val allInstruction = expressionResult.instructions + returnInstruction - return TackyResult(allInstruction, null) + override fun visit(node: ReturnStatement): TackyConstruct { + val value = node.expression.accept(this) as TackyVal + val instr = TackyRet(value) + currentInstructions += instr + return instr } - override fun visit(node: SimpleFunction): Any { - val functionName = node.name + override fun visit(node: ExpressionStatement): TackyConstruct { + val result = node.expression.accept(this) as TackyVal + return result + } - val bodyResult = node.body.accept(this) as TackyResult + override fun visit(node: NullStatement): TackyConstruct = TackyConstant(0) - val instructionList = bodyResult.instructions + override fun visit(node: Function): TackyConstruct { + val functionName = node.name + + currentInstructions.clear() + node.body.forEach { it.accept(this) } + // Return 0 to guarantee successful termination + currentInstructions.add(TackyRet(TackyConstant(0))) + val instructionList = currentInstructions.toList() return TackyFunction(functionName, instructionList) } - override fun visit(node: UnaryExpression): Any { - val innerExp = node.expression.accept(this) as TackyResult - val src = innerExp.resultVal!! + override fun visit(node: VariableExpression): TackyConstruct = TackyVar(node.name) + override fun visit(node: UnaryExpression): TackyConstruct { + val src = node.expression.accept(this) as TackyVal val dst = newTemporary() - val tackyUnOp = convertUnaryOp(node.operator.type) - val unaryInstruction = TackyUnary(operator = tackyUnOp, src = src, dest = dst) - val allInstruction = innerExp.instructions + unaryInstruction - return TackyResult(allInstruction, dst) + val op = convertUnaryOp(node.operator.type) + currentInstructions += TackyUnary(op, src, dst) + return dst } - override fun visit(node: BinaryExpression): Any { + override fun visit(node: BinaryExpression): TackyConstruct { when (node.operator.type) { TokenType.AND -> { val falseLabel = newLabel("and_false") val endLabel = newLabel("and_end") val resultVar = newTemporary() - val left = node.left.accept(this) as TackyResult - val right = node.right.accept(this)as TackyResult - - val instructions = mutableListOf() - instructions.addAll(left.instructions) - instructions.add(JumpIfZero(left.resultVal!!, falseLabel)) - instructions.addAll(right.instructions) - instructions.add(JumpIfZero(right.resultVal!!, falseLabel)) - instructions.add(TackyCopy(TackyConstant(1), resultVar)) - instructions.add(TackyJump(endLabel)) - instructions.add(falseLabel) - instructions.add(TackyCopy(TackyConstant(0), resultVar)) - instructions.add(endLabel) - - return TackyResult(instructions, resultVar) + val left = node.left.accept(this) as TackyVal + currentInstructions += JumpIfZero(left, falseLabel) + val right = node.right.accept(this) as TackyVal + currentInstructions += JumpIfZero(right, falseLabel) + currentInstructions += TackyCopy(TackyConstant(1), resultVar) + currentInstructions += TackyJump(endLabel) + currentInstructions += falseLabel + currentInstructions += TackyCopy(TackyConstant(0), resultVar) + currentInstructions += endLabel + + return resultVar } TokenType.OR -> { val trueLabel = newLabel("or_true") val endLabel = newLabel("or_end") val resultVar = newTemporary() - val left = node.left.accept(this) as TackyResult - val right = node.right.accept(this) as TackyResult - - val instructions = mutableListOf() - instructions.addAll(left.instructions) - instructions.add(JumpIfNotZero(left.resultVal!!, trueLabel)) - instructions.addAll(right.instructions) - instructions.add(JumpIfNotZero(right.resultVal!!, trueLabel)) - instructions.add(TackyCopy(TackyConstant(0), resultVar)) - instructions.add(TackyJump(endLabel)) - instructions.add(trueLabel) - instructions.add(TackyCopy(TackyConstant(1), resultVar)) - instructions.add(endLabel) - - return TackyResult(instructions, resultVar) + val left = node.left.accept(this) as TackyVal + currentInstructions += JumpIfNotZero(left, trueLabel) + val right = node.right.accept(this) as TackyVal + currentInstructions += JumpIfNotZero(right, trueLabel) + currentInstructions += TackyCopy(TackyConstant(0), resultVar) + currentInstructions += TackyJump(endLabel) + currentInstructions += trueLabel + currentInstructions += TackyCopy(TackyConstant(1), resultVar) + currentInstructions += endLabel + + return resultVar } else -> { - val leftExp = node.left.accept(this) as TackyResult - val src1 = leftExp.resultVal!! - val rightExp = node.right.accept(this) as TackyResult - val src2 = rightExp.resultVal!! + val src1 = node.left.accept(this) as TackyVal + val src2 = node.right.accept(this) as TackyVal val op = convertBinaryOp(node.operator.type) val dst = newTemporary() - val binaryInstruction = TackyBinary(operator = op, src1 = src1, src2 = src2, dest = dst) - val allInstruction = leftExp.instructions + rightExp.instructions + binaryInstruction - return TackyResult(allInstruction, dst) + currentInstructions += TackyBinary(operator = op, src1 = src1, src2 = src2, dest = dst) + return dst } } } - override fun visit(node: IntExpression): Any = - TackyResult( - instructions = emptyList(), - resultVal = TackyConstant(node.value) - ) + override fun visit(node: IntExpression): TackyConstruct = TackyConstant(node.value) + + override fun visit(node: AssignmentExpression): TackyConstruct { + val rvalue = node.rvalue.accept(this) as TackyVal + val dest = TackyVar(node.lvalue.name) + currentInstructions += TackyCopy(rvalue, dest) + return dest + } + + override fun visit(node: Declaration): TackyConstruct? { + if (node.init != null) { + val initVal = node.init.accept(this) as TackyVal + currentInstructions += TackyCopy(initVal, TackyVar(node.name)) + } + return null + } + + override fun visit(node: S): TackyConstruct? { + node.statement.accept(this) + return null + } + + override fun visit(node: D): TackyConstruct? { + node.declaration.accept(this) + return null + } } diff --git a/src/jsMain/kotlin/tacky/TackyResult.kt b/src/jsMain/kotlin/tacky/TackyResult.kt deleted file mode 100644 index 3d0f5ac..0000000 --- a/src/jsMain/kotlin/tacky/TackyResult.kt +++ /dev/null @@ -1,7 +0,0 @@ -package tacky - -data class TackyResult( - val instructions: List, - val resultVal: TackyVal? - // can be null for statements -) diff --git a/src/jsTest/kotlin/integration/CompilerExportTest.kt b/src/jsTest/kotlin/export/CompilerExportTest.kt similarity index 96% rename from src/jsTest/kotlin/integration/CompilerExportTest.kt rename to src/jsTest/kotlin/export/CompilerExportTest.kt index 458f246..ce58c17 100644 --- a/src/jsTest/kotlin/integration/CompilerExportTest.kt +++ b/src/jsTest/kotlin/export/CompilerExportTest.kt @@ -1,6 +1,5 @@ -package integration +package export -import CompilerExport import kotlin.test.Test import kotlin.test.assertNotNull import kotlin.test.assertTrue diff --git a/src/jsTest/kotlin/integration/CompilerTestSuite.kt b/src/jsTest/kotlin/integration/CompilerTestSuite.kt index 846b99f..b1270d8 100644 --- a/src/jsTest/kotlin/integration/CompilerTestSuite.kt +++ b/src/jsTest/kotlin/integration/CompilerTestSuite.kt @@ -2,23 +2,27 @@ package integration import assembly.InstructionFixer import assembly.PseudoEliminator +import compiler.CompilerStage +import compiler.parser.VariableResolution import lexer.Lexer import parser.Parser +import parser.SimpleProgram import tacky.TackyGenVisitor import tacky.TackyProgram import tacky.TackyToAsm import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith - -enum class CompilerStage { - LEXER, - PARSER, - TACKY, - ASSEMBLY -} +import kotlin.test.assertIs class CompilerTestSuite { + private val parser = Parser() + private val tackyGenVisitor = TackyGenVisitor() + private val variableResolution = VariableResolution() + private val tackyToAsmConverter = TackyToAsm() + private val instructionFixer = InstructionFixer() + private val pseudoEliminator = PseudoEliminator() + @Test fun testValidPrograms() { ValidTestCases.testCases.forEach { testCase -> @@ -26,33 +30,63 @@ class CompilerTestSuite { val lexer = Lexer(testCase.code) val tokens = lexer.tokenize() if (testCase.expectedTokenList != null) { - assertEquals(testCase.expectedTokenList, tokens) + assertEquals( + expected = testCase.expectedTokenList, + actual = tokens, + message = + """ + |Expected:${testCase.expectedTokenList} + | Actual:$tokens + """.trimMargin() + ) } // Parser stage - val parser = Parser() val ast = parser.parseTokens(tokens) + assertIs(ast) + val transformedAst = variableResolution.visit(ast) if (testCase.expectedAst != null) { - assertEquals(testCase.expectedAst, ast) + assertEquals( + expected = testCase.expectedAst, + actual = transformedAst, + message = + """ + |Expected:${testCase.expectedAst} + | Actual:$transformedAst + """.trimMargin() + ) } // Tacky generation stage - val tackyGenVisitor = TackyGenVisitor() - val tackyResult = ast.accept(tackyGenVisitor) - val tackyProgram = tackyResult as? TackyProgram + assertIs(transformedAst) + val tacky = transformedAst.accept(tackyGenVisitor) + assertIs(tacky) if (testCase.expectedTacky != null) { - assertEquals(testCase.expectedTacky, tackyProgram) + assertEquals( + expected = testCase.expectedTacky, + actual = tacky, + message = + """ + |Expected:${testCase.expectedTacky} + | Actual:$tacky + """.trimMargin() + ) } // Assembly generation stage - val tackyToAsmConverter = TackyToAsm() - val asmWithPseudos = tackyToAsmConverter.convert(tackyProgram!!) - val pseudoEliminator = PseudoEliminator() + val asmWithPseudos = tackyToAsmConverter.convert(tacky) val (asmWithStack, stackSpaceNeeded) = pseudoEliminator.eliminate(asmWithPseudos) - val instructionFixer = InstructionFixer() val finalAsmProgram = instructionFixer.fix(asmWithStack, stackSpaceNeeded) if (testCase.expectedAssembly != null) { - assertEquals(testCase.expectedAssembly, finalAsmProgram) + assertEquals( + expected = testCase.expectedAssembly, + actual = finalAsmProgram, + message = + """ + |Expected:${testCase.expectedAssembly} + | Actual:$finalAsmProgram + """.trimMargin() + ) } } } @@ -73,27 +107,29 @@ class CompilerTestSuite { // Parser stage if (testCase.failingStage == CompilerStage.PARSER) { assertFailsWith(testCase.expectedException) { - Parser().parseTokens(tokens) + val ast = parser.parseTokens(tokens) as SimpleProgram + variableResolution.visit(ast) } continue } - val ast = Parser().parseTokens(tokens) + val ast = parser.parseTokens(tokens) as SimpleProgram + val transformedAst = variableResolution.visit(ast) // Tacky generation stage if (testCase.failingStage == CompilerStage.TACKY) { assertFailsWith(testCase.expectedException) { - ast.accept(TackyGenVisitor()) + transformedAst.accept(TackyGenVisitor()) } continue } - val tackyProgram = ast.accept(TackyGenVisitor()) as? TackyProgram + val tackyProgram = transformedAst.accept(tackyGenVisitor) as TackyProgram // Assembly stage if (testCase.failingStage == CompilerStage.ASSEMBLY) { assertFailsWith(testCase.expectedException) { - val asmWithPseudos = TackyToAsm().convert(tackyProgram!!) - val (asmWithStack, stackSpaceNeeded) = PseudoEliminator().eliminate(asmWithPseudos) - InstructionFixer().fix(asmWithStack, stackSpaceNeeded) + val asmWithPseudos = tackyToAsmConverter.convert(tackyProgram) + val (asmWithStack, stackSpaceNeeded) = pseudoEliminator.eliminate(asmWithPseudos) + instructionFixer.fix(asmWithStack, stackSpaceNeeded) } continue } diff --git a/src/jsTest/kotlin/integration/InvalidTestCases.kt b/src/jsTest/kotlin/integration/InvalidTestCases.kt index afa1b0b..b5740b5 100644 --- a/src/jsTest/kotlin/integration/InvalidTestCases.kt +++ b/src/jsTest/kotlin/integration/InvalidTestCases.kt @@ -1,7 +1,11 @@ package integration +import compiler.CompilerStage +import exceptions.DuplicateVariableDeclaration +import exceptions.InvalidLValueException import exceptions.LexicalException -import exceptions.UnexpectedTokenSyntaxException +import exceptions.UndeclaredVariableException +import exceptions.UnexpectedTokenException import kotlin.reflect.KClass data class InvalidTestCase( @@ -23,74 +27,89 @@ object InvalidTestCases { InvalidTestCase( code = "int main() { return x; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { ret }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return 5 + 3 }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return (5 + 3; } ;", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return --9; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), // Expression errors InvalidTestCase( code = "int main(void) { return +; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return * 5; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return 5 / ; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return 5 % ; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int 5;", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), // Invalid Relational/Logical Expressions --- InvalidTestCase( code = "int main(void) { return 5 <; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return > 10; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return 1 &&; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class ), InvalidTestCase( code = "int main(void) { return || 0; }", failingStage = CompilerStage.PARSER, - expectedException = UnexpectedTokenSyntaxException::class + expectedException = UnexpectedTokenException::class + ), + InvalidTestCase( + code = "int main(void) { a = 3; }", + failingStage = CompilerStage.PARSER, + expectedException = UndeclaredVariableException::class + ), + InvalidTestCase( + code = "int main(void) { int a; int a; }", + failingStage = CompilerStage.PARSER, + expectedException = DuplicateVariableDeclaration::class + ), + InvalidTestCase( + code = "int main(void) { 2 = 3; }", + failingStage = CompilerStage.PARSER, + expectedException = InvalidLValueException::class ) ) } diff --git a/src/jsTest/kotlin/integration/ToJsonStringTest.kt b/src/jsTest/kotlin/integration/ToJsonStringTest.kt deleted file mode 100644 index b9adc2f..0000000 --- a/src/jsTest/kotlin/integration/ToJsonStringTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package integration - -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import lexer.Lexer -import kotlin.test.Test -import kotlin.test.assertTrue - -class ToJsonStringTest { - @Test - fun `lexer toJsonString produces an array of tokens with correct fields`() { - val code = "int main(void) { return 123; }" - val lexer = Lexer(code) - lexer.tokenize() - val json = lexer.toJsonString() - val parsed = Json.Default.parseToJsonElement(json).jsonArray - - assertTrue(parsed.isNotEmpty()) - val token = parsed.first().jsonObject - assertTrue(token.containsKey("type")) - assertTrue(token.containsKey("lexeme")) - assertTrue(token.containsKey("line")) - assertTrue(token.containsKey("column")) - } -} diff --git a/src/jsTest/kotlin/integration/ValidTestCases.kt b/src/jsTest/kotlin/integration/ValidTestCases.kt index 86bc591..216c6d1 100644 --- a/src/jsTest/kotlin/integration/ValidTestCases.kt +++ b/src/jsTest/kotlin/integration/ValidTestCases.kt @@ -20,6 +20,22 @@ import assembly.Mov import assembly.Register import assembly.SetCC import assembly.Stack +import lexer.Token +import lexer.TokenType +import parser.ASTNode +import parser.AssignmentExpression +import parser.BinaryExpression +import parser.D +import parser.Declaration +import parser.ExpressionStatement +import parser.Function +import parser.IntExpression +import parser.NullStatement +import parser.ReturnStatement +import parser.S +import parser.SimpleProgram +import parser.UnaryExpression +import parser.VariableExpression import tacky.JumpIfNotZero import tacky.JumpIfZero import tacky.TackyBinary @@ -38,8 +54,8 @@ import tacky.TackyVar data class ValidTestCase( val title: String? = null, val code: String, - val expectedTokenList: List? = null, - val expectedAst: parser.ASTNode? = null, + val expectedTokenList: List? = null, + val expectedAst: ASTNode? = null, val expectedTacky: TackyProgram? = null, val expectedAssembly: AsmProgram? = null ) @@ -52,79 +68,83 @@ object ValidTestCases { code = "int main(void) \n { return (5 - 3) * 4 + ~(-5) / 6 % 3; }", expectedTokenList = listOf( - lexer.Token(lexer.TokenType.KEYWORD_INT, "int", 1, 1), - lexer.Token(lexer.TokenType.IDENTIFIER, "main", 1, 5), - lexer.Token(lexer.TokenType.LEFT_PAREN, "(", 1, 9), - lexer.Token(lexer.TokenType.KEYWORD_VOID, "void", 1, 10), - lexer.Token(lexer.TokenType.RIGHT_PAREN, ")", 1, 14), - lexer.Token(lexer.TokenType.LEFT_BRACK, "{", 2, 2), - lexer.Token(lexer.TokenType.KEYWORD_RETURN, "return", 2, 4), - lexer.Token(lexer.TokenType.LEFT_PAREN, "(", 2, 11), - lexer.Token(lexer.TokenType.INT_LITERAL, "5", 2, 12), - lexer.Token(lexer.TokenType.NEGATION, "-", 2, 14), - lexer.Token(lexer.TokenType.INT_LITERAL, "3", 2, 16), - lexer.Token(lexer.TokenType.RIGHT_PAREN, ")", 2, 17), - lexer.Token(lexer.TokenType.MULTIPLY, "*", 2, 19), - lexer.Token(lexer.TokenType.INT_LITERAL, "4", 2, 21), - lexer.Token(lexer.TokenType.PLUS, "+", 2, 23), - lexer.Token(lexer.TokenType.TILDE, "~", 2, 25), - lexer.Token(lexer.TokenType.LEFT_PAREN, "(", 2, 26), - lexer.Token(lexer.TokenType.NEGATION, "-", 2, 27), - lexer.Token(lexer.TokenType.INT_LITERAL, "5", 2, 28), - lexer.Token(lexer.TokenType.RIGHT_PAREN, ")", 2, 29), - lexer.Token(lexer.TokenType.DIVIDE, "/", 2, 31), - lexer.Token(lexer.TokenType.INT_LITERAL, "6", 2, 33), - lexer.Token(lexer.TokenType.REMAINDER, "%", 2, 35), - lexer.Token(lexer.TokenType.INT_LITERAL, "3", 2, 37), - lexer.Token(lexer.TokenType.SEMICOLON, ";", 2, 38), - lexer.Token(lexer.TokenType.RIGHT_BRACK, "}", 2, 40), - lexer.Token(lexer.TokenType.EOF, "", 2, 41) + Token(TokenType.KEYWORD_INT, "int", 1, 1), + Token(TokenType.IDENTIFIER, "main", 1, 5), + Token(TokenType.LEFT_PAREN, "(", 1, 9), + Token(TokenType.KEYWORD_VOID, "void", 1, 10), + Token(TokenType.RIGHT_PAREN, ")", 1, 14), + Token(TokenType.LEFT_BRACK, "{", 2, 2), + Token(TokenType.KEYWORD_RETURN, "return", 2, 4), + Token(TokenType.LEFT_PAREN, "(", 2, 11), + Token(TokenType.INT_LITERAL, "5", 2, 12), + Token(TokenType.NEGATION, "-", 2, 14), + Token(TokenType.INT_LITERAL, "3", 2, 16), + Token(TokenType.RIGHT_PAREN, ")", 2, 17), + Token(TokenType.MULTIPLY, "*", 2, 19), + Token(TokenType.INT_LITERAL, "4", 2, 21), + Token(TokenType.PLUS, "+", 2, 23), + Token(TokenType.TILDE, "~", 2, 25), + Token(TokenType.LEFT_PAREN, "(", 2, 26), + Token(TokenType.NEGATION, "-", 2, 27), + Token(TokenType.INT_LITERAL, "5", 2, 28), + Token(TokenType.RIGHT_PAREN, ")", 2, 29), + Token(TokenType.DIVIDE, "/", 2, 31), + Token(TokenType.INT_LITERAL, "6", 2, 33), + Token(TokenType.REMAINDER, "%", 2, 35), + Token(TokenType.INT_LITERAL, "3", 2, 37), + Token(TokenType.SEMICOLON, ";", 2, 38), + Token(TokenType.RIGHT_BRACK, "}", 2, 40), + Token(TokenType.EOF, "", 2, 41) ), expectedAst = - parser.SimpleProgram( + SimpleProgram( functionDefinition = - parser.SimpleFunction( + Function( name = "main", body = - parser.ReturnStatement( - expression = - parser.BinaryExpression( - left = - parser.BinaryExpression( - left = - parser.BinaryExpression( - left = parser.IntExpression(5), - operator = lexer.Token(lexer.TokenType.NEGATION, "-", 2, 14), - right = parser.IntExpression(3) - ), - operator = lexer.Token(lexer.TokenType.MULTIPLY, "*", 2, 19), - right = parser.IntExpression(4) - ), - operator = lexer.Token(lexer.TokenType.PLUS, "+", 2, 23), - right = - parser.BinaryExpression( - left = - parser.BinaryExpression( + listOf( + S( + ReturnStatement( + expression = + BinaryExpression( left = - parser.UnaryExpression( - operator = lexer.Token(lexer.TokenType.TILDE, "~", 2, 25), - expression = - parser.UnaryExpression( - operator = - lexer.Token( - lexer.TokenType.NEGATION, - "-", - 2, - 27 - ), - expression = parser.IntExpression(5) - ) + BinaryExpression( + left = + BinaryExpression( + left = IntExpression(5), + operator = Token(TokenType.NEGATION, "-", 2, 14), + right = IntExpression(3) + ), + operator = Token(TokenType.MULTIPLY, "*", 2, 19), + right = IntExpression(4) ), - operator = lexer.Token(lexer.TokenType.DIVIDE, "/", 2, 31), - right = parser.IntExpression(6) - ), - operator = lexer.Token(lexer.TokenType.REMAINDER, "%", 2, 35), - right = parser.IntExpression(3) + operator = Token(TokenType.PLUS, "+", 2, 23), + right = + BinaryExpression( + left = + BinaryExpression( + left = + UnaryExpression( + operator = Token(TokenType.TILDE, "~", 2, 25), + expression = + UnaryExpression( + operator = + Token( + TokenType.NEGATION, + "-", + 2, + 27 + ), + expression = IntExpression(5) + ) + ), + operator = Token(TokenType.DIVIDE, "/", 2, 31), + right = IntExpression(6) + ), + operator = Token(TokenType.REMAINDER, "%", 2, 35), + right = IntExpression(3) + ) + ) ) ) ) @@ -151,7 +171,9 @@ object ValidTestCases { // tmp.1 + tmp.5 -> tmp.6 TackyBinary(TackyBinaryOP.ADD, TackyVar("tmp.1"), TackyVar("tmp.5"), TackyVar("tmp.6")), // Return tmp.6 - TackyRet(TackyVar("tmp.6")) + TackyRet(TackyVar("tmp.6")), + // Return 0 + TackyRet(TackyConstant(0)) ) ) ), @@ -202,7 +224,8 @@ object ValidTestCases { Mov(Stack(-24), Register(HardwareRegister.R10D)), AsmBinary(AsmBinaryOp.ADD, Register(HardwareRegister.R10D), Stack(-28)), // Final return - Mov(Stack(-28), Register(HardwareRegister.EAX)) + Mov(Stack(-28), Register(HardwareRegister.EAX)), + Mov(src = Imm(value = 0), dest = Register(name = HardwareRegister.EAX)) ) ) ) @@ -250,7 +273,8 @@ object ValidTestCases { // End of OR block TackyLabel(".L_or_end_1"), // Final return - TackyRet(TackyVar("tmp.0")) + TackyRet(TackyVar("tmp.0")), + TackyRet(TackyConstant(0)) ) ) ), @@ -303,10 +327,124 @@ object ValidTestCases { Mov(src = Imm(value = 1), dest = Stack(offset = -20)), Label(name = ".L_or_end_1"), // Final return tmp.0 - Mov(src = Stack(offset = -20), dest = Register(name = HardwareRegister.EAX)) + Mov(src = Stack(offset = -20), dest = Register(name = HardwareRegister.EAX)), + Mov(src = Imm(value = 0), dest = Register(name = HardwareRegister.EAX)) + ) + ) + ) + ), + ValidTestCase( + title = "Local variables", + code = + """int main(void) { + |int b; + |int a = 10 + 1; + |b = (a=2) * 2; + |return b; + |; + |} + """.trimMargin(), + expectedTokenList = + listOf( + Token(TokenType.KEYWORD_INT, "int", 1, 1), + Token(TokenType.IDENTIFIER, "main", 1, 5), + Token(TokenType.LEFT_PAREN, "(", 1, 9), + Token(TokenType.KEYWORD_VOID, "void", 1, 10), + Token(TokenType.RIGHT_PAREN, ")", 1, 14), + Token(TokenType.LEFT_BRACK, "{", 1, 16), + // int b; + Token(TokenType.KEYWORD_INT, "int", 2, 1), + Token(TokenType.IDENTIFIER, "b", 2, 5), + Token(TokenType.SEMICOLON, ";", 2, 6), + // int a = 10 + 1; + Token(TokenType.KEYWORD_INT, "int", 3, 1), + Token(TokenType.IDENTIFIER, "a", 3, 5), + Token(TokenType.ASSIGN, "=", 3, 7), + Token(TokenType.INT_LITERAL, "10", 3, 9), + Token(TokenType.PLUS, "+", 3, 12), + Token(TokenType.INT_LITERAL, "1", 3, 14), + Token(TokenType.SEMICOLON, ";", 3, 15), + // b = (a=2) * 2; + Token(TokenType.IDENTIFIER, "b", 4, 1), + Token(TokenType.ASSIGN, "=", 4, 3), + Token(TokenType.LEFT_PAREN, "(", 4, 5), + Token(TokenType.IDENTIFIER, "a", 4, 6), + Token(TokenType.ASSIGN, "=", 4, 7), + Token(TokenType.INT_LITERAL, "2", 4, 8), + Token(TokenType.RIGHT_PAREN, ")", 4, 9), + Token(TokenType.MULTIPLY, "*", 4, 11), + Token(TokenType.INT_LITERAL, "2", 4, 13), + Token(TokenType.SEMICOLON, ";", 4, 14), + // return b; + Token(TokenType.KEYWORD_RETURN, "return", 5, 1), + Token(TokenType.IDENTIFIER, "b", 5, 8), + Token(TokenType.SEMICOLON, ";", 5, 9), + Token(TokenType.SEMICOLON, ";", 6, 1), + Token(TokenType.RIGHT_BRACK, "}", 7, 1), + Token(TokenType.EOF, "", 7, 2) + ), + expectedAst = + SimpleProgram( + functionDefinition = + Function( + name = "main", + body = + listOf( + D(Declaration(name = "b.0", init = null)), + D( + Declaration( + name = "a.1", + init = + BinaryExpression( + left = IntExpression(10), + operator = Token(TokenType.PLUS, "+", 3, 12), + right = IntExpression(1) + ) + ) + ), + S( + ExpressionStatement( + AssignmentExpression( + lvalue = VariableExpression("b.0"), + rvalue = + BinaryExpression( + left = + AssignmentExpression( + lvalue = VariableExpression("a.1"), + rvalue = IntExpression(2) + ), + operator = Token(TokenType.MULTIPLY, "*", 4, 11), + right = IntExpression(2) + ) + ) + ) + ), + S( + ReturnStatement( + expression = VariableExpression("b.0") + ) + ), + S(NullStatement()) + ) + ) + ), + expectedTacky = + TackyProgram( + TackyFunction( + name = "main", + body = + listOf( + TackyBinary(TackyBinaryOP.ADD, TackyConstant(10), TackyConstant(1), TackyVar("tmp.0")), + TackyCopy(TackyVar("tmp.0"), TackyVar("a.1")), + TackyCopy(TackyConstant(2), TackyVar("a.1")), + TackyBinary(TackyBinaryOP.MULTIPLY, TackyVar("a.1"), TackyConstant(2), TackyVar("tmp.1")), + TackyCopy(TackyVar("tmp.1"), TackyVar("b.0")), + TackyRet(TackyVar("b.0")), + TackyRet(TackyConstant(0)) ) ) ) + // No need to test the assembly here since this feature doesn't effect the assembly generation stage ) ) } diff --git a/src/jsTest/kotlin/parser/VariableResolutionTest.kt b/src/jsTest/kotlin/parser/VariableResolutionTest.kt new file mode 100644 index 0000000..dd1b67c --- /dev/null +++ b/src/jsTest/kotlin/parser/VariableResolutionTest.kt @@ -0,0 +1,98 @@ +package parser + +import compiler.parser.VariableResolution +import exceptions.DuplicateVariableDeclaration +import exceptions.UndeclaredVariableException +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class VariableResolutionTest { + @Test + fun testVariableRenamingAndResolution() { + val ast: ASTNode = + SimpleProgram( + functionDefinition = + Function( + name = "main", + body = + listOf( + D(Declaration(name = "a", init = null)), + S( + ExpressionStatement( + AssignmentExpression( + lvalue = VariableExpression("a"), + rvalue = IntExpression(1) + ) + ) + ), + S(ReturnStatement(expression = VariableExpression("a"))) + ) + ) + ) + + val resolved = VariableResolution().visit(ast as SimpleProgram) as SimpleProgram + + val expected: ASTNode = + SimpleProgram( + functionDefinition = + Function( + name = "main", + body = + listOf( + D(Declaration(name = "a.0", init = null)), + S( + ExpressionStatement( + AssignmentExpression( + lvalue = VariableExpression("a.0"), + rvalue = IntExpression(1) + ) + ) + ), + S(ReturnStatement(expression = VariableExpression("a.0"))) + ) + ) + ) + + assertEquals(expected, resolved) + } + + @Test + fun testDuplicateDeclarationThrows() { + val ast: ASTNode = + SimpleProgram( + functionDefinition = + Function( + name = "main", + body = + listOf( + D(Declaration(name = "a", init = null)), + D(Declaration(name = "a", init = null)) + ) + ) + ) + + assertFailsWith { + VariableResolution().visit(ast as SimpleProgram) + } + } + + @Test + fun testUndeclaredVariableThrows() { + val ast: ASTNode = + SimpleProgram( + functionDefinition = + Function( + name = "main", + body = + listOf( + S(ReturnStatement(expression = VariableExpression("x"))) + ) + ) + ) + + assertFailsWith { + VariableResolution().visit(ast as SimpleProgram) + } + } +}