basic-calculator/main.py

118 lines
3.3 KiB
Python

from typing import Optional
from sys import argv, stdin as _stdin, stdout as _stdout
from std.stdlibrary import stdnamespace
from src.interpreter import interpret
from src.tokentypes.types import PyCalcError, NoCodeError
PROMPT = ">> "
def _format_exc(
code: str,
exc: PyCalcError,
file: str = "<anonymous>") -> str:
lineno, pos = exc.pos
line = code.split("\n")[lineno]
return f"{line}\n" + \
" " * pos + "^\n" \
f"{file}:{lineno+1}:{pos+1}: " \
f"{exc.__class__.__name__}: {exc}"
class InteractiveShell:
def __init__(self,
prompt: str = PROMPT,
interpreter: Optional[interpret.ABCInterpreter] = None
):
self.prompt = prompt
self.interpreter = interpreter or interpret.Interpreter()
def session(self, stdin=_stdin, stdout=_stdout):
while True:
print(end=self.prompt, file=stdout)
try:
expression = stdin.readline().strip()
except KeyboardInterrupt:
return
if not expression:
continue
try:
print(self.interpreter.interpret(expression, stdnamespace),
file=stdout)
except PyCalcError as exc:
print(_format_exc(expression, exc, file="<repl>"),
file=stdout)
except Exception as exc:
print(f"<repl>:1:?: internal interpreter error: {exc.__class__.__name__}: {exc}",
file=stdout)
def interactive_mode():
interpreter = interpret.Interpreter()
shell = InteractiveShell(
prompt=PROMPT,
interpreter=interpreter
)
shell.session()
def expr_exec_mode(expr: str):
try:
print(interpret.Interpreter().interpret(expr, stdnamespace))
except PyCalcError as exc:
print(_format_exc(expr, exc, file="<cli>"))
except NoCodeError:
pass
except Exception as exc:
print(f"<cli>:1:?: internal interpreter error: {exc.__class__.__name__}({repr(exc)})")
def script_exec_mode(filename: str):
if not filename.endswith(".calc"):
print("unsupported file extension:", filename)
return
try:
with open(filename) as fd:
code = fd.read()
except FileNotFoundError:
print("file not found:", filename)
return
interpreter = interpret.Interpreter()
try:
interpreter.interpret(code, stdnamespace)
except PyCalcError as exc:
print(_format_exc(code, exc, file=fd.name))
except NoCodeError:
pass
except Exception as exc:
print(f"{fd.name}:?:?: internal interpreter error:")
raise exc
if __name__ == '__main__':
options = {
"-e": expr_exec_mode,
"--execute": expr_exec_mode,
"-s": script_exec_mode,
"--script": script_exec_mode,
}
if len(argv) > 1 and (argv[1] not in options or len(argv) != 3):
print("Invalid options:", " ".join(argv[1:]))
print("Available options:")
print("\t-e, --execute <expression>: execute expression right from a command line")
print("\t-s, --script <filename>.calc: execute program from a file")
elif len(argv) == 3:
option, value = argv[1:]
options[option](value)
else:
interactive_mode()