diff --git a/main.py b/main.py index f119544..e67b81f 100755 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import argparse import os from pathlib import Path +from src.repl import REPL from src.runner import Runner @@ -23,7 +24,7 @@ def main(): args = parser.parse_args() if args.script is None: - Runner.repl() + REPL().run() else: Runner.run_file(args.script) diff --git a/src/repl.py b/src/repl.py new file mode 100644 index 0000000..212122a --- /dev/null +++ b/src/repl.py @@ -0,0 +1,113 @@ +import atexit +import readline +from pathlib import Path + +from src.ast.stmt import Stmt +from src.interpreter.interpreter import Interpreter +from src.interpreter.resolver import Resolver +from src.parser.parser import Parser +from src.pebble import Pebble +from src.token.lexer import Lexer +from src.token.token import Token, TokenType + + +class REPL: + HIST_FILE: Path = Path("~").expanduser() / ".pebble_history" + + def __init__(self): + self.interpreter: Interpreter = Interpreter() + + @staticmethod + def greet(): + print(f"Welcome to Pebble v{Pebble.version()}!") + print("Press CTRL+D to quit") + + def init_history(self): + h_len: int = 0 + try: + readline.read_history_file(self.HIST_FILE) + h_len = readline.get_current_history_length() + except FileNotFoundError: + with open(self.HIST_FILE, "wb"): + pass + + def save(prev_h_len: int, hist_file: Path): + new_h_len: int = readline.get_current_history_length() + readline.set_history_length(1000) + readline.append_history_file(new_h_len - prev_h_len, hist_file) + + atexit.register(save, h_len, self.HIST_FILE) + + def run(self): + line: str + tokens: list[Token] + program: list[Stmt] + + self.greet() + self.init_history() + + buf: str = "" + while True: + try: + line = input(">>> " if len(buf) == 0 else "... ") + except EOFError: + print() + break + except KeyboardInterrupt: + buf = "" + print() + continue + + buf += line + "\n" + try: + tokens = Lexer(buf).process() + except: + continue + if Pebble.had_error: + Pebble.had_error = False + continue + + if self.unfinished_statement(tokens): + continue + + program = Parser(tokens).parse() + buf = "" + + if Pebble.had_error: + Pebble.had_error = False + continue + + Resolver(self.interpreter).resolve(*program) + if Pebble.had_error: + Pebble.had_error = False + continue + + try: + self.interpreter.interpret(program) + except KeyboardInterrupt: + print() + + if Pebble.had_runtime_error: + Pebble.had_runtime_error = False + continue + + @staticmethod + def unfinished_statement(tokens: list[Token]) -> bool: + brace_depth: int = 0 + paren_depth: int = 0 + fstring_depth: int = 0 + for token in tokens: + match token.type: + case TokenType.LEFT_BRACE: + brace_depth += 1 + case TokenType.RIGHT_BRACE: + brace_depth = max(0, brace_depth - 1) + case TokenType.LEFT_PAREN: + paren_depth += 1 + case TokenType.RIGHT_PAREN: + paren_depth = max(0, paren_depth - 1) + case TokenType.FSTRING_START: + fstring_depth += 1 + case TokenType.FSTRING_END: + fstring_depth = max(0, fstring_depth - 1) + return brace_depth > 0 or paren_depth > 0 or fstring_depth > 0 diff --git a/src/runner.py b/src/runner.py index 670b7ad..070f822 100644 --- a/src/runner.py +++ b/src/runner.py @@ -5,10 +5,10 @@ from src.ast.stmt import Stmt from src.formatter import Formatter from src.interpreter.interpreter import Interpreter from src.interpreter.resolver import Resolver -from src.token.lexer import Lexer from src.parser.parser import Parser from src.pebble import Pebble -from src.token.token import Token, TokenType +from src.token.lexer import Lexer +from src.token.token import Token class Runner: @@ -55,77 +55,3 @@ class Runner: formatter: Formatter = Formatter() with open("formatted.peb", "w") as f: f.write(formatter.print(program)) - - @staticmethod - def repl(): - line: str - lexer: Lexer - tokens: list[Token] - parser: Parser - program: list[Stmt] - interpreter: Interpreter = Interpreter() - resolver: Resolver - - print(f"Welcome to Pebble v{Pebble.version()}!") - print("Press CTRL+D to quit") - - brace_depth: int - paren_depth: int - buf: str = "" - while True: - try: - line = input(">>> " if len(buf) == 0 else "... ") - except EOFError: - print() - break - except KeyboardInterrupt: - buf = "" - print() - continue - - buf += line + "\n" - lexer = Lexer(buf) - tokens = lexer.process() - if Pebble.had_error: - Pebble.had_error = False - continue - - brace_depth = 0 - paren_depth = 0 - for token in tokens: - match token.type: - case TokenType.LEFT_BRACE: - brace_depth += 1 - case TokenType.RIGHT_BRACE: - brace_depth = max(0, brace_depth - 1) - case TokenType.LEFT_PAREN: - paren_depth += 1 - case TokenType.RIGHT_PAREN: - paren_depth = max(0, paren_depth - 1) - - if brace_depth > 0 or paren_depth > 0: - continue - - parser = Parser(tokens) - program = parser.parse() - buf = "" - - if Pebble.had_error: - Pebble.had_error = False - continue - - resolver: Resolver = Resolver(interpreter) - - resolver.resolve(*program) - if Pebble.had_error: - Pebble.had_error = False - continue - - try: - interpreter.interpret(program) - except KeyboardInterrupt: - print() - - if Pebble.had_runtime_error: - Pebble.had_runtime_error = False - continue