Easy Patterns in Python: The Interpreter

Introduction

The Interpreter pattern can be used to interpret and evaluate sentences in a language. The idea is to make a class for each symbol, both terminal and non-terminal and construct a syntax tree which can be evaluated and interpreted.

The interpreter pattern I will use here is probably one of the simplest implementations of this pattern. Before you use this pattern, what you need is a well-defined grammar for your language so that it can be evaluated and interpreted.

The simplest form of the interpreter looks like this:

This is not the whole picture, as we also need to build a syntax tree, which can be done in a parser, which is outside the scope of this article.

I think the idea will become much clearer when we implement this pattern.

Implementation in Python

We will start with the Expression base class:

class Expression:
    def interpret(self) -> bool:
        pass

All this class does is interpret an expression, which in our case will return a boolean.

Now, since we are returning booleans, we can define an AndExpression:

class AndExpression(Expression):
    _left_expression: Expression = None
    _right_expression: Expression = None

    def __init__(self, left: Expression, right: Expression):
        self._left_expression = left
        self._right_expression = right

    def interpret(self) -> bool:
        return self._left_expression.interpret() and self._right_expression.interpret()

Two notes:

  1. In the constructor we pass two expressions to the constructor
  2. The interpret() method just interprets the expression and performs an and-operation. An and-operation, as you may know, returns true if both operands are true

The OrExpression is built in a similar way. Not that an or-operation is true if either one operand is true or both are true.

class OrExpression(Expression):
    _left_expression: Expression = None
    _right_expression: Expression = None

    def __init__(self, left: Expression, right: Expression):
        self._left_expression = left
        self._right_expression = right

    def interpret(self) -> bool:
        return self._left_expression.interpret() or self._right_expression.interpret()

Now we also need a TerminalExpression:

class TerminalExpression(Expression):
    _data: str = None

    def __init__(self, value: str):
        self._data = value

    def interpret(self) -> bool:
        return 'hello' in self._data

Some notes on this code:

Also a simple expression:

  1. We first pass a piece of string data to the constructor
  2. Next, we return true in the interpret() method if the string contains ‘hello’

Time to test:

We can now test this setup:

if __name__ == "__main__":
    expression1: Expression = TerminalExpression("hello")
    expression2: Expression = TerminalExpression("world")

    expression3: Expression = OrExpression(expression1, expression2)
    print(expression3.interpret())

    expression4: Expression = TerminalExpression("hello everyone")
    expression5: Expression = AndExpression(expression1, expression4)
    print(expression5.interpret())

Line by line

  • We construct two objects of the TerminalExpression class.
  • Next we pass those to an OrExpression and interpret that. That should return True
  • We make a new TerminalExpression and pass that to an AndExpression
  • We interpret that, and it should also return True. However, try changing the ‘hello’ to ‘hallo’ for example, and the last expression will return False.

Implementing this was quite easy. The hardest part will probably be building a parser so that these, and more expressions, can be put into some sort of Abstract Syntax Tree, and be evaluated from there. This will be the subject of another post.

The beauty of this thing is the fact that it forms a recursive datastructure, which translates quite elegantly into Python.

Leave a Reply

Your email address will not be published. Required fields are marked *