From 0dc2e0572feecaf304a128f2d4012c7dd4af1be2 Mon Sep 17 00:00:00 2001 From: bakk Date: Mon, 17 May 2021 16:51:16 +0200 Subject: [PATCH] Fixed "dx" in integrals, and created calculus.rs Previously, eg. "dx" would not be parsed as "dx" after a function, since the parser did not keep track of whether or not it was currently inside an integral or not, properly. This commit fixes that, and also makes it possible to use any variable after the "d", eg. "dy". The integration function was also put in its own file: calculus.rs. --- kalk/src/calculus.rs | 78 +++++++++++++++++++++++++++++++++++++++++ kalk/src/interpreter.rs | 72 +++++-------------------------------- kalk/src/lib.rs | 1 + kalk/src/parser.rs | 10 ++++-- 4 files changed, 95 insertions(+), 66 deletions(-) create mode 100644 kalk/src/calculus.rs diff --git a/kalk/src/calculus.rs b/kalk/src/calculus.rs new file mode 100644 index 0000000..3aff5cd --- /dev/null +++ b/kalk/src/calculus.rs @@ -0,0 +1,78 @@ +use crate::ast::Expr; +use crate::ast::Stmt; +use crate::interpreter; +use crate::kalk_num::KalkNum; +use crate::lexer::TokenKind; +use crate::parser::CalcError; + +pub fn integrate( + context: &mut interpreter::Context, + expressions: &[Expr], +) -> Result { + let mut result = KalkNum::default(); + let mut integration_variable: Option<&str> = None; + + // integral(a, b, expr dx) + if let Expr::Binary(_, TokenKind::Star, right) = &expressions[2] { + if let Expr::Var(right_name) = &**right { + if right_name.starts_with("d") { + // Take the value, but remove the d, so that only eg. x is left from dx + integration_variable = Some(&right_name[1..]); + } + } + } + + if integration_variable.is_none() { + return Err(CalcError::ExpectedDx); + } + + // delta_x/2[f(a) + 2f(x_1) + 2f(x_2) + ...2f(x_n) + f(b)] + // where delta_x = (b - a) / n + // and x_n = a + i * delta_x + + // f(a) + context.symbol_table.set(Stmt::VarDecl( + integration_variable.unwrap().into(), + Box::new(expressions[0].clone()), + )); + + // "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value. + context.symbol_table.set(Stmt::VarDecl( + format!("d{}", integration_variable.unwrap()), + Box::new(Expr::Literal(1f64)), + )); + + result.value += interpreter::eval_expr(context, &expressions[2], "")?.value; + + // 2f(x_n) + // where x_n = a + i * delta_x + const N: i32 = 100; + let a = interpreter::eval_expr(context, &expressions[0], "")? + .value + .to_f64(); + let b = interpreter::eval_expr(context, &expressions[1], "")? + .value + .to_f64(); + let delta_x = (b - a) / N as f64; + for i in 1..N { + context.symbol_table.set(Stmt::VarDecl( + integration_variable.unwrap().into(), + Box::new(Expr::Literal(a + i as f64 * delta_x)), + )); + + // 2f(x_n) + result.value += 2 * interpreter::eval_expr(context, &expressions[2], "")?.value; + } + + // f(b) + context.symbol_table.set(Stmt::VarDecl( + integration_variable.unwrap().into(), + Box::new(expressions[1].clone()), + )); + result.value += interpreter::eval_expr(context, &expressions[2], "")?.value; + + // Finally, delta_x/2 for all of it + result.value *= delta_x / 2f64; + + return Ok(result); +} diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index c49b3f4..b8df058 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -1,4 +1,5 @@ use crate::ast::{Expr, Stmt}; +use crate::calculus; use crate::kalk_num::KalkNum; use crate::lexer::TokenKind; use crate::parser::CalcError; @@ -7,7 +8,7 @@ use crate::prelude; use crate::symbol_table::SymbolTable; pub struct Context<'a> { - symbol_table: &'a mut SymbolTable, + pub symbol_table: &'a mut SymbolTable, angle_unit: String, #[cfg(feature = "rug")] precision: u32, @@ -92,7 +93,11 @@ fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result Result { +pub(crate) fn eval_expr( + context: &mut Context, + expr: &Expr, + unit: &str, +) -> Result { #[cfg(not(target_arch = "wasm32"))] if let (Ok(elapsed), Some(timeout)) = (context.start_time.elapsed(), context.timeout) { if elapsed.as_millis() >= timeout { @@ -312,68 +317,7 @@ fn eval_fn_call_expr( )); } - let mut result = KalkNum::default(); - let mut integration_variable: Option<&str> = None; - - // integral(a, b, expr dx) - if let Expr::Binary(_, TokenKind::Star, right) = &expressions[2] { - if let Expr::Var(right_name) = &**right { - if right_name.starts_with("d") { - // Take the value, but remove the d, so that only eg. x is left from dx - integration_variable = Some(&right_name[1..]); - } - } - } - - if integration_variable.is_none() { - unimplemented!(); // TODO: Error message - } - - // delta_x/2[f(a) + 2f(x_1) + 2f(x_2) + ...2f(x_n) + f(b)] - // where delta_x = (b - a) / n - // and x_n = a + i * delta_x - - // f(a) - context.symbol_table.set(Stmt::VarDecl( - integration_variable.unwrap().into(), - Box::new(expressions[0].clone()), - )); - - // "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value. - context.symbol_table.set(Stmt::VarDecl( - String::from("dx"), - Box::new(Expr::Literal(1f64)), - )); - - result.value += eval_expr(context, &expressions[2], "")?.value; - - // 2f(x_n) - // where x_n = a + i * delta_x - const N: i32 = 100; - let a = eval_expr(context, &expressions[0], "")?.value.to_f64(); - let b = eval_expr(context, &expressions[1], "")?.value.to_f64(); - let delta_x = (b - a) / N as f64; - for i in 1..N { - context.symbol_table.set(Stmt::VarDecl( - integration_variable.unwrap().into(), - Box::new(Expr::Literal(a + i as f64 * delta_x)), - )); - - // 2f(x_n) - result.value += 2 * eval_expr(context, &expressions[2], "")?.value; - } - - // f(b) - context.symbol_table.set(Stmt::VarDecl( - integration_variable.unwrap().into(), - Box::new(expressions[1].clone()), - )); - result.value += eval_expr(context, &expressions[2], "")?.value; - - // Finally, delta_x/2 for all of it - result.value *= delta_x / 2f64; - - return Ok(result); + return calculus::integrate(context, expressions); } _ => (), } diff --git a/kalk/src/lib.rs b/kalk/src/lib.rs index f100d58..7f30996 100644 --- a/kalk/src/lib.rs +++ b/kalk/src/lib.rs @@ -1,4 +1,5 @@ pub mod ast; +mod calculus; mod interpreter; mod inverter; pub mod kalk_num; diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 2168678..a217c60 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -92,6 +92,7 @@ impl Default for Context { /// Error that occured during parsing or evaluation. #[derive(Debug, Clone, PartialEq)] pub enum CalcError { + ExpectedDx, IncorrectAmountOfArguments(usize, String, usize), InvalidNumberLiteral(String), InvalidOperator, @@ -110,6 +111,7 @@ pub enum CalcError { impl ToString for CalcError { fn to_string(&self) -> String { match self { + CalcError::ExpectedDx => format!("Expected eg. dx, to specify for which variable the operation is being done to. Example with integration: ∫(0, 1, x dx)."), CalcError::IncorrectAmountOfArguments(expected, func, got) => format!( "Expected {} arguments for function {}, but got {}.", expected, func, got @@ -493,7 +495,8 @@ fn parse_identifier(context: &mut Context) -> Result { if !parse_as_var_instead && match_token(context, TokenKind::OpenParenthesis) { advance(context); - if identifier.value == "integrate" || identifier.value == "∫" { + let is_integral = identifier.value == "integrate" || identifier.value == "∫"; + if is_integral { context.is_in_integral = true; } @@ -506,7 +509,10 @@ fn parse_identifier(context: &mut Context) -> Result { } consume(context, TokenKind::ClosedParenthesis)?; - context.is_in_integral = false; + + if is_integral { + context.is_in_integral = false; + } return Ok(Expr::FnCall(identifier.value, parameters)); }