Skip to content

Commit

Permalink
fix oopsie
Browse files Browse the repository at this point in the history
  • Loading branch information
keplerHaloxx committed Aug 18, 2024
0 parents commit 553d6e6
Show file tree
Hide file tree
Showing 8 changed files with 422 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
/.vscode
Cargo.lock
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "math_interpreter"
version = "0.1.0"
edition = "2021"

[dependencies]
colored = "2.1.0"
strum = "0.26"
strum_macros = "0.26"
32 changes: 32 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::fmt::Display;

use colored::Colorize;

#[derive(Debug, Clone)]
pub enum ErrorReason {
Error(String),
}

impl Display for ErrorReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorReason::Error(reason) => write!(f, "{}", reason.red()),
}
}
}

#[derive(Debug, Clone)]
pub struct ParserError {
#[allow(dead_code)]
pub error: ErrorReason,
pub description: String,
}

impl ParserError {
pub fn new(error: ErrorReason) -> Self {
Self {
description: error.to_string(),
error,
}
}
}
88 changes: 88 additions & 0 deletions src/lexer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::iter::Peekable;

use crate::{
errors::ErrorReason,
token::{Token, TokenKind, TokenValue},
};

const DIGITS: &str = ".0123456789";

pub struct Lexer {
source: String,
}

impl Lexer {
pub fn new(source: String) -> Self {
Self { source }
}

pub fn tokenize(&self) -> Result<Vec<Token>, ErrorReason> {
let mut tokens: Vec<Token> = Vec::new();
let mut chars = self
.source
.chars()
.filter(|c| !c.is_whitespace())
.peekable();

while let Some(&current) = chars.peek() {
// Best solution i could find to making sure it doesn't skip
if DIGITS.contains(current) {
tokens.push(self.generate_number(&mut chars)?);
} else {
let token = match current {
'+' => Token::new(TokenKind::Plus, None),
'-' => Token::new(TokenKind::Minus, None),
'*' => Token::new(TokenKind::Multiply, None),
'/' => Token::new(TokenKind::Divide, None),
'^' => Token::new(TokenKind::Power, None),
'(' => Token::new(TokenKind::LParen, None),
')' => Token::new(TokenKind::RParen, None),
_ => Token::new(
TokenKind::Unknown,
Some(TokenValue::StrValue(current.to_string())),
),
};
tokens.push(token);
chars.next();
}
}

Ok(tokens)
}

fn generate_number<I>(&self, chars: &mut Peekable<I>) -> Result<Token, ErrorReason>
where
I: Iterator<Item = char>,
{
let mut decimal_point_counter = 0;
let mut number = String::new();

while let Some(&current) = chars.peek() {
if current == '.' {
decimal_point_counter += 1;
if decimal_point_counter > 1 {
return Err(ErrorReason::Error("Too many decimal points".into()));
}
}
number.push(current);
chars.next();

// Peek the next character and check if it's valid for a number
if let Some(&next_char) = chars.peek() {
if !DIGITS.contains(next_char) {
if number.trim() == "." {
return Err(ErrorReason::Error("Random decimal place found ".into()));
}
break;
}
}
}

Ok(Token::new(
TokenKind::Number,
Some(TokenValue::NumValue(number.parse::<f64>().unwrap_or_else(
|_| panic!("Error parsing number '{number}'"),
))),
))
}
}
102 changes: 102 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
mod errors;
mod lexer;
mod parser;
mod token;
mod traits;

use std::io::{stdin, stdout, Write};

use colored::Colorize;
use errors::{ErrorReason, ParserError};
use lexer::Lexer;
use parser::Parser;
use token::{Token, TokenKind, TokenValue};

/// Prompts user for input and returns trimmed result
fn prompt_input(prompt: &str) -> String {
let mut input = String::new();

print!("{}", prompt);
stdout().flush().unwrap();
stdin().read_line(&mut input).unwrap();

input.trim().to_owned()
}

fn main() {
println!("MATH INTERPRETER");
println!("----------------");

loop {
let input = prompt_input("> ");

let lexer = Lexer::new(input);
let tokens_result = lexer.tokenize();
if let Err(err) = tokens_result {
println!("{}", err);
continue;
}
let tokens = tokens_result.unwrap();

let token_errors = get_tokens_errors(tokens.clone());
if !token_errors.is_empty() {
for err in token_errors {
println!(
"{}",
ParserError::new(ErrorReason::Error(format!("Invalid sequence: '{}'", err)))
.description
);
}
println!();
} else {
pretty_print_tokens(tokens.clone());
let mut parser = Parser::new(tokens.into_iter());
let result = parser.parse_expr();
println!("{:?}", result);
println!(
"{} {}",
"RESULT:".bright_green(),
result.unwrap().evaluate().to_string().bright_green()
);
}
}
}

