peon/src/main.nim

421 lines
18 KiB
Nim

# 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 ["<string>", ""]:
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 = "<string>"
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 ["<string>", ""]:
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)