This directory contains examples that demonstrate how to use yapcol to build real-world parsers.
We provide two different implementations of a simple arithmetic expression parser and evaluator. Both examples implement the same grammar and logic, but they differ in how they process the input.
This example parses the input directly: the input string is converted into a stream of characters (char) and passed to
the parser. Its parsing logic is a bit complex because it must handle both lexical and syntactic structures.
This example performs lexical analysis (a.k.a. lexing, tokenization) on the input string before sending it to the
parser. The parser's input is a stream of Token (a user-defined type) instead of a stream of characters. This approach
leverages separation of concerns: lexical and syntactic structures are analyzed at different stages.
Each example is its own binary crate that can be executed using cargo run --example <name>:
# Run string-based example
cargo run --example evaluate_expression_string
# Run token-based example
cargo run --example evaluate_expression_tokenThe examples are interactive command-line applications that query the user for arithmetic expressions that use (a combination of) the following:
- Addition and subtraction (e.g.,
5+32-1). - Multiplication and division (e.g.,
96/4*3). - Exponentiation (e.g.,
2^3). - Parenthesis to group expressions, possibly changing the precedence of operators (e.g.,
(4+3)*2).
Once the application starts, it requests an expression as input:
Enter expression, or 'q' to quitThe user can then type an expression and submit it by pressing the Enter/⏎ key. The application will then attempt to parse and evaluate the provided expression. If it succeeds, the value of the expression is displayed:
Enter expression, or 'q' to quit:
(4+3)*2
Success: 14If it fails, an error will be displayed:
Enter expression, or 'q' to quit:
4*banana
Failed to parse expression: UnexpectedTokenThe application keeps requesting new input expressions indefinitely, or until the user requests it to quit by providing
q as input.
The examples share code inside the expression module. It contains:
- The
ExpressionandOperatorenums. - The
evaluatefunction.
Both examples implement the same grammar, described here in Backus–Naur Form (BNF):
<expression> ::= <term>
| <expression> "+" <term>
| <expression> "-" <term>
<term> ::= <factor>
| <term> "*" <factor>
| <term> "/" <factor>
<factor> ::= <exponential>
| <exponential> "^" <factor>
<exponential> ::= <number>
| "(" <expression> ")"
<number> ::= <digit>
| <number> <digit>
<digit> ::= "0" | "1" | "2" | "3" | "4"
| "5" | "6" | "7" | "8" | "9"
Where:
- Operator precedence follows the given order:
+/-<*//<^. - Parenthesis can be used to change the operator precedence.
- Addition, subtraction, multiplication and division are left-associative.
- Exponentiation is right-associative.
You can run the unit tests for each example to see how individual components are verified:
# Test string-based example
cargo test --example evaluate_expression_string
# Test token-based example
cargo test --example evaluate_expression_token