added output directory command line option

improved cli parser by using kotlinx.cli
This commit is contained in:
Irmen de Jong 2019-08-23 00:07:49 +02:00
parent 2d768c3f28
commit aa94300bdd
11 changed files with 93 additions and 96 deletions

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -20,6 +20,7 @@ repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter() jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
} }
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
@ -35,8 +36,8 @@ dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
compile 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
} }
compileKotlin { compileKotlin {

View File

@ -14,5 +14,6 @@
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" /> <orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" /> <orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" /> <orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
</component> </component>
</module> </module>

Binary file not shown.

View File

@ -1,5 +1,6 @@
package prog8 package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
@ -14,11 +15,8 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) { fun main(args: Array<String>) {
printSoftwareHeader("compiler") printSoftwareHeader("compiler")
if (args.isEmpty())
usage()
compileMain(args) compileMain(args)
} }
@ -30,46 +28,37 @@ internal fun printSoftwareHeader(what: String) {
private fun compileMain(args: Array<String>) { private fun compileMain(args: Array<String>) {
var emulatorToStart = "" val cli = CommandLineInterface("prog8compiler")
var moduleFile = "" val startEmulator1 by cli.flagArgument("-emu", "auto-start the 'x64' C-64 emulator after successful compilation")
var writeAssembly = true val startEmulator2 by cli.flagArgument("-emu2", "auto-start the 'x64sc' C-64 emulator after successful compilation")
var optimize = true val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
var launchAstVm = false val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
var watchMode = false val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
for (arg in args) { val launchSimulator by cli.flagArgument("-sim", "launch the prog8 virtual machine/simulator after compilation")
if(arg=="-emu") val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes)")
emulatorToStart = "x64" val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
else if(arg=="-emu2")
emulatorToStart = "x64sc" try {
else if(arg=="-noasm") cli.parse(args)
writeAssembly = false } catch (e: Exception) {
else if(arg=="-noopt") exitProcess(1)
optimize = false
else if(arg=="-sim") {
launchAstVm = true
writeAssembly = false
emulatorToStart = ""
}
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
} }
if(watchMode) { val outputPath = Paths.get(outputDir)
if(moduleFile.isBlank()) if(!outputPath.toFile().isDirectory) {
usage() System.err.println("Output path doesn't exist")
exitProcess(1)
}
if(watchMode && moduleFiles.size<=1) {
val watchservice = FileSystems.getDefault().newWatchService() val watchservice = FileSystems.getDefault().newWatchService()
while(true) { while(true) {
val filepath = Paths.get(moduleFile).normalize() val filepath = Paths.get(moduleFiles.single()).normalize()
println("Continuous watch mode active. Main module: $filepath") println("Continuous watch mode active. Main module: $filepath")
try { try {
val compilationResult = compileProgram(filepath, optimize, writeAssembly) val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
println("Imported files (now watching:)") println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) { for (importedFile in compilationResult.importedFiles) {
print(" ") print(" ")
@ -90,14 +79,11 @@ private fun compileMain(args: Array<String>) {
} }
} else { } else {
if(moduleFile.isBlank()) for(filepathRaw in moduleFiles) {
usage() val filepath = Paths.get(filepathRaw).normalize()
val filepath = Paths.get(moduleFile).normalize()
val compilationResult: CompilationResult val compilationResult: CompilationResult
try { try {
compilationResult = compileProgram(filepath, optimize, writeAssembly) compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
if(!compilationResult.success) if(!compilationResult.success)
exitProcess(1) exitProcess(1)
} catch (x: ParsingFailedError) { } catch (x: ParsingFailedError) {
@ -106,35 +92,24 @@ private fun compileMain(args: Array<String>) {
exitProcess(1) exitProcess(1)
} }
if (launchAstVm) { if (launchSimulator) {
println("\nLaunching AST-based vm...") println("\nLaunching AST-based simulator...")
val vm = AstVm(compilationResult.programAst) val vm = AstVm(compilationResult.programAst)
vm.run() vm.run()
} }
if (emulatorToStart.isNotEmpty()) { if (startEmulator1 || startEmulator2) {
if (compilationResult.programName.isEmpty()) if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
else { else {
println("\nStarting C-64 emulator $emulatorToStart...") val emulator = if(startEmulator1) "x64" else "x64sc"
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start() val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor() process.waitFor()
} }
} }
} }
} }
private fun usage() {
System.err.println("Missing argument(s):")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
System.err.println(" [-sim] launch the prog8 virtual machine/simulator after compilation")
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
} }

View File

@ -5,7 +5,7 @@ import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) { fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>() val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red System.err.print("\u001b[91m") // bright red
errors.forEach { errors.forEach {
val msg = it.toString() val msg = it.toString()
if(msg !in reportedMessages) { if(msg !in reportedMessages) {
@ -13,7 +13,7 @@ fun printErrors(errors: List<Any>, moduleName: String) {
reportedMessages.add(msg) reportedMessages.add(msg)
} }
} }
print("\u001b[0m") // reset color System.err.print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty()) if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
} }

View File

@ -25,7 +25,8 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path, fun compileProgram(filepath: Path,
optimize: Boolean, optimize: Boolean,
writeAssembly: Boolean): CompilationResult { writeAssembly: Boolean,
outputDir: Path): CompilationResult {
lateinit var programAst: Program lateinit var programAst: Program
var programName: String? = null var programName: String? = null
@ -102,7 +103,7 @@ fun compileProgram(filepath: Path,
// asm generation directly from the Ast, no need for intermediate code // asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions) val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup() programAst.anonscopeVarsCleanup()
val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize) val assembly = AsmGen(programAst, compilerOptions, zeropage, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
programName = assembly.name programName = assembly.name
} }

View File

@ -2,12 +2,14 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType import prog8.compiler.OutputType
import java.io.File import java.nio.file.Path
import kotlin.system.exitProcess import kotlin.system.exitProcess
class AssemblyProgram(val name: String) { class AssemblyProgram(val name: String, val outputDir: Path) {
private val assemblyFile = "$name.asm" private val assemblyFile = outputDir.resolve("$name.asm")
private val viceMonListFile = "$name.vice-mon-list" private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
companion object { companion object {
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names // 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
@ -27,21 +29,22 @@ class AssemblyProgram(val name: String) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps // add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch", "-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor") "--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when(options.output) { val outFile = when(options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
println("\nCreating C-64 prg.") println("\nCreating C-64 prg.")
"$name.prg" prgFile
} }
OutputType.RAW -> { OutputType.RAW -> {
command.add("--nostart") command.add("--nostart")
println("\nCreating raw binary.") println("\nCreating raw binary.")
"$name.bin" binFile
} }
} }
command.addAll(listOf("--output", outFile, assemblyFile)) command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
println("ASM COMMAND: $command") // TODO
val proc = ProcessBuilder(command).inheritIO().start() val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor() val result = proc.waitFor()
@ -57,7 +60,7 @@ class AssemblyProgram(val name: String) {
// builds list of breakpoints, appends to monitor list file // builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>() val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in File(viceMonListFile).readLines()) { for(line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line) val match = pattern.matchEntire(line)
if(match!=null) if(match!=null)
breakpoints.add("break \$" + match.groupValues[1]) breakpoints.add("break \$" + match.groupValues[1])
@ -66,6 +69,6 @@ class AssemblyProgram(val name: String) {
breakpoints.add(0, "; vice monitor breakpoint list now follows") breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined") breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del") breakpoints.add(2, "del")
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n") viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
} }
} }

View File

@ -14,8 +14,8 @@ import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature import prog8.functions.FunctionSignature
import java.io.File
import java.math.RoundingMode import java.math.RoundingMode
import java.nio.file.Path
import java.util.* import java.util.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -25,7 +25,8 @@ internal class AssemblyError(msg: String) : RuntimeException(msg)
internal class AsmGen(val program: Program, internal class AsmGen(val program: Program,
val options: CompilationOptions, val options: CompilationOptions,
val zeropage: Zeropage) { val zeropage: Zeropage,
val outputDir: Path) {
private val assemblyLines = mutableListOf<String>() private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname) private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
@ -62,11 +63,12 @@ internal class AsmGen(val program: Program,
} }
} }
File("${program.name}.asm").printWriter().use { val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) } for (line in assemblyLines) { it.println(line) }
} }
return AssemblyProgram(program.name) return AssemblyProgram(program.name, outputDir)
} }
private fun header() { private fun header() {

View File

@ -19,7 +19,6 @@ import prog8.vm.RuntimeValueNumeric
import java.io.CharConversionException import java.io.CharConversionException
import kotlin.test.* import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompiler { class TestCompiler {
@Test @Test
@ -54,7 +53,6 @@ class TestCompiler {
assertFailsWith<CompilerException> { 65536L.toHex() } assertFailsWith<CompilerException> { 65536L.toHex() }
} }
@Test @Test
fun testFloatToMflpt5() { fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))

View File

@ -94,6 +94,13 @@ Start the compiler with the ``-watch`` argument to enable this.
It will compile your program and then instead of exiting, it waits for any changes in the module source files. It will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again. As soon as a change happens, the program gets compiled again.
Other options
^^^^^^^^^^^^^
There's an option to specify the output directory if you're not happy with the default (the current working directory).
Also it is possible to specify more than one main module to compile:
this can be useful to quickly recompile multiple separate programs quickly.
(compiling in a batch like this is a lot faster than invoking the compiler again once per main file)
Module source code files Module source code files
------------------------ ------------------------