Added spring-2025 stuff

This commit is contained in:
Win 2025-01-15 19:38:53 +07:00
parent b26c48598d
commit 9bfea21b80
40 changed files with 445 additions and 1 deletions

View File

@ -1 +0,0 @@
,winsdominoes,fedora,14.12.2024 11:59,file:///home/winsdominoes/.var/app/org.libreoffice.LibreOffice/config/libreoffice/4;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/eval.iml" filepath="$PROJECT_DIR$/.idea/eval.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

View File

@ -0,0 +1,9 @@
[package]
name = "expression-eval"
version = "0.1.0"
authors = ["cmkl"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,44 @@
/// This is the main command-line application for arithmetic expression evaluator
// Standard library
use std::io;
// code for arithmetic expression evaluation is in parsemath module
mod parsemath;
use parsemath::ast;
use parsemath::parser::{ParseError, Parser};
// Function to invoke Parser and evaluate expression
fn evaluate(expr: String) -> Result<f64, ParseError> {
let expr = expr.split_whitespace().collect::<String>(); // remove whitespace chars
let mut math_parser = Parser::new(&expr)?;
let ast = math_parser.parse()?;
println!("The generated AST is {:?}", ast);
Ok(ast::eval(ast)?)
}
// Main function reads arithmetic expression from command-line and displays result and error.
// It calls the evaluate function to perform computation.
fn main() {
println!("Hello! Welcome to Arithmetic expression evaluator.");
println!("You can calculate value for expression such as 2*3+(4-5)+2^3/4. ");
println!("Allowed numbers: positive, negative and decimals.");
println!("Supported operations: Add, Subtract, Multiply, Divide, PowerOf(^). ");
println!("Enter your arithmetic expression below:");
loop {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_) => {
match evaluate(input) {
Ok(val) => println!("The computed number is {}\n", val),
Err(_) => {
println!("Error in evaluating expression. Please enter valid expression\n");
}
};
}
Err(error) => println!("error: {}", error),
}
}
}

View File

@ -0,0 +1,64 @@
/// This program contains list of valid AST nodes that can be constructed and also evaluates an AST to compute a value
// Standard lib
use std::error;
//structs
// List of allowed AST nodes that can be constructed by Parser
// Tokens can be arithmetic operators or a Number
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
// WARNING: Bitwise And and Or operation only works on integer value
And(Box<Node>, Box<Node>),
Or(Box<Node>, Box<Node>),
Add(Box<Node>, Box<Node>),
Subtract(Box<Node>, Box<Node>),
Multiply(Box<Node>, Box<Node>),
Divide(Box<Node>, Box<Node>),
Caret(Box<Node>, Box<Node>),
Negative(Box<Node>),
Number(f64),
}
// Given an AST, calculate the numeric value.
pub fn eval(expr: Node) -> Result<f64, Box<dyn error::Error>> {
use self::Node::*;
match expr {
Number(i) => Ok(i),
Add(expr1, expr2) => Ok(eval(*expr1)? + eval(*expr2)?),
// TODO: complete the match expression to evaluate the numeric value
_ => panic!()
}
}
//Unit tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expr1() {
use crate::parsemath::parser::Parser;
let ast = Parser::new("1+2-3").unwrap().parse().unwrap();
let value = eval(ast).unwrap();
assert_eq!(value, 0.0);
}
#[test]
fn test_expr2() {
use crate::parsemath::parser::Parser;
let ast = Parser::new("3+2-1*5/4").unwrap().parse().unwrap();
let value = eval(ast).unwrap();
assert_eq!(value, 3.75);
}
#[test]
fn test_expr3() {
use crate::parsemath::parser::Parser;
let ast = Parser::new("3+3 | 4").unwrap().parse().unwrap();
let value = eval(ast).unwrap();
assert_eq!(value, 6.0);
}
}

View File

@ -0,0 +1,5 @@
/// Module Parsemath
pub mod ast;
pub mod parser;
pub mod token;
pub mod tokenizer;

View File

