Mate is a library designed for parsing and calculating arithmetic expressions inputted as strings. It utilizes a Lexer structure, similar to those found in interpreted programming languages, to parse string input into a token list. The Calculator structure is then used to compute the final result from this token list. A general wrapper structure is also implemented that encapsulates both the Lexer and Calculator, simplifying the process of calculating arithmetic expression results without the need for manual parsing and calculation.
Note: This project serves as a demonstration and is not intended for production use. It represents my initial steps in learning to create a programming language.
This crate is available on crates.io and can be added to your project's dependencies in your Cargo.toml
file.
[dependencies]
mate-rs = "0.1.4"
Mate
is a general wrapper structure for Lexer
and Calculator
. It provides a single method for calculating results from string input.
use mate_rs::mate::Mate;
let result = Mate::calculate("6 * 7");
match result {
Ok(v) => assert_eq!(v, 42.0),
Err(_) => {
// Handle error ...
}
};
Lexer
is the primary structure that parses string input into a token list. Calculator
is used to compute the final result from the Lexer
's output.
use mate_rs::{calculator::Calculator, lexer::Lexer};
let input = "[ (2 + 5) * (5 - 9 + (8 - 5)) ] + 35";
let tokens = Lexer::lex(input.clone()).unwrap(); // Error handling should also be implemented
View the generated token tree
// The generated tokens will resemble the following:
//
// Token(
// type: SUBEXP,
// tokens: [
// Token(
// type: SUBEXP,
// tokens: [
// Token(type: NUMBER, literal: "2")
// Token(type: PLUS, literal: "+")
// Token(type: NUMBER, literal: "5")
// ],
// ),
// Token(type: PRODUCT, literal: "*"),
// Token(
// type: SUBEXP,
// tokens: [
// Token(type: NUMBER, literal: "5")
// Token(type: MINUS, literal: "-")
// Token(type: NUMBER, literal: "9")
// Token(type: PLUS, literal: "+")
// Token(
// type: SUBEXP,
// tokens: [
// Token(type: NUMBER, literal: "8")
// Token(type: PLUS, literal: "-")
// Token(type: NUMBER, literal: "5")
// ],
// ),
// ],
// ),
// ],
// ),
// Token(type: PLUS, literal: "+")
// Token(type: NUMBER, literal: "35")
// The result is calculated from the tokens using the X/O/Y algorithm.
//
// ╭────────╮ ╭───────────╮ ╭────────╮
// │ NUMBER │ │ OPERATION │ │ NUMBER │
// ╰────────╯ ╰───────────╯ ╰────────╯
// ╰───╮ │ ╭───╯
// ▼ ▼ ▼
// X [+, -, *, /] Y
//
let result = Calculator::calculate(tokens, input.clone());
Mate primarily consists of two main structures: [Lexer] and [Calculator]. The [Lexer] structure is responsible for parsing and interpreting the given string expression, while the [Calculator] structure calculates the final result from the parsed tokens.
The Lexer iterates through the given input string, reading and converting each character into a [Token] structure. The main types of tokens include:
ILLEGAL
- Represents an illegal character.NUMBER
- Represents a number.MINUS
,PLUS
,PRODUCT
,DIVIDE
,PERCENTAGE
,POWER
- Represents operations.LPAREN
,RPAREN
- Represents parentheses.LABS
,RABS
- Represents absolute values.SUBEXP
- Represents sub-expressions, which are expressions inside parentheses, absolute values, or combinations of division and multiplication.
The Lexer's lex
function converts each character into one of these tokens. It groups tokens related to multiplication or division operations into a single sub-expression to maintain the correct operation priority. It also nests parentheses, absolute values, and powers using a custom level-to-expression algorithm.
The level-to-expression algorithm is a mapping algorithm that maps a specific expression to its nesting level.
For example, given the token list (2 + 5) : (5 - 9 / (8 - 5))
, the generated result will be:
This approach ensures the correct operation priority is maintained.
The Calculator takes the parsed token list and calculates the final result. It uses a custom X/O/Y
algorithm, also known as X/OPERATION/Y
, where X
and Y
are numbers, and O
is an operation. If X
or Y
cannot be obtained, they are taken as zero.
╭────────╮ ╭───────────╮ ╭────────╮
│ NUMBER │ │ OPERATION │ │ NUMBER │
╰────────╯ ╰───────────╯ ╰────────╯
╰───╮ │ ╭───╯
▼ ▼ ▼
X [+, -, *, /] Y
The syntax of Mate
is designed to be as simple and basic as possible. It follows the plain-text mathematics syntax with a few customizations. Here are some examples:
2 + 5
and 2 - 5
are valid syntaxes. (in x <operation> y
where x and y could be NUMBER
or SUBEXP
).
4 * 2
, 4 / 2
, and 4 % 2
are valid syntaxes. (in x <operation> y
where x and y could be NUMBER
or SUBEXP
). Also, 4(10 / 2)
or 4(2)
are valid syntaxes for multiplication.
4 ^ 2
is a valid syntax. (in x <operation> y
where x and y could be NUMBER
or SUBEXP
). Continuous powers are also accepted: 4 ^ 2 ^ 3
, which will be automatically converted to 4 ^ (2 ^ 3)
.
(2 * 5)
is a valid syntax. (in x <operation> y
where x and y could be NUMBER
or SUBEXP
). Nested parentheses expressions are also accepted: (2 * ((5 * 2) / (9 - 5)))
.
[2 - 12]
is a valid syntax. (in x <operation> y
where x and y could be NUMBER
or SUBEXP
). Nested absolute-value expressions are also accepted: [7 - 14] * [5 - 9 / [5 - 3]]
.