Skip to content

Commit 553d6e6

Browse files
committed
fix oopsie
0 parents  commit 553d6e6

File tree

8 files changed

+422
-0
lines changed

8 files changed

+422
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
/.vscode
3+
Cargo.lock

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "math_interpreter"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
colored = "2.1.0"
8+
strum = "0.26"
9+
strum_macros = "0.26"

src/errors.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::fmt::Display;
2+
3+
use colored::Colorize;
4+
5+
#[derive(Debug, Clone)]
6+
pub enum ErrorReason {
7+
Error(String),
8+
}
9+
10+
impl Display for ErrorReason {
11+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12+
match self {
13+
ErrorReason::Error(reason) => write!(f, "{}", reason.red()),
14+
}
15+
}
16+
}
17+
18+
#[derive(Debug, Clone)]
19+
pub struct ParserError {
20+
#[allow(dead_code)]
21+
pub error: ErrorReason,
22+
pub description: String,
23+
}
24+
25+
impl ParserError {
26+
pub fn new(error: ErrorReason) -> Self {
27+
Self {
28+
description: error.to_string(),
29+
error,
30+
}
31+
}
32+
}

src/lexer.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use std::iter::Peekable;
2+
3+
use crate::{
4+
errors::ErrorReason,
5+
token::{Token, TokenKind, TokenValue},
6+
};
7+
8+
const DIGITS: &str = ".0123456789";
9+
10+
pub struct Lexer {
11+
source: String,
12+
}
13+
14+
impl Lexer {
15+
pub fn new(source: String) -> Self {
16+
Self { source }
17+
}
18+
19+
pub fn tokenize(&self) -> Result<Vec<Token>, ErrorReason> {
20+
let mut tokens: Vec<Token> = Vec::new();
21+
let mut chars = self
22+
.source
23+
.chars()
24+
.filter(|c| !c.is_whitespace())
25+
.peekable();
26+
27+
while let Some(&current) = chars.peek() {
28+
// Best solution i could find to making sure it doesn't skip
29+
if DIGITS.contains(current) {
30+
tokens.push(self.generate_number(&mut chars)?);
31+
} else {
32+
let token = match current {
33+
'+' => Token::new(TokenKind::Plus, None),
34+
'-' => Token::new(TokenKind::Minus, None),
35+
'*' => Token::new(TokenKind::Multiply, None),
36+
'/' => Token::new(TokenKind::Divide, None),
37+
'^' => Token::new(TokenKind::Power, None),
38+
'(' => Token::new(TokenKind::LParen, None),
39+
')' => Token::new(TokenKind::RParen, None),
40+
_ => Token::new(
41+
TokenKind::Unknown,
42+
Some(TokenValue::StrValue(current.to_string())),
43+
),
44+
};
45+
tokens.push(token);
46+
chars.next();
47+
}
48+
}
49+
50+
Ok(tokens)
51+
}
52+
53+
fn generate_number<I>(&self, chars: &mut Peekable<I>) -> Result<Token, ErrorReason>
54+
where
55+
I: Iterator<Item = char>,
56+
{
57+
let mut decimal_point_counter = 0;
58+
let mut number = String::new();
59+
60+
while let Some(&current) = chars.peek() {
61+
if current == '.' {
62+
decimal_point_counter += 1;
63+
if decimal_point_counter > 1 {
64+
return Err(ErrorReason::Error("Too many decimal points".into()));
65+
}
66+
}
67+
number.push(current);
68+
chars.next();
69+
70+
// Peek the next character and check if it's valid for a number
71+
if let Some(&next_char) = chars.peek() {
72+
if !DIGITS.contains(next_char) {
73+
if number.trim() == "." {
74+
return Err(ErrorReason::Error("Random decimal place found ".into()));
75+
}
76+
break;
77+
}
78+
}
79+
}
80+
81+
Ok(Token::new(
82+
TokenKind::Number,
83+
Some(TokenValue::NumValue(number.parse::<f64>().unwrap_or_else(
84+
|_| panic!("Error parsing number '{number}'"),
85+
))),
86+
))
87+
}
88+
}

