From 210d11e47f62774e61a93416cc278debcee45b44 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Wed, 17 Sep 2025 19:05:31 +0200 Subject: [PATCH 01/26] Precompute all assembly results --- .../kotlin/optimizations/ControlFlowGraph.kt | 48 +- .../optimizations/DeadStoreElimination.kt | 188 +++---- .../kotlin/optimizations/Optimization.kt | 37 +- .../optimizations/ControlFlowGraphTest.kt | 309 +++++----- .../optimizations/DeadStoreEliminationTest.kt | 529 ++++++++---------- .../kotlin/optimizations/OptimizationTest.kt | 425 -------------- 6 files changed, 520 insertions(+), 1016 deletions(-) delete mode 100644 src/jsTest/kotlin/optimizations/OptimizationTest.kt diff --git a/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt b/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt index 9a05f74..b6634e5 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,7 +34,10 @@ 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, @@ -42,7 +45,10 @@ data class ControlFlowGraph( val blocks: List = emptyList(), val edges: List = emptyList() ) { - 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,16 +91,22 @@ 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 + ): List { 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 @@ -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/DeadStoreElimination.kt b/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt index 2ae30cc..1ad9105 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 @@ -16,130 +15,113 @@ class DeadStoreElimination : Optimization() { override val optimizationType: OptimizationType = OptimizationType.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..b79985e 100644 --- a/src/jsMain/kotlin/optimizations/Optimization.kt +++ b/src/jsMain/kotlin/optimizations/Optimization.kt @@ -1,7 +1,5 @@ package optimizations -import tacky.TackyProgram - enum class OptimizationType { CONSTANT_FOLDING, DEAD_STORE_ELIMINATION, @@ -11,32 +9,23 @@ enum class OptimizationType { 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.CONSTANT_FOLDING to ConstantFolding(), + OptimizationType.DEAD_STORE_ELIMINATION to DeadStoreElimination(), + OptimizationType.UNREACHABLE_CODE_ELIMINATION to UnreachableCodeElimination(), + OptimizationType.COPY_PROPAGATION to CopyPropagation() + ) + + fun applyOptimizations( + cfg: ControlFlowGraph, + enabledOptimizations: Set + ): ControlFlowGraph { var currentCfg = cfg while (true) { 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()) - } -} From 57291b30366d76c6aeda4c7bc2c37dfc9dd30ee4 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Wed, 17 Sep 2025 19:05:39 +0200 Subject: [PATCH 02/26] Fix export issues --- src/jsMain/kotlin/CompilerWorkflow.kt | 34 +++- src/jsMain/kotlin/export/CompilationOutput.kt | 1 + src/jsMain/kotlin/export/CompilerExport.kt | 189 ++++++++++++------ 3 files changed, 154 insertions(+), 70 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index 2d6b66a..1bb7a60 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -7,8 +7,10 @@ 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 @@ -41,6 +43,8 @@ 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) @@ -75,17 +79,31 @@ sealed class CompilerWorkflow { return tacky } - fun take(tacky: TackyProgram, optimizations: Set): TackyProgram { + fun take( + tackyProgram: TackyProgram, + optimizations: Set + ): TackyProgram { + val tacky = tackyProgram.copy() 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) { + if (optimization == OptimizationType.CONSTANT_FOLDING) { + cfg = constantFolding.apply(cfg) + } else if (optimization == OptimizationType.DEAD_STORE_ELIMINATION) { + cfg = deadStoreElimination.apply(cfg) + } else if (optimization == OptimizationType.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/export/CompilationOutput.kt b/src/jsMain/kotlin/export/CompilationOutput.kt index a796299..df5160a 100644 --- a/src/jsMain/kotlin/export/CompilationOutput.kt +++ b/src/jsMain/kotlin/export/CompilationOutput.kt @@ -45,6 +45,7 @@ data class TackyOutput( val tacky: String? = null, val tackyPretty: String? = null, val precomputedCFGs: String = "", + val precomputedAssembly: String = "", val optimizations: Array = arrayOf("CONSTANT_FOLDING", "DEAD_STORE_ELIMINATION", "COPY_PROPAGATION", "UNREACHABLE_CODE_ELIMINATION"), val functionNames: Array = emptyArray(), override val errors: Array, diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index dfd287d..8b506e3 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -48,10 +48,16 @@ data class CFGEntry( val cfg: String ) +@Serializable +data class AssemblyEntry( + val functionName: String, + 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 @@ -98,14 +104,26 @@ class CompilerExport { tackyPretty = tackyProgram.toPseudoCode(), functionNames = tackyProgram.functions.map { it.name }.toTypedArray(), precomputedCFGs = precomputeAllCFGs(tackyProgram), + precomputedAssembly = precomputeAllAssembly(tackyProgram), errors = emptyArray(), tacky = Json.encodeToString(tackyProgram), sourceLocation = sourceLocationInfo ) ) - val asm = CompilerWorkflow.take(tacky as TackyProgram) + val optimizedTacky = + CompilerWorkflow.take( + tacky, + optimizations = + setOf( + OptimizationType.CONSTANT_FOLDING, + OptimizationType.DEAD_STORE_ELIMINATION, + OptimizationType.COPY_PROPAGATION, + OptimizationType.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(), @@ -164,27 +182,64 @@ 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 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)) + } } } - } 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 allOptSets = generateOptimizationCombinations() + val assemblies = mutableListOf() + + println("Precomputing assembly for ${program.functions.size} functions with ${allOptSets.size} optimization combinations") + + for (optSet in allOptSets) { + try { + val optimizedTacky = CompilerWorkflow.take( + program, + optimizations = optSet.mapNotNull(optTypeMap::get).toSet() + ) + val asm = CompilerWorkflow.take(optimizedTacky) + val finalAssemblyString = CodeEmitter().emit(asm as AsmProgram) + + println("Generated assembly with optimizations $optSet: ${finalAssemblyString.length} chars") + + // Create entries for each function with the full assembly + for (fn in program.functions.filter { it.body.isNotEmpty() }) { + assemblies.add(AssemblyEntry(fn.name, optSet.sorted(), finalAssemblyString)) + } + } catch (e: Exception) { + println("Error generating assembly with optimizations $optSet: ${e.message}") + // Create empty entries for each function + for (fn in program.functions.filter { it.body.isNotEmpty() }) { + assemblies.add(AssemblyEntry(fn.name, optSet.sorted(), "")) + } + } + } + + val result = Json.encodeToString(assemblies) + println("Precomputed assembly result: ${result.length} chars, ${assemblies.size} entries") + return result + } + + 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 generateOptimizationCombinations(): List> { val opts = optTypeMap.keys.toList() @@ -204,51 +259,60 @@ class CompilerExport { 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" + } + 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" + } + CFGEdge(fromId, toId) } - CFGEdge(fromId, toId) - } return Json.encodeToString(CFGExport(cfg.functionName ?: "unknown", nodes, edges)) } - 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" + 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" } - else -> "unknown" - } - 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 +332,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) + ) ) - ) ) ) } From b0e9ab37dc9da71131fb333470b78ca1a2b8ef88 Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Wed, 17 Sep 2025 22:16:59 +0200 Subject: [PATCH 03/26] fix highlight for Tacky --- src/jsMain/kotlin/CompilerWorkflow.kt | 2 +- src/jsMain/kotlin/tacky/Instructions.kt | 19 +++++++++++++++++++ src/jsMain/kotlin/tacky/Tacky.kt | 21 ++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index 1bb7a60..a1ca911 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -83,7 +83,7 @@ sealed class CompilerWorkflow { tackyProgram: TackyProgram, optimizations: Set ): TackyProgram { - val tacky = tackyProgram.copy() + val tacky = tackyProgram.deepCopy() tacky.functions.forEach { while (true) { var cfg = ControlFlowGraph().construct(it.name, it.body) diff --git a/src/jsMain/kotlin/tacky/Instructions.kt b/src/jsMain/kotlin/tacky/Instructions.kt index 69da434..0429cde 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( @@ -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 + ) + } } From 9c4c31ffbbdcf145dbd560156f5af931ca409258 Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Wed, 17 Sep 2025 22:42:45 +0200 Subject: [PATCH 04/26] bug fixes for highlighting --- src/jsMain/kotlin/CompilerWorkflow.kt | 6 +++--- src/jsMain/kotlin/export/CompilerExport.kt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index a1ca911..b9eb21d 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -1,6 +1,6 @@ package compiler -import assembly.AsmConstruct +import assembly.AsmProgram import assembly.InstructionFixer import assembly.PseudoEliminator import lexer.Lexer @@ -108,10 +108,10 @@ sealed class CompilerWorkflow { return tacky } - fun take(tacky: TackyProgram): AsmConstruct { + fun take(tacky: TackyProgram): AsmProgram { val asm = tackyToAsmConverter.convert(tacky) val asmWithStackSizes = pseudoEliminator.eliminate(asm) - val finalAsmProgram = instructionFixer.fix(asmWithStackSizes) + val finalAsmProgram = instructionFixer.fix(asmWithStackSizes) as AsmProgram return finalAsmProgram } } diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 8b506e3..afd9aed 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -122,8 +122,9 @@ class CompilerExport { ) ) val asm = CompilerWorkflow.take(optimizedTacky) + val rawAsm = CompilerWorkflow.take(tacky) val finalAssemblyString = codeEmitter.emit(asm as AsmProgram) - val rawAssembly = codeEmitter.emitRaw(asm) + val rawAssembly = codeEmitter.emitRaw(rawAsm) outputs.add( AssemblyOutput( errors = emptyArray(), From 8f41b48c5f331797d9006018d182c732b2faa6fd Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Wed, 17 Sep 2025 23:26:09 +0200 Subject: [PATCH 05/26] generate the assembly based on optimized tacky --- src/jsMain/kotlin/export/CompilerExport.kt | 3 +- .../kotlin/optimizations/ConstantFolding.kt | 4 +-- .../kotlin/optimizations/CopyPropagation.kt | 34 +++++++++++-------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index afd9aed..8b506e3 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -122,9 +122,8 @@ class CompilerExport { ) ) val asm = CompilerWorkflow.take(optimizedTacky) - val rawAsm = CompilerWorkflow.take(tacky) val finalAssemblyString = codeEmitter.emit(asm as AsmProgram) - val rawAssembly = codeEmitter.emitRaw(rawAsm) + val rawAssembly = codeEmitter.emitRaw(asm) outputs.add( AssemblyOutput( errors = emptyArray(), diff --git a/src/jsMain/kotlin/optimizations/ConstantFolding.kt b/src/jsMain/kotlin/optimizations/ConstantFolding.kt index 8ba7237..f7ffc7f 100644 --- a/src/jsMain/kotlin/optimizations/ConstantFolding.kt +++ b/src/jsMain/kotlin/optimizations/ConstantFolding.kt @@ -39,7 +39,7 @@ class ConstantFolding : Optimization() { TackyUnaryOP.NEGATE -> -src.value TackyUnaryOP.NOT -> if (src.value == 0) 1 else 0 } - return TackyCopy(TackyConstant(result), inst.dest) + return TackyCopy(TackyConstant(result), inst.dest, inst.sourceId) } private fun foldBinary(inst: TackyBinary): TackyInstruction { @@ -60,7 +60,7 @@ class ConstantFolding : Optimization() { 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? { diff --git a/src/jsMain/kotlin/optimizations/CopyPropagation.kt b/src/jsMain/kotlin/optimizations/CopyPropagation.kt index 8fc6e79..b7b58c2 100644 --- a/src/jsMain/kotlin/optimizations/CopyPropagation.kt +++ b/src/jsMain/kotlin/optimizations/CopyPropagation.kt @@ -40,7 +40,7 @@ class CopyPropagation : Optimization() { } copyMap[instr.dest.name] = newSrc - newInstructions.add(TackyCopy(newSrc, instr.dest)) + newInstructions.add(TackyCopy(newSrc, instr.dest, instr.sourceId)) } } is TackyRet -> { @@ -49,7 +49,7 @@ class CopyPropagation : Optimization() { } else { instr.value } - newInstructions.add(TackyRet(newValue)) + newInstructions.add(TackyRet(newValue, instr.sourceId)) } is TackyUnary -> { val newSrc = if (instr.src is TackyVar && copyMap.containsKey(instr.src.name)) { @@ -58,7 +58,7 @@ class CopyPropagation : Optimization() { instr.src } copyMap.remove(instr.dest.name) - newInstructions.add(TackyUnary(instr.operator, newSrc, instr.dest)) + newInstructions.add(TackyUnary(instr.operator, newSrc, instr.dest, instr.sourceId)) } is TackyBinary -> { val newSrc1 = if (instr.src1 is TackyVar && copyMap.containsKey(instr.src1.name)) { @@ -72,7 +72,7 @@ class CopyPropagation : Optimization() { instr.src2 } copyMap.remove(instr.dest.name) - newInstructions.add(TackyBinary(instr.operator, newSrc1, newSrc2, instr.dest)) + newInstructions.add(TackyBinary(instr.operator, newSrc1, newSrc2, instr.dest, instr.sourceId)) } is TackyFunCall -> { val newArgs = instr.args.map { arg -> @@ -83,7 +83,7 @@ class CopyPropagation : Optimization() { } } copyMap.remove(instr.dest.name) - newInstructions.add(TackyFunCall(instr.funName, newArgs, instr.dest)) + newInstructions.add(TackyFunCall(instr.funName, newArgs, instr.dest, instr.sourceId)) } is JumpIfZero -> { val newCondition = if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { @@ -91,7 +91,7 @@ class CopyPropagation : Optimization() { } else { instr.condition } - newInstructions.add(JumpIfZero(newCondition, instr.target)) + newInstructions.add(JumpIfZero(newCondition, instr.target, instr.sourceId)) } is JumpIfNotZero -> { val newCondition = if (instr.condition is TackyVar && copyMap.containsKey(instr.condition.name)) { @@ -99,7 +99,7 @@ class CopyPropagation : Optimization() { } else { instr.condition } - newInstructions.add(JumpIfNotZero(newCondition, instr.target)) + newInstructions.add(JumpIfNotZero(newCondition, instr.target, instr.sourceId)) } else -> { newInstructions.add(instr) @@ -198,34 +198,40 @@ class CopyPropagation : Optimization() { val substitutionMap = reaching.associate { it.dest.name to it.src } return when (instruction) { - is TackyRet -> TackyRet(value = substitute(instruction.value, substitutionMap)) + is TackyRet -> TackyRet(value = substitute(instruction.value, substitutionMap), instruction.sourceId) is TackyUnary -> TackyUnary( operator = instruction.operator, src = substitute(instruction.src, substitutionMap), - dest = instruction.dest + dest = instruction.dest, + instruction.sourceId ) is TackyBinary -> TackyBinary( operator = instruction.operator, src1 = substitute(instruction.src1, substitutionMap), src2 = substitute(instruction.src2, substitutionMap), - dest = instruction.dest + dest = instruction.dest, + instruction.sourceId ) is TackyCopy -> TackyCopy( src = substitute(instruction.src, substitutionMap), - dest = instruction.dest + dest = instruction.dest, + instruction.sourceId ) is TackyFunCall -> TackyFunCall( funName = instruction.funName, args = instruction.args.map { substitute(it, substitutionMap) }, - dest = instruction.dest + dest = instruction.dest, + instruction.sourceId ) is JumpIfZero -> JumpIfZero( condition = substitute(instruction.condition, substitutionMap), - target = instruction.target + target = instruction.target, + instruction.sourceId ) is JumpIfNotZero -> JumpIfNotZero( condition = substitute(instruction.condition, substitutionMap), - target = instruction.target + target = instruction.target, + instruction.sourceId ) else -> instruction } From 94e5db58745d309b8d8093310761db222491f1f6 Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Thu, 18 Sep 2025 12:54:27 +0200 Subject: [PATCH 06/26] debugging optimized assembly --- src/jsMain/kotlin/export/CompilerExport.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 8b506e3..81d39ff 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -50,7 +50,6 @@ data class CFGEntry( @Serializable data class AssemblyEntry( - val functionName: String, val optimizations: List, val asmCode: String ) @@ -211,19 +210,19 @@ class CompilerExport { optimizations = optSet.mapNotNull(optTypeMap::get).toSet() ) val asm = CompilerWorkflow.take(optimizedTacky) - val finalAssemblyString = CodeEmitter().emit(asm as AsmProgram) + val finalAssemblyString = CodeEmitter().emitRaw(asm as AsmProgram) println("Generated assembly with optimizations $optSet: ${finalAssemblyString.length} chars") // Create entries for each function with the full assembly for (fn in program.functions.filter { it.body.isNotEmpty() }) { - assemblies.add(AssemblyEntry(fn.name, optSet.sorted(), finalAssemblyString)) + assemblies.add(AssemblyEntry(optSet.toList().sorted(), finalAssemblyString)) } } catch (e: Exception) { println("Error generating assembly with optimizations $optSet: ${e.message}") // Create empty entries for each function for (fn in program.functions.filter { it.body.isNotEmpty() }) { - assemblies.add(AssemblyEntry(fn.name, optSet.sorted(), "")) + assemblies.add(AssemblyEntry(optSet.toList().sorted(), "")) } } } From 519744813897f90153082411ecbfb7b10b0a5e1a Mon Sep 17 00:00:00 2001 From: sidrasali Date: Thu, 18 Sep 2025 23:03:04 +0200 Subject: [PATCH 07/26] Fix optimization order --- src/jsMain/kotlin/CompilerWorkflow.kt | 22 +++++--- src/jsMain/kotlin/export/CompilerExport.kt | 52 +++++++------------ .../kotlin/optimizations/Optimization.kt | 2 +- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index b9eb21d..c02d037 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -1,6 +1,6 @@ package compiler -import assembly.AsmProgram +import assembly.AsmConstruct import assembly.InstructionFixer import assembly.PseudoEliminator import lexer.Lexer @@ -50,7 +50,17 @@ sealed class CompilerWorkflow { 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.CONSTANT_FOLDING, + OptimizationType.DEAD_STORE_ELIMINATION, + OptimizationType.UNREACHABLE_CODE_ELIMINATION, + OptimizationType.COPY_PROPAGATION + ) + ) + val asm = take(tacky) return mapOf( CompilerStage.LEXER to tokens, @@ -81,13 +91,13 @@ sealed class CompilerWorkflow { fun take( tackyProgram: TackyProgram, - optimizations: Set + optimizations: List ): TackyProgram { val tacky = tackyProgram.deepCopy() tacky.functions.forEach { while (true) { var cfg = ControlFlowGraph().construct(it.name, it.body) - for (optimization in optimizations) { + for (optimization in optimizations.sorted()) { if (optimization == OptimizationType.CONSTANT_FOLDING) { cfg = constantFolding.apply(cfg) } else if (optimization == OptimizationType.DEAD_STORE_ELIMINATION) { @@ -108,10 +118,10 @@ sealed class CompilerWorkflow { return tacky } - fun take(tacky: TackyProgram): AsmProgram { + fun take(tacky: TackyProgram): AsmConstruct { val asm = tackyToAsmConverter.convert(tacky) val asmWithStackSizes = pseudoEliminator.eliminate(asm) - val finalAsmProgram = instructionFixer.fix(asmWithStackSizes) as AsmProgram + val finalAsmProgram = instructionFixer.fix(asmWithStackSizes) return finalAsmProgram } } diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 81d39ff..2bc40d8 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -113,7 +113,7 @@ class CompilerExport { CompilerWorkflow.take( tacky, optimizations = - setOf( + listOf( OptimizationType.CONSTANT_FOLDING, OptimizationType.DEAD_STORE_ELIMINATION, OptimizationType.COPY_PROPAGATION, @@ -180,17 +180,17 @@ class CompilerExport { } private fun precomputeAllCFGs(program: TackyProgram): String { - val allOptSets = generateOptimizationCombinations() + val allOptLists = generateOptimizationCombinations() val cfgs = program.functions.filter { it.body.isNotEmpty() }.flatMap { fn -> - allOptSets.map { optSet -> + allOptLists.map { optList -> try { val cfg = ControlFlowGraph().construct(fn.name, fn.body) - val types = optSet.mapNotNull(optTypeMap::get).toSet() + val types = optList.mapNotNull(optTypeMap::get) val optimized = OptimizationManager.applyOptimizations(cfg, types) - CFGEntry(fn.name, optSet.sorted(), exportControlFlowGraph(optimized)) + CFGEntry(fn.name, optList, exportControlFlowGraph(optimized)) } catch (_: Exception) { - CFGEntry(fn.name, optSet.sorted(), createEmptyCFGJson(fn.name)) + CFGEntry(fn.name, optList.sorted(), createEmptyCFGJson(fn.name)) } } } @@ -198,37 +198,23 @@ class CompilerExport { } private fun precomputeAllAssembly(program: TackyProgram): String { - val allOptSets = generateOptimizationCombinations() + val allOptLists = generateOptimizationCombinations() val assemblies = mutableListOf() - println("Precomputing assembly for ${program.functions.size} functions with ${allOptSets.size} optimization combinations") - - for (optSet in allOptSets) { - try { - val optimizedTacky = CompilerWorkflow.take( + for (optList in allOptLists) { + val optimizedTacky = + CompilerWorkflow.take( program, - optimizations = optSet.mapNotNull(optTypeMap::get).toSet() + optimizations = optList.mapNotNull(optTypeMap::get) ) - val asm = CompilerWorkflow.take(optimizedTacky) - val finalAssemblyString = CodeEmitter().emitRaw(asm as AsmProgram) - - println("Generated assembly with optimizations $optSet: ${finalAssemblyString.length} chars") + val asm = CompilerWorkflow.take(optimizedTacky) + val finalAssemblyString = CodeEmitter().emit(asm as AsmProgram) - // Create entries for each function with the full assembly - for (fn in program.functions.filter { it.body.isNotEmpty() }) { - assemblies.add(AssemblyEntry(optSet.toList().sorted(), finalAssemblyString)) - } - } catch (e: Exception) { - println("Error generating assembly with optimizations $optSet: ${e.message}") - // Create empty entries for each function - for (fn in program.functions.filter { it.body.isNotEmpty() }) { - assemblies.add(AssemblyEntry(optSet.toList().sorted(), "")) - } - } + // Create one entry per optimization set with the full program assembly + assemblies.add(AssemblyEntry(optList, finalAssemblyString)) } val result = Json.encodeToString(assemblies) - println("Precomputed assembly result: ${result.length} chars, ${assemblies.size} entries") return result } @@ -240,10 +226,10 @@ class CompilerExport { "UNREACHABLE_CODE_ELIMINATION" to OptimizationType.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 } } } @@ -277,7 +263,6 @@ class CompilerExport { 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) { @@ -288,7 +273,6 @@ class CompilerExport { val index = cfg.blocks.indexOfFirst { it.id == edge.to.id } if (index >= 0) "block_$index" else "unknown_block" } - else -> "unknown_block" } CFGEdge(fromId, toId) } diff --git a/src/jsMain/kotlin/optimizations/Optimization.kt b/src/jsMain/kotlin/optimizations/Optimization.kt index b79985e..1e55dfc 100644 --- a/src/jsMain/kotlin/optimizations/Optimization.kt +++ b/src/jsMain/kotlin/optimizations/Optimization.kt @@ -24,7 +24,7 @@ object OptimizationManager { fun applyOptimizations( cfg: ControlFlowGraph, - enabledOptimizations: Set + enabledOptimizations: List ): ControlFlowGraph { var currentCfg = cfg From ef662de41f5ed264f3c94c127d3fefca68e1a5ee Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sun, 21 Sep 2025 23:59:36 +0200 Subject: [PATCH 08/26] Export instruction count for CFG results --- src/jsMain/kotlin/CompilerWorkflow.kt | 4 +++- src/jsMain/kotlin/export/CompilerExport.kt | 24 ++++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index c02d037..8012090 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -27,6 +27,7 @@ enum class CompilerStage { LEXER, PARSER, TACKY, + OPTIMIZATIONS, ASSEMBLY } @@ -60,12 +61,13 @@ sealed class CompilerWorkflow { OptimizationType.COPY_PROPAGATION ) ) - val asm = take(tacky) + 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 ) } diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 2bc40d8..64c9467 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 @@ -113,12 +114,12 @@ class CompilerExport { CompilerWorkflow.take( tacky, optimizations = - listOf( - OptimizationType.CONSTANT_FOLDING, - OptimizationType.DEAD_STORE_ELIMINATION, - OptimizationType.COPY_PROPAGATION, - OptimizationType.UNREACHABLE_CODE_ELIMINATION - ) + listOf( + OptimizationType.CONSTANT_FOLDING, + OptimizationType.DEAD_STORE_ELIMINATION, + OptimizationType.COPY_PROPAGATION, + OptimizationType.UNREACHABLE_CODE_ELIMINATION + ) ) val asm = CompilerWorkflow.take(optimizedTacky) val finalAssemblyString = codeEmitter.emit(asm as AsmProgram) @@ -152,6 +153,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 -> null // TODO We're currently exporting optimization results inside TackyOutput, we should move it to its own object } } catch (e: Exception) { // Fallback for any unexpected runtime errors @@ -238,7 +240,8 @@ 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 ) ) @@ -277,7 +280,10 @@ class CompilerExport { 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 } + + return Json.encodeToString(CFGExport(cfg.functionName ?: "unknown", nodes, edges, instructionCount)) } private fun Any.toId(cfg: ControlFlowGraph): String = From 93c3ea3728af254a3c0edd903708ff0169340cce Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Sun, 21 Sep 2025 23:59:55 +0200 Subject: [PATCH 09/26] adjust the equal in TackyBinaryOP to == --- src/jsMain/kotlin/tacky/Instructions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jsMain/kotlin/tacky/Instructions.kt b/src/jsMain/kotlin/tacky/Instructions.kt index 0429cde..5d76ba1 100644 --- a/src/jsMain/kotlin/tacky/Instructions.kt +++ b/src/jsMain/kotlin/tacky/Instructions.kt @@ -55,7 +55,7 @@ enum class TackyBinaryOP( GREATER(">"), LESS_EQUAL("<="), GREATER_EQUAL(">="), - EQUAL("="), + EQUAL("=="), NOT_EQUAL("!=") } From 29b6873abe64b5f712317030e613514f8315ed79 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Mon, 22 Sep 2025 00:07:49 +0200 Subject: [PATCH 10/26] Setup test suite from "Writing a C Compiler" book --- .idea/misc.xml | 2 +- build.gradle.kts | 74 +++++++ gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 47 +++-- gradlew.bat | 183 +++++++++--------- src/jsMain/kotlin/parser/Parser.kt | 16 +- .../stage_1/invalid/missing_paren.c | 3 + .../stage_1/invalid/missing_retval.c | 3 + .../stage_1/invalid/no_brace.c | 2 + .../stage_1/invalid/no_semicolon.c | 3 + .../stage_1/invalid/no_space.c | 3 + .../stage_1/invalid/wrong_case.c | 3 + .../stage_1/valid/multi_digit.c | 3 + .../stage_1/valid/newlines.c | 10 + .../stage_1/valid/no_newlines.c | 1 + .../stage_1/valid/return_0.c | 3 + .../stage_1/valid/return_2.c | 3 + .../stage_1/valid/spaces.c | 1 + .../stage_10/invalid/fun_redefined_as_var.c | 9 + .../stage_10/invalid/multiple_global_defs.c | 7 + .../stage_10/invalid/non_constant_init.c | 6 + .../stage_10/invalid/use_before_declaration.c | 5 + .../stage_10/invalid/var_redefined_as_fun.c | 9 + .../stage_10/invalid/variable_used_as_fun.c | 5 + .../stage_10/valid/forward_declaration.c | 7 + .../stage_10/valid/fun_shadowed_by_variable.c | 8 + .../stage_10/valid/global.c | 5 + .../stage_10/valid/global_not_initialized.c | 7 + .../stage_10/valid/global_shadowed.c | 10 + .../stage_10/valid/multiple_global.c | 6 + .../stage_2/invalid/missing_const.c | 3 + .../stage_2/invalid/missing_semicolon.c | 3 + .../stage_2/invalid/nested_missing_const.c | 3 + .../stage_2/invalid/wrong_order.c | 3 + .../stage_2/valid/bitwise.c | 3 + .../stage_2/valid/bitwise_zero.c | 3 + .../stage_2/valid/neg.c | 3 + .../stage_2/valid/nested_ops.c | 3 + .../stage_2/valid/nested_ops_2.c | 3 + .../stage_2/valid/not_five.c | 3 + .../stage_2/valid/not_zero.c | 3 + .../stage_3/invalid/malformed_paren.c | 3 + .../stage_3/invalid/missing_first_op.c | 3 + .../stage_3/invalid/missing_second_op.c | 3 + .../stage_3/invalid/no_semicolon.c | 3 + .../stage_3/valid/add.c | 3 + .../stage_3/valid/associativity.c | 3 + .../stage_3/valid/associativity_2.c | 3 + .../stage_3/valid/div.c | 3 + .../stage_3/valid/div_neg.c | 3 + .../stage_3/valid/mult.c | 3 + .../stage_3/valid/parens.c | 3 + .../stage_3/valid/precedence.c | 3 + .../stage_3/valid/sub.c | 3 + .../stage_3/valid/sub_neg.c | 3 + .../stage_3/valid/unop_add.c | 3 + .../stage_3/valid/unop_parens.c | 3 + .../stage_4/invalid/missing_first_op.c | 3 + .../stage_4/invalid/missing_mid_op.c | 3 + .../stage_4/invalid/missing_second_op.c | 3 + .../stage_4/invalid/missing_semicolon.c | 3 + .../stage_4/valid/and_false.c | 3 + .../stage_4/valid/and_true.c | 3 + .../stage_4/valid/eq_false.c | 3 + .../stage_4/valid/eq_true.c | 3 + .../stage_4/valid/ge_false.c | 3 + .../stage_4/valid/ge_true.c | 3 + .../stage_4/valid/gt_false.c | 3 + .../stage_4/valid/gt_true.c | 3 + .../stage_4/valid/le_false.c | 3 + .../stage_4/valid/le_true.c | 3 + .../stage_4/valid/lt_false.c | 3 + .../stage_4/valid/lt_true.c | 3 + .../stage_4/valid/ne_false.c | 3 + .../stage_4/valid/ne_true.c | 3 + .../stage_4/valid/or_false.c | 3 + .../stage_4/valid/or_true.c | 3 + .../stage_4/valid/precedence.c | 3 + .../stage_4/valid/precedence_2.c | 3 + .../stage_4/valid/precedence_3.c | 3 + .../stage_4/valid/precedence_4.c | 3 + .../skip_on_failure_multi_short_circuit.c | 5 + .../valid/skip_on_failure_short_circuit_and.c | 6 + .../valid/skip_on_failure_short_circuit_or.c | 6 + .../stage_5/invalid/redefine.c | 5 + .../stage_5/invalid/syntax_err_bad_decl.c | 4 + .../stage_5/invalid/syntax_err_bad_decl_2.c | 4 + .../stage_5/invalid/syntax_err_bad_lvalue.c | 5 + .../stage_5/invalid/syntax_err_bad_lvalue_2.c | 5 + .../stage_5/invalid/syntax_err_no_semicolon.c | 5 + .../stage_5/invalid/undeclared_var.c | 3 + .../stage_5/invalid/var_declared_late.c | 5 + .../stage_5/valid/assign.c | 5 + .../stage_5/valid/assign_val.c | 5 + .../stage_5/valid/exp_return_val.c | 6 + .../stage_5/valid/initialize.c | 4 + .../stage_5/valid/missing_return.c | 3 + .../stage_5/valid/multiple_vars.c | 5 + .../stage_5/valid/no_initialize.c | 4 + .../stage_5/valid/refer.c | 4 + .../stage_5/valid/unused_exp.c | 4 + .../invalid/expression/incomplete_ternary.c | 3 + .../invalid/expression/malformed_ternary.c | 3 + .../invalid/expression/malformed_ternary_2.c | 3 + .../invalid/expression/ternary_assign.c | 6 + .../invalid/statement/declare_statement.c | 4 + .../stage_6/invalid/statement/if_assignment.c | 8 + .../invalid/statement/mismatched_nesting.c | 9 + .../stage_6/valid/expression/assign_ternary.c | 5 + .../valid/expression/multiple_ternary.c | 5 + .../stage_6/valid/expression/nested_ternary.c | 7 + .../valid/expression/nested_ternary_2.c | 5 + .../stage_6/valid/expression/rh_assignment.c | 6 + .../stage_6/valid/expression/ternary.c | 4 + .../valid/expression/ternary_short_circuit.c | 6 + .../expression/ternary_short_circuit_2.c | 6 + .../stage_6/valid/statement/else.c | 7 + .../stage_6/valid/statement/if_nested.c | 9 + .../stage_6/valid/statement/if_nested_2.c | 9 + .../stage_6/valid/statement/if_nested_3.c | 10 + .../stage_6/valid/statement/if_nested_4.c | 10 + .../stage_6/valid/statement/if_nested_5.c | 12 ++ .../stage_6/valid/statement/if_not_taken.c | 7 + .../stage_6/valid/statement/if_taken.c | 7 + .../stage_6/valid/statement/multiple_if.c | 16 ++ .../stage_7/invalid/double_define.c | 6 + .../stage_7/invalid/out_of_scope.c | 6 + .../stage_7/invalid/syntax_err_extra_brace.c | 6 + .../invalid/syntax_err_missing_brace.c | 5 + .../stage_7/valid/consecutive_blocks.c | 9 + .../stage_7/valid/consecutive_declarations.c | 12 ++ .../stage_7/valid/declare_after_block.c | 8 + .../stage_7/valid/declare_block.c | 6 + .../stage_7/valid/declare_late.c | 8 + .../stage_7/valid/multi_nesting.c | 10 + .../stage_7/valid/nested_if.c | 15 ++ .../stage_7/valid/nested_scope.c | 9 + .../stage_8/invalid/break_not_in_loop.c | 3 + .../stage_8/invalid/continue_not_in_loop.c | 3 + .../stage_8/invalid/out_of_scope.c | 8 + .../stage_8/invalid/out_of_scope_do_while.c | 6 + .../invalid/syntax_err_do_no_semicolon.c | 5 + .../stage_8/invalid/syntax_err_empty_clause.c | 4 + .../invalid/syntax_err_paren_mismatch.c | 4 + .../syntax_err_statement_in_condition.c | 5 + .../invalid/syntax_err_too_few_for_clauses.c | 5 + .../invalid/syntax_err_too_many_for_clauses.c | 5 + .../stage_8/valid/break.c | 9 + .../stage_8/valid/continue.c | 9 + .../stage_8/valid/continue_empty_post.c | 10 + .../stage_8/valid/do_while.c | 8 + .../stage_8/valid/empty_expression.c | 7 + .../stage_8/valid/for.c | 7 + .../stage_8/valid/for_decl.c | 7 + .../stage_8/valid/for_empty.c | 10 + .../stage_8/valid/for_nested_scope.c | 14 ++ .../stage_8/valid/for_variable_shadow.c | 10 + .../stage_8/valid/nested_break.c | 10 + .../stage_8/valid/nested_while.c | 12 ++ .../stage_8/valid/return_in_while.c | 5 + .../stage_8/valid/while_multi_statement.c | 11 ++ .../stage_8/valid/while_single_statement.c | 8 + .../stage_9/invalid/bad_arg.c | 7 + .../stage_9/invalid/declaration_mismatch.c | 9 + .../stage_9/invalid/declaration_mismatch_2.c | 9 + .../stage_9/invalid/redefine_function.c | 11 ++ .../stage_9/invalid/redefine_variable.c | 7 + .../stage_9/invalid/too_many_args.c | 7 + .../stage_9/valid/expression_args.c | 8 + .../stage_9/valid/fib.c | 12 ++ .../stage_9/valid/forward_decl.c | 9 + .../stage_9/valid/forward_decl_args.c | 9 + .../stage_9/valid/forward_decl_multi_arg.c | 9 + .../stage_9/valid/fun_in_expr.c | 9 + .../stage_9/valid/hello_world.c | 18 ++ .../stage_9/valid/later_decl.c | 9 + .../stage_9/valid/multi_arg.c | 7 + .../stage_9/valid/mutual_recursion.c | 22 +++ .../stage_9/valid/no_arg.c | 7 + .../stage_9/valid/precedence.c | 7 + .../stage_9/valid/rename_function_param.c | 9 + .../stage_9/valid/single_arg.c | 7 + .../stage_9/valid/variable_as_arg.c | 8 + src/resources/write_a_c_compiler-tests/test.c | 1 + .../write_a_c_compiler-tests/test_compiler.sh | 158 +++++++++++++++ .../test_compiler_kotlin.sh | 162 ++++++++++++++++ 187 files changed, 1554 insertions(+), 114 deletions(-) create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_paren.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/missing_retval.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/no_brace.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/no_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/no_space.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/invalid/wrong_case.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/multi_digit.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/newlines.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/no_newlines.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/return_0.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/return_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_1/valid/spaces.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/fun_redefined_as_var.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/multiple_global_defs.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/non_constant_init.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/use_before_declaration.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/var_redefined_as_fun.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/invalid/variable_used_as_fun.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/forward_declaration.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/fun_shadowed_by_variable.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/global.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/global_not_initialized.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/global_shadowed.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_10/valid/multiple_global.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_const.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/invalid/missing_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/invalid/nested_missing_const.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/invalid/wrong_order.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/bitwise_zero.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/neg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/nested_ops_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/not_five.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_2/valid/not_zero.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/invalid/malformed_paren.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_first_op.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/invalid/missing_second_op.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/invalid/no_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/add.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/associativity.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/associativity_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/div.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/div_neg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/mult.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/parens.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/precedence.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/sub.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/sub_neg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/unop_add.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_3/valid/unop_parens.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_first_op.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_mid_op.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_second_op.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/invalid/missing_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/and_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/and_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/eq_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/eq_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/ge_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/ge_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/gt_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/gt_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/le_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/le_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/lt_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/lt_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/ne_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/ne_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/or_false.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/or_true.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/precedence.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_3.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/precedence_4.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_multi_short_circuit.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_and.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_4/valid/skip_on_failure_short_circuit_or.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/redefine.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_decl_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_bad_lvalue_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/syntax_err_no_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/undeclared_var.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/invalid/var_declared_late.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/assign.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/assign_val.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/exp_return_val.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/initialize.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/missing_return.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/multiple_vars.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/no_initialize.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/refer.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_5/valid/unused_exp.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/incomplete_ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/malformed_ternary_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/expression/ternary_assign.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/declare_statement.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/if_assignment.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/invalid/statement/mismatched_nesting.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/assign_ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/multiple_ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/nested_ternary_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/rh_assignment.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/expression/ternary_short_circuit_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/else.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_3.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_4.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_nested_5.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_not_taken.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/if_taken.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_6/valid/statement/multiple_if.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/invalid/double_define.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/invalid/out_of_scope.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_extra_brace.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/invalid/syntax_err_missing_brace.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_blocks.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/consecutive_declarations.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/declare_after_block.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/declare_block.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/declare_late.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/multi_nesting.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/nested_if.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_7/valid/nested_scope.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/break_not_in_loop.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/continue_not_in_loop.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/out_of_scope_do_while.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_do_no_semicolon.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_empty_clause.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_paren_mismatch.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_statement_in_condition.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_few_for_clauses.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/invalid/syntax_err_too_many_for_clauses.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/break.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/continue.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/continue_empty_post.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/do_while.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/empty_expression.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/for.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/for_decl.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/for_empty.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/for_nested_scope.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/for_variable_shadow.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/nested_break.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/nested_while.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/return_in_while.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/while_multi_statement.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_8/valid/while_single_statement.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/bad_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/declaration_mismatch_2.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_function.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/redefine_variable.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/invalid/too_many_args.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/expression_args.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/fib.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_args.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/forward_decl_multi_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/fun_in_expr.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/hello_world.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/later_decl.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/multi_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/mutual_recursion.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/no_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/precedence.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/rename_function_param.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/single_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/stage_9/valid/variable_as_arg.c create mode 100644 src/resources/write_a_c_compiler-tests/test.c create mode 100755 src/resources/write_a_c_compiler-tests/test_compiler.sh create mode 100755 src/resources/write_a_c_compiler-tests/test_compiler_kotlin.sh 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/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 249e5832f090a2944b7473328c07c9755baa3196..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch literal 43764 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 /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/parser/Parser.kt b/src/jsMain/kotlin/parser/Parser.kt index 75ba66a..e6c0b5c 100644 --- a/src/jsMain/kotlin/parser/Parser.kt +++ b/src/jsMain/kotlin/parser/Parser.kt @@ -59,15 +59,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 @@ -89,15 +90,16 @@ class Parser { 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 @@ -131,7 +133,7 @@ class Parser { val name = 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)))) } else { 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 From 427a60611c2c9920ecc73e8fb9a9d6937898f04b Mon Sep 17 00:00:00 2001 From: sidrasali Date: Mon, 22 Sep 2025 00:20:11 +0200 Subject: [PATCH 11/26] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3588036..3968dd9 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,14 @@ To generate a test coverage report, ``./gradlew koverHtmlReport`` - _\* These two commands are also part of the build command_ \ No newline at end of file + _\* These two commands are also part of the build command_ + +We also included the test suite of the book "Writing a C Compiler" by Nora Sandler. To run these tests, follow these steps: + +``` +# Build the compiler +./gradlew createCompilerJar +# Run the test script +cd src/resources/write_a_c_compiler-tests +./test_compiler_kotlin.sh ../../../build/libs/compiler-1.0-SNAPSHOT.jar +``` \ No newline at end of file From 25e640a9374a1f77308e7d86b707cc695ae8b09f Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Mon, 22 Sep 2025 01:13:02 +0200 Subject: [PATCH 12/26] Fix return statement bugs: add proper ret instructions and epilogue handling --- src/jsMain/kotlin/assembly/CodeEmitter.kt | 13 ++++++++----- src/jsMain/kotlin/export/CompilerExport.kt | 12 ++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) 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/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 64c9467..2a8c8c0 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -114,12 +114,12 @@ class CompilerExport { CompilerWorkflow.take( tacky, optimizations = - listOf( - OptimizationType.CONSTANT_FOLDING, - OptimizationType.DEAD_STORE_ELIMINATION, - OptimizationType.COPY_PROPAGATION, - OptimizationType.UNREACHABLE_CODE_ELIMINATION - ) + listOf( + OptimizationType.CONSTANT_FOLDING, + OptimizationType.DEAD_STORE_ELIMINATION, + OptimizationType.COPY_PROPAGATION, + OptimizationType.UNREACHABLE_CODE_ELIMINATION + ) ) val asm = CompilerWorkflow.take(optimizedTacky) val finalAssemblyString = codeEmitter.emit(asm as AsmProgram) From 1c29393614c6bb10b172da79fedec5b16eb6c573 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Mon, 22 Sep 2025 23:37:27 +0200 Subject: [PATCH 13/26] Small change --- src/jsMain/kotlin/tacky/TackyToAsm.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/jsMain/kotlin/tacky/TackyToAsm.kt b/src/jsMain/kotlin/tacky/TackyToAsm.kt index 8871554..bf37e99 100644 --- a/src/jsMain/kotlin/tacky/TackyToAsm.kt +++ b/src/jsMain/kotlin/tacky/TackyToAsm.kt @@ -42,7 +42,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 +200,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 +217,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 From 4f8988a459577fffc07af0f59c722d3be39dc2ab Mon Sep 17 00:00:00 2001 From: sidrasali Date: Wed, 24 Sep 2025 17:39:25 +0200 Subject: [PATCH 14/26] fix: Detect unexpected characters in the lexer --- .../exceptions/CompilationExceptions.kt | 26 +++++++++++++------ src/jsMain/kotlin/lexer/Lexer.kt | 9 +++++-- .../kotlin/semanticAnalysis/TypeChecker.kt | 4 +-- .../kotlin/integration/InvalidTestCases.kt | 25 +++++++++++------- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index cf39b54..cf38d2d 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( + val expected: String, + val 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 @@ -119,3 +122,10 @@ class IllegalStateException( line: Int? = null, column: Int? = null ) : CompilationException("Internal error: Variable '$name' should have been caught by IdentifierResolution.") + +// TACKY +class TackyException( + operator: String, + line: Int? = null, + column: Int? = null +) : CompilationException("TackyException(Invalid operator: $operator)", line, column) diff --git a/src/jsMain/kotlin/lexer/Lexer.kt b/src/jsMain/kotlin/lexer/Lexer.kt index 543922c..698893b 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.UnexpectedTokenException sealed class TokenType { // keywords @@ -189,11 +190,15 @@ class Lexer( '&' -> { if (match('&')) { addToken(TokenType.AND) + } else { + throw UnexpectedTokenException(char.toString(), "&", line, current - lineStart) } } '|' -> { if (match('|')) { addToken(TokenType.OR) + } else { + throw UnexpectedTokenException(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/semanticAnalysis/TypeChecker.kt b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt index a58c97e..fcb29a3 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -90,7 +90,7 @@ class TypeChecker : Visitor { } 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) } } diff --git a/src/jsTest/kotlin/integration/InvalidTestCases.kt b/src/jsTest/kotlin/integration/InvalidTestCases.kt index baee698..84891d4 100644 --- a/src/jsTest/kotlin/integration/InvalidTestCases.kt +++ b/src/jsTest/kotlin/integration/InvalidTestCases.kt @@ -4,9 +4,9 @@ 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 @@ -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,7 +237,8 @@ object InvalidTestCases { ), // NotFunctionException - Calling something that's not a function InvalidTestCase( - code = """ + code = + """ int main(void) { int a = 5; return a(1, 2); @@ -244,7 +249,8 @@ object InvalidTestCases { ), // NotVariableException - Using function as a variable InvalidTestCase( - code = """ + code = + """ int func(int x) { return x + 1; } @@ -258,7 +264,8 @@ object InvalidTestCases { ), // ReDeclarationFunctionException - Function defined more than once InvalidTestCase( - code = """ + code = + """ int func(int x) { return x + 1; } From b430e2f12c69ce8e0240279c44c4916536ecebff Mon Sep 17 00:00:00 2001 From: sidrasali Date: Thu, 25 Sep 2025 12:56:29 +0200 Subject: [PATCH 15/26] Add exception names to messages --- .../exceptions/CompilationExceptions.kt | 22 ++++++++---- .../kotlin/semanticAnalysis/TypeChecker.kt | 8 ++--- .../kotlin/integration/InvalidTestCases.kt | 36 +++++++++---------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index cf38d2d..605bbc6 100644 --- a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -95,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, @@ -115,13 +119,17 @@ 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.)") // TACKY class TackyException( diff --git a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt index fcb29a3..3698de1 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -2,8 +2,8 @@ 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.AssignmentExpression import parser.BinaryExpression @@ -111,7 +111,7 @@ class TypeChecker : Visitor { ?: throw IllegalStateException(node.name) if (symbol.type !is IntType) { - throw NotVariableException(node.name) + throw NotAVariableException(node.name) } } @@ -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/jsTest/kotlin/integration/InvalidTestCases.kt b/src/jsTest/kotlin/integration/InvalidTestCases.kt index 84891d4..aaa2d04 100644 --- a/src/jsTest/kotlin/integration/InvalidTestCases.kt +++ b/src/jsTest/kotlin/integration/InvalidTestCases.kt @@ -9,8 +9,8 @@ import exceptions.InvalidLValueException import exceptions.InvalidStatementException 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 @@ -184,20 +184,20 @@ object InvalidTestCases { // Function redeclaration with different parameter count (illegal in C) InvalidTestCase( code = - """ + """ int func(int a, int b); int func(int a); int main(void) { return func(1, 2); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = IncompatibleFuncDeclarationException::class ), // Nested function inside block InvalidTestCase( code = - """ + """ int main(void) { { int nested(int x) { @@ -206,51 +206,51 @@ object InvalidTestCases { } return 0; } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NestedFunctionException::class ), // ArgumentCountException - Wrong number of arguments for function InvalidTestCase( code = - """ + """ int func(int a, int b); int main(void) { return func(1); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = ArgumentCountException::class ), InvalidTestCase( code = - """ + """ int main(void) { int nested(int x) { return x + 1; } return nested(5); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NestedFunctionException::class ), // NotFunctionException - Calling something that's not a function InvalidTestCase( code = - """ + """ int main(void) { int a = 5; return a(1, 2); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, - expectedException = NotFunctionException::class + expectedException = NotAFunctionException::class ), // NotVariableException - Using function as a variable InvalidTestCase( code = - """ + """ int func(int x) { return x + 1; } @@ -258,14 +258,14 @@ object InvalidTestCases { int a = func; return a; } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, - expectedException = NotVariableException::class + expectedException = NotAVariableException::class ), // ReDeclarationFunctionException - Function defined more than once InvalidTestCase( code = - """ + """ int func(int x) { return x + 1; } @@ -275,7 +275,7 @@ object InvalidTestCases { int main(void) { return func(5); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = ReDeclarationFunctionException::class ) From bca582b24350e8bab938c9c051a0d75324bc3f94 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 27 Sep 2025 12:21:49 +0200 Subject: [PATCH 16/26] Change order of optimizations to reduce iterations --- src/jsMain/kotlin/CompilerWorkflow.kt | 14 +- .../exceptions/CompilationExceptions.kt | 16 +- src/jsMain/kotlin/export/CompilerExport.kt | 16 +- .../kotlin/optimizations/ConstantFolding.kt | 79 +++--- .../kotlin/optimizations/CopyPropagation.kt | 256 ++++++++++-------- .../optimizations/DeadStoreElimination.kt | 2 +- .../kotlin/optimizations/Optimization.kt | 16 +- .../UnreachableCodeElimination.kt | 53 ++-- .../kotlin/semanticAnalysis/LoopLabeling.kt | 2 +- .../kotlin/integration/InvalidTestCases.kt | 28 +- 10 files changed, 265 insertions(+), 217 deletions(-) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index 8012090..bea0e2a 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -55,10 +55,10 @@ sealed class CompilerWorkflow { take( tacky as TackyProgram, listOf( - OptimizationType.CONSTANT_FOLDING, - OptimizationType.DEAD_STORE_ELIMINATION, - OptimizationType.UNREACHABLE_CODE_ELIMINATION, - OptimizationType.COPY_PROPAGATION + OptimizationType.B_CONSTANT_FOLDING, + OptimizationType.D_DEAD_STORE_ELIMINATION, + OptimizationType.C_UNREACHABLE_CODE_ELIMINATION, + OptimizationType.A_COPY_PROPAGATION ) ) val asm = take(optimizedTacky) @@ -100,11 +100,11 @@ sealed class CompilerWorkflow { while (true) { var cfg = ControlFlowGraph().construct(it.name, it.body) for (optimization in optimizations.sorted()) { - if (optimization == OptimizationType.CONSTANT_FOLDING) { + if (optimization == OptimizationType.B_CONSTANT_FOLDING) { cfg = constantFolding.apply(cfg) - } else if (optimization == OptimizationType.DEAD_STORE_ELIMINATION) { + } else if (optimization == OptimizationType.D_DEAD_STORE_ELIMINATION) { cfg = deadStoreElimination.apply(cfg) - } else if (optimization == OptimizationType.COPY_PROPAGATION) { + } else if (optimization == OptimizationType.A_COPY_PROPAGATION) { cfg = copyPropagation.apply(cfg) } else { cfg = unreachableCodeElimination.apply(cfg) diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index 605bbc6..f02cb0b 100644 --- a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -96,10 +96,10 @@ class IncompatibleFuncDeclarationException( line: Int? = null, column: Int? = null ) : CompilationException( - "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", - line, - column - ) + "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", + line, + column +) class NotAFunctionException( name: String, @@ -120,10 +120,10 @@ class ArgumentCountException( line: Int? = null, column: Int? = null ) : CompilationException( - "ArgumentCountException(Wrong number of arguments for function '$name'. Expected $expected, got $actual.)", - line, - column - ) + "ArgumentCountException(Wrong number of arguments for function '$name'. Expected $expected, got $actual.)", + line, + column +) class IllegalStateException( name: String, diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 2a8c8c0..1636783 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -115,10 +115,10 @@ class CompilerExport { tacky, optimizations = listOf( - OptimizationType.CONSTANT_FOLDING, - OptimizationType.DEAD_STORE_ELIMINATION, - OptimizationType.COPY_PROPAGATION, - OptimizationType.UNREACHABLE_CODE_ELIMINATION + OptimizationType.B_CONSTANT_FOLDING, + OptimizationType.D_DEAD_STORE_ELIMINATION, + OptimizationType.A_COPY_PROPAGATION, + OptimizationType.C_UNREACHABLE_CODE_ELIMINATION ) ) val asm = CompilerWorkflow.take(optimizedTacky) @@ -222,10 +222,10 @@ class CompilerExport { 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 + "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> { diff --git a/src/jsMain/kotlin/optimizations/ConstantFolding.kt b/src/jsMain/kotlin/optimizations/ConstantFolding.kt index f7ffc7f..40889d0 100644 --- a/src/jsMain/kotlin/optimizations/ConstantFolding.kt +++ b/src/jsMain/kotlin/optimizations/ConstantFolding.kt @@ -14,31 +14,34 @@ 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 { foldInstruction(it) } + 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 - } + 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) } @@ -46,28 +49,34 @@ class ConstantFolding : Optimization() { 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, 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/CopyPropagation.kt b/src/jsMain/kotlin/optimizations/CopyPropagation.kt index b7b58c2..067e937 100644 --- a/src/jsMain/kotlin/optimizations/CopyPropagation.kt +++ b/src/jsMain/kotlin/optimizations/CopyPropagation.kt @@ -21,93 +21,102 @@ class CopyPropagation : Optimization() { // 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, instr.sourceId)) + } } - } - 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, instr.sourceId)) - } - 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, instr.sourceId)) - } - 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, instr.sourceId)) - } - 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, instr.sourceId)) - } - 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, instr.sourceId)) - } - 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, instr.sourceId)) - } - else -> { - newInstructions.add(instr) } } + block.copy(instructions = newInstructions) } - block.copy(instructions = newInstructions) - } return cfg.copy(blocks = newBlocks) } @@ -116,7 +125,11 @@ class CopyPropagation : Optimization() { outSets = mutableMapOf() instructionReachingCopies.clear() - val allCopies = cfg.blocks.flatMap { it.instructions }.filterIsInstance().toSet() + val allCopies = + cfg.blocks + .flatMap { it.instructions } + .filterIsInstance() + .toSet() val worklist = cfg.blocks.toMutableList() cfg.blocks.forEach { @@ -141,7 +154,10 @@ class CopyPropagation : Optimization() { } } - private fun meet(block: Block, allCopies: Set): Set { + private fun meet( + block: Block, + allCopies: Set + ): Set { if (block.predecessors.all { it == 0 }) { return emptySet() } @@ -157,7 +173,10 @@ class CopyPropagation : Optimization() { return incomingCopies } - private fun transfer(block: Block, inSet: Set): Set { + private fun transfer( + block: Block, + inSet: Set + ): Set { var currentCopies = inSet.toMutableSet() for (instruction in block.instructions) { instructionReachingCopies[instruction] = currentCopies.toSet() @@ -194,54 +213,65 @@ class CopyPropagation : Optimization() { return currentCopies } - private fun rewrite(instruction: TackyInstruction, reaching: Set): TackyInstruction { + 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), instruction.sourceId) - is TackyUnary -> TackyUnary( - operator = instruction.operator, - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyBinary -> TackyBinary( - operator = instruction.operator, - src1 = substitute(instruction.src1, substitutionMap), - src2 = substitute(instruction.src2, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyCopy -> TackyCopy( - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyFunCall -> TackyFunCall( - funName = instruction.funName, - args = instruction.args.map { substitute(it, substitutionMap) }, - dest = instruction.dest, - instruction.sourceId - ) - is JumpIfZero -> JumpIfZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target, - instruction.sourceId - ) - is JumpIfNotZero -> JumpIfNotZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target, - instruction.sourceId - ) + is TackyUnary -> + TackyUnary( + operator = instruction.operator, + src = substitute(instruction.src, substitutionMap), + dest = instruction.dest, + instruction.sourceId + ) + is TackyBinary -> + TackyBinary( + operator = instruction.operator, + src1 = substitute(instruction.src1, substitutionMap), + src2 = substitute(instruction.src2, substitutionMap), + dest = instruction.dest, + instruction.sourceId + ) + is TackyCopy -> + TackyCopy( + src = substitute(instruction.src, substitutionMap), + dest = instruction.dest, + instruction.sourceId + ) + is TackyFunCall -> + TackyFunCall( + funName = instruction.funName, + args = instruction.args.map { substitute(it, substitutionMap) }, + dest = instruction.dest, + instruction.sourceId + ) + is JumpIfZero -> + JumpIfZero( + condition = substitute(instruction.condition, substitutionMap), + target = instruction.target, + instruction.sourceId + ) + is JumpIfNotZero -> + JumpIfNotZero( + condition = substitute(instruction.condition, substitutionMap), + target = instruction.target, + instruction.sourceId + ) else -> instruction } } - private fun substitute(value: TackyVal, substitutionMap: Map): TackyVal { - return if (value is TackyVar && substitutionMap.containsKey(value.name)) { + private fun substitute( + value: TackyVal, + substitutionMap: Map + ): TackyVal = + 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 1ad9105..9d688fd 100644 --- a/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt +++ b/src/jsMain/kotlin/optimizations/DeadStoreElimination.kt @@ -12,7 +12,7 @@ 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 liveness = LivenessAnalysis() diff --git a/src/jsMain/kotlin/optimizations/Optimization.kt b/src/jsMain/kotlin/optimizations/Optimization.kt index 1e55dfc..7054d29 100644 --- a/src/jsMain/kotlin/optimizations/Optimization.kt +++ b/src/jsMain/kotlin/optimizations/Optimization.kt @@ -1,10 +1,10 @@ package optimizations 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 { @@ -16,10 +16,10 @@ sealed class Optimization { 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() + 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( diff --git a/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt b/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt index 88fff96..86dd316 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,13 +38,14 @@ 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, @@ -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,9 +158,10 @@ class UnreachableCodeElimination : Optimization() { return cfg } - val blocksToRemove = cfg.blocks - .filter { it.instructions.isEmpty() && it.successors.size <= 1 } - .toMutableSet() + val blocksToRemove = + cfg.blocks + .filter { it.instructions.isEmpty() && it.successors.size <= 1 } + .toMutableSet() val newEdges = cfg.edges.toMutableList() val blocksToKeep = cfg.blocks.filter { it !in blocksToRemove }.toMutableList() @@ -194,16 +198,21 @@ class UnreachableCodeElimination : Optimization() { ) } - 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/semanticAnalysis/LoopLabeling.kt b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt index bc1eb91..431cd90 100644 --- a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt +++ b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt @@ -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) diff --git a/src/jsTest/kotlin/integration/InvalidTestCases.kt b/src/jsTest/kotlin/integration/InvalidTestCases.kt index aaa2d04..9c935cd 100644 --- a/src/jsTest/kotlin/integration/InvalidTestCases.kt +++ b/src/jsTest/kotlin/integration/InvalidTestCases.kt @@ -184,20 +184,20 @@ object InvalidTestCases { // Function redeclaration with different parameter count (illegal in C) InvalidTestCase( code = - """ + """ int func(int a, int b); int func(int a); int main(void) { return func(1, 2); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = IncompatibleFuncDeclarationException::class ), // Nested function inside block InvalidTestCase( code = - """ + """ int main(void) { { int nested(int x) { @@ -206,51 +206,51 @@ object InvalidTestCases { } return 0; } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NestedFunctionException::class ), // ArgumentCountException - Wrong number of arguments for function InvalidTestCase( code = - """ + """ int func(int a, int b); int main(void) { return func(1); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = ArgumentCountException::class ), InvalidTestCase( code = - """ + """ int main(void) { int nested(int x) { return x + 1; } return nested(5); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NestedFunctionException::class ), // NotFunctionException - Calling something that's not a function InvalidTestCase( code = - """ + """ int main(void) { int a = 5; return a(1, 2); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NotAFunctionException::class ), // NotVariableException - Using function as a variable InvalidTestCase( code = - """ + """ int func(int x) { return x + 1; } @@ -258,14 +258,14 @@ object InvalidTestCases { int a = func; return a; } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = NotAVariableException::class ), // ReDeclarationFunctionException - Function defined more than once InvalidTestCase( code = - """ + """ int func(int x) { return x + 1; } @@ -275,7 +275,7 @@ object InvalidTestCases { int main(void) { return func(5); } - """.trimIndent(), + """.trimIndent(), failingStage = CompilerStage.PARSER, expectedException = ReDeclarationFunctionException::class ) From 462e71e8089d5a45aa03d154b7d1ed858b1f4ca1 Mon Sep 17 00:00:00 2001 From: JoudiAlakkad Date: Sat, 27 Sep 2025 15:21:05 +0200 Subject: [PATCH 17/26] remove unused code --- src/jsMain/kotlin/export/CompilerExport.kt | 13 +- .../kotlin/optimizations/CopyPropagation.kt | 162 ------------------ 2 files changed, 1 insertion(+), 174 deletions(-) diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 1636783..d892332 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -250,7 +250,7 @@ class CompilerExport { nodes += cfg.blocks.mapIndexed { i, block -> val id = "block_$i" - val label = block.instructions.joinToString(";\n") { it.toPseudoCode(0) }.ifEmpty { "Empty Block" } + val label = block.instructions.joinToString("\n") { it.toPseudoCode(0) }.ifEmpty { "Empty Block" } CFGNode(id, label, "block") } nodes += CFGNode("exit", "Exit", "exit") @@ -286,17 +286,6 @@ class CompilerExport { return Json.encodeToString(CFGExport(cfg.functionName ?: "unknown", nodes, edges, instructionCount)) } - 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" - } - fun getCFGForFunction( precomputed: String?, fn: String, diff --git a/src/jsMain/kotlin/optimizations/CopyPropagation.kt b/src/jsMain/kotlin/optimizations/CopyPropagation.kt index 067e937..18c227d 100644 --- a/src/jsMain/kotlin/optimizations/CopyPropagation.kt +++ b/src/jsMain/kotlin/optimizations/CopyPropagation.kt @@ -12,14 +12,6 @@ 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.A_COPY_PROPAGATION @@ -120,158 +112,4 @@ class CopyPropagation : Optimization() { 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), instruction.sourceId) - is TackyUnary -> - TackyUnary( - operator = instruction.operator, - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyBinary -> - TackyBinary( - operator = instruction.operator, - src1 = substitute(instruction.src1, substitutionMap), - src2 = substitute(instruction.src2, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyCopy -> - TackyCopy( - src = substitute(instruction.src, substitutionMap), - dest = instruction.dest, - instruction.sourceId - ) - is TackyFunCall -> - TackyFunCall( - funName = instruction.funName, - args = instruction.args.map { substitute(it, substitutionMap) }, - dest = instruction.dest, - instruction.sourceId - ) - is JumpIfZero -> - JumpIfZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target, - instruction.sourceId - ) - is JumpIfNotZero -> - JumpIfNotZero( - condition = substitute(instruction.condition, substitutionMap), - target = instruction.target, - instruction.sourceId - ) - else -> instruction - } - } - - private fun substitute( - value: TackyVal, - substitutionMap: Map - ): TackyVal = - if (value is TackyVar && substitutionMap.containsKey(value.name)) { - substitutionMap.getValue(value.name) - } else { - value - } } From 240e1715bf8a4864c50bb65583804813e20f7d34 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 27 Sep 2025 16:23:32 +0200 Subject: [PATCH 18/26] fix: Remove edge to unreachable blocks when folding jumps --- .../kotlin/optimizations/ConstantFolding.kt | 33 ++- .../kotlin/optimizations/ControlFlowGraph.kt | 6 +- .../UnreachableCodeElimination.kt | 6 +- .../UnreachableCodeEliminationTest.kt | 214 ++++++++++-------- 4 files changed, 155 insertions(+), 104 deletions(-) diff --git a/src/jsMain/kotlin/optimizations/ConstantFolding.kt b/src/jsMain/kotlin/optimizations/ConstantFolding.kt index 40889d0..c9c9a79 100644 --- a/src/jsMain/kotlin/optimizations/ConstantFolding.kt +++ b/src/jsMain/kotlin/optimizations/ConstantFolding.kt @@ -19,7 +19,38 @@ class ConstantFolding : Optimization() { override fun apply(cfg: ControlFlowGraph): ControlFlowGraph { val optimizedBlocks = cfg.blocks.map { block -> - val optimizedInstructions = block.instructions.mapNotNull { foldInstruction(it) } + 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 since we're now jumping unconditionally to a different target + val nextBlock = cfg.blocks.find { it.id == block.id + 1 } + if (nextBlock != null) { + 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) diff --git a/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt b/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt index b6634e5..670e2ca 100644 --- a/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt +++ b/src/jsMain/kotlin/optimizations/ControlFlowGraph.kt @@ -43,7 +43,7 @@ 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, @@ -98,7 +98,7 @@ data class ControlFlowGraph( private fun buildEdges( nodes: List, blocks: List - ): List { + ): MutableList { val edges = mutableListOf() val entry = nodes.filterIsInstance().firstOrNull() val exit = nodes.filterIsInstance().firstOrNull() @@ -113,7 +113,7 @@ data class ControlFlowGraph( } // 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() diff --git a/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt b/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt index 86dd316..bf022e5 100644 --- a/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt +++ b/src/jsMain/kotlin/optimizations/UnreachableCodeElimination.kt @@ -51,7 +51,7 @@ class UnreachableCodeElimination : Optimization() { functionName = cfg.functionName, root = cfg.root, blocks = reachableBlocks, - edges = reachableEdges + edges = reachableEdges.toMutableList() ) } @@ -162,7 +162,7 @@ class UnreachableCodeElimination : Optimization() { cfg.blocks .filter { it.instructions.isEmpty() && it.successors.size <= 1 } .toMutableSet() - val newEdges = cfg.edges.toMutableList() + val newEdges = cfg.edges val blocksToKeep = cfg.blocks.filter { it !in blocksToRemove }.toMutableList() // map for easy search of nodes by their ID @@ -194,7 +194,7 @@ class UnreachableCodeElimination : Optimization() { functionName = cfg.functionName, root = cfg.root, blocks = blocksToKeep, - edges = newEdges.distinct() + edges = newEdges.distinct().toMutableList() ) } 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 From 380670485c92ca5d8243555209277b564add58a5 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 27 Sep 2025 20:41:59 +0200 Subject: [PATCH 19/26] fix: Remove edge to next block only if truly unreachable --- src/jsMain/kotlin/optimizations/ConstantFolding.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/jsMain/kotlin/optimizations/ConstantFolding.kt b/src/jsMain/kotlin/optimizations/ConstantFolding.kt index c9c9a79..6b20068 100644 --- a/src/jsMain/kotlin/optimizations/ConstantFolding.kt +++ b/src/jsMain/kotlin/optimizations/ConstantFolding.kt @@ -39,9 +39,16 @@ class ConstantFolding : Optimization() { } } } else if (folded is TackyJump) { - // Remove the edge to the next block since we're now jumping unconditionally to a different target + // 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 } - if (nextBlock != 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 (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) { From de23fd7fc481532809039abfb0ef4062eba2429b Mon Sep 17 00:00:00 2001 From: sidrasali Date: Fri, 3 Oct 2025 11:21:29 +0200 Subject: [PATCH 20/26] refactor: Add new wrapper for optimization results --- src/jsMain/kotlin/export/CompilationOutput.kt | 15 +++++++++-- src/jsMain/kotlin/export/CompilerExport.kt | 27 ++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/jsMain/kotlin/export/CompilationOutput.kt b/src/jsMain/kotlin/export/CompilationOutput.kt index df5160a..9843a90 100644 --- a/src/jsMain/kotlin/export/CompilationOutput.kt +++ b/src/jsMain/kotlin/export/CompilationOutput.kt @@ -44,9 +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 precomputedAssembly: 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 @@ -60,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 d892332..2593c8c 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -102,24 +102,30 @@ class CompilerExport { outputs.add( TackyOutput( tackyPretty = tackyProgram.toPseudoCode(), - functionNames = tackyProgram.functions.map { it.name }.toTypedArray(), - precomputedCFGs = precomputeAllCFGs(tackyProgram), - precomputedAssembly = precomputeAllAssembly(tackyProgram), errors = emptyArray(), tacky = Json.encodeToString(tackyProgram), sourceLocation = sourceLocationInfo ) ) + val cfgs = precomputeAllCFGs(tackyProgram) + outputs.add( + OptimizationOutput( + optimizations = optTypeMap.keys.toTypedArray(), + 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 - ) + 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) @@ -129,6 +135,7 @@ class CompilerExport { errors = emptyArray(), assembly = finalAssemblyString, rawAssembly = rawAssembly, + precomputedAssembly = precomputeAllAssembly(tackyProgram), sourceLocation = sourceLocationInfo ) ) @@ -153,7 +160,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 -> null // TODO We're currently exporting optimization results inside TackyOutput, we should move it to its own object + CompilerStage.OPTIMIZATIONS -> outputs.add(OptimizationOutput(errors = arrayOf(error), sourceLocation = sourceLocationInfo)) } } catch (e: Exception) { // Fallback for any unexpected runtime errors From ec387d75b70ceee4e95ce9a286282113ea465cba Mon Sep 17 00:00:00 2001 From: sidrasali Date: Fri, 3 Oct 2025 11:22:07 +0200 Subject: [PATCH 21/26] fix: Propagate precomputed assembly correctly --- src/jsMain/kotlin/export/CompilerExport.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 2593c8c..f1ce826 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -211,12 +211,21 @@ class CompilerExport { val assemblies = mutableListOf() for (optList in allOptLists) { - val optimizedTacky = - CompilerWorkflow.take( - program, - optimizations = optList.mapNotNull(optTypeMap::get) + 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(optimizedTacky) + + val asm = CompilerWorkflow.take(optimizedProgram) val finalAssemblyString = CodeEmitter().emit(asm as AsmProgram) // Create one entry per optimization set with the full program assembly From 7e587d9cc7da19ffe44eab5c5973c53644d27898 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Fri, 3 Oct 2025 12:19:37 +0200 Subject: [PATCH 22/26] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3968dd9..86a912b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To generate a test coverage report, _\* These two commands are also part of the build command_ -We also included the test suite of the book "Writing a C Compiler" by Nora Sandler. To run these tests, follow these steps: +We also included the test suite of the book "Writing a C Compiler" by Nora Sandler. To run the test suite, follow these steps: ``` # Build the compiler @@ -49,4 +49,5 @@ We also included the test suite of the book "Writing a C Compiler" by Nora Sandl # Run the test script cd src/resources/write_a_c_compiler-tests ./test_compiler_kotlin.sh ../../../build/libs/compiler-1.0-SNAPSHOT.jar -``` \ No newline at end of file +``` +For more information, see the [test suite README](https://github.com/nlsandler/write_a_c_compiler/blob/master/README.md). \ No newline at end of file From e05c2f7f510bb9dfee1df5841ad7e62e3d8f779c Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 4 Oct 2025 14:17:03 +0200 Subject: [PATCH 23/26] Fix parsing bugs: scopes, conditional statements --- README.md | 2 +- src/jsMain/kotlin/export/ASTExport.kt | 79 ++++++---- src/jsMain/kotlin/export/CompilerExport.kt | 28 ++-- src/jsMain/kotlin/parser/Expressions.kt | 6 +- src/jsMain/kotlin/parser/Parser.kt | 124 ++++++++++++--- .../semanticAnalysis/IdentifierResolution.kt | 12 +- .../kotlin/semanticAnalysis/LoopLabeling.kt | 2 +- .../kotlin/semanticAnalysis/TypeChecker.kt | 2 +- src/jsMain/kotlin/tacky/TackyGenVisitor.kt | 7 +- .../kotlin/integration/CompilerTestSuite.kt | 144 ++++++++++++------ 10 files changed, 289 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 86a912b..13302be 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,6 @@ We also included the test suite of the book "Writing a C Compiler" by Nora Sandl ./gradlew createCompilerJar # Run the test script cd src/resources/write_a_c_compiler-tests -./test_compiler_kotlin.sh ../../../build/libs/compiler-1.0-SNAPSHOT.jar +./test_compiler_kotlin.sh ../../../build/libs/compiler-1.0-SNAPSHOT.jar && cd ../../.. ``` For more information, see the [test suite README](https://github.com/nlsandler/write_a_c_compiler/blob/master/README.md). \ No newline at end of file diff --git a/src/jsMain/kotlin/export/ASTExport.kt b/src/jsMain/kotlin/export/ASTExport.kt index 90f38e2..cc3e3d6 100644 --- a/src/jsMain/kotlin/export/ASTExport.kt +++ b/src/jsMain/kotlin/export/ASTExport.kt @@ -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 { @@ -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 = @@ -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/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index f1ce826..4a491ed 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -120,12 +120,12 @@ class CompilerExport { CompilerWorkflow.take( tacky, optimizations = - listOf( - OptimizationType.B_CONSTANT_FOLDING, - OptimizationType.D_DEAD_STORE_ELIMINATION, - OptimizationType.A_COPY_PROPAGATION, - OptimizationType.C_UNREACHABLE_CODE_ELIMINATION - ) + 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) @@ -214,15 +214,15 @@ class CompilerExport { 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 - } + 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) diff --git a/src/jsMain/kotlin/parser/Expressions.kt b/src/jsMain/kotlin/parser/Expressions.kt index 6dbe0d8..7200723 100644 --- a/src/jsMain/kotlin/parser/Expressions.kt +++ b/src/jsMain/kotlin/parser/Expressions.kt @@ -2,7 +2,9 @@ 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, @@ -44,7 +46,7 @@ data class AssignmentExpression( } data class ConditionalExpression( - val codition: Expression, + val condition: Expression, val thenExpression: Expression, val elseExpression: Expression, override val location: SourceLocation diff --git a/src/jsMain/kotlin/parser/Parser.kt b/src/jsMain/kotlin/parser/Parser.kt index e6c0b5c..9fe8c1a 100644 --- a/src/jsMain/kotlin/parser/Parser.kt +++ b/src/jsMain/kotlin/parser/Parser.kt @@ -87,7 +87,11 @@ 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) { @@ -135,7 +139,15 @@ class Parser { if (lookaheadTokens.firstOrNull()?.type == TokenType.LEFT_PAREN) { 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) @@ -216,7 +228,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) @@ -231,7 +248,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 @@ -240,22 +260,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) @@ -263,7 +299,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) @@ -273,7 +314,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) @@ -297,7 +343,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)) + } } } } @@ -306,8 +358,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)) @@ -333,13 +389,27 @@ class Parser { throw InvalidLValueException() } 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) @@ -374,7 +444,10 @@ 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() @@ -388,17 +461,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/semanticAnalysis/IdentifierResolution.kt b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt index a406266..b7f3525 100644 --- a/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt +++ b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt @@ -209,7 +209,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) @@ -241,14 +241,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 @@ -266,14 +264,14 @@ class IdentifierResolution : Visitor { } 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/LoopLabeling.kt b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt index 431cd90..080363e 100644 --- a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt +++ b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt @@ -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 3698de1..49bf13c 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -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) } diff --git a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt index 151572f..fe79c1a 100644 --- a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt +++ b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt @@ -39,7 +39,10 @@ class TackyGenVisitor : Visitor { 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() @@ -284,7 +287,7 @@ class TackyGenVisitor : Visitor { 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 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 = """ From 54147fe83d26093eefc9e4b36e323d4d14a1dfee Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 4 Oct 2025 14:25:30 +0200 Subject: [PATCH 24/26] Update README.md --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 13302be..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,15 +39,21 @@ To generate a test coverage report, ``./gradlew koverHtmlReport`` - _\* These two commands are also part of the build command_ +_All of these commands are also part of the build command_ -We also included the test suite of the book "Writing a C Compiler" by Nora Sandler. To run the test suite, follow these steps: +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. -``` -# Build the compiler -./gradlew createCompilerJar -# 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 README](https://github.com/nlsandler/write_a_c_compiler/blob/master/README.md). \ No newline at end of file + +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 From d0d1e2a6864b7b4c6654cbe3cb927690b289c170 Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 4 Oct 2025 14:41:45 +0200 Subject: [PATCH 25/26] Add line & col to every viewable exception --- .../exceptions/CompilationExceptions.kt | 22 +++--- src/jsMain/kotlin/parser/ASTNode.kt | 12 +++- src/jsMain/kotlin/parser/BlockItems.kt | 20 ++++-- src/jsMain/kotlin/parser/Parser.kt | 11 ++- src/jsMain/kotlin/parser/Programs.kt | 4 +- .../semanticAnalysis/IdentifierResolution.kt | 26 ++++--- .../kotlin/semanticAnalysis/LoopLabeling.kt | 4 +- .../kotlin/semanticAnalysis/TypeChecker.kt | 4 +- src/jsMain/kotlin/tacky/TackyGenVisitor.kt | 70 +++++++++++-------- 9 files changed, 110 insertions(+), 63 deletions(-) diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index f02cb0b..9f3fb11 100644 --- a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -96,10 +96,10 @@ class IncompatibleFuncDeclarationException( line: Int? = null, column: Int? = null ) : CompilationException( - "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", - line, - column -) + "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", + line, + column + ) class NotAFunctionException( name: String, @@ -120,16 +120,20 @@ class ArgumentCountException( line: Int? = null, column: Int? = null ) : CompilationException( - "ArgumentCountException(Wrong number of arguments for function '$name'. Expected $expected, got $actual.)", - line, - column -) + "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("IllegalStateException(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( diff --git a/src/jsMain/kotlin/parser/ASTNode.kt b/src/jsMain/kotlin/parser/ASTNode.kt index df079ab..aae98ea 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()) { +sealed class ASTNode( + open val location: SourceLocation, + open val id: String = Random.nextLong().toString() +) { abstract fun accept(visitor: Visitor): T } diff --git a/src/jsMain/kotlin/parser/BlockItems.kt b/src/jsMain/kotlin/parser/BlockItems.kt index 60bb64b..0813849 100644 --- a/src/jsMain/kotlin/parser/BlockItems.kt +++ b/src/jsMain/kotlin/parser/BlockItems.kt @@ -1,6 +1,8 @@ package parser -sealed class Statement(location: SourceLocation) : ASTNode(location) +sealed class Statement( + location: SourceLocation +) : ASTNode(location) data class ReturnStatement( val expression: Expression, @@ -16,7 +18,9 @@ data class ExpressionStatement( override fun accept(visitor: Visitor): T = visitor.visit(this) } -class NullStatement(override val location: SourceLocation) : Statement(location) { +class NullStatement( + override val location: SourceLocation +) : Statement(location) { override fun accept(visitor: Visitor): T = visitor.visit(this) override fun equals(other: Any?): Boolean = other is NullStatement @@ -67,7 +71,9 @@ data class ForStatement( override fun accept(visitor: Visitor): T = visitor.visit(this) } -sealed class ForInit(location: SourceLocation) : ASTNode(location) +sealed class ForInit( + location: SourceLocation +) : ASTNode(location) data class InitDeclaration( val varDeclaration: VariableDeclaration, @@ -107,7 +113,9 @@ class LabeledStatement( override fun accept(visitor: Visitor): T = visitor.visit(this) } -sealed class Declaration(location: SourceLocation) : ASTNode(location) +sealed class Declaration( + location: SourceLocation +) : ASTNode(location) data class VariableDeclaration( val name: String, @@ -129,7 +137,9 @@ data class FunDecl( override fun accept(visitor: Visitor): T = visitor.visit(this) } -sealed class BlockItem(location: SourceLocation) : ASTNode(location) +sealed class BlockItem( + location: SourceLocation +) : ASTNode(location) data class S( val statement: Statement diff --git a/src/jsMain/kotlin/parser/Parser.kt b/src/jsMain/kotlin/parser/Parser.kt index 9fe8c1a..c7a1ad0 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 } @@ -386,7 +391,7 @@ 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( @@ -494,7 +499,7 @@ class Parser { val nToken = tokens.removeFirst() throw UnexpectedTokenException( expected = - "${TokenType.INT_LITERAL}, ${TokenType.IDENTIFIER}, unary operator, ${TokenType.LEFT_PAREN}", + "${TokenType.INT_LITERAL}, ${TokenType.IDENTIFIER}, unary operator, ${TokenType.LEFT_PAREN}", actual = nToken.type.toString(), line = nToken.startLine, column = nToken.startColumn diff --git a/src/jsMain/kotlin/parser/Programs.kt b/src/jsMain/kotlin/parser/Programs.kt index 05694cc..e99578d 100644 --- a/src/jsMain/kotlin/parser/Programs.kt +++ b/src/jsMain/kotlin/parser/Programs.kt @@ -1,6 +1,8 @@ package parser -sealed class Program(location: SourceLocation) : ASTNode(location) +sealed class Program( + location: SourceLocation +) : ASTNode(location) data class SimpleProgram( val functionDeclaration: List, diff --git a/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt index b7f3525..598d5ae 100644 --- a/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt +++ b/src/jsMain/kotlin/semanticAnalysis/IdentifierResolution.kt @@ -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? @@ -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) } @@ -257,7 +263,7 @@ 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) } diff --git a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt index 080363e..2d0cd89 100644 --- a/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt +++ b/src/jsMain/kotlin/semanticAnalysis/LoopLabeling.kt @@ -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!! } diff --git a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt index 49bf13c..e39d2df 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -86,7 +86,7 @@ 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) { @@ -111,7 +111,7 @@ class TypeChecker : Visitor { ?: throw IllegalStateException(node.name) if (symbol.type !is IntType) { - throw NotAVariableException(node.name) + throw NotAVariableException(node.name, node.location.startLine, node.location.startCol) } } diff --git a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt index fe79c1a..f12ebf4 100644 --- a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt +++ b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt @@ -52,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()) + } } } From fe88e6040ba6a9731d04875260c7bae8574ac5bf Mon Sep 17 00:00:00 2001 From: sidrasali Date: Sat, 4 Oct 2025 16:05:31 +0200 Subject: [PATCH 26/26] Code cleanup --- src/jsMain/kotlin/CompilerWorkflow.kt | 2 +- .../kotlin/{tacky => assembly}/TackyToAsm.kt | 48 ++++++++----------- .../exceptions/CompilationExceptions.kt | 28 +++++------ src/jsMain/kotlin/export/ASTExport.kt | 8 ++-- src/jsMain/kotlin/export/CompilerExport.kt | 1 - src/jsMain/kotlin/lexer/Lexer.kt | 6 +-- src/jsMain/kotlin/parser/ASTNode.kt | 2 +- .../parser/{Visitor.kt => ASTVisitor.kt} | 2 +- src/jsMain/kotlin/parser/BlockItems.kt | 40 ++++++++-------- src/jsMain/kotlin/parser/Expressions.kt | 14 +++--- .../kotlin/parser/FunctionDeclaration.kt | 2 +- src/jsMain/kotlin/parser/Parser.kt | 6 +-- src/jsMain/kotlin/parser/Programs.kt | 2 +- .../semanticAnalysis/IdentifierResolution.kt | 4 +- .../kotlin/semanticAnalysis/LabelCollector.kt | 6 +-- .../kotlin/semanticAnalysis/LoopLabeling.kt | 4 +- .../kotlin/semanticAnalysis/TypeChecker.kt | 4 +- src/jsMain/kotlin/tacky/TackyGenVisitor.kt | 9 ++-- 18 files changed, 88 insertions(+), 100 deletions(-) rename src/jsMain/kotlin/{tacky => assembly}/TackyToAsm.kt (93%) rename src/jsMain/kotlin/parser/{Visitor.kt => ASTVisitor.kt} (97%) diff --git a/src/jsMain/kotlin/CompilerWorkflow.kt b/src/jsMain/kotlin/CompilerWorkflow.kt index bea0e2a..7808619 100644 --- a/src/jsMain/kotlin/CompilerWorkflow.kt +++ b/src/jsMain/kotlin/CompilerWorkflow.kt @@ -3,6 +3,7 @@ package compiler import assembly.AsmConstruct import assembly.InstructionFixer import assembly.PseudoEliminator +import assembly.TackyToAsm import lexer.Lexer import lexer.Token import optimizations.ConstantFolding @@ -21,7 +22,6 @@ import semanticAnalysis.TypeChecker import tacky.TackyConstruct import tacky.TackyGenVisitor import tacky.TackyProgram -import tacky.TackyToAsm enum class CompilerStage { LEXER, diff --git a/src/jsMain/kotlin/tacky/TackyToAsm.kt b/src/jsMain/kotlin/assembly/TackyToAsm.kt similarity index 93% rename from src/jsMain/kotlin/tacky/TackyToAsm.kt rename to src/jsMain/kotlin/assembly/TackyToAsm.kt index bf37e99..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 { diff --git a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt index 9f3fb11..8cf05f8 100644 --- a/src/jsMain/kotlin/exceptions/CompilationExceptions.kt +++ b/src/jsMain/kotlin/exceptions/CompilationExceptions.kt @@ -27,8 +27,8 @@ class InvalidCharacterException( ) : CompilationException("InvalidCharacterException('$character' is not a valid character)", line, column) class UnexpectedCharacterException( - val expected: String, - val actual: String, + expected: String, + actual: String, line: Int? = null, column: Int? = null ) : CompilationException("UnexpectedCharacterException(Expected '$expected', got '$actual')", line, column) @@ -96,10 +96,10 @@ class IncompatibleFuncDeclarationException( line: Int? = null, column: Int? = null ) : CompilationException( - "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", - line, - column - ) + "IncompatibleFuncDeclarationException(Function '$name' redeclared with a different number of parameters.)", + line, + column +) class NotAFunctionException( name: String, @@ -120,20 +120,20 @@ class ArgumentCountException( line: Int? = null, column: Int? = null ) : CompilationException( - "ArgumentCountException(Wrong number of arguments for function '$name'. Expected $expected, got $actual.)", - line, - column - ) + "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( - "IllegalStateException(Internal error: Variable '$name' should have been caught by IdentifierResolution.)", - line, - column - ) + "IllegalStateException(Internal error: Variable '$name' should have been caught by IdentifierResolution.)", + line, + column +) // TACKY class TackyException( diff --git a/src/jsMain/kotlin/export/ASTExport.kt b/src/jsMain/kotlin/export/ASTExport.kt index cc3e3d6..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( @@ -82,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) @@ -131,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) } @@ -147,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) } diff --git a/src/jsMain/kotlin/export/CompilerExport.kt b/src/jsMain/kotlin/export/CompilerExport.kt index 4a491ed..4ba351a 100644 --- a/src/jsMain/kotlin/export/CompilerExport.kt +++ b/src/jsMain/kotlin/export/CompilerExport.kt @@ -110,7 +110,6 @@ class CompilerExport { val cfgs = precomputeAllCFGs(tackyProgram) outputs.add( OptimizationOutput( - optimizations = optTypeMap.keys.toTypedArray(), precomputedCFGs = cfgs, functionNames = tackyProgram.functions.map { it.name }.toTypedArray(), errors = emptyArray() diff --git a/src/jsMain/kotlin/lexer/Lexer.kt b/src/jsMain/kotlin/lexer/Lexer.kt index 698893b..388e32c 100644 --- a/src/jsMain/kotlin/lexer/Lexer.kt +++ b/src/jsMain/kotlin/lexer/Lexer.kt @@ -1,7 +1,7 @@ package lexer import exceptions.InvalidCharacterException -import exceptions.UnexpectedTokenException +import exceptions.UnexpectedCharacterException sealed class TokenType { // keywords @@ -191,14 +191,14 @@ class Lexer( if (match('&')) { addToken(TokenType.AND) } else { - throw UnexpectedTokenException(char.toString(), "&", line, current - lineStart) + throw UnexpectedCharacterException(char.toString(), "&", line, current - lineStart) } } '|' -> { if (match('|')) { addToken(TokenType.OR) } else { - throw UnexpectedTokenException(char.toString(), "|", line, current - lineStart) + throw UnexpectedCharacterException(char.toString(), "|", line, current - lineStart) } } '=' -> { diff --git a/src/jsMain/kotlin/parser/ASTNode.kt b/src/jsMain/kotlin/parser/ASTNode.kt index aae98ea..3364aaf 100644 --- a/src/jsMain/kotlin/parser/ASTNode.kt +++ b/src/jsMain/kotlin/parser/ASTNode.kt @@ -13,5 +13,5 @@ sealed class ASTNode( open val location: SourceLocation, open val id: String = Random.nextLong().toString() ) { - abstract fun accept(visitor: Visitor): T + 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 0813849..d695c68 100644 --- a/src/jsMain/kotlin/parser/BlockItems.kt +++ b/src/jsMain/kotlin/parser/BlockItems.kt @@ -8,20 +8,20 @@ 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) + override fun accept(visitor: ASTVisitor): T = visitor.visit(this) override fun equals(other: Any?): Boolean = other is NullStatement @@ -32,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( @@ -48,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( @@ -57,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( @@ -68,7 +68,7 @@ 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( @@ -79,14 +79,14 @@ 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( @@ -95,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( @@ -110,7 +110,7 @@ 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( @@ -122,19 +122,19 @@ data class VariableDeclaration( 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( @@ -144,25 +144,25 @@ sealed class BlockItem( 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 7200723..7a57f55 100644 --- a/src/jsMain/kotlin/parser/Expressions.kt +++ b/src/jsMain/kotlin/parser/Expressions.kt @@ -10,14 +10,14 @@ 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( @@ -25,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( @@ -34,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( @@ -42,7 +42,7 @@ 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( @@ -51,7 +51,7 @@ data class ConditionalExpression( 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( @@ -59,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 c7a1ad0..ecfe1a7 100644 --- a/src/jsMain/kotlin/parser/Parser.kt +++ b/src/jsMain/kotlin/parser/Parser.kt @@ -139,7 +139,7 @@ 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) // consume the int keyword @@ -458,7 +458,7 @@ class Parser { 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 { @@ -499,7 +499,7 @@ class Parser { val nToken = tokens.removeFirst() throw UnexpectedTokenException( expected = - "${TokenType.INT_LITERAL}, ${TokenType.IDENTIFIER}, unary operator, ${TokenType.LEFT_PAREN}", + "${TokenType.INT_LITERAL}, ${TokenType.IDENTIFIER}, unary operator, ${TokenType.LEFT_PAREN}", actual = nToken.type.toString(), line = nToken.startLine, column = nToken.startColumn diff --git a/src/jsMain/kotlin/parser/Programs.kt b/src/jsMain/kotlin/parser/Programs.kt index e99578d..ddcc20e 100644 --- a/src/jsMain/kotlin/parser/Programs.kt +++ b/src/jsMain/kotlin/parser/Programs.kt @@ -8,5 +8,5 @@ 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 598d5ae..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++}" 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 2d0cd89..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 diff --git a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt index e39d2df..2102623 100644 --- a/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt +++ b/src/jsMain/kotlin/semanticAnalysis/TypeChecker.kt @@ -5,6 +5,7 @@ import exceptions.IncompatibleFuncDeclarationException 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) diff --git a/src/jsMain/kotlin/tacky/TackyGenVisitor.kt b/src/jsMain/kotlin/tacky/TackyGenVisitor.kt index f12ebf4..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,10 +31,9 @@ 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 @@ -293,7 +293,7 @@ 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) @@ -319,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 @@ -361,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)