diff --git a/setup.py b/setup.py index 36fa0f3..9b54892 100644 --- a/setup.py +++ b/setup.py @@ -24,9 +24,13 @@ setup( packages=[ "flowmetal", ], - scripts=[ - ], + entry_points = { + 'console_scripts': [ + 'iflow=flowmetal.repl:main' + ], + }, install_requires=[ + 'prompt-toolkit~=3.0.0', ], extras_require={ } diff --git a/src/python/flowmetal/parser.py b/src/python/flowmetal/parser.py index 0492676..fcd27a3 100644 --- a/src/python/flowmetal/parser.py +++ b/src/python/flowmetal/parser.py @@ -216,16 +216,21 @@ class Parser(SexpParser): @classmethod def parse(cls, f: PosTrackingBufferedReader): - if f.peek() == "(": - return cls.parse_list(f) - elif f.peek() == "[": - return cls.parse_sqlist(f) - elif f.peek() == '"': - return cls.parse_str(f) + if not f.peek(): + raise SyntaxError(f"Got end of file ({f.pos()}) while parsing") + elif cls.ispunct(f.peek()): + if f.peek() == "(": + return cls.parse_list(f) + elif f.peek() == "[": + return cls.parse_sqlist(f) + elif f.peek() == '"': + return cls.parse_str(f) + elif f.peek() == ";": + return cls.parse_comment(f) + else: + raise SyntaxError(f"Got unexpected punctuation {f.read()!r} at {f.pos()} while parsing") elif cls.isspace(f.peek()): return cls.parse_whitespace(f) - elif f.peek() == ";": - return cls.parse_comment(f) else: return cls.parse_symbol(f) @@ -258,6 +263,8 @@ class Parser(SexpParser): pos = rtb.pos() acc = [] while f.peek() != closec: + if not f.peek(): + raise SyntaxError(f"Got end of file ({f.pos()}) while parsing {openc!r}...{closec!r} starting at {pos}") acc.append(cls.parse(rtb)) assert rtb.read() == closec # Discard the trailing delimeter return ctor(acc, str(rtb), pos) diff --git a/src/python/flowmetal/repl.py b/src/python/flowmetal/repl.py new file mode 100644 index 0000000..a834877 --- /dev/null +++ b/src/python/flowmetal/repl.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import sys +import pprint + +from flowmetal.syntax_analyzer import analyzes + +from prompt_toolkit import print_formatted_text, prompt, PromptSession +from prompt_toolkit.formatted_text import FormattedText +from prompt_toolkit.history import FileHistory +from prompt_toolkit.styles import Style + + +STYLE = Style.from_dict({ + # User input (default text). + "": "", + "prompt": "ansigreen", + "time": "ansiyellow" +}) + + +class InterpreterInterrupt(Exception): + """An exception used to break the prompt or evaluation.""" + + +parser = argparse.ArgumentParser() + +def main(): + """REPL entry point.""" + + args = parser.parse_args(sys.argv[1:]) + logger = logging.getLogger("arrdem.datalog") + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ch.setFormatter(formatter) + logger.addHandler(ch) + + session = PromptSession(history=FileHistory(".datalog.history")) + line_no = 0 + + while True: + try: + line = session.prompt([("class:prompt", ">>> ")], style=STYLE) + except (InterpreterInterrupt, KeyboardInterrupt): + continue + except EOFError: + break + + try: + pprint.pprint(analyzes(line, source_name=f"repl@{line_no}")) + except Exception as e: + print(e) + finally: + line_no += 1