fn pretty_print_tokens(tokens: Vec<Token>) {
let token_len = tokens.len();
tokens.iter().enumerate().for_each(|(i, token)| {
if i == token_len - 1 {
println!("{token}");
} else {
print!("{}, ", token);
}
})
}

fn get_tokens_errors(tokens: Vec<Token>) -> Vec<String> {
let mut error_str: Vec<String> = vec![];
let mut current_sequence: String = String::new();
let mut tokens_iter = tokens.iter().peekable();
while let Some(token) = tokens_iter.next() {
// If there's multiple correct tokens just skip that
if token.kind != TokenKind::Unknown {
continue;
}
if let TokenValue::StrValue(value) = &token.value {
// Push illegal char into current sequence
current_sequence.push(value.chars().next().unwrap());
}

// Check if next value is empty or not unknown
// If true then push current sequence to end string
let peek = tokens_iter.peek();
if !current_sequence.is_empty()
&& (peek.is_none() || peek.unwrap().kind != TokenKind::Unknown)
{
// Append the current sequence
error_str.push(current_sequence.clone());
current_sequence.clear();
}
}
error_str
}
117 changes: 117 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// - i needed some help making the AST. i dont think i could've made ts by myself 😭

use std::iter::Peekable;

use crate::{
errors::{ErrorReason, ParserError},
token::{Token, TokenKind, TokenValue},
traits::Round,
};

#[allow(dead_code)]
#[derive(Debug)]
pub enum ASTNode {
Number(f64),
BinaryOp(Box<ASTNode>, TokenKind, Box<ASTNode>),
}

pub struct Parser<I: Iterator<Item = Token>> {
tokens: Peekable<I>,
}

impl ASTNode {
pub fn evaluate(&self) -> f64 {
match self {
ASTNode::Number(val) => *val,
ASTNode::BinaryOp(left_node, op, right_node) => {
let left = left_node.evaluate();
let right = right_node.evaluate();
match op {
TokenKind::Plus => left + right,
TokenKind::Minus => left - right,
TokenKind::Multiply => left * right,
TokenKind::Divide => left / right,
TokenKind::Power => left.powf(right),
_ => panic!("wrong operation i cbf making proper errors for this"),
}
.round_to(5)
}
}
}
}

impl<I: Iterator<Item = Token>> Parser<I> {
pub fn new(tokens: I) -> Self {
Self {
tokens: tokens.peekable(),
}
}

fn advance(&mut self) -> Option<Token> {
self.tokens.next()
}

fn peek(&mut self) -> Option<&Token> {
self.tokens.peek()
}

pub fn parse_expr(&mut self) -> Result<ASTNode, ParserError> {
let mut node = self.parse_term()?;

while matches!(
self.peek().map(|t| t.kind),
Some(TokenKind::Plus) | Some(TokenKind::Minus) | Some(TokenKind::Power)
) {
let operator = self.advance().unwrap().kind;
let right = self.parse_term()?;
node = ASTNode::BinaryOp(Box::new(node), operator, Box::new(right));
}

Ok(node)
}

fn parse_term(&mut self) -> Result<ASTNode, ParserError> {
let mut node = self.parse_factor()?;

while matches!(
self.peek().map(|t| t.kind),
Some(TokenKind::Multiply) | Some(TokenKind::Divide)
) {
let operator = self.advance().unwrap().kind;
let right = self.parse_factor()?;
node = ASTNode::BinaryOp(Box::new(node), operator, Box::new(right));
}

Ok(node)
}

fn parse_factor(&mut self) -> Result<ASTNode, ParserError> {
match self.peek().map(|t| t.kind) {
Some(TokenKind::Number) => {
let value = if let TokenValue::NumValue(val) = self.advance().unwrap().value {
val
} else {
return Err(ParserError::new(ErrorReason::Error(
"Expected a number".to_string(),
)));
};
Ok(ASTNode::Number(value))
}
Some(TokenKind::LParen) => {
self.advance(); // Consume '('
let node = self.parse_expr()?;
if self.peek().map(|t| t.kind) != Some(TokenKind::RParen) {
return Err(ParserError::new(ErrorReason::Error(
"Expected ')'".into(),
)));
}
self.advance(); // Consume ')'
Ok(node)
}
_ => Err(ParserError::new(ErrorReason::Error(format!(
"Unexpected token: {:?}",
self.peek()
)))),
}
}
}
Loading

0 comments on commit 553d6e6

Please sign in to comment.