mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
added continuous compilation mode (file watching)
This commit is contained in:
parent
126c2162f1
commit
7ddc01f883
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
36
compiler/src/prog8/repl/ReplMain.kt
Normal file
36
compiler/src/prog8/repl/ReplMain.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user