4
\$\begingroup\$

Here is a very basic object orientated implementation of a read evaluate print loop AKA REPL. I encluded REShell to demonstrate how one could use Reple

#-*-coding:utf8;-*-
#qpy:3
#qpy:console

import re
import sre_constants

PS1 = '>> '

DEFAULT_EVALUATOR = print

class Repl(object):
    def __init__(self, evaluator=DEFAULT_EVALUATOR):
        self.ps1 = PS1
        self.evaluator = evaluator

    def read(self):
        return input(self.ps1)

    def evaluate(self):
        return self.evaluator(self.read())

    def run(self):
        while 1:
            try:
                self.evaluate()
            except KeyboardInterrupt:
                sys.exit(0)


class REShell(Repl):
    def __init__(self, data):
        self.data = data
        self.ps1 = PS1

    def evaluate(self):
        try:
            expression = re.compile(self.read())
            print(*expression.findall(self.data), sep='\n')
        except sre_constants.error as error:
            print(error)


def source_code():
    with open(__file__, 'r') as source:
        return source.read()

if __name__ == '__main__':
    data = source_code()
    print(data)
    shell = REShell(data)
    shell.run()
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Looks quite good to me! There's some things I think you could improve:

  • When a user hits Ctrl-C, the program flow is suddenly interrupted. The call to sys.exit should use 1 as a status code to indicate that something went wrong.

  • The REPL prompt is currently hardcoded, but you could easily change Reple's signature to allow a custom prompt:

    class Reple(object):
        def __init__(self, evaluator=DEFAULT_EVALUATOR, prompt=PS1):
            self.prompt = prompt
    

    On that note, PS1 is a bit ambiguous, maybe rename it to DEFAULT_PROMPT.

  • This is more of a design argument, but in my opinion it would make more sense to have run call evaluate, making evaluator superfluous; in pseudocode:

    while true
        read from stdin
        evaluate(expression)
    

    And in Python:

    class Reple(object):
        ...
    
        def run(self):
            while 1:
                try:
                    expression = input(self.prompt)
                except KeyboardInterrupt:
                    sys.exit(1)
                else:
                    self.evaluate(expression=expression)
    
        def evaluate(self, expression):
            raise NotImplementedError("This method must be implemented in a derived class")
    
  • You can use abc.ABCMeta together with abc.abstractmethod to provide 'truly' abstract methods:

    import abc
    
    class Reple(object, metaclass=abc.ABCMeta):
        ...
    
        @abc.abstractmethod
        def evaluate(self, expression):
            return
    

    And here's a Python 2 version:

    import abc
    
    class Reple(object):
        __metaclass__ = abc.ABCMeta
        ...
    
        @abc.abstractmethod
        def evaluate(self, expression):
            return
    
  • You don't have any docstrings, so it's hard to tell what certain methods do or how they should be implemented by subclasses of Reple.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ PS1 is a nod to us Linux hackers. Bash was my introduction to programming. I \$\endgroup\$ Commented Dec 24, 2017 at 0:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.