From 1713618d4cc0194674f91fd2d24ef2de88f21784 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Thu, 18 Jan 2024 18:29:10 +0100 Subject: create small iced demo --- src/commonsense.rs | 90 ------------- src/complex.rs | 191 --------------------------- src/context.rs | 66 ---------- src/expression.rs | 68 ---------- src/expression_function.rs | 55 -------- src/function.rs | 68 ---------- src/main.rs | 276 +++++++++++++++++++++++++++++++++++----- src/math/commonsense.rs | 90 +++++++++++++ src/math/complex.rs | 191 +++++++++++++++++++++++++++ src/math/context.rs | 74 +++++++++++ src/math/expression.rs | 73 +++++++++++ src/math/expression_function.rs | 60 +++++++++ src/math/function.rs | 68 ++++++++++ src/math/mod.rs | 8 ++ src/math/operation.rs | 32 +++++ src/math/string.rs | 61 +++++++++ src/operation.rs | 32 ----- src/string.rs | 61 --------- 18 files changed, 901 insertions(+), 663 deletions(-) delete mode 100644 src/commonsense.rs delete mode 100644 src/complex.rs delete mode 100644 src/context.rs delete mode 100644 src/expression.rs delete mode 100644 src/expression_function.rs delete mode 100644 src/function.rs create mode 100644 src/math/commonsense.rs create mode 100644 src/math/complex.rs create mode 100644 src/math/context.rs create mode 100644 src/math/expression.rs create mode 100644 src/math/expression_function.rs create mode 100644 src/math/function.rs create mode 100644 src/math/mod.rs create mode 100644 src/math/operation.rs create mode 100644 src/math/string.rs delete mode 100644 src/operation.rs delete mode 100644 src/string.rs (limited to 'src') diff --git a/src/commonsense.rs b/src/commonsense.rs deleted file mode 100644 index c42da00..0000000 --- a/src/commonsense.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::complex::Complex; - -pub fn add(a: Complex, b: Complex) -> Complex { - a + b -} - -pub fn sub(a: Complex, b: Complex) -> Complex { - a - b -} - -pub fn mul(a: Complex, b: Complex) -> Complex { - a * b -} - -pub fn div(a: Complex, b: Complex) -> Complex { - a / b -} - -pub fn pow(a: Complex, b: Complex) -> Complex { - a.pow(b) -} - -pub fn sqrt(a: Complex) -> Complex { - a.sqrt() -} - -pub fn sin(a: Complex) -> Complex { - a.sin() -} - -pub fn cos(a: Complex) -> Complex { - a.cos() -} - -pub fn tan(a: Complex) -> Complex { - a.tan() -} - -#[macro_export] -macro_rules! commonsense_functions { - {$($x:expr => $y:expr), *} => { - { - use std::sync::Arc; - let mut h : HashMap> = HashMap::new(); - h.extend(functions!{ - "sqrt" => &$crate::commonsense::sqrt, - "sin" => &$crate::commonsense::sin, - "cos" => &$crate::commonsense::cos, - "tan" => &$crate::commonsense::tan - }); - $( - h.insert($x.to_string(), Arc::new($y)); - )* - h - } - }; -} - -#[macro_export] -macro_rules! commonsense_operations { - {$($x:expr => $y:expr), *} => { - vec![ - Operation::new('+', $crate::commonsense::add), - Operation::new('-', $crate::commonsense::sub), - Operation::new('*', $crate::commonsense::mul), - Operation::new('/', $crate::commonsense::div), - Operation::new('^', $crate::commonsense::pow) - $( - Operation::new($x, Box::new($y)), - )*] - }; -} - -#[macro_export] -macro_rules! commonsense_variables { - {$($x:expr => $y:expr), *} => { - { - let mut h : HashMap = HashMap::new(); - h.extend(variables!{ - "pi" => Complex::new(std::f64::consts::PI, 0.0), - "e" => Complex::new(std::f64::consts::E, 0.0) - }); - $( - h.insert($x.to_string(), $y); - )* - h - } - }; -} - diff --git a/src/complex.rs b/src/complex.rs deleted file mode 100644 index 5b95df4..0000000 --- a/src/complex.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::f64::consts::E; -use std::num::ParseFloatError; -use std::str::FromStr; - -#[derive(Clone, Copy, Default, Debug)] -pub struct Complex { - pub real: f64, - pub imag: f64, -} - -impl Complex { - pub fn new(real: f64, imag: f64) -> Self { - Self { real, imag } - } - - pub fn is_real(&self) -> bool { - self.imag == 0.0 - } - - pub fn abs(&self) -> f64 { - (self.real.powi(2) + self.imag.powi(2)).sqrt() - } - - pub fn pow(&self, rhs: Self) -> Self { - if self.is_real() && rhs.is_real() { - Complex::new(self.real.powf(rhs.real), 0.0) - } else { - let r = self.abs(); - let x = (self.imag / self.real).atan(); - let lnr = r.ln(); - let alpha = lnr * rhs.imag + x * rhs.real; - let e_exp = E.powf(lnr * rhs.real - rhs.imag * x); - Complex::new(e_exp * alpha.cos(), e_exp * alpha.sin()) - } - } - - pub fn sqrt(&self) -> Self { - self.pow(Complex::new(0.5, 0.0)) - } - - pub fn cos(&self) -> Self { - let e: Complex = E.into(); - let i = Complex::new(0.0, 1.0); - - Complex::new(e.pow(&i * self).real, 0.0) - } - - pub fn sin(&self) -> Self { - let e: Complex = E.into(); - let i = Complex::new(0.0, 1.0); - - Complex::new(e.pow(&i * self).imag, 0.0) - } - - pub fn tan(&self) -> Self { - self.sin() / self.cos() - } -} - -impl std::fmt::Display for Complex { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if self.imag == 0.0 { - write!(f, "{}", self.real) - } else if self.real == 0.0 { - if self.imag == 1.0 { - write!(f, "i") - } else { - write!(f, "{}i", self.imag) - } - } else if self.imag == 1.0 { - write!(f, "{} + i", self.real) - } else { - write!(f, "{} + {}i", self.real, self.imag) - } - } -} - -#[derive(Debug)] -pub struct ComplexParseError; - -impl From for ComplexParseError { - fn from(_: ParseFloatError) -> Self { - Self {} - } -} - -impl FromStr for Complex { - type Err = ComplexParseError; - - fn from_str(s: &str) -> Result { - let mut c = Complex::default(); - let s = s.replace(' ', ""); - if s.contains('+') { - let (a, b) = s.split_once('+').unwrap(); - if a.contains('i') { - c.imag = a[..a.len() - 1].parse()?; - c.real = b.parse()?; - } else { - c.real = a.parse()?; - c.imag = b[..b.len() - 1].parse()?; - } - } else if s.contains('i') { - if s.len() == 1 { - c.imag = 1.0; - } else { - c.imag = s[..s.len() - 1].parse()?; - } - } else { - c.real = s.parse()?; - } - - Ok(c) - } -} - -impl From for Complex { - fn from(val: f64) -> Self { - Complex::new(val, 0.0) - } -} - -impl std::ops::Add for &Complex { - type Output = Complex; - - fn add(self, rhs: Self) -> Self::Output { - *self + *rhs - } -} - -impl std::ops::Add for Complex { - type Output = Complex; - - fn add(self, rhs: Self) -> Self::Output { - Complex::new(self.real + rhs.real, self.imag + rhs.imag) - } -} - -impl std::ops::Sub for &Complex { - type Output = Complex; - - fn sub(self, rhs: Self) -> Self::Output { - *self - *rhs - } -} - -impl std::ops::Sub for Complex { - type Output = Complex; - - fn sub(self, rhs: Self) -> Self::Output { - Complex::new(self.real - rhs.real, self.imag - rhs.imag) - } -} - -impl std::ops::Mul for &Complex { - type Output = Complex; - - fn mul(self, rhs: Self) -> Self::Output { - *self * *rhs - } -} - -impl std::ops::Mul for Complex { - type Output = Complex; - - fn mul(self, rhs: Self) -> Self::Output { - Complex::new( - self.real * rhs.real - self.imag * rhs.imag, - self.real * rhs.imag + self.imag * rhs.real, - ) - } -} - -impl std::ops::Div for &Complex { - type Output = Complex; - - fn div(self, rhs: Self) -> Self::Output { - *self / *rhs - } -} - -impl std::ops::Div for Complex { - type Output = Complex; - - fn div(self, rhs: Self) -> Self::Output { - let z = rhs.real * rhs.real + rhs.imag * rhs.imag; - Complex::new( - (self.real * rhs.real + self.imag * rhs.imag) / z, - (self.imag * rhs.real - self.real * rhs.imag) / z, - ) - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 409600f..0000000 --- a/src/context.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::complex::Complex; -use crate::function::Function; -use crate::operation::Operation; -use std::sync::Arc; -use std::collections::HashMap; - -#[derive(Default, Clone)] -pub struct Context { - ops: Vec, - vars: HashMap, - funcs: HashMap>, -} - -impl Context { - pub fn new() -> Self { - Self::default() - } - - pub fn with_operations(mut self, ops: Vec) -> Self { - self.ops = ops; - self - } - - pub fn with_variables(mut self, vars: HashMap) -> Self { - self.vars = vars; - self - } - - pub fn with_functions(mut self, funcs: HashMap>) -> Self { - self.funcs = funcs; - self - } - - pub fn operations(&self) -> &Vec { - &self.ops - } - - pub fn set_variable(&mut self, name: &str, value: Complex) { - self.vars.insert(name.to_string(), value); - } - - pub fn set_function(&mut self, name: &str, func: Arc) { - self.funcs.insert(name.to_string(), func); - } - - pub fn variable(&self, name: &str) -> Option<&Complex> { - self.vars.get(name) - } - - pub fn function(&self, name: &str) -> Option<&Arc> { - self.funcs.get(name) - } -} - -#[macro_export] -macro_rules! variables { - {$($x:expr => $y:expr), *} => { - { - let mut h : HashMap = HashMap::new(); - $( - h.insert($x.to_string(), $y); - )* - h - } - }; -} diff --git a/src/expression.rs b/src/expression.rs deleted file mode 100644 index c7574a9..0000000 --- a/src/expression.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::complex::Complex; -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 { - 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::() { - 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), context)?) - } 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/expression_function.rs b/src/expression_function.rs deleted file mode 100644 index 8cafd76..0000000 --- a/src/expression_function.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{iter::zip}; -use std::sync::Arc; - -use crate::{ - complex::Complex, - context::Context, - expression::Expression, - function::{Function, FunctionArgument, EmptyFunction} -}; - -pub struct ExpressionFunction { - expr: Expression, - name: String, - args: Vec, -} - -impl ExpressionFunction { - pub fn from_string(str: String) -> Self { - let str = str.replace(' ', ""); - let (lhs, expr) = str.split_once('=').unwrap(); - let (name, a) = lhs.split_once('(').unwrap(); - let args: Vec = a[0..a.len() - 1] - .split(',') - .map(|s| s.to_string()) - .collect(); - Self { - expr: Expression::from_string(expr), - name: name.to_string(), - args, - } - } - - pub fn name(&self) -> &str { - &self.name - } -} - -impl Function for ExpressionFunction { - fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result { - if args.len() == self.args.len() { - let mut nctx = ctx.clone(); - for (n, v) in zip(self.args.iter(), args.data().iter()) { - nctx.set_variable(n, *v) - } - nctx.set_function(self.name(), Arc::new(EmptyFunction {})); - self.expr.evaluate(&nctx) - } else { - Err(format!( - "{} takes {} parameters", - self.name, - self.args.len() - )) - } - } -} diff --git a/src/function.rs b/src/function.rs deleted file mode 100644 index 4daebf4..0000000 --- a/src/function.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::{complex::Complex, context::Context}; - -pub struct FunctionArgument { - args : Vec, -} - -impl FunctionArgument { - pub fn new(args: Vec) -> Self { - Self { args } - } - - pub fn get(&self, i : usize) -> Complex { - self.args[i] - } - - pub fn len(&self) -> usize { - self.args.len() - } - - pub fn is_empty(&self) -> bool { - self.args.len() == 0 - } - - pub fn data(&self) -> &[Complex] { - &self.args - } -} - -pub trait Function { - fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result; -} - -#[macro_export] -macro_rules! functions { - {$($x:expr => $y:expr), *} => { - { - let mut h : HashMap> = HashMap::new(); - $( - h.insert($x.to_string(), Arc::new($y)); - )* - h - } - }; -} - - -// Some default implementations -impl Function for T -where - T: Fn(Complex) -> Complex, -{ - fn eval(&self, args: FunctionArgument, _ctx: &Context) -> Result { - if args.len() == 1 { - Ok(self(args.get(0))) - } else { - Err("too many arguments".to_string()) - } - } -} - - -pub struct EmptyFunction {} - -impl Function for EmptyFunction { - fn eval(&self, _args: FunctionArgument, _ctx: &Context) -> Result { - Err("function not implemented in this scope".to_string()) - } -} diff --git a/src/main.rs b/src/main.rs index 5f42b10..8876f16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,246 @@ -pub mod complex; -pub mod context; -pub mod expression; -pub mod function; -pub mod operation; -pub mod string; -pub mod commonsense; -pub mod expression_function; - -use std::collections::HashMap; - -use complex::Complex; -use context::Context; -use expression::Expression; -use function::Function; -use operation::Operation; -use expression_function::ExpressionFunction; - -fn main() { - let func = ExpressionFunction::from_string("f(x) = x^2".to_string()); - - let expr = "f(4 + 3i)"; - - let ctx: Context = Context::new() - .with_operations(commonsense_operations!{}) - .with_functions(commonsense_functions!{func.name() => func}) - .with_variables(commonsense_variables!{}); - - let value = Expression::from_string(expr); - match value.evaluate(&ctx) { - Ok(res) => println!("{} = {}", expr, res), - Err(err) => println!("Error: {}", err), +pub mod math; + +use iced::executor; +use iced::mouse; +use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; +use iced::widget::column; +use iced::widget::text_input; +use iced::widget::{canvas, container}; +use iced::{ + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, Settings, Theme, + Vector, +}; +use math::context::Context; +use math::expression_function::ExpressionFunction; +use math::function::Function; + +use crate::math::complex::Complex; +use crate::math::function::FunctionArgument; + +pub fn main() -> iced::Result { + Graph::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +#[derive(Default)] +struct Graph { + graph: Cache, + center_x: f32, + center_y: f32, + scale: f32, + func: ExpressionFunction, + ctx: Context, + input_state: String, +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + FunctionRun, +} + +impl Graph { + fn map_coords(&self, x: f32, y: f32) -> Point { + Point::new( + (self.center_x + x) * self.scale, + (self.center_y - y) * self.scale, + ) + } +} + +impl Application for Graph { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + ( + Graph { + input_state: "f(x) = x^2".to_string(), + func: ExpressionFunction::from_string("f(x) = x^2".to_string()), + ctx: Context::commonsense(), + scale: 50.0, + ..Default::default() + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("MathEval") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::InputChanged(s) => { + self.input_state = s; + } + Message::FunctionRun => { + self.func = ExpressionFunction::from_string(self.input_state.clone()); + self.graph.clear(); + } + } + Command::none() + } + + fn view(&self) -> Element { + let input = text_input("x", &self.input_state) + .on_input(Message::InputChanged) + .on_submit(Message::FunctionRun) + .padding(15) + .size(30); + let canvas = canvas(self as &Self) + .width(Length::Fill) + .height(Length::Fill); + + let container = container(canvas) + .width(Length::Fill) + .height(Length::Fill) + .padding(20); + + column![input, container].into() + } +} + +impl canvas::Program for Graph { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> Vec { + let graph = self.graph.draw(renderer, bounds.size(), |frame| { + let unitline = Stroke { + width: 2.0, + style: stroke::Style::Solid(Color::new(0.0, 0.0, 0.0, 1.0)), + line_cap: LineCap::Square, + ..Stroke::default() + }; + let unitline_whole = Stroke { + width: 1.0, + style: stroke::Style::Solid(Color::new(0.5, 0.5, 0.5, 1.0)), + line_cap: LineCap::Square, + ..Stroke::default() + }; + let zeroline = Stroke { + width: 3.0, + style: stroke::Style::Solid(Color::new(0.0, 0.0, 0.8, 1.0)), + line_cap: LineCap::Square, + ..Stroke::default() + }; + + let graphline = Stroke { + width: 3.0, + style: stroke::Style::Solid(Color::new(0.8, 0.0, 0.0, 1.0)), + line_cap: LineCap::Square, + ..Stroke::default() + }; + + frame.translate(Vector { + x: frame.center().x, + y: frame.center().y, + }); + + frame.with_save(|frame| { + let start_y = ((self.center_y - frame.height() / 2.0) / self.scale) as i64 + * self.scale as i64; + let start_x = + ((self.center_x - frame.width() / 2.0) / self.scale) as i64 * self.scale as i64; + let end_y = ((self.center_y + frame.height() / 2.0) / self.scale) as i64 + * self.scale as i64; + let end_x = + ((self.center_x + frame.width() / 2.0) / self.scale) as i64 * self.scale as i64; + + for y in (start_y..end_y + self.scale as i64).step_by(1) { + let line = Path::line( + self.map_coords(start_x as f32, y as f32), + self.map_coords(end_x as f32, y as f32), + ); + frame.stroke(&line, unitline_whole.clone()); + } + + for x in (start_x..end_x + self.scale as i64).step_by(1) { + let line = Path::line( + self.map_coords(x as f32, start_y as f32), + self.map_coords(x as f32, end_y as f32), + ); + frame.stroke(&line, unitline_whole.clone()); + } + + let start_y_100 = ((self.center_y - frame.height() / 2.0) / self.scale * 10.0) + as i64 + * 10 + * self.scale as i64; + let start_x_100 = ((self.center_x - frame.width() / 2.0) / self.scale * 10.0) + as i64 + * 10 + * self.scale as i64; + + for y in (start_y_100..end_y).step_by(10) { + let line = Path::line( + self.map_coords(start_x as f32, y as f32), + self.map_coords(end_x as f32, y as f32), + ); + frame.stroke(&line, unitline.clone()); + } + + for x in (start_x_100..end_x).step_by(10) { + let line = Path::line( + self.map_coords(x as f32, start_y as f32), + self.map_coords(x as f32, end_y as f32), + ); + frame.stroke(&line, unitline.clone()); + } + + let line = Path::line( + self.map_coords(0.0, start_y as f32), + self.map_coords(0.0, end_y as f32), + ); + frame.stroke(&line, zeroline.clone()); + + let line = Path::line( + self.map_coords(start_x as f32, 0.0), + self.map_coords(end_x as f32, 0.0), + ); + frame.stroke(&line, zeroline.clone()); + + for x in start_x..end_x { + for d in 0..10 { + let d = d as f32 / 10.0; + let x1 = x as f32 + d; + let y1 = self + .func + .eval( + FunctionArgument::new(vec![Complex::new(x1 as f64, 0.0)]), + &self.ctx, + ) + .unwrap() + .real as f32; + let x2 = x as f32 + d + 0.1; + let y2 = self + .func + .eval( + FunctionArgument::new(vec![Complex::new(x2 as f64, 0.0)]), + &self.ctx, + ) + .unwrap() + .real as f32; + if y1.is_finite() && !y1.is_nan() && y2.is_finite() && !y2.is_nan() { + let line = Path::line(self.map_coords(x1, y1), self.map_coords(x2, y2)); + frame.stroke(&line, graphline.clone()) + } + } + } + }); + }); + + vec![graph] } } diff --git a/src/math/commonsense.rs b/src/math/commonsense.rs new file mode 100644 index 0000000..e75ccc5 --- /dev/null +++ b/src/math/commonsense.rs @@ -0,0 +1,90 @@ +use crate::math::complex::Complex; + +pub fn add(a: Complex, b: Complex) -> Complex { + a + b +} + +pub fn sub(a: Complex, b: Complex) -> Complex { + a - b +} + +pub fn mul(a: Complex, b: Complex) -> Complex { + a * b +} + +pub fn div(a: Complex, b: Complex) -> Complex { + a / b +} + +pub fn pow(a: Complex, b: Complex) -> Complex { + a.pow(b) +} + +pub fn sqrt(a: Complex) -> Complex { + a.sqrt() +} + +pub fn sin(a: Complex) -> Complex { + a.sin() +} + +pub fn cos(a: Complex) -> Complex { + a.cos() +} + +pub fn tan(a: Complex) -> Complex { + a.tan() +} + +#[macro_export] +macro_rules! commonsense_functions { + {$($x:expr => $y:expr), *} => { + { + use std::sync::Arc; + let mut h : HashMap> = HashMap::new(); + h.extend(functions!{ + "sqrt" => &$crate::math::commonsense::sqrt, + "sin" => &$crate::math::commonsense::sin, + "cos" => &$crate::math::commonsense::cos, + "tan" => &$crate::math::commonsense::tan + }); + $( + h.insert($x.to_string(), Arc::new($y)); + )* + h + } + }; +} + +#[macro_export] +macro_rules! commonsense_operations { + {$($x:expr => $y:expr), *} => { + vec![ + Operation::new('+', $crate::math::commonsense::add), + Operation::new('-', $crate::math::commonsense::sub), + Operation::new('*', $crate::math::commonsense::mul), + Operation::new('/', $crate::math::commonsense::div), + Operation::new('^', $crate::math::commonsense::pow) + $( + Operation::new($x, Box::new($y)), + )*] + }; +} + +#[macro_export] +macro_rules! commonsense_variables { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap = HashMap::new(); + h.extend(variables!{ + "pi" => Complex::new(std::f64::consts::PI, 0.0), + "e" => Complex::new(std::f64::consts::E, 0.0) + }); + $( + h.insert($x.to_string(), $y); + )* + h + } + }; +} + diff --git a/src/math/complex.rs b/src/math/complex.rs new file mode 100644 index 0000000..5b95df4 --- /dev/null +++ b/src/math/complex.rs @@ -0,0 +1,191 @@ +use std::f64::consts::E; +use std::num::ParseFloatError; +use std::str::FromStr; + +#[derive(Clone, Copy, Default, Debug)] +pub struct Complex { + pub real: f64, + pub imag: f64, +} + +impl Complex { + pub fn new(real: f64, imag: f64) -> Self { + Self { real, imag } + } + + pub fn is_real(&self) -> bool { + self.imag == 0.0 + } + + pub fn abs(&self) -> f64 { + (self.real.powi(2) + self.imag.powi(2)).sqrt() + } + + pub fn pow(&self, rhs: Self) -> Self { + if self.is_real() && rhs.is_real() { + Complex::new(self.real.powf(rhs.real), 0.0) + } else { + let r = self.abs(); + let x = (self.imag / self.real).atan(); + let lnr = r.ln(); + let alpha = lnr * rhs.imag + x * rhs.real; + let e_exp = E.powf(lnr * rhs.real - rhs.imag * x); + Complex::new(e_exp * alpha.cos(), e_exp * alpha.sin()) + } + } + + pub fn sqrt(&self) -> Self { + self.pow(Complex::new(0.5, 0.0)) + } + + pub fn cos(&self) -> Self { + let e: Complex = E.into(); + let i = Complex::new(0.0, 1.0); + + Complex::new(e.pow(&i * self).real, 0.0) + } + + pub fn sin(&self) -> Self { + let e: Complex = E.into(); + let i = Complex::new(0.0, 1.0); + + Complex::new(e.pow(&i * self).imag, 0.0) + } + + pub fn tan(&self) -> Self { + self.sin() / self.cos() + } +} + +impl std::fmt::Display for Complex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.imag == 0.0 { + write!(f, "{}", self.real) + } else if self.real == 0.0 { + if self.imag == 1.0 { + write!(f, "i") + } else { + write!(f, "{}i", self.imag) + } + } else if self.imag == 1.0 { + write!(f, "{} + i", self.real) + } else { + write!(f, "{} + {}i", self.real, self.imag) + } + } +} + +#[derive(Debug)] +pub struct ComplexParseError; + +impl From for ComplexParseError { + fn from(_: ParseFloatError) -> Self { + Self {} + } +} + +impl FromStr for Complex { + type Err = ComplexParseError; + + fn from_str(s: &str) -> Result { + let mut c = Complex::default(); + let s = s.replace(' ', ""); + if s.contains('+') { + let (a, b) = s.split_once('+').unwrap(); + if a.contains('i') { + c.imag = a[..a.len() - 1].parse()?; + c.real = b.parse()?; + } else { + c.real = a.parse()?; + c.imag = b[..b.len() - 1].parse()?; + } + } else if s.contains('i') { + if s.len() == 1 { + c.imag = 1.0; + } else { + c.imag = s[..s.len() - 1].parse()?; + } + } else { + c.real = s.parse()?; + } + + Ok(c) + } +} + +impl From for Complex { + fn from(val: f64) -> Self { + Complex::new(val, 0.0) + } +} + +impl std::ops::Add for &Complex { + type Output = Complex; + + fn add(self, rhs: Self) -> Self::Output { + *self + *rhs + } +} + +impl std::ops::Add for Complex { + type Output = Complex; + + fn add(self, rhs: Self) -> Self::Output { + Complex::new(self.real + rhs.real, self.imag + rhs.imag) + } +} + +impl std::ops::Sub for &Complex { + type Output = Complex; + + fn sub(self, rhs: Self) -> Self::Output { + *self - *rhs + } +} + +impl std::ops::Sub for Complex { + type Output = Complex; + + fn sub(self, rhs: Self) -> Self::Output { + Complex::new(self.real - rhs.real, self.imag - rhs.imag) + } +} + +impl std::ops::Mul for &Complex { + type Output = Complex; + + fn mul(self, rhs: Self) -> Self::Output { + *self * *rhs + } +} + +impl std::ops::Mul for Complex { + type Output = Complex; + + fn mul(self, rhs: Self) -> Self::Output { + Complex::new( + self.real * rhs.real - self.imag * rhs.imag, + self.real * rhs.imag + self.imag * rhs.real, + ) + } +} + +impl std::ops::Div for &Complex { + type Output = Complex; + + fn div(self, rhs: Self) -> Self::Output { + *self / *rhs + } +} + +impl std::ops::Div for Complex { + type Output = Complex; + + fn div(self, rhs: Self) -> Self::Output { + let z = rhs.real * rhs.real + rhs.imag * rhs.imag; + Complex::new( + (self.real * rhs.real + self.imag * rhs.imag) / z, + (self.imag * rhs.real - self.real * rhs.imag) / z, + ) + } +} diff --git a/src/math/context.rs b/src/math/context.rs new file mode 100644 index 0000000..cb0bca0 --- /dev/null +++ b/src/math/context.rs @@ -0,0 +1,74 @@ +use crate::math::complex::Complex; +use crate::math::function::Function; +use crate::math::operation::Operation; +use crate::{commonsense_functions, commonsense_operations, commonsense_variables, functions, variables}; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Default, Clone)] +pub struct Context { + ops: Vec, + vars: HashMap, + funcs: HashMap>, +} + +impl Context { + pub fn new() -> Self { + Self::default() + } + + pub fn commonsense() -> Self { + Self::default() + .with_operations(commonsense_operations!{}) + .with_functions(commonsense_functions!{}) + .with_variables(commonsense_variables!{}) + } + + pub fn with_operations(mut self, ops: Vec) -> Self { + self.ops = ops; + self + } + + pub fn with_variables(mut self, vars: HashMap) -> Self { + self.vars = vars; + self + } + + pub fn with_functions(mut self, funcs: HashMap>) -> Self { + self.funcs = funcs; + self + } + + pub fn operations(&self) -> &Vec { + &self.ops + } + + pub fn set_variable(&mut self, name: &str, value: Complex) { + self.vars.insert(name.to_string(), value); + } + + pub fn set_function(&mut self, name: &str, func: Arc) { + self.funcs.insert(name.to_string(), func); + } + + pub fn variable(&self, name: &str) -> Option<&Complex> { + self.vars.get(name) + } + + pub fn function(&self, name: &str) -> Option<&Arc> { + self.funcs.get(name) + } +} + +#[macro_export] +macro_rules! variables { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap = HashMap::new(); + $( + h.insert($x.to_string(), $y); + )* + h + } + }; +} diff --git a/src/math/expression.rs b/src/math/expression.rs new file mode 100644 index 0000000..9d6d4f3 --- /dev/null +++ b/src/math/expression.rs @@ -0,0 +1,73 @@ +use crate::math::complex::Complex; +use crate::math::context::Context; +use crate::math::function::FunctionArgument; +use crate::math::string::{ContainsAndSkipBrackets, SplitMatchingBracket}; + +#[derive(Default)] +pub struct Expression { + repr: String, +} + +impl Expression { + pub fn from_string(str: &str) -> Self { + Self { + repr: str.replace(' ', ""), + } + } + + pub fn content(&self) -> &str { + &self.repr + } + + pub fn evaluate(&self, context: &Context) -> Result { + 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::() { + 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), context)?) + } 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/math/expression_function.rs b/src/math/expression_function.rs new file mode 100644 index 0000000..20f4816 --- /dev/null +++ b/src/math/expression_function.rs @@ -0,0 +1,60 @@ +use std::iter::zip; +use std::sync::Arc; + +use crate::math::{ + complex::Complex, + context::Context, + expression::Expression, + function::{Function, FunctionArgument, EmptyFunction} +}; + +#[derive(Default)] +pub struct ExpressionFunction { + expr: Expression, + name: String, + args: Vec, +} + +impl ExpressionFunction { + pub fn from_string(str: String) -> Self { + let str = str.replace(' ', ""); + let (lhs, expr) = str.split_once('=').unwrap(); + let (name, a) = lhs.split_once('(').unwrap(); + let args: Vec = a[0..a.len() - 1] + .split(',') + .map(|s| s.to_string()) + .collect(); + Self { + expr: Expression::from_string(expr), + name: name.to_string(), + args, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn content(&self) -> &str { + &self.expr.content() + } +} + +impl Function for ExpressionFunction { + fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result { + if args.len() == self.args.len() { + let mut nctx = ctx.clone(); + for (n, v) in zip(self.args.iter(), args.data().iter()) { + nctx.set_variable(n, *v) + } + nctx.set_function(self.name(), Arc::new(EmptyFunction {})); + self.expr.evaluate(&nctx) + } else { + Err(format!( + "{} takes {} parameters", + self.name, + self.args.len() + )) + } + } +} diff --git a/src/math/function.rs b/src/math/function.rs new file mode 100644 index 0000000..bd782f3 --- /dev/null +++ b/src/math/function.rs @@ -0,0 +1,68 @@ +use crate::math::{complex::Complex, context::Context}; + +pub struct FunctionArgument { + args : Vec, +} + +impl FunctionArgument { + pub fn new(args: Vec) -> Self { + Self { args } + } + + pub fn get(&self, i : usize) -> Complex { + self.args[i] + } + + pub fn len(&self) -> usize { + self.args.len() + } + + pub fn is_empty(&self) -> bool { + self.args.len() == 0 + } + + pub fn data(&self) -> &[Complex] { + &self.args + } +} + +pub trait Function { + fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result; +} + +#[macro_export] +macro_rules! functions { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap> = HashMap::new(); + $( + h.insert($x.to_string(), Arc::new($y)); + )* + h + } + }; +} + + +// Some default implementations +impl Function for T +where + T: Fn(Complex) -> Complex, +{ + fn eval(&self, args: FunctionArgument, _ctx: &Context) -> Result { + if args.len() == 1 { + Ok(self(args.get(0))) + } else { + Err("too many arguments".to_string()) + } + } +} + + +pub struct EmptyFunction {} + +impl Function for EmptyFunction { + fn eval(&self, _args: FunctionArgument, _ctx: &Context) -> Result { + Err("function not implemented in this scope".to_string()) + } +} diff --git a/src/math/mod.rs b/src/math/mod.rs new file mode 100644 index 0000000..7b9c17c --- /dev/null +++ b/src/math/mod.rs @@ -0,0 +1,8 @@ +pub mod complex; +pub mod context; +pub mod expression; +pub mod function; +pub mod operation; +pub mod string; +pub mod commonsense; +pub mod expression_function; diff --git a/src/math/operation.rs b/src/math/operation.rs new file mode 100644 index 0000000..bcf6510 --- /dev/null +++ b/src/math/operation.rs @@ -0,0 +1,32 @@ +use crate::math::complex::Complex; + +pub type Operator = fn(Complex, Complex) -> Complex; + +#[derive(Clone)] +pub struct Operation { + sign: char, + func: Operator +} + +impl Operation { + pub fn new(sign: char, func: Operator) -> Self { + Self { sign, func } + } + + pub fn sign(&self) -> char { + self.sign + } + + pub fn evaluate(&self, a: Complex, b: Complex) -> Complex { + (self.func)(a, b) + } +} + +#[macro_export] +macro_rules! operations { + {$($x:expr => $y:expr), *} => { + vec![$( + Operation::new($x, Box::new($y)), + )*] + }; +} diff --git a/src/math/string.rs b/src/math/string.rs new file mode 100644 index 0000000..c8d719b --- /dev/null +++ b/src/math/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 + } +} diff --git a/src/operation.rs b/src/operation.rs deleted file mode 100644 index 7eca774..0000000 --- a/src/operation.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::complex::Complex; - -pub type Operator = fn(Complex, Complex) -> Complex; - -#[derive(Clone)] -pub struct Operation { - sign: char, - func: Operator -} - -impl Operation { - pub fn new(sign: char, func: Operator) -> Self { - Self { sign, func } - } - - pub fn sign(&self) -> char { - self.sign - } - - pub fn evaluate(&self, a: Complex, b: Complex) -> Complex { - (self.func)(a, b) - } -} - -#[macro_export] -macro_rules! operations { - {$($x:expr => $y:expr), *} => { - vec![$( - Operation::new($x, Box::new($y)), - )*] - }; -} diff --git a/src/string.rs b/src/string.rs deleted file mode 100644 index c8d719b..0000000 --- a/src/string.rs +++ /dev/null @@ -1,61 +0,0 @@ -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 - } -} -- cgit v1.2.3-70-g09d2