package prog8tests.compiler import com.github.michaelbull.result.expectError import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.onFailure import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.collections.shouldNotBeIn import io.kotest.matchers.comparables.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import prog8.code.core.* import prog8.code.target.C64Target import prog8.code.target.Cx16Target import prog8.code.target.zp.C64Zeropage import prog8.code.target.zp.CX16Zeropage import prog8tests.helpers.ErrorReporterForTests class TestAbstractZeropage: FunSpec({ class DummyZeropage(options: CompilationOptions) : Zeropage(options) { override val SCRATCH_B1 = 0x10u override val SCRATCH_REG = 0x11u override val SCRATCH_W1 = 0x20u override val SCRATCH_W2 = 0x30u override val SCRATCH_PTR = 0x40u init { free.addAll(0u..255u) removeReservedFromFreePool() retainAllowed() } } test("testAbstractZeropage") { val zp = DummyZeropage( CompilationOptions( OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf((0x50u..0x5fu)), CompilationOptions.AllZeropageAllowed, floats = false, noSysInit = false, romable = false, compTarget = C64Target(), compilerVersion="99.99", loadAddress = 999u, memtopAddress = 0xffffu ) ) zp.free.size shouldBe 256-8-16 } }) class TestC64Zeropage: FunSpec({ val errors = ErrorReporterForTests() val c64target = C64Target() test("testNames") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, floats = false, noSysInit = false, romable = false, compTarget = c64target, compilerVersion="99.99", loadAddress = 999u, memtopAddress = 0xffffu )) var result = zp.allocate("", DataType.UBYTE, null, null, errors) result.onFailure { error(it.toString()) } result = zp.allocate("", DataType.UBYTE, null, null, errors) result.onFailure { error(it.toString()) } result = zp.allocate("varname", DataType.UBYTE, null, null, errors) result.onFailure { error(it.toString()) } shouldThrow { zp.allocate("varname", DataType.UBYTE,null, null, errors) } result = zp.allocate("varname2", DataType.UBYTE, null, null, errors) result.onFailure { error(it.toString()) } } test("testZpFloatEnable") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) var result = zp.allocate("", DataType.FLOAT, null, null, errors) result.expectError { "should be allocation error due to disabled floats" } val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) result = zp2.allocate("", DataType.FLOAT, null, null, errors) result.expectError { "should be allocation error due to disabled ZP use" } val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) zp3.allocate("", DataType.FLOAT, null, null, errors) } test("testZpModesWithFloats") { C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) shouldThrow { C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) } shouldThrow { C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) } } test("testZpDontuse") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) println(zp.free) zp.availableBytes() shouldBe 0 val result = zp.allocate("", DataType.BYTE, null, null, errors) result.expectError { "expected error due to disabled ZP use" } } test("testFreeSpacesBytes") { val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) zp1.availableBytes() shouldBe 15 val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp2.availableBytes() shouldBe 85 val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp3.availableBytes() shouldBe 94 val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp4.availableBytes() shouldBe 205 zp4.allocate("test", DataType.UBYTE, null, null, errors) zp4.availableBytes() shouldBe 204 zp4.allocate("test2", DataType.UBYTE, null, null, errors) zp4.availableBytes() shouldBe 203 } test("testReservedSpace") { val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp1.availableBytes() shouldBe 205 4u shouldNotBeIn zp1.free 5u shouldNotBeIn zp1.free 6u shouldNotBeIn zp1.free 35u shouldNotBeIn zp1.free 50u shouldBeIn zp1.free 100u shouldBeIn zp1.free 49u shouldBeIn zp1.free 101u shouldBeIn zp1.free 200u shouldBeIn zp1.free 255u shouldBeIn zp1.free 199u shouldBeIn zp1.free val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp2.availableBytes() shouldBe 105 4u shouldNotBeIn zp2.free 5u shouldNotBeIn zp2.free 6u shouldNotBeIn zp2.free 35u shouldNotBeIn zp2.free 50u shouldNotBeIn zp2.free 100u shouldNotBeIn zp2.free 49u shouldBeIn zp2.free 101u shouldBeIn zp2.free 200u shouldNotBeIn zp2.free 255u shouldNotBeIn zp2.free 199u shouldBeIn zp2.free val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp2.availableBytes() shouldBe 105 4u shouldBeIn zp3.free 5u shouldBeIn zp3.free 6u shouldBeIn zp3.free 35u shouldNotBeIn zp3.free } test("testBasicsafeAllocation") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) zp.availableBytes() shouldBe 15 zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true var result = zp.allocate("", DataType.FLOAT, null, null, errors) result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" } (0 until zp.availableBytes()).forEach { val alloc = zp.allocate("", DataType.UBYTE, null, null, errors) alloc.getOrElse { throw it } } zp.availableBytes() shouldBe 0 zp.hasByteAvailable() shouldBe false zp.hasWordAvailable() shouldBe false result = zp.allocate("", DataType.UBYTE, null, null, errors) result.expectError { "expected allocation error" } result = zp.allocate("", DataType.UWORD, null, null, errors) result.expectError { "expected allocation error" } } test("testFullAllocation") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) zp.availableBytes() shouldBe 205 zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true var result = zp.allocate("", DataType.UWORD, null, null, errors) zp.availableBytes() shouldBe 203 val loc = result.getOrElse { throw it } .address loc shouldBeGreaterThan 3u loc shouldNotBeIn zp.free val num = zp.availableBytes() / 2 (0..num-3).forEach { zp.allocate("", DataType.UWORD, null, null, errors) } zp.availableBytes() shouldBe 7 // can't allocate because no more sequential bytes, only fragmented result = zp.allocate("", DataType.UWORD, null, null, errors) result.expectError { "should give allocation error" } (0..6).forEach { zp.allocate("", DataType.UBYTE, null, null, errors) } zp.availableBytes() shouldBe 0 zp.hasByteAvailable() shouldBe false zp.hasWordAvailable() shouldBe false result = zp.allocate("", DataType.UBYTE, null, null, errors) result.expectError { "should give allocation error" } } test("testEfficientAllocation") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) zp.availableBytes() shouldBe 15 zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xa6u zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u zp.availableBytes() shouldBe 0 } test("testReservedLocations") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) withClue("zp _B1 and _REG must be next to each other to create a word") { zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG } } }) class TestCx16Zeropage: FunSpec({ val errors = ErrorReporterForTests() val cx16target = Cx16Target() test("testReservedLocations") { val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) withClue("zp _B1 and _REG must be next to each other to create a word") { zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG } } test("testFreeSpacesBytes") { val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, "99.99", 999u, 0xffffu)) zp1.availableBytes() shouldBe 86 val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) zp2.availableBytes() shouldBe 173 val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) zp3.availableBytes() shouldBe 214 zp3.allocate("test", DataType.UBYTE, null, null, errors) zp3.availableBytes() shouldBe 213 zp3.allocate("test2", DataType.UBYTE, null, null, errors) zp3.availableBytes() shouldBe 212 } test("testReservedSpace") { val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) zp1.availableBytes() shouldBe 214 0x22u shouldNotBeIn zp1.free 0x23u shouldNotBeIn zp1.free 0x24u shouldBeIn zp1.free 0x80u shouldBeIn zp1.free 0xffu shouldBeIn zp1.free 0x02u shouldNotBeIn zp1.free 0x21u shouldNotBeIn zp1.free } test("preallocated zp vars") { val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) zp1.allocatedVariables["test"] shouldBe null zp1.allocatedVariables["cx16.r0"] shouldNotBe null zp1.allocatedVariables["cx16.r15"] shouldNotBe null zp1.allocatedVariables["cx16.r0L"] shouldNotBe null zp1.allocatedVariables["cx16.r15L"] shouldNotBe null zp1.allocatedVariables["cx16.r0sH"] shouldNotBe null zp1.allocatedVariables["cx16.r15sH"] shouldNotBe null } })