# 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 util/fmterr 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() vm = newPeonVM() debugger = newDebugger() serializer = newSerializer() editor = getLineEditor() input: string current: string 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"): editor.content.`end`() while keep: try: input = editor.read() if input == "#reset": compiled = newChunk() current = "" continue elif input == "#show": echo current elif input == "#clear": stdout.write("\x1Bc") continue else: current &= input & "\n" if current.len() == 0: continue tokens = tokenizer.lex(current, "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 = newParser().parse(tokens, "stdin", tokenizer.getLines(), current) if tree.len() == 0: continue when debugParser: styledEcho fgCyan, "Parsing step:" for node in tree: styledEcho fgGreen, "\t", $node echo "" compiled = newCompiler(replMode=true).compile(tree, "stdin", tokenizer.getLines(), current) 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: print(LexingError(getCurrentException())) except ParseError: print(ParseError(getCurrentException())) except CompileError: print(CompileError(getCurrentException())) except SerializationError: print(SerializationError(getCurrentException())) quit(0) proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false, warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug) = 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") and not f.endsWith(".pbc"): f &= ".pn" if dis: debugger.disassembleChunk(serializer.loadFile(f).chunk, f) return 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, disabledWarnings=warnings, showMismatches=mismatches, mode=mode) 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 and 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: print(LexingError(getCurrentException())) except ParseError: print(ParseError(getCurrentException())) except CompileError: print(CompileError(getCurrentException())) except SerializationError: print(SerializationError(getCurrentException())) except IOError: print(IOError(getCurrentException()[]), f) except OSError: print(OSError(getCurrentException()[]), f, osLastError()) 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 dis: bool = false var mismatches: bool = false 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 "no-dump": 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 "shadowingOuterScope": warnings.add(WarningKind.ShadowOuterScope) else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'warnings'") 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": dis = true else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"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 "n": dump = false 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 "d": dis = true else: stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown option '{key}'") quit() else: echo "usage: peon [options] [filename.pn]" quit() if file == "": repl() else: runFile(file, fromString, dump, breaks, dis, warnings, mismatches) proc getLineEditor: LineEditor = result = newLineEditor() result.prompt = "=> " result.populateDefaults() let history = result.plugHistory() result.bindHistory(history)