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.
This commit is contained in:
bakk 2021-05-17 16:51:16 +02:00
parent 651b289f4b
commit 0dc2e0572f
4 changed files with 95 additions and 66 deletions

78
kalk/src/calculus.rs Normal file
View File

@ -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<KalkNum, CalcError> {
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);
}

View File

@ -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<KalkNum, CalcErr
eval_expr(context, &expr, "")
}
fn eval_expr(context: &mut Context, expr: &Expr, unit: &str) -> Result<KalkNum, CalcError> {
pub(crate) fn eval_expr(
context: &mut Context,
expr: &Expr,
unit: &str,
) -> Result<KalkNum, CalcError> {
#[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);
}
_ => (),
}

View File

@ -1,4 +1,5 @@
pub mod ast;
mod calculus;
mod interpreter;
mod inverter;
pub mod kalk_num;

View File

@ -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<Expr, CalcError> {
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<Expr, CalcError> {
}
consume(context, TokenKind::ClosedParenthesis)?;
context.is_in_integral = false;
if is_integral {
context.is_in_integral = false;
}
return Ok(Expr::FnCall(identifier.value, parameters));
}