# 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/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 # Builtins & external libs import std/strformat 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 "" compiled = compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=true) 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) 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) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] compiled: Chunk serialized: Serialized tokenizer = newLexer() parser = newParser() compiler = newBytecodeCompiler() debugger {.used.} = newDebugger() serializer = newSerializer() vm = newPeonVM() input: string tokenizer.fillSymbolTable() try: var f = f if not fromString: if not f.endsWith(".pn") and not f.endsWith(".pbc"): f &= ".pn" input = readFile(f) else: input = f f = "" if not f.endsWith(".pbc"): tokens = tokenizer.lex(input, f) if tokens.len() == 0: return 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, f, tokenizer.getLines(), input) if tree.len() == 0: return if debugParser: styledEcho fgCyan, "Parsing step:" for node in tree: styledEcho fgGreen, "\t", $node echo "" case backend: of PeonBackend.Bytecode: compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings, showMismatches=mismatches, mode=mode) if debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, f) var path = splitFile(if output.len() > 0: output else: f).dir if path.len() > 0: path &= "/" if not path.endsWith(".pbc"): path &= splitFile(if output.len() > 0: output else: f).name & ".pbc" if dump and not fromString: serializer.dumpFile(compiled, f , path) serialized = serializer.loadFile(path) else: serialized = serializer.loadBytes(serializer.dumpBytes(compiled, f)) else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "the selected backend is not implemented yet") elif backend == PeonBackend.Bytecode: serialized = serializer.loadFile(f) if backend == PeonBackend.Bytecode and 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" stdout.styledWrite(fgBlue, "\t- Modules segment: ") if serialized.chunk.modules == compiled.modules: styledEcho fgGreen, "OK" else: styledEcho fgRed, "Corrupted" if run: case backend: of PeonBackend.Bytecode: 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 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 trying to read ", fgYellow, f, fgDefault, &": {exc.msg}") except OSError: let exc = getCurrentException() stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg} ({osErrorMsg(osLastError())})", fgRed, "[errno ", fgYellow, $osLastError(), fgRed, "]") when isMainModule: setControlCHook(proc () {.noconv.} = quit(0)) var optParser = initOptParser(commandLineParams()) var file: string = "" var fromString: bool = false var dump: bool = true var warnings: seq[WarningKind] = @[] var breaks: seq[uint64] = @[] var mismatches: bool = false var mode: CompileMode = CompileMode.Debug var run: bool = true var backend: PeonBackend var output: string = "" for kind, key, value in optParser.getopt(): case kind: of cmdArgument: file = key of cmdLongOption: case key: of "mode": if value.toLowerAscii() == "release": mode = CompileMode.Release elif value.toLowerAscii() == "debug": discard else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'mode' (valid options are: debug, release)") quit() of "help": echo HELP_MESSAGE quit() of "version": echo PEON_VERSION_STRING quit() of "string": file = key fromString = true of "noDump": dump = false of "warnings": if value.toLowerAscii() in ["yes", "on"]: warnings = @[] elif value.toLowerAscii() in ["no", "off"]: for warning in WarningKind: warnings.add(warning) else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'warnings' (valid options are: yes, on, no, off)") quit() of "showMismatches": mismatches = true of "noWarn": case value: of "unusedVariable": warnings.add(WarningKind.UnusedName) of "unreachableCode": warnings.add(WarningKind.UnreachableCode) of "shadowOuterScope": warnings.add(WarningKind.ShadowOuterScope) of "mutateOuterScope": warnings.add(WarningKind.MutateOuterScope) else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'noWarn'") quit() of "breakpoints": when not debugVM: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode") quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'") quit() of "disassemble": debugCompiler = true of "compile": run = false of "output": output = value of "backend": case value: of "bytecode": backend = PeonBackend.Bytecode of "c": backend = PeonBackend.NativeC of "debug-dump": debugSerializer = true of "debug-lexer": debugLexer = true of "debug-parser": debugParser = true else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'") quit() of cmdShortOption: case key: of "o": output = value of "h": echo HELP_MESSAGE quit() of "v": echo PEON_VERSION_STRING quit() of "s": file = key fromString = true of "n": dump = false of "w": if value.toLowerAscii() in ["yes", "on"]: warnings = @[] elif value.toLowerAscii() in ["no", "off"]: for warning in WarningKind: warnings.add(warning) else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'w' (valid options are: yes, on, no, off)") quit() of "b": when not debugVM: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode") quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'") quit() of "c": run = false of "d": debugCompiler = true else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown option '{key}'") quit() else: echo "usage: peon [options] [filename.pn]" quit() if breaks.len() == 0 and debugVM: breaks.add(0) if file == "": repl(warnings, mismatches, mode, breaks) else: runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, backend, output)