# 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 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/os import std/times import std/strutils import std/terminal import std/parseopt 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) = 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 f = f tokenizer.fillSymbolTable() try: 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) compiled = serialized.chunk if debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(serialized.chunk, 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 as exc: print(exc) except ParseError as exc: print(exc) except CompileError as exc: print(exc) #raise 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, &": {exc.msg}") except IOError as exc: stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg}") 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, "]") 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 target: 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 HelpMessage quit() of "version": echo PeonVersionString 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 "target": case value: of "bytecode": target = PeonBackend.Bytecode of "c": target = 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 HelpMessage quit() of "v": echo PeonVersionString 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 == "": echo HelpMessage else: runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, target, output)