peon-rewrite/src/main.nim

403 lines
17 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.
import config
import util/fmterr
import util/symbols
import frontend/parser/lexer
import frontend/parser/parser
import frontend/compiler/typechecker
import backend/bytecode/codegen/generator
import backend/bytecode/tooling/serializer
import backend/bytecode/opcodes
import backend/bytecode/tooling/debugger
import backend/bytecode/vm
import std/os
import std/parseopt
import std/strutils
import std/terminal
import std/strformat
import std/times
# 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 `$`(self: TypedNode): string =
if self.node.isConst():
var self = TypedExpr(self)
return &"{self.node}: {self.kind[]}"
case self.node.kind:
of varDecl, typeDecl, funDecl:
var self = TypedDecl(self)
result = &"{self.name[]}: {self.name.valueType[]}"
of identExpr, binaryExpr, unaryExpr:
var self = TypedExpr(self)
result &= &"{self.node}: {self.kind[]}"
else:
result = &"{self.node}: ? ({self.node.kind})"
proc runFile(filename: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[],
disabledWarnings: seq[WarningKind] = @[], mismatches: bool = false, run: bool = true,
backend: PeonBackend = PeonBackend.Bytecode, output: string) =
var
tokens: seq[Token]
tree: ParseTree
typedNodes: seq[TypedNode]
tokenizer = newLexer()
parser = newParser()
typeChecker = newTypeChecker()
input: string
filename = filename
isBinary = false
output = output
tokenizer.fillSymbolTable()
if not fromString and filename.endsWith(".pbc"):
isBinary = true
if fromString:
input = filename
filename = "<string>"
else:
input = readFile(filename)
try:
if not isBinary:
tokens = tokenizer.lex(input, filename)
if tokens.len() == 0:
return
if debugLexer:
styledEcho fgCyan, "Tokenizer output:"
for i, token in tokens:
if i == tokens.high():
# Who cares about EOF?
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, filename, tokenizer.getLines(), input)
if tree.len() == 0:
return
if debugParser:
styledEcho fgCyan, "Parser output:"
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
typedNodes = typeChecker.validate(tree, filename, tokenizer.getSource(), mismatches, disabledWarnings)
if debugTypeChecker:
styledEcho fgCyan, "Typechecker output:"
for typedNode in typedNodes:
case typedNode.node.kind:
of exprStmt:
# *Technically* an expression statement has no type, but that isn't really useful for debug
# purposes, so we print the type of the expression within it instead
let exprNode = TypedExprStmt(typedNode).expression
styledEcho fgGreen, &"\t{typedNode.node} (inner) -> {typeChecker.stringify(exprNode.kind)}\n"
else:
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
case backend:
of PeonBackend.Bytecode:
var
debugger = newBytecodeDebugger()
generator = newBytecodeGenerator()
serializer = newBytecodeSerializer()
vm = newPeonVM()
chunk: Chunk = newChunk()
serialized: SerializedBytecode
if not isBinary:
chunk = generator.generate(typedNodes, typeChecker)
serialized = serializer.loadBytes(serializer.dumpBytes(chunk, filename))
else:
serialized = serializer.loadFile(filename)
chunk = serialized.chunk
if dump and not fromString:
if not output.endsWith(".pbc"):
output.add(".pbc")
serializer.dumpFile(chunk, filename, output)
if debugCompiler:
styledEcho fgCyan, "Disassembler output below"
debugger.disassembleChunk(chunk, filename)
if debugSerializer:
styledEcho fgCyan, "Serializer checks: "
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.styledWriteLine(fgBlue, "\t- Total binary size: ", fgYellow, formatSize(serialized.size))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == chunk.consts:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, &"\t- Code segment: ")
if serialized.chunk.code == chunk.code:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Line info segment: ")
if serialized.chunk.lines == chunk.lines:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Functions segment: ")
if serialized.chunk.functions == chunk.functions:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Modules segment: ")
if serialized.chunk.modules == chunk.modules:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
if run:
vm.run(chunk, breakpoints, repl=false)
else:
discard
except LexingError as exc:
print(exc)
except ParseError as exc:
print(exc)
except TypeCheckError as exc:
print(exc)
except CodeGenError as exc:
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(file, getCurrentDir())
stderr.styledWriteLine(fgRed, styleBright, "Error while generating code for ", fgYellow, file, fgDefault, &": {exc.msg}")
except SerializationError as exc:
var file = exc.file
if file notin ["<string>", ""]:
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, filename, fgDefault, &": {exc.msg}")
except OSError as exc:
stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, filename, fgDefault, &": {exc.msg} ({osErrorMsg(osLastError())})",
fgRed, "[errno ", fgYellow, $osLastError(), fgRed, "]")
#[
proc repl(warnings: seq[WarningKind] = @[], showMismatches: bool = false) =
var
keep = true
tokens: seq[Token]
tree: ParseTree
typeChecker = newTypeChecker()
lexer = newLexer()
parser = newParser()
editor = getLineEditor()
input: string
lexer.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 = lexer.lex(input, "stdin")
if tokens.len() == 0:
continue
if debugLexer:
styledEcho fgCyan, "Tokenizer output:"
for i, token in tokens:
if i == tokens.high():
# Who cares about EOF?
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, "stdin", lexer.getLines(), input, persist=true)
if tree.len() == 0:
continue
if debugParser:
styledEcho fgCyan, "Parser output:"
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
if debugTypeChecker:
styledEcho fgCyan, "Typechecker output:"
for typedNode in typeChecker.validate(parser.parse(lexer.lex(input, "<stdin>"), lexer.getFile(), lexer.getLines(), lexer.getSource()),
lexer.getFile(), lexer.getSource(), showMismatches=showMismatches, disabledWarnings=warnings):
if debugTypeChecker:
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
echo ""
except LexingError:
print(LexingError(getCurrentException()))
except ParseError:
print(ParseError(getCurrentException()))
except TypeCheckError:
print(TypeCheckError(getCurrentException()))
quit(0)
]#
when isMainModule:
setControlCHook(proc () {.noconv.} = quit(0))
var
optParser = initOptParser(commandLineParams())
file: string
fromString: bool
dump: bool = true
warnings: seq[WarningKind] = @[]
showMismatches: bool
#mode: CompileMode = CompileMode.Debug
run: bool = true
backend: PeonBackend
output: string
breakpoints: seq[uint64]
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":
showMismatches = true
of "noWarn":
case value:
of "UserWarning":
warnings.add(UserWarning)
else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'noWarn'")
quit()
of "listWarns":
echo "Currently supported warnings: "
for warning in WarningKind:
echo &" - {warning}"
quit(0)
of "debugTypeChecker":
debugTypeChecker = true
of "debugCompiler":
debugCompiler = true
of "debugSerializer":
debugSerializer = 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 "debugLexer":
debugLexer = true
of "debugParser":
debugParser = true
of "breakpoints":
when debugVM:
for point in value.strip(chars={' '}).split(","):
try:
breakpoints.add(parseBiggestUInt(point))
except ValueError:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
quit()
when not debugVM:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "VM debugging is off, cannot set breakpoints (recompile with -d:debugVM to fix this)")
quit()
else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown long 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 "c":
run = false
else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown short option '{key}'")
quit()
else:
echo "usage: peon [options] [filename.pn]"
quit()
if file == "":
echo "Sorry, the REPL is broken :("
# repl(warnings, showMismatches, backend, dump)
else:
runFile(file, fromString, dump, breakpoints, warnings, showMismatches, run, backend, output)