src/main.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
mod errors;
2+
mod lexer;
3+
mod parser;
4+
mod token;
5+
mod traits;
6+
7+
use std::io::{stdin, stdout, Write};
8+
9+
use colored::Colorize;
10+
use errors::{ErrorReason, ParserError};
11+
use lexer::Lexer;
12+
use parser::Parser;
13+
use token::{Token, TokenKind, TokenValue};
14+
15+
/// Prompts user for input and returns trimmed result
16+
fn prompt_input(prompt: &str) -> String {
17+
let mut input = String::new();
18+
19+
print!("{}", prompt);
20+
stdout().flush().unwrap();
21+
stdin().read_line(&mut input).unwrap();
22+
23+
input.trim().to_owned()
24+
}
25+
26+
fn main() {
27+
println!("MATH INTERPRETER");
28+
println!("----------------");
29+
30+
loop {
31+
let input = prompt_input("> ");
32+
33+
let lexer = Lexer::new(input);
34+
let tokens_result = lexer.tokenize();
35+
if let Err(err) = tokens_result {
36+
println!("{}", err);
37+
continue;
38+
}
39+
let tokens = tokens_result.unwrap();
40+
41+
let token_errors = get_tokens_errors(tokens.clone());
42+
if !token_errors.is_empty() {
43+
for err in token_errors {
44+
println!(
45+
"{}",
46+
ParserError::new(ErrorReason::Error(format!("Invalid sequence: '{}'", err)))
47+
.description
48+
);
49+
}
50+
println!();
51+
} else {
52+
pretty_print_tokens(tokens.clone());
53+
let mut parser = Parser::new(tokens.into_iter());
54+
let result = parser.parse_expr();
55+
println!("{:?}", result);
56+
println!(
57+
"{} {}",
58+
"RESULT:".bright_green(),
59+
result.unwrap().evaluate().to_string().bright_green()
60+
);
61+
}
62+
}
63+
}
64+
65+
fn pretty_print_tokens(tokens: Vec<Token>) {
66+
let token_len = tokens.len();
67+
tokens.iter().enumerate().for_each(|(i, token)| {
68+
if i == token_len - 1 {
69+
println!("{token}");
70+
} else {
71+
print!("{}, ", token);
72+
}
73+
})
74+
}
75+
76+
fn get_tokens_errors(tokens: Vec<Token>) -> Vec<String> {
77+
let mut error_str: Vec<String> = vec![];
78+
let mut current_sequence: String = String::new();
79+
let mut tokens_iter = tokens.iter().peekable();
80+
while let Some(token) = tokens_iter.next() {
81+
// If there's multiple correct tokens just skip that
82+
if token.kind != TokenKind::Unknown {
83+
continue;
84+
}
85+
if let TokenValue::StrValue(value) = &token.value {
86+
// Push illegal char into current sequence
87+
current_sequence.push(value.chars().next().unwrap());
88+
}
89+
90+
// Check if next value is empty or not unknown
91+
// If true then push current sequence to end string
92+
let peek = tokens_iter.peek();
93+
if !current_sequence.is_empty()
94+
&& (peek.is_none() || peek.unwrap().kind != TokenKind::Unknown)
95+
{
96+
// Append the current sequence
97+
error_str.push(current_sequence.clone());
98+
current_sequence.clear();
99+
}
100+
}
101+
error_str
102+
}

src/parser.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// - i needed some help making the AST. i dont think i could've made ts by myself 😭
2+
3+
use std::iter::Peekable;
4+
5+
use crate::{
6+
errors::{ErrorReason, ParserError},
7+
token::{Token, TokenKind, TokenValue},
8+
traits::Round,
9+
};
10+
11+
#[allow(dead_code)]
12+
#[derive(Debug)]
13+
pub enum ASTNode {
14+
Number(f64),
15+
BinaryOp(Box<ASTNode>, TokenKind, Box<ASTNode>),
16+
}
17+
18+
pub struct Parser<I: Iterator<Item = Token>> {
19+
tokens: Peekable<I>,
20+
}
21+
22+
impl ASTNode {
23+
pub fn evaluate(&self) -> f64 {
24+
match self {
25+
ASTNode::Number(val) => *val,
26+
ASTNode::BinaryOp(left_node, op, right_node) => {
27+
let left = left_node.evaluate();
28+
let right = right_node.evaluate();
29+
match op {
30+
TokenKind::Plus => left + right,
31+
TokenKind::Minus => left - right,
32+
TokenKind::Multiply => left * right,
33+
TokenKind::Divide => left / right,
34+
TokenKind::Power => left.powf(right),
35+
_ => panic!("wrong operation i cbf making proper errors for this"),
36+
}
37+
.round_to(5)
38+
}
39+
}
40+
}
41+
}
42+
43+
impl<I: Iterator<Item = Token>> Parser<I> {
44+
pub fn new(tokens: I) -> Self {
45+
Self {
46+
tokens: tokens.peekable(),
47+
}
48+
}
49+
50+
fn advance(&mut self) -> Option<Token> {
51+
self.tokens.next()
52+
}
53+
54+
fn peek(&mut self) -> Option<&Token> {
55+
self.tokens.peek()
56+
}
57+
58+
pub fn parse_expr(&mut self) -> Result<ASTNode, ParserError> {
59+
let mut node = self.parse_term()?;
60+
61+
while matches!(
62+
self.peek().map(|t| t.kind),
63+
Some(TokenKind::Plus) | Some(TokenKind::Minus) | Some(TokenKind::Power)
64+
) {
65+
let operator = self.advance().unwrap().kind;
66+
let right = self.parse_term()?;
67+
node = ASTNode::BinaryOp(Box::new(node), operator, Box::new(right));
68+
}
69+
70+
Ok(node)
71+
}
72+
73+
fn parse_term(&mut self) -> Result<ASTNode, ParserError> {
74+
let mut node = self.parse_factor()?;
75+
76+
while matches!(
77+
self.peek().map(|t| t.kind),
78+
Some(TokenKind::Multiply) | Some(TokenKind::Divide)
79+
) {
80+
let operator = self.advance().unwrap().kind;
81+
let right = self.parse_factor()?;
82+
node = ASTNode::BinaryOp(Box::new(node), operator, Box::new(right));
83+
}
84+
85+
Ok(node)
86+
}
87+
88+
fn parse_factor(&mut self) -> Result<ASTNode, ParserError> {
89+
match self.peek().map(|t| t.kind) {
90+
Some(TokenKind::Number) => {
91+
let value = if let TokenValue::NumValue(val) = self.advance().unwrap().value {
92+
val
93+
} else {
94+
return Err(ParserError::new(ErrorReason::Error(
95+
"Expected a number".to_string(),
96+
)));
97+
};
98+
Ok(ASTNode::Number(value))
99+
}
100+
Some(TokenKind::LParen) => {
101+
self.advance(); // Consume '('
102+
let node = self.parse_expr()?;
103+
if self.peek().map(|t| t.kind) != Some(TokenKind::RParen) {
104+
return Err(ParserError::new(ErrorReason::Error(
105+
"Expected ')'".into(),
106+
)));
107+
}
108+
self.advance(); // Consume ')'
109+
Ok(node)
110+
}
111+
_ => Err(ParserError::new(ErrorReason::Error(format!(
112+
"Unexpected token: {:?}",
113+
self.peek()
114+
)))),
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)