diff --git a/.idea/misc.xml b/.idea/misc.xml index 2c3c021..06d5bb2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 3588036..966eb68 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ parsing, and multiple other phases. - ``jsMain``: contains the core compiler logic - ``jsTest``: test directory for the main logic -These two packages are compiled to JS and used for production +These `js` packages are compiled to JavaScript and used for production. - ``jvmMain`` and ``jvmTest``: generated automatically through building the project. These packages are copied versions of ``jsMain`` and ``jsTest`` without js-specific code and are used only to generate test coverage reports, since Kover (the plugin we use to generate test reports) only supports JVM-compatible Kotlin code. @@ -39,4 +39,21 @@ To generate a test coverage report, ``./gradlew koverHtmlReport`` - _\* These two commands are also part of the build command_ \ No newline at end of file +_All of these commands are also part of the build command_ + +More test cases are found in the test suite of the book "Writing a C Compiler" by Nora Sandler. The test suite is also included in this project and can be run by following the steps below. + + +1. Build the compiler + +``./gradlew build`` + +2. Create a jar file + +``./gradlew createCompilerJar`` + +3. Run the test script + +``cd src/resources/write_a_c_compiler-tests ./test_compiler_kotlin.sh ../../../build/libs/compiler-1.0-SNAPSHOT.jar && cd ../../..`` + +For more information, see the [test suite's README](https://github.com/nlsandler/write_a_c_compiler/blob/master/README.md). \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3bb7bf3..bb4efe1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -130,3 +130,77 @@ tasks.named("build") { tasks.named("koverHtmlReport") { dependsOn("jsTest", "jvmTest") } + +// Task to create the main class +tasks.register("compileMainClass") { + group = "build" + description = "Compiles the main class for the JAR" + val tempDir = temporaryDir + val mainClassFile = File(tempDir, "CompilerMain.java") + // Create the main class file during configuration + val mainClassContent = + """ +package compiler; + +import java.io.File; +import java.nio.file.Files; + +public class CompilerMain { + public static void main(String[] args) { + if (args.length == 0) { + System.out.println("Usage: java -jar compiler.jar "); + System.exit(1); + } + + File inputFile = new File(args[0]); + if (!inputFile.exists()) { + System.out.println("Error: File " + args[0] + " does not exist"); + System.exit(1); + } + + try { + String sourceCode = new String(Files.readAllBytes(inputFile.toPath())); + CompilerWorkflow.Companion.fullCompile(sourceCode); + System.exit(0); + } catch (Exception e) { + System.err.println("Exception: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } +} + """.trimIndent() + + mainClassFile.writeText(mainClassContent) + + source = fileTree(tempDir) { include("**/*.java") } + destinationDirectory = file("$buildDir/classes/java/main") + classpath = kotlin.jvm().compilations["main"].runtimeDependencyFiles + files(kotlin.jvm().compilations["main"].output) + + dependsOn("jvmMainClasses") +} + +// Create executable JAR for JVM target +tasks.register("createCompilerJar") { + group = "build" + description = "Creates an executable JAR for the JVM target" + from(kotlin.jvm().compilations["main"].output) + from("$buildDir/classes/java/main") { + include("compiler/CompilerMain.class") + } + archiveBaseName.set("compiler") + archiveClassifier.set("") + manifest { + attributes["Main-Class"] = "compiler.CompilerMain" + } + dependsOn("jvmMainClasses", "compileMainClass") + // Include all dependencies in the JAR + from( + kotlin + .jvm() + .compilations["main"] + .runtimeDependencyFiles + .map { if (it.isDirectory) it else zipTree(it) } + ) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e583..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 623ed80..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Mon Jul 21 19:49:00 CEST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,22 +133,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index 2d6b66a..7808619 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -3,12 +3,15 @@ package compiler import assembly.AsmConstruct import assembly.InstructionFixer import assembly.PseudoEliminator +import assembly.TackyToAsm import lexer.Lexer import lexer.Token import optimizations.ConstantFolding import optimizations.ControlFlowGraph +import optimizations.CopyPropagation import optimizations.DeadStoreElimination import optimizations.OptimizationType +import optimizations.UnreachableCodeElimination import parser.ASTNode import parser.Parser import parser.SimpleProgram @@ -19,12 +22,12 @@ import semanticAnalysis.TypeChecker import tacky.TackyConstruct import tacky.TackyGenVisitor import tacky.TackyProgram -import tacky.TackyToAsm enum class CompilerStage { LEXER, PARSER, TACKY, + OPTIMIZATIONS, ASSEMBLY } @@ -41,17 +44,30 @@ sealed class CompilerWorkflow { private val pseudoEliminator = PseudoEliminator() private val constantFolding = ConstantFolding() private val deadStoreElimination = DeadStoreElimination() + private val copyPropagation = CopyPropagation() + private val unreachableCodeElimination = UnreachableCodeElimination() fun fullCompile(code: String): Map { val tokens = take(code) val ast = take(tokens) val tacky = take(ast) - val asm = take(tacky as TackyProgram) + val optimizedTacky = + take( + tacky as TackyProgram, + listOf( + OptimizationType.B_CONSTANT_FOLDING, + OptimizationType.D_DEAD_STORE_ELIMINATION, + OptimizationType.C_UNREACHABLE_CODE_ELIMINATION, + OptimizationType.A_COPY_PROPAGATION + ) + ) + val asm = take(optimizedTacky) return mapOf( CompilerStage.LEXER to tokens, CompilerStage.PARSER to ast, CompilerStage.TACKY to tacky, + CompilerStage.OPTIMIZATIONS to optimizedTacky, CompilerStage.ASSEMBLY to asm ) } @@ -75,17 +91,31 @@ sealed class CompilerWorkflow { return tacky } - fun take(tacky: TackyProgram, optimizations: Set): TackyProgram { + fun take( + tackyProgram: TackyProgram, + optimizations: List + ): TackyProgram { + val tacky = tackyProgram.deepCopy() tacky.functions.forEach { - var cfg = ControlFlowGraph().construct(it.name, it.body) - for (optimization in optimizations) { - if (optimization == OptimizationType.CONSTANT_FOLDING) { - cfg = constantFolding.apply(cfg) - } else if (optimization == OptimizationType.DEAD_STORE_ELIMINATION) { - cfg = deadStoreElimination.apply(cfg) + while (true) { + var cfg = ControlFlowGraph().construct(it.name, it.body) + for (optimization in optimizations.sorted()) { + if (optimization == OptimizationType.B_CONSTANT_FOLDING) { + cfg = constantFolding.apply(cfg) + } else if (optimization == OptimizationType.D_DEAD_STORE_ELIMINATION) { + cfg = deadStoreElimination.apply(cfg) + } else if (optimization == OptimizationType.A_COPY_PROPAGATION) { + cfg = copyPropagation.apply(cfg) + } else { + cfg = unreachableCodeElimination.apply(cfg) + } + } + val optimizedBody = cfg.toInstructions() + it.body = optimizedBody + if (optimizedBody == it.body || optimizedBody.isEmpty()) { + break } } - it.body = cfg.toInstructions() } return tacky } diff --git a/src/jsMain/kotlin/assembly/CodeEmitter.kt b/src/jsMain/kotlin/assembly/CodeEmitter.kt index 193fff9..3e9b2b0 100644 --- a/src/jsMain/kotlin/assembly/CodeEmitter.kt +++ b/src/jsMain/kotlin/assembly/CodeEmitter.kt @@ -21,6 +21,7 @@ class CodeEmitter { private fun emitFunction(function: AsmFunction): String { val functionName = formatLabel(function.name) val bodyAsm = function.body.joinToString("\n") { emitInstruction(it) } + val endsWithRet = function.body.lastOrNull() is Ret return buildString { appendLine(" .globl $functionName") @@ -30,9 +31,11 @@ class CodeEmitter { if (bodyAsm.isNotEmpty()) { appendLine(bodyAsm) } - appendLine(" mov rsp, rbp") - appendLine(" pop rbp") - append(" ret") + if (!endsWithRet) { + appendLine(" mov rsp, rbp") + appendLine(" pop rbp") + append(" ret") + } } } @@ -68,7 +71,7 @@ class CodeEmitter { RawInstruction("${indent}set${instruction.condition.text} $destOperand", instruction.sourceId) } - is Ret -> RawInstruction("", instruction.sourceId) + is Ret -> RawInstruction("${indent}mov rsp, rbp\n${indent}pop rbp\n${indent}ret", instruction.sourceId) } } @@ -95,7 +98,7 @@ class CodeEmitter { val destOperand = emitOperand(instruction.dest, size = OperandSize.BYTE) "${indent}set${instruction.condition.text} $destOperand" } - is Ret -> "" + is Ret -> "${indent}mov rsp, rbp\n${indent}pop rbp\n${indent}ret" } } diff --git a/src/jsMain/kotlin/tacky/TackyToAsm.kt b/src/jsMain/kotlin/assembly/TackyToAsm.kt similarity index 92% rename from src/jsMain/kotlin/tacky/TackyToAsm.kt rename to src/jsMain/kotlin/assembly/TackyToAsm.kt index 8871554..cae75f5 100644 --- a/src/jsMain/kotlin/tacky/TackyToAsm.kt +++ b/src/jsMain/kotlin/assembly/TackyToAsm.kt @@ -1,32 +1,22 @@ -package tacky - -import assembly.AllocateStack -import assembly.AsmBinary -import assembly.AsmBinaryOp -import assembly.AsmFunction -import assembly.AsmProgram -import assembly.AsmUnary -import assembly.AsmUnaryOp -import assembly.Call -import assembly.Cdq -import assembly.Cmp -import assembly.ConditionCode -import assembly.DeAllocateStack -import assembly.HardwareRegister -import assembly.Idiv -import assembly.Imm -import assembly.Instruction -import assembly.Jmp -import assembly.JmpCC -import assembly.Label -import assembly.Mov -import assembly.Operand -import assembly.Pseudo -import assembly.Push -import assembly.Register -import assembly.Ret -import assembly.SetCC -import assembly.Stack +package assembly + +import tacky.JumpIfNotZero +import tacky.JumpIfZero +import tacky.TackyBinary +import tacky.TackyBinaryOP +import tacky.TackyConstant +import tacky.TackyCopy +import tacky.TackyFunCall +import tacky.TackyFunction +import tacky.TackyInstruction +import tacky.TackyJump +import tacky.TackyLabel +import tacky.TackyProgram +import tacky.TackyRet +import tacky.TackyUnary +import tacky.TackyUnaryOP +import tacky.TackyVal +import tacky.TackyVar class TackyToAsm { fun convert(tackyProgram: TackyProgram): AsmProgram { @@ -42,7 +32,10 @@ class TackyToAsm { return AsmFunction(tackyFunc.name, paramSetupInstructions + bodyInstructions) } - private fun generateParamSetup(params: List, sourceId: String): List { + private fun generateParamSetup( + params: List, + sourceId: String + ): List { val instructions = mutableListOf() val argRegisters = listOf( @@ -197,6 +190,12 @@ class TackyToAsm { instructions.add(AllocateStack(stackPadding, tackyInstr.sourceId)) } + // Pass arguments in registers + registerArgs.forEachIndexed { index, arg -> + val asmArg = convertVal(arg) + instructions.add(Mov(asmArg, Register(argRegisters[index]), tackyInstr.sourceId)) + } + // Pass arguments on the stack in reverse order stackArgs.asReversed().forEach { arg -> val asmArg = convertVal(arg) @@ -208,12 +207,6 @@ class TackyToAsm { } } - // Pass arguments in registers - registerArgs.forEachIndexed { index, arg -> - val asmArg = convertVal(arg) - instructions.add(Mov(asmArg, Register(argRegisters[index]), tackyInstr.sourceId)) - } - instructions.add(Call(tackyInstr.funName, tackyInstr.sourceId)) // Clean up stack diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index cf39b54..8cf05f8 100644 --- a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -19,12 +19,21 @@ sealed class CompilationException( } } -class LexicalException( +// Lexer +class InvalidCharacterException( character: Char, line: Int? = null, column: Int? = null -) : CompilationException("LexicalException(Invalid character '$character')", line, column) +) : CompilationException("InvalidCharacterException('$character' is not a valid character)", line, column) +class UnexpectedCharacterException( + expected: String, + actual: String, + line: Int? = null, + column: Int? = null +) : CompilationException("UnexpectedCharacterException(Expected '$expected', got '$actual')", line, column) + +// Parser class UnexpectedTokenException( val expected: String, val actual: String, @@ -71,12 +80,6 @@ class InvalidStatementException( column: Int? = null ) : CompilationException("InvalidStatementException($message)", line, column) -class TackyException( - operator: String, - line: Int? = null, - column: Int? = null -) : CompilationException("TackyException(Invalid operator: $operator)", line, column) - class NestedFunctionException( line: Int? = null, column: Int? = null @@ -92,19 +95,23 @@ class IncompatibleFuncDeclarationException( name: String, line: Int? = null, column: Int? = null -) : CompilationException("Function '$name' redeclared with a different number of parameters.", line, column) +) : CompilationException( + "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", + line, + column +) -class NotFunctionException( +class NotAFunctionException( name: String, line: Int? = null, column: Int? = null -) : CompilationException("Cannot call '$name' because it is not a function.", line, column) +) : CompilationException("NotAFunctionException(Cannot call '$name' because it is not a function.)", line, column) -class NotVariableException( +class NotAVariableException( name: String, line: Int? = null, column: Int? = null -) : CompilationException("Cannot use function '$name' as a variable.", line, column) +) : CompilationException("NotAVariableException(Cannot use function '$name' as a variable.)", line, column) class ArgumentCountException( name: String, @@ -112,10 +119,25 @@ class ArgumentCountException( actual: Int, line: Int? = null, column: Int? = null -) : CompilationException("Wrong number of arguments for function '$name'. Expected $expected, got $actual.", line, column) +) : CompilationException( + "ArgumentCountException(Wrong number of arguments for function '$name'. Expected $expected, got $actual.)", + line, + column +) class IllegalStateException( name: String, line: Int? = null, column: Int? = null -) : CompilationException("Internal error: Variable '$name' should have been caught by IdentifierResolution.") +) : CompilationException( + "IllegalStateException(Internal error: Variable '$name' should have been caught by IdentifierResolution.)", + line, + column +) + +// TACKY +class TackyException( + operator: String, + line: Int? = null, + column: Int? = null +) : CompilationException("TackyException(Invalid operator: $operator)", line, column) diff --git a/src/jsMain/kotlin/export/ASTExport.kt b/src/jsMain/kotlin/export/ASTExport.kt index 90f38e2..bcb164a 100644 --- a/src/jsMain/kotlin/export/ASTExport.kt +++ b/src/jsMain/kotlin/export/ASTExport.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -32,7 +33,6 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement fun createJsonNode( @@ -43,22 +43,24 @@ fun createJsonNode( location: parser.SourceLocation? = null, id: String? = null ): JsonObject { - val nodeMap = mutableMapOf( - "type" to JsonPrimitive(type), - "label" to JsonPrimitive(label), - "children" to children, - "edgeLabels" to JsonPrimitive(edgeLabels) - ) + val nodeMap = + mutableMapOf( + "type" to JsonPrimitive(type), + "label" to JsonPrimitive(label), + "children" to children, + "edgeLabels" to JsonPrimitive(edgeLabels) + ) location?.let { - nodeMap["location"] = JsonObject( - mapOf( - "startLine" to JsonPrimitive(it.startLine), - "startCol" to JsonPrimitive(it.startCol), - "endLine" to JsonPrimitive(it.endLine), - "endCol" to JsonPrimitive(it.endCol) + nodeMap["location"] = + JsonObject( + mapOf( + "startLine" to JsonPrimitive(it.startLine), + "startCol" to JsonPrimitive(it.startCol), + "endLine" to JsonPrimitive(it.endLine), + "endCol" to JsonPrimitive(it.endCol) + ) ) - ) } id?.let { @@ -80,7 +82,7 @@ enum class NodeType { Declaration } -class ASTExport : Visitor { +class ASTExport : ASTVisitor { override fun visit(node: SimpleProgram): JsonObject { val decls = JsonArray(node.functionDeclaration.map { it.accept(this) }) return createJsonNode(NodeType.Program.name, "Program", JsonObject(mapOf("declarations" to decls)), false, node.location, node.id) @@ -96,11 +98,14 @@ class ASTExport : Visitor { return createJsonNode(NodeType.Statement.name, "ExpressionStatement", children, false, node.location, node.id) } - override fun visit(node: NullStatement): JsonObject = createJsonNode(NodeType.Statement.name, "NullStatement", JsonObject(emptyMap()), false, node.location, node.id) + override fun visit(node: NullStatement): JsonObject = + createJsonNode(NodeType.Statement.name, "NullStatement", JsonObject(emptyMap()), false, node.location, node.id) - override fun visit(node: BreakStatement): JsonObject = createJsonNode(NodeType.Statement.name, "BreakStatement", JsonObject(emptyMap()), false, node.location, node.id) + override fun visit(node: BreakStatement): JsonObject = + createJsonNode(NodeType.Statement.name, "BreakStatement", JsonObject(emptyMap()), false, node.location, node.id) - override fun visit(node: ContinueStatement): JsonObject = createJsonNode(NodeType.Statement.name, "continue", JsonObject(emptyMap()), false, node.location, node.id) + override fun visit(node: ContinueStatement): JsonObject = + createJsonNode(NodeType.Statement.name, "continue", JsonObject(emptyMap()), false, node.location, node.id) override fun visit(node: WhileStatement): JsonObject { val children = @@ -126,7 +131,7 @@ class ASTExport : Visitor { override fun visit(node: ForStatement): JsonObject { val childrenMap = - mutableMapOf( + mutableMapOf( "init" to node.init.accept(this) ) node.condition?.let { childrenMap["cond"] = it.accept(this) } @@ -142,7 +147,7 @@ class ASTExport : Visitor { } override fun visit(node: InitExpression): JsonObject { - val childrenMap = mutableMapOf() + val childrenMap = mutableMapOf() node.expression?.let { childrenMap["expression"] = it.accept(this) } return createJsonNode(NodeType.Expression.name, "Expression", JsonObject(childrenMap), false, node.location, node.id) } @@ -174,7 +179,13 @@ class ASTExport : Visitor { "expression" to node.expression.accept(this) ) ) - return createJsonNode(NodeType.Expression.name, "UnaryExpression(${node.operator.type})", children, location = node.location, id = node.id) + return createJsonNode( + NodeType.Expression.name, + "UnaryExpression(${node.operator.type})", + children, + location = node.location, + id = node.id + ) } override fun visit(node: BinaryExpression): JsonObject { @@ -185,10 +196,18 @@ class ASTExport : Visitor { "right" to node.right.accept(this) ) ) - return createJsonNode(NodeType.Expression.name, "BinaryExpression(${node.operator.type})", children, edgeLabels = true, location = node.location, id = node.id) + return createJsonNode( + NodeType.Expression.name, + "BinaryExpression(${node.operator.type})", + children, + edgeLabels = true, + location = node.location, + id = node.id + ) } - override fun visit(node: IntExpression): JsonObject = createJsonNode(NodeType.Expression.name, "Int(${node.value})", JsonObject(emptyMap()), false, node.location, node.id) + override fun visit(node: IntExpression): JsonObject = + createJsonNode(NodeType.Expression.name, "Int(${node.value})", JsonObject(emptyMap()), false, node.location, node.id) override fun visit(node: IfStatement): JsonObject { val childrenMap = @@ -204,7 +223,7 @@ class ASTExport : Visitor { val children = JsonObject( mapOf( - "cond" to node.codition.accept(this), + "cond" to node.condition.accept(this), "then" to node.thenExpression.accept(this), "else" to node.elseExpression.accept(this) ) @@ -242,16 +261,24 @@ class ASTExport : Visitor { override fun visit(node: VariableDeclaration): JsonObject { val childrenMap = mutableMapOf("name" to JsonPrimitive(node.name)) node.init?.let { childrenMap["initializer"] = it.accept(this) } - return createJsonNode(NodeType.Declaration.name, "Declaration(${node.name})", JsonObject(childrenMap), false, node.location, node.id) + return createJsonNode( + NodeType.Declaration.name, + "Declaration(${node.name})", + JsonObject(childrenMap), + false, + node.location, + node.id + ) } override fun visit(node: S): JsonObject = node.statement.accept(this) - override fun visit(node: D): JsonObject = when (node.declaration) { - is VarDecl -> node.declaration.accept(this) - is FunDecl -> node.declaration.accept(this) - is VariableDeclaration -> node.declaration.accept(this) - } + override fun visit(node: D): JsonObject = + when (node.declaration) { + is VarDecl -> node.declaration.accept(this) + is FunDecl -> node.declaration.accept(this) + is VariableDeclaration -> node.declaration.accept(this) + } override fun visit(node: Block): JsonObject { val blockItems = node.items.map { it.accept(this) } diff --git a/src/jsMain/kotlin/export/CompilationOutput.kt b/src/jsMain/kotlin/export/CompilationOutput.kt index a796299..9843a90 100644 --- a/src/jsMain/kotlin/export/CompilationOutput.kt +++ b/src/jsMain/kotlin/export/CompilationOutput.kt @@ -44,8 +44,19 @@ data class TackyOutput( override val stage: String = CompilerStage.TACKY.name.lowercase(), val tacky: String? = null, val tackyPretty: String? = null, + override val errors: Array, + val sourceLocation: SourceLocationInfo? = null +) : CompilationOutput() + +@OptIn(ExperimentalJsExport::class) +@JsExport +@Serializable +@SerialName("OptimizationOutput") +data class OptimizationOutput( + override val stage: String = CompilerStage.OPTIMIZATIONS.name.lowercase(), val precomputedCFGs: String = "", - val optimizations: Array = arrayOf("CONSTANT_FOLDING", "DEAD_STORE_ELIMINATION", "COPY_PROPAGATION", "UNREACHABLE_CODE_ELIMINATION"), + val optimizations: Array = + arrayOf("CONSTANT_FOLDING", "DEAD_STORE_ELIMINATION", "COPY_PROPAGATION", "UNREACHABLE_CODE_ELIMINATION"), val functionNames: Array = emptyArray(), override val errors: Array, val sourceLocation: SourceLocationInfo? = null @@ -59,6 +70,7 @@ data class AssemblyOutput( override val stage: String = CompilerStage.ASSEMBLY.name.lowercase(), val assembly: String? = null, val rawAssembly: String? = null, + val precomputedAssembly: String = "", override val errors: Array, val sourceLocation: SourceLocationInfo? = null ) : CompilationOutput() diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index dfd287d..4ba351a 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -38,7 +38,8 @@ data class CFGEdge( data class CFGExport( val functionName: String, val nodes: List, - val edges: List + val edges: List, + val instructionCount: Int ) @Serializable @@ -48,10 +49,15 @@ data class CFGEntry( val cfg: String ) +@Serializable +data class AssemblyEntry( + val optimizations: List, + val asmCode: String +) + @OptIn(ExperimentalJsExport::class) @JsExport class CompilerExport { - private fun calculateSourceLocationInfo(code: String): SourceLocationInfo { val lines = code.split('\n') val totalLines = lines.size @@ -96,21 +102,39 @@ class CompilerExport { outputs.add( TackyOutput( tackyPretty = tackyProgram.toPseudoCode(), - functionNames = tackyProgram.functions.map { it.name }.toTypedArray(), - precomputedCFGs = precomputeAllCFGs(tackyProgram), errors = emptyArray(), tacky = Json.encodeToString(tackyProgram), sourceLocation = sourceLocationInfo ) ) - val asm = CompilerWorkflow.take(tacky as TackyProgram) + val cfgs = precomputeAllCFGs(tackyProgram) + outputs.add( + OptimizationOutput( + precomputedCFGs = cfgs, + functionNames = tackyProgram.functions.map { it.name }.toTypedArray(), + errors = emptyArray() + ) + ) + val optimizedTacky = + CompilerWorkflow.take( + tacky, + optimizations = + listOf( + OptimizationType.B_CONSTANT_FOLDING, + OptimizationType.D_DEAD_STORE_ELIMINATION, + OptimizationType.A_COPY_PROPAGATION, + OptimizationType.C_UNREACHABLE_CODE_ELIMINATION + ) + ) + val asm = CompilerWorkflow.take(optimizedTacky) val finalAssemblyString = codeEmitter.emit(asm as AsmProgram) - val rawAssembly = codeEmitter.emitRaw(asm as AsmProgram) + val rawAssembly = codeEmitter.emitRaw(asm) outputs.add( AssemblyOutput( errors = emptyArray(), assembly = finalAssemblyString, rawAssembly = rawAssembly, + precomputedAssembly = precomputeAllAssembly(tackyProgram), sourceLocation = sourceLocationInfo ) ) @@ -135,6 +159,7 @@ class CompilerExport { CompilerStage.PARSER -> outputs.add(ParserOutput(errors = arrayOf(error), sourceLocation = sourceLocationInfo)) CompilerStage.TACKY -> outputs.add(TackyOutput(errors = arrayOf(error), sourceLocation = sourceLocationInfo)) CompilerStage.ASSEMBLY -> outputs.add(AssemblyOutput(errors = arrayOf(error), sourceLocation = sourceLocationInfo)) + CompilerStage.OPTIMIZATIONS -> outputs.add(OptimizationOutput(errors = arrayOf(error), sourceLocation = sourceLocationInfo)) } } catch (e: Exception) { // Fallback for any unexpected runtime errors @@ -163,33 +188,65 @@ class CompilerExport { } private fun precomputeAllCFGs(program: TackyProgram): String { - val allOptSets = generateOptimizationCombinations() - val cfgs = program.functions.filter { it.body.isNotEmpty() }.flatMap { fn -> - allOptSets.map { optSet -> - try { - val cfg = ControlFlowGraph().construct(fn.name, fn.body) - val types = optSet.mapNotNull(optTypeMap::get).toSet() - val optimized = OptimizationManager.applyOptimizations(cfg, types) - CFGEntry(fn.name, optSet.sorted(), exportControlFlowGraph(optimized)) - } catch (_: Exception) { - CFGEntry(fn.name, optSet.sorted(), createEmptyCFGJson(fn.name)) + val allOptLists = generateOptimizationCombinations() + val cfgs = + program.functions.filter { it.body.isNotEmpty() }.flatMap { fn -> + allOptLists.map { optList -> + try { + val cfg = ControlFlowGraph().construct(fn.name, fn.body) + val types = optList.mapNotNull(optTypeMap::get) + val optimized = OptimizationManager.applyOptimizations(cfg, types) + CFGEntry(fn.name, optList, exportControlFlowGraph(optimized)) + } catch (_: Exception) { + CFGEntry(fn.name, optList.sorted(), createEmptyCFGJson(fn.name)) + } } } - } return Json.encodeToString(cfgs) } - private val optTypeMap = mapOf( - "CONSTANT_FOLDING" to OptimizationType.CONSTANT_FOLDING, - "DEAD_STORE_ELIMINATION" to OptimizationType.DEAD_STORE_ELIMINATION, - "COPY_PROPAGATION" to OptimizationType.COPY_PROPAGATION, - "UNREACHABLE_CODE_ELIMINATION" to OptimizationType.UNREACHABLE_CODE_ELIMINATION - ) + private fun precomputeAllAssembly(program: TackyProgram): String { + val allOptLists = generateOptimizationCombinations() + val assemblies = mutableListOf() + + for (optList in allOptLists) { + val optimizedProgram = + TackyProgram( + functions = + program.functions.map { function -> + if (function.body.isNotEmpty()) { + val cfg = ControlFlowGraph().construct(function.name, function.body) + val optimizedCfg = OptimizationManager.applyOptimizations(cfg, optList.mapNotNull(optTypeMap::get)) + function.copy(body = optimizedCfg.toInstructions()) + } else { + function + } + } + ) + + val asm = CompilerWorkflow.take(optimizedProgram) + val finalAssemblyString = CodeEmitter().emit(asm as AsmProgram) + + // Create one entry per optimization set with the full program assembly + assemblies.add(AssemblyEntry(optList, finalAssemblyString)) + } + + val result = Json.encodeToString(assemblies) + return result + } + + private val optTypeMap = + mapOf( + "CONSTANT_FOLDING" to OptimizationType.B_CONSTANT_FOLDING, + "DEAD_STORE_ELIMINATION" to OptimizationType.D_DEAD_STORE_ELIMINATION, + "COPY_PROPAGATION" to OptimizationType.A_COPY_PROPAGATION, + "UNREACHABLE_CODE_ELIMINATION" to OptimizationType.C_UNREACHABLE_CODE_ELIMINATION + ) - private fun generateOptimizationCombinations(): List> { - val opts = optTypeMap.keys.toList() + private fun generateOptimizationCombinations(): List> { + val opts = optTypeMap.keys.sorted() return (0 until (1 shl opts.size)).map { mask -> - opts.filterIndexed { i, _ -> mask and (1 shl i) != 0 }.toSet() + opts.filterIndexed { i, _ -> mask and (1 shl i) != 0 } } } @@ -198,57 +255,57 @@ class CompilerExport { CFGExport( functionName = fn, nodes = listOf(CFGNode("entry", "Entry", "entry"), CFGNode("exit", "Exit", "exit")), - edges = listOf(CFGEdge("entry", "exit")) + edges = listOf(CFGEdge("entry", "exit")), + instructionCount = 0 ) ) private fun exportControlFlowGraph(cfg: ControlFlowGraph): String { val nodes = mutableListOf(CFGNode("entry", "Entry", "entry")) - nodes += cfg.blocks.mapIndexed { i, block -> - val id = "block_$i" - val label = block.instructions.joinToString(";\n") { it.toPseudoCode(0) }.ifEmpty { "Empty Block" } - CFGNode(id, label, "block") - } + nodes += + cfg.blocks.mapIndexed { i, block -> + val id = "block_$i" + val label = block.instructions.joinToString("\n") { it.toPseudoCode(0) }.ifEmpty { "Empty Block" } + CFGNode(id, label, "block") + } nodes += CFGNode("exit", "Exit", "exit") - val edges = cfg.edges.map { edge -> - val fromId = when (edge.from) { - is START -> "entry" - is EXIT -> "exit" - is Block -> { - // Find block by ID instead of object equality - val index = cfg.blocks.indexOfFirst { it.id == edge.from.id } - if (index >= 0) "block_$index" else "unknown_block" - } - else -> "unknown_block" - } - val toId = when (edge.to) { - is START -> "entry" - is EXIT -> "exit" - is Block -> { - // Find block by ID instead of object equality - val index = cfg.blocks.indexOfFirst { it.id == edge.to.id } - if (index >= 0) "block_$index" else "unknown_block" - } - else -> "unknown_block" + val edges = + cfg.edges.map { edge -> + val fromId = + when (edge.from) { + is START -> "entry" + is EXIT -> "exit" + is Block -> { + // Find block by ID instead of object equality + val index = cfg.blocks.indexOfFirst { it.id == edge.from.id } + if (index >= 0) "block_$index" else "unknown_block" + } + } + val toId = + when (edge.to) { + is START -> "entry" + is EXIT -> "exit" + is Block -> { + // Find block by ID instead of object equality + val index = cfg.blocks.indexOfFirst { it.id == edge.to.id } + if (index >= 0) "block_$index" else "unknown_block" + } + } + CFGEdge(fromId, toId) } - CFGEdge(fromId, toId) - } - return Json.encodeToString(CFGExport(cfg.functionName ?: "unknown", nodes, edges)) - } + // Calculate total instruction count across all blocks + val instructionCount = cfg.blocks.sumOf { it.instructions.size } - private fun Any.toId(cfg: ControlFlowGraph): String = when (this) { - is START -> "entry" - is EXIT -> "exit" - is Block -> { - val index = cfg.blocks.indexOf(this) - if (index >= 0) "block_$index" else "unknown_block" - } - else -> "unknown" + return Json.encodeToString(CFGExport(cfg.functionName ?: "unknown", nodes, edges, instructionCount)) } - fun getCFGForFunction(precomputed: String?, fn: String, enabledOpts: Array): String { + fun getCFGForFunction( + precomputed: String?, + fn: String, + enabledOpts: Array + ): String { if (precomputed == null) return createEmptyCFGJson(fn) val sortedOpts = enabledOpts.sorted() return try { @@ -268,14 +325,15 @@ fun List.toJsonString(): String { mapOf( "type" to JsonPrimitive(token.type.toString()), "lexeme" to JsonPrimitive(token.lexeme), - "location" to JsonObject( - mapOf( - "startLine" to JsonPrimitive(token.startLine), - "startCol" to JsonPrimitive(token.startColumn), - "endLine" to JsonPrimitive(token.endLine), - "endCol" to JsonPrimitive(token.endColumn) + "location" to + JsonObject( + mapOf( + "startLine" to JsonPrimitive(token.startLine), + "startCol" to JsonPrimitive(token.startColumn), + "endLine" to JsonPrimitive(token.endLine), + "endCol" to JsonPrimitive(token.endColumn) + ) ) - ) ) ) } diff --git a/src/jsMain/kotlin/lexer/Lexer.kt b/src/jsMain/kotlin/lexer/Lexer.kt index 543922c..388e32c 100644 --- a/src/jsMain/kotlin/lexer/Lexer.kt +++ b/src/jsMain/kotlin/lexer/Lexer.kt @@ -1,6 +1,7 @@ package lexer -import exceptions.LexicalException +import exceptions.InvalidCharacterException +import exceptions.UnexpectedCharacterException sealed class TokenType { // keywords @@ -189,11 +190,15 @@ class Lexer( '&' -> { if (match('&')) { addToken(TokenType.AND) + } else { + throw UnexpectedCharacterException(char.toString(), "&", line, current - lineStart) } } '|' -> { if (match('|')) { addToken(TokenType.OR) + } else { + throw UnexpectedCharacterException(char.toString(), "|", line, current - lineStart) } } '=' -> { @@ -237,7 +242,7 @@ class Lexer( } else if (isAlphabetic(char)) { identifier() } else { - throw LexicalException(char, line, current - lineStart) + throw InvalidCharacterException(char, line, current - lineStart) } } } diff --git a/src/jsMain/kotlin/optimizations/ConstantFolding.kt b/src/jsMain/kotlin/optimizations/ConstantFolding.kt index 8ba7237..6b20068 100644 --- a/src/jsMain/kotlin/optimizations/ConstantFolding.kt +++ b/src/jsMain/kotlin/optimizations/ConstantFolding.kt @@ -14,60 +14,107 @@ import tacky.TackyUnaryOP import tacky.TackyVal class ConstantFolding : Optimization() { - override val optimizationType = OptimizationType.CONSTANT_FOLDING + override val optimizationType = OptimizationType.B_CONSTANT_FOLDING override fun apply(cfg: ControlFlowGraph): ControlFlowGraph { - val optimizedBlocks = cfg.blocks.map { block -> - val optimizedInstructions = block.instructions.mapNotNull { foldInstruction(it) } - block.copy(instructions = optimizedInstructions) - } + val optimizedBlocks = + cfg.blocks.map { block -> + val optimizedInstructions = + block.instructions.mapNotNull { instruction -> + val folded = foldInstruction(instruction) + if (folded == null && (instruction is JumpIfZero || instruction is JumpIfNotZero)) { + // Remove the edge to the target label since the condition is always false (folded = null) + val target = + when (instruction) { + is JumpIfZero -> instruction.target + is JumpIfNotZero -> instruction.target + else -> null + } + val labelBlock = cfg.blocks.find { it.instructions.first() == target } + if (labelBlock != null) { + block.successors.remove(labelBlock.id) + val edgeToRemove = cfg.edges.find { it.from.id == block.id && it.to.id == labelBlock.id } + if (edgeToRemove != null) { + cfg.edges.remove(edgeToRemove) + } + } + } else if (folded is TackyJump) { + // Remove the edge to the next block if next block ≠ target block, since we're now jumping unconditionally to target + val nextBlock = cfg.blocks.find { it.id == block.id + 1 } + val target = + when (instruction) { + is JumpIfZero -> instruction.target + is JumpIfNotZero -> instruction.target + else -> null + } + val labelBlock = cfg.blocks.find { it.instructions.first() == target } + if (nextBlock != null && nextBlock.id != labelBlock?.id) { + block.successors.remove(block.id + 1) + val edgeToRemove = cfg.edges.find { it.from.id == block.id && it.to.id == block.id + 1 } + if (edgeToRemove != null) { + cfg.edges.remove(edgeToRemove) + } + } + } + folded + } + block.copy(instructions = optimizedInstructions) + } return cfg.copy(blocks = optimizedBlocks) } - private fun foldInstruction(instruction: TackyInstruction): TackyInstruction? = when (instruction) { - is TackyUnary -> foldUnary(instruction) - is TackyBinary -> foldBinary(instruction) - is JumpIfZero -> foldJump(instruction.condition, expectZero = true, target = instruction.target) - is JumpIfNotZero -> foldJump(instruction.condition, expectZero = false, target = instruction.target) - else -> instruction - } + private fun foldInstruction(instruction: TackyInstruction): TackyInstruction? = + when (instruction) { + is TackyUnary -> foldUnary(instruction) + is TackyBinary -> foldBinary(instruction) + is JumpIfZero -> foldJump(instruction.condition, expectZero = true, target = instruction.target) + is JumpIfNotZero -> foldJump(instruction.condition, expectZero = false, target = instruction.target) + else -> instruction + } private fun foldUnary(inst: TackyUnary): TackyInstruction { val src = inst.src as? TackyConstant ?: return inst - val result = when (inst.operator) { - TackyUnaryOP.COMPLEMENT -> src.value.inv() - TackyUnaryOP.NEGATE -> -src.value - TackyUnaryOP.NOT -> if (src.value == 0) 1 else 0 - } - return TackyCopy(TackyConstant(result), inst.dest) + val result = + when (inst.operator) { + TackyUnaryOP.COMPLEMENT -> src.value.inv() + TackyUnaryOP.NEGATE -> -src.value + TackyUnaryOP.NOT -> if (src.value == 0) 1 else 0 + } + return TackyCopy(TackyConstant(result), inst.dest, inst.sourceId) } private fun foldBinary(inst: TackyBinary): TackyInstruction { val lhs = inst.src1 as? TackyConstant ?: return inst val rhs = inst.src2 as? TackyConstant ?: return inst - val result = when (inst.operator) { - TackyBinaryOP.ADD -> lhs.value + rhs.value - TackyBinaryOP.SUBTRACT -> lhs.value - rhs.value - TackyBinaryOP.MULTIPLY -> lhs.value * rhs.value - TackyBinaryOP.DIVIDE -> if (rhs.value == 0) return inst else lhs.value / rhs.value - TackyBinaryOP.REMAINDER -> if (rhs.value == 0) return inst else lhs.value % rhs.value - TackyBinaryOP.LESS -> if (lhs.value < rhs.value) 1 else 0 - TackyBinaryOP.GREATER -> if (lhs.value > rhs.value) 1 else 0 - TackyBinaryOP.LESS_EQUAL -> if (lhs.value <= rhs.value) 1 else 0 - TackyBinaryOP.GREATER_EQUAL -> if (lhs.value >= rhs.value) 1 else 0 - TackyBinaryOP.EQUAL -> if (lhs.value == rhs.value) 1 else 0 - TackyBinaryOP.NOT_EQUAL -> if (lhs.value != rhs.value) 1 else 0 - } + val result = + when (inst.operator) { + TackyBinaryOP.ADD -> lhs.value + rhs.value + TackyBinaryOP.SUBTRACT -> lhs.value - rhs.value + TackyBinaryOP.MULTIPLY -> lhs.value * rhs.value + TackyBinaryOP.DIVIDE -> if (rhs.value == 0) return inst else lhs.value / rhs.value + TackyBinaryOP.REMAINDER -> if (rhs.value == 0) return inst else lhs.value % rhs.value + TackyBinaryOP.LESS -> if (lhs.value < rhs.value) 1 else 0 + TackyBinaryOP.GREATER -> if (lhs.value > rhs.value) 1 else 0 + TackyBinaryOP.LESS_EQUAL -> if (lhs.value <= rhs.value) 1 else 0 + TackyBinaryOP.GREATER_EQUAL -> if (lhs.value >= rhs.value) 1 else 0 + TackyBinaryOP.EQUAL -> if (lhs.value == rhs.value) 1 else 0 + TackyBinaryOP.NOT_EQUAL -> if (lhs.value != rhs.value) 1 else 0 + } - return TackyCopy(TackyConstant(result), inst.dest) + return TackyCopy(TackyConstant(result), inst.dest, inst.sourceId) } - private fun foldJump(condition: TackyVal, expectZero: Boolean, target: TackyLabel): TackyInstruction? { - val constant = condition as? TackyConstant ?: return when (expectZero) { - true -> JumpIfZero(condition, target) - false -> JumpIfNotZero(condition, target) - } + private fun foldJump( + condition: TackyVal, + expectZero: Boolean, + target: TackyLabel + ): TackyInstruction? { + val constant = + condition as? TackyConstant ?: return when (expectZero) { + true -> JumpIfZero(condition, target) + false -> JumpIfNotZero(condition, target) + } return if ((constant.value == 0) == expectZero) { TackyJump(target) } else { diff --git a/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt b/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt index 9a05f74..670e2ca 100644 --- a/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt +++ b/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt @@ -14,14 +14,14 @@ sealed class CFGNode { } data class START( - override val id: Int, + override val id: Int = -1, override val successors: MutableList = mutableListOf() ) : CFGNode() { override val predecessors: MutableList = mutableListOf() } data class EXIT( - override val id: Int, + override val id: Int = -2, override val predecessors: MutableList = mutableListOf() ) : CFGNode() { override val successors: MutableList = mutableListOf() @@ -34,15 +34,21 @@ data class Block( override val successors: MutableList = mutableListOf() ) : CFGNode() -data class Edge(val from: CFGNode, val to: CFGNode) +data class Edge( + val from: CFGNode, + val to: CFGNode +) data class ControlFlowGraph( val functionName: String? = null, val root: CFGNode? = null, val blocks: List = emptyList(), - val edges: List = emptyList() + val edges: MutableList = mutableListOf() ) { - fun construct(functionName: String, functionBody: List): ControlFlowGraph { + fun construct( + functionName: String, + functionBody: List + ): ControlFlowGraph { val nodes = toBasicBlocks(functionBody) val blocks = nodes.filterIsInstance() val edges = buildEdges(nodes, blocks) @@ -54,15 +60,14 @@ data class ControlFlowGraph( ) } - fun toInstructions(): List = - blocks.flatMap { it.instructions } + fun toInstructions(): List = blocks.flatMap { it.instructions } private fun toBasicBlocks(instructions: List): List { val nodes = mutableListOf() val current = mutableListOf() var blockId = 0 - nodes += START(blockId++) + nodes += START() for (inst in instructions) { when (inst) { @@ -86,23 +91,29 @@ data class ControlFlowGraph( nodes += Block(blockId++, current.toList()) } - nodes += EXIT(blockId++) + nodes += EXIT() return nodes } - private fun buildEdges(nodes: List, blocks: List): List { + private fun buildEdges( + nodes: List, + blocks: List + ): MutableList { val edges = mutableListOf() val entry = nodes.filterIsInstance().firstOrNull() val exit = nodes.filterIsInstance().firstOrNull() - fun connect(from: CFGNode, to: CFGNode) { + fun connect( + from: CFGNode, + to: CFGNode + ) { edges += Edge(from, to) from.successors += to.id to.predecessors += from.id } // entry -> first block - blocks.firstOrNull()?.let { connect(entry!!, it) } + blocks.firstOrNull()?.let { connect(entry ?: return@let, it) } for ((i, block) in blocks.withIndex()) { val last = block.instructions.lastOrNull() @@ -116,11 +127,12 @@ data class ControlFlowGraph( } is JumpIfZero, is JumpIfNotZero -> { - val target = when (last) { - is JumpIfZero -> last.target - is JumpIfNotZero -> last.target - else -> null - } + val target = + when (last) { + is JumpIfZero -> last.target + is JumpIfNotZero -> last.target + else -> null + } target?.let { t -> findBlockByLabel(blocks, t)?.let { connect(block, it) } } next?.let { connect(block, next) } } @@ -138,6 +150,8 @@ data class ControlFlowGraph( return edges } - private fun findBlockByLabel(blocks: List, label: TackyLabel): Block? = - blocks.find { blk -> blk.instructions.any { it is TackyLabel && it.name == label.name } } + private fun findBlockByLabel( + blocks: List, + label: TackyLabel + ): Block? = blocks.find { blk -> blk.instructions.any { it is TackyLabel && it.name == label.name } } } diff --git a/src/jsMain/kotlin/optimizations/CopyPropagation.kt b/src/jsMain/kotlin/optimizations/CopyPropagation.kt index 8fc6e79..18c227d 100644 --- a/src/jsMain/kotlin/optimizations/CopyPropagation.kt +++ b/src/jsMain/kotlin/optimizations/CopyPropagation.kt @@ -12,230 +12,104 @@ import tacky.TackyVal import tacky.TackyVar class CopyPropagation : Optimization() { - // A map to store the set of copies reaching the *entry* of each block. - private lateinit var inSets: MutableMap> - // A map to store the set of copies reaching the *exit* of each block. - private lateinit var outSets: MutableMap> - - // A map to store which copies reach each specific instruction. This is needed for the final rewrite. - private val instructionReachingCopies = mutableMapOf>() - - override val optimizationType: OptimizationType = OptimizationType.COPY_PROPAGATION + override val optimizationType: OptimizationType = OptimizationType.A_COPY_PROPAGATION override fun apply(cfg: ControlFlowGraph): ControlFlowGraph { - val newBlocks = cfg.blocks.map { block -> - val newInstructions = mutableListOf() - val copyMap = mutableMapOf() - - for (instr in block.instructions) { - when (instr) { - is TackyCopy -> { - if (instr.src is TackyVar && instr.src.name == instr.dest.name) { - } else { - val newSrc = if (instr.src is TackyVar && copyMap.containsKey(instr.src.name)) { - copyMap[instr.src.name]!! + val newBlocks = + cfg.blocks.map { block -> + val newInstructions = mutableListOf() + val copyMap = mutableMapOf() + + for (instr in block.instructions) { + when (instr) { + is TackyCopy -> { + if (instr.src is TackyVar && instr.src.name == instr.dest.name) { } else { - instr.src + val newSrc = + if (instr.src is TackyVar && copyMap.containsKey(instr.src.name)) { + copyMap[instr.src.name]!! + } else { + instr.src + } + + copyMap[instr.dest.name] = newSrc + newInstructions.add(TackyCopy(newSrc, instr.dest, instr.sourceId)) } - - copyMap[instr.dest.name] = newSrc - newInstructions.add(TackyCopy(newSrc, instr.dest)) } - } - is TackyRet -> { - val newValue = if (instr.value is TackyVar && copyMap.containsKey(instr.value.name)) { - copyMap[instr.value.name]!! - } else { - instr.value + is TackyRet -> { + val newValue = + if (instr.value is TackyVar && copyMap.containsKey(instr.value.name)) { + copyMap[instr.value.name]!! + } else { + instr.value + } + newInstructions.add(TackyRet(newValue, instr.sourceId)) } - newInstructions.add(TackyRet(newValue)) - } - is TackyUnary -> { - val newSrc = if (instr.src is TackyVar && copyMap.containsKey(instr.src.name)) { - copyMap[instr.src.name]!! - } else { - instr.src + is TackyUnary -> { + val newSrc = + if (instr.src is TackyVar && copyMap.containsKey(instr.src.name)) { + copyMap[instr.src.name]!! + } else { + instr.src + } + copyMap.remove(instr.dest.name) + newInstructions.add(TackyUnary(instr.operator, newSrc, instr.dest, instr.sourceId)) } - copyMap.remove(instr.dest.name) - newInstructions.add(TackyUnary(instr.operator, newSrc, instr.dest)) - } - is TackyBinary -> { - val newSrc1 = if (instr.src1 is TackyVar && copyMap.containsKey(instr.src1.name)) { - copyMap[instr.src1.name]!! - } else { - instr.src1 + is TackyBinary -> { + val newSrc1 = + if (instr.src1 is TackyVar && copyMap.containsKey(instr.src1.name)) { + copyMap[instr.src1.name]!! + } else { + instr.src1 + } + val newSrc2 = + if (instr.src2 is TackyVar && copyMap.containsKey(instr.src2.name)) { + copyMap[instr.src2.name]!! + } else { + instr.src2 + } + copyMap.remove(instr.dest.name) + newInstructions.add(TackyBinary(instr.operator, newSrc1, newSrc2, instr.dest, instr.sourceId)) } - val newSrc2 = if (instr.src2 is TackyVar && copyMap.containsKey(instr.src2.name)) { - copyMap[instr.src2.name]!! - } else { - instr.src2 + is TackyFunCall -> { + val newArgs = + instr.args.map { arg -> + if (arg is TackyVar && copyMap.containsKey(arg.name)) { + copyMap[arg.name]!! + } else { + arg + } + } + copyMap.remove(instr.dest.name) + newInstructions.add(TackyFunCall(instr.funName, newArgs, instr.dest, instr.sourceId)) } - copyMap.remove(instr.dest.name) - newInstructions.add(TackyBinary(instr.operator, newSrc1, newSrc2, instr.dest)) - } - is TackyFunCall -> { - val newArgs = instr.args.map { arg -> - if (arg is TackyVar && copyMap.containsKey(arg.name)) { - copyMap[arg.name]!! - } else { - arg - } + is JumpIfZero -> { + val newCondition = + if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { + copyMap[instr.condition.name]!! + } else { + instr.condition + } + newInstructions.add(JumpIfZero(newCondition, instr.target, instr.sourceId)) } - copyMap.remove(instr.dest.name) - newInstructions.add(TackyFunCall(instr.funName, newArgs, instr.dest)) - } - is JumpIfZero -> { - val newCondition = if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { - copyMap[instr.condition.name]!! - } else { - instr.condition + is JumpIfNotZero -> { + val newCondition = + if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { + copyMap[instr.condition.name]!! + } else { + instr.condition + } + newInstructions.add(JumpIfNotZero(newCondition, instr.target, instr.sourceId)) } - newInstructions.add(JumpIfZero(newCondition, instr.target)) - } - is JumpIfNotZero -> { - val newCondition = if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { - copyMap[instr.condition.name]!! - } else { - instr.condition + else -> { + newInstructions.add(instr) } - newInstructions.add(JumpIfNotZero(newCondition, instr.target)) - } - else -> { - newInstructions.add(instr) } } + block.copy(instructions = newInstructions) } - block.copy(instructions = newInstructions) - } return cfg.copy(blocks = newBlocks) } - - private fun runAnalysis(cfg: ControlFlowGraph) { - outSets = mutableMapOf() - instructionReachingCopies.clear() - - val allCopies = cfg.blocks.flatMap { it.instructions }.filterIsInstance().toSet() - - val worklist = cfg.blocks.toMutableList() - cfg.blocks.forEach { - outSets[it.id] = allCopies - } - - while (worklist.isNotEmpty()) { - val block = worklist.removeAt(0) - val inSet = meet(block, allCopies) - val newOut = transfer(block, inSet) - - if (newOut != outSets[block.id]) { - outSets[block.id] = newOut - block.successors.forEach { succId -> - cfg.blocks.find { it.id == succId }?.let { successorBlock -> - if (!worklist.contains(successorBlock)) { - worklist.add(successorBlock) - } - } - } - } - } - } - - private fun meet(block: Block, allCopies: Set): Set { - if (block.predecessors.all { it == 0 }) { - return emptySet() - } - - var incomingCopies: Set = allCopies - - for (predId in block.predecessors) { - val predOutSet = outSets[predId] - if (predOutSet != null) { - incomingCopies = incomingCopies.intersect(predOutSet) - } - } - return incomingCopies - } - - private fun transfer(block: Block, inSet: Set): Set { - var currentCopies = inSet.toMutableSet() - for (instruction in block.instructions) { - instructionReachingCopies[instruction] = currentCopies.toSet() - val toRemove = mutableSetOf() - when (instruction) { - is TackyCopy -> { - val destVar = instruction.dest.name - // Kill all previous copies to or from the destination variable. - currentCopies.forEach { if (it.src is TackyVar && it.src.name == destVar || it.dest.name == destVar) toRemove.add(it) } - currentCopies.removeAll(toRemove) - currentCopies.add(instruction) - } - is TackyUnary -> { - val destVar = instruction.dest.name - // Kill any copies to or from the destination variable. - currentCopies.forEach { if (it.src is TackyVar && it.src.name == destVar || it.dest.name == destVar) toRemove.add(it) } - currentCopies.removeAll(toRemove) - } - is TackyBinary -> { - val destVar = instruction.dest.name - // Kill any copies to or from the destination variable. - currentCopies.forEach { if (it.src is TackyVar && it.src.name == destVar || it.dest.name == destVar) toRemove.add(it) } - currentCopies.removeAll(toRemove) - } - is TackyFunCall -> { - val destVar = instruction.dest.name - // Kill any copies to or from the destination variable. - currentCopies.forEach { if (it.src is TackyVar && it.src.name == destVar || it.dest.name == destVar) toRemove.add(it) } - currentCopies.removeAll(toRemove) - } - else -> {} - } - } - return currentCopies - } - - private fun rewrite(instruction: TackyInstruction, reaching: Set): TackyInstruction { - val substitutionMap = reaching.associate { it.dest.name to it.src } - - return when (instruction) { - is TackyRet -> TackyRet(value = substitute(instruction.value, substitutionMap)) - is TackyUnary -> TackyUnary( - operator = instruction.operator, - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest - ) - is TackyBinary -> TackyBinary( - operator = instruction.operator, - src1 = substitute(instruction.src1, substitutionMap), - src2 = substitute(instruction.src2, substitutionMap), - dest = instruction.dest - ) - is TackyCopy -> TackyCopy( - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest - ) - is TackyFunCall -> TackyFunCall( - funName = instruction.funName, - args = instruction.args.map { substitute(it, substitutionMap) }, - dest = instruction.dest - ) - is JumpIfZero -> JumpIfZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target - ) - is JumpIfNotZero -> JumpIfNotZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target - ) - else -> instruction - } - } - - private fun substitute(value: TackyVal, substitutionMap: Map): TackyVal { - return if (value is TackyVar && substitutionMap.containsKey(value.name)) { - substitutionMap.getValue(value.name) - } else { - value - } - } } diff --git a/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt b/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt index 2ae30cc..9d688fd 100644 --- a/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt +++ b/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt @@ -5,7 +5,6 @@ import tacky.JumpIfZero import tacky.TackyBinary import tacky.TackyCopy import tacky.TackyFunCall -import tacky.TackyInstruction import tacky.TackyJump import tacky.TackyLabel import tacky.TackyRet @@ -13,133 +12,116 @@ import tacky.TackyUnary import tacky.TackyVar class DeadStoreElimination : Optimization() { - override val optimizationType: OptimizationType = OptimizationType.DEAD_STORE_ELIMINATION + override val optimizationType: OptimizationType = OptimizationType.D_DEAD_STORE_ELIMINATION override fun apply(cfg: ControlFlowGraph): ControlFlowGraph { - val livenessAnalysis = LivenessAnalysis() - val liveVariables = livenessAnalysis.analyze(cfg) - - val optimizedBlocks = cfg.blocks.map { block -> - val optimizedInstructions = block.instructions.withIndex().filterNot { (idx, instr) -> - isDeadStore(block.id, idx, instr, liveVariables) - }.map { it.value } - - block.copy(instructions = optimizedInstructions) - } + val liveness = LivenessAnalysis() + val liveAfter = liveness.analyze(cfg) + + val optimizedBlocks = + cfg.blocks.map { block -> + val optimizedInstructions = + block.instructions + .withIndex() + .filterNot { (idx, instr) -> + when (instr) { + is TackyFunCall -> false + is TackyUnary, is TackyBinary, is TackyCopy -> { + val live = liveAfter[block.id to idx] ?: emptySet() + val dest = + when (instr) { + is TackyUnary -> instr.dest.name + is TackyBinary -> instr.dest.name + is TackyCopy -> instr.dest.name + else -> "" + } + dest !in live + } + else -> false + } + }.map { it.value } + block.copy(instructions = optimizedInstructions) + } return cfg.copy(blocks = optimizedBlocks) } - - internal fun isDeadStore( - blockId: Int, - idx: Int, - instruction: TackyInstruction, - liveVariables: Map, Set> - ): Boolean { - // Never eliminate function calls (side effects) - if (instruction is TackyFunCall) return false - - // Only instructions with destinations are considered - val dest = when (instruction) { - is TackyUnary -> instruction.dest.name - is TackyBinary -> instruction.dest.name - is TackyCopy -> instruction.dest.name - else -> return false - } - - val liveAfter = liveVariables[blockId to idx] ?: emptySet() - return dest !in liveAfter - } } class LivenessAnalysis { - fun analyze(cfg: ControlFlowGraph): Map, Set> { - val allStaticVariables = extractStaticVariables(cfg) - val blockOut = mutableMapOf>() - val worklist = ArrayDeque() - - // init: all blocks start with empty live-out - cfg.blocks.forEach { block -> - blockOut[block.id] = emptySet() - worklist.add(block.id) - } - - // backward fixpoint - while (worklist.isNotEmpty()) { - val currentId = worklist.removeFirst() - val currentBlock = cfg.blocks.find { it.id == currentId } ?: continue - - val succLive = currentBlock.successors.flatMap { succId -> - blockOut[succId] ?: emptySet() - }.toSet() + private val instructionAnnotations = mutableMapOf, Set>() + private val blockAnnotations = mutableMapOf>() - if (succLive != blockOut[currentId]) { - blockOut[currentId] = succLive - currentBlock.predecessors.forEach { worklist.add(it) } - } + fun analyze(cfg: ControlFlowGraph): Map, Set> { + for (block in cfg.blocks) { + blockAnnotations[block.id] = emptySet() } - // instruction-level liveness - val instructionLiveVars = mutableMapOf, Set>() - - cfg.blocks.forEach { block -> - var live = blockOut[block.id] ?: emptySet() - - block.instructions.withIndex().reversed().forEach { (idx, instr) -> - instructionLiveVars[block.id to idx] = live - live = transfer(instr, live, allStaticVariables) + val workList = cfg.blocks.map { it.id }.toMutableList() + while (workList.isNotEmpty()) { + val blockId = workList.removeFirst() + val block = cfg.blocks.find { it.id == blockId } ?: continue + val out = meet(block, emptySet()) + val newIn = transfer(block, out) + if (newIn != blockAnnotations[block.id]) { + blockAnnotations[block.id] = newIn + workList.addAll(block.predecessors) } } - return instructionLiveVars + return instructionAnnotations } - internal fun transfer( - instruction: TackyInstruction, + private fun transfer( + block: Block, liveAfter: Set, - allStaticVariables: Set + staticVariables: Set = emptySet() ): Set { val liveBefore = liveAfter.toMutableSet() - - when (instruction) { - is TackyUnary -> { - liveBefore.remove(instruction.dest.name) - if (instruction.src is TackyVar) liveBefore.add(instruction.src.name) - } - is TackyBinary -> { - liveBefore.remove(instruction.dest.name) - if (instruction.src1 is TackyVar) liveBefore.add(instruction.src1.name) - if (instruction.src2 is TackyVar) liveBefore.add(instruction.src2.name) - } - is TackyCopy -> { - liveBefore.remove(instruction.dest.name) - if (instruction.src is TackyVar) liveBefore.add(instruction.src.name) - } - is TackyFunCall -> { - liveBefore.remove(instruction.dest.name) - instruction.args.forEach { arg -> - if (arg is TackyVar) liveBefore.add(arg.name) + block.instructions.withIndex().reversed().forEach { (idx, instruction) -> + instructionAnnotations[block.id to idx] = liveBefore.toSet() + when (instruction) { + is TackyUnary -> { + liveBefore.remove(instruction.dest.name) + if (instruction.src is TackyVar) liveBefore.add(instruction.src.name) } - liveBefore.addAll(allStaticVariables) // conservatively keep statics alive - } - is TackyRet -> { - if (instruction.value is TackyVar) liveBefore.add(instruction.value.name) - } - is JumpIfZero -> { - if (instruction.condition is TackyVar) liveBefore.add(instruction.condition.name) - } - is JumpIfNotZero -> { - if (instruction.condition is TackyVar) liveBefore.add(instruction.condition.name) + is TackyBinary -> { + liveBefore.remove(instruction.dest.name) + if (instruction.src1 is TackyVar) liveBefore.add(instruction.src1.name) + if (instruction.src2 is TackyVar) liveBefore.add(instruction.src2.name) + } + is TackyCopy -> { + liveBefore.remove(instruction.dest.name) + if (instruction.src is TackyVar) liveBefore.add(instruction.src.name) + } + is TackyFunCall -> { + liveBefore.remove(instruction.dest.name) + instruction.args.forEach { arg -> + if (arg is TackyVar) liveBefore.add(arg.name) + } + } + is TackyRet -> { + if (instruction.value is TackyVar) liveBefore.add(instruction.value.name) + } + is JumpIfZero -> { + if (instruction.condition is TackyVar) liveBefore.add(instruction.condition.name) + } + is JumpIfNotZero -> { + if (instruction.condition is TackyVar) liveBefore.add(instruction.condition.name) + } + is TackyJump, is TackyLabel -> {} } - is TackyJump -> { /* no effect */ } - is TackyLabel -> { /* no effect */ } } - return liveBefore } - internal fun extractStaticVariables(cfg: ControlFlowGraph): Set { - // stub: no statics for now - return emptySet() + private fun meet( + block: Block, + allStaticVariables: Set + ): MutableSet { + val liveVariables = mutableSetOf() + for (suc in block.successors) { + liveVariables.addAll(blockAnnotations.getOrElse(suc) { emptySet() }) + } + return liveVariables } } diff --git a/src/jsMain/kotlin/optimizations/Optimization.kt b/src/jsMain/kotlin/optimizations/Optimization.kt index 736d54c..7054d29 100644 --- a/src/jsMain/kotlin/optimizations/Optimization.kt +++ b/src/jsMain/kotlin/optimizations/Optimization.kt @@ -1,42 +1,31 @@ package optimizations -import tacky.TackyProgram - enum class OptimizationType { - CONSTANT_FOLDING, - DEAD_STORE_ELIMINATION, - UNREACHABLE_CODE_ELIMINATION, - COPY_PROPAGATION + B_CONSTANT_FOLDING, + D_DEAD_STORE_ELIMINATION, + C_UNREACHABLE_CODE_ELIMINATION, + A_COPY_PROPAGATION } sealed class Optimization { abstract val optimizationType: OptimizationType + abstract fun apply(cfg: ControlFlowGraph): ControlFlowGraph } object OptimizationManager { - private val optimizations: Map = mapOf( - OptimizationType.CONSTANT_FOLDING to ConstantFolding(), - OptimizationType.DEAD_STORE_ELIMINATION to DeadStoreElimination(), - OptimizationType.UNREACHABLE_CODE_ELIMINATION to UnreachableCodeElimination(), - OptimizationType.COPY_PROPAGATION to CopyPropagation() - ) - - fun optimizeProgram(program: TackyProgram, enabledOptimizations: Set): TackyProgram { - val optimizedFunctions = program.functions.map { function -> - if (function.body.isEmpty()) { - function - } else { - val cfg = ControlFlowGraph().construct(function.name, function.body) - val optimizedCfg = applyOptimizations(cfg, enabledOptimizations) - val optimizedInstructions = optimizedCfg.toInstructions() - function.copy(body = optimizedInstructions) - } - } - return program.copy(functions = optimizedFunctions) - } - - fun applyOptimizations(cfg: ControlFlowGraph, enabledOptimizations: Set): ControlFlowGraph { + private val optimizations: Map = + mapOf( + OptimizationType.B_CONSTANT_FOLDING to ConstantFolding(), + OptimizationType.D_DEAD_STORE_ELIMINATION to DeadStoreElimination(), + OptimizationType.C_UNREACHABLE_CODE_ELIMINATION to UnreachableCodeElimination(), + OptimizationType.A_COPY_PROPAGATION to CopyPropagation() + ) + + fun applyOptimizations( + cfg: ControlFlowGraph, + enabledOptimizations: List + ): ControlFlowGraph { var currentCfg = cfg while (true) { diff --git a/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt b/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt index 88fff96..bf022e5 100644 --- a/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt +++ b/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt @@ -7,7 +7,7 @@ import tacky.TackyJump import tacky.TackyLabel class UnreachableCodeElimination : Optimization() { - override val optimizationType: OptimizationType = OptimizationType.UNREACHABLE_CODE_ELIMINATION + override val optimizationType: OptimizationType = OptimizationType.C_UNREACHABLE_CODE_ELIMINATION override fun apply(cfg: ControlFlowGraph): ControlFlowGraph { var currentCfg = removeUnreachableBlocks(cfg) @@ -38,19 +38,20 @@ class UnreachableCodeElimination : Optimization() { val reachableBlocks = cfg.blocks.filter { it.id in reachableNodeIds } - val reachableEdges = cfg.edges.filter { edge -> - val fromReachable = edge.from.id in reachableNodeIds - val toReachable = edge.to.id in reachableNodeIds - val toExit = edge.to is EXIT + val reachableEdges = + cfg.edges.filter { edge -> + val fromReachable = edge.from.id in reachableNodeIds + val toReachable = edge.to.id in reachableNodeIds + val toExit = edge.to is EXIT - fromReachable && (toReachable || toExit) - } + fromReachable && (toReachable || toExit) + } return ControlFlowGraph( functionName = cfg.functionName, root = cfg.root, blocks = reachableBlocks, - edges = reachableEdges + edges = reachableEdges.toMutableList() ) } @@ -86,10 +87,11 @@ class UnreachableCodeElimination : Optimization() { } // rebuild the blocks with the redundant jumps removed - val newBlocks = cfg.blocks.map { oldBlock -> - val newInstructions = oldBlock.instructions.filterNot { it in jumpsToRemove } - Block(oldBlock.id, newInstructions, oldBlock.predecessors, oldBlock.successors) - } + val newBlocks = + cfg.blocks.map { oldBlock -> + val newInstructions = oldBlock.instructions.filterNot { it in jumpsToRemove } + Block(oldBlock.id, newInstructions, oldBlock.predecessors, oldBlock.successors) + } return ControlFlowGraph( functionName = cfg.functionName, @@ -135,10 +137,11 @@ class UnreachableCodeElimination : Optimization() { return cfg } - val newBlocks = cfg.blocks.map { oldBlock -> - val newInstructions = oldBlock.instructions.filterNot { it in labelsToRemove } - Block(oldBlock.id, newInstructions, oldBlock.predecessors, oldBlock.successors) - } + val newBlocks = + cfg.blocks.map { oldBlock -> + val newInstructions = oldBlock.instructions.filterNot { it in labelsToRemove } + Block(oldBlock.id, newInstructions, oldBlock.predecessors, oldBlock.successors) + } return ControlFlowGraph( functionName = cfg.functionName, @@ -155,10 +158,11 @@ class UnreachableCodeElimination : Optimization() { return cfg } - val blocksToRemove = cfg.blocks - .filter { it.instructions.isEmpty() && it.successors.size <= 1 } - .toMutableSet() - val newEdges = cfg.edges.toMutableList() + val blocksToRemove = + cfg.blocks + .filter { it.instructions.isEmpty() && it.successors.size <= 1 } + .toMutableSet() + val newEdges = cfg.edges val blocksToKeep = cfg.blocks.filter { it !in blocksToRemove }.toMutableList() // map for easy search of nodes by their ID @@ -190,20 +194,25 @@ class UnreachableCodeElimination : Optimization() { functionName = cfg.functionName, root = cfg.root, blocks = blocksToKeep, - edges = newEdges.distinct() + edges = newEdges.distinct().toMutableList() ) } - private fun findNodeById(cfg: ControlFlowGraph, nodeId: Int): CFGNode? { + private fun findNodeById( + cfg: ControlFlowGraph, + nodeId: Int + ): CFGNode? { cfg.blocks.find { it.id == nodeId }?.let { return it } cfg.root?.let { if (it.id == nodeId) return it } return null } - private fun findBlockByLabel(blocks: List, label: TackyLabel): Block? { - return blocks.find { block -> + private fun findBlockByLabel( + blocks: List, + label: TackyLabel + ): Block? = + blocks.find { block -> val firstInstruction = block.instructions.firstOrNull() firstInstruction is TackyLabel && firstInstruction.name == label.name } - } } diff --git a/src/jsMain/kotlin/parser/ASTNode.kt b/src/jsMain/kotlin/parser/ASTNode.kt index df079ab..3364aaf 100644 --- a/src/jsMain/kotlin/parser/ASTNode.kt +++ b/src/jsMain/kotlin/parser/ASTNode.kt @@ -2,8 +2,16 @@ package parser import kotlin.random.Random -data class SourceLocation(val startLine: Int, val startCol: Int, val endLine: Int, val endCol: Int) +data class SourceLocation( + val startLine: Int, + val startCol: Int, + val endLine: Int, + val endCol: Int +) -sealed class ASTNode(open val location: SourceLocation, open val id: String = Random.nextLong().toString()) { - abstract fun accept(visitor: Visitor): T +sealed class ASTNode( + open val location: SourceLocation, + open val id: String = Random.nextLong().toString() +) { + abstract fun accept(visitor: ASTVisitor): T } diff --git a/src/jsMain/kotlin/parser/Visitor.kt b/src/jsMain/kotlin/parser/ASTVisitor.kt similarity index 97% rename from src/jsMain/kotlin/parser/Visitor.kt rename to src/jsMain/kotlin/parser/ASTVisitor.kt index 3cf9e83..532aab1 100644 --- a/src/jsMain/kotlin/parser/Visitor.kt +++ b/src/jsMain/kotlin/parser/ASTVisitor.kt @@ -1,6 +1,6 @@ package parser -interface Visitor { +interface ASTVisitor { fun visit(node: SimpleProgram): T fun visit(node: ReturnStatement): T diff --git a/src/jsMain/kotlin/parser/BlockItems.kt b/src/jsMain/kotlin/parser/BlockItems.kt index 60bb64b..d695c68 100644 --- a/src/jsMain/kotlin/parser/BlockItems.kt +++ b/src/jsMain/kotlin/parser/BlockItems.kt @@ -1,23 +1,27 @@ package parser -sealed class Statement(location: SourceLocation) : ASTNode(location) +sealed class Statement( + location: SourceLocation +) : ASTNode(location) data class ReturnStatement( val expression: Expression, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class ExpressionStatement( val expression: Expression, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } -class NullStatement(override val location: SourceLocation) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) +class NullStatement( + override val location: SourceLocation +) : Statement(location) { + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) override fun equals(other: Any?): Boolean = other is NullStatement @@ -28,14 +32,14 @@ data class BreakStatement( var label: String = "", override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class ContinueStatement( var label: String = "", override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class WhileStatement( @@ -44,7 +48,7 @@ data class WhileStatement( var label: String = "", override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class DoWhileStatement( @@ -53,7 +57,7 @@ data class DoWhileStatement( var label: String = "", override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class ForStatement( @@ -64,23 +68,25 @@ data class ForStatement( var label: String = "", override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } -sealed class ForInit(location: SourceLocation) : ASTNode(location) +sealed class ForInit( + location: SourceLocation +) : ASTNode(location) data class InitDeclaration( val varDeclaration: VariableDeclaration, override val location: SourceLocation ) : ForInit(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class InitExpression( val expression: Expression?, override val location: SourceLocation ) : ForInit(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } class IfStatement( @@ -89,14 +95,14 @@ class IfStatement( val _else: Statement?, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } class GotoStatement( val label: String, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } class LabeledStatement( @@ -104,55 +110,59 @@ class LabeledStatement( val statement: Statement, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } -sealed class Declaration(location: SourceLocation) : ASTNode(location) +sealed class Declaration( + location: SourceLocation +) : ASTNode(location) data class VariableDeclaration( val name: String, val init: Expression?, override val location: SourceLocation ) : Declaration(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class VarDecl( val varDecl: VariableDeclaration ) : Declaration(location = varDecl.location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class FunDecl( val funDecl: FunctionDeclaration ) : Declaration(location = funDecl.location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } -sealed class BlockItem(location: SourceLocation) : ASTNode(location) +sealed class BlockItem( + location: SourceLocation +) : ASTNode(location) data class S( val statement: Statement ) : BlockItem(location = statement.location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class D( val declaration: Declaration ) : BlockItem(declaration.location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class CompoundStatement( val block: Block, override val location: SourceLocation ) : Statement(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class Block( val items: List, override val location: SourceLocation ) : ASTNode(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/Expressions.kt b/src/jsMain/kotlin/parser/Expressions.kt index 6dbe0d8..7a57f55 100644 --- a/src/jsMain/kotlin/parser/Expressions.kt +++ b/src/jsMain/kotlin/parser/Expressions.kt @@ -2,20 +2,22 @@ package parser import lexer.Token -sealed class Expression(location: SourceLocation) : ASTNode(location) +sealed class Expression( + location: SourceLocation +) : ASTNode(location) data class IntExpression( val value: Int, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class VariableExpression( val name: String, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class UnaryExpression( @@ -23,7 +25,7 @@ data class UnaryExpression( val expression: Expression, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class BinaryExpression( @@ -32,7 +34,7 @@ data class BinaryExpression( val right: Expression, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class AssignmentExpression( @@ -40,16 +42,16 @@ data class AssignmentExpression( val rvalue: Expression, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class ConditionalExpression( - val codition: Expression, + val condition: Expression, val thenExpression: Expression, val elseExpression: Expression, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } data class FunctionCall( @@ -57,5 +59,5 @@ data class FunctionCall( val arguments: List, override val location: SourceLocation ) : Expression(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/FunctionDeclaration.kt b/src/jsMain/kotlin/parser/FunctionDeclaration.kt index c478a32..2c0e38a 100644 --- a/src/jsMain/kotlin/parser/FunctionDeclaration.kt +++ b/src/jsMain/kotlin/parser/FunctionDeclaration.kt @@ -8,5 +8,5 @@ data class FunctionDeclaration( val body: Block?, override val location: SourceLocation ) : ASTNode(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/parser/Parser.kt b/src/jsMain/kotlin/parser/Parser.kt index 75ba66a..ecfe1a7 100644 --- a/src/jsMain/kotlin/parser/Parser.kt +++ b/src/jsMain/kotlin/parser/Parser.kt @@ -33,7 +33,12 @@ class Parser { // After a full program is parsed, we must be at EOF expect(TokenType.EOF, tokenSet) if (!tokenSet.isEmpty()) { - throw UnexpectedEndOfFileException() + throw UnexpectedTokenException( + line = tokenSet.first().startLine, + column = tokens.first().startColumn, + expected = TokenType.EOF.toString(), + actual = tokens.first().type.toString() + ) } return ast } @@ -59,15 +64,16 @@ class Parser { val name = parseIdentifier(tokens) expect(TokenType.LEFT_PAREN, tokens) val params = mutableListOf() - if (tokens.firstOrNull()?.type != TokenType.KEYWORD_VOID) { + if (tokens.firstOrNull()?.type == TokenType.KEYWORD_VOID) { + tokens.removeFirst() // consume 'void' + } else if (tokens.firstOrNull()?.type == TokenType.KEYWORD_INT) { // get params do { expect(TokenType.KEYWORD_INT, tokens) params.add(parseIdentifier(tokens)) } while (tokens.firstOrNull()?.type == TokenType.COMMA && tokens.removeFirst().type == TokenType.COMMA) - } else { - tokens.removeFirst() // consume 'void' } + // If neither void nor int, assume no parameters (empty parameter list) val endParan = expect(TokenType.RIGHT_PAREN, tokens) val body: Block? val endLine: Int @@ -86,18 +92,23 @@ class Parser { return FunctionDeclaration(name, params, body, SourceLocation(func.startLine, func.startColumn, endLine, endColumn)) } - private fun parseFunctionDeclarationFromBody(tokens: MutableList, name: String, location: SourceLocation): FunctionDeclaration { + private fun parseFunctionDeclarationFromBody( + tokens: MutableList, + name: String, + location: SourceLocation + ): FunctionDeclaration { expect(TokenType.LEFT_PAREN, tokens) val params = mutableListOf() - if (tokens.firstOrNull()?.type != TokenType.KEYWORD_VOID) { + if (tokens.firstOrNull()?.type == TokenType.KEYWORD_VOID) { + tokens.removeFirst() // consume 'void' + } else if (tokens.firstOrNull()?.type == TokenType.KEYWORD_INT) { // get params do { expect(TokenType.KEYWORD_INT, tokens) params.add(parseIdentifier(tokens)) } while (tokens.firstOrNull()?.type == TokenType.COMMA && tokens.removeFirst().type == TokenType.COMMA) - } else { - tokens.removeFirst() // consume 'void' } + // If neither void nor int, assume no parameters (empty parameter list) val end = expect(TokenType.RIGHT_PAREN, tokens) val body: Block? val finalLocation: SourceLocation @@ -128,12 +139,20 @@ class Parser { if (tokens.firstOrNull()?.type == TokenType.KEYWORD_INT) { val lookaheadTokens = tokens.toMutableList() val start = expect(TokenType.KEYWORD_INT, lookaheadTokens) - val name = parseIdentifier(lookaheadTokens) + parseIdentifier(lookaheadTokens) if (lookaheadTokens.firstOrNull()?.type == TokenType.LEFT_PAREN) { - expect(TokenType.KEYWORD_INT, tokens) + expect(TokenType.KEYWORD_INT, tokens) // consume the int keyword val actualName = parseIdentifier(tokens) - D(FunDecl(parseFunctionDeclarationFromBody(tokens, actualName, SourceLocation(start.startLine, start.startColumn, start.endLine, start.endColumn)))) + D( + FunDecl( + parseFunctionDeclarationFromBody( + tokens, + actualName, + SourceLocation(start.startLine, start.startColumn, start.endLine, start.endColumn) + ) + ) + ) } else { val end = expect(TokenType.KEYWORD_INT, tokens) val actualName = parseIdentifier(tokens) @@ -214,7 +233,12 @@ class Parser { endLine = elseStatement.location.endLine endCol = elseStatement.location.endCol } - return IfStatement(condition, thenStatement, elseStatement, SourceLocation(ifToken.startLine, ifToken.startColumn, endLine, endCol)) + return IfStatement( + condition, + thenStatement, + elseStatement, + SourceLocation(ifToken.startLine, ifToken.startColumn, endLine, endCol) + ) } TokenType.KEYWORD_RETURN -> { val returnToken = expect(TokenType.KEYWORD_RETURN, tokens) @@ -229,7 +253,10 @@ class Parser { val gotoToken = expect(TokenType.GOTO, tokens) val label = parseIdentifier(tokens) val semicolonToken = expect(TokenType.SEMICOLON, tokens) - return GotoStatement(label, SourceLocation(gotoToken.startLine, gotoToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn)) + return GotoStatement( + label, + SourceLocation(gotoToken.startLine, gotoToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn) + ) } TokenType.IDENTIFIER -> { // Handle labeled statements: IDENTIFIER followed by COLON @@ -238,22 +265,38 @@ class Parser { val labelName = labelToken.lexeme expect(TokenType.COLON, tokens) val statement = parseStatement(tokens) - return LabeledStatement(labelName, statement, SourceLocation(labelToken.startLine, labelToken.startColumn, statement.location.endLine, statement.location.endCol)) + return LabeledStatement( + labelName, + statement, + SourceLocation(labelToken.startLine, labelToken.startColumn, statement.location.endLine, statement.location.endCol) + ) } else { // Not a label, parse as expression statement by delegating to default branch val expression = parseOptionalExpression(tokens = tokens, followedByType = TokenType.SEMICOLON) - return if (expression != null) ExpressionStatement(expression, expression.location) else NullStatement(SourceLocation(0, 0, 0, 0)) + return if (expression != + null + ) { + ExpressionStatement(expression, expression.location) + } else { + NullStatement(SourceLocation(0, 0, 0, 0)) + } } } TokenType.KEYWORD_BREAK -> { val breakToken = expect(TokenType.KEYWORD_BREAK, tokens) val semicolonToken = expect(TokenType.SEMICOLON, tokens) - return BreakStatement("", SourceLocation(breakToken.startLine, breakToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn)) + return BreakStatement( + "", + SourceLocation(breakToken.startLine, breakToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn) + ) } TokenType.KEYWORD_CONTINUE -> { val continueToken = expect(TokenType.KEYWORD_CONTINUE, tokens) val semicolonToken = expect(TokenType.SEMICOLON, tokens) - return ContinueStatement("", SourceLocation(continueToken.startLine, continueToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn)) + return ContinueStatement( + "", + SourceLocation(continueToken.startLine, continueToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn) + ) } TokenType.KEYWORD_WHILE -> { val whileToken = expect(TokenType.KEYWORD_WHILE, tokens) @@ -261,7 +304,12 @@ class Parser { val condition = parseExpression(tokens = tokens) expect(TokenType.RIGHT_PAREN, tokens) val body = parseStatement(tokens) - return WhileStatement(condition, body, "", SourceLocation(whileToken.startLine, whileToken.startColumn, body.location.endLine, body.location.endCol)) + return WhileStatement( + condition, + body, + "", + SourceLocation(whileToken.startLine, whileToken.startColumn, body.location.endLine, body.location.endCol) + ) } TokenType.KEYWORD_DO -> { val doToken = expect(TokenType.KEYWORD_DO, tokens) @@ -271,7 +319,12 @@ class Parser { val condition = parseExpression(tokens = tokens) expect(TokenType.RIGHT_PAREN, tokens) val semicolonToken = expect(TokenType.SEMICOLON, tokens) - return DoWhileStatement(condition, body, "", SourceLocation(doToken.startLine, doToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn)) + return DoWhileStatement( + condition, + body, + "", + SourceLocation(doToken.startLine, doToken.startColumn, semicolonToken.endLine, semicolonToken.endColumn) + ) } TokenType.KEYWORD_FOR -> { val forToken = expect(TokenType.KEYWORD_FOR, tokens) @@ -295,7 +348,13 @@ class Parser { } else -> { val expression = parseOptionalExpression(tokens = tokens, followedByType = TokenType.SEMICOLON) - return if (expression != null) ExpressionStatement(expression, expression.location) else NullStatement(SourceLocation(0, 0, 0, 0)) + return if (expression != + null + ) { + ExpressionStatement(expression, expression.location) + } else { + NullStatement(SourceLocation(0, 0, 0, 0)) + } } } } @@ -304,8 +363,12 @@ class Parser { if (tokens.firstOrNull()?.type == TokenType.KEYWORD_INT) { val start = expect(TokenType.KEYWORD_INT, tokens) val name = parseIdentifier(tokens) - val declaration = parseVariableDeclaration(tokens, name, SourceLocation(start.startLine, start.startColumn, start.endLine, start.endColumn)) - return InitDeclaration(declaration, SourceLocation(start.startLine, start.startColumn, declaration.location.endLine, declaration.location.endCol)) + val declaration = + parseVariableDeclaration(tokens, name, SourceLocation(start.startLine, start.startColumn, start.endLine, start.endColumn)) + return InitDeclaration( + declaration, + SourceLocation(start.startLine, start.startColumn, declaration.location.endLine, declaration.location.endCol) + ) } val expression = parseOptionalExpression(tokens = tokens, followedByType = TokenType.SEMICOLON) return InitExpression(expression, expression?.location ?: SourceLocation(0, 0, 0, 0)) @@ -328,16 +391,30 @@ class Parser { when (nextType) { TokenType.ASSIGN -> { if (left !is VariableExpression) { - throw InvalidLValueException() + throw InvalidLValueException(line = left.location.startLine, column = left.location.startCol) } val right = parseExpression(prec, tokens) - AssignmentExpression(left, right, SourceLocation(left.location.startLine, left.location.startCol, right.location.endLine, right.location.endCol)) + AssignmentExpression( + left, + right, + SourceLocation(left.location.startLine, left.location.startCol, right.location.endLine, right.location.endCol) + ) } TokenType.QUESTION_MARK -> { - val thenExpression = parseExpression(prec, tokens) + val thenExpression = parseExpression(tokens = tokens) expect(TokenType.COLON, tokens) val elseExpression = parseExpression(prec, tokens) - return ConditionalExpression(left, thenExpression, elseExpression, SourceLocation(left.location.startLine, left.location.startCol, elseExpression.location.endLine, elseExpression.location.endCol)) + return ConditionalExpression( + left, + thenExpression, + elseExpression, + SourceLocation( + left.location.startLine, + left.location.startCol, + elseExpression.location.endLine, + elseExpression.location.endCol + ) + ) } else -> { val right = parseExpression(prec + 1, tokens) @@ -372,13 +449,16 @@ class Parser { when (nextToken.type) { TokenType.INT_LITERAL -> { nextToken = tokens.removeFirst() - return IntExpression(value = nextToken.lexeme.toInt(), SourceLocation(nextToken.startLine, nextToken.startColumn, nextToken.endLine, nextToken.endColumn)) + return IntExpression( + value = nextToken.lexeme.toInt(), + SourceLocation(nextToken.startLine, nextToken.startColumn, nextToken.endLine, nextToken.endColumn) + ) } TokenType.IDENTIFIER -> { nextToken = tokens.removeFirst() if (tokens.firstOrNull()?.type == TokenType.LEFT_PAREN) { // function call - val leftParen = tokens.removeFirst() // consume '(' + tokens.removeFirst() // consume '(' val args = mutableListOf() if (tokens.firstOrNull()?.type != TokenType.RIGHT_PAREN) { do { @@ -386,17 +466,28 @@ class Parser { } while (tokens.firstOrNull()?.type == TokenType.COMMA && tokens.removeFirst().type == TokenType.COMMA) } val rightParen = expect(TokenType.RIGHT_PAREN, tokens) - return FunctionCall(nextToken.lexeme, args, SourceLocation(nextToken.startLine, nextToken.startColumn, rightParen.endLine, rightParen.endColumn)) + return FunctionCall( + nextToken.lexeme, + args, + SourceLocation(nextToken.startLine, nextToken.startColumn, rightParen.endLine, rightParen.endColumn) + ) } else { // It's a variable - return VariableExpression(nextToken.lexeme, SourceLocation(nextToken.startLine, nextToken.startColumn, nextToken.endLine, nextToken.endColumn)) + return VariableExpression( + nextToken.lexeme, + SourceLocation(nextToken.startLine, nextToken.startColumn, nextToken.endLine, nextToken.endColumn) + ) } } TokenType.TILDE, TokenType.NEGATION, TokenType.NOT -> { val operator = tokens.removeFirst() val factor = parseFactor(tokens) - return UnaryExpression(operator = operator, expression = factor, SourceLocation(operator.startLine, operator.startColumn, factor.location.endLine, factor.location.endCol)) + return UnaryExpression( + operator = operator, + expression = factor, + SourceLocation(operator.startLine, operator.startColumn, factor.location.endLine, factor.location.endCol) + ) } TokenType.LEFT_PAREN -> { expect(TokenType.LEFT_PAREN, tokens) diff --git a/src/jsMain/kotlin/parser/Programs.kt b/src/jsMain/kotlin/parser/Programs.kt index 05694cc..ddcc20e 100644 --- a/src/jsMain/kotlin/parser/Programs.kt +++ b/src/jsMain/kotlin/parser/Programs.kt @@ -1,10 +1,12 @@ package parser -sealed class Program(location: SourceLocation) : ASTNode(location) +sealed class Program( + location: SourceLocation +) : ASTNode(location) data class SimpleProgram( val functionDeclaration: List, override val location: SourceLocation ) : Program(location) { - override fun accept(visitor: Visitor): T = visitor.visit(this) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) } diff --git a/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt index a406266..f961447 100644 --- a/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt +++ b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt @@ -4,6 +4,7 @@ import exceptions.DuplicateVariableDeclaration import exceptions.MissingDeclarationException import exceptions.NestedFunctionException import parser.ASTNode +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -36,7 +37,6 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement data class SymbolInfo( @@ -44,7 +44,7 @@ data class SymbolInfo( val hasLinkage: Boolean ) -class IdentifierResolution : Visitor { +class IdentifierResolution : ASTVisitor { private var tempCounter = 0 private fun newTemporary(name: String): String = "$name.${tempCounter++}" @@ -67,7 +67,9 @@ class IdentifierResolution : Visitor { private fun declare( name: String, - hasLinkage: Boolean + hasLinkage: Boolean, + line: Int = -1, + col: Int = -1 ): String { val currentScope = scopeStack.last() val existing = currentScope[name] @@ -75,7 +77,7 @@ class IdentifierResolution : Visitor { if (existing != null) { // A redeclaration in the same scope is only okay if both have linkage. if (!existing.hasLinkage || !hasLinkage) { - throw DuplicateVariableDeclaration() + throw DuplicateVariableDeclaration(line, col) } // If both have linkage (e.g., two function declarations), it's okay. return existing.uniqueName @@ -89,13 +91,17 @@ class IdentifierResolution : Visitor { private fun leaveScope() = scopeStack.removeAt(scopeStack.lastIndex) - private fun resolve(name: String): SymbolInfo { + private fun resolve( + name: String, + line: Int = -1, + col: Int = -1 + ): SymbolInfo { scopeStack.asReversed().forEach { scope -> if (scope.containsKey(name)) { return scope.getValue(name) } } - throw MissingDeclarationException(name) + throw MissingDeclarationException(name, line, col) } override fun visit(node: SimpleProgram): ASTNode { @@ -161,19 +167,19 @@ class IdentifierResolution : Visitor { // We're inside another function - check if this is a prototype or definition if (node.body != null) { // Function definition with body - not allowed inside other functions - throw NestedFunctionException() + throw NestedFunctionException(node.location.startLine, node.location.startCol) } else { - declare(node.name, hasLinkage = true) + declare(node.name, hasLinkage = true, node.location.startLine, node.location.startCol) return FunctionDeclaration(node.name, node.params, null, node.location) } } else { - declare(node.name, hasLinkage = true) + declare(node.name, hasLinkage = true, node.location.startLine, node.location.startCol) enterScope() val newParams = node.params.map { paramName -> - declare(paramName, hasLinkage = false) + declare(paramName, hasLinkage = false, node.location.startLine, node.location.startCol) } val newBody = node.body?.accept(this) as Block? @@ -209,7 +215,7 @@ class IdentifierResolution : Visitor { } override fun visit(node: ConditionalExpression): ASTNode { - val condition = node.codition.accept(this) as Expression + val condition = node.condition.accept(this) as Expression val thenExpression = node.thenExpression.accept(this) as Expression val elseExpression = node.elseExpression.accept(this) as Expression return ConditionalExpression(condition, thenExpression, elseExpression, node.location) @@ -231,7 +237,7 @@ class IdentifierResolution : Visitor { override fun visit(node: VariableDeclaration): ASTNode { val newInit = node.init?.accept(this) as Expression? - val uniqueName = declare(node.name, hasLinkage = false) + val uniqueName = declare(node.name, hasLinkage = false, node.location.startLine, node.location.startCol) return VariableDeclaration(uniqueName, newInit, node.location) } @@ -241,14 +247,12 @@ class IdentifierResolution : Visitor { return S(statement) } - override fun visit(node: D): ASTNode { - val declaration = node.declaration.accept(this) - return when (declaration) { + override fun visit(node: D): ASTNode = + when (val declaration = node.declaration.accept(this)) { is VarDecl -> D(declaration) is FunDecl -> D(declaration) else -> throw IllegalStateException("Unexpected declaration type: ${declaration::class.simpleName}") } - } override fun visit(node: VarDecl): ASTNode { val newVarDeclData = node.varDecl.accept(this) as VariableDeclaration @@ -259,21 +263,21 @@ class IdentifierResolution : Visitor { val funDecl = node.funDecl.accept(this) as FunctionDeclaration if (funDecl.body != null) { - throw NestedFunctionException() + throw NestedFunctionException(node.location.startLine, node.location.startCol) } else { return FunDecl(funDecl) } } override fun visit(node: Block): ASTNode { - enterScope() val newItems = node.items.map { it.accept(this) as BlockItem } - leaveScope() return Block(newItems, node.location) } override fun visit(node: CompoundStatement): ASTNode { + enterScope() val newBlock = node.block.accept(this) as Block + leaveScope() return CompoundStatement(newBlock, node.location) } diff --git a/src/jsMain/kotlin/semanticAnalysis/LabelCollector.kt b/src/jsMain/kotlin/semanticAnalysis/LabelCollector.kt index 12c1a26..0c39f38 100644 --- a/src/jsMain/kotlin/semanticAnalysis/LabelCollector.kt +++ b/src/jsMain/kotlin/semanticAnalysis/LabelCollector.kt @@ -3,6 +3,7 @@ package semanticAnalysis import exceptions.DuplicateLabelException import exceptions.UndeclaredLabelException import parser.ASTNode +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -31,10 +32,9 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement -class LabelCollector : Visitor { +class LabelCollector : ASTVisitor { val definedLabels: MutableSet = mutableSetOf() override fun visit(node: LabeledStatement) { @@ -150,7 +150,7 @@ class LabelCollector : Visitor { private class GotoValidator( private val definedLabels: Set - ) : Visitor { + ) : ASTVisitor { override fun visit(node: GotoStatement) { if (node.label !in definedLabels) { throw UndeclaredLabelException(node.label) diff --git a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt index bc1eb91..d8f134b 100644 --- a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt +++ b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt @@ -1,6 +1,7 @@ package semanticAnalysis import exceptions.InvalidStatementException +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -29,10 +30,9 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement -class LoopLabeling : Visitor { +class LoopLabeling : ASTVisitor { var currentLabel: String? = null private var labelCounter = 0 @@ -56,14 +56,14 @@ class LoopLabeling : Visitor { override fun visit(node: BreakStatement) { if (currentLabel == null) { - throw InvalidStatementException("Break statement outside of loop") + throw InvalidStatementException("Break statement outside of loop", node.location.startLine, node.location.startCol) } node.label = currentLabel!! } override fun visit(node: ContinueStatement) { if (currentLabel == null) { - throw InvalidStatementException("Continue statement outside of loop") + throw InvalidStatementException("Continue statement outside of loop", node.location.startLine, node.location.startCol) } node.label = currentLabel!! } @@ -86,7 +86,7 @@ class LoopLabeling : Visitor { override fun visit(node: ForStatement) { currentLabel = newLabel() - node.label = currentLabel!! + node.label = (currentLabel ?: return) node.body.accept(this) currentLabel = null node.post?.accept(this) @@ -128,7 +128,7 @@ class LoopLabeling : Visitor { } override fun visit(node: ConditionalExpression) { - node.codition.accept(this) + node.condition.accept(this) node.thenExpression.accept(this) node.elseExpression.accept(this) } diff --git a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt index a58c97e..2102623 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -2,9 +2,10 @@ package semanticAnalysis import exceptions.ArgumentCountException import exceptions.IncompatibleFuncDeclarationException -import exceptions.NotFunctionException -import exceptions.NotVariableException +import exceptions.NotAFunctionException +import exceptions.NotAVariableException import exceptions.ReDeclarationFunctionException +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -33,10 +34,9 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement -class TypeChecker : Visitor { +class TypeChecker : ASTVisitor { fun analyze(program: SimpleProgram) { SymbolTable.clear() // Ensure the table is fresh for each compilation. program.accept(this) @@ -86,11 +86,11 @@ class TypeChecker : Visitor { val existingSymbol = SymbolTable.get(node.name) if (existingSymbol != null) { if (existingSymbol.type != funType) { - throw IncompatibleFuncDeclarationException(node.name) + throw IncompatibleFuncDeclarationException(node.name, node.location.startLine, node.location.startCol) } isAlreadyDefined = existingSymbol.isDefined if (isAlreadyDefined && hasBody) { - throw ReDeclarationFunctionException("Function '${node.name}' cannot be defined more than once.") + throw ReDeclarationFunctionException(node.name) } } @@ -101,7 +101,7 @@ class TypeChecker : Visitor { node.params.forEach { paramName -> SymbolTable.add(paramName, Symbol(IntType, isDefined = true)) } - node.body!!.accept(this) + node.body.accept(this) } } @@ -111,7 +111,7 @@ class TypeChecker : Visitor { ?: throw IllegalStateException(node.name) if (symbol.type !is IntType) { - throw NotVariableException(node.name) + throw NotAVariableException(node.name, node.location.startLine, node.location.startCol) } } @@ -134,7 +134,7 @@ class TypeChecker : Visitor { } override fun visit(node: ConditionalExpression) { - node.codition.accept(this) + node.condition.accept(this) node.thenExpression.accept(this) node.elseExpression.accept(this) } @@ -185,7 +185,7 @@ class TypeChecker : Visitor { ?: throw exceptions.IllegalStateException(node.name) when (val type = symbol.type) { - is IntType -> throw NotFunctionException(node.name) + is IntType -> throw NotAFunctionException(node.name) is FunType -> { if (type.paramCount != node.arguments.size) { throw ArgumentCountException( diff --git a/src/jsMain/kotlin/tacky/Instructions.kt b/src/jsMain/kotlin/tacky/Instructions.kt index 69da434..5d76ba1 100644 --- a/src/jsMain/kotlin/tacky/Instructions.kt +++ b/src/jsMain/kotlin/tacky/Instructions.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable @Serializable sealed class TackyInstruction() : TackyConstruct() { abstract val sourceId: String + abstract fun deepCopy(): TackyInstruction } @Serializable @@ -16,6 +17,8 @@ data class TackyRet( ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}return ${value.toPseudoCode()}" + + override fun deepCopy(): TackyInstruction = TackyRet(value.deepCopy(), sourceId) } enum class TackyUnaryOP( @@ -36,6 +39,8 @@ data class TackyUnary( ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}${dest.toPseudoCode()} = ${operator.text}${src.toPseudoCode()}" + + override fun deepCopy(): TackyInstruction = TackyUnary(operator, src.deepCopy(), dest.deepCopy() as TackyVar, sourceId) } enum class TackyBinaryOP( @@ -50,7 +55,7 @@ enum class TackyBinaryOP( GREATER(">"), LESS_EQUAL("<="), GREATER_EQUAL(">="), - EQUAL("="), + EQUAL("=="), NOT_EQUAL("!=") } @@ -65,6 +70,8 @@ data class TackyBinary( ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}${dest.toPseudoCode()} = ${src1.toPseudoCode()} ${operator.text} ${src2.toPseudoCode()}" + + override fun deepCopy(): TackyInstruction = TackyBinary(operator, src1.deepCopy(), src2.deepCopy(), dest.deepCopy() as TackyVar, sourceId) } @Serializable @@ -75,6 +82,8 @@ data class TackyCopy( override val sourceId: String = "" ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}${dest.toPseudoCode()} = ${src.toPseudoCode()}" + + override fun deepCopy(): TackyInstruction = TackyCopy(src.deepCopy(), dest.deepCopy() as TackyVar, sourceId) } @Serializable @@ -84,6 +93,8 @@ data class TackyJump( override val sourceId: String = "" ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}goto ${target.name}" + + override fun deepCopy(): TackyInstruction = TackyJump(target.deepCopy() as TackyLabel, sourceId) } @Serializable @@ -95,6 +106,8 @@ data class JumpIfZero( ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}if (${condition.toPseudoCode()} == 0) goto ${target.name}" + + override fun deepCopy(): TackyInstruction = JumpIfZero(condition.deepCopy(), target.deepCopy() as TackyLabel, sourceId) } @Serializable @@ -106,6 +119,8 @@ data class JumpIfNotZero( ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "${indent(indentationLevel)}if (${condition.toPseudoCode()} != 0) goto ${target.name}" + + override fun deepCopy(): TackyInstruction = JumpIfNotZero(condition.deepCopy(), target.deepCopy() as TackyLabel, sourceId) } @Serializable @@ -120,6 +135,8 @@ data class TackyFunCall( val argString = args.joinToString(", ") { it.toPseudoCode() } return "${indent(indentationLevel)}${dest.toPseudoCode()} = $funName($argString)" } + + override fun deepCopy(): TackyInstruction = TackyFunCall(funName, args.map { it.deepCopy() }, dest.deepCopy() as TackyVar, sourceId) } @Serializable @@ -129,4 +146,6 @@ data class TackyLabel( override val sourceId: String = "" ) : TackyInstruction() { override fun toPseudoCode(indentationLevel: Int): String = "$name:" + + override fun deepCopy(): TackyInstruction = TackyLabel(name, sourceId) } diff --git a/src/jsMain/kotlin/tacky/Tacky.kt b/src/jsMain/kotlin/tacky/Tacky.kt index d4ca0a7..f8867a8 100644 --- a/src/jsMain/kotlin/tacky/Tacky.kt +++ b/src/jsMain/kotlin/tacky/Tacky.kt @@ -11,7 +11,9 @@ sealed class TackyConstruct() { } @Serializable -sealed class TackyVal() : TackyConstruct() +sealed class TackyVal() : TackyConstruct() { + abstract fun deepCopy(): TackyVal +} @Serializable @SerialName("TackyConstant") @@ -19,6 +21,8 @@ data class TackyConstant( val value: Int ) : TackyVal() { override fun toPseudoCode(indentationLevel: Int): String = value.toString() + + override fun deepCopy(): TackyVal = TackyConstant(value) } @Serializable @@ -27,6 +31,8 @@ data class TackyVar( val name: String ) : TackyVal() { override fun toPseudoCode(indentationLevel: Int): String = name + + override fun deepCopy(): TackyVal = TackyVar(name) } @Serializable @@ -35,6 +41,10 @@ data class TackyProgram( val functions: List ) : TackyConstruct() { override fun toPseudoCode(indentationLevel: Int): String = functions.joinToString("\n\n") { it.toPseudoCode(indentationLevel) } + + fun deepCopy(): TackyProgram { + return TackyProgram(functions.map { it.deepCopy() }) + } } @Serializable @@ -54,4 +64,13 @@ data class TackyFunction( append(bodyAsCode) } } + + fun deepCopy(): TackyFunction { + return TackyFunction( + name = name, + args = args.toList(), // Create a new list + body = body.map { it.deepCopy() }, + sourceId = sourceId + ) + } } diff --git a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt index 151572f..1eb5ce0 100644 --- a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt +++ b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt @@ -2,6 +2,7 @@ package tacky import exceptions.TackyException import lexer.TokenType +import parser.ASTVisitor import parser.AssignmentExpression import parser.BinaryExpression import parser.Block @@ -30,16 +31,18 @@ import parser.UnaryExpression import parser.VarDecl import parser.VariableDeclaration import parser.VariableExpression -import parser.Visitor import parser.WhileStatement -class TackyGenVisitor : Visitor { +class TackyGenVisitor : ASTVisitor { private var tempCounter = 0 private var labelCounter = 0 private fun newTemporary(): TackyVar = TackyVar("tmp.${tempCounter++}") - private fun newLabel(base: String, sourceId: String = ""): TackyLabel = TackyLabel(".L_${base}_${labelCounter++}", sourceId) + private fun newLabel( + base: String, + sourceId: String = "" + ): TackyLabel = TackyLabel(".L_${base}_${labelCounter++}", sourceId) private val currentInstructions = mutableListOf() @@ -49,43 +52,55 @@ class TackyGenVisitor : Visitor { currentInstructions.clear() } - private fun convertUnaryOp(tokenType: TokenType): TackyUnaryOP { + private fun convertUnaryOp(tokenType: TokenType): TackyUnaryOP = if (tokenType == TokenType.TILDE) { - return TackyUnaryOP.COMPLEMENT + TackyUnaryOP.COMPLEMENT } else if (tokenType == TokenType.NEGATION) { - return TackyUnaryOP.NEGATE + TackyUnaryOP.NEGATE } else if (tokenType == TokenType.NOT) { - return TackyUnaryOP.NOT + TackyUnaryOP.NOT } else { throw TackyException(tokenType.toString()) } - } private fun convertBinaryOp(tokenType: TokenType): TackyBinaryOP { - if (tokenType == TokenType.PLUS) { - return TackyBinaryOP.ADD - } else if (tokenType == TokenType.NEGATION) { - return TackyBinaryOP.SUBTRACT - } else if (tokenType == TokenType.MULTIPLY) { - return TackyBinaryOP.MULTIPLY - } else if (tokenType == TokenType.DIVIDE) { - return TackyBinaryOP.DIVIDE - } else if (tokenType == TokenType.REMAINDER) { - return TackyBinaryOP.REMAINDER - } else if (tokenType == TokenType.EQUAL_TO) { - return TackyBinaryOP.EQUAL - } else if (tokenType == TokenType.GREATER) { - return TackyBinaryOP.GREATER - } else if (tokenType == TokenType.LESS) { - return TackyBinaryOP.LESS - } else if (tokenType == TokenType.GREATER_EQUAL) { - return TackyBinaryOP.GREATER_EQUAL - } else if (tokenType == TokenType.LESS_EQUAL) { - return TackyBinaryOP.LESS_EQUAL - } else if (tokenType == TokenType.NOT_EQUAL) { - return TackyBinaryOP.NOT_EQUAL - } else { - throw TackyException(tokenType.toString()) + when (tokenType) { + TokenType.PLUS -> { + return TackyBinaryOP.ADD + } + TokenType.NEGATION -> { + return TackyBinaryOP.SUBTRACT + } + TokenType.MULTIPLY -> { + return TackyBinaryOP.MULTIPLY + } + TokenType.DIVIDE -> { + return TackyBinaryOP.DIVIDE + } + TokenType.REMAINDER -> { + return TackyBinaryOP.REMAINDER + } + TokenType.EQUAL_TO -> { + return TackyBinaryOP.EQUAL + } + TokenType.GREATER -> { + return TackyBinaryOP.GREATER + } + TokenType.LESS -> { + return TackyBinaryOP.LESS + } + TokenType.GREATER_EQUAL -> { + return TackyBinaryOP.GREATER_EQUAL + } + TokenType.LESS_EQUAL -> { + return TackyBinaryOP.LESS_EQUAL + } + TokenType.NOT_EQUAL -> { + return TackyBinaryOP.NOT_EQUAL + } + else -> { + throw TackyException(tokenType.toString()) + } } } @@ -278,13 +293,13 @@ class TackyGenVisitor : Visitor { return null } - override fun visit(node: ConditionalExpression): TackyConstruct? { + override fun visit(node: ConditionalExpression): TackyConstruct { val resultVar = newTemporary() val elseLabel = newLabel("cond_else", node.id) val endLabel = newLabel("cond_end", node.id) - val conditionResult = node.codition.accept(this) as TackyVal + val conditionResult = node.condition.accept(this) as TackyVal currentInstructions += JumpIfZero(conditionResult, elseLabel, node.id) val thenResult = node.thenExpression.accept(this) as TackyVal @@ -304,7 +319,6 @@ class TackyGenVisitor : Visitor { } override fun visit(node: LabeledStatement): TackyConstruct? { - val label = node.label currentInstructions += TackyLabel(node.label, node.id) node.statement.accept(this) return null @@ -346,7 +360,7 @@ class TackyGenVisitor : Visitor { return null } - override fun visit(node: FunctionCall): TackyConstruct? { + override fun visit(node: FunctionCall): TackyConstruct { val args = node.arguments.map { it.accept(this) as TackyVal } val dest = newTemporary() currentInstructions += TackyFunCall(node.name, args, dest, node.id) diff --git a/src/jsTest/kotlin/integration/CompilerTestSuite.kt b/src/jsTest/kotlin/integration/CompilerTestSuite.kt index bd3c3b3..5e8df49 100644 --- a/src/jsTest/kotlin/integration/CompilerTestSuite.kt +++ b/src/jsTest/kotlin/integration/CompilerTestSuite.kt @@ -12,8 +12,11 @@ import kotlin.test.assertIs import kotlin.test.assertTrue class CompilerTestSuite { - - private fun assertAstEquals(expected: SimpleProgram, actual: SimpleProgram, message: String) { + private fun assertAstEquals( + expected: SimpleProgram, + actual: SimpleProgram, + message: String + ) { // Compare function declarations assertEquals(expected.functionDeclaration.size, actual.functionDeclaration.size, "$message - Function count mismatch") @@ -31,7 +34,11 @@ class CompilerTestSuite { } } - private fun assertBlockEquals(expected: parser.Block, actual: parser.Block, message: String) { + private fun assertBlockEquals( + expected: parser.Block, + actual: parser.Block, + message: String + ) { assertEquals(expected.items.size, actual.items.size, "$message - Block item count mismatch") expected.items.forEachIndexed { index, expectedItem -> @@ -43,14 +50,21 @@ class CompilerTestSuite { } is parser.D -> { assertIs(actualItem, "$message - Expected D but got ${actualItem::class.simpleName} at index $index") - assertDeclarationEquals(expectedItem.declaration, actualItem.declaration, "$message - Declaration mismatch at index $index") + assertDeclarationEquals( + expectedItem.declaration, + actualItem.declaration, + "$message - Declaration mismatch at index $index" + ) } - else -> assertEquals(expectedItem, actualItem, "$message - Block item mismatch at index $index") } } } - private fun assertStatementEquals(expected: parser.Statement, actual: parser.Statement, message: String) { + private fun assertStatementEquals( + expected: parser.Statement, + actual: parser.Statement, + message: String + ) { when (expected) { is parser.ReturnStatement -> { assertIs(actual, "$message - Expected ReturnStatement but got ${actual::class.simpleName}") @@ -119,11 +133,14 @@ class CompilerTestSuite { assertIs(actual, "$message - Expected CompoundStatement but got ${actual::class.simpleName}") assertBlockEquals(expected.block, actual.block, "$message - CompoundStatement block mismatch") } - else -> assertEquals(expected, actual, "$message - Statement type mismatch") } } - private fun assertExpressionEquals(expected: parser.Expression, actual: parser.Expression, message: String) { + private fun assertExpressionEquals( + expected: parser.Expression, + actual: parser.Expression, + message: String + ) { when (expected) { is parser.IntExpression -> { assertIs(actual, "$message - Expected IntExpression but got ${actual::class.simpleName}") @@ -145,7 +162,10 @@ class CompilerTestSuite { assertExpressionEquals(expected.expression, actual.expression, "$message - Unary expression mismatch") } is parser.AssignmentExpression -> { - assertIs(actual, "$message - Expected AssignmentExpression but got ${actual::class.simpleName}") + assertIs( + actual, + "$message - Expected AssignmentExpression but got ${actual::class.simpleName}" + ) assertExpressionEquals(expected.lvalue, actual.lvalue, "$message - Assignment lvalue mismatch") assertExpressionEquals(expected.rvalue, actual.rvalue, "$message - Assignment rvalue mismatch") } @@ -158,16 +178,22 @@ class CompilerTestSuite { } } is parser.ConditionalExpression -> { - assertIs(actual, "$message - Expected ConditionalExpression but got ${actual::class.simpleName}") - assertExpressionEquals(expected.codition, actual.codition, "$message - Conditional condition mismatch") + assertIs( + actual, + "$message - Expected ConditionalExpression but got ${actual::class.simpleName}" + ) + assertExpressionEquals(expected.condition, actual.condition, "$message - Conditional condition mismatch") assertExpressionEquals(expected.thenExpression, actual.thenExpression, "$message - Conditional then mismatch") assertExpressionEquals(expected.elseExpression, actual.elseExpression, "$message - Conditional else mismatch") } - else -> assertEquals(expected, actual, "$message - Expression type mismatch") } } - private fun assertForInitEquals(expected: parser.ForInit, actual: parser.ForInit, message: String) { + private fun assertForInitEquals( + expected: parser.ForInit, + actual: parser.ForInit, + message: String + ) { when (expected) { is parser.InitDeclaration -> { assertIs(actual, "$message - Expected InitDeclaration but got ${actual::class.simpleName}") @@ -181,11 +207,14 @@ class CompilerTestSuite { assertEquals(expected.expression, actual.expression, "$message - InitExpression null mismatch") } } - else -> assertEquals(expected, actual, "$message - ForInit type mismatch") } } - private fun assertTackyEquals(expected: TackyProgram, actual: TackyProgram, message: String) { + private fun assertTackyEquals( + expected: TackyProgram, + actual: TackyProgram, + message: String + ) { assertEquals(expected.functions.size, actual.functions.size, "$message - Tacky function count mismatch") expected.functions.forEachIndexed { index, expectedFunc -> @@ -196,12 +225,20 @@ class CompilerTestSuite { expectedFunc.body.forEachIndexed { instrIndex, expectedInstr -> val actualInstr = actualFunc.body[instrIndex] - assertTackyInstructionEquals(expectedInstr, actualInstr, "$message - Tacky instruction mismatch at function $index, instruction $instrIndex") + assertTackyInstructionEquals( + expectedInstr, + actualInstr, + "$message - Tacky instruction mismatch at function $index, instruction $instrIndex" + ) } } } - private fun assertTackyInstructionEquals(expected: tacky.TackyInstruction, actual: tacky.TackyInstruction, message: String) { + private fun assertTackyInstructionEquals( + expected: tacky.TackyInstruction, + actual: tacky.TackyInstruction, + message: String + ) { // Check sourceId for all instructions if (expected.sourceId.isEmpty()) { assertTrue(actual.sourceId.isNotEmpty(), "$message - TackyInstruction sourceId should not be empty") @@ -259,11 +296,14 @@ class CompilerTestSuite { assertIs(actual, "$message - Expected TackyLabel but got ${actual::class.simpleName}") assertTackyLabelEquals(expected, actual, "$message - TackyLabel mismatch") } - else -> assertEquals(expected, actual, "$message - Tacky instruction type mismatch") } } - private fun assertTackyLabelEquals(expected: tacky.TackyLabel, actual: tacky.TackyLabel, message: String) { + private fun assertTackyLabelEquals( + expected: tacky.TackyLabel, + actual: tacky.TackyLabel, + message: String + ) { assertEquals(expected.name, actual.name, "$message - TackyLabel name mismatch") // For test data with empty sourceId, we expect actual sourceId to be non-empty if (expected.sourceId.isEmpty()) { @@ -273,7 +313,11 @@ class CompilerTestSuite { } } - private fun assertTackyValueEquals(expected: tacky.TackyVal, actual: tacky.TackyVal, message: String) { + private fun assertTackyValueEquals( + expected: tacky.TackyVal, + actual: tacky.TackyVal, + message: String + ) { when (expected) { is tacky.TackyConstant -> { assertIs(actual, "$message - Expected TackyConstant but got ${actual::class.simpleName}") @@ -283,11 +327,14 @@ class CompilerTestSuite { assertIs(actual, "$message - Expected TackyVar but got ${actual::class.simpleName}") assertEquals(expected.name, actual.name, "$message - TackyVar name mismatch") } - else -> assertEquals(expected, actual, "$message - Tacky value type mismatch") } } - private fun assertAssemblyEquals(expected: AsmProgram, actual: AsmProgram, message: String) { + private fun assertAssemblyEquals( + expected: AsmProgram, + actual: AsmProgram, + message: String + ) { assertEquals(expected.functions.size, actual.functions.size, "$message - Assembly function count mismatch") expected.functions.forEachIndexed { index, expectedFunc -> @@ -298,12 +345,20 @@ class CompilerTestSuite { expectedFunc.body.forEachIndexed { instrIndex, expectedInstr -> val actualInstr = actualFunc.body[instrIndex] - assertAssemblyInstructionEquals(expectedInstr, actualInstr, "$message - Assembly instruction mismatch at function $index, instruction $instrIndex") + assertAssemblyInstructionEquals( + expectedInstr, + actualInstr, + "$message - Assembly instruction mismatch at function $index, instruction $instrIndex" + ) } } } - private fun assertAssemblyInstructionEquals(expected: assembly.Instruction, actual: assembly.Instruction, message: String) { + private fun assertAssemblyInstructionEquals( + expected: assembly.Instruction, + actual: assembly.Instruction, + message: String + ) { when (expected) { is assembly.Mov -> { assertIs(actual, "$message - Expected Mov but got ${actual::class.simpleName}") @@ -370,15 +425,22 @@ class CompilerTestSuite { is assembly.Ret -> { assertIs(actual, "$message - Expected Ret but got ${actual::class.simpleName}") } - else -> assertEquals(expected, actual, "$message - Assembly instruction type mismatch") } } - private fun assertAssemblyLabelEquals(expected: assembly.Label, actual: assembly.Label, message: String) { + private fun assertAssemblyLabelEquals( + expected: assembly.Label, + actual: assembly.Label, + message: String + ) { assertEquals(expected.name, actual.name, "$message - Assembly Label name mismatch") } - private fun assertAssemblyOperandEquals(expected: assembly.Operand, actual: assembly.Operand, message: String) { + private fun assertAssemblyOperandEquals( + expected: assembly.Operand, + actual: assembly.Operand, + message: String + ) { when (expected) { is assembly.Imm -> { assertIs(actual, "$message - Expected Imm but got ${actual::class.simpleName}") @@ -396,11 +458,14 @@ class CompilerTestSuite { assertIs(actual, "$message - Expected Pseudo but got ${actual::class.simpleName}") assertEquals(expected.name, actual.name, "$message - Pseudo name mismatch") } - else -> assertEquals(expected, actual, "$message - Assembly operand type mismatch") } } - private fun assertFunctionDeclarationEquals(expected: parser.FunctionDeclaration, actual: parser.FunctionDeclaration, message: String) { + private fun assertFunctionDeclarationEquals( + expected: parser.FunctionDeclaration, + actual: parser.FunctionDeclaration, + message: String + ) { assertEquals(expected.name, actual.name, "$message - Function declaration name mismatch") assertEquals(expected.params, actual.params, "$message - Function declaration params mismatch") if (expected.body != null && actual.body != null) { @@ -410,7 +475,11 @@ class CompilerTestSuite { } } - private fun assertDeclarationEquals(expected: parser.Declaration, actual: parser.Declaration, message: String) { + private fun assertDeclarationEquals( + expected: parser.Declaration, + actual: parser.Declaration, + message: String + ) { when (expected) { is parser.VarDecl -> { assertIs(actual, "$message - Expected VarDecl but got ${actual::class.simpleName}") @@ -429,17 +498,6 @@ class CompilerTestSuite { assertEquals(expected.init, actual.init, "$message - Variable declaration init null mismatch") } } - is parser.FunctionDeclaration -> { - assertIs(actual, "$message - Expected FunctionDeclaration but got ${actual::class.simpleName}") - assertEquals(expected.name, actual.name, "$message - Function declaration name mismatch") - assertEquals(expected.params, actual.params, "$message - Function declaration params mismatch") - if (expected.body != null && actual.body != null) { - assertBlockEquals(expected.body, actual.body, "$message - Function declaration body mismatch") - } else { - assertEquals(expected.body, actual.body, "$message - Function declaration body null mismatch") - } - } - else -> assertEquals(expected, actual, "$message - Declaration type mismatch") } } @@ -463,11 +521,11 @@ class CompilerTestSuite { // Parser stage val ast = CompilerWorkflow.take(tokens) assertIs(ast) - val simpleProgram = ast as SimpleProgram + val simpleProgram = ast if (testCase.expectedAst != null) { assertIs(testCase.expectedAst, "Expected AST should be SimpleProgram") assertAstEquals( - expected = testCase.expectedAst as SimpleProgram, + expected = testCase.expectedAst, actual = simpleProgram, message = """ diff --git a/src/jsTest/kotlin/integration/InvalidTestCases.kt b/src/jsTest/kotlin/integration/InvalidTestCases.kt index baee698..9c935cd 100644 --- a/src/jsTest/kotlin/integration/InvalidTestCases.kt +++ b/src/jsTest/kotlin/integration/InvalidTestCases.kt @@ -4,13 +4,13 @@ import compiler.CompilerStage import exceptions.ArgumentCountException import exceptions.DuplicateVariableDeclaration import exceptions.IncompatibleFuncDeclarationException +import exceptions.InvalidCharacterException import exceptions.InvalidLValueException import exceptions.InvalidStatementException -import exceptions.LexicalException import exceptions.MissingDeclarationException import exceptions.NestedFunctionException -import exceptions.NotFunctionException -import exceptions.NotVariableException +import exceptions.NotAFunctionException +import exceptions.NotAVariableException import exceptions.ReDeclarationFunctionException import exceptions.UnexpectedTokenException import kotlin.reflect.KClass @@ -28,7 +28,7 @@ object InvalidTestCases { InvalidTestCase( code = "#", failingStage = CompilerStage.LEXER, - expectedException = LexicalException::class + expectedException = InvalidCharacterException::class ), // Parser errors InvalidTestCase( @@ -183,7 +183,8 @@ object InvalidTestCases { ), // Function redeclaration with different parameter count (illegal in C) InvalidTestCase( - code = """ + code = + """ int func(int a, int b); int func(int a); int main(void) { @@ -195,7 +196,8 @@ object InvalidTestCases { ), // Nested function inside block InvalidTestCase( - code = """ + code = + """ int main(void) { { int nested(int x) { @@ -210,7 +212,8 @@ object InvalidTestCases { ), // ArgumentCountException - Wrong number of arguments for function InvalidTestCase( - code = """ + code = + """ int func(int a, int b); int main(void) { return func(1); @@ -220,7 +223,8 @@ object InvalidTestCases { expectedException = ArgumentCountException::class ), InvalidTestCase( - code = """ + code = + """ int main(void) { int nested(int x) { return x + 1; @@ -233,18 +237,20 @@ object InvalidTestCases { ), // NotFunctionException - Calling something that's not a function InvalidTestCase( - code = """ + code = + """ int main(void) { int a = 5; return a(1, 2); } """.trimIndent(), failingStage = CompilerStage.PARSER, - expectedException = NotFunctionException::class + expectedException = NotAFunctionException::class ), // NotVariableException - Using function as a variable InvalidTestCase( - code = """ + code = + """ int func(int x) { return x + 1; } @@ -254,11 +260,12 @@ object InvalidTestCases { } """.trimIndent(), failingStage = CompilerStage.PARSER, - expectedException = NotVariableException::class + expectedException = NotAVariableException::class ), // ReDeclarationFunctionException - Function defined more than once InvalidTestCase( - code = """ + code = + """ int func(int x) { return x + 1; } diff --git a/src/jsTest/kotlin/optimizations/ControlFlowGraphTest.kt b/src/jsTest/kotlin/optimizations/ControlFlowGraphTest.kt index 21979b2..901d36a 100644 --- a/src/jsTest/kotlin/optimizations/ControlFlowGraphTest.kt +++ b/src/jsTest/kotlin/optimizations/ControlFlowGraphTest.kt @@ -20,7 +20,6 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class ControlFlowGraphTest { - // ===== ControlFlowGraph Construction Tests ===== @Test @@ -46,10 +45,11 @@ class ControlFlowGraphTest { @Test fun `construct should create single block for simple instructions`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyCopy(TackyConstant(2), TackyVar("y")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyCopy(TackyConstant(2), TackyVar("y")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(1, cfg.blocks.size) @@ -59,13 +59,14 @@ class ControlFlowGraphTest { @Test fun `construct should split blocks at labels`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("label1"), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("label2"), - TackyCopy(TackyConstant(3), TackyVar("z")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("label1"), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("label2"), + TackyCopy(TackyConstant(3), TackyVar("z")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(3, cfg.blocks.size) @@ -76,12 +77,13 @@ class ControlFlowGraphTest { @Test fun `construct should split blocks at jumps`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyJump(TackyLabel("end")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("end") - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyJump(TackyLabel("end")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("end") + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(3, cfg.blocks.size) @@ -92,11 +94,12 @@ class ControlFlowGraphTest { @Test fun `construct should split blocks at returns`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")), - TackyCopy(TackyConstant(2), TackyVar("y")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyRet(TackyVar("x")), + TackyCopy(TackyConstant(2), TackyVar("y")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(2, cfg.blocks.size) // Adjusted to match actual behavior @@ -106,12 +109,13 @@ class ControlFlowGraphTest { @Test fun `construct should split blocks at conditional jumps`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - JumpIfZero(TackyVar("x"), TackyLabel("end")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("end") - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + JumpIfZero(TackyVar("x"), TackyLabel("end")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("end") + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(3, cfg.blocks.size) @@ -122,15 +126,16 @@ class ControlFlowGraphTest { @Test fun `construct should handle mixed control flow instructions`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("start"), - JumpIfZero(TackyVar("x"), TackyLabel("end")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyJump(TackyLabel("start")), - TackyLabel("end"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("start"), + JumpIfZero(TackyVar("x"), TackyLabel("end")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyJump(TackyLabel("start")), + TackyLabel("end"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(4, cfg.blocks.size) // Adjusted to match actual behavior @@ -146,17 +151,18 @@ class ControlFlowGraphTest { assertTrue(cfg.edges.isNotEmpty()) val startEdge = cfg.edges.find { it.from is START } assertNotNull(startEdge) - assertEquals(0, startEdge.from.id) - assertEquals(1, startEdge.to.id) // First block has id 1 + assertEquals(-1, startEdge.from.id) + assertEquals(0, startEdge.to.id) // First block has id 0 } @Test fun `buildEdges should connect blocks sequentially`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyCopy(TackyConstant(3), TackyVar("z")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyCopy(TackyConstant(3), TackyVar("z")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(1, cfg.blocks.size) // All in one block @@ -165,10 +171,11 @@ class ControlFlowGraphTest { @Test fun `buildEdges should connect return to EXIT`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) val returnEdge = cfg.edges.find { it.to is EXIT } @@ -178,49 +185,54 @@ class ControlFlowGraphTest { @Test fun `buildEdges should connect jumps to target labels`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyJump(TackyLabel("target")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("target"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyJump(TackyLabel("target")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("target"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) - val jumpEdge = cfg.edges.find { - it.from is Block && it.to is Block && (it.from as Block).instructions.any { inst -> inst is TackyJump } - } + val jumpEdge = + cfg.edges.find { + it.from is Block && it.to is Block && it.from.instructions.any { inst -> inst is TackyJump } + } assertNotNull(jumpEdge) } @Test fun `buildEdges should connect conditional jumps to both target and next`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - JumpIfZero(TackyVar("x"), TackyLabel("target")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("target"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + JumpIfZero(TackyVar("x"), TackyLabel("target")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("target"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) - val conditionalBlock = cfg.blocks.find { - it.instructions.any { inst -> inst is JumpIfZero } - } + val conditionalBlock = + cfg.blocks.find { + it.instructions.any { inst -> inst is JumpIfZero } + } assertNotNull(conditionalBlock) - assertTrue(conditionalBlock!!.successors.size >= 1) + assertTrue(conditionalBlock.successors.isNotEmpty()) } // ===== toInstructions Tests ===== @Test fun `toInstructions should return all instructions from all blocks`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("label"), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("label"), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) val result = cfg.toInstructions() @@ -239,18 +251,18 @@ class ControlFlowGraphTest { @Test fun `START node should have correct properties`() { - val start = START(0) + val start = START() - assertEquals(0, start.id) + assertEquals(-1, start.id) assertTrue(start.predecessors.isEmpty()) assertTrue(start.successors.isEmpty()) } @Test fun `EXIT node should have correct properties`() { - val exit = EXIT(0) + val exit = EXIT() - assertEquals(0, exit.id) + assertEquals(-2, exit.id) assertTrue(exit.predecessors.isEmpty()) assertTrue(exit.successors.isEmpty()) } @@ -282,7 +294,7 @@ class ControlFlowGraphTest { @Test fun `Edge should connect two nodes`() { - val start = START(0) + val start = START() val block = Block(1, listOf(TackyCopy(TackyConstant(1), TackyVar("x")))) val edge = Edge(start, block) @@ -294,34 +306,36 @@ class ControlFlowGraphTest { @Test fun `findBlockByLabel should find block containing label`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("target"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("target"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) - val targetLabel = TackyLabel("target") - val foundBlock = cfg.blocks.find { - it.instructions.any { inst -> inst is TackyLabel && inst.name == "target" } - } + val foundBlock = + cfg.blocks.find { + it.instructions.any { inst -> inst is TackyLabel && inst.name == "target" } + } assertNotNull(foundBlock) - assertTrue(foundBlock!!.instructions.any { it is TackyLabel && it.name == "target" }) + assertTrue(foundBlock.instructions.any { it is TackyLabel && it.name == "target" }) } @Test fun `findBlockByLabel should return null for non-existent label`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) - val nonExistentLabel = TackyLabel("nonexistent") - val foundBlock = cfg.blocks.find { - it.instructions.any { inst -> inst is TackyLabel && inst.name == "nonexistent" } - } + val foundBlock = + cfg.blocks.find { + it.instructions.any { inst -> inst is TackyLabel && inst.name == "nonexistent" } + } assertNull(foundBlock) } @@ -330,16 +344,17 @@ class ControlFlowGraphTest { @Test fun `construct should handle nested control flow`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("outer"), - JumpIfZero(TackyVar("x"), TackyLabel("inner")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyJump(TackyLabel("outer")), - TackyLabel("inner"), - TackyCopy(TackyConstant(3), TackyVar("z")), - TackyRet(TackyVar("z")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("outer"), + JumpIfZero(TackyVar("x"), TackyLabel("inner")), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyJump(TackyLabel("outer")), + TackyLabel("inner"), + TackyCopy(TackyConstant(3), TackyVar("z")), + TackyRet(TackyVar("z")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertTrue(cfg.blocks.size >= 4) // Should have multiple blocks @@ -348,29 +363,31 @@ class ControlFlowGraphTest { @Test fun `construct should handle multiple returns`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("branch1"), - TackyRet(TackyVar("x")), - TackyLabel("branch2"), - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("branch1"), + TackyRet(TackyVar("x")), + TackyLabel("branch2"), + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(3, cfg.blocks.size) // Adjusted to match actual behavior val returnEdges = cfg.edges.filter { it.to is EXIT } - assertTrue(returnEdges.size >= 1) // Should have paths to EXIT + assertTrue(returnEdges.isNotEmpty()) // Should have paths to EXIT } @Test fun `construct should handle unreachable code`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyJump(TackyLabel("end")), - TackyCopy(TackyConstant(2), TackyVar("y")), // Unreachable - TackyLabel("end"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyJump(TackyLabel("end")), + TackyCopy(TackyConstant(2), TackyVar("y")), // Unreachable + TackyLabel("end"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("test", instructions) // Should still create blocks for unreachable code @@ -381,11 +398,12 @@ class ControlFlowGraphTest { @Test fun `construct should handle only labels`() { - val instructions = listOf( - TackyLabel("label1"), - TackyLabel("label2"), - TackyLabel("label3") - ) + val instructions = + listOf( + TackyLabel("label1"), + TackyLabel("label2"), + TackyLabel("label3") + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(3, cfg.blocks.size) // One block per label @@ -393,10 +411,11 @@ class ControlFlowGraphTest { @Test fun `construct should handle only jumps`() { - val instructions = listOf( - TackyJump(TackyLabel("target")), - TackyLabel("target") - ) + val instructions = + listOf( + TackyJump(TackyLabel("target")), + TackyLabel("target") + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(2, cfg.blocks.size) // Jump block + label block @@ -404,9 +423,10 @@ class ControlFlowGraphTest { @Test fun `construct should handle only returns`() { - val instructions = listOf( - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertEquals(1, cfg.blocks.size) // One block with return @@ -415,14 +435,15 @@ class ControlFlowGraphTest { @Test fun `construct should handle mixed instruction types`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyUnary(TackyUnaryOP.NEGATE, TackyVar("x"), TackyVar("y")), - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyVar("y"), TackyVar("z")), - TackyLabel("loop"), - JumpIfNotZero(TackyVar("z"), TackyLabel("loop")), - TackyRet(TackyVar("z")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyUnary(TackyUnaryOP.NEGATE, TackyVar("x"), TackyVar("y")), + TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyVar("y"), TackyVar("z")), + TackyLabel("loop"), + JumpIfNotZero(TackyVar("z"), TackyLabel("loop")), + TackyRet(TackyVar("z")) + ) val cfg = ControlFlowGraph().construct("test", instructions) assertTrue(cfg.blocks.size >= 2) diff --git a/src/jsTest/kotlin/optimizations/DeadStoreEliminationTest.kt b/src/jsTest/kotlin/optimizations/DeadStoreEliminationTest.kt index 59ca3a3..49a0d7c 100644 --- a/src/jsTest/kotlin/optimizations/DeadStoreEliminationTest.kt +++ b/src/jsTest/kotlin/optimizations/DeadStoreEliminationTest.kt @@ -6,7 +6,6 @@ import tacky.TackyConstant import tacky.TackyCopy import tacky.TackyFunCall import tacky.TackyFunction -import tacky.TackyInstruction import tacky.TackyJump import tacky.TackyLabel import tacky.TackyProgram @@ -16,338 +15,262 @@ import tacky.TackyUnaryOP import tacky.TackyVar import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class DeadStoreEliminationTest { - private val optimization = DeadStoreElimination() @Test - fun `it should eliminate simple dead store`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyCopy(TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyCopy(TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test basic dead store elimination`() { + // Test case: x = 5; y = 10; (x and y are never used) + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyCopy(TackyConstant(10), TackyVar("y")), + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Both dead stores should be eliminated, only ret should remain + assertEquals(1, result.blocks.size) + assertEquals(1, result.blocks[0].instructions.size) + assertEquals(TackyRet(TackyConstant(0)), result.blocks[0].instructions[0]) } @Test - fun `it should not eliminate store that is used later`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyConstant(1), TackyVar("y")), - TackyRet(TackyVar("y")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyConstant(1), TackyVar("y")), - TackyRet(TackyVar("y")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test live store preservation`() { + // Test case: x = 5; return x; (x is used, should be preserved) + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyRet(TackyVar("x")) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Both instructions should be preserved + assertEquals(1, result.blocks.size) + assertEquals(2, result.blocks[0].instructions.size) + assertEquals(TackyCopy(TackyConstant(5), TackyVar("x")), result.blocks[0].instructions[0]) + assertEquals(TackyRet(TackyVar("x")), result.blocks[0].instructions[1]) } @Test - fun `it should eliminate dead store in conditional branch`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyLabel("label1"), - TackyCopy(TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyLabel("label1"), - TackyCopy(TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test binary operation dead store elimination`() { + // Test case: x = a + b; (x is never used) + val instructions = listOf( + TackyBinary(TackyBinaryOP.ADD, TackyVar("a"), TackyVar("b"), TackyVar("x")), + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Binary operation should be eliminated + assertEquals(1, result.blocks.size) + assertEquals(1, result.blocks[0].instructions.size) + assertEquals(TackyRet(TackyConstant(0)), result.blocks[0].instructions[0]) } @Test - fun `it should not eliminate function calls even if destination is dead`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyFunCall("foo", emptyList(), TackyVar("x")), - TackyRet(TackyConstant(0)) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyFunCall("foo", emptyList(), TackyVar("x")), - TackyRet(TackyConstant(0)) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test unary operation dead store elimination`() { + // Test case: x = -a; (x is never used) + val instructions = listOf( + TackyUnary(TackyUnaryOP.NEGATE, TackyVar("a"), TackyVar("x")), + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Unary operation should be eliminated + assertEquals(1, result.blocks.size) + assertEquals(1, result.blocks[0].instructions.size) + assertEquals(TackyRet(TackyConstant(0)), result.blocks[0].instructions[0]) } @Test - fun `it should eliminate multiple dead stores`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyCopy(TackyConstant(3), TackyVar("z")), - TackyRet(TackyConstant(0)) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyRet(TackyConstant(0)) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test function call preservation`() { + // Test case: x = f(); (function calls should never be eliminated) + val instructions = listOf( + TackyFunCall("f", emptyList(), TackyVar("x")), + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Function call should be preserved even if result is not used + assertEquals(1, result.blocks.size) + assertEquals(2, result.blocks[0].instructions.size) + assertEquals(TackyFunCall("f", emptyList(), TackyVar("x")), result.blocks[0].instructions[0]) + assertEquals(TackyRet(TackyConstant(0)), result.blocks[0].instructions[1]) } @Test - fun `it should handle unary operations`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyUnary(TackyUnaryOP.NEGATE, TackyConstant(5), TackyVar("x")), - TackyUnary(TackyUnaryOP.NEGATE, TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyUnary(TackyUnaryOP.NEGATE, TackyConstant(10), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test mixed live and dead stores`() { + // Test case: x = 5; y = 10; z = x + y; return z; + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyCopy(TackyConstant(10), TackyVar("y")), + TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyVar("y"), TackyVar("z")), + TackyRet(TackyVar("z")) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // All instructions should be preserved as they form a live chain + assertEquals(1, result.blocks.size) + assertEquals(4, result.blocks[0].instructions.size) + assertEquals(TackyCopy(TackyConstant(5), TackyVar("x")), result.blocks[0].instructions[0]) + assertEquals(TackyCopy(TackyConstant(10), TackyVar("y")), result.blocks[0].instructions[1]) + assertEquals(TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyVar("y"), TackyVar("z")), result.blocks[0].instructions[2]) + assertEquals(TackyRet(TackyVar("z")), result.blocks[0].instructions[3]) } @Test - fun `it should handle binary operations`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyBinary(TackyBinaryOP.MULTIPLY, TackyConstant(3), TackyConstant(4), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyBinary(TackyBinaryOP.MULTIPLY, TackyConstant(3), TackyConstant(4), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test conditional jump with dead stores`() { + // Test case: x = 5; if (x) jump L1; y = 10; L1: return 0; + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyJump(TackyLabel("L1")), + TackyCopy(TackyConstant(10), TackyVar("y")), // Dead store + TackyLabel("L1"), + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Dead store after unreachable code should be eliminated + val blockWithDeadStore = result.blocks.find { it.instructions.any { instr -> instr is TackyCopy && instr.dest.name == "y" } } + if (blockWithDeadStore != null) { + // If the block exists, the dead store should be eliminated + assertTrue(blockWithDeadStore.instructions.none { instr -> instr is TackyCopy && instr.dest.name == "y" }) + } } @Test - fun `it should not eliminate instructions that read and write the same variable`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - val expectedInstructions = - listOf( - TackyCopy(TackyConstant(5), TackyVar("x")), - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test empty block handling`() { + // Test case: empty function + val instructions = listOf( + TackyRet(TackyConstant(0)) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Should handle empty blocks gracefully + assertEquals(1, result.blocks.size) + assertEquals(1, result.blocks[0].instructions.size) + assertEquals(TackyRet(TackyConstant(0)), result.blocks[0].instructions[0]) } @Test - fun `it should handle empty function`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - emptyList() - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - // Assert - assertEquals(emptyList(), optimizedInstructions) + fun `test variable reuse after dead store`() { + // Test case: x = 5; x = 10; return x; (first assignment to x is dead) + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), // Dead store + TackyCopy(TackyConstant(10), TackyVar("x")), // Live store + TackyRet(TackyVar("x")) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // First assignment should be eliminated, second should be preserved + assertEquals(1, result.blocks.size) + assertEquals(2, result.blocks[0].instructions.size) + assertEquals(TackyCopy(TackyConstant(10), TackyVar("x")), result.blocks[0].instructions[0]) + assertEquals(TackyRet(TackyVar("x")), result.blocks[0].instructions[1]) } @Test - fun `it should handle complex control flow with jumps`() { - // Arrange - val program = - TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("start"), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyJump(TackyLabel("end")), - TackyLabel("middle"), - TackyCopy(TackyConstant(3), TackyVar("z")), - TackyLabel("end"), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val cfg = ControlFlowGraph().construct("test", program.functions[0].body) - - // Act - val optimizedCfg = optimization.apply(cfg) - val optimizedInstructions = optimizedCfg.toInstructions() - - val expectedInstructions = - listOf( - TackyLabel("start"), - TackyJump(TackyLabel("end")), - TackyLabel("middle"), - TackyLabel("end"), - TackyRet(TackyVar("x")) - ) - assertEquals(expectedInstructions, optimizedInstructions) + fun `test complex control flow with live variables`() { + // Test case: x = 5; if (x) { y = 10; } else { z = 15; } return x; + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyJump(TackyLabel("else")), + TackyCopy(TackyConstant(10), TackyVar("y")), // Dead store + TackyJump(TackyLabel("end")), + TackyLabel("else"), + TackyCopy(TackyConstant(15), TackyVar("z")), // Dead store + TackyLabel("end"), + TackyRet(TackyVar("x")) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // x assignment should be preserved, y and z assignments should be eliminated + val allInstructions = result.blocks.flatMap { it.instructions } + assertTrue(allInstructions.any { it is TackyCopy && it.dest.name == "x" }) + assertTrue(allInstructions.none { it is TackyCopy && it.dest.name == "y" }) + assertTrue(allInstructions.none { it is TackyCopy && it.dest.name == "z" }) + } + + @Test + fun `test liveness analysis with multiple blocks`() { + // Test case: x = 5; jump L1; L1: y = x; return y; + val instructions = listOf( + TackyCopy(TackyConstant(5), TackyVar("x")), + TackyJump(TackyLabel("L1")), + TackyLabel("L1"), + TackyCopy(TackyVar("x"), TackyVar("y")), + TackyRet(TackyVar("y")) + ) + + val function = TackyFunction("test", emptyList(), instructions) + val program = TackyProgram(listOf(function)) + val cfg = ControlFlowGraph().construct("test", instructions) + val optimizer = DeadStoreElimination() + + val result = optimizer.apply(cfg) + + // Both x and y assignments should be preserved as they form a live chain + val allInstructions = result.blocks.flatMap { it.instructions } + assertTrue(allInstructions.any { it is TackyCopy && it.dest.name == "x" }) + assertTrue(allInstructions.any { it is TackyCopy && it.dest.name == "y" }) } } diff --git a/src/jsTest/kotlin/optimizations/OptimizationTest.kt b/src/jsTest/kotlin/optimizations/OptimizationTest.kt deleted file mode 100644 index a136ec5..0000000 --- a/src/jsTest/kotlin/optimizations/OptimizationTest.kt +++ /dev/null @@ -1,425 +0,0 @@ -package optimizations - -import tacky.TackyBinary -import tacky.TackyBinaryOP -import tacky.TackyConstant -import tacky.TackyCopy -import tacky.TackyFunction -import tacky.TackyLabel -import tacky.TackyProgram -import tacky.TackyRet -import tacky.TackyVar -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OptimizationTest { - - // ===== OptimizationType Tests ===== - - @Test - fun `OptimizationType should have correct values`() { - assertEquals("CONSTANT_FOLDING", OptimizationType.CONSTANT_FOLDING.name) - assertEquals("DEAD_STORE_ELIMINATION", OptimizationType.DEAD_STORE_ELIMINATION.name) - } - - // ===== OptimizationManager Tests ===== - - @Test - fun `optimizeProgram should return same program for empty functions`() { - val program = TackyProgram( - listOf( - TackyFunction("empty", emptyList(), emptyList()) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(program, result) - } - - @Test - fun `optimizeProgram should return same program for empty optimization set`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, emptySet()) - - assertEquals(program, result) - } - - @Test - fun `optimizeProgram should apply constant folding optimization`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - // Should fold 1 + 2 to 3 - val optimizedInstructions = result.functions[0].body - assertTrue( - optimizedInstructions.any { - it is TackyCopy && it.src is TackyConstant && (it.src as TackyConstant).value == 3 - } - ) - } - - @Test - fun `optimizeProgram should apply dead store elimination optimization`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), // Dead store - TackyCopy(TackyConstant(2), TackyVar("y")), // Dead store - TackyRet(TackyConstant(0)) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.DEAD_STORE_ELIMINATION)) - - // Should eliminate dead stores - val optimizedInstructions = result.functions[0].body - assertEquals(1, optimizedInstructions.size) - assertTrue(optimizedInstructions[0] is TackyRet) - } - - @Test - fun `optimizeProgram should apply multiple optimizations`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyCopy(TackyConstant(3), TackyVar("y")), // Dead store - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram( - program, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - val optimizedInstructions = result.functions[0].body - // Should have constant folding result and no dead stores - assertTrue(optimizedInstructions.size <= 2) - assertTrue(optimizedInstructions.any { it is TackyRet }) - } - - @Test - fun `optimizeProgram should handle multiple functions`() { - val program = TackyProgram( - listOf( - TackyFunction( - "func1", - emptyList(), - listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ), - TackyFunction( - "func2", - emptyList(), - listOf( - TackyCopy(TackyConstant(3), TackyVar("y")), - TackyRet(TackyVar("y")) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(2, result.functions.size) - assertEquals("func1", result.functions[0].name) - assertEquals("func2", result.functions[1].name) - } - - @Test - fun `optimizeProgram should preserve function names and arguments`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - listOf("arg1", "arg2"), - listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals("test", result.functions[0].name) - assertEquals(listOf("arg1", "arg2"), result.functions[0].args) - } - - // ===== applyOptimizations Tests ===== - - @Test - fun `applyOptimizations should return same CFG for empty optimization set`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations(cfg, emptySet()) - - assertEquals(cfg, result) - } - - @Test - fun `applyOptimizations should apply single optimization`() { - val instructions = listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations(cfg, setOf(OptimizationType.CONSTANT_FOLDING)) - - val optimizedInstructions = result.toInstructions() - assertTrue( - optimizedInstructions.any { - it is TackyCopy && it.src is TackyConstant && (it.src as TackyConstant).value == 3 - } - ) - } - - @Test - fun `applyOptimizations should apply multiple optimizations in sequence`() { - val instructions = listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyCopy(TackyConstant(3), TackyVar("y")), // Dead store - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - val optimizedInstructions = result.toInstructions() - // Should have constant folding result and no dead stores - assertTrue(optimizedInstructions.size <= 2) - assertTrue(optimizedInstructions.any { it is TackyRet }) - } - - @Test - fun `applyOptimizations should terminate when no changes occur`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations(cfg, setOf(OptimizationType.CONSTANT_FOLDING)) - - // Should not change since no constant folding is possible - assertEquals(cfg.toInstructions(), result.toInstructions()) - } - - @Test - fun `applyOptimizations should handle valid optimization types`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - // This should not cause an error - val result = OptimizationManager.applyOptimizations(cfg, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertNotNull(result) - } - - // ===== Iterative Optimization Tests ===== - - @Test - fun `applyOptimizations should iterate until convergence`() { - // Create a program that requires multiple optimization passes - val instructions = listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), // 1+2=3 - TackyBinary(TackyBinaryOP.ADD, TackyVar("x"), TackyConstant(1), TackyVar("y")), // 3+1=4 - TackyCopy(TackyConstant(5), TackyVar("z")), // Dead store - TackyRet(TackyVar("y")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - val optimizedInstructions = result.toInstructions() - // Should have optimized both constant folding and dead store elimination - assertTrue(optimizedInstructions.size <= 3) - assertTrue(optimizedInstructions.any { it is TackyRet }) - } - - @Test - fun `applyOptimizations should handle empty CFG`() { - val cfg = ControlFlowGraph() - - val result = OptimizationManager.applyOptimizations(cfg, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(cfg, result) - } - - @Test - fun `applyOptimizations should handle CFG with no instructions`() { - val cfg = ControlFlowGraph().construct("test", emptyList()) - - val result = OptimizationManager.applyOptimizations(cfg, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(cfg, result) - } - - // ===== Edge Cases ===== - - @Test - fun `optimizeProgram should handle program with no functions`() { - val program = TackyProgram(emptyList()) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(program, result) - } - - @Test - fun `optimizeProgram should handle functions with only labels and jumps`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyLabel("start"), - TackyLabel("end") - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(program, result) - } - - @Test - fun `optimizeProgram should handle functions with only returns`() { - val program = TackyProgram( - listOf( - TackyFunction( - "test", - emptyList(), - listOf( - TackyRet(TackyConstant(0)) - ) - ) - ) - ) - - val result = OptimizationManager.optimizeProgram(program, setOf(OptimizationType.CONSTANT_FOLDING)) - - assertEquals(program, result) - } - - // ===== Optimization Order Tests ===== - - @Test - fun `applyOptimizations should apply optimizations in the order specified`() { - val instructions = listOf( - TackyBinary(TackyBinaryOP.ADD, TackyConstant(1), TackyConstant(2), TackyVar("x")), - TackyCopy(TackyConstant(3), TackyVar("y")), // Dead store - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - // Apply constant folding first, then dead store elimination - val result1 = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - // Apply dead store elimination first, then constant folding - val result2 = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.DEAD_STORE_ELIMINATION, OptimizationType.CONSTANT_FOLDING) - ) - - // Both should converge to the same result - assertEquals(result1.toInstructions(), result2.toInstructions()) - } - - // ===== Performance Tests ===== - - @Test - fun `applyOptimizations should not infinite loop`() { - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyRet(TackyVar("x")) - ) - val cfg = ControlFlowGraph().construct("test", instructions) - - // This should terminate quickly - val result = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - assertNotNull(result) - } - - @Test - fun `applyOptimizations should handle large instruction sequences`() { - val instructions = (1..100).map { i -> - TackyBinary(TackyBinaryOP.ADD, TackyConstant(i), TackyConstant(1), TackyVar("x$i")) - } + listOf(TackyRet(TackyVar("x100"))) - - val cfg = ControlFlowGraph().construct("test", instructions) - - val result = OptimizationManager.applyOptimizations( - cfg, - setOf(OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION) - ) - - assertNotNull(result) - assertTrue(result.toInstructions().isNotEmpty()) - } -} diff --git a/src/jsTest/kotlin/optimizations/UnreachableCodeEliminationTest.kt b/src/jsTest/kotlin/optimizations/UnreachableCodeEliminationTest.kt index a5c9bca..968b065 100644 --- a/src/jsTest/kotlin/optimizations/UnreachableCodeEliminationTest.kt +++ b/src/jsTest/kotlin/optimizations/UnreachableCodeEliminationTest.kt @@ -13,7 +13,6 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class UnreachableCodeEliminationTest { - private val optimization = UnreachableCodeElimination() // --- Tests for removeUnreachableBlocks --- @@ -21,12 +20,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUnreachableBlocks should keep all blocks when all are reachable`() { // Arrange - val instructions = listOf( - JumpIfZero(TackyConstant(0), TackyLabel("L1")), - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("L1"), - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + JumpIfZero(TackyConstant(0), TackyLabel("L1")), + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("L1"), + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("main", instructions) val originalBlockCount = cfg.blocks.size @@ -40,12 +40,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUnreachableBlocks should eliminate block after an unconditional jump`() { // Arrange - val instructions = listOf( - TackyJump(TackyLabel("L1")), - TackyCopy(TackyConstant(5), TackyVar("x")), // Unreachable - TackyLabel("L1"), - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + TackyJump(TackyLabel("L1")), + TackyCopy(TackyConstant(5), TackyVar("x")), // Unreachable + TackyLabel("L1"), + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("main", instructions) val originalBlockCount = cfg.blocks.size @@ -54,19 +55,21 @@ class UnreachableCodeEliminationTest { // Assert assertTrue(result.blocks.size < originalBlockCount, "Should have removed unreachable blocks.") - val hasUnreachableCopy = result.toInstructions().any { instruction -> - instruction is TackyCopy && instruction.src is TackyConstant && (instruction.src as TackyConstant).value == 5 - } + val hasUnreachableCopy = + result.toInstructions().any { instruction -> + instruction is TackyCopy && instruction.src is TackyConstant && instruction.src.value == 5 + } assertFalse(hasUnreachableCopy, "The unreachable TackyCopy instruction should be gone.") } @Test fun `removeUnreachableBlocks should eliminate block after a return statement`() { // Arrange - val instructions = listOf( - TackyRet(TackyConstant(1)), - TackyCopy(TackyConstant(5), TackyVar("x")) // Unreachable - ) + val instructions = + listOf( + TackyRet(TackyConstant(1)), + TackyCopy(TackyConstant(5), TackyVar("x")) // Unreachable + ) val cfg = ControlFlowGraph().construct("main", instructions) val originalBlockCount = cfg.blocks.size @@ -83,15 +86,16 @@ class UnreachableCodeEliminationTest { @Test fun `removeUnreachableBlocks should handle complex unreachable code patterns`() { // Arrange - val instructions = listOf( - TackyJump(TackyLabel("L2")), - TackyCopy(TackyConstant(1), TackyVar("a")), // Unreachable - TackyCopy(TackyConstant(2), TackyVar("b")), // Unreachable - TackyLabel("L1"), - TackyCopy(TackyConstant(3), TackyVar("c")), // Unreachable - TackyLabel("L2"), - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + TackyJump(TackyLabel("L2")), + TackyCopy(TackyConstant(1), TackyVar("a")), // Unreachable + TackyCopy(TackyConstant(2), TackyVar("b")), // Unreachable + TackyLabel("L1"), + TackyCopy(TackyConstant(3), TackyVar("c")), // Unreachable + TackyLabel("L2"), + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -109,12 +113,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessJumps should remove a jump to the next block`() { // Arrange - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyJump(TackyLabel("Next")), // This jump is useless - TackyLabel("Next"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyJump(TackyLabel("Next")), // This jump is useless + TackyLabel("Next"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -130,12 +135,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessJumps should keep a necessary jump`() { // Arrange - val instructions = listOf( - JumpIfZero(TackyConstant(0), TackyLabel("Far")), // Conditional jump - necessary - TackyCopy(TackyConstant(1), TackyVar("x")), // This will be in the next block - TackyLabel("Far"), - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + JumpIfZero(TackyConstant(0), TackyLabel("Far")), // Conditional jump - necessary + TackyCopy(TackyConstant(1), TackyVar("x")), // This will be in the next block + TackyLabel("Far"), + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -149,12 +155,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessJumps should remove useless conditional jumps`() { // Arrange - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - JumpIfZero(TackyVar("x"), TackyLabel("Next")), // Useless - jumps to next block - TackyLabel("Next"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + JumpIfZero(TackyVar("x"), TackyLabel("Next")), // Useless - jumps to next block + TackyLabel("Next"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -167,12 +174,13 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessJumps should keep necessary conditional jumps`() { // Arrange - val instructions = listOf( - JumpIfZero(TackyConstant(0), TackyLabel("Else")), - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("Else"), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + JumpIfZero(TackyConstant(0), TackyLabel("Else")), + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("Else"), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -187,11 +195,12 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessLabels should remove a label that is only fallen into`() { // Arrange - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("Unused"), // This label is useless - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("Unused"), // This label is useless + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -207,13 +216,14 @@ class UnreachableCodeEliminationTest { @Test fun `removeUselessLabels should remove multiple useless labels`() { // Arrange - val instructions = listOf( - TackyCopy(TackyConstant(1), TackyVar("x")), - TackyLabel("L1"), // Useless - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyLabel("L2"), // Useless - TackyRet(TackyVar("y")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(1), TackyVar("x")), + TackyLabel("L1"), // Useless + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyLabel("L2"), // Useless + TackyRet(TackyVar("y")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -243,12 +253,13 @@ class UnreachableCodeEliminationTest { block1.successors.add(block2.id) block2.predecessors.add(block1.id) - val cfg = ControlFlowGraph( - functionName = "main", - root = root, - blocks = listOf(block0, block1, block2), - edges = listOf(Edge(root, block0), Edge(block0, block1), Edge(block1, block2)) - ) + val cfg = + ControlFlowGraph( + functionName = "main", + root = root, + blocks = listOf(block0, block1, block2), + edges = mutableListOf(Edge(root, block0), Edge(block0, block1), Edge(block1, block2)) + ) // Act val result = optimization.apply(cfg) @@ -284,19 +295,25 @@ class UnreachableCodeEliminationTest { block2.predecessors.add(block1.id) block3.predecessors.add(block1.id) - val cfg = ControlFlowGraph( - functionName = "main", - root = root, - blocks = listOf(block0, block1, block2, block3), - edges = listOf(Edge(root, block0), Edge(block0, block1), Edge(block1, block2), Edge(block1, block3)) - ) + val cfg = + ControlFlowGraph( + functionName = "main", + root = root, + blocks = listOf(block0, block1, block2, block3), + edges = mutableListOf(Edge(root, block0), Edge(block0, block1), Edge(block1, block2), Edge(block1, block3)) + ) // Act val result = optimization.apply(cfg) // Assert assertEquals(4, result.blocks.size, "Should not remove empty block with multiple successors.") - assertTrue(result.blocks.any { block -> block.id == block1.id && block.instructions.isEmpty() }, "Empty block with multiple successors should remain.") + assertTrue( + result.blocks.any { block -> + block.id == block1.id && block.instructions.isEmpty() + }, + "Empty block with multiple successors should remain." + ) } // --- Integration tests --- @@ -304,15 +321,16 @@ class UnreachableCodeEliminationTest { @Test fun `apply should perform all optimizations in sequence`() { // Arrange - val instructions = listOf( - TackyJump(TackyLabel("L1")), // Will create unreachable code - TackyCopy(TackyConstant(1), TackyVar("x")), // Unreachable - TackyLabel("L1"), - TackyCopy(TackyConstant(2), TackyVar("y")), - TackyJump(TackyLabel("Next")), // Useless jump - TackyLabel("Next"), - TackyRet(TackyVar("y")) - ) + val instructions = + listOf( + TackyJump(TackyLabel("L1")), // Will create unreachable code + TackyCopy(TackyConstant(1), TackyVar("x")), // Unreachable + TackyLabel("L1"), + TackyCopy(TackyConstant(2), TackyVar("y")), + TackyJump(TackyLabel("Next")), // Useless jump + TackyLabel("Next"), + TackyRet(TackyVar("y")) + ) val cfg = ControlFlowGraph().construct("main", instructions) // Act @@ -324,7 +342,7 @@ class UnreachableCodeEliminationTest { // Should not contain unreachable code assertFalse( resultInstructions.any { instruction -> - instruction is TackyCopy && instruction.src is TackyConstant && (instruction.src as TackyConstant).value == 1 + instruction is TackyCopy && instruction.src is TackyConstant && instruction.src.value == 1 }, "Unreachable copy should be removed." ) @@ -335,7 +353,7 @@ class UnreachableCodeEliminationTest { // Should contain the essential instructions assertTrue( resultInstructions.any { instruction -> - instruction is TackyCopy && instruction.src is TackyConstant && (instruction.src as TackyConstant).value == 2 + instruction is TackyCopy && instruction.src is TackyConstant && instruction.src.value == 2 }, "Reachable copy should remain." ) @@ -345,7 +363,7 @@ class UnreachableCodeEliminationTest { @Test fun `apply should handle empty CFG gracefully`() { // Arrange - val cfg = ControlFlowGraph(functionName = "empty", root = null, blocks = emptyList(), edges = emptyList()) + val cfg = ControlFlowGraph(functionName = "empty", root = null, blocks = emptyList(), edges = mutableListOf()) // Act val result = optimization.apply(cfg) @@ -358,10 +376,11 @@ class UnreachableCodeEliminationTest { @Test fun `apply should handle single block CFG`() { // Arrange - val instructions = listOf( - TackyCopy(TackyConstant(42), TackyVar("x")), - TackyRet(TackyVar("x")) - ) + val instructions = + listOf( + TackyCopy(TackyConstant(42), TackyVar("x")), + TackyRet(TackyVar("x")) + ) val cfg = ControlFlowGraph().construct("single", instructions) // Act @@ -378,9 +397,10 @@ class UnreachableCodeEliminationTest { @Test fun `apply should preserve function name and root`() { // Arrange - val instructions = listOf( - TackyRet(TackyConstant(0)) - ) + val instructions = + listOf( + TackyRet(TackyConstant(0)) + ) val cfg = ControlFlowGraph().construct("testFunction", instructions) // Act diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_paren.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_paren.c new file mode 100644 index 0000000..8e72a5c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_paren.c @@ -0,0 +1,3 @@ +int main( { + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_retval.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_retval.c new file mode 100644 index 0000000..2f8d9bf --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_retval.c @@ -0,0 +1,3 @@ +int main() { + return; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_brace.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_brace.c new file mode 100644 index 0000000..96aac66 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_brace.c @@ -0,0 +1,2 @@ +int main() { + return 0; diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_semicolon.c new file mode 100644 index 0000000..584f789 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_semicolon.c @@ -0,0 +1,3 @@ +int main() { + return 0 +} diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_space.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_space.c new file mode 100644 index 0000000..14a289b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/no_space.c @@ -0,0 +1,3 @@ +int main() { + return0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/invalid/wrong_case.c b/src/resources/write_a_c_compiler-tests/stage_1/invalid/wrong_case.c new file mode 100644 index 0000000..bbae350 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/invalid/wrong_case.c @@ -0,0 +1,3 @@ +int main() { + RETURN 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/multi_digit.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/multi_digit.c new file mode 100644 index 0000000..13f3123 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/multi_digit.c @@ -0,0 +1,3 @@ +int main() { + return 100; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/newlines.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/newlines.c new file mode 100644 index 0000000..7d8ba2e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/newlines.c @@ -0,0 +1,10 @@ + +int +main +( +) +{ +return +0 +; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/no_newlines.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/no_newlines.c new file mode 100644 index 0000000..6a86477 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/no_newlines.c @@ -0,0 +1 @@ +int main(){return 0;} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/return_0.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/return_0.c new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/return_0.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/return_2.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/return_2.c new file mode 100644 index 0000000..5c52fee --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/return_2.c @@ -0,0 +1,3 @@ +int main() { + return 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_1/valid/spaces.c b/src/resources/write_a_c_compiler-tests/stage_1/valid/spaces.c new file mode 100644 index 0000000..e77bfef --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_1/valid/spaces.c @@ -0,0 +1 @@ + int main ( ) { return 0 ; } \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/fun_redefined_as_var.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/fun_redefined_as_var.c new file mode 100644 index 0000000..8efff10 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/fun_redefined_as_var.c @@ -0,0 +1,9 @@ +int foo() { + return 3; +} + +int foo = 4; + +int main() { + return foo; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/multiple_global_defs.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/multiple_global_defs.c new file mode 100644 index 0000000..d46a90e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/multiple_global_defs.c @@ -0,0 +1,7 @@ +int foo = 3; + +int main() { + return foo; +} + +int foo = 0; \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/non_constant_init.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/non_constant_init.c new file mode 100644 index 0000000..aa5f3f8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/non_constant_init.c @@ -0,0 +1,6 @@ +int foo = 3; +int bar = foo + 1; + +int main() { + return bar; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/use_before_declaration.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/use_before_declaration.c new file mode 100644 index 0000000..32504c6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/use_before_declaration.c @@ -0,0 +1,5 @@ +int main() { + return foo; +} + +int foo = 3; \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/var_redefined_as_fun.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/var_redefined_as_fun.c new file mode 100644 index 0000000..cf39438 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/var_redefined_as_fun.c @@ -0,0 +1,9 @@ +int foo = 4; + +int foo() { + return 3; +} + +int main() { + return foo; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/invalid/variable_used_as_fun.c b/src/resources/write_a_c_compiler-tests/stage_10/invalid/variable_used_as_fun.c new file mode 100644 index 0000000..3d4884b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/invalid/variable_used_as_fun.c @@ -0,0 +1,5 @@ +int foo = 3; + +int main() { + return foo(); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/forward_declaration.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/forward_declaration.c new file mode 100644 index 0000000..ce6e560 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/forward_declaration.c @@ -0,0 +1,7 @@ +int foo; + +int main() { + return foo; +} + +int foo = 3; \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/fun_shadowed_by_variable.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/fun_shadowed_by_variable.c new file mode 100644 index 0000000..8f400c8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/fun_shadowed_by_variable.c @@ -0,0 +1,8 @@ +int foo() { + return 3; +} + +int main() { + int foo = 5; + return foo; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/global.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/global.c new file mode 100644 index 0000000..59095a6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/global.c @@ -0,0 +1,5 @@ +int foo = 4; + +int main() { + return foo + 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/global_not_initialized.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/global_not_initialized.c new file mode 100644 index 0000000..d8b75ec --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/global_not_initialized.c @@ -0,0 +1,7 @@ +int foo; + +int main() { + for (int i = 0; i < 3; i = i + 1) + foo = foo + 1; + return foo; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/global_shadowed.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/global_shadowed.c new file mode 100644 index 0000000..762c326 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/global_shadowed.c @@ -0,0 +1,10 @@ +int a = 3; + +int main() { + int ret = 0; + if (a) { + int a = 0; + ret = 4; + } + return ret; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_10/valid/multiple_global.c b/src/resources/write_a_c_compiler-tests/stage_10/valid/multiple_global.c new file mode 100644 index 0000000..909e246 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_10/valid/multiple_global.c @@ -0,0 +1,6 @@ +int a = 3; +int b = 4; + +int main() { + return a * b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_const.c b/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_const.c new file mode 100644 index 0000000..6dd069e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_const.c @@ -0,0 +1,3 @@ +int main() { + return !; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_semicolon.c new file mode 100644 index 0000000..5570d64 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_semicolon.c @@ -0,0 +1,3 @@ +int main() { + return !5 +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/invalid/nested_missing_const.c b/src/resources/write_a_c_compiler-tests/stage_2/invalid/nested_missing_const.c new file mode 100644 index 0000000..43b7097 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/invalid/nested_missing_const.c @@ -0,0 +1,3 @@ +int main() { + return !~; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/invalid/wrong_order.c b/src/resources/write_a_c_compiler-tests/stage_2/invalid/wrong_order.c new file mode 100644 index 0000000..27a9f02 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/invalid/wrong_order.c @@ -0,0 +1,3 @@ +int main() { + return 4-; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise.c new file mode 100644 index 0000000..a0070d3 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise.c @@ -0,0 +1,3 @@ +int main() { + return ~12; +} diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise_zero.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise_zero.c new file mode 100644 index 0000000..2c2ed2e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise_zero.c @@ -0,0 +1,3 @@ +int main() { + return ~0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/neg.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/neg.c new file mode 100644 index 0000000..b7ac431 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/neg.c @@ -0,0 +1,3 @@ +int main() { + return -5; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops.c new file mode 100644 index 0000000..9fb3f87 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops.c @@ -0,0 +1,3 @@ +int main() { + return !-3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops_2.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops_2.c new file mode 100644 index 0000000..416d4d1 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops_2.c @@ -0,0 +1,3 @@ +int main() { + return -~0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/not_five.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/not_five.c new file mode 100644 index 0000000..df792bb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/not_five.c @@ -0,0 +1,3 @@ +int main() { + return !5; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_2/valid/not_zero.c b/src/resources/write_a_c_compiler-tests/stage_2/valid/not_zero.c new file mode 100644 index 0000000..b6b7cb5 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_2/valid/not_zero.c @@ -0,0 +1,3 @@ +int main() { + return !0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/invalid/malformed_paren.c b/src/resources/write_a_c_compiler-tests/stage_3/invalid/malformed_paren.c new file mode 100644 index 0000000..3c444bc --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/invalid/malformed_paren.c @@ -0,0 +1,3 @@ +int main() { + return 2 (- 3); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_first_op.c b/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_first_op.c new file mode 100644 index 0000000..84a3c34 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_first_op.c @@ -0,0 +1,3 @@ +int main() { + return /3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_second_op.c b/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_second_op.c new file mode 100644 index 0000000..a0a313b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_second_op.c @@ -0,0 +1,3 @@ +int main() { + return 1 + ; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/invalid/no_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_3/invalid/no_semicolon.c new file mode 100644 index 0000000..0704f18 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/invalid/no_semicolon.c @@ -0,0 +1,3 @@ +int main() { + return 2*2 +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/add.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/add.c new file mode 100644 index 0000000..464d543 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/add.c @@ -0,0 +1,3 @@ +int main() { + return 1 + 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity.c new file mode 100644 index 0000000..d099bdf --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity.c @@ -0,0 +1,3 @@ +int main() { + return 1 - 2 - 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity_2.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity_2.c new file mode 100644 index 0000000..511ba51 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/associativity_2.c @@ -0,0 +1,3 @@ +int main() { + return 6 / 3 / 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/div.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/div.c new file mode 100644 index 0000000..ebe1577 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/div.c @@ -0,0 +1,3 @@ +int main() { + return 4 / 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/div_neg.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/div_neg.c new file mode 100644 index 0000000..a8feb93 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/div_neg.c @@ -0,0 +1,3 @@ +int main() { + return (-12) / 5; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/mult.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/mult.c new file mode 100644 index 0000000..fb22f95 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/mult.c @@ -0,0 +1,3 @@ +int main() { + return 2 * 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/parens.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/parens.c new file mode 100644 index 0000000..f7d427b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/parens.c @@ -0,0 +1,3 @@ +int main() { + return 2 * (3 + 4); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/precedence.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/precedence.c new file mode 100644 index 0000000..b6d3c3d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/precedence.c @@ -0,0 +1,3 @@ +int main() { + return 2 + 3 * 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/sub.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/sub.c new file mode 100644 index 0000000..39cdcfa --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/sub.c @@ -0,0 +1,3 @@ +int main() { + return 1 - 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/sub_neg.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/sub_neg.c new file mode 100644 index 0000000..67b0dac --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/sub_neg.c @@ -0,0 +1,3 @@ +int main() { + return 2- -1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_add.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_add.c new file mode 100644 index 0000000..3049b00 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_add.c @@ -0,0 +1,3 @@ +int main() { + return ~2 + 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_parens.c b/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_parens.c new file mode 100644 index 0000000..d3f6e24 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_3/valid/unop_parens.c @@ -0,0 +1,3 @@ +int main() { + return ~(1 + 1); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_first_op.c b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_first_op.c new file mode 100644 index 0000000..5af3bb3 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_first_op.c @@ -0,0 +1,3 @@ +int main() { + return <= 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_mid_op.c b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_mid_op.c new file mode 100644 index 0000000..4b36056 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_mid_op.c @@ -0,0 +1,3 @@ +int main() { + return 1 < > 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_second_op.c b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_second_op.c new file mode 100644 index 0000000..8f1513a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_second_op.c @@ -0,0 +1,3 @@ +int main() { + return 2 && +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_semicolon.c new file mode 100644 index 0000000..deafd68 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_semicolon.c @@ -0,0 +1,3 @@ +int main() { + return 1 || 2 +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/and_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/and_false.c new file mode 100644 index 0000000..f1ad90b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/and_false.c @@ -0,0 +1,3 @@ +int main() { + return 1 && 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/and_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/and_true.c new file mode 100644 index 0000000..87d3131 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/and_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 && -1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_false.c new file mode 100644 index 0000000..39895e9 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_false.c @@ -0,0 +1,3 @@ +int main() { + return 1 == 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_true.c new file mode 100644 index 0000000..1a6f8eb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/eq_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 == 1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_false.c new file mode 100644 index 0000000..abeaf9a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_false.c @@ -0,0 +1,3 @@ +int main() { + return 1 >= 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_true.c new file mode 100644 index 0000000..e3729d0 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/ge_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 >= 1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_false.c new file mode 100644 index 0000000..3bf766b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_false.c @@ -0,0 +1,3 @@ +int main() { + return 1 > 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_true.c new file mode 100644 index 0000000..ad051a4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/gt_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 > 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/le_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/le_false.c new file mode 100644 index 0000000..e29deeb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/le_false.c @@ -0,0 +1,3 @@ +int main() { + return 1 <= -1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/le_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/le_true.c new file mode 100644 index 0000000..d45dbeb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/le_true.c @@ -0,0 +1,3 @@ +int main() { + return 0 <= 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_false.c new file mode 100644 index 0000000..5e33fae --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_false.c @@ -0,0 +1,3 @@ +int main() { + return 2 < 1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_true.c new file mode 100644 index 0000000..a82436b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/lt_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 < 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_false.c new file mode 100644 index 0000000..b13c3a8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_false.c @@ -0,0 +1,3 @@ +int main() { + return 0 != 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_true.c new file mode 100644 index 0000000..de775a4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/ne_true.c @@ -0,0 +1,3 @@ +int main() { + return -1 != -2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/or_false.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/or_false.c new file mode 100644 index 0000000..693c1f5 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/or_false.c @@ -0,0 +1,3 @@ +int main() { + return 0 || 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/or_true.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/or_true.c new file mode 100644 index 0000000..fb40e98 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/or_true.c @@ -0,0 +1,3 @@ +int main() { + return 1 || 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence.c new file mode 100644 index 0000000..711bde1 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence.c @@ -0,0 +1,3 @@ +int main() { + return 1 || 0 && 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_2.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_2.c new file mode 100644 index 0000000..7d3fc81 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_2.c @@ -0,0 +1,3 @@ +int main() { + return (1 || 0) && 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_3.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_3.c new file mode 100644 index 0000000..8bf357a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_3.c @@ -0,0 +1,3 @@ +int main() { + return 2 == 2 > 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_4.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_4.c new file mode 100644 index 0000000..87d4a39 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_4.c @@ -0,0 +1,3 @@ +int main() { + return 2 == 2 || 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_multi_short_circuit.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_multi_short_circuit.c new file mode 100644 index 0000000..8d6912f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_multi_short_circuit.c @@ -0,0 +1,5 @@ +int main() { + int a = 0; + a || (a = 3) || (a = 4); + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_and.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_and.c new file mode 100644 index 0000000..a48b05c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_and.c @@ -0,0 +1,6 @@ +int main() { + int a = 0; + int b = 0; + a && (b = 5); + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_or.c b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_or.c new file mode 100644 index 0000000..934f771 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_or.c @@ -0,0 +1,6 @@ +int main() { + int a = 1; + int b = 0; + a || (b = 5); + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/redefine.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/redefine.c new file mode 100644 index 0000000..3b2f9c4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/redefine.c @@ -0,0 +1,5 @@ +int main() { + int a = 1; + int a = 2; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl.c new file mode 100644 index 0000000..1ed774d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl.c @@ -0,0 +1,4 @@ +int main() { + ints a = 1; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl_2.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl_2.c new file mode 100644 index 0000000..5d6b4d0 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl_2.c @@ -0,0 +1,4 @@ +int main() { + int foo bar = 3; + return bar; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue.c new file mode 100644 index 0000000..7f8a5f8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue.c @@ -0,0 +1,5 @@ +int main() { + int a = 2; + a + 3 = 4; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue_2.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue_2.c new file mode 100644 index 0000000..2954621 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue_2.c @@ -0,0 +1,5 @@ +int main() { + int a = 2; + !a = 3; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_no_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_no_semicolon.c new file mode 100644 index 0000000..29f6738 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_no_semicolon.c @@ -0,0 +1,5 @@ +int main() { + int a = 2 + a = a + 4; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/undeclared_var.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/undeclared_var.c new file mode 100644 index 0000000..a49ad45 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/undeclared_var.c @@ -0,0 +1,3 @@ +int main() { + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/invalid/var_declared_late.c b/src/resources/write_a_c_compiler-tests/stage_5/invalid/var_declared_late.c new file mode 100644 index 0000000..a283aab --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/invalid/var_declared_late.c @@ -0,0 +1,5 @@ +int main() { + a = 1 + 2; + int a; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/assign.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/assign.c new file mode 100644 index 0000000..10f91a0 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/assign.c @@ -0,0 +1,5 @@ +int main() { + int a; + a = 2; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/assign_val.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/assign_val.c new file mode 100644 index 0000000..3a8d163 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/assign_val.c @@ -0,0 +1,5 @@ +int main() { + int a; + int b = a = 0; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/exp_return_val.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/exp_return_val.c new file mode 100644 index 0000000..76c2f6c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/exp_return_val.c @@ -0,0 +1,6 @@ +int main() { + int a; + int b; + a = b = 4; + return a - b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/initialize.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/initialize.c new file mode 100644 index 0000000..6f0f5dd --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/initialize.c @@ -0,0 +1,4 @@ +int main() { + int a = 2; + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/missing_return.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/missing_return.c new file mode 100644 index 0000000..704dacc --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/missing_return.c @@ -0,0 +1,3 @@ +int main() { + +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/multiple_vars.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/multiple_vars.c new file mode 100644 index 0000000..0d4caef --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/multiple_vars.c @@ -0,0 +1,5 @@ +int main() { + int a = 1; + int b = 2; + return a + b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/no_initialize.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/no_initialize.c new file mode 100644 index 0000000..b6543fb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/no_initialize.c @@ -0,0 +1,4 @@ +int main() { + int a; + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/refer.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/refer.c new file mode 100644 index 0000000..89e9b3f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/refer.c @@ -0,0 +1,4 @@ +int main() { + int a = 2; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_5/valid/unused_exp.c b/src/resources/write_a_c_compiler-tests/stage_5/valid/unused_exp.c new file mode 100644 index 0000000..82e2181 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_5/valid/unused_exp.c @@ -0,0 +1,4 @@ +int main() { + 2 + 2; + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/incomplete_ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/incomplete_ternary.c new file mode 100644 index 0000000..d0e69e9 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/incomplete_ternary.c @@ -0,0 +1,3 @@ +int main() { + return 1 ? 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary.c new file mode 100644 index 0000000..020d41a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary.c @@ -0,0 +1,3 @@ +int main() { + return 1 ? 2 : 3 : 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary_2.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary_2.c new file mode 100644 index 0000000..e902288 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary_2.c @@ -0,0 +1,3 @@ +int main() { + return 1 ? 2 ? 3 : 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/ternary_assign.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/ternary_assign.c new file mode 100644 index 0000000..b2ff76b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/ternary_assign.c @@ -0,0 +1,6 @@ +int main() { + int a = 2; + int b = 1; + a > b ? a = 1 : a = 0; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/declare_statement.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/declare_statement.c new file mode 100644 index 0000000..dca9d0c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/declare_statement.c @@ -0,0 +1,4 @@ +int main() { + if (5) + int i = 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/if_assignment.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/if_assignment.c new file mode 100644 index 0000000..ad3036d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/if_assignment.c @@ -0,0 +1,8 @@ +int main() { + int flag = 0; + int a = if (flag) + 2; + else + 3; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/mismatched_nesting.c b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/mismatched_nesting.c new file mode 100644 index 0000000..a3322bc --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/mismatched_nesting.c @@ -0,0 +1,9 @@ +int main() { + int a = 0; + if (1) + return 1; + else + return 2; + else + return 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/assign_ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/assign_ternary.c new file mode 100644 index 0000000..519ce72 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/assign_ternary.c @@ -0,0 +1,5 @@ +int main() { + int a = 0; + a = 1 ? 2 : 3; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/multiple_ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/multiple_ternary.c new file mode 100644 index 0000000..d2e7cc2 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/multiple_ternary.c @@ -0,0 +1,5 @@ +int main() { + int a = 1 > 2 ? 3 : 4; + int b = 1 > 2 ? 5 : 6; + return a + b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary.c new file mode 100644 index 0000000..b18a4d1 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary.c @@ -0,0 +1,7 @@ +int main() { + int a = 1; + int b = 2; + int flag = 0; + + return a > b ? 5 : flag ? 6 : 7; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary_2.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary_2.c new file mode 100644 index 0000000..8da4bd7 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary_2.c @@ -0,0 +1,5 @@ +int main() { + int a = 1 ? 2 ? 3 : 4 : 5; + int b = 0 ? 2 ? 3 : 4 : 5; + return a * b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/rh_assignment.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/rh_assignment.c new file mode 100644 index 0000000..d08f6b6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/rh_assignment.c @@ -0,0 +1,6 @@ +int main() { + int flag = 1; + int a = 0; + flag ? a = 1 : (a = 0); + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary.c new file mode 100644 index 0000000..4013c4f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary.c @@ -0,0 +1,4 @@ +int main() { + int a = 0; + return a > -1 ? 4 : 5; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit.c new file mode 100644 index 0000000..70203ef --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit.c @@ -0,0 +1,6 @@ +int main() { + int a = 1; + int b = 0; + a ? (b = 1) : (b = 2); + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit_2.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit_2.c new file mode 100644 index 0000000..78d225e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit_2.c @@ -0,0 +1,6 @@ +int main() { + int a = 0; + int b = 0; + a ? (b = 1) : (b = 2); + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/else.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/else.c new file mode 100644 index 0000000..1314bbe --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/else.c @@ -0,0 +1,7 @@ +int main() { + int a = 0; + if (a) + return 1; + else + return 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested.c new file mode 100644 index 0000000..9b0afa8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested.c @@ -0,0 +1,9 @@ +int main() { + int a = 1; + int b = 0; + if (a) + b = 1; + else if (b) + b = 2; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_2.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_2.c new file mode 100644 index 0000000..2826706 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_2.c @@ -0,0 +1,9 @@ +int main() { + int a = 0; + int b = 1; + if (a) + b = 1; + else if (b) + b = 2; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_3.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_3.c new file mode 100644 index 0000000..e387f00 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_3.c @@ -0,0 +1,10 @@ +int main() { + int a = 0; + if (1) + if (2) + a = 3; + else + a = 4; + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_4.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_4.c new file mode 100644 index 0000000..e2a321c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_4.c @@ -0,0 +1,10 @@ +int main() { + int a = 0; + if (1) + if (0) + a = 3; + else + a = 4; + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_5.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_5.c new file mode 100644 index 0000000..a83f7c8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_5.c @@ -0,0 +1,12 @@ +int main() { + int a = 0; + if (0) + if (0) + a = 3; + else + a = 4; + else + a = 1; + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_not_taken.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_not_taken.c new file mode 100644 index 0000000..41b22c0 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_not_taken.c @@ -0,0 +1,7 @@ +int main() { + int a = 0; + int b = 0; + if (a) + b = 1; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_taken.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_taken.c new file mode 100644 index 0000000..d136154 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_taken.c @@ -0,0 +1,7 @@ +int main() { + int a = 1; + int b = 0; + if (a) + b = 1; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/multiple_if.c b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/multiple_if.c new file mode 100644 index 0000000..b651130 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_6/valid/statement/multiple_if.c @@ -0,0 +1,16 @@ +int main() { + int a = 0; + int b = 0; + + if (a) + a = 2; + else + a = 3; + + if (b) + b = 4; + else + b = 5; + + return a + b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/invalid/double_define.c b/src/resources/write_a_c_compiler-tests/stage_7/invalid/double_define.c new file mode 100644 index 0000000..ae3ed74 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/invalid/double_define.c @@ -0,0 +1,6 @@ +int main() { + { + int a; + int a; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/invalid/out_of_scope.c b/src/resources/write_a_c_compiler-tests/stage_7/invalid/out_of_scope.c new file mode 100644 index 0000000..7a12aba --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/invalid/out_of_scope.c @@ -0,0 +1,6 @@ +int main() { + { + int a = 2; + } + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_extra_brace.c b/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_extra_brace.c new file mode 100644 index 0000000..782c2d6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_extra_brace.c @@ -0,0 +1,6 @@ +int main() { + if(0){ + return 1; + }} + return 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_missing_brace.c b/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_missing_brace.c new file mode 100644 index 0000000..0b1cfc6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_missing_brace.c @@ -0,0 +1,5 @@ +int main() { + if(0){ + return 1; + return 2; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_blocks.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_blocks.c new file mode 100644 index 0000000..1839fb5 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_blocks.c @@ -0,0 +1,9 @@ +int main() { + int a = 1; + { + int a = 2; + } + { + return a; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_declarations.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_declarations.c new file mode 100644 index 0000000..34fad83 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_declarations.c @@ -0,0 +1,12 @@ +int main() { + int a = 0; + { + int b = 1; + a = b; + } + { + int b = 2; + a = a + b; + } + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_after_block.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_after_block.c new file mode 100644 index 0000000..25ad580 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_after_block.c @@ -0,0 +1,8 @@ +int main() { + int i = 0; + { + int a = 2; + } + int b = 3; + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_block.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_block.c new file mode 100644 index 0000000..3c4469c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_block.c @@ -0,0 +1,6 @@ +int main() { + if (5) { + int i = 0; + return i; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_late.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_late.c new file mode 100644 index 0000000..948977d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/declare_late.c @@ -0,0 +1,8 @@ +int main() { + int a = 2; + { + a = 3; + int a = 0; + } + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/multi_nesting.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/multi_nesting.c new file mode 100644 index 0000000..3e4acd6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/multi_nesting.c @@ -0,0 +1,10 @@ +int main(){ + int a = 2; + if (a < 3) { + { + int a = 3; + return a; + } + return a; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_if.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_if.c new file mode 100644 index 0000000..f20a7bf --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_if.c @@ -0,0 +1,15 @@ +int main() { + int a = 0; + if (a) { + int b = 2; + return b; + } else { + int c = 3; + if (a < c) { + return 4; + } else { + return 5; + } + } + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_scope.c b/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_scope.c new file mode 100644 index 0000000..a7268f4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_7/valid/nested_scope.c @@ -0,0 +1,9 @@ +int main() { + int a = 2; + int b = 3; + { + int a = 1; + b = b + a; + } + return b; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/break_not_in_loop.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/break_not_in_loop.c new file mode 100644 index 0000000..f2b16d6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/break_not_in_loop.c @@ -0,0 +1,3 @@ +int main() { + break; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/continue_not_in_loop.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/continue_not_in_loop.c new file mode 100644 index 0000000..4f268a6 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/continue_not_in_loop.c @@ -0,0 +1,3 @@ +int main() { + continue; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope.c new file mode 100644 index 0000000..1246d92 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope.c @@ -0,0 +1,8 @@ +int main() { + + while (1) { + int a = 2; + } + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope_do_while.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope_do_while.c new file mode 100644 index 0000000..7a2815c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope_do_while.c @@ -0,0 +1,6 @@ +int main() { + do { + int a = 2; + } while (a); + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_do_no_semicolon.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_do_no_semicolon.c new file mode 100644 index 0000000..a6552fe --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_do_no_semicolon.c @@ -0,0 +1,5 @@ +int main() { + do + 3; + while (4) +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_empty_clause.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_empty_clause.c new file mode 100644 index 0000000..43ad620 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_empty_clause.c @@ -0,0 +1,4 @@ +int main() { + for (int i = 2; )) + int a = 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_paren_mismatch.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_paren_mismatch.c new file mode 100644 index 0000000..43ad620 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_paren_mismatch.c @@ -0,0 +1,4 @@ +int main() { + for (int i = 2; )) + int a = 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_statement_in_condition.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_statement_in_condition.c new file mode 100644 index 0000000..12457b4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_statement_in_condition.c @@ -0,0 +1,5 @@ +int main() { + while(int a) { + 2; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_few_for_clauses.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_few_for_clauses.c new file mode 100644 index 0000000..9e4205d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_few_for_clauses.c @@ -0,0 +1,5 @@ +int main() { + for (int i = 2; i < 3) + 3; + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_many_for_clauses.c b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_many_for_clauses.c new file mode 100644 index 0000000..fdb2f1c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_many_for_clauses.c @@ -0,0 +1,5 @@ +int main() { + for (;;;) + 3; + return 0; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/break.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/break.c new file mode 100644 index 0000000..bd3d237 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/break.c @@ -0,0 +1,9 @@ +int main() { + int sum = 0; + for (int i = 0; i < 10; i = i + 1) { + sum = sum + i; + if (sum > 10) + break; + } + return sum; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/continue.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/continue.c new file mode 100644 index 0000000..3e70d3f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/continue.c @@ -0,0 +1,9 @@ +int main() { + int sum = 0; + for (int i = 0; i < 10; i = i + 1) { + if ((sum / 2) * 2 != sum) + continue; + sum = sum + i; + } + return sum; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/continue_empty_post.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/continue_empty_post.c new file mode 100644 index 0000000..9e2674f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/continue_empty_post.c @@ -0,0 +1,10 @@ +int main() { + int sum = 0; + for (int i = 0; i < 10;) { + i = i + 1; + if (i % 2) + continue; + sum = sum + i; + } + return sum; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/do_while.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/do_while.c new file mode 100644 index 0000000..d6b038a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/do_while.c @@ -0,0 +1,8 @@ +int main() { + int a = 1; + do { + a = a * 2; + } while(a < 11); + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/empty_expression.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/empty_expression.c new file mode 100644 index 0000000..da04c1a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/empty_expression.c @@ -0,0 +1,7 @@ +int main() { + int i = 3; + ; + for (int i = 0; i < 10; i = i + 1) + ; + return i; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/for.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/for.c new file mode 100644 index 0000000..02f4453 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/for.c @@ -0,0 +1,7 @@ +int main() { + int a = 0; + + for (a = 0; a < 3; a = a + 1) + a = a * 2; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/for_decl.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_decl.c new file mode 100644 index 0000000..9505855 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_decl.c @@ -0,0 +1,7 @@ +int main() { + int a = 0; + + for (int i = 0; i < 3; i = i + 1) + a = a + 1; + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/for_empty.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_empty.c new file mode 100644 index 0000000..b55ae31 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_empty.c @@ -0,0 +1,10 @@ +int main() { + int a = 0; + for (; ; ) { + a = a + 1; + if (a > 3) + break; + } + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/for_nested_scope.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_nested_scope.c new file mode 100644 index 0000000..2b961da --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_nested_scope.c @@ -0,0 +1,14 @@ +int main() { + int i = 0; + int j = 0; + + for (int i = 100; i > 0; i = i - 1) { + int i = 0; + int k = j; + int j = k * 2 + i; + } + + int k = 3; + + return j + k; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/for_variable_shadow.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_variable_shadow.c new file mode 100644 index 0000000..89adf1d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/for_variable_shadow.c @@ -0,0 +1,10 @@ +int main() { + int i = 0; + int j = 0; + for (i = 0; i < 10; i = i + 1) { + int k = i; + for (int i = k; i < 10; i = i + 1) + j = j + 1; + } + return j + i; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_break.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_break.c new file mode 100644 index 0000000..da6704c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_break.c @@ -0,0 +1,10 @@ +int main() { + int ans = 0; + for (int i = 0; i < 10; i = i + 1) + for (int j = 0; j < 10; j = j + 1) + if ((i / 2)*2 == i) + break; + else + ans = ans + i; + return ans; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_while.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_while.c new file mode 100644 index 0000000..0ec1105 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/nested_while.c @@ -0,0 +1,12 @@ +int main() { + int a = 1; + + while (a / 3 < 20) { + int b = 1; + while (b < 10) + b = b*2; + a = a + b; + } + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/return_in_while.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/return_in_while.c new file mode 100644 index 0000000..6ead13d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/return_in_while.c @@ -0,0 +1,5 @@ +int main() { + while (1) { + return 2; + } +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/while_multi_statement.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/while_multi_statement.c new file mode 100644 index 0000000..62d4a5d --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/while_multi_statement.c @@ -0,0 +1,11 @@ +int main() { + int a = 0; + int b = 1; + + while (a < 5) { + a = a + 2; + b = b * a; + } + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_8/valid/while_single_statement.c b/src/resources/write_a_c_compiler-tests/stage_8/valid/while_single_statement.c new file mode 100644 index 0000000..b9c382f --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_8/valid/while_single_statement.c @@ -0,0 +1,8 @@ +int main() { + int a = 0; + + while (a < 5) + a = a + 2; + + return a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/bad_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/bad_arg.c new file mode 100644 index 0000000..651ade1 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/bad_arg.c @@ -0,0 +1,7 @@ +int foo(int a){ + return 3 + a; +} + +int main(){ + return foo(); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch.c new file mode 100644 index 0000000..4f10f78 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch.c @@ -0,0 +1,9 @@ +int foo(int a); + +int main() { + return 5; +} + +int foo(int a, int b) { + return 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch_2.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch_2.c new file mode 100644 index 0000000..944655c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch_2.c @@ -0,0 +1,9 @@ +int foo(int a, int b); + +int main() { + return 5; +} + +int foo(int a) { + return 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_function.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_function.c new file mode 100644 index 0000000..8250e6c --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_function.c @@ -0,0 +1,11 @@ +int foo(){ + return 3; +} + +int main() { + return foo(); +} + +int foo(){ + return 4; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_variable.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_variable.c new file mode 100644 index 0000000..b42c1d9 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_variable.c @@ -0,0 +1,7 @@ +int foo(int x) { + int x = 3; +} + +int main() { + foo(1); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/invalid/too_many_args.c b/src/resources/write_a_c_compiler-tests/stage_9/invalid/too_many_args.c new file mode 100644 index 0000000..9b074d4 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/invalid/too_many_args.c @@ -0,0 +1,7 @@ +int foo(int a) { + return a + 1; +} + +int main() { + return foo(1, 2); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/expression_args.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/expression_args.c new file mode 100644 index 0000000..be4f694 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/expression_args.c @@ -0,0 +1,8 @@ +int add(int a, int b) { + return a + b; +} + +int main() { + int sum = add(1 + 2, 4); + return sum + sum; +} diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/fib.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/fib.c new file mode 100644 index 0000000..6b5e982 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/fib.c @@ -0,0 +1,12 @@ +int fib(int n) { + if (n == 0 || n == 1) { + return n; + } else { + return fib(n - 1) + fib(n - 2); + } +} + +int main() { + int n = 5; + return fib(n); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl.c new file mode 100644 index 0000000..795ebe8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl.c @@ -0,0 +1,9 @@ +int foo(); + +int main() { + return foo(); +} + +int foo() { + return 3; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_args.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_args.c new file mode 100644 index 0000000..aea4e2b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_args.c @@ -0,0 +1,9 @@ +int foo(int a); + +int main(){ + return foo(3); +} + +int foo(int a){ + return a + 1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_multi_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_multi_arg.c new file mode 100644 index 0000000..4694fba --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_multi_arg.c @@ -0,0 +1,9 @@ +int foo(int a, int b); + +int main() { + return foo(1, 2); +} + +int foo(int x, int y){ + return x - y; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/fun_in_expr.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/fun_in_expr.c new file mode 100644 index 0000000..35e71e8 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/fun_in_expr.c @@ -0,0 +1,9 @@ +int sum(int a, int b) { + return a + b; +} + +int main() { + int a = sum(1, 2) - (sum(1, 2) / 2) * 2; + int b = 2*sum(3, 4) + sum(1, 2); + return b - a; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/hello_world.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/hello_world.c new file mode 100644 index 0000000..d829028 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/hello_world.c @@ -0,0 +1,18 @@ +int putchar(int c); + +int main() { + putchar(72); + putchar(101); + putchar(108); + putchar(108); + putchar(111); + putchar(44); + putchar(32); + putchar(87); + putchar(111); + putchar(114); + putchar(108); + putchar(100); + putchar(33); + putchar(10); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/later_decl.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/later_decl.c new file mode 100644 index 0000000..10d463b --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/later_decl.c @@ -0,0 +1,9 @@ +int foo(int a) { + return a + 1; +} + +int main() { + return foo(4); +} + +int foo(int a); \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/multi_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/multi_arg.c new file mode 100644 index 0000000..db04cc2 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/multi_arg.c @@ -0,0 +1,7 @@ +int sub_3(int x, int y, int z) { + return x - y - z; +} + +int main() { + return sub_3(10, 4, 2); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/mutual_recursion.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/mutual_recursion.c new file mode 100644 index 0000000..71de6bd --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/mutual_recursion.c @@ -0,0 +1,22 @@ +int foo(int a); +int bar(int b); + +int main() { + return foo(5); +} + +int foo(int a) { + if (a <= 0) { + return a; + } + + return a + bar(a - 1); +} + +int bar(int b) { + if (b <= 0) { + return b; + } + + return b + bar(b / 2); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/no_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/no_arg.c new file mode 100644 index 0000000..e5dd720 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/no_arg.c @@ -0,0 +1,7 @@ +int three(){ + return 3; +} + +int main() { + return three(); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/precedence.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/precedence.c new file mode 100644 index 0000000..3f9b432 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/precedence.c @@ -0,0 +1,7 @@ +int three() { + return 3; +} + +int main() { + return !three(); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/rename_function_param.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/rename_function_param.c new file mode 100644 index 0000000..2115d9e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/rename_function_param.c @@ -0,0 +1,9 @@ +int foo(int b); + +int main(){ + return foo(3); +} + +int foo(int a){ + return a + 1; +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/single_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/single_arg.c new file mode 100644 index 0000000..3fdfcfb --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/single_arg.c @@ -0,0 +1,7 @@ +int twice(int x){ + return 2 * x; +} + +int main() { + return twice(3); +} \ No newline at end of file diff --git a/src/resources/write_a_c_compiler-tests/stage_9/valid/variable_as_arg.c b/src/resources/write_a_c_compiler-tests/stage_9/valid/variable_as_arg.c new file mode 100644 index 0000000..f4bc83e --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/stage_9/valid/variable_as_arg.c @@ -0,0 +1,8 @@ +int foo(int x) { + return x + 1; +} + +int main() { + int a = 1; + return foo(a); +} diff --git a/src/resources/write_a_c_compiler-tests/test.c b/src/resources/write_a_c_compiler-tests/test.c new file mode 100644 index 0000000..dbff730 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/test.c @@ -0,0 +1 @@ +int main() { return 42; } diff --git a/src/resources/write_a_c_compiler-tests/test_compiler.sh b/src/resources/write_a_c_compiler-tests/test_compiler.sh new file mode 100755 index 0000000..ccc485a --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/test_compiler.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +padding_dots=$(printf '%0.1s' "."{1..60}) +padlength=50 +cmp=$1 +success_total=0 +failure_total=0 + +print_test_name () { + test_name=$1 + printf '%s' "$test_name" + printf '%*.*s' 0 $((padlength - ${#test_name})) "$padding_dots" +} + +test_success () { + echo "OK" + ((success++)) +} + +test_failure () { + echo "FAIL" + ((fail++)) +} + +test_not_implemented () { + echo "NOT IMPLEMENTED" +} + +run_our_program () { + actual_out=`./$1 2>/dev/null` + actual_exit_code=$? + rm $1 2>/dev/null +} + +run_correct_program () { + expected_out=`./a.out` + expected_exit_code=$? + rm a.out +} + +compare_program_results () { + # make sure exit code is correct + if [ "$expected_exit_code" -ne "$actual_exit_code" ] || [ "$expected_out" != "$actual_out" ] + then + test_failure + else + test_success + fi +} + +test_stage () { + success=0 + fail=0 + echo "====================================================" + echo "STAGE $1" + echo "===================Valid Programs===================" + for prog in `find . -type f -name "*.c" -path "./stage_$1/valid/*" -not -path "*/valid_multifile/*" 2>/dev/null`; do + + gcc -w $prog + run_correct_program + + base="${prog%.*}" #name of executable (filename w/out extension) + test_name="${base##*valid/}" + + print_test_name $test_name + $cmp $prog 2>/dev/null + status=$? + + if [[ $test_name == "skip_on_failure"* ]]; then + # this may depend on features we haven't implemented yet + # if compilation succeeds, make sure it gives the right result + # otherwise don't count it as success or failure + if [[ -f $base ]] && [[ $status -eq 0 ]]; then + # it succeeded, so run it and make sure it gives the right result + run_our_program $base + compare_program_results + else + test_not_implemented + fi + else + run_our_program $base + compare_program_results + fi + done + # programs with multiple source files + for dir in `ls -d stage_$1/valid_multifile/* 2>/dev/null` ; do + gcc -w $dir/* + + run_correct_program + + base="${dir%.*}" #name of executable (directory w/out extension) + test_name="${base##*valid_multifile/}" + + # need to explicitly specify output name + $cmp -o "$test_name" $dir/* >/dev/null + + print_test_name $test_name + + # check output/exit codes + run_our_program $test_name + compare_program_results + + done + echo "===================Invalid Programs=================" + for prog in `ls stage_$1/invalid/{,**/}*.c 2>/dev/null`; do + + base="${prog%.*}" #name of executable (filename w/out extension) + test_name="${base##*invalid/}" + + $cmp $prog >/dev/null 2>&1 + status=$? #failed, as we expect, if exit code != 0 + print_test_name $test_name + + # make sure neither executable nor assembly was produced + # and exit code is non-zero + if [[ -f $base || -f $base".s" ]] + then + test_failure + rm $base 2>/dev/null + rm $base".s" 2>/dev/null + else + test_success + fi + done + echo "===================Stage $1 Summary=================" + printf "%d successes, %d failures\n" $success $fail + ((success_total=success_total+success)) + ((failure_total=failure_total + fail)) +} + +total_summary () { + echo "===================TOTAL SUMMARY====================" + printf "%d successes, %d failures\n" $success_total $failure_total +} + +if [ "$1" == "" ]; then + echo "USAGE: ./test_compiler.sh /path/to/compiler [stages(optional)]" + echo "EXAMPLE(test specific stages): ./test_compiler.sh ./mycompiler 1 2 4" + echo "EXAMPLE(test all): ./test_compiler.sh ./mycompiler" + exit 1 +fi + +if test 1 -lt $#; then + testcases=("$@") # [1..-1] is testcases + for i in `seq 2 $#`; do + test_stage ${testcases[$i-1]} + done + total_summary + exit 0 +fi + +num_stages=10 + +for i in `seq 1 $num_stages`; do + test_stage $i +done + +total_summary diff --git a/src/resources/write_a_c_compiler-tests/test_compiler_kotlin.sh b/src/resources/write_a_c_compiler-tests/test_compiler_kotlin.sh new file mode 100755 index 0000000..f860827 --- /dev/null +++ b/src/resources/write_a_c_compiler-tests/test_compiler_kotlin.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +padding_dots=$(printf '%0.1s' "."{1..60}) +padlength=50 +compiler_jar=$1 +success_total=0 +failure_total=0 + +print_test_name () { + test_name=$1 + printf '%s' "$test_name" + printf '%*.*s' 0 $((padlength - ${#test_name})) "$padding_dots" +} + +test_success () { + echo "OK" + ((success++)) +} + +test_failure () { + echo "FAIL" + ((fail++)) +} + +test_not_implemented () { + echo "NOT IMPLEMENTED" +} + +run_our_compiler () { + # Run the Kotlin compiler and capture exit code + java -jar "$compiler_jar" "$1" >/dev/null 2>&1 + actual_exit_code=$? +} + +run_correct_program () { + # Compile with gcc to check if it's a valid C program + gcc -w "$1" >/dev/null 2>&1 + expected_exit_code=$? + if [ $expected_exit_code -eq 0 ]; then + rm a.out 2>/dev/null + fi +} + +compare_program_results () { + # For valid programs, both should succeed (exit code 0) + # For invalid programs, both should fail (exit code != 0) + if [ "$expected_exit_code" -eq 0 ] && [ "$actual_exit_code" -eq 0 ]; then + test_success + elif [ "$expected_exit_code" -ne 0 ] && [ "$actual_exit_code" -ne 0 ]; then + test_success + else + test_failure + fi +} + +test_stage () { + success=0 + fail=0 + echo "====================================================" + echo "STAGE $1" + echo "===================Valid Programs===================" + for prog in `find . -type f -name "*.c" -path "./stage_$1/valid/*" -not -path "*/valid_multifile/*" 2>/dev/null`; do + + run_correct_program "$prog" + + base="${prog%.*}" #name of executable (filename w/out extension) + test_name="${base##*valid/}" + + print_test_name "$test_name" + + if [[ $test_name == "skip_on_failure"* ]]; then + # this may depend on features we haven't implemented yet + # if compilation succeeds, make sure it gives the right result + # otherwise don't count it as success or failure + run_our_compiler "$prog" + if [ "$actual_exit_code" -eq 0 ]; then + # it succeeded, so check if it gives the right result + compare_program_results + else + test_not_implemented + fi + else + run_our_compiler "$prog" + compare_program_results + fi + done + # programs with multiple source files + for dir in `ls -d stage_$1/valid_multifile/* 2>/dev/null` ; do + gcc -w $dir/* >/dev/null 2>&1 + + if [ $? -eq 0 ]; then + expected_out=`./a.out 2>/dev/null` + expected_exit_code=$? + rm a.out 2>/dev/null + else + expected_exit_code=1 + expected_out="" + fi + + base="${dir%.*}" #name of executable (directory w/out extension) + test_name="${base##*valid_multifile/}" + + print_test_name "$test_name" + + # For multifile programs, we'll just test if our compiler can handle the first file + # since the Kotlin compiler doesn't support multifile compilation in the same way + first_file=$(ls $dir/*.c | head -1) + run_our_compiler "$first_file" + compare_program_results + + done + echo "===================Invalid Programs=================" + for prog in `ls stage_$1/invalid/{,**/}*.c 2>/dev/null`; do + + base="${prog%.*}" #name of executable (filename w/out extension) + test_name="${base##*invalid/}" + + print_test_name "$test_name" + + run_our_compiler "$prog" + + # For invalid programs, we expect compilation to fail (exit code != 0) + if [ "$actual_exit_code" -ne 0 ]; then + test_success + else + test_failure + fi + done + echo "===================Stage $1 Summary=================" + printf "%d successes, %d failures\n" $success $fail + ((success_total=success_total+success)) + ((failure_total=failure_total + fail)) +} + +total_summary () { + echo "===================TOTAL SUMMARY====================" + printf "%d successes, %d failures\n" $success_total $failure_total +} + +if [ "$1" == "" ]; then + echo "USAGE: ./test_compiler_kotlin.sh /path/to/compiler.jar [stages(optional)]" + echo "EXAMPLE(test specific stages): ./test_compiler_kotlin.sh ./compiler.jar 1 2 4" + echo "EXAMPLE(test all): ./test_compiler_kotlin.sh ./compiler.jar" + exit 1 +fi + +if test 1 -lt $#; then + testcases=("$@") # [1..-1] is testcases + for i in `seq 2 $#`; do + test_stage ${testcases[$i-1]} + done + total_summary + exit 0 +fi + +num_stages=10 + +for i in `seq 1 $num_stages`; do + test_stage $i +done + +total_summary