mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
280 lines
12 KiB
Kotlin
280 lines
12 KiB
Kotlin
package prog8tests.compiler
|
|
|
|
import io.kotest.assertions.withClue
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.engine.spec.tempdir
|
|
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.VMTarget
|
|
import prog8.compiler.CompilationResult
|
|
import prog8.compiler.CompilerArguments
|
|
import prog8.compiler.compileProgram
|
|
import prog8tests.helpers.ErrorReporterForTests
|
|
import prog8tests.helpers.assumeReadableFile
|
|
import prog8tests.helpers.fixturesDir
|
|
import java.nio.file.Files
|
|
import java.nio.file.Path
|
|
import kotlin.io.path.*
|
|
|
|
|
|
private val minimalProgram = """
|
|
%zeropage basicsafe
|
|
%option no_sysinit
|
|
main {
|
|
sub start() {
|
|
cx16.r0++
|
|
}
|
|
}
|
|
"""
|
|
|
|
private fun writeSourceTo(dir: Path, source: String): Path {
|
|
val filepath = dir.resolve("testoutput.p8")
|
|
filepath.toFile().writeText(source)
|
|
return filepath
|
|
}
|
|
|
|
// local copy of the compileTheThing pattern: the shared compileText/compileFile helpers cannot pass an outputFile
|
|
private fun compileWithOutputFile(
|
|
filepath: Path,
|
|
target: ICompilationTarget,
|
|
outputDir: Path,
|
|
outputFile: Path,
|
|
assemble: Boolean = false
|
|
): CompilationResult? {
|
|
val args = CompilerArguments(
|
|
filepath,
|
|
optimize = false,
|
|
writeAssembly = true,
|
|
warnSymbolShadowing = false,
|
|
warnImplicitTypeCasts = false,
|
|
quietAll = true,
|
|
quietAssembler = true,
|
|
showTimings = false,
|
|
asmListfile = false,
|
|
includeSourcelines = false,
|
|
newCodegen = false,
|
|
dumpVariables = false,
|
|
dumpSymbols = false,
|
|
varsHighBank = null,
|
|
varsGolden = false,
|
|
varsAddress = null,
|
|
compilationTarget = target.name,
|
|
breakpointCpuInstruction = null,
|
|
printAst1 = false,
|
|
printAst2 = false,
|
|
ignoreFootguns = false,
|
|
profilingInstrumentation = false,
|
|
symbolDefs = emptyMap(),
|
|
outputDir = outputDir,
|
|
outputFile = outputFile,
|
|
errors = ErrorReporterForTests(),
|
|
assemble = assemble
|
|
)
|
|
return compileProgram(args)
|
|
}
|
|
|
|
class TestOutputFileName : FunSpec({
|
|
|
|
test("outputStemOf strips the last extension") {
|
|
outputStemOf(Path.of("game.pgz")) shouldBe "game"
|
|
outputStemOf(Path.of("my.game")) shouldBe "my"
|
|
}
|
|
|
|
test("outputStemOf keeps names without an extension unchanged") {
|
|
outputStemOf(Path.of("game")) shouldBe "game"
|
|
}
|
|
|
|
test("outputStemOf treats a leading dot as not an extension") {
|
|
outputStemOf(Path.of(".pgz")) shouldBe ".pgz"
|
|
}
|
|
|
|
test("programFile maps each OutputType to its legacy extension") {
|
|
val outputDir = tempdir().toPath()
|
|
fun options(output: OutputType) = CompilationOptions.builder(C64Target())
|
|
.output(output)
|
|
.outputDir(outputDir)
|
|
.build()
|
|
options(OutputType.PRG).programFile("prog") shouldBe outputDir.resolve("prog.prg")
|
|
options(OutputType.XEX).programFile("prog") shouldBe outputDir.resolve("prog.xex")
|
|
options(OutputType.RAW).programFile("prog") shouldBe outputDir.resolve("prog.bin")
|
|
options(OutputType.LIBRARY).programFile("prog") shouldBe outputDir.resolve("prog.bin")
|
|
options(OutputType.ELF).programFile("prog") shouldBe outputDir.resolve("prog.elf")
|
|
options(OutputType.AMIGAHUNK).programFile("prog") shouldBe outputDir.resolve("prog")
|
|
}
|
|
|
|
test("programFile special-cases VMTarget to the p8ir artifact") {
|
|
val outputDir = tempdir().toPath()
|
|
val options = CompilationOptions.builder(VMTarget())
|
|
.output(OutputType.ELF)
|
|
.outputDir(outputDir)
|
|
.build()
|
|
options.programFile("prog") shouldBe outputDir.resolve("prog.p8ir")
|
|
}
|
|
|
|
test("programFile enforces the .p8ir extension for -o on the virtual target") {
|
|
val tmp = tempdir().toPath()
|
|
val builder = { ir: Path ->
|
|
CompilationOptions.builder(VMTarget()).outputDir(tmp).outputFile(ir).build()
|
|
}
|
|
// the VM identifies the IR by its extension, so an extension-less -o must get one appended;
|
|
// only the exact lowercase ".p8ir" is recognized as already being an IR file
|
|
builder(tmp.resolve("game")).programFile("prog") shouldBe tmp.resolve("game.p8ir")
|
|
builder(tmp.resolve("game.p8ir")).programFile("prog") shouldBe tmp.resolve("game.p8ir")
|
|
builder(tmp.resolve("game.P8IR")).programFile("prog") shouldBe tmp.resolve("game.P8IR.p8ir")
|
|
builder(tmp.resolve("sub/game")).programFile("prog") shouldBe tmp.resolve("sub/game.p8ir")
|
|
|
|
// ... and the stem must follow the resolved name, so the aux files stay consistent with the artifact
|
|
OutputFiles.of(builder(tmp.resolve("game")), "prog").ir() shouldBe tmp.resolve("game.p8ir")
|
|
OutputFiles.of(builder(tmp.resolve("game")), "prog").stem shouldBe "game"
|
|
}
|
|
|
|
test("OutputFiles.of with -o resolves the artifact verbatim and all aux files from its stem") {
|
|
val tmp = tempdir().toPath()
|
|
val outDir = tmp.resolve("out")
|
|
val options = CompilationOptions.builder(Cx16Target())
|
|
.outputDir(tmp)
|
|
.outputFile(outDir.resolve("custom.pgz"))
|
|
.build()
|
|
val outs = OutputFiles.of(options, "prog")
|
|
outs.programFile shouldBe outDir.resolve("custom.pgz")
|
|
outs.stem shouldBe "custom"
|
|
outs.dir shouldBe outDir
|
|
outs.asm() shouldBe outDir.resolve("custom.asm")
|
|
outs.list() shouldBe outDir.resolve("custom.list")
|
|
outs.viceMonList() shouldBe outDir.resolve("custom.vice-mon-list")
|
|
outs.binFile() shouldBe outDir.resolve("custom.bin")
|
|
outs.ir() shouldBe outDir.resolve("custom.p8ir")
|
|
outs.bankedCalls() shouldBe outDir.resolve("custom.bankedcalls")
|
|
outs.obj() shouldBe outDir.resolve("custom.o")
|
|
outs.linkScript() shouldBe outDir.resolve("custom.link.ld")
|
|
}
|
|
|
|
test("viceMonList is derived from the stem, never from the program file name") {
|
|
val tmp = tempdir().toPath()
|
|
val outDir = tmp.resolve("out")
|
|
val options = CompilationOptions.builder(Cx16Target())
|
|
.outputDir(tmp)
|
|
.outputFile(outDir.resolve("custom.pgz"))
|
|
.build()
|
|
val outs = OutputFiles.of(options, "prog")
|
|
withClue("vice-mon-list must not keep the program file extension") {
|
|
outs.viceMonList() shouldNotBe outDir.resolve("custom.pgz.vice-mon-list")
|
|
}
|
|
}
|
|
|
|
test("OutputFiles.of with a bare -o filename resolves aux files relative to outputDir") {
|
|
val tmp = tempdir().toPath()
|
|
val options = CompilationOptions.builder(Cx16Target())
|
|
.outputDir(tmp)
|
|
.outputFile(Path.of("game.pgz"))
|
|
.build()
|
|
val outs = OutputFiles.of(options, "prog")
|
|
outs.programFile shouldBe Path.of("game.pgz")
|
|
outs.dir shouldBe tmp
|
|
outs.asm() shouldBe tmp.resolve("game.asm")
|
|
outs.ir() shouldBe tmp.resolve("game.p8ir")
|
|
}
|
|
|
|
test("legacy naming keeps the full dotted program stem when -o is not used") {
|
|
val tmp = tempdir().toPath()
|
|
val options = CompilationOptions.builder(Cx16Target()).outputDir(tmp).build()
|
|
val outs = OutputFiles.of(options, "my.game")
|
|
withClue("legacy stem must be used verbatim, not re-derived by outputStemOf") {
|
|
outs.stem shouldBe "my.game"
|
|
outs.asm() shouldBe tmp.resolve("my.game.asm")
|
|
outs.list() shouldBe tmp.resolve("my.game.list")
|
|
outs.viceMonList() shouldBe tmp.resolve("my.game.vice-mon-list")
|
|
outs.ir() shouldBe tmp.resolve("my.game.p8ir")
|
|
outs.bankedCalls() shouldBe tmp.resolve("my.game.bankedcalls")
|
|
outs.obj() shouldBe tmp.resolve("my.game.o")
|
|
outs.linkScript() shouldBe tmp.resolve("my.game.link.ld")
|
|
}
|
|
outs.asm() shouldNotBe tmp.resolve("my.asm")
|
|
}
|
|
|
|
test("legacy program file path is unchanged without -o") {
|
|
val tmp = tempdir().toPath()
|
|
val options = CompilationOptions.builder(Cx16Target()).outputDir(tmp).build()
|
|
OutputFiles.of(options, "prog").programFile shouldBe tmp.resolve("prog.prg")
|
|
}
|
|
|
|
test("compiling with -o writes the aux files next to the artifact") {
|
|
val outputDir = tempdir().toPath()
|
|
val outDir = outputDir.resolve("out")
|
|
outDir.createDirectories()
|
|
val outputFile = outDir.resolve("custom.pgz")
|
|
val filepath = writeSourceTo(outputDir, minimalProgram)
|
|
val result = compileWithOutputFile(filepath, Cx16Target(), outputDir, outputFile)
|
|
result shouldNotBe null
|
|
withClue("no assembler ran, so the artifact must not exist yet") {
|
|
outputFile.exists() shouldBe false
|
|
}
|
|
withClue("the aux .asm must be named after the -o stem") {
|
|
outDir.resolve("custom.asm").exists() shouldBe true
|
|
}
|
|
withClue("no source-named .asm may appear in the output directory") {
|
|
outDir.resolve("testoutput.asm").exists() shouldBe false
|
|
val topLevelAsms = Files.list(outputDir).use { stream ->
|
|
stream.filter { it.name.endsWith(".asm") }.toList()
|
|
}
|
|
topLevelAsms shouldBe emptyList()
|
|
}
|
|
result!!.compilationOptions.outputFile shouldBe outputFile
|
|
}
|
|
|
|
test("compiling with -o and assemble=true writes the final artifact to the -o path") {
|
|
val outputDir = tempdir().toPath()
|
|
val outDir = outputDir.resolve("out")
|
|
outDir.createDirectories()
|
|
val outputFile = outDir.resolve("custom.prg")
|
|
val filepath = writeSourceTo(outputDir, minimalProgram)
|
|
val result = compileWithOutputFile(filepath, Cx16Target(), outputDir, outputFile, assemble = true)
|
|
result shouldNotBe null
|
|
withClue("the assembled artifact must exist at the -o path") {
|
|
outputFile.exists() shouldBe true
|
|
outputFile.isRegularFile() shouldBe true
|
|
}
|
|
withClue("the aux .asm must be named after the -o stem") {
|
|
outDir.resolve("custom.asm").exists() shouldBe true
|
|
}
|
|
}
|
|
|
|
test("%asmbinary under -o keeps the .binary path relative to the generated .asm") {
|
|
val outputDir = tempdir().toPath()
|
|
val outputFile = outputDir.resolve("game.prg")
|
|
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryFromSameFolder.p8")
|
|
val result = compileWithOutputFile(p8Path, Cx16Target(), outputDir, outputFile)
|
|
result shouldNotBe null
|
|
val asmPath = OutputFiles.of(result!!.compilationOptions, result.compilerAst.name).asm()
|
|
asmPath.exists() shouldBe true
|
|
val asmLines = asmPath.readLines().map { it.trim() }
|
|
val binaryLine = asmLines.first { it.contains(".binary") }
|
|
val binPath = binaryLine.substringAfter(".binary \"").substringBefore('"')
|
|
withClue("the .binary path must be relative to the .asm file, not absolute: $binaryLine") {
|
|
Path.of(binPath).isAbsolute shouldBe false
|
|
}
|
|
}
|
|
|
|
test("virtual target -o writes the .p8ir artifact") {
|
|
val outputDir = tempdir().toPath()
|
|
val outputFile = outputDir.resolve("ir.p8ir")
|
|
val filepath = writeSourceTo(outputDir, minimalProgram)
|
|
val result = compileWithOutputFile(filepath, VMTarget(), outputDir, outputFile, assemble = true)
|
|
result shouldNotBe null
|
|
outputFile.exists() shouldBe true
|
|
outputFile.isRegularFile() shouldBe true
|
|
}
|
|
|
|
test("virtual target -o without extension still writes a .p8ir artifact") {
|
|
val outputDir = tempdir().toPath()
|
|
val filepath = writeSourceTo(outputDir, minimalProgram)
|
|
val result = compileWithOutputFile(filepath, VMTarget(), outputDir, outputDir.resolve("ir"), assemble = true)
|
|
result shouldNotBe null
|
|
outputDir.resolve("ir.p8ir").exists() shouldBe true
|
|
outputDir.resolve("ir").exists() shouldBe false
|
|
}
|
|
})
|