diff --git a/codeCore/src/prog8/code/core/CompilationOptions.kt b/codeCore/src/prog8/code/core/CompilationOptions.kt index 590d75637..480c14c2c 100644 --- a/codeCore/src/prog8/code/core/CompilationOptions.kt +++ b/codeCore/src/prog8/code/core/CompilationOptions.kt @@ -43,5 +43,81 @@ class CompilationOptions(val output: OutputType, companion object { val AllZeropageAllowed: List = listOf(0u..255u) + + fun builder(compTarget: ICompilationTarget) = Builder(compTarget) + + class Builder(private val compTarget: ICompilationTarget) { + private var output: OutputType = compTarget.defaultOutputType + private var launcher: CbmPrgLauncherType = CbmPrgLauncherType.NONE + private var zeropage: ZeropageType = ZeropageType.DONTUSE + private var zpReserved: List = emptyList() + private var zpAllowed: List = AllZeropageAllowed + private var floats: Boolean = false + private var noSysInit: Boolean = false + private var romable: Boolean = false + private var compilerVersion: String = "unknown" + private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS + private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS + private var warnSymbolShadowing: Boolean = false + private var warnImplicitTypeCast: Boolean = false + private var optimize: Boolean = false + private var asmQuiet: Boolean = false + private var asmListfile: Boolean = false + private var includeSourcelines: Boolean = false + private var dumpVariables: Boolean = false + private var dumpSymbols: Boolean = false + private var experimentalCodegen: Boolean = false + private var varsHighBank: Int? = null + private var varsGolden: Boolean = false + private var slabsHighBank: Int? = null + private var slabsGolden: Boolean = false + private var breakpointCpuInstruction: String? = null + private var ignoreFootguns: Boolean = false + private var outputDir: Path = Path("") + private var quiet: Boolean = false + private var profilingInstrumentation: Boolean = false + private var symbolDefs: Map = emptyMap() + + fun output(output: OutputType) = apply { this.output = output } + fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher } + fun zeropage(zeropage: ZeropageType) = apply { this.zeropage = zeropage } + fun zpReserved(zpReserved: List) = apply { this.zpReserved = zpReserved } + fun zpAllowed(zpAllowed: List) = apply { this.zpAllowed = zpAllowed } + fun floats(floats: Boolean) = apply { this.floats = floats } + fun noSysInit(noSysInit: Boolean) = apply { this.noSysInit = noSysInit } + fun romable(romable: Boolean) = apply { this.romable = romable } + fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion } + fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress } + fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress } + fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing } + fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast } + fun optimize(optimize: Boolean) = apply { this.optimize = optimize } + fun asmQuiet(asmQuiet: Boolean) = apply { this.asmQuiet = asmQuiet } + fun asmListfile(asmListfile: Boolean) = apply { this.asmListfile = asmListfile } + fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines } + fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables } + fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols } + fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen } + fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank } + fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden } + fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank } + fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden } + fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction } + fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns } + fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir } + fun quiet(quiet: Boolean) = apply { this.quiet = quiet } + fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation } + fun symbolDefs(symbolDefs: Map) = apply { this.symbolDefs = symbolDefs } + + fun build(): CompilationOptions { + return CompilationOptions( + output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion, + loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile, + includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden, + slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet, + profilingInstrumentation, symbolDefs + ) + } + } } } diff --git a/codeCore/test/prog8tests/codecore/TestZeropageFreeList.kt b/codeCore/test/prog8tests/codecore/TestZeropageFreeList.kt index 5788bb902..82b01cacd 100644 --- a/codeCore/test/prog8tests/codecore/TestZeropageFreeList.kt +++ b/codeCore/test/prog8tests/codecore/TestZeropageFreeList.kt @@ -33,20 +33,12 @@ class TestZeropageFreeList: FunSpec({ // ============================================================================ fun createOptions(target: ICompilationTarget, zpType: ZeropageType, floats: Boolean = false): CompilationOptions { - return CompilationOptions( - output = OutputType.RAW, - launcher = CbmPrgLauncherType.NONE, - zeropage = zpType, - zpReserved = emptyList(), - zpAllowed = CompilationOptions.AllZeropageAllowed, - floats = floats, - noSysInit = false, - romable = false, - compTarget = target, - compilerVersion = "test", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = target.PROGRAM_MEMTOP_ADDRESS - ) + return CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(zpType) + .floats(floats) + .compilerVersion("test") + .build() } fun verifyScratchLocationsReserved(zp: Zeropage) { diff --git a/codeGenCpu6502/test/TestCodegen.kt b/codeGenCpu6502/test/TestCodegen.kt index 66761d4dc..c85e990f9 100644 --- a/codeGenCpu6502/test/TestCodegen.kt +++ b/codeGenCpu6502/test/TestCodegen.kt @@ -24,20 +24,13 @@ class TestCodegen: FunSpec({ fun getTestOptions(): CompilationOptions { val target = C64Target() - return CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - zpReserved = emptyList(), - zpAllowed = CompilationOptions.AllZeropageAllowed, - floats = true, - noSysInit = false, - romable = false, - compTarget = target, - compilerVersion="99.99", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = 0xffffu - ) + return CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .floats(true) + .compilerVersion("99.99") + .memtopAddress(0xffffu) + .build() } test("augmented assign on arrays") { diff --git a/codeGenIntermediate/test/TestIRPeepholeOpt.kt b/codeGenIntermediate/test/TestIRPeepholeOpt.kt index 4ec248bf2..8673a072e 100644 --- a/codeGenIntermediate/test/TestIRPeepholeOpt.kt +++ b/codeGenIntermediate/test/TestIRPeepholeOpt.kt @@ -14,20 +14,12 @@ class TestIRPeepholeOpt: FunSpec({ chunks.forEach { sub += it } block += sub val target = VMTarget() - val options = CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - emptyList(), - CompilationOptions.AllZeropageAllowed, - floats = false, - noSysInit = true, - romable = false, - compTarget = target, - compilerVersion="99.99", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = 0xffffu - ) + val options = CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .noSysInit(true) + .compilerVersion("99.99") + .build() val prog = IRProgram("test", IRSymbolTable(), options, target) prog.addBlock(block) prog.linkChunks() diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt index b9ac21e26..b5596809f 100644 --- a/codeGenIntermediate/test/TestVmCodeGen.kt +++ b/codeGenIntermediate/test/TestVmCodeGen.kt @@ -19,20 +19,12 @@ class TestVmCodeGen: FunSpec({ fun getTestOptions(): CompilationOptions { val target = VMTarget() - return CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - zpReserved = emptyList(), - zpAllowed = CompilationOptions.AllZeropageAllowed, - floats = true, - noSysInit = false, - romable = false, - compTarget = target, - compilerVersion="99.99", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = 0xffffu - ) + return CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .floats(true) + .compilerVersion("99.99") + .build() } test("augmented assigns") { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 5ac039604..7d8935101 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -461,7 +461,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat val allOptions = program.modules.flatMap { it.options() }.toSet() val floatsEnabled = "enable_floats" in allOptions var noSysInit = "no_sysinit" in allOptions - val rombale = "romable" in allOptions + val romable = "romable" in allOptions var zpType: ZeropageType = if (zpoption == null) if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE @@ -516,11 +516,17 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat noSysInit = true } - return CompilationOptions( - outputType, launcherType, - zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale, - compTarget, VERSION, 0u, 0xffffu - ) + return CompilationOptions.builder(compTarget) + .output(outputType) + .launcher(launcherType) + .zeropage(zpType) + .zpReserved(zpReserved) + .zpAllowed(if (zpAllowed.isEmpty()) CompilationOptions.AllZeropageAllowed else zpAllowed) + .floats(floatsEnabled) + .noSysInit(noSysInit) + .romable(romable) + .compilerVersion(VERSION) + .build() } private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { diff --git a/compiler/test/TestZeropageAllocation.kt b/compiler/test/TestZeropageAllocation.kt index 81c64b612..1180043a7 100644 --- a/compiler/test/TestZeropageAllocation.kt +++ b/compiler/test/TestZeropageAllocation.kt @@ -1,5 +1,4 @@ package prog8tests.compiler - import com.github.michaelbull.result.expectError import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.onErr @@ -20,62 +19,45 @@ import prog8.code.target.zp.C64Zeropage import prog8.code.target.zp.CX16Zeropage import prog8.code.target.zp.PETZeropage 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 - ) + CompilationOptions.builder(C64Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.FULL) + .zpReserved(listOf(0x50u..0x5fu)) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() ) 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 - )) - + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) var result = zp.allocate("", DataType.UBYTE, null, null, errors) result.onErr { error(it.toString()) } result = zp.allocate("", DataType.UBYTE, null, null, errors) @@ -86,50 +68,59 @@ class TestC64Zeropage: FunSpec({ result = zp.allocate("varname2", DataType.UBYTE, null, null, errors) result.onErr { 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)) + val zp = C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FULL).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) 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)) + val zp2 = C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.DONTUSE).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) 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)) + val zp3 = C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FLOATSAFE).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) 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)) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FULL).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.KERNALSAFE).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.BASICSAFE).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FLOATSAFE).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.BASICSAFE).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FLOATSAFE).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) shouldThrow { - C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.FULL).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) } shouldThrow { - C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) + C64Zeropage(CompilationOptions.builder(c64target).output(OutputType.RAW).zeropage(ZeropageType.KERNALSAFE).floats(true).compilerVersion("99.99").loadAddress(999u).memtopAddress(0xffffu).build()) } } - test("testZpDontuse") { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) 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("testBasicsafeAllocation") { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .floats(true) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zp.availableBytes() shouldBe 14 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 } @@ -142,9 +133,15 @@ class TestC64Zeropage: FunSpec({ 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)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.FULL) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zp.availableBytes() shouldBe 207 zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true @@ -154,29 +151,32 @@ class TestC64Zeropage: FunSpec({ loc shouldBeGreaterThan 3u loc shouldNotBeIn zp.free val num = zp.availableBytes() / 2 - (0..num).forEach { zp.allocate("", DataType.UWORD, null, null, errors) } zp.availableBytes() shouldBe 5 - // 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..10).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)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .floats(true) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zp.availableBytes() shouldBe 14 zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u @@ -191,48 +191,75 @@ class TestC64Zeropage: FunSpec({ zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u zp.availableBytes() shouldBe 0 } - test("testLongAllocation") { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.FULL) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zp.availableBytes() shouldBe 207 - // LONG requires 4 sequential bytes val result = zp.allocate("", DataType.LONG, null, null, errors) result.onErr { error(it.toString()) } zp.availableBytes() shouldBe 203 // 207 - 4 - // Allocate several LONGs (1..10).forEach { zp.allocate("", DataType.LONG, null, null, errors).onErr { error(it.toString()) } } zp.availableBytes() shouldBe 163 // 203 - 40 } - test("testFloatAllocation") { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu)) - + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.FLOATSAFE) + .floats(true) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) // FLOAT requires 5 sequential bytes and floats must be enabled val initialFree = zp.availableBytes() val result = zp.allocate("", DataType.FLOAT, null, null, errors) result.onErr { error(it.toString()) } zp.availableBytes() shouldBe initialFree - 5 // FLOAT is 5 bytes - // Verify warning is generated for large allocation errors.warnings.any { it.contains("float") && it.contains("5 bytes") } shouldBe true } - test("testReservedLocations") { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) + val zp = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) withClue("zp _B1 and _REG must be next to each other to create a word") { zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG } } - test("virtual registers") { - val zpbasic = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) + val zpbasic = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpbasic.allocatedVariables.any {it.key=="cx16.r0"} shouldBe false - - val zpkernal = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu)) + val zpkernal = C64Zeropage(CompilationOptions.builder(c64target) + .output(OutputType.RAW) + .zeropage(ZeropageType.KERNALSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpkernal.allocatedVariables.any {it.key=="cx16.r0"} shouldBe true val r0bL = zpkernal.allocatedVariables.getValue("cx16.r0bL") r0bL.size shouldBe 1 @@ -244,13 +271,25 @@ class TestC64Zeropage: FunSpec({ r14r15sl.address shouldBe 32u } }) - class TestPetZeropage: FunSpec({ test("virtual registers") { - val zpbasic = PETZeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, PETTarget(), "99.99", 999u, 0xffffu)) + val zpbasic = PETZeropage(CompilationOptions.builder(PETTarget()) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpbasic.allocatedVariables.any {it.key=="cx16.r0"} shouldBe false - - val zpkernal = PETZeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, PETTarget(), "99.99", 999u, 0xffffu)) + val zpkernal = PETZeropage(CompilationOptions.builder(PETTarget()) + .output(OutputType.RAW) + .zeropage(ZeropageType.KERNALSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpkernal.allocatedVariables.any {it.key=="cx16.r0"} shouldBe true val r0bL = zpkernal.allocatedVariables.getValue("cx16.r0bL") r0bL.size shouldBe 1 @@ -262,13 +301,25 @@ class TestPetZeropage: FunSpec({ r14r15sl.address shouldBe 32u } }) - class TestC128Zeropage: FunSpec({ test("virtual registers") { - val zpbasic = C128Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, C128Target(), "99.99", 999u, 0xffffu)) + val zpbasic = C128Zeropage(CompilationOptions.builder(C128Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpbasic.allocatedVariables.any {it.key=="cx16.r0"} shouldBe false - - val zpkernal = C128Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, C128Target(), "99.99", 999u, 0xffffu)) + val zpkernal = C128Zeropage(CompilationOptions.builder(C128Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.KERNALSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpkernal.allocatedVariables.any {it.key=="cx16.r0"} shouldBe true val r0bL = zpkernal.allocatedVariables.getValue("cx16.r0bL") r0bL.size shouldBe 1 @@ -280,20 +331,31 @@ class TestC128Zeropage: FunSpec({ r14r15sl.address shouldBe 38u } }) - 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)) + val zp = CX16Zeropage(CompilationOptions.builder(cx16target) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) withClue("zp _B1 and _REG must be next to each other to create a word") { zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG } } - 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)) + val zp1 = CX16Zeropage(CompilationOptions.builder(cx16target) + .output(OutputType.RAW) + .zeropage(ZeropageType.FULL) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zp1.allocatedVariables["test"] shouldBe null zp1.allocatedVariables["cx16.r0"] shouldNotBe null zp1.allocatedVariables["cx16.r15"] shouldNotBe null @@ -302,12 +364,24 @@ class TestCx16Zeropage: FunSpec({ zp1.allocatedVariables["cx16.r0sH"] shouldNotBe null zp1.allocatedVariables["cx16.r15sH"] shouldNotBe null } - test("virtual registers") { - val zpbasic = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, Cx16Target(), "99.99", 999u, 0xffffu)) + val zpbasic = CX16Zeropage(CompilationOptions.builder(Cx16Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.BASICSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpbasic.allocatedVariables.any {it.key=="cx16.r0"} shouldBe true - - val zpkernal = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, Cx16Target(), "99.99", 999u, 0xffffu)) + val zpkernal = CX16Zeropage(CompilationOptions.builder(Cx16Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.KERNALSAFE) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() + ) zpkernal.allocatedVariables.any {it.key=="cx16.r0"} shouldBe true val r0bL = zpkernal.allocatedVariables.getValue("cx16.r0bL") r0bL.size shouldBe 1 @@ -318,32 +392,26 @@ class TestCx16Zeropage: FunSpec({ r14r15sl.dt shouldBe DataType.LONG r14r15sl.address shouldBe 30u } - test("testLongAllocation") { val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu)) zp.availableBytes() shouldBe 214 - // LONG requires 4 sequential bytes val result = zp.allocate("", DataType.LONG, null, null, errors) result.onErr { error(it.toString()) } zp.availableBytes() shouldBe 210 // 214 - 4 - // Allocate several LONGs (1..10).forEach { zp.allocate("", DataType.LONG, null, null, errors).onErr { error(it.toString()) } } zp.availableBytes() shouldBe 170 // 210 - 40 } - test("testFloatAllocation") { val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, "99.99", 999u, 0xffffu)) - // FLOAT requires 5 sequential bytes and floats must be enabled val initialFree = zp.availableBytes() val result = zp.allocate("", DataType.FLOAT, null, null, errors) result.onErr { error(it.toString()) } zp.availableBytes() shouldBe initialFree - 5 // FLOAT is 5 bytes - // Verify warning is generated for large allocation errors.warnings.any { it.contains("float") && it.contains("5 bytes") } shouldBe true } diff --git a/compiler/test/ast/TestBuilders.kt b/compiler/test/ast/TestBuilders.kt new file mode 100644 index 000000000..4bbcd2ec6 --- /dev/null +++ b/compiler/test/ast/TestBuilders.kt @@ -0,0 +1,122 @@ +package prog8tests.ast + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.ast.expressions.* +import prog8.ast.statements.* +import prog8.code.core.* + +class TestBuilders : FunSpec({ + val pos = Position("test", 1, 1, 1) + + context("VarDecl builder") { + test("VarDecl builder basic usage") { + val decl = VarDecl.builder(DataType.UBYTE, pos) + .names("myvar") + .build() + + decl.name shouldBe "myvar" + decl.datatype shouldBe DataType.UBYTE + decl.type shouldBe VarDeclType.VAR + decl.origin shouldBe VarDeclOrigin.USERCODE + decl.zeropage shouldBe ZeropageWish.DONTCARE + decl.splitwordarray shouldBe SplitWish.DONTCARE + decl.isPrivate shouldBe false + decl.names.isEmpty() shouldBe true + } + + test("VarDecl builder multiple names") { + val decl = VarDecl.builder(DataType.UBYTE, pos) + .names("v1", "v2", "v3") + .build() + + decl.name shouldBe "" + decl.names shouldBe listOf("v1", "v2", "v3") + } + + test("VarDecl builder advanced properties") { + val valExpr = NumericLiteral.optimalInteger(42, pos) + val numColsExpr = NumericLiteral.optimalInteger(10, pos) + val arraySize = ArrayIndex(NumericLiteral.optimalInteger(100, pos), pos) + + val decl = VarDecl.builder(DataType.UWORD, pos) + .names("myconst") + .type(VarDeclType.CONST) + .origin(VarDeclOrigin.ARRAYLITERAL) + .zeropage(ZeropageWish.REQUIRE_ZEROPAGE) + .splitwordarray(SplitWish.NOSPLIT) + .isPrivate(true) + .sharedWithAsm(true) + .alignment(4u) + .dirty(true) + .value(valExpr) + .matrixNumCols(numColsExpr) + .arraysize(arraySize) + .build() + + decl.name shouldBe "myconst" + decl.datatype shouldBe DataType.UWORD + decl.type shouldBe VarDeclType.CONST + decl.origin shouldBe VarDeclOrigin.ARRAYLITERAL + decl.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE + decl.splitwordarray shouldBe SplitWish.NOSPLIT + decl.isPrivate shouldBe true + decl.sharedWithAsm shouldBe true + decl.alignment shouldBe 4u + decl.dirty shouldBe true + decl.value shouldBe valExpr + decl.matrixNumCols shouldBe numColsExpr + decl.arraysize shouldBe arraySize + } + + test("VarDecl builder name is required") { + val builder = VarDecl.builder(DataType.UBYTE, pos) + val exception = io.kotest.assertions.throwables.shouldThrow { + builder.build() + } + exception.message shouldBe "name is required" + } + + test("VarDecl builder at least one name required") { + val builder = VarDecl.builder(DataType.UBYTE, pos) + io.kotest.assertions.throwables.shouldThrow { + builder.names() + } + } + } + + context("CompilationOptions builder") { + test("CompilationOptions builder basic usage") { + val target = prog8.code.target.C64Target() + val options = CompilationOptions.builder(target).build() + + options.compTarget shouldBe target + options.output shouldBe target.defaultOutputType + options.launcher shouldBe CbmPrgLauncherType.NONE + options.zeropage shouldBe ZeropageType.DONTUSE + options.compilerVersion shouldBe "unknown" + options.loadAddress shouldBe target.PROGRAM_LOAD_ADDRESS + options.memtopAddress shouldBe target.PROGRAM_MEMTOP_ADDRESS + } + + test("CompilationOptions builder custom properties") { + val target = prog8.code.target.Cx16Target() + val options = CompilationOptions.builder(target) + .output(OutputType.RAW) + .launcher(CbmPrgLauncherType.BASIC) + .zeropage(ZeropageType.FLOATSAFE) + .floats(true) + .optimize(true) + .warnSymbolShadowing(true) + .build() + + options.compTarget shouldBe target + options.output shouldBe OutputType.RAW + options.launcher shouldBe CbmPrgLauncherType.BASIC + options.zeropage shouldBe ZeropageType.FLOATSAFE + options.floats shouldBe true + options.optimize shouldBe true + options.warnSymbolShadowing shouldBe true + } + } +}) diff --git a/compiler/test/codegeneration/TestAsmGenSymbols.kt b/compiler/test/codegeneration/TestAsmGenSymbols.kt index ee21422cd..78d592cff 100644 --- a/compiler/test/codegeneration/TestAsmGenSymbols.kt +++ b/compiler/test/codegeneration/TestAsmGenSymbols.kt @@ -88,7 +88,14 @@ class TestAsmGenSymbols: StringSpec({ fun createTestAsmGen6502(program: Program): AsmGen6502Internal { val errors = ErrorReporterForTests() - val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), "99.99", 999u, 0xffffu) + val options = CompilationOptions.builder(C64Target()) + .output(OutputType.RAW) + .zeropage(ZeropageType.FULL) + .noSysInit(true) + .compilerVersion("99.99") + .loadAddress(999u) + .memtopAddress(0xffffu) + .build() val astchecker = AstChecker(program, errors, options) astchecker.visit(program) errors.report() diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 3b9fef31f..7035efdae 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -273,6 +273,52 @@ class VarDecl( companion object { private var autoHeapValueSequenceNumber = 0 + fun builder(datatype: DataType, position: Position) = Builder(datatype, position) + + class Builder( + private val datatype: DataType, + private val position: Position + ) { + private var name: String? = null + private var type = VarDeclType.VAR + private var origin = VarDeclOrigin.USERCODE + private var zeropage = ZeropageWish.DONTCARE + private var splitwordarray = SplitWish.DONTCARE + private var arraysize: ArrayIndex? = null + private var matrixNumCols: Expression? = null + private var additionalNames: List = emptyList() + private var value: Expression? = null + private var sharedWithAsm = false + private var alignment = 0u + private var dirty = false + private var isPrivate = false + + fun names(vararg names: String): Builder = apply { + require(names.isNotEmpty()) { "at least one name is required" } + this.name = if(names.size > 1) "" else names[0] + this.additionalNames = if(names.size > 1) names.toList() else emptyList() + } + fun type(t: VarDeclType) = apply { this.type = t } + fun origin(o: VarDeclOrigin) = apply { this.origin = o } + fun zeropage(z: ZeropageWish) = apply { this.zeropage = z } + fun splitwordarray(s: SplitWish) = apply { this.splitwordarray = s } + fun arraysize(a: ArrayIndex?) = apply { this.arraysize = a } + fun matrixNumCols(m: Expression?) = apply { this.matrixNumCols = m } + fun value(v: Expression?) = apply { this.value = v } + fun sharedWithAsm(s: Boolean) = apply { this.sharedWithAsm = s } + fun alignment(a: UInt) = apply { this.alignment = a } + fun dirty(d: Boolean) = apply { this.dirty = d } + fun isPrivate(p: Boolean) = apply { this.isPrivate = p } + + fun build(): VarDecl { + val finalName = name ?: throw IllegalStateException("name is required") + return VarDecl( + type, origin, datatype, zeropage, splitwordarray, arraysize, matrixNumCols, + finalName, additionalNames, value, sharedWithAsm, alignment, dirty, isPrivate, position + ) + } + } + fun fromParameter(param: SubroutineParameter): VarDecl { val decltype: VarDeclType val value: Expression? @@ -416,7 +462,6 @@ class VarDecl( class StructDecl(override val name: String, val fields: Array>, override val position: Position) : Statement(), INamedStatement, ISubType { override lateinit var parent: Node - override fun linkParents(parent: Node) { this.parent = parent } @@ -466,7 +511,6 @@ class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, v class Enumeration(override val name: String, val type: BaseDataType, val members: Array>, override val position: Position) : Statement(), INamedStatement { override lateinit var parent: Node - override fun linkParents(parent: Node) { this.parent = parent } @@ -1485,7 +1529,6 @@ class OnGoto( ) : Statement() { override lateinit var parent: Node - override fun linkParents(parent: Node) { this.parent = parent index.linkParents(this) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 37ed37076..68ca10090 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -22,7 +22,7 @@ Weird Heisenbug Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ -- kotlin: use builder patterns instead of humongous constructor parameter lists (vardecl mostly)? +- kotlin: use builder patterns instead of humongous constructor parameter lists (vardecl mostly)? (PARTIALLY DONE: VarDecl now has a builder in its companion object) - symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting. - why are (interned) strings stored as initialization value in the SymbolTable AND as string nodes in the interned string block? Something seems redundant here? - when implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index 4946d7d31..df0fc66da 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -150,22 +150,19 @@ class IRFileReader { } } - return CompilationOptions( - outputType, - launcher, - zeropage, - zpReserved, - zpAllowed, - false, - false, - romable, - target, - compilerVersion, - loadAddress, - memtop, - outputDir = outputDir, - optimize = optimize - ) + return CompilationOptions.builder(target) + .output(outputType) + .launcher(launcher) + .zeropage(zeropage) + .zpReserved(zpReserved) + .zpAllowed(zpAllowed) + .romable(romable) + .compilerVersion(compilerVersion) + .loadAddress(loadAddress) + .memtopAddress(memtop) + .outputDir(outputDir) + .optimize(optimize) + .build() } private fun parseAsmSymbols(reader: XMLEventReader): Map { diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt index 7730b61af..aad7d178e 100644 --- a/intermediate/test/TestIRFileInOut.kt +++ b/intermediate/test/TestIRFileInOut.kt @@ -13,21 +13,15 @@ class TestIRFileInOut: FunSpec({ test("test IR writer") { val target = Cx16Target() val tempdir = Path(System.getProperty("java.io.tmpdir")) - val options = CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - emptyList(), - CompilationOptions.AllZeropageAllowed, - floats = false, - noSysInit = true, - romable = false, - compTarget = target, - compilerVersion = "99.99", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = 0xffffu, - outputDir = tempdir - ) + val options = CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .noSysInit(true) + .compilerVersion("99.99") + .loadAddress(target.PROGRAM_LOAD_ADDRESS) + .memtopAddress(0xffffu) + .outputDir(tempdir) + .build() val program = IRProgram("unittest-irwriter", IRSymbolTable(), options, target) val writer = IRFileWriter(program, null) val generatedFile = writer.write() diff --git a/simpleAst/src/prog8/code/ast/AstStatements.kt b/simpleAst/src/prog8/code/ast/AstStatements.kt index 8396b87cd..24252392f 100644 --- a/simpleAst/src/prog8/code/ast/AstStatements.kt +++ b/simpleAst/src/prog8/code/ast/AstStatements.kt @@ -31,6 +31,30 @@ class PtAsmSub( ) : PtNamedNode(name, position), IPtSubroutine { class Address(val constbank: UByte?, var varbank: PtIdentifier?, val address: UInt) + + companion object { + fun builder(name: String, position: Position) = Builder(name, position) + } + + class Builder(val name: String, val position: Position) { + private var address: Address? = null + private var clobbers: Set = emptySet() + private var parameters: MutableList> = mutableListOf() + private var returns: MutableList> = mutableListOf() + private var inline: Boolean = false + + fun address(a: Address?) = apply { address = a } + fun clobbers(c: Set) = apply { clobbers = c } + fun parameters(p: Iterable>) = apply { parameters = p.toMutableList() } + fun parameters(vararg p: Pair) = apply { parameters = p.toMutableList() } + fun addParameter(reg: RegisterOrStatusflag, param: PtSubroutineParameter) = apply { parameters.add(reg to param) } + fun returns(r: Iterable>) = apply { returns = r.toMutableList() } + fun returns(vararg r: Pair) = apply { returns = r.toMutableList() } + fun addReturn(reg: RegisterOrStatusflag, type: DataType) = apply { returns.add(reg to type) } + fun inline(i: Boolean) = apply { inline = i } + + fun build() = PtAsmSub(name, address, clobbers, parameters, returns, inline, position) + } } @@ -46,6 +70,24 @@ class PtSub(name: String, position: Position) : PtNamedNode(name, position), IPt get() = children[0] as PtSubSignature override fun copy(): PtNode = PtSub(name, position) + + companion object { + fun builder(name: String, position: Position) = Builder(name, position) + } + + class Builder(val name: String, val position: Position) { + private var parameters: MutableList = mutableListOf() + private var returntypes: MutableList = mutableListOf() + + fun parameters(p: Iterable) = apply { parameters = p.toMutableList() } + fun parameters(vararg p: PtSubroutineParameter) = apply { parameters = p.toMutableList() } + fun addParameter(p: PtSubroutineParameter) = apply { parameters.add(p) } + fun returntypes(r: Iterable) = apply { returntypes = r.toMutableList() } + fun returntypes(vararg r: DataType) = apply { returntypes = r.toMutableList() } + fun addReturntype(t: DataType) = apply { returntypes.add(t) } + + fun build() = PtSub(name, parameters, returntypes, position) + } } @@ -321,6 +363,26 @@ class PtVariable( value?.let {it.parent=this} } + + companion object { + fun builder(name: String, type: DataType, position: Position) = Builder(name, type, position) + } + + class Builder(val name: String, val type: DataType, val position: Position) { + private var zeropage: ZeropageWish = ZeropageWish.DONTCARE + private var align: UInt = 0u + private var dirty: Boolean = false + private var value: PtExpression? = null + private var arraySize: UInt? = null + + fun zeropage(z: ZeropageWish) = apply { zeropage = z } + fun align(a: UInt) = apply { align = a } + fun dirty(d: Boolean) = apply { dirty = d } + fun value(v: PtExpression?) = apply { value = v } + fun arraySize(s: UInt?) = apply { arraySize = s } + + fun build() = PtVariable(name, type, zeropage, align, dirty, value, arraySize, position) + } } @@ -330,18 +392,60 @@ class PtConstant( val value: Double?, // either a constant number... val memorySlab: StMemorySlab?, // .. or a memory() allocation position: Position -) : PtNamedNode(name, position), IPtVariable +) : PtNamedNode(name, position), IPtVariable { // note: a constant is a value but IS NOT a PtExpression node; all constants must have been replaced by their actual value + companion object { + fun builder(name: String, type: DataType, position: Position) = Builder(name, type, position) + } + + class Builder(val name: String, val type: DataType, val position: Position) { + private var value: Double? = null + private var memorySlab: StMemorySlab? = null + + fun value(v: Double?) = apply { value = v } + fun memorySlab(s: StMemorySlab?) = apply { memorySlab = s } + + fun build() = PtConstant(name, type, value, memorySlab, position) + } +} + class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable { init { require(!type.isString) } + + companion object { + fun builder(name: String, type: DataType, address: UInt, position: Position) = Builder(name, type, address, position) + } + + class Builder(val name: String, val type: DataType, val address: UInt, val position: Position) { + private var arraySize: UInt? = null + + fun arraySize(s: UInt?) = apply { arraySize = s } + + fun build() = PtMemMapped(name, type, address, arraySize, position) + } } -class PtStructDecl(name: String, val fields: List>, position: Position) : PtNamedNode(name, position) +class PtStructDecl(name: String, val fields: List>, position: Position) : PtNamedNode(name, position) { + + companion object { + fun builder(name: String, position: Position) = Builder(name, position) + } + + class Builder(val name: String, val position: Position) { + private var fields: MutableList> = mutableListOf() + + fun fields(f: Iterable>) = apply { fields = f.toMutableList() } + fun fields(vararg f: Pair) = apply { fields = f.toMutableList() } + fun addField(type: DataType, name: String) = apply { fields.add(type to name) } + + fun build() = PtStructDecl(name, fields, position) + } +} class PtWhen(position: Position) : PtNode(position) { diff --git a/simpleAst/test/prog8tests/code/TestPtBuilders.kt b/simpleAst/test/prog8tests/code/TestPtBuilders.kt new file mode 100644 index 000000000..dfe220b82 --- /dev/null +++ b/simpleAst/test/prog8tests/code/TestPtBuilders.kt @@ -0,0 +1,94 @@ +package prog8tests.code + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.collections.shouldHaveSize +import prog8.code.ast.* +import prog8.code.core.* + +class TestPtBuilders : FunSpec({ + + test("PtVariable builder") { + val pos = Position.DUMMY + val variable = PtVariable.builder("x", DataType.UBYTE, pos) + .zeropage(ZeropageWish.REQUIRE_ZEROPAGE) + .align(2u) + .dirty(true) + .arraySize(10u) + .build() + + variable.name shouldBe "x" + variable.type shouldBe DataType.UBYTE + variable.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE + variable.align shouldBe 2u + variable.dirty shouldBe true + variable.arraySize shouldBe 10u + variable.position shouldBe pos + } + + test("PtAsmSub builder") { + val pos = Position.DUMMY + val sub = PtAsmSub.builder("my_asm_sub", pos) + .clobbers(setOf(CpuRegister.A, CpuRegister.X)) + .inline(true) + .addReturn(RegisterOrStatusflag(RegisterOrPair.A, null), DataType.UBYTE) + .build() + + sub.name shouldBe "my_asm_sub" + sub.clobbers shouldBe setOf(CpuRegister.A, CpuRegister.X) + sub.inline shouldBe true + sub.returns shouldHaveSize 1 + sub.returns[0].first.registerOrPair shouldBe RegisterOrPair.A + sub.returns[0].second shouldBe DataType.UBYTE + } + + test("PtConstant builder") { + val pos = Position.DUMMY + val constant = PtConstant.builder("MY_CONST", DataType.FLOAT, pos) + .value(3.14) + .build() + + constant.name shouldBe "MY_CONST" + constant.type shouldBe DataType.FLOAT + constant.value shouldBe 3.14 + constant.memorySlab shouldBe null + } + + test("PtMemMapped builder") { + val pos = Position.DUMMY + val mem = PtMemMapped.builder("SCREEN", DataType.UBYTE, 0x0400u, pos) + .arraySize(1000u) + .build() + + mem.name shouldBe "SCREEN" + mem.type shouldBe DataType.UBYTE + mem.address shouldBe 0x0400u + mem.arraySize shouldBe 1000u + } + + test("PtSub builder") { + val pos = Position.DUMMY + val sub = PtSub.builder("my_sub", pos) + .addReturntype(DataType.UWORD) + .addParameter(PtSubroutineParameter("p1", DataType.UBYTE, null, pos)) + .build() + + sub.name shouldBe "my_sub" + sub.signature.returns shouldBe listOf(DataType.UWORD) + sub.signature.children shouldHaveSize 1 + (sub.signature.children[0] as PtSubroutineParameter).name shouldBe "p1" + } + + test("PtStructDecl builder") { + val pos = Position.DUMMY + val struct = PtStructDecl.builder("Point", pos) + .addField(DataType.UBYTE, "x") + .addField(DataType.UBYTE, "y") + .build() + + struct.name shouldBe "Point" + struct.fields shouldHaveSize 2 + struct.fields[0] shouldBe (DataType.UBYTE to "x") + struct.fields[1] shouldBe (DataType.UBYTE to "y") + } +}) diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt index e15059470..0cd3d5789 100644 --- a/virtualmachine/test/TestVm.kt +++ b/virtualmachine/test/TestVm.kt @@ -14,20 +14,14 @@ class TestVm: FunSpec( { fun getTestOptions(): CompilationOptions { val target = VMTarget() - return CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - zpReserved = emptyList(), - zpAllowed = CompilationOptions.AllZeropageAllowed, - floats = true, - noSysInit = false, - romable = false, - compTarget = target, - compilerVersion ="99.99", - loadAddress = target.PROGRAM_LOAD_ADDRESS, - memtopAddress = 0xffffu - ) + return CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .floats(true) + .compilerVersion("99.99") + .loadAddress(target.PROGRAM_LOAD_ADDRESS) + .memtopAddress(0xffffu) + .build() } test("vm execution: empty program") {