diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-01-17 18:24:21 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-01-17 18:24:21 +0100 |
| commit | 5ccc6e5ec8a433f68bfeb17e8dcedec5b62a36a9 (patch) | |
| tree | 1101f7eeffd038a68d0a6a3e8a6cb77e39a537e5 /src | |
First sketch of matheval
Diffstat (limited to 'src')
| -rw-r--r-- | src/context.rs | 51 | ||||
| -rw-r--r-- | src/expression.rs | 73 | ||||
| -rw-r--r-- | src/function.rs | 17 | ||||
| -rw-r--r-- | src/main.rs | 58 | ||||
| -rw-r--r-- | src/operation.rs | 30 | ||||
| -rw-r--r-- | src/string.rs | 61 |
6 files changed, 290 insertions, 0 deletions
diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..12bea6b --- /dev/null +++ b/src/context.rs @@ -0,0 +1,51 @@ +use crate::function::Function; +use crate::operation::Operation; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Context { + ops: Vec<Operation>, + vars: HashMap<String, f64>, + funcs: HashMap<String, Box<dyn Function>>, +} + +impl Context { + pub fn new() -> Self { + Self::default() + } + + pub fn with_operations(mut self, ops: Vec<Operation>) -> Self { + self.ops = ops; + self + } + + pub fn with_variables(mut self, vars: HashMap<String, f64>) -> Self { + self.vars = vars; + self + } + + pub fn with_functions(mut self, funcs: HashMap<String, Box<dyn Function>>) -> Self { + self.funcs = funcs; + self + } + + pub fn operations(&self) -> &Vec<Operation> { + &self.ops + } + + pub fn variable_mut(&mut self, name: &str) -> Option<&mut f64> { + self.vars.get_mut(name) + } + + pub fn function_mut(&mut self, name: &str) -> Option<&mut Box<dyn Function>> { + self.funcs.get_mut(name) + } + + pub fn variable(&self, name: &str) -> Option<&f64> { + self.vars.get(name) + } + + pub fn function(&self, name: &str) -> Option<&Box<dyn Function>> { + self.funcs.get(name) + } +} diff --git a/src/expression.rs b/src/expression.rs new file mode 100644 index 0000000..5f2660b --- /dev/null +++ b/src/expression.rs @@ -0,0 +1,73 @@ +use crate::context::Context; +use crate::function::FunctionArgument; +use crate::string::{ContainsAndSkipBrackets, SplitMatchingBracket}; + +pub struct Expression { + repr: String, +} + +impl Expression { + pub fn from_string(str: &str) -> Self { + Self { + repr: str.replace(' ', ""), + } + } + + pub fn evaluate(&self, context: &Context) -> Result<f64, String> { + let (repr, oprepr) = { + if self.repr.starts_with('(') { + let (oprepr, r) = self.repr.split_on_matching_bracket(); + if r.is_empty() { + let repr = oprepr[1..oprepr.len() - 1].to_string(); + (repr.clone(), repr) + } else { + (self.repr.to_string(), r.to_string()) + } + } else { + (self.repr.to_string(), self.repr.to_string()) + } + }; + + let curop: Option<_> = { + let mut o = None; + for op in context.operations() { + if oprepr.contains_and_skip_brackets(op.sign().to_string().as_str()) { + o = Some(op); + break; + } + } + o + }; + + if let Some(op) = curop { + let (first, rest) = repr.split_once_and_skipt_brackets(op.sign()).unwrap(); + let first_expr = Expression::from_string(first); + let rest_expr = Expression::from_string(rest); + Ok(op.evaluate(first_expr.evaluate(context)?, rest_expr.evaluate(context)?)) + } else { + if let Ok(r) = repr.parse::<f64>() { + Ok(r) + } else { + if let Some((func, args)) = repr.split_once('(') { + let mut argv = Vec::new(); + + for arg in args[..args.len() - 1].split(',') { + argv.push(Expression::from_string(arg).evaluate(context)?); + } + + if let Some(func) = context.function(func) { + Ok(func.eval(FunctionArgument::new(argv))) + } else { + Err(format!("function '{func}' not found")) + } + } else { + if let Some(res) = context.variable(&repr) { + Ok(*res) + } else { + Err(format!("variable '{repr}' not found")) + } + } + } + } + } +} diff --git a/src/function.rs b/src/function.rs new file mode 100644 index 0000000..547ae0e --- /dev/null +++ b/src/function.rs @@ -0,0 +1,17 @@ +pub struct FunctionArgument { + args : Vec<f64>, +} + +impl FunctionArgument { + pub fn new(args: Vec<f64>) -> Self { + Self { args } + } + + pub fn get(&self, i : usize) -> f64 { + self.args[i] + } +} + +pub trait Function { + fn eval(&self, args: FunctionArgument) -> f64; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..313769f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,58 @@ +pub mod context; +pub mod expression; +pub mod function; +pub mod operation; +pub mod string; + +use std::collections::HashMap; + +use expression::Expression; +use function::Function; +use operation::Operation; + +use crate::context::Context; + +fn add(a: f64, b: f64) -> f64 { + a + b +} + +fn mul(a: f64, b: f64) -> f64 { + a * b +} + +fn div(a: f64, b: f64) -> f64 { + a / b +} + +fn pow(a: f64, b: f64) -> f64 { + a.powf(b) +} + +fn sqrt(a: f64) -> f64 { + a.sqrt() +} + +impl<T> Function for T +where + T: Fn(f64) -> f64, +{ + fn eval(&self, args: function::FunctionArgument) -> f64 { + self(args.get(0)) + } +} + +fn main() { + let expr = "(2 + (3 + 10) * (3 + 10)) * 2"; + + let mut funcs : HashMap<String, Box<dyn Function>> = HashMap::new(); + funcs.insert("sqrt".to_string(), Box::new(&sqrt)); + let ctx: Context = Context::new() + .with_operations(opvec![('+', &add), ('*', &mul), ('/', &div), ('^', &pow)]) + .with_functions(funcs); + let value = Expression::from_string(expr); + + match value.evaluate(&ctx) { + Ok(res) => println!("{} = {}", expr, res), + Err(err) => println!("Error: {}", err), + } +} diff --git a/src/operation.rs b/src/operation.rs new file mode 100644 index 0000000..861fb5a --- /dev/null +++ b/src/operation.rs @@ -0,0 +1,30 @@ + +pub type Operator = dyn Fn(f64, f64) -> f64; + +pub struct Operation { + sign: char, + func: Box<Operator> +} + +impl Operation { + pub fn new(sign: char, func: Box<Operator>) -> Self { + Self { sign, func } + } + + pub fn sign(&self) -> char { + self.sign + } + + pub fn evaluate(&self, a: f64, b: f64) -> f64 { + (self.func)(a, b) + } +} + +#[macro_export] +macro_rules! opvec { + ($(($x:expr, $y:expr)), *) => { + vec![$( + Operation::new($x, Box::new($y)), + )*] + }; +} diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 0000000..c8d719b --- /dev/null +++ b/src/string.rs @@ -0,0 +1,61 @@ +pub trait ContainsAndSkipBrackets { + fn contains_and_skip_brackets(&self, pattern: &str) -> bool; +} + +impl ContainsAndSkipBrackets for String { + fn contains_and_skip_brackets(&self, pattern: &str) -> bool { + if self.contains(pattern) { + let (first, _) = self.split_once(pattern).unwrap(); + let opening_count = first.chars().filter(|c| *c == '(').count(); + let closing_count = first.chars().filter(|c| *c == ')').count(); + return opening_count == closing_count; + } + false + } +} + +pub trait SplitMatchingBracket { + fn split_on_matching_bracket(&self) -> (&str, &str); + fn split_once_and_skipt_brackets(&self, pattern: char) -> Option<(&str, &str)>; +} + +impl SplitMatchingBracket for String { + fn split_on_matching_bracket(&self) -> (&str, &str) { + let mut i = 0; + let mut bracket_balance = -1; + + for c in self.chars() { + i += 1; + match c { + '(' => bracket_balance += 1, + ')' => bracket_balance -= 1, + _ => {} + } + + if bracket_balance < 0 { + break; + } + } + self.split_at(i) + } + + fn split_once_and_skipt_brackets(&self, pattern: char) -> Option<(&str, &str)> { + let mut i = 0; + let mut bracket_balance = 0; + + for c in self.chars() { + i += 1; + match c { + '(' => bracket_balance += 1, + ')' => bracket_balance -= 1, + _ => {} + } + + if bracket_balance <= 0 && c == pattern { + let (a, b) = self.split_at(i); + return Some((&a[0..a.len() - 1], b)) + } + } + None + } +} |