Added spring-2025 stuff
This commit is contained in:
parent
b26c48598d
commit
9bfea21b80
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings" defaultProject="true" />
|
||||||
|
</project>
|
|
@ -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]
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// Module Parsemath
|
||||||
|
pub mod ast;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod token;
|
||||||
|
pub mod tokenizer;
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue