Interpreter Design Pattern in Go

1. Definition

The Interpreter Design Pattern provides a way to evaluate language grammar or expressions for particular languages. It represents simple language grammar with classes and uses these classes to interpret the sentences of the language.

2. Problem Statement

Consider needing to evaluate expressions written in a simple custom language or DSL (Domain Specific Language). Hand-coding the parsing and evaluation logic for every new expression or language construct can become tedious, error-prone, and hard to maintain.

3. Solution

Define a grammar for the language, represent each grammar rule with a class, and use a parser that composes these classes based on the input expression. The resulting object structure (often an abstract syntax tree) can then be traversed to evaluate the expression.

4. Real-World Use Cases

1. Evaluating mathematical expressions.

2. SQL parsers that translate SQL queries into executable commands.

3. Configuration or rule engines that interpret custom DSLs.

5. Implementation Steps

1. Define an abstract expression that declares an interpret() method.

2. For each grammar rule, create a concrete class that implements the interpret() method.

3. Implement the parser that constructs the abstract syntax tree of the expression using the grammar classes.

4. To evaluate an expression, traverse the tree and call the interpret() method.

6. Implementation in Go

// Abstract Expression
type Expression interface {
	Interpret(context string) bool
}
// TerminalExpression
type TerminalExpression struct {
	data string
}
func (t *TerminalExpression) Interpret(context string) bool {
	if strings.Contains(context, t.data) {
		return true
	}
	return false
}
// OrExpression
type OrExpression struct {
	expr1 Expression
	expr2 Expression
}
func (o *OrExpression) Interpret(context string) bool {
	return o.expr1.Interpret(context) || o.expr2.Interpret(context)
}
// AndExpression
type AndExpression struct {
	expr1 Expression
	expr2 Expression
}
func (a *AndExpression) Interpret(context string) bool {
	return a.expr1.Interpret(context) && a.expr2.Interpret(context)
}
// Client code
func main() {
	john := &TerminalExpression{data: "John"}
	married := &TerminalExpression{data: "Married"}
	isMarriedMan := &AndExpression{expr1: john, expr2: married}
	fmt.Println(isMarriedMan.Interpret("John is a Married man"))
}

Output:

true

Explanation:

1. The Expression interface is the abstract representation of any expression and declares the Interpret() method.

2. TerminalExpression checks if the data is contained within the context.

3. OrExpression and AndExpression are compound expressions. They evaluate based on the result of their individual expressions.

4. In the client code, an AndExpression is constructed to check if "John" and "Married" are both present in the string. The interpretation of "John is a Married man" returns true.

7. When to use?

Use the Interpreter Pattern when:

1. There's a grammar/syntax to interpret, and this grammar is well-defined.

2. Efficiency isn't a top concern – the pattern can be slow and complex.

3. You want to provide an easy way to extend and add new ways of interpreting the language.


Comments