aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/context.rs51
-rw-r--r--src/expression.rs73
-rw-r--r--src/function.rs17
-rw-r--r--src/main.rs58
-rw-r--r--src/operation.rs30
-rw-r--r--src/string.rs61
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
+ }
+}