diff --git a/README.md b/README.md index a658e9960..77f842263 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 6d2576210..d172ee50c 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -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) { 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) { 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) { 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) } diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index 6a4d6fbcb..4cbee751d 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -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) + + fun compileProgram(filepath: Path, optimize: Boolean, optimizeInlining: Boolean, - writeAssembly: Boolean): Pair { + writeAssembly: Boolean): CompilationResult { lateinit var programAst: Program var programName: String? = null + var importedFiles: List = 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) { diff --git a/compiler/src/prog8/repl/ReplMain.kt b/compiler/src/prog8/repl/ReplMain.kt new file mode 100644 index 000000000..3ed9d0cbd --- /dev/null +++ b/compiler/src/prog8/repl/ReplMain.kt @@ -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() + } + +} diff --git a/docs/source/building.rst b/docs/source/building.rst index 1d9aa0ca8..3d4b981ef 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -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) diff --git a/docs/source/index.rst b/docs/source/index.rst index e9c5b5d89..b91d0dbc8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -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. diff --git a/examples/tehtriz.p8 b/examples/tehtriz.p8 index bf7161e33..dd5bd0f07 100644 --- a/examples/tehtriz.p8 +++ b/examples/tehtriz.p8 @@ -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 diff --git a/examples/test.p8 b/examples/test.p8 index 71cfec818..2ba94d442 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 + } } diff --git a/gradle.properties b/gradle.properties index 7eb0c557d..3e6e032d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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