You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
18 KiB
594 lines
18 KiB
use crate::error::Error; |
|
use std::collections::{HashMap, HashSet}; |
|
use std::fmt; |
|
use std::io::Write; |
|
|
|
#[derive(Debug, PartialEq, Clone)] |
|
pub enum FunctionType { |
|
Sin, |
|
Cos, |
|
Tan, |
|
Sec, |
|
Csc, |
|
Cot, |
|
Asin, |
|
Acos, |
|
Atan, |
|
Sign, |
|
Abs, |
|
Sqrt, |
|
Exp, |
|
Ln, |
|
Log, |
|
Neg, |
|
} |
|
|
|
impl fmt::Display for FunctionType { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
let text = match *self { |
|
FunctionType::Sin => "sin", |
|
FunctionType::Cos => "cos", |
|
FunctionType::Tan => "tan", |
|
FunctionType::Sec => "sec", |
|
FunctionType::Csc => "csc", |
|
FunctionType::Cot => "cot", |
|
FunctionType::Asin => "asin", |
|
FunctionType::Acos => "acos", |
|
FunctionType::Atan => "atan", |
|
FunctionType::Sign => "sign", |
|
FunctionType::Abs => "abs", |
|
FunctionType::Sqrt => "√", |
|
FunctionType::Exp => "exp", |
|
FunctionType::Ln => "ln", |
|
FunctionType::Log => "log", |
|
FunctionType::Neg => "-", |
|
}; |
|
text.fmt(f) |
|
} |
|
} |
|
|
|
#[derive(Debug, Clone)] |
|
pub struct FunctionData { |
|
kind: FunctionType, |
|
arg: Box<Expression>, |
|
} |
|
|
|
impl fmt::Display for FunctionData { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{}({})", self.kind, self.arg) |
|
} |
|
} |
|
|
|
impl FunctionData { |
|
pub fn new(kind: FunctionType, arg_exp: Expression) -> Self { |
|
let arg = Box::new(arg_exp); |
|
FunctionData { kind, arg } |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<f64, Error> { |
|
let value = self.arg.eval(idents, vars)?; |
|
|
|
match self.kind { |
|
FunctionType::Sin => Ok(value.sin()), |
|
FunctionType::Cos => Ok(value.cos()), |
|
FunctionType::Tan => Ok(value.tan()), |
|
FunctionType::Sec => Ok(value.cos().recip()), |
|
FunctionType::Csc => Ok(value.sin().recip()), |
|
FunctionType::Cot => Ok(value.tan().recip()), |
|
FunctionType::Asin => Ok(value.asin()), |
|
FunctionType::Acos => Ok(value.acos()), |
|
FunctionType::Atan => Ok(value.atan()), |
|
FunctionType::Sign => Ok(value.signum()), |
|
FunctionType::Abs => Ok(value.abs()), |
|
FunctionType::Sqrt => Ok(value.sqrt()), |
|
FunctionType::Exp => Ok(value.exp()), |
|
FunctionType::Ln => Ok(value.ln()), |
|
FunctionType::Log => Ok(value.log10()), |
|
FunctionType::Neg => Ok(-value), |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
let label = match self.kind { |
|
FunctionType::Sin => "sin", |
|
FunctionType::Cos => "cos", |
|
FunctionType::Tan => "tan", |
|
FunctionType::Sec => "sec", |
|
FunctionType::Csc => "csc", |
|
FunctionType::Cot => "cot", |
|
FunctionType::Asin => "asin", |
|
FunctionType::Acos => "acos", |
|
FunctionType::Atan => "atan", |
|
FunctionType::Sign => "sign", |
|
FunctionType::Abs => "abs", |
|
FunctionType::Sqrt => "sqrt", |
|
FunctionType::Exp => "exp", |
|
FunctionType::Ln => "ln", |
|
FunctionType::Log => "log_10", |
|
FunctionType::Neg => "-", |
|
}; |
|
writeln!( |
|
output, |
|
"\tnode{} [label=\"{}\",shape=trapezium];", |
|
id, label |
|
)?; |
|
let max_id = self.arg.graph(output, id + 1)?; |
|
writeln!(output, "\tnode{} -> node{};", id, max_id)?; |
|
Ok(max_id) |
|
} |
|
} |
|
|
|
#[derive(Debug, Clone)] |
|
pub struct OperationData { |
|
kind: char, |
|
left: Box<Expression>, |
|
right: Box<Expression>, |
|
} |
|
|
|
impl fmt::Display for OperationData { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "({} {} {})", self.left, self.kind, self.right) |
|
} |
|
} |
|
|
|
impl OperationData { |
|
pub fn new(kind: char, left_exp: Expression, right_exp: Expression) -> Self { |
|
let left = Box::new(left_exp); |
|
let right = Box::new(right_exp); |
|
OperationData { kind, left, right } |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<f64, Error> { |
|
let left = self.left.eval(idents, vars)?; |
|
let right = self.right.eval(idents, vars)?; |
|
|
|
match self.kind { |
|
'+' => Ok(left + right), |
|
'-' => Ok(left - right), |
|
'*' => Ok(left * right), |
|
'/' => Ok(left / right), |
|
'^' => Ok(left.powf(right)), |
|
_ => Err(Error::UnrecognisedBinaryOperator), |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
writeln!( |
|
output, |
|
"\tnode{} [label=\"{}\",shape=invtriangle];", |
|
id, self.kind |
|
)?; |
|
let new_id = self.left.graph(output, id + 1)?; |
|
let max_id = self.right.graph(output, new_id + 1)?; |
|
writeln!(output, "\tnode{} -> node{} [label=\"left\"];", id, id + 1)?; |
|
writeln!( |
|
output, |
|
"\tnode{} -> node{} [label=\"right\"];", |
|
id, |
|
new_id + 1 |
|
)?; |
|
Ok(max_id) |
|
} |
|
} |
|
|
|
#[derive(Debug, Clone)] |
|
pub enum Expression { |
|
Var(char), |
|
Float(f64), |
|
Ident(String), |
|
Function(FunctionData), |
|
Operation(OperationData), |
|
} |
|
|
|
impl fmt::Display for Expression { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match self { |
|
Expression::Var(v) => v.fmt(f), |
|
Expression::Float(n) => n.fmt(f), |
|
Expression::Ident(s) => s.fmt(f), |
|
Expression::Function(function) => function.fmt(f), |
|
Expression::Operation(op) => op.fmt(f), |
|
} |
|
} |
|
} |
|
|
|
impl Expression { |
|
pub fn var(c: char) -> Self { |
|
Expression::Var(c) |
|
} |
|
|
|
pub fn float<T: Into<f64>>(f: T) -> Self { |
|
Expression::Float(f.into()) |
|
} |
|
|
|
pub fn ident(s: String) -> Self { |
|
Expression::Ident(s) |
|
} |
|
|
|
pub fn function(f: FunctionType, arg: Expression) -> Self { |
|
let data = FunctionData::new(f, arg); |
|
Expression::Function(data) |
|
} |
|
|
|
pub fn operation(kind: char, left: Expression, right: Expression) -> Self { |
|
let data = OperationData::new(kind, left, right); |
|
Expression::Operation(data) |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<f64, Error> { |
|
match self { |
|
Expression::Float(f) => Ok(*f), |
|
Expression::Function(f) => f.eval(idents, vars), |
|
Expression::Operation(o) => o.eval(idents, vars), |
|
Expression::Var(c) => { |
|
let var_values = vars.as_ref().ok_or::<Error>(Error::MissingVarMap)?; |
|
let value = var_values.get(c).ok_or::<Error>(Error::MissingVarValue)?; |
|
Ok(*value) |
|
} |
|
Expression::Ident(s) => { |
|
let ident_exps = idents.as_ref().ok_or::<Error>(Error::MissingIdentMap)?; |
|
let referred = ident_exps |
|
.get(s) |
|
.ok_or::<Error>(Error::MissingIdentAssignment)?; |
|
referred.eval(idents, vars) |
|
} |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
match self { |
|
Expression::Float(f) => { |
|
writeln!(output, "\tnode{} [label=\"{}\",shape=oval];", id, f)?; |
|
Ok(id) |
|
} |
|
Expression::Function(f) => f.graph(output, id), |
|
Expression::Operation(o) => o.graph(output, id), |
|
Expression::Var(c) => { |
|
writeln!(output, "\tnode{} [label=\"{}\",shape=egg];", id, c)?; |
|
Ok(id) |
|
} |
|
Expression::Ident(s) => { |
|
writeln!(output, "\tnode{} [label=\"{}\",shape=cds];", id, s)?; |
|
Ok(id) |
|
} |
|
} |
|
} |
|
|
|
pub fn var_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<char>, Error> { |
|
match self { |
|
Expression::Float(_) => Ok(HashSet::new()), |
|
Expression::Function(f) => f.arg.var_dependencies(idents), |
|
Expression::Operation(o) => { |
|
let left = o.left.var_dependencies(idents)?; |
|
let right = o.right.var_dependencies(idents)?; |
|
let mut result: HashSet<char> = HashSet::new(); |
|
for dep in left { |
|
result.insert(dep); |
|
} |
|
for dep in right { |
|
result.insert(dep); |
|
} |
|
Ok(result) |
|
} |
|
Expression::Var(c) => { |
|
let mut result: HashSet<char> = HashSet::new(); |
|
result.insert(*c); |
|
Ok(result) |
|
} |
|
Expression::Ident(s) => { |
|
let ident_exps = idents.as_ref().ok_or::<Error>(Error::MissingIdentMap)?; |
|
let referred = ident_exps |
|
.get(s) |
|
.ok_or::<Error>(Error::MissingIdentAssignment)?; |
|
referred.var_dependencies(idents) |
|
} |
|
} |
|
} |
|
|
|
pub fn ident_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<(String, usize)>, Error> { |
|
match self { |
|
Expression::Float(_) => Ok(HashSet::new()), |
|
Expression::Function(f) => f.arg.ident_dependencies(idents), |
|
Expression::Operation(o) => { |
|
let left = o.left.ident_dependencies(idents)?; |
|
let right = o.right.ident_dependencies(idents)?; |
|
let mut result: HashSet<(String, usize)> = HashSet::new(); |
|
for dep in left.union(&right) { |
|
result.insert(dep.to_owned()); |
|
} |
|
Ok(result) |
|
} |
|
Expression::Var(_) => Ok(HashSet::new()), |
|
Expression::Ident(s) => { |
|
let mut result: HashSet<(String, usize)> = HashSet::new(); |
|
|
|
let mut max_recursion: usize = 0; |
|
|
|
let ident_exps = idents.as_ref().ok_or::<Error>(Error::MissingIdentMap)?; |
|
let referred = ident_exps |
|
.get(s) |
|
.ok_or::<Error>(Error::MissingIdentAssignment)?; |
|
let recursive = referred.ident_dependencies(idents)?; |
|
for dep in recursive { |
|
let (_, level) = dep; |
|
result.insert(dep); |
|
if level + 1 > max_recursion { |
|
max_recursion = level + 1; |
|
} |
|
} |
|
result.insert((s.to_owned(), max_recursion)); |
|
Ok(result) |
|
} |
|
} |
|
} |
|
} |
|
|
|
#[derive(Debug)] |
|
pub struct Condition { |
|
kind: char, |
|
left: Box<Expression>, |
|
right: Box<Expression>, |
|
} |
|
|
|
impl fmt::Display for Condition { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{} {} {}", self.left, self.kind, self.right) |
|
} |
|
} |
|
|
|
impl Condition { |
|
pub fn new(kind: char, left_exp: Expression, right_exp: Expression) -> Self { |
|
let left = Box::new(left_exp); |
|
let right = Box::new(right_exp); |
|
Condition { kind, left, right } |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<bool, Error> { |
|
let left_val = self.left.eval(idents, vars)?; |
|
let right_val = self.right.eval(idents, vars)?; |
|
match self.kind { |
|
'=' => Ok((left_val - right_val).abs() < 100.0_f64 * f64::EPSILON), |
|
'<' => Ok(left_val < right_val), |
|
'>' => Ok(left_val > right_val), |
|
'≤' => Ok(left_val <= right_val), |
|
'≥' => Ok(left_val >= right_val), |
|
_ => Err(Error::UnrecognisedCondition), |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
writeln!( |
|
output, |
|
"\tnode{} [label=\"{}\",shape=square];", |
|
id, self.kind |
|
)?; |
|
let new_id = self.left.graph(output, id + 1)?; |
|
let max_id = self.right.graph(output, new_id + 1)?; |
|
writeln!(output, "\tnode{} -> node{} [label=\"left\"];", id, id + 1)?; |
|
writeln!( |
|
output, |
|
"\tnode{} -> node{} [label=\"right\"];", |
|
id, |
|
new_id + 1 |
|
)?; |
|
Ok(max_id) |
|
} |
|
|
|
pub fn ident_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<(String, usize)>, Error> { |
|
let left_idents = self.left.ident_dependencies(idents)?; |
|
let right_idents = self.right.ident_dependencies(idents)?; |
|
let mut result: HashSet<(String, usize)> = HashSet::new(); |
|
for dep in left_idents.union(&right_idents) { |
|
result.insert(dep.to_owned()); |
|
} |
|
Ok(result) |
|
} |
|
} |
|
|
|
#[derive(Debug)] |
|
pub struct JunctionData { |
|
kind: char, |
|
left: Box<Junction>, |
|
right: Box<Junction>, |
|
} |
|
|
|
impl fmt::Display for JunctionData { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{{{} {} {}}}", self.left, self.kind, self.right) |
|
} |
|
} |
|
|
|
impl JunctionData { |
|
pub fn new(kind: char, left_cond: Junction, right_cond: Junction) -> Self { |
|
let left = Box::new(left_cond); |
|
let right = Box::new(right_cond); |
|
JunctionData { kind, left, right } |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<bool, Error> { |
|
let left_val = self.left.eval(idents, vars)?; |
|
let right_val = self.right.eval(idents, vars)?; |
|
match self.kind { |
|
'⋀' => Ok(left_val & right_val), |
|
'⋁' => Ok(left_val | right_val), |
|
'⊻' => Ok(left_val ^ right_val), |
|
'⊼' => Ok(!(left_val & right_val)), |
|
'⊽' => Ok(!(left_val | right_val)), |
|
_ => Err(Error::UnrecognisedJunction), |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
writeln!( |
|
output, |
|
"\tnode{} [label=\"{}\",shape=hexagon];", |
|
id, self.kind |
|
)?; |
|
let new_id = self.left.graph(output, id + 1)?; |
|
let max_id = self.right.graph(output, new_id + 1)?; |
|
writeln!(output, "\tnode{} -> node{} [label=\"left\"];", id, id + 1)?; |
|
writeln!( |
|
output, |
|
"\tnode{} -> node{} [label=\"right\"];", |
|
id, |
|
new_id + 1 |
|
)?; |
|
Ok(max_id) |
|
} |
|
|
|
pub fn ident_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<(String, usize)>, Error> { |
|
let left_idents = self.left.ident_dependencies(idents)?; |
|
let right_idents = self.right.ident_dependencies(idents)?; |
|
let mut result: HashSet<(String, usize)> = HashSet::new(); |
|
for dep in left_idents.union(&right_idents) { |
|
result.insert(dep.to_owned()); |
|
} |
|
Ok(result) |
|
} |
|
} |
|
|
|
#[derive(Debug)] |
|
pub enum Junction { |
|
Singleton(Condition), |
|
Meta(JunctionData), |
|
} |
|
|
|
impl fmt::Display for Junction { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match self { |
|
Junction::Singleton(condition) => condition.fmt(f), |
|
Junction::Meta(meta) => meta.fmt(f), |
|
} |
|
} |
|
} |
|
|
|
impl Junction { |
|
pub fn singleton(cond: Condition) -> Self { |
|
Junction::Singleton(cond) |
|
} |
|
|
|
pub fn meta(kind: char, left_cond: Junction, right_cond: Junction) -> Self { |
|
let data = JunctionData::new(kind, left_cond, right_cond); |
|
Junction::Meta(data) |
|
} |
|
|
|
pub fn eval( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
vars: &Option<&HashMap<char, f64>>, |
|
) -> Result<bool, Error> { |
|
match self { |
|
Junction::Meta(meta) => meta.eval(idents, vars), |
|
Junction::Singleton(cond) => cond.eval(idents, vars), |
|
} |
|
} |
|
|
|
pub fn graph(&self, output: &mut impl Write, id: usize) -> Result<usize, Error> { |
|
match self { |
|
Junction::Meta(meta) => meta.graph(output, id), |
|
Junction::Singleton(cond) => cond.graph(output, id), |
|
} |
|
} |
|
|
|
pub fn ident_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<(String, usize)>, Error> { |
|
match self { |
|
Junction::Singleton(cond) => cond.ident_dependencies(idents), |
|
Junction::Meta(meta) => meta.ident_dependencies(idents), |
|
} |
|
} |
|
} |
|
|
|
#[derive(Debug)] |
|
pub struct Boundary { |
|
pub var: char, |
|
pub min: Expression, |
|
pub max: Expression, |
|
} |
|
|
|
impl fmt::Display for Boundary { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{} ≤ {} ≤ {}", self.min, self.var, self.max) |
|
} |
|
} |
|
|
|
impl Boundary { |
|
pub fn new( |
|
l: Expression, |
|
lcond: char, |
|
var: char, |
|
rcond: char, |
|
r: Expression, |
|
) -> Result<Self, ()> { |
|
if var != 'x' && var != 'y' && var != 'z' { |
|
return Err(()); |
|
} |
|
if lcond == '<' || lcond == '≤' { |
|
if rcond == '<' || rcond == '≤' { |
|
let min = l; |
|
let max = r; |
|
return Ok(Boundary { var, min, max }); |
|
} |
|
return Err(()); |
|
} else if lcond == '>' || lcond == '≥' { |
|
if rcond == '>' || rcond == '≥' { |
|
let min = r; |
|
let max = l; |
|
return Ok(Boundary { var, min, max }); |
|
} |
|
return Err(()); |
|
} |
|
Err(()) |
|
} |
|
|
|
pub fn ident_dependencies( |
|
&self, |
|
idents: &Option<&HashMap<String, Expression>>, |
|
) -> Result<HashSet<(String, usize)>, Error> { |
|
let left_idents = self.min.ident_dependencies(idents)?; |
|
let right_idents = self.max.ident_dependencies(idents)?; |
|
let mut result: HashSet<(String, usize)> = HashSet::new(); |
|
for dep in left_idents.union(&right_idents) { |
|
result.insert(dep.to_owned()); |
|
} |
|
Ok(result) |
|
} |
|
} |
|
|
|
pub type Boundaries = [Boundary; 3]; |
|
|
|
pub type Structure = (Option<HashMap<String, Expression>>, Boundaries, Junction);
|
|
|