C++ Interpreter Design Pattern Example

1. Definition

The Interpreter Design Pattern provides a way to evaluate language grammars or expressions for particular languages. It primarily involves defining a representation of the grammar and an interpreter to process it.

2. Problem Statement

You want to develop a system that can evaluate expressions. For instance, a calculator that takes mathematical expressions in string format and returns the computed result. How do you design a system that can understand and evaluate these expressions?

3. Solution

Define a grammar for the language in which expressions will be written. Represent each grammar rule as a class. Interpret expressions by constructing a syntax tree composed of instances of these classes and then invoking the interpreter method on this tree.

4. Real-World Use Cases

1. Compilers and interpreters for programming languages.

2. Rule engines where business rules are specified in a domain-specific language.

3. Query processors in databases.

5. Implementation Steps

1. Define an abstract Expression class that declares an interpret() method.

2. For each grammar rule, derive a concrete class from the Expression class.

3. Use a parser (possibly created by a third-party tool) to build abstract syntax trees made of these concrete classes.

4. Invoke the interpret() method on these syntax trees to evaluate expressions.

6. Implementation in C++

// Step 1: Abstract Expression class
class Expression {
public:
    virtual ~Expression() {}
    virtual int interpret() const = 0;
};
// Step 2: Concrete Expressions
class Number : public Expression {
private:
    int number;
public:
    explicit Number(int n) : number(n) {}
    int interpret() const override {
        return number;
    }
};
class Add : public Expression {
private:
    Expression* left;
    Expression* right;
public:
    Add(Expression* l, Expression* r) : left(l), right(r) {}
    int interpret() const override {
        return left->interpret() + right->interpret();
    }
    ~Add() {
        delete left;
        delete right;
    }
};
// Example usage
int main() {
    Expression* five = new Number(5);
    Expression* three = new Number(3);
    Expression* sum = new Add(five, three);
    std::cout << sum->interpret() << std::endl;
    delete sum;
    return 0;
}

Output:

8

Explanation:

1. Expression is the abstract base class for all nodes in the abstract syntax tree.

2. Number is a terminal expression that represents a number.

3. Add is a non-terminal expression that represents the addition operation.

In the main function, we create expressions for the numbers 5 and 3. We then create an addition expression that adds the two numbers. Invoking the interpret method on the Add expression gives the result 8.

7. When to use?

Use the Interpreter pattern when:

1. The grammar of the language is simple. For complex grammars, tools like parsers or compilers are more appropriate.

2. Efficiency is not a primary concern. Interpreters can be slow due to the overhead of interpreting an abstract syntax tree.

3. You need a tool to interpret expressions in a specific domain or language.


Comments