From 218fb910533f0c80f5ce22b55288f7ac88037999 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sun, 8 Feb 2026 14:32:41 +0100 Subject: [PATCH] feat(repl): add auto-completer --- src/completer/__init__.py | 0 src/completer/completer.py | 38 ++++++++++++++++++++++++++++++++++++++ src/repl.py | 19 +++++++++++++------ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 src/completer/__init__.py create mode 100644 src/completer/completer.py diff --git a/src/completer/__init__.py b/src/completer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/completer/completer.py b/src/completer/completer.py new file mode 100644 index 0000000..578a285 --- /dev/null +++ b/src/completer/completer.py @@ -0,0 +1,38 @@ +from typing import Optional + +from src.interpreter.environment import Environment +from src.interpreter.interpreter import Interpreter + + +class Completer: + def __init__(self, interpreter: Interpreter): + self.interpreter: Interpreter = interpreter + self.options: list[str] = [] + self.matches: list[str] = [] + + def complete(self, text: str, state: int) -> Optional[str]: + if state == 0: + self.update_matches(text) + try: + return self.matches[state] + except IndexError: + return None + + def update_matches(self, text: str): + self.options = [] + + env: Optional[Environment] = self.interpreter.env + while env is not None: + self.options.extend(env.values.keys()) + env = env.enclosing + + self.options = sorted(self.options) + + if not text: + self.matches = self.options + else: + self.matches = [ + opt + for opt in self.options + if opt.startswith(text) + ] diff --git a/src/repl.py b/src/repl.py index 212122a..651aba5 100644 --- a/src/repl.py +++ b/src/repl.py @@ -3,6 +3,7 @@ import readline from pathlib import Path from src.ast.stmt import Stmt +from src.completer.completer import Completer from src.interpreter.interpreter import Interpreter from src.interpreter.resolver import Resolver from src.parser.parser import Parser @@ -16,6 +17,8 @@ class REPL: def __init__(self): self.interpreter: Interpreter = Interpreter() + self.completer: Completer = Completer(self.interpreter) + self.buf: str = "" @staticmethod def greet(): @@ -38,6 +41,10 @@ class REPL: atexit.register(save, h_len, self.HIST_FILE) + def init_completer(self): + readline.set_completer(self.completer.complete) + readline.parse_and_bind("tab: complete") + def run(self): line: str tokens: list[Token] @@ -45,22 +52,22 @@ class REPL: self.greet() self.init_history() + self.init_completer() - buf: str = "" while True: try: - line = input(">>> " if len(buf) == 0 else "... ") + line = input(">>> " if len(self.buf) == 0 else "... ") except EOFError: print() break except KeyboardInterrupt: - buf = "" + self.buf = "" print() continue - buf += line + "\n" + self.buf += line + "\n" try: - tokens = Lexer(buf).process() + tokens = Lexer(self.buf).process() except: continue if Pebble.had_error: @@ -71,7 +78,7 @@ class REPL: continue program = Parser(tokens).parse() - buf = "" + self.buf = "" if Pebble.had_error: Pebble.had_error = False