Basics of derivation

Derivation implemented for function calls (only). Eg. f'(2). It is not
yet possible to do something like f''(2), but this should be implemented
in the future. It should also be possible to derive normal expressions,
but this is not yet possible.
This commit is contained in:
bakk 2021-05-17 20:36:53 +02:00
parent 9acdd71589
commit 15c30cfeee
5 changed files with 84 additions and 51 deletions

View File

@ -1,22 +1,51 @@
use crate::ast::Expr; use crate::ast::Expr;
use crate::ast::Identifier;
use crate::ast::Stmt; use crate::ast::Stmt;
use crate::interpreter; use crate::interpreter;
use crate::kalk_num::KalkNum; use crate::kalk_num::KalkNum;
use crate::lexer::TokenKind; use crate::lexer::TokenKind;
use crate::parser::CalcError; use crate::parser::CalcError;
pub fn derive_func(
context: &mut interpreter::Context,
name: &Identifier,
argument: KalkNum,
) -> Result<KalkNum, CalcError> {
const H: f64 = 0.000001;
let unit = &argument.unit.to_string();
let argument_with_h = Expr::Literal(argument.clone().add(context, H.into()).to_f64());
let argument_without_h = Expr::Literal(argument.to_f64());
let f_x_h = interpreter::eval_fn_call_expr(
context,
&Identifier::from_full_name(&name.pure_name),
&[argument_with_h],
unit,
)?;
let f_x = interpreter::eval_fn_call_expr(
context,
&Identifier::from_full_name(&name.pure_name),
&[argument_without_h],
unit,
)?;
Ok(f_x_h.sub(context, f_x).div(context, H.into()))
}
pub fn integrate( pub fn integrate(
context: &mut interpreter::Context, context: &mut interpreter::Context,
expressions: &[Expr], a: &Expr,
b: &Expr,
expr: &Expr,
) -> Result<KalkNum, CalcError> { ) -> Result<KalkNum, CalcError> {
let mut integration_variable: Option<&str> = None; let mut integration_variable: Option<&str> = None;
// integral(a, b, expr dx) // integral(a, b, expr dx)
if let Expr::Binary(_, TokenKind::Star, right) = &expressions[2] { if let Expr::Binary(_, TokenKind::Star, right) = expr {
if let Expr::Var(right_name) = &**right { if let Expr::Var(right_name) = &**right {
if right_name.starts_with("d") { if right_name.full_name.starts_with("d") {
// Take the value, but remove the d, so that only eg. x is left from dx // Take the value, but remove the d, so that only eg. x is left from dx
integration_variable = Some(&right_name[1..]); integration_variable = Some(&right_name.full_name[1..]);
} }
} }
} }
@ -27,17 +56,11 @@ pub fn integrate(
// "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value. // "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value.
context.symbol_table.set(Stmt::VarDecl( context.symbol_table.set(Stmt::VarDecl(
format!("d{}", integration_variable.unwrap()), Identifier::from_full_name(&format!("d{}", integration_variable.unwrap())),
Box::new(Expr::Literal(1f64)), Box::new(Expr::Literal(1f64)),
)); ));
simpsons_rule( simpsons_rule(context, a, b, expr, integration_variable.unwrap())
context,
&expressions[0],
&expressions[1],
&expressions[2],
integration_variable.unwrap(),
)
} }
/// Composite Simpson's 3/8 rule /// Composite Simpson's 3/8 rule
@ -56,16 +79,14 @@ fn simpsons_rule(
let h = (b - a) / N as f64; let h = (b - a) / N as f64;
for i in 0..=N { for i in 0..=N {
context.symbol_table.set(Stmt::VarDecl( context.symbol_table.set(Stmt::VarDecl(
integration_variable.into(), Identifier::from_full_name(integration_variable),
Box::new(Expr::Literal(a + i as f64 * h)), Box::new(Expr::Literal(a + i as f64 * h)),
)); ));
let factor = if i == 0 || i == N { let factor = match i {
1 0 | N => 1,
} else if i % 3 == 0 { _ if i % 3 == 0 => 2,
2 _ => 3,
} else {
3
}; };
// factor * f(x_n) // factor * f(x_n)

View File

@ -244,7 +244,7 @@ fn eval_var_expr(
.cloned(); .cloned();
match var_decl { match var_decl {
Some(Stmt::VarDecl(_, expr)) => eval_expr(context, &expr, unit), Some(Stmt::VarDecl(_, expr)) => eval_expr(context, &expr, unit),
_ => Err(CalcError::UndefinedVar(identifier.full_name)), _ => Err(CalcError::UndefinedVar(identifier.full_name.clone())),
} }
} }
@ -272,13 +272,28 @@ pub(crate) fn eval_fn_call_expr(
// Prelude // Prelude
let prelude_func = match expressions.len() { let prelude_func = match expressions.len() {
1 => { 1 => {
let x = eval_expr(context, &expressions[0], "")?.value; let x = eval_expr(context, &expressions[0], "")?;
prelude::call_unary_func(context, identifier, x, &context.angle_unit.clone()) if identifier.prime_count > 0 {
return calculus::derive_func(context, &identifier, x);
} else {
prelude::call_unary_func(
context,
&identifier.full_name,
x.value,
&context.angle_unit.clone(),
)
}
} }
2 => { 2 => {
let x = eval_expr(context, &expressions[0], "")?.value; let x = eval_expr(context, &expressions[0], "")?.value;
let y = eval_expr(context, &expressions[1], "")?.value; let y = eval_expr(context, &expressions[1], "")?.value;
prelude::call_binary_func(context, identifier, x, y, &context.angle_unit.clone()) prelude::call_binary_func(
context,
&identifier.full_name,
x,
y,
&context.angle_unit.clone(),
)
} }
_ => None, _ => None,
}; };
@ -339,7 +354,7 @@ pub(crate) fn eval_fn_call_expr(
if arguments.len() != expressions.len() { if arguments.len() != expressions.len() {
return Err(CalcError::IncorrectAmountOfArguments( return Err(CalcError::IncorrectAmountOfArguments(
arguments.len(), arguments.len(),
identifier.full_name, identifier.full_name.clone(),
expressions.len(), expressions.len(),
)); ));
} }
@ -357,7 +372,7 @@ pub(crate) fn eval_fn_call_expr(
eval_expr(context, &fn_body, unit) eval_expr(context, &fn_body, unit)
} }
_ => Err(CalcError::UndefinedFn(identifier.full_name)), _ => Err(CalcError::UndefinedFn(identifier.full_name.clone())),
} }
} }