@ -0,0 +1,174 @@
/// This program reads tokens returned by Tokenizer and converts them into AST.
// Standard lib
use std::fmt;
// Internal modules
use super::ast::Node;
use super::token::{OperPrec, Token};
use super::tokenizer::Tokenizer;
//Structs and constants
// Parser struct
pub struct Parser<'a> {
tokenizer: Tokenizer<'a>,
current_token: Token,
}
// Public methods of Parser
impl<'a> Parser<'a> {
// Create a new instance of Parser
pub fn new(expr: &'a str) -> Result<Self, ParseError> {
let mut lexer = Tokenizer::new(expr);
let cur_token = match lexer.next() {
Some(token) => token,
None => return Err(ParseError::InvalidOperator("Invalid character".into())),
};
Ok(Parser {
tokenizer: lexer,
current_token: cur_token,
})
}
// Take an arithmetic expression as input and return an AST
pub fn parse(&mut self) -> Result<Node, ParseError> {
let ast = self.generate_ast(OperPrec::DefaultZero);
match ast {
// TODO: Replace this with proper handling of return value from generate_ast result
_ => panic!()
}
}
}
// Private methods of Parser
impl<'a> Parser<'a> {
// Retrieve the next token from arithmetic expression and set it to current_token field in Parser struct
fn get_next_token(&mut self) -> Result<(), ParseError> {
let next_token = match self.tokenizer.next() {
Some(token) => token,
None => return Err(ParseError::InvalidOperator("Invalid character".into())),
};
self.current_token = next_token;
Ok(())
}
// Main workhorse method that is called recursively
fn generate_ast(&mut self, oper_prec: OperPrec) -> Result<Node, ParseError> {
let mut left_expr = self.parse_number()?;
while oper_prec < self.current_token.get_oper_prec() {
if self.current_token == Token::EOF {
break;
}
let right_expr = self.convert_token_to_node(left_expr.clone())?;
left_expr = right_expr;
}
Ok(left_expr)
}
// Construct AST node for numbers, taking into account negative prefixes while handling parenthesis
fn parse_number(&mut self) -> Result<Node, ParseError> {
let token = self.current_token.clone();
match token {
Token::Subtract => {
self.get_next_token()?;
let expr = self.generate_ast(OperPrec::Negative)?;
Ok(Node::Negative(Box::new(expr)))
}
Token::Num(i) => {
self.get_next_token()?;
Ok(Node::Number(i))
}
Token::LeftParen => {
self.get_next_token()?;
// TODO: Replace the following code to check for matching parenthesis;
// also convert (x)(y) to x times y
let expr = self.generate_ast(OperPrec::DefaultZero)?;
Ok(expr)
}
_ => Err(ParseError::UnableToParse("Unable to parse".to_string())),
}
}
// Check for balancing parenthesis
fn check_paren(&mut self, expected: Token) -> Result<(), ParseError> {
if expected == self.current_token {
self.get_next_token()?;
Ok(())
} else {
Err(ParseError::InvalidOperator(format!(
"Expected {:?}, got {:?}",
expected, self.current_token
)))
}
}
// Construct Operator AST nodes
fn convert_token_to_node(&mut self, left_expr: Node) -> Result<Node, ParseError> {
match self.current_token {
Token::Add => {
self.get_next_token()?;
//Get right-side expression
let right_expr = self.generate_ast(OperPrec::AddSub)?;
Ok(Node::Add(Box::new(left_expr), Box::new(right_expr)))
}
// TODO: Complete the node construction for other tokens
_ => Err(ParseError::InvalidOperator(format!(
"Please enter valid operator {:?}",
self.current_token
))),
}
}
}
// Custom error handler for Parser
#[derive(Debug)]
pub enum ParseError {
UnableToParse(String),
InvalidOperator(String),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
ParseError::UnableToParse(e) => write!(f, "Error in evaluating {}", e),
ParseError::InvalidOperator(e) => write!(f, "Error in evaluating {}", e),
}
}
}
// Handle error thrown from AST module
impl From<Box<dyn std::error::Error>> for ParseError {
fn from(_evalerr: Box<dyn std::error::Error>) -> Self {
ParseError::UnableToParse("Unable to parse".into())
}
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
use crate::parsemath::ast::Node::{Add, Or, Number};
#[test]
fn test_addition() {
let mut parser = Parser::new("1+2").unwrap();
let expected = Add(Box::new(Number(1.0)), Box::new(Number(2.0)));
assert_eq!(parser.parse().unwrap(), expected);
}
#[test]
fn test_bitwise_or() {
let mut parser = Parser::new("6|2").unwrap();
let expected = Or(Box::new(Number(6.0)), Box::new(Number(2.0)));
assert_eq!(parser.parse().unwrap(), expected);
}
}

View File

@ -0,0 +1,48 @@
/// This contains enum for list of Tokens, and handles Operator precedence rules.
// List of valid tokens that can be constructed from arithmetic expression by Tokenizer
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
And, // &
Or, // |
Add, // +
Subtract, // -
Multiply, // *
Divide, // /
Caret, // ^
LeftParen, // (
RightParen, // )
Num(f64), // 12.34
EOF,
}
// Order of operators as per operator precedence rules (low to high)
#[derive(Debug, PartialEq, PartialOrd)]
/// Defines all the OperPrec levels, from lowest to highest.
pub enum OperPrec {
DefaultZero,
AndOr,
AddSub,
MulDiv,
Power,
Negative,
}
// This contains methods to retrieve operator precedence for a given arithmetic operator
impl Token {
pub fn get_oper_prec(&self) -> OperPrec {
use self::OperPrec::*;
use self::Token::*;
match *self {
And | Or => AndOr,
Add | Subtract => AddSub,
Multiply | Divide => MulDiv,
Caret => Power,
_ => DefaultZero,
}
}
}

View File

@ -0,0 +1,70 @@
/// This module reads characters in arithmetic expression and converts them to tokens.
/// The allowed tokens are defined in ast module.
// Standard lib
use std::iter::Peekable;
use std::str::Chars;
//Other internal modules
use super::token::Token;
// Other structs
// Tokenizer struct contains a Peekable iterator on the arithmetic expression
pub struct Tokenizer<'a> {
expr: Peekable<Chars<'a>>,
}
// Constructs a new instance of Tokenizer
impl<'a> Tokenizer<'a> {
pub fn new(new_expr: &'a str) -> Self {
Tokenizer {
expr: new_expr.chars().peekable(),
}
}
}
// Implement Iterator trait for Tokenizer struct.
// With this, we can use next() method on tokenizer to retrieve the next token from arithmetic expression
impl<'a> Iterator for Tokenizer<'a> {
type Item = Token;
fn next(&mut self) -> Option<Token> {
let next_char = self.expr.next();
match next_char {
Some('0'..='9') => {
// TODO: Iterate & peeking through the next characters to create Num token
// Make sure to return None if the value is not parsable
None
},
// TODO: return the appropriate tokens for available symbols
None => Some(Token::EOF),
Some(_) => None,
}
}
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_positive_integer() {
let mut tokenizer = Tokenizer::new("34");
assert_eq!(tokenizer.next().unwrap(), Token::Num(34.0))
}
#[test]
fn test_decimal_number() {
let mut tokenizer = Tokenizer::new("34.5");
assert_eq!(tokenizer.next().unwrap(), Token::Num(34.5))
}
#[test]
fn test_invalid_char() {
let mut tokenizer = Tokenizer::new("#$%");
assert_eq!(tokenizer.next(), None);
}
}