# Copyright 2022 Mattia Giambirtone & All Contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## Peon's main executable # Our stuff import frontend/lexer as l import frontend/parser as p import frontend/compiler as c import backend/vm as v import util/serializer as s import util/debugger import util/symbols import config # Builtins & external libs import std/strformat import std/strutils import std/terminal import std/parseopt when debugSerializer: 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 # Forward declarations proc getLineEditor: LineEditor proc repl = styledEcho fgMagenta, "Welcome into the peon REPL!" var keep = true tokens: seq[Token] = @[] tree: seq[Declaration] = @[] 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 = "" editor.bindKey("ctrl+a"): editor.content.home() editor.bindKey("ctrl+e"): editor.content.`end`() while keep: try: input = editor.read() if input.len() == 0: continue elif input == "#reset": compiled = newChunk() compiler = newCompiler() parser = newParser() incremental = false continue elif input == "#clear": stdout.write("\x1Bc") continue tokens = tokenizer.lex(input, "stdin") if tokens.len() == 0: continue when 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 when debugParser: styledEcho fgCyan, "Parsing step:" for node in tree: styledEcho fgGreen, "\t", $node echo "" discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, incremental=incremental) incremental = true when debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, "stdin") echo "" serialized = serializer.loadBytes(serializer.dumpBytes(compiled, "stdin")) when 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- CFI segment: ") if serialized.chunk.cfi == compiled.cfi: styledEcho fgGreen, "OK" else: 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'}) stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) 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 let relPos = exc.parser.getRelPos(lineNo) let fn = parser.getCurrentFunction() let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) var fnMsg = "" if fn != nil and fn.kind == funDecl: fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) except CompileError: let exc = CompileError(getCurrentException()) let lexeme = exc.node.token.lexeme var lineNo = exc.node.token.line let relPos = exc.compiler.getRelPos(lineNo) let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) var fn = exc.compiler.getCurrentFunction() var fnMsg = "" if fn != nil and fn.kind == funDecl: fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) except SerializationError: let exc = SerializationError(getCurrentException()) stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg()) quit(0) proc runFile(f: string, interactive: bool = false, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[]) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] compiled: Chunk serialized: Serialized tokenizer = newLexer() parser = newParser() compiler = newCompiler() debugger {.used.} = newDebugger() serializer = newSerializer() vm = newPeonVM() input: string tokenizer.fillSymbolTable() try: var f = f if not fromString: if not f.endsWith(".pn"): f &= ".pn" input = readFile(f) else: input = f f = "" tokens = tokenizer.lex(input, f) if tokens.len() == 0: return when 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, f, tokenizer.getLines(), input) if tree.len() == 0: return when debugParser: styledEcho fgCyan, "Parsing step:" for node in tree: styledEcho fgGreen, "\t", $node echo "" compiled = compiler.compile(tree, f, tokenizer.getLines(), input) when debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, f) echo "" var path = splitFile(f).dir if path.len() > 0: path &= "/" path &= splitFile(f).name & ".pbc" if dump or not fromString: serializer.dumpFile(compiled, f, path) serialized = serializer.loadFile(path) else: serialized = serializer.loadBytes(serializer.dumpBytes(compiled, f)) when 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- CFI segment: ") if serialized.chunk.cfi == compiled.cfi: styledEcho fgGreen, "OK" else: styledEcho fgRed, "Corrupted" vm.run(serialized.chunk, breakpoints) except LexingError: var exc = LexingError(getCurrentException()) let relPos = exc.lexer.getRelPos(exc.line) let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) 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: let exc = ParseError(getCurrentException()) let lexeme = exc.token.lexeme var lineNo = exc.token.line let relPos = exc.parser.getRelPos(lineNo) let fn = parser.getCurrentFunction() let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) var fnMsg = "" if fn != nil and fn.kind == funDecl: fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) except CompileError: let exc = CompileError(getCurrentException()) let lexeme = exc.node.token.lexeme var lineNo = exc.node.token.line let relPos = exc.compiler.getRelPos(lineNo) let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) var fn = exc.compiler.getCurrentFunction() var fnMsg = "" if fn != nil and fn.kind == funDecl: fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ", fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", fgRed, ": ", fgGreen , getCurrentExceptionMsg()) styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) except SerializationError: let exc = SerializationError(getCurrentException()) stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg()) except IOError: stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}") except OSError: stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]") when isMainModule: setControlCHook(proc () {.noconv.} = quit(0)) var optParser = initOptParser(commandLineParams()) var file: string = "" var fromString: bool = false var interactive: bool = false var dump: bool = true var breaks: seq[uint64] = @[] for kind, key, value in optParser.getopt(): case kind: of cmdArgument: file = key of cmdLongOption: case key: of "help": echo HELP_MESSAGE quit() of "version": echo PEON_VERSION_STRING quit() of "string": file = key fromString = true of "interactive": interactive = true of "no-dump": dump = false of "breakpoints": when not debugVM: echo "error: cannot set breakpoints in release mode" quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: echo &"error: invalid breakpoint value '{point}'" quit() else: echo &"error: unkown option '{key}'" quit() of cmdShortOption: case key: of "h": echo HELP_MESSAGE quit() of "v": echo PEON_VERSION_STRING quit() of "s": file = key fromString = true of "i": interactive = true of "d": dump = false of "b": when not debugVM: echo "error: cannot set breakpoints in release mode" quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: echo &"error: invalid breakpoint value '{point}'" quit() 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) proc getLineEditor: LineEditor = result = newLineEditor() result.prompt = "=> " result.populateDefaults() let history = result.plugHistory() result.bindHistory(history)