Python Tutorial: Interpreter Pattern 🎯

beginner
13 min

Python Tutorial: Interpreter Pattern 🎯

Welcome to our comprehensive guide on the Interpreter Pattern in Python! In this lesson, we'll explore how to use this powerful design pattern to parse and evaluate expressions in a flexible and extendable manner. Let's dive in!

Understanding the Interpreter Pattern 📝

The Interpreter Pattern is a behavioral design pattern that allows the interpretation of a language-like grammar. In simple terms, it allows us to create a program that understands and executes a set of rules or instructions.

Here are the key components of the Interpreter Pattern:

  1. Language (abstract grammar): Defines the abstract syntax and semantics of the language to be interpreted.
  2. Abstract Interpreter: Defines the interface for interpreters, including parsing and evaluating the language.
  3. Concrete Interpreter: Implements the interface of the Abstract Interpreter for a specific language and handles the parsing and evaluation of that language.
  4. Terminal and Non-terminal Expressions: These are the basic building blocks of the language, with terminals being simple elements and non-terminals being complex expressions.

Creating an Expression Grammar 📝

To create an Expression Grammar, we'll define a simple calculator language with the following syntax:

<expression> ::= <term> ("+" | "-")? <expression> | <term> <term> ::= <factor> ("*" | "/")? <term> | <factor> <factor> ::= <number> | "(" <expression> ")"

This grammar allows us to define expressions containing addition, subtraction, multiplication, and division. Let's see how we can implement this in Python.

Implementing the Interpreter Pattern 💡

First, let's create classes for the Abstract Interpreter, Abstract Terminal, and Abstract Non-terminal Expressions:

python
from abc import ABC, abstractmethod class AbstractExpression(ABC): @abstractmethod def interpreter(self, context): pass class AbstractTerminalExpression(AbstractExpression): pass class AbstractNonterminalExpression(AbstractExpression): pass

Next, we'll create concrete implementations for our specific calculator language:

python
class Number(AbstractTerminalExpression): def __init__(self, value): self.value = value def interpreter(self, context): return self.value class Addition(AbstractNonterminalExpression): def __init__(self, left, right): self.left = left self.right = right def interpreter(self, context): return self.left.interpreter(context) + self.right.interpreter(context) class Subtraction(AbstractNonterminalExpression): def __init__(self, left, right): self.left = left self.right = right def interpreter(self, context): return self.left.interpreter(context) - self.right.interpreter(context) class Multiplication(AbstractNonterminalExpression): def __init__(self, left, right): self.left = left self.right = right def interpreter(self, context): return self.left.interpreter(context) * self.right.interpreter(context) class Division(AbstractNonterminalExpression): def __init__(self, left, right): self.left = left self.right = right def interpreter(self, context): return self.left.interpreter(context) / self.right.interpreter(context) class Grouping(AbstractExpression): def __init__(self, expression): self.expression = expression def interpreter(self, context): return self.expression.interpreter(context)

With the concrete implementations in place, we can now create and evaluate expressions:

python
def parse_expression(expression_str): tokens = expression_str.split() return parse_tokens(tokens) def parse_tokens(tokens): if not tokens: return None current_token = tokens[0] if current_token.isdigit(): return Number(int(current_token)) if current_token == "(": return Grouping(parse_tokens(tokens[1:-1])) left = parse_tokens(tokens[1:]) operator = tokens[0] right = parse_tokens(tokens[2:]) if operator == "+": return Addition(left, right) elif operator == "-": return Subtraction(left, right) elif operator == "*": return Multiplication(left, right) elif operator == "/": return Division(left, right) def evaluate_expression(expression): context = Context() result = expression.interpreter(context) return result class Context: def __init__(self): self.operand_stack = [] def execute_operand(self, operand): self.operand_stack.append(operand) def execute_command(self, command): if command == "+": self.pop_operand() self.pop_operand() self.operand_stack.append(lambda x, y: x + y) elif command == "-": self.pop_operand() self.pop_operand() self.operand_stack.append(lambda x, y: x - y) elif command == "*": self.pop_operand() self.pop_operand() self.operand_stack.append(lambda x, y: x * y) elif command == "/": self.pop_operand() self.pop_operand() self.operand_stack.append(lambda x, y: x / y) def pop_operand(self): return self.operand_stack.pop() # Example usage: expression = parse_expression("3 + ( 5 * 2 )") result = evaluate_expression(expression) print(result) # Output: 13
Quick Quiz
Question 1 of 1

What is the Interpreter Pattern in Python?


This tutorial is just the beginning of exploring the Interpreter Pattern in Python. In the next lesson, we'll dive deeper and learn about optimizing interpreters, recursive descent parsing, and more! 📝