diff --git a/README.md b/README.md index fd453b5..f38b3ea 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,13 @@ Peon is a simple, functional, async-first programming language with a focus on c **Disclaimer**: The project is still in its very early days: lots of stuff is not implemented, a work in progress or otherwise outright broken. Feel free to report bugs! + +**Disclaimer 2**: Currently the REPL is very basic (it incrementally joins your lines with a newline, as if it was compiling a new file every time), +because incremental compilation is designed for modules and it doesn't play well with the interactive nature of a REPL session. To show the current state +of the REPL, type `#show` (this will print all the code that has been typed so far), while to reset everything (will un-import modules and stuff), type +`#reset`. You can also type `#clear` if you want a clean slate to type in, but note that it won't reset the REPL state. + + Also, yes: peon is yet another programming language inspired by Bob's book, but it is also **very** different from Lox, which is an object-oriented, dynamically typed and very high level programming language, whereas peon is a statically-typed, functional language which aims to allow low-level interfacing with C and Nim code while diff --git a/src/config.nim b/src/config.nim index f7d8567..5bae1bf 100644 --- a/src/config.nim +++ b/src/config.nim @@ -55,10 +55,11 @@ $ peon file.pbc Runs the given Peon bytecode file Command-line options -------------------- --h, --help Shows this help text and exits --v, --version Prints the peon version number and exits --s, --string Executes the passed string as if it was a file --i, --interactive Enables interactive mode: a REPL session is opened after execution --d, --nodump Disables dumping the result of bytecode compilation to files --b, --breakpoints Pauses execution of the peon virtual machine and runs the debugger at specific bytecode offsets +-h, --help Show this help text and exits +-v, --version Print the peon version number and exits +-s, --string Execute the passed string as if it was a file +-n, --nodump Don't dump the result of compilation to a *.pbc file +-b, --breakpoints Run the debugger at specific bytecode offsets (comma-separated). + Only available when compiled with -d:debugVM +-d, --disassemble Disassemble the given bytecode file instead of executing it """ diff --git a/src/main.nim b/src/main.nim index cfa0c8c..878cd31 100644 --- a/src/main.nim +++ b/src/main.nim @@ -55,21 +55,19 @@ proc repl = compiled: Chunk = newChunk() serialized: Serialized tokenizer = newLexer() - parser = newParser() - compiler = newCompiler(replMode=true) vm = newPeonVM() debugger = newDebugger() serializer = newSerializer() editor = getLineEditor() input: string current: string - incremental: bool = false tokenizer.fillSymbolTable() editor.bindEvent(jeQuit): stdout.styledWriteLine(fgGreen, "Goodbye!") editor.prompt = "=> " keep = false input = "" + current = "" editor.bindKey("ctrl+a"): editor.content.home() editor.bindKey("ctrl+e"): @@ -77,18 +75,20 @@ proc repl = while keep: try: input = editor.read() - if input.len() == 0: - continue - elif input == "#reset": + if input == "#reset": compiled = newChunk() - compiler = newCompiler() - parser = newParser() - incremental = false + current = "" continue + elif input == "#show": + echo current elif input == "#clear": stdout.write("\x1Bc") continue - tokens = tokenizer.lex(input, "stdin") + else: + current &= input & "\n" + if current.len() == 0: + continue + tokens = tokenizer.lex(current, "stdin") if tokens.len() == 0: continue when debugLexer: @@ -99,7 +99,7 @@ proc repl = break styledEcho fgGreen, "\t", $token echo "" - tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input, persist=true) + tree = newParser().parse(tokens, "stdin", tokenizer.getLines(), current) if tree.len() == 0: continue when debugParser: @@ -107,8 +107,7 @@ proc repl = for node in tree: styledEcho fgGreen, "\t", $node echo "" - discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, incremental=incremental) - incremental = true + compiled = newCompiler(replMode=true).compile(tree, "stdin", tokenizer.getLines(), current) when debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, "stdin") @@ -141,7 +140,6 @@ proc repl = styledEcho fgRed, "Corrupted" vm.run(serialized.chunk) except LexingError: - input = "" var exc = LexingError(getCurrentException()) let relPos = exc.lexer.getRelPos(exc.line) let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) @@ -151,13 +149,14 @@ proc repl = styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(exc.lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(exc.lexeme))) except ParseError: - input = "" let exc = ParseError(getCurrentException()) let lexeme = exc.token.lexeme var lineNo = exc.token.line + if exc.token.kind == EndOfFile: + dec(lineNo) let relPos = exc.parser.getRelPos(lineNo) - let fn = parser.getCurrentFunction() - let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) + let fn = exc.parser.getCurrentFunction() + let line = exc.parser.getSource().splitLines()[lineNo - 1].strip() var fnMsg = "" if fn != nil and fn.kind == funDecl: fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" @@ -187,7 +186,7 @@ proc repl = quit(0) -proc runFile(f: string, interactive: bool = false, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[]) = +proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] @@ -204,8 +203,11 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false, dum try: var f = f if not fromString: - if not f.endsWith(".pn"): + if not f.endsWith(".pn") and not f.endsWith(".pbc"): f &= ".pn" + if dis: + debugger.disassembleChunk(serializer.loadFile(f).chunk, f) + return input = readFile(f) else: input = f @@ -238,7 +240,7 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false, dum if path.len() > 0: path &= "/" path &= splitFile(f).name & ".pbc" - if dump or not fromString: + if dump and not fromString: serializer.dumpFile(compiled, f, path) serialized = serializer.loadFile(path) else: @@ -281,6 +283,8 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false, dum let exc = ParseError(getCurrentException()) let lexeme = exc.token.lexeme var lineNo = exc.token.line + if exc.token.kind == EndOfFile: + dec(lineNo) let relPos = exc.parser.getRelPos(lineNo) let fn = parser.getCurrentFunction() let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) @@ -322,9 +326,9 @@ when isMainModule: var optParser = initOptParser(commandLineParams()) var file: string = "" var fromString: bool = false - var interactive: bool = false var dump: bool = true var breaks: seq[uint64] = @[] + var dis: bool = false for kind, key, value in optParser.getopt(): case kind: of cmdArgument: @@ -340,8 +344,6 @@ when isMainModule: of "string": file = key fromString = true - of "interactive": - interactive = true of "no-dump": dump = false of "breakpoints": @@ -354,6 +356,8 @@ when isMainModule: except ValueError: echo &"error: invalid breakpoint value '{point}'" quit() + of "disassemble": + dis = true else: echo &"error: unkown option '{key}'" quit() @@ -368,9 +372,7 @@ when isMainModule: of "s": file = key fromString = true - of "i": - interactive = true - of "d": + of "n": dump = false of "b": when not debugVM: @@ -382,17 +384,18 @@ when isMainModule: except ValueError: echo &"error: invalid breakpoint value '{point}'" quit() + of "d": + dis = true else: echo &"error: unkown option '{key}'" quit() else: echo "usage: peon [options] [filename.pn]" quit() - # TODO: Use interactive if file == "": repl() else: - runFile(file, interactive, fromString, dump, breaks) + runFile(file, fromString, dump, breaks, dis)