added continuous compilation mode (file watching)

This commit is contained in:
Irmen de Jong 2019-08-05 23:36:24 +02:00
parent 126c2162f1
commit 7ddc01f883
9 changed files with 185 additions and 52 deletions

View File

@ -17,7 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- conditional branches
@ -33,7 +33,7 @@ which aims to provide many conveniences over raw assembly code (even when using
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (less than 1 second)
- quick compilation times (around a second, and less than 0.1 seconds when using the continuous compilation mode)
- option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly

View File

@ -1,8 +1,14 @@
package prog8
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm
import java.nio.file.Paths
import prog8.repl.Repl
import java.lang.Exception
import java.nio.file.*
import java.util.*
import kotlin.system.exitProcess
@ -29,6 +35,8 @@ private fun compileMain(args: Array<String>) {
var optimize = true
var optimizeInlining = true
var launchAstVm = false
var launchRepl = false
var watchMode = false
for (arg in args) {
if(arg=="-emu")
emulatorToStart = "x64"
@ -42,33 +50,81 @@ private fun compileMain(args: Array<String>) {
optimizeInlining = false
else if(arg=="-avm")
launchAstVm = true
else if(arg=="-repl")
launchRepl = true
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
}
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
if(watchMode) {
if(moduleFile.isBlank())
usage()
val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining, writeAssembly)
val watchservice = FileSystems.getDefault().newWatchService()
if(launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(programAst)
vm.run()
}
while(true) {
val filepath = Paths.get(moduleFile).normalize()
println("Continuous watch mode active. Main module: $filepath")
if(emulatorToStart.isNotEmpty()) {
if(programName==null)
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
try {
val compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
println(importedFile)
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("${Date()}: Waiting for file changes.")
val event = watchservice.take()
for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path
println(" change detected: ${changedPath}")
}
event.reset()
println("\u001b[H\u001b[2J") // clear the screen
} catch (x: Exception) {
throw x
}
}
} else if(launchRepl) {
val repl = Repl()
repl.loop()
} else {
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: AstException) {
exitProcess(1)
}
if (launchAstVm) {
println("\nLaunching AST-based vm...")
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()
}
}
}
}
@ -76,12 +132,14 @@ private fun compileMain(args: Array<String>) {
private fun usage() {
System.err.println("Missing argument(s):")
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(" [-noasm] don't create assembly code")
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-nooptinline] don't perform subroutine inlining 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(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-repl] launch the prog8 REPL (interactive mode)")
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
}

View File

@ -14,15 +14,20 @@ import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
class CompilationResult(val programAst: Program, val programName: String, val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean, optimizeInlining: Boolean,
writeAssembly: Boolean): Pair<Program, String?> {
writeAssembly: Boolean): CompilationResult {
lateinit var programAst: Program
var programName: String? = null
var importedFiles: List<Path> = emptyList()
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
@ -30,6 +35,8 @@ fun compileProgram(filepath: Path,
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
@ -60,7 +67,7 @@ fun compileProgram(filepath: Path,
programAst.removeNopsFlattenAnonScopes()
// if you want to print the AST, do it before shuffling the statements around below
printAst(programAst)
//printAst(programAst)
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
@ -88,7 +95,7 @@ fun compileProgram(filepath: Path,
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
printAst(programAst)
//printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
@ -105,12 +112,10 @@ fun compileProgram(filepath: Path,
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
@ -124,7 +129,7 @@ fun compileProgram(filepath: Path,
System.out.flush()
throw x
}
return Pair(programAst, programName)
return CompilationResult(programAst, programName ?: "", importedFiles)
}
fun printAst(programAst: Program) {

View File

@ -0,0 +1,36 @@
package prog8.repl
import prog8.compiler.compileProgram
import prog8.compiler.printAst
import java.io.File
import java.nio.file.Path
class Repl() {
fun loop() {
println("~~totally unfinished experimental REPL~~\n")
while(true) {
print("> ")
val input = readLine() ?: break
val replmodule = createReplModule(input)
val compilationResult = compileProgram(replmodule, false, false, false)
printAst(compilationResult.programAst)
println("")
}
}
private fun createReplModule(input: String): Path {
val replmodule = File("replmodule.p8")
replmodule.writeText("""
%option enable_floats
main {
sub start() {
${input.trim()}
}
}
""")
return replmodule.toPath()
}
}

View File

@ -87,6 +87,13 @@ If you use the option to let the compiler auto-start a C-64 emulator, it will do
a successful compilation. This will load your program and the symbol and breakpoint lists
(for the machine code monitor) into the emulator.
Continuous compilation mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Almost instant compilation times (<0.1 second) can be achieved when using the continuous compilation mode.
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.
Module source code files
------------------------
@ -101,6 +108,13 @@ They are embedded into the packaged release version of the compiler so you don't
where they are, but their names are still reserved.
User defined library files
^^^^^^^^^^^^^^^^^^^^^^^^^^
You can create library files yourself too that can be shared among programs.
You can tell the compiler where it should look for these files, by setting the java command line property ``prog8.libdir``
or by setting the ``PROG8_LIBDIR`` environment variable to the correct directory.
.. _debugging:
Debugging (with Vice)

View File

@ -140,7 +140,8 @@ Design principles and features
- 'One statement per line' code style, resulting in clear readable programs.
- Modular programming and scoping via modules, code blocks, and subroutines.
- Provide high level programming constructs but stay close to the metal;
still able to directly use memory addresses, CPU registers and ROM subroutines
still able to directly use memory addresses, CPU registers and ROM subroutines,
and inline assembly to have full control when every cycle or byte matters
- Arbitrary number of subroutine parameters (constrained only by available memory)
- Complex nested expressions are possible
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.

View File

@ -189,8 +189,9 @@ waitkey:
; check if line(s) are full -> flash/clear line(s) + add score + move rest down
ubyte[boardHeight] complete_lines
ubyte num_lines=0
ubyte linepos
memset(complete_lines, len(complete_lines), 0)
for ubyte linepos in boardOffsetY to boardOffsetY+boardHeight-1 {
for linepos in boardOffsetY to boardOffsetY+boardHeight-1 {
if blocklogic.isLineFull(linepos) {
complete_lines[num_lines]=linepos
num_lines++
@ -208,7 +209,7 @@ waitkey:
; slight delay to flash the line
}
c64.TIME_LO=0
for ubyte linepos in complete_lines
for linepos in complete_lines
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
lines += num_lines

View File

@ -1,27 +1,45 @@
%import c64utils
%import c64flt
%option enable_floats
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
sub start() {
byte bb
ubyte ub
word ww
uword uw
float fl
bb = 10*bb
ub = 12*ub
ww = 15*ww
uw = 20*uw
fl = 20*fl
sub start() {
memset(sieve, 256, false)
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
ubyte prime = find_next_prime()
if prime==0
break
c64scr.print_ub(prime)
c64scr.print(", ")
amount++
}
c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
}
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
}
return candidate_prime
}
}

View File

@ -1,7 +1,7 @@
org.gradle.caching=true
org.gradle.console=rich
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048M
org.gradle.jvmargs=-Xmx2500M
org.gradle.daemon=true
kotlinVersion=1.3.41