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 126e2e86f3
commit 7a56322e0f
5 changed files with 84 additions and 51 deletions

View File

@ -1,22 +1,51 @@
use crate::ast::Expr;
use crate::ast::Identifier;
use crate::ast::Stmt;
use crate::interpreter;
use crate::kalk_num::KalkNum;
use crate::lexer::TokenKind;
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(
context: &mut interpreter::Context,
expressions: &[Expr],
a: &Expr,
b: &Expr,
expr: &Expr,
) -> Result<KalkNum, CalcError> {
let mut integration_variable: Option<&str> = None;
// 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 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
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.
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)),
));
simpsons_rule(
context,
&expressions[0],
&expressions[1],
&expressions[2],
integration_variable.unwrap(),
)
simpsons_rule(context, a, b, expr, integration_variable.unwrap())
}
/// Composite Simpson's 3/8 rule
@ -56,16 +79,14 @@ fn simpsons_rule(
let h = (b - a) / N as f64;
for i in 0..=N {
context.symbol_table.set(Stmt::VarDecl(
integration_variable.into(),
Identifier::from_full_name(integration_variable),
Box::new(Expr::Literal(a + i as f64 * h)),
));
let factor = if i == 0 || i == N {
1
} else if i % 3 == 0 {
2
} else {
3
let factor = match i {
0 | N => 1,
_ if i % 3 == 0 => 2,
_ => 3,
};
// factor * f(x_n)

View File

@ -244,7 +244,7 @@ fn eval_var_expr(
.cloned();
match var_decl {
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
let prelude_func = match expressions.len() {
1 => {
let x = eval_expr(context, &expressions[0], "")?.value;
prelude::call_unary_func(context, identifier, x, &context.angle_unit.clone())
let x = eval_expr(context, &expressions[0], "")?;
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 => {
let x = eval_expr(context, &expressions[0], "")?.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,
};
@ -339,7 +354,7 @@ pub(crate) fn eval_fn_call_expr(
if arguments.len() != expressions.len() {
return Err(CalcError::IncorrectAmountOfArguments(
arguments.len(),
identifier.full_name,
identifier.full_name.clone(),
expressions.len(),
));
}
@ -357,7 +372,7 @@ pub(crate) fn eval_fn_call_expr(
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,
) -> Result<(Expr, Expr), CalcError> {
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)) =
symbol_table.get_var(&identifier.full_name).cloned()
{
invert(target_expr, symbol_table, &var_expr, unknown_var)
} else {
Ok((target_expr, Expr::Var(*identifier)))
Ok((target_expr, Expr::Var(identifier.clone())))
}
}
@ -329,14 +329,14 @@ fn invert_fn_call(
{
(parameters, body)
} else {
return Err(CalcError::UndefinedFn(identifier.full_name));
return Err(CalcError::UndefinedFn(identifier.full_name.clone()));
};
// Make sure the input is valid.
if parameters.len() != arguments.len() {
return Err(CalcError::IncorrectAmountOfArguments(
parameters.len(),
identifier.full_name,
identifier.full_name.clone(),
arguments.len(),
));
}

View File

@ -16,6 +16,7 @@ pub enum TokenKind {
Equals,
Exclamation,
Percent,
Tick,
UnitKeyword,
ToKeyword,
@ -114,6 +115,7 @@ impl<'a> Lexer<'a> {
',' => build(TokenKind::Comma, "", span),
';' => build(TokenKind::Semicolon, "", span),
'%' => build(TokenKind::Percent, "", span),
'\'' => build(TokenKind::Tick, "", 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> {
let identifier = advance(context).clone();
let identifier = Identifier::from_full_name(&advance(context).value);
// Eg. sqrt64
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 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));
return Ok(Expr::FnCall(
Identifier::from_full_name(&identifier.value),
vec![parameter],
));
return Ok(Expr::FnCall(identifier, vec![parameter]));
}
}
let parse_as_var_instead = match_token(context, TokenKind::OpenParenthesis)
&& !context.parsing_identifier_stmt
&& !context.symbol_table.contains_fn(&identifier.value);
&& !context.symbol_table.contains_fn(&identifier.pure_name);
// Eg. sqrt(64)
// 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) {
advance(context);
let is_integral = identifier.value == "integrate" || identifier.value == "";
let is_integral = identifier.full_name == "integrate" || identifier.full_name == "";
if is_integral {
context.is_in_integral = true;
}
@ -522,34 +519,31 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
context.is_in_integral = false;
}
return Ok(Expr::FnCall(
Identifier::from_full_name(&identifier.value),
parameters,
));
return Ok(Expr::FnCall(identifier, parameters));
}
// Eg. dx inside an integral, should be parsed as *one* identifier
if context.is_in_integral && identifier.value.starts_with("d") {
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value)));
if context.is_in_integral && identifier.full_name.starts_with("d") {
return Ok(Expr::Var(identifier));
}
// Eg. x
if parse_as_var_instead || context.symbol_table.contains_var(&identifier.value) {
Ok(Expr::Var(Identifier::from_full_name(&identifier.value)))
if parse_as_var_instead || context.symbol_table.contains_var(&identifier.pure_name) {
Ok(Expr::Var(identifier))
} 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)))
} else {
if let Some(equation_var) = &context.equation_variable {
if &identifier.value == equation_var {
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value)));
if &identifier.full_name == equation_var {
return Ok(Expr::Var(identifier));
}
} else if context.contains_equal_sign {
context.equation_variable = Some(identifier.value.clone());
return Ok(Expr::Var(Identifier::from_full_name(&identifier.value)));
context.equation_variable = Some(identifier.full_name.clone());
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(
&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));
}
// TODO: When implementing derivation for variables, make sure to add the derivation here.
Ok(left)
}
}