From 919cc25579e2fdac942e9deaaf2b8c8d36c25a31 Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Thu, 29 Jun 2023 13:57:50 +0200 Subject: [PATCH] Get rid of REPL + minor refactorings --- README.md | 22 ++-- src/backend/{ => bytecode}/vm.nim | 12 +- src/config.nim | 19 ++- src/frontend/compiler/compiler.nim | 1 - src/main.nim | 196 ++++++----------------------- 5 files changed, 59 insertions(+), 191 deletions(-) rename src/backend/{ => bytecode}/vm.nim (99%) diff --git a/README.md b/README.md index 6e6fe2c..7c1ae5f 100644 --- a/README.md +++ b/README.md @@ -114,21 +114,13 @@ out for yourself. Fortunately, the process is quite straightforward: - First, you're gonna have to install [Nim](https://nim-lang.org/), the language peon is written in. I highly recommend using [choosenim](https://github.com/dom96/choosenim) to manage your Nim installations as it makes switching between them and updating them a breeze -- Once Nim is installed, you should install [jale](https://git.nocturn9x.space/japl/jale), peon's custom line editor - library written by our beloved [Art](https://git.nocturn9x.space/art). This is needed for the REPL to work: just clone the repository, `cd` into it and run `nimble install`; this will install the library on your - system so that the Nim compiler can find it later -- After jale has been installed, clone this repository and run the REPL with `nim r src/main` (in the appropriate - directory of course). If you want to do more than play around in the REPL, I recommend compiling peon in release - mode with `nim -d:release --passC:"-flto" -o:peon`, which should produce a `peon` binary ready for you to play with - (if your C toolchain doesn't support LTO then you can just omit the `--passC` option, although that would be pretty weird - for a modern linker) +- Then, clone this repository and compile peon in release mode with `nim c -d:release --passC:"-flto" -o:peon src/main`, which should produce`peon` binary + ready for you to play with (if your C toolchain doesn't support LTO then you can just omit the `--passC` option, although that would be pretty weird for + a modern linker) - If you want to move the executable to a different directory (say, into your `PATH`), you should copy peon's standard - library (found in `/src/peon/stdlib`) into a known folder and edit the `moduleLookupPaths` variable inside `src/config.nim` - by adding said folder to it so that the peon compiler knows where to find modules when you `import std;`. Hopefully I will - automate this soon, but as of right now the work is all manual (and it's part of the fun, IMHO ;)) + library (found in `/src/peon/stdlib`) into a known folder, edit the `moduleLookupPaths` variable inside `src/config.nim` + by adding said folder to it so that the peon compiler knows where to find modules when you `import std;` and then recompile + peon. Hopefully I will automate this soon, but as of right now the work is all manual -__Note__: On Linux, peon will also look into `~/.local/peon/stdlib` - -If you've done everything right, you should be able to run `peon` in your terminal and have it drop you into the REPL. Good -luck and have fun! \ No newline at end of file +__Note__: On Linux, peon will also look into `~/.local/peon/stdlib` by default, so you can just create the `~/.local/peon` folder and copy `src/peon/stdlib` there \ No newline at end of file diff --git a/src/backend/vm.nim b/src/backend/bytecode/vm.nim similarity index 99% rename from src/backend/vm.nim rename to src/backend/bytecode/vm.nim index 41a9700..3baf998 100644 --- a/src/backend/vm.nim +++ b/src/backend/bytecode/vm.nim @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. ## The Peon runtime environment -import ../config +import config # Sorry, but there only is enough space # for one GC in this VM :( @@ -30,16 +30,16 @@ import std/strutils import std/sets import std/monotimes - -import ../frontend/compiler/targets/bytecode/opcodes -import ../frontend/compiler/targets/bytecode/util/multibyte - - when debugVM or debugMem or debugGC or debugAlloc: import std/strformat import std/sequtils import std/terminal + +import frontend/compiler/targets/bytecode/opcodes +import frontend/compiler/targets/bytecode/util/multibyte + + when debugVM: proc clearerr(stream: File) {.header: "stdio.h", importc.} diff --git a/src/config.nim b/src/config.nim index 18db086..c6fc861 100644 --- a/src/config.nim +++ b/src/config.nim @@ -48,7 +48,6 @@ http://www.apache.org/licenses/LICENSE-2.0 for more info. Basic Usage ----------- -$ peon Open an interactive session (REPL) $ peon file.pn Run the given Peon source file $ peon file.pbc Run the given Peon bytecode file @@ -62,9 +61,9 @@ Options -n, --noDump Don't dump the result of compilation to a file. Note that no dump is created when using -s/--string -b, --breakpoints Run the debugger at specific bytecode offsets (comma-separated). - Only available with --backend:bytecode and when compiled with VM - debugging on --d, --disassemble Disassemble the output of compilation (only makes sense with --backend:bytecode) + Only available with --target:bytecode and when compiled with VM + debugging on (-d:debugVM at build time) +-d, --disassemble Disassemble the output of compilation (only makes sense with --target:bytecode) -m, --mode Set the compilation mode. Acceptable values are 'debug' and 'release'. Defaults to 'debug' -c, --compile Compile the code, but do not execute it. Useful along with -d @@ -72,11 +71,11 @@ Options yes/on and no/off --noWarn Disable a specific warning (for example, --noWarn:unusedVariable) --showMismatches Show all mismatches when function dispatching fails (output is really verbose) ---backend Select the compilation backend (valid values are: 'c' and 'bytecode'). Note - that the REPL always uses the bytecode target. Defaults to 'bytecode' --o, --output Rename the output file with this value (with --backend:bytecode, a '.pbc' extension +--target Select the compilation target (valid values are: 'c' and 'bytecode'). Defaults to + 'bytecode' +-o, --output Rename the output file with this value (with --target:bytecode, a '.pbc' extension is added if not already present) ---debug-dump Debug the bytecode serializer. Only makes sense with --backend:bytecode ---debug-lexer Debug the peon lexer ---debug-parser Debug the peon parser +--debug-dump Debug the bytecode serializer. Only makes sense with --target:bytecode +--debug-lexer Show the lexer's output +--debug-parser Show the parser's output """ diff --git a/src/frontend/compiler/compiler.nim b/src/frontend/compiler/compiler.nim index 42359bf..ab8e2f3 100644 --- a/src/frontend/compiler/compiler.nim +++ b/src/frontend/compiler/compiler.nim @@ -829,7 +829,6 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow msg = &"call to undefined function '{name}'" self.error(msg, node) elif impl.len() > 1: - echo "AAAAAA\n\n" # If we happen to find more than one match, we try again # and ignore forward declarations and automatic functions impl = filterIt(impl, not it.valueType.forwarded and not it.valueType.isAuto) diff --git a/src/main.nim b/src/main.nim index 26461ed..8012e8f 100644 --- a/src/main.nim +++ b/src/main.nim @@ -15,149 +15,30 @@ ## Peon's main executable # Our stuff -import frontend/parsing/lexer as l -import frontend/parsing/parser as p -import frontend/compiler/targets/bytecode/target as b -import frontend/compiler/compiler as c -import backend/vm as v -import frontend/compiler/targets/bytecode/util/serializer as s -import frontend/compiler/targets/bytecode/util/debugger -import util/symbols -import util/fmterr import config +import util/fmterr +import util/symbols +import backend/bytecode/vm +import frontend/parsing/lexer +import frontend/parsing/parser +import frontend/compiler/compiler +import frontend/compiler/targets/bytecode/util/debugger +import frontend/compiler/targets/bytecode/util/serializer +import frontend/compiler/targets/bytecode/target as bytecode + # Builtins & external libs -import std/strformat +import std/os +import std/times import std/strutils import std/terminal import std/parseopt -import std/times -import std/os - -# Thanks art <3 -import jale/editor as ed -import jale/templates -import jale/plugin/defaults -import jale/plugin/editor_history -import jale/keycodes -import jale/multiline - - -proc getLineEditor: LineEditor = - result = newLineEditor() - result.prompt = "=> " - result.populateDefaults() - let history = result.plugHistory() - result.bindHistory(history) - -#[ -proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, breakpoints: seq[uint64] = @[]) = - styledEcho fgMagenta, "Welcome into the peon REPL!" - var - keep = true - tokens: seq[Token] = @[] - tree: seq[Declaration] = @[] - compiler = newBytecodeCompiler(replMode=true) - compiled: Chunk = newChunk() - serialized: Serialized - tokenizer = newLexer() - vm = newPeonVM() - parser = newParser() - debugger = newDebugger() - serializer = newSerializer() - editor = getLineEditor() - input: string - first: bool = false - tokenizer.fillSymbolTable() - editor.bindEvent(jeQuit): - stdout.styledWriteLine(fgGreen, "Goodbye!") - keep = false - input = "" - editor.bindKey("ctrl+a"): - editor.content.home() - editor.bindKey("ctrl+e"): - editor.content.`end`() - while keep: - try: - input = editor.read() - if input == "#clear": - stdout.write("\x1Bc") - continue - elif input == "": - continue - tokens = tokenizer.lex(input, "stdin") - if tokens.len() == 0: - continue - if debugLexer: - styledEcho fgCyan, "Tokenization step:" - for i, token in tokens: - if i == tokens.high(): - # Who cares about EOF? - break - styledEcho fgGreen, "\t", $token - echo "" - tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input, persist=true) - if tree.len() == 0: - continue - if debugParser: - styledEcho fgCyan, "Parsing step:" - for node in tree: - styledEcho fgGreen, "\t", $node - echo "" - discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=first) - if debugCompiler: - styledEcho fgCyan, "Compilation step:\n" - debugger.disassembleChunk(compiled, "stdin") - echo "" - - serialized = serializer.loadBytes(serializer.dumpBytes(compiled, "stdin")) - if debugSerializer: - styledEcho fgCyan, "Serialization step: " - styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch - stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss")) - stdout.styledWrite(fgBlue, &"\t- Constants segment: ") - if serialized.chunk.consts == compiled.consts: - styledEcho fgGreen, "OK" - else: - styledEcho fgRed, "Corrupted" - stdout.styledWrite(fgBlue, &"\t- Code segment: ") - if serialized.chunk.code == compiled.code: - styledEcho fgGreen, "OK" - else: - styledEcho fgRed, "Corrupted" - stdout.styledWrite(fgBlue, "\t- Line info segment: ") - if serialized.chunk.lines == compiled.lines: - styledEcho fgGreen, "OK" - else: - styledEcho fgRed, "Corrupted" - stdout.styledWrite(fgBlue, "\t- Functions segment: ") - if serialized.chunk.functions == compiled.functions: - styledEcho fgGreen, "OK" - else: - styledEcho fgRed, "Corrupted" - if not first: - vm.run(serialized.chunk, repl=true, breakpoints=breakpoints) - first = true - else: - vm.resume(serialized.chunk) - except LexingError: - print(LexingError(getCurrentException())) - except ParseError: - print(ParseError(getCurrentException())) - except CompileError: - print(CompileError(getCurrentException())) - except SerializationError: - var file = SerializationError(getCurrentException()).file - if file notin ["", ""]: - file = relativePath(file, getCurrentDir()) - stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}") - quit(0) -]# +import std/strformat proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], - warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, run: bool = true, - backend: PeonBackend = PeonBackend.Bytecode, output: string) = + warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, + run: bool = true, backend: PeonBackend = PeonBackend.Bytecode, output: string) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] @@ -170,9 +51,9 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints serializer = newSerializer() vm = newPeonVM() input: string + f = f tokenizer.fillSymbolTable() try: - var f = f if not fromString: if not f.endsWith(".pn") and not f.endsWith(".pbc"): f &= ".pn" @@ -258,22 +139,20 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints vm.run(serialized.chunk, breakpoints) else: discard - except LexingError: - print(LexingError(getCurrentException())) - except ParseError: - print(ParseError(getCurrentException())) - except CompileError: - print(CompileError(getCurrentException())) - except SerializationError: - var file = SerializationError(getCurrentException()).file + except LexingError as exc: + print(exc) + except ParseError as exc: + print(exc) + except CompileError as exc: + print(exc) + except SerializationError as exc: + var file = exc.file if file notin ["", ""]: file = relativePath(file, getCurrentDir()) - stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}") - except IOError: - let exc = getCurrentException() + stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {exc.msg}") + except IOError as exc: stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg}") - except OSError: - let exc = getCurrentException() + except OSError as exc: stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg} ({osErrorMsg(osLastError())})", fgRed, "[errno ", fgYellow, $osLastError(), fgRed, "]") @@ -290,7 +169,7 @@ when isMainModule: var mismatches: bool = false var mode: CompileMode = CompileMode.Debug var run: bool = true - var backend: PeonBackend + var target: PeonBackend var output: string = "" for kind, key, value in optParser.getopt(): case kind: @@ -307,10 +186,10 @@ when isMainModule: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'mode' (valid options are: debug, release)") quit() of "help": - echo HELP_MESSAGE + echo HelpMessage quit() of "version": - echo PEON_VERSION_STRING + echo PeonVersionString quit() of "string": file = key @@ -357,12 +236,12 @@ when isMainModule: run = false of "output": output = value - of "backend": + of "target": case value: of "bytecode": - backend = PeonBackend.Bytecode + target = PeonBackend.Bytecode of "c": - backend = PeonBackend.NativeC + target = PeonBackend.NativeC of "debug-dump": debugSerializer = true of "debug-lexer": @@ -377,10 +256,10 @@ when isMainModule: of "o": output = value of "h": - echo HELP_MESSAGE + echo HelpMessage quit() of "v": - echo PEON_VERSION_STRING + echo PeonVersionString quit() of "s": file = key @@ -419,7 +298,6 @@ when isMainModule: if breaks.len() == 0 and debugVM: breaks.add(0) if file == "": - echo "Sorry, the REPL is currently broken :(" - #repl(warnings, mismatches, mode, breaks) + echo HelpMessage else: - runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, backend, output) + runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, target, output)