package prog8tests import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual import io.kotest.matchers.maps.shouldContainKey import io.kotest.matchers.maps.shouldNotContainKey import io.kotest.matchers.shouldBe import prog8.ast.Program import prog8.ast.statements.Block import prog8.ast.statements.Subroutine import prog8.codegen.target.C64Target import prog8.compilerinterface.CallGraph import prog8.parser.Prog8Parser.parseModule import prog8.parser.SourceCode import prog8tests.helpers.* class TestCallgraph: FunSpec({ test("testGraphForEmptySubs") { val sourcecode = """ %import string main { sub start() { } sub empty() { } } """ val result = compileText(C64Target, false, sourcecode).assertSuccess() val graph = CallGraph(result.program) graph.imports.size shouldBe 1 graph.importedBy.size shouldBe 1 val toplevelModule = result.program.toplevelModule val importedModule = graph.imports.getValue(toplevelModule).single() importedModule.name shouldBe "string" val importedBy = graph.importedBy.getValue(importedModule).single() importedBy.name.startsWith("on_the_fly_test") shouldBe true graph.unused(toplevelModule) shouldBe false graph.unused(importedModule) shouldBe false val mainBlock = toplevelModule.statements.filterIsInstance().single() for(stmt in mainBlock.statements) { val sub = stmt as Subroutine graph.calls shouldNotContainKey sub graph.calledBy shouldNotContainKey sub if(sub === result.program.entrypoint) withClue("start() should always be marked as used to avoid having it removed") { graph.unused(sub) shouldBe false } else graph.unused(sub) shouldBe true } } test("testGraphForEmptyButReferencedSub") { val sourcecode = """ %import string main { sub start() { uword xx = &empty xx++ } sub empty() { } } """ val result = compileText(C64Target, false, sourcecode).assertSuccess() val graph = CallGraph(result.program) graph.imports.size shouldBe 1 graph.importedBy.size shouldBe 1 val toplevelModule = result.program.toplevelModule val importedModule = graph.imports.getValue(toplevelModule).single() importedModule.name shouldBe "string" val importedBy = graph.importedBy.getValue(importedModule).single() importedBy.name.startsWith("on_the_fly_test") shouldBe true graph.unused(toplevelModule) shouldBe false graph.unused(importedModule) shouldBe false val mainBlock = toplevelModule.statements.filterIsInstance().single() val startSub = mainBlock.statements.filterIsInstance().single{it.name=="start"} val emptySub = mainBlock.statements.filterIsInstance().single{it.name=="empty"} withClue("start 'calls' (references) empty") { graph.calls shouldContainKey startSub } withClue("empty doesn't call anything") { graph.calls shouldNotContainKey emptySub } withClue("empty gets 'called'") { graph.calledBy shouldContainKey emptySub } withClue( "start doesn't get called (except as entrypoint ofc.)") { graph.calledBy shouldNotContainKey startSub } } test("allIdentifiers separates for different positions of the IdentifierReferences") { val sourcecode = """ main { sub start() { uword x1 = &empty uword x2 = &empty empty() } sub empty() { %asm {{ nop }} } } """ val result = compileText(C64Target, false, sourcecode).assertSuccess() val graph = CallGraph(result.program) graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9 val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") } empties.size shouldBe 3 empties[0].position.line shouldBe 4 empties[1].position.line shouldBe 5 empties[2].position.line shouldBe 6 } test("checking block and subroutine names usage in assembly code") { val source = """ main { sub start() { %asm {{ lda #().single { it.name == "start" } val subSubroutine = blockBlockname.statements.filterIsInstance().single { it.name == "subroutine" } val subCorrectlabel = blockBlockname.statements.filterIsInstance().single { it.name == "correctlabel" } val subRout = blockLocknam.statements.filterIsInstance().single { it.name == "rout" } val subOrrectlab = blockLocknam.statements.filterIsInstance().single { it.name == "orrectlab" } callgraph.unused(blockMain) shouldBe false callgraph.unused(blockBlockname) shouldBe false callgraph.unused(blockLocknam) shouldBe true callgraph.unused(subStart) shouldBe false callgraph.unused(subSubroutine) shouldBe false callgraph.unused(subCorrectlabel) shouldBe false callgraph.unused(subRout) shouldBe true callgraph.unused(subOrrectlab) shouldBe true } })