docs: improve interpreter

This commit is contained in:
Ilkka Seppälä committed 2024-05-01 11:01:49 +03:00
1 parent 943f3fbb26
commit 58ec27de87
2 files changed
+40 -28

No files matched your search

+39 -27
View File
@@ -3,37 +3,36 @@ title: Interpreter
category: Behavioral category: Behavioral
language: en language: en
tag: tag:
- Abstraction
- Data transformation
- Decoupling
- Domain
- Gang of Four - Gang of Four
- Polymorphism
- Runtime
--- ---
## Intent ## Intent
Given a language, define a representation for its grammar along with an interpreter that uses the The Interpreter pattern is used to define a grammatical representation for a language and provides an interpreter to deal with this grammar.
representation to interpret sentences in the language.
## Explanation ## Explanation
Real-world example Real-world example
> The halfling kids are learning basic math at school. They start from the very basics "1 + 1", > Consider a calculator application designed to interpret and calculate expressions entered by users. The application uses the Interpreter pattern to parse and evaluate arithmetic expressions such as "5 + 3 * 2". Here, the Interpreter translates each part of the expression into objects that represent numbers and operations (like addition and multiplication). These objects follow a defined grammar that allows the application to understand and compute the result correctly based on the rules of arithmetic. Each element of the expression corresponds to a class in the program's structure, simplifying the parsing and evaluation process for any inputted arithmetic formula.
> "4 - 2", "5 + 5", and so forth.
In plain words In plain words
> Interpreter pattern interprets sentences in the desired language. > The Interpreter design pattern defines a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences in the language.
Wikipedia says Wikipedia says
> In computer programming, the interpreter pattern is a design pattern that specifies how to > In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client.
> evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or
> nonterminal) in a specialized computer language. The syntax tree of a sentence in the language
> is an instance of the composite pattern and is used to evaluate (interpret) the sentence for
> a client.
**Programmatic example** **Programmatic example**
To be able to interpret basic math, we need a hierarchy of expressions. The basic abstraction for To be able to interpret basic math, we need a hierarchy of expressions. The basic abstraction for it is the `Expression` class.
it is the `Expression` class.
```java ```java
public abstract class Expression { public abstract class Expression {
@@ -45,8 +44,7 @@ public abstract class Expression {
} }
``` ```
The simplest of the expressions is the `NumberExpression` that contains only a single integer The simplest of the expressions is the `NumberExpression` that contains only a single integer number.
number.
```java ```java
public class NumberExpression extends Expression { public class NumberExpression extends Expression {
@@ -73,8 +71,7 @@ public class NumberExpression extends Expression {
} }
``` ```
The more complex expressions are operations such as `PlusExpression`, `MinusExpression`, and The more complex expressions are operations such as `PlusExpression`, `MinusExpression`, and `MultiplyExpression`. Here's the first of them, the others are similar.
`MultiplyExpression`. Here's the first of them, the others are similar.
```java ```java
public class PlusExpression extends Expression { public class PlusExpression extends Expression {
@@ -99,7 +96,7 @@ public class PlusExpression extends Expression {
} }
``` ```
Now we are able to show the interpreter pattern in action parsing some simple math. Now, we are able to show the interpreter pattern in action parsing some simple math.
```java ```java
// the halfling kids are learning some basic math at school // the halfling kids are learning some basic math at school
@@ -117,8 +114,7 @@ Now we are able to show the interpreter pattern in action parsing some simple ma
// the stack // the stack
var rightExpression = stack.pop(); var rightExpression = stack.pop();
var leftExpression = stack.pop(); var leftExpression = stack.pop();
LOGGER.info("popped from stack left: {} right: {}", LOGGER.info("popped from stack left: {} right: {}", leftExpression.interpret(),rightExpression.interpret());
leftExpression.interpret(), rightExpression.interpret());
var operator = getOperatorInstance(s, leftExpression, rightExpression); var operator = getOperatorInstance(s, leftExpression, rightExpression);
LOGGER.info("operator: {}", operator); LOGGER.info("operator: {}", operator);
var result = operator.interpret(); var result = operator.interpret();
@@ -151,15 +147,14 @@ result: 8
## Class diagram ## Class diagram
![alt text](./etc/interpreter_1.png "Interpreter") ![Interpreter](./etc/interpreter_1.png "Interpreter")
## Applicability ## Applicability
Use the Interpreter pattern when there is a language to interpret, and you can represent statements Use the Interpreter pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when
in the language as abstract syntax trees. The Interpreter pattern works best when
* The grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time * The grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time.
* Efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable * Efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable.
## Known uses ## Known uses
@@ -167,10 +162,27 @@ in the language as abstract syntax trees. The Interpreter pattern works best whe
* [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) * [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html)
* All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) * All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html)
* [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) * [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html)
* SQL parsers in various database management systems.
## Consequences
Benefits:
* Adds new operations to interpret expressions easily without changing the grammar or classes of data.
* Implements grammar directly in the language, making it easy to modify or extend.
Trade-offs:
* Can become complex and inefficient for large grammars.
* Each rule in the grammar results in a class, leading to a large number of classes for complex grammars.
## Related Patterns
* [Composite](https://java-design-patterns.com/patterns/composite/): Often used together, where the Interpreter pattern leverages the Composite pattern to represent the grammar as a tree structure.
* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Useful for sharing state to reduce memory usage in the Interpreter pattern, particularly for interpreters that deal with repetitive elements in a language.
## Credits ## Credits
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) * [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) * [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) * [Refactoring to Patterns](https://amzn.to/3VOO4F5)
@@ -60,7 +60,7 @@ class NumberExpressionTest extends ExpressionTest<NumberExpression> {
*/ */
@ParameterizedTest @ParameterizedTest
@MethodSource("expressionProvider") @MethodSource("expressionProvider")
void testFromString(NumberExpression first) throws Exception { void testFromString(NumberExpression first) {
final var expectedValue = first.interpret(); final var expectedValue = first.interpret();
final var testStringValue = String.valueOf(expectedValue); final var testStringValue = String.valueOf(expectedValue);
final var numberExpression = new NumberExpression(testStringValue); final var numberExpression = new NumberExpression(testStringValue);