Adding Interpreter design pattern (#211)
parent
1d173dab4e
commit
a524dc27ea
@ -0,0 +1,147 @@
|
||||
# Interpreter
|
||||
|
||||
## Description
|
||||
|
||||
If a problem occurs very often and requires long and repetitive steps to solve
|
||||
it, then the problem instances might be expressed in a simple language and an
|
||||
interpreter object could solve it by interpreting the sentences written in this
|
||||
simple language.
|
||||
|
||||
Basically, for any kind of problems we define:
|
||||
|
||||
- a [domain specific language](https://en.wikipedia.org/wiki/Domain-specific_language),
|
||||
- a grammar for this language,
|
||||
- an interpreter that solves the problem instances.
|
||||
|
||||
## Motivation
|
||||
|
||||
Our goal is to translate simple mathematical expressions into postfix expressions
|
||||
(or [Reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation))
|
||||
For simplicity, our expressions consist of ten digits `0`, ..., `9` and two
|
||||
operations `+`, `-`. For example, the expression `2 + 4` is translated into
|
||||
`2 4 +`.
|
||||
|
||||
## Context Free Grammar for our problem
|
||||
|
||||
Our task is translate infix expressions into postfix ones. Let's define a context
|
||||
free grammar for a set of infix expressions over `0`, ..., `9`, `+`, and `-`,
|
||||
where:
|
||||
|
||||
- terminal symbols: `0`, ..., `9`, `+`, `-`
|
||||
- non-terminal symbols: `exp`, `term`
|
||||
- start symbol is `exp`
|
||||
- and the following are production rules
|
||||
|
||||
```ignore
|
||||
exp -> exp + term
|
||||
exp -> exp - term
|
||||
exp -> term
|
||||
term -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
||||
```
|
||||
|
||||
__NOTE:__ This grammar should be further transformed depending on what we are going
|
||||
to do with it. For example, we might need to remove left recursion. For more
|
||||
details please see [Compilers: Principles,Techniques, and Tools
|
||||
](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools)
|
||||
(aka Dragon Book).
|
||||
|
||||
## Solution
|
||||
|
||||
We simply implement a recursive descent parser. For simplicity's sake, the code
|
||||
panics when an expression is syntactically wrong (for example `2-34` or `2+5-`
|
||||
are wrong according to the grammar definition).
|
||||
|
||||
```rust
|
||||
pub struct Interpreter<'a> {
|
||||
it: std::str::Chars<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Interpreter<'a> {
|
||||
|
||||
pub fn new(infix: &'a str) -> Self {
|
||||
Self { it: infix.chars() }
|
||||
}
|
||||
|
||||
fn next_char(&mut self) -> Option<char> {
|
||||
self.it.next()
|
||||
}
|
||||
|
||||
pub fn interpret(&mut self, out: &mut String) {
|
||||
self.term(out);
|
||||
|
||||
while let Some(op) = self.next_char() {
|
||||
if op == '+' || op == '-' {
|
||||
self.term(out);
|
||||
out.push(op);
|
||||
} else {
|
||||
panic!("Unexpected symbol '{}'", op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn term(&mut self, out: &mut String) {
|
||||
match self.next_char() {
|
||||
Some(ch) if ch.is_digit(10) => out.push(ch),
|
||||
Some(ch) => panic!("Unexpected symbol '{}'", ch),
|
||||
None => panic!("Unexpected end of string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let mut intr = Interpreter::new("2+3");
|
||||
let mut postfix = String::new();
|
||||
intr.interpret(&mut postfix);
|
||||
assert_eq!(postfix, "23+");
|
||||
|
||||
intr = Interpreter::new("1-2+3-4");
|
||||
postfix.clear();
|
||||
intr.interpret(&mut postfix);
|
||||
assert_eq!(postfix, "12-3+4-");
|
||||
}
|
||||
```
|
||||
|
||||
## Discussion
|
||||
|
||||
There may be a wrong perception that the Interpreter design pattern is about design
|
||||
grammars for formal languages and implementation of parsers for these grammars.
|
||||
In fact, this pattern is about expressing problem instances in a more specific
|
||||
way and implementing functions/classes/structs that solve these problem instances.
|
||||
Rust language has `macro_rules!` that allow to define special syntax and rules
|
||||
on how to expand this syntax into source code.
|
||||
|
||||
In the following example we create a simple `macro_rules!` that computes
|
||||
[Euclidean length](https://en.wikipedia.org/wiki/Euclidean_distance) of `n`
|
||||
dimensional vectors. Writing `norm!(x,1,2)` might be easier to express and more
|
||||
efficient than packing `x,1,2` into a `Vec` and calling a function computing
|
||||
the length.
|
||||
|
||||
```rust
|
||||
macro_rules! norm {
|
||||
($($element:expr),*) => {
|
||||
{
|
||||
let mut n = 0.0;
|
||||
$(
|
||||
n += ($element as f64)*($element as f64);
|
||||
)*
|
||||
n.sqrt()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = -3f64;
|
||||
let y = 4f64;
|
||||
|
||||
assert_eq!(3f64, norm!(x));
|
||||
assert_eq!(5f64, norm!(x, y));
|
||||
assert_eq!(0f64, norm!(0, 0, 0));
|
||||
assert_eq!(1f64, norm!(0.5, -0.5, 0.5, -0.5));
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Interpreter pattern](https://en.wikipedia.org/wiki/Interpreter_pattern)
|
||||
- [Context free grammar](https://en.wikipedia.org/wiki/Context-free_grammar)
|
||||
- [macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html)
|
Loading…
Reference in New Issue