View File

@ -259,13 +259,13 @@ fn invert_var(
unknown_var: &str, unknown_var: &str,
) -> Result<(Expr, Expr), CalcError> { ) -> Result<(Expr, Expr), CalcError> {
if identifier.full_name == unknown_var { if identifier.full_name == unknown_var {
Ok((target_expr, Expr::Var(*identifier))) Ok((target_expr, Expr::Var(identifier.clone())))
} else if let Some(Stmt::VarDecl(_, var_expr)) = } else if let Some(Stmt::VarDecl(_, var_expr)) =
symbol_table.get_var(&identifier.full_name).cloned() symbol_table.get_var(&identifier.full_name).cloned()
{ {
invert(target_expr, symbol_table, &var_expr, unknown_var) invert(target_expr, symbol_table, &var_expr, unknown_var)
} else { } else {
Ok((target_expr, Expr::Var(*identifier))) Ok((target_expr, Expr::Var(identifier.clone())))
} }
} }
@ -329,14 +329,14 @@ fn invert_fn_call(
{ {
(parameters, body) (parameters, body)
} else { } else {
return Err(CalcError::UndefinedFn(identifier.full_name)); return Err(CalcError::UndefinedFn(identifier.full_name.clone()));
}; };
// Make sure the input is valid. // Make sure the input is valid.
if parameters.len() != arguments.len() { if parameters.len() != arguments.len() {
return Err(CalcError::IncorrectAmountOfArguments( return Err(CalcError::IncorrectAmountOfArguments(
parameters.len(), parameters.len(),
identifier.full_name, identifier.full_name.clone(),
arguments.len(), arguments.len(),
)); ));
} }

View File

@ -16,6 +16,7 @@ pub enum TokenKind {
Equals, Equals,
Exclamation, Exclamation,
Percent, Percent,
Tick,
UnitKeyword, UnitKeyword,
ToKeyword, ToKeyword,
@ -114,6 +115,7 @@ impl<'a> Lexer<'a> {
',' => build(TokenKind::Comma, "", span), ',' => build(TokenKind::Comma, "", span),
';' => build(TokenKind::Semicolon, "", span), ';' => build(TokenKind::Semicolon, "", span),
'%' => build(TokenKind::Percent, "", span), '%' => build(TokenKind::Percent, "", span),
'\'' => build(TokenKind::Tick, "", span),
_ => build(TokenKind::Unknown, "", span), _ => build(TokenKind::Unknown, "", span),
}; };

View File

@ -478,23 +478,20 @@ fn parse_group_fn(context: &mut Context) -> Result<Expr, CalcError> {
} }
fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> { fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
let identifier = advance(context).clone(); let identifier = Identifier::from_full_name(&advance(context).value);
// Eg. sqrt64 // Eg. sqrt64
if match_token(context, TokenKind::Literal) { if match_token(context, TokenKind::Literal) {
// If there is a function with this name, parse it as a function, with the next token as the argument. // If there is a function with this name, parse it as a function, with the next token as the argument.
if context.symbol_table.contains_fn(&identifier.value) { if context.symbol_table.contains_fn(&identifier.pure_name) {
let parameter = Expr::Literal(string_to_num(&advance(context).value)); let parameter = Expr::Literal(string_to_num(&advance(context).value));
return Ok(Expr::FnCall( return Ok(Expr::FnCall(identifier, vec![parameter]));
Identifier::from_full_name(&identifier.value),
vec![parameter],
));
} }
} }
let parse_as_var_instead = match_token(context, TokenKind::OpenParenthesis) let parse_as_var_instead = match_token(context, TokenKind::OpenParenthesis)
&& !context.parsing_identifier_stmt && !context.parsing_identifier_stmt
&& !context.symbol_table.contains_fn(&identifier.value); && !context.symbol_table.contains_fn(&identifier.pure_name);
// Eg. sqrt(64) // Eg. sqrt(64)
// If the function doesn't exist, parse it as a variable and multiplication instead. // If the function doesn't exist, parse it as a variable and multiplication instead.
@ -503,7 +500,7 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
if !parse_as_var_instead && match_token(context, TokenKind::OpenParenthesis) { if !parse_as_var_instead && match_token(context, TokenKind::OpenParenthesis) {
advance(context); advance(context);
let is_integral = identifier.value == "integrate" || identifier.value == ""; let is_integral = identifier.full_name == "integrate" || identifier.full_name == "";
if is_integral { if is_integral {
context.is_in_integral = true; context.is_in_integral = true;
} }
@ -522,34 +519,31 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
context.is_in_integral = false; context.is_in_integral = false;
} }
return Ok(Expr::FnCall( return Ok(Expr::FnCall(identifier, parameters));
Identifier::from_full_name(&identifier.value),
parameters,
));
} }
// Eg. dx inside an integral, should be parsed as *one* identifier // Eg. dx inside an integral, should be parsed as *one* identifier
if context.is_in_integral && identifier.value.starts_with("d") { if context.is_in_integral && identifier.full_name.starts_with("d") {
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value))); return Ok(Expr::Var(identifier));
} }
// Eg. x // Eg. x
if parse_as_var_instead || context.symbol_table.contains_var(&identifier.value) { if parse_as_var_instead || context.symbol_table.contains_var(&identifier.pure_name) {
Ok(Expr::Var(Identifier::from_full_name(&identifier.value))) Ok(Expr::Var(identifier))
} else if context.parsing_unit_decl { } else if context.parsing_unit_decl {
context.unit_decl_base_unit = Some(identifier.value); context.unit_decl_base_unit = Some(identifier.full_name);
Ok(Expr::Var(Identifier::from_full_name(DECL_UNIT))) Ok(Expr::Var(Identifier::from_full_name(DECL_UNIT)))
} else { } else {
if let Some(equation_var) = &context.equation_variable { if let Some(equation_var) = &context.equation_variable {
if &identifier.value == equation_var { if &identifier.full_name == equation_var {
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value))); return Ok(Expr::Var(identifier));
} }
} else if context.contains_equal_sign { } else if context.contains_equal_sign {
context.equation_variable = Some(identifier.value.clone()); context.equation_variable = Some(identifier.full_name.clone());
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value))); return Ok(Expr::Var(identifier));
} }
let mut chars = identifier.value.chars(); let mut chars = identifier.pure_name.chars();
let mut left = Expr::Var(Identifier::from_full_name( let mut left = Expr::Var(Identifier::from_full_name(
&chars.next().unwrap().to_string(), &chars.next().unwrap().to_string(),
)); ));
@ -575,6 +569,7 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
left = Expr::Binary(Box::new(left), TokenKind::Star, Box::new(right)); left = Expr::Binary(Box::new(left), TokenKind::Star, Box::new(right));
} }
// TODO: When implementing derivation for variables, make sure to add the derivation here.
Ok(left) Ok(left)
} }
} }