diff --git a/.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml b/.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml new file mode 100644 index 000000000..7fee0aedc --- /dev/null +++ b/.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/compiler/build.gradle b/compiler/build.gradle index 4c5cfd5e8..d68a4cb86 100644 --- a/compiler/build.gradle +++ b/compiler/build.gradle @@ -20,6 +20,7 @@ repositories { mavenLocal() mavenCentral() jcenter() + maven { url "https://dl.bintray.com/orangy/maven/" } } 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.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' + compile 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5' } compileKotlin { diff --git a/compiler/compiler.iml b/compiler/compiler.iml index 9df271cf3..10103c91c 100644 --- a/compiler/compiler.iml +++ b/compiler/compiler.iml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar b/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar new file mode 100644 index 000000000..2edc1e111 Binary files /dev/null and b/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar differ diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 6a9beef3e..6e3a5535a 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -1,5 +1,6 @@ package prog8 +import kotlinx.cli.* import prog8.ast.base.AstException import prog8.compiler.CompilationResult import prog8.compiler.compileProgram @@ -14,11 +15,8 @@ import kotlin.system.exitProcess fun main(args: Array) { - printSoftwareHeader("compiler") - if (args.isEmpty()) - usage() compileMain(args) } @@ -30,46 +28,37 @@ internal fun printSoftwareHeader(what: String) { private fun compileMain(args: Array) { - var emulatorToStart = "" - var moduleFile = "" - var writeAssembly = true - var optimize = true - var launchAstVm = false - var watchMode = false - for (arg in args) { - if(arg=="-emu") - emulatorToStart = "x64" - else if(arg=="-emu2") - emulatorToStart = "x64sc" - else if(arg=="-noasm") - writeAssembly = false - else if(arg=="-noopt") - 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() + val cli = CommandLineInterface("prog8compiler") + val startEmulator1 by cli.flagArgument("-emu", "auto-start the 'x64' C-64 emulator after successful compilation") + val startEmulator2 by cli.flagArgument("-emu2", "auto-start the 'x64sc' C-64 emulator after successful compilation") + val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".") + val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code") + val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations") + val launchSimulator by cli.flagArgument("-sim", "launch the prog8 virtual machine/simulator after compilation") + val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes)") + val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1) + + try { + cli.parse(args) + } catch (e: Exception) { + exitProcess(1) } - if(watchMode) { - if(moduleFile.isBlank()) - usage() + val outputPath = Paths.get(outputDir) + if(!outputPath.toFile().isDirectory) { + System.err.println("Output path doesn't exist") + exitProcess(1) + } + if(watchMode && moduleFiles.size<=1) { val watchservice = FileSystems.getDefault().newWatchService() while(true) { - val filepath = Paths.get(moduleFile).normalize() + val filepath = Paths.get(moduleFiles.single()).normalize() println("Continuous watch mode active. Main module: $filepath") try { - val compilationResult = compileProgram(filepath, optimize, writeAssembly) + val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath) println("Imported files (now watching:)") for (importedFile in compilationResult.importedFiles) { print(" ") @@ -90,51 +79,37 @@ private fun compileMain(args: Array) { } } else { - if(moduleFile.isBlank()) - usage() - - val filepath = Paths.get(moduleFile).normalize() - val compilationResult: CompilationResult - - try { - compilationResult = compileProgram(filepath, optimize, writeAssembly) - if(!compilationResult.success) + for(filepathRaw in moduleFiles) { + val filepath = Paths.get(filepathRaw).normalize() + val compilationResult: CompilationResult + try { + compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath) + if(!compilationResult.success) + exitProcess(1) + } catch (x: ParsingFailedError) { exitProcess(1) - } catch (x: ParsingFailedError) { - exitProcess(1) - } catch (x: AstException) { - exitProcess(1) - } + } catch (x: AstException) { + exitProcess(1) + } - if (launchAstVm) { - println("\nLaunching AST-based vm...") - val vm = AstVm(compilationResult.programAst) - vm.run() - } + if (launchSimulator) { + println("\nLaunching AST-based simulator...") + val vm = AstVm(compilationResult.programAst) + vm.run() + } - if (emulatorToStart.isNotEmpty()) { - if (compilationResult.programName.isEmpty()) - println("\nCan't start emulator because no program was assembled.") - else { - println("\nStarting C-64 emulator $emulatorToStart...") - val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", - "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") - val process = ProcessBuilder(cmdline).inheritIO().start() - process.waitFor() + if (startEmulator1 || startEmulator2) { + if (compilationResult.programName.isEmpty()) + println("\nCan't start emulator because no program was assembled.") + else { + val emulator = if(startEmulator1) "x64" else "x64sc" + 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") + val process = ProcessBuilder(cmdline).inheritIO().start() + 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) -} diff --git a/compiler/src/prog8/ast/base/ErrorReporting.kt b/compiler/src/prog8/ast/base/ErrorReporting.kt index 19b4365ec..df3191f03 100644 --- a/compiler/src/prog8/ast/base/ErrorReporting.kt +++ b/compiler/src/prog8/ast/base/ErrorReporting.kt @@ -5,7 +5,7 @@ import prog8.parser.ParsingFailedError fun printErrors(errors: List, moduleName: String) { val reportedMessages = mutableSetOf() - print("\u001b[91m") // bright red + System.err.print("\u001b[91m") // bright red errors.forEach { val msg = it.toString() if(msg !in reportedMessages) { @@ -13,7 +13,7 @@ fun printErrors(errors: List, moduleName: String) { reportedMessages.add(msg) } } - print("\u001b[0m") // reset color + System.err.print("\u001b[0m") // reset color if(reportedMessages.isNotEmpty()) throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") } diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index 304e34c7c..b2378292e 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -25,7 +25,8 @@ class CompilationResult(val success: Boolean, fun compileProgram(filepath: Path, optimize: Boolean, - writeAssembly: Boolean): CompilationResult { + writeAssembly: Boolean, + outputDir: Path): CompilationResult { lateinit var programAst: Program var programName: String? = null @@ -102,7 +103,7 @@ fun compileProgram(filepath: Path, // asm generation directly from the Ast, no need for intermediate code val zeropage = MachineDefinition.C64Zeropage(compilerOptions) programAst.anonscopeVarsCleanup() - val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize) + val assembly = AsmGen(programAst, compilerOptions, zeropage, outputDir).compileToAssembly(optimize) assembly.assemble(compilerOptions) programName = assembly.name } diff --git a/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt b/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt index d4de7ffcf..89ccc2b85 100644 --- a/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt +++ b/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt @@ -2,12 +2,14 @@ package prog8.compiler.target.c64 import prog8.compiler.CompilationOptions import prog8.compiler.OutputType -import java.io.File +import java.nio.file.Path import kotlin.system.exitProcess -class AssemblyProgram(val name: String) { - private val assemblyFile = "$name.asm" - private val viceMonListFile = "$name.vice-mon-list" +class AssemblyProgram(val name: String, val outputDir: Path) { + private val assemblyFile = outputDir.resolve("$name.asm") + 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 { // 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 val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--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) { OutputType.PRG -> { command.add("--cbm-prg") println("\nCreating C-64 prg.") - "$name.prg" + prgFile } OutputType.RAW -> { command.add("--nostart") 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 result = proc.waitFor() @@ -57,7 +60,7 @@ class AssemblyProgram(val name: String) { // builds list of breakpoints, appends to monitor list file val breakpoints = mutableListOf() 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) if(match!=null) 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(1, "; $num breakpoints have been defined") breakpoints.add(2, "del") - File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n") + viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n") } } diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index a95130c90..76ea1f304 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -14,8 +14,8 @@ import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.Petscii import prog8.functions.BuiltinFunctions import prog8.functions.FunctionSignature -import java.io.File import java.math.RoundingMode +import java.nio.file.Path import java.util.* import kotlin.math.absoluteValue @@ -25,7 +25,8 @@ internal class AssemblyError(msg: String) : RuntimeException(msg) internal class AsmGen(val program: Program, val options: CompilationOptions, - val zeropage: Zeropage) { + val zeropage: Zeropage, + val outputDir: Path) { private val assemblyLines = mutableListOf() private val globalFloatConsts = mutableMapOf() // 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) } } - return AssemblyProgram(program.name) + return AssemblyProgram(program.name, outputDir) } private fun header() { diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index 6125a0eff..5a14d83b0 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -19,7 +19,6 @@ import prog8.vm.RuntimeValueNumeric import java.io.CharConversionException import kotlin.test.* - @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TestCompiler { @Test @@ -54,7 +53,6 @@ class TestCompiler { assertFailsWith { 65536L.toHex() } } - @Test fun testFloatToMflpt5() { assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) diff --git a/docs/source/building.rst b/docs/source/building.rst index d1de6909f..faff28a29 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -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. 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 ------------------------