mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-26 00:28:11 +08:00
Move the math expression parser from Pest to Chumsky and add more features (#2685)
Rewrite the math-parser library using a chumsky-based lexer and parser, adding functions, comparisons, logic, and conditionals
This commit is contained in:
Generated
+38
-59
@@ -654,6 +654,19 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chumsky"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14377e276b2c8300513dff55ba4cc4142b44e5d6de6d00eb5b2307d650bb4ec1"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"regex-automata 0.3.9",
|
||||||
|
"serde",
|
||||||
|
"unicode-ident",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ciborium"
|
name = "ciborium"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -1536,8 +1549,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set 0.8.0",
|
"bit-set 0.8.0",
|
||||||
"regex-automata",
|
"regex-automata 0.4.14",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3358,7 +3371,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex-automata",
|
"regex-automata 0.4.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3379,11 +3392,10 @@ dependencies = [
|
|||||||
name = "math-parser"
|
name = "math-parser"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chumsky",
|
||||||
"criterion",
|
"criterion",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"num-complex",
|
"num-complex",
|
||||||
"pest",
|
|
||||||
"pest_derive",
|
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4148,50 +4160,6 @@ version = "2.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"ucd-trie",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_derive"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"pest_generator",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_generator"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"pest_meta",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_meta"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"sha2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -4834,8 +4802,19 @@ checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata 0.4.14",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.7.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4846,9 +4825,15 @@ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -6319,7 +6304,7 @@ dependencies = [
|
|||||||
"matchers",
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex-automata",
|
"regex-automata 0.4.14",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
@@ -6398,12 +6383,6 @@ version = "1.18.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ucd-trie"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
|
|||||||
@@ -550,7 +550,6 @@ fn evaluate_and_validate_number_input(expression: &str, number_input: &NumberInp
|
|||||||
let value = math_parser::evaluate(expression)
|
let value = math_parser::evaluate(expression)
|
||||||
.inspect_err(|err| error!("Math parser error on \"{expression}\": {err}"))
|
.inspect_err(|err| error!("Math parser error on \"{expression}\": {err}"))
|
||||||
.ok()?
|
.ok()?
|
||||||
.0
|
|
||||||
.inspect_err(|err| error!("Math evaluate error on \"{expression}\": {err}"))
|
.inspect_err(|err| error!("Math evaluate error on \"{expression}\": {err}"))
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ description = "Parser for Graphite style mathematics expressions"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pest = "2.7"
|
|
||||||
pest_derive = "2.7"
|
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
num-complex = "0.4"
|
num-complex = "0.4"
|
||||||
|
chumsky = { version = "0.10", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true }
|
criterion = { workspace = true }
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ macro_rules! generate_benchmarks {
|
|||||||
$(
|
$(
|
||||||
c.bench_function(concat!("parse ", $input), |b| {
|
c.bench_function(concat!("parse ", $input), |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let _ = black_box(ast::Node::try_parse_from_str($input)).unwrap();
|
let _ = black_box(ast::Node::try_parse_from_str($input));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
)*
|
)*
|
||||||
@@ -17,7 +17,10 @@ macro_rules! generate_benchmarks {
|
|||||||
|
|
||||||
fn evaluation_bench(c: &mut Criterion) {
|
fn evaluation_bench(c: &mut Criterion) {
|
||||||
$(
|
$(
|
||||||
let expr = ast::Node::try_parse_from_str($input).unwrap().0;
|
let expr = match ast::Node::try_parse_from_str($input) {
|
||||||
|
Ok(expr) => expr,
|
||||||
|
Err(err) => panic!("failed to parse `{}`: {err}", $input),
|
||||||
|
};
|
||||||
let context = EvalContext::default();
|
let context = EvalContext::default();
|
||||||
|
|
||||||
c.bench_function(concat!("eval ", $input), |b| {
|
c.bench_function(concat!("eval ", $input), |b| {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl Unit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Literal {
|
pub enum Literal {
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Complex(Complex),
|
Complex(Complex),
|
||||||
@@ -54,8 +54,19 @@ pub enum BinaryOp {
|
|||||||
Add,
|
Add,
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
|
/// Logical AND (nonzero treated as true, returns 1. or 0.)
|
||||||
|
And,
|
||||||
Div,
|
Div,
|
||||||
|
/// Logical OR (nonzero treated as true, returns 1. or 0.)
|
||||||
|
Or,
|
||||||
|
Modulo,
|
||||||
Pow,
|
Pow,
|
||||||
|
Leq,
|
||||||
|
Lt,
|
||||||
|
Geq,
|
||||||
|
Gt,
|
||||||
|
Neq,
|
||||||
|
Eq,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
@@ -63,6 +74,7 @@ pub enum UnaryOp {
|
|||||||
Neg,
|
Neg,
|
||||||
Sqrt,
|
Sqrt,
|
||||||
Fac,
|
Fac,
|
||||||
|
Not,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -72,4 +84,5 @@ pub enum Node {
|
|||||||
FnCall { name: String, expr: Vec<Node> },
|
FnCall { name: String, expr: Vec<Node> },
|
||||||
BinOp { lhs: Box<Node>, op: BinaryOp, rhs: Box<Node> },
|
BinOp { lhs: Box<Node>, op: BinaryOp, rhs: Box<Node> },
|
||||||
UnaryOp { expr: Box<Node>, op: UnaryOp },
|
UnaryOp { expr: Box<Node>, op: UnaryOp },
|
||||||
|
Conditional { condition: Box<Node>, if_block: Box<Node>, else_block: Box<Node> },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,21 @@ use crate::value::{Number, Value};
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use num_complex::{Complex, ComplexFloat};
|
use num_complex::{Complex, ComplexFloat};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::{LN_2, PI};
|
||||||
|
|
||||||
type FunctionImplementation = Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync>;
|
type FunctionImplementation = Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync>;
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {
|
pub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {
|
||||||
let mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();
|
let mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"sqrt",
|
||||||
|
Box::new(|values| match values{
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"sin",
|
"sin",
|
||||||
Box::new(|values| match values {
|
Box::new(|values| match values {
|
||||||
@@ -63,6 +71,7 @@ lazy_static! {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Inverse trig with legacy names and standard aliases
|
||||||
map.insert(
|
map.insert(
|
||||||
"invsin",
|
"invsin",
|
||||||
Box::new(|values| match values {
|
Box::new(|values| match values {
|
||||||
@@ -71,6 +80,14 @@ lazy_static! {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"asin",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
"invcos",
|
"invcos",
|
||||||
@@ -80,6 +97,14 @@ lazy_static! {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"acos",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
"invtan",
|
"invtan",
|
||||||
@@ -89,6 +114,14 @@ lazy_static! {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"atan",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
"invcsc",
|
"invcsc",
|
||||||
@@ -98,6 +131,14 @@ lazy_static! {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"acsc",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
"invsec",
|
"invsec",
|
||||||
@@ -107,12 +148,486 @@ lazy_static! {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"asec",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
"invcot",
|
"invcot",
|
||||||
Box::new(|values| match values {
|
Box::new(|values| match values {
|
||||||
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real((PI / 2. - real).atan()))),
|
[Value::Number(Number::Real(real))] => {
|
||||||
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex((Complex::new(PI / 2., 0.) - complex).atan()))),
|
Some(Value::Number(Number::Real(real.recip().atan())))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Complex(complex))] => {
|
||||||
|
Some(Value::Number(Number::Complex(complex.recip().atan())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"acot",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => {
|
||||||
|
Some(Value::Number(Number::Real(real.recip().atan())))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Complex(complex))] => {
|
||||||
|
Some(Value::Number(Number::Complex(complex.recip().atan())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Hyperbolic Functions
|
||||||
|
map.insert(
|
||||||
|
"sinh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"cosh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"tanh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reciprocal hyperbolic functions
|
||||||
|
map.insert(
|
||||||
|
"csch",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sinh().recip()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sinh().recip()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"sech",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cosh().recip()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cosh().recip()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"coth",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tanh().recip()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tanh().recip()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inverse Hyperbolic Functions
|
||||||
|
map.insert(
|
||||||
|
"asinh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asinh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asinh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"acosh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acosh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acosh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"atanh",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atanh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atanh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inverse reciprocal hyperbolic functions
|
||||||
|
map.insert(
|
||||||
|
"acsch",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asinh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asinh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"asech",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acosh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acosh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"acoth",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().atanh()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().atanh()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Logarithm Functions
|
||||||
|
map.insert(
|
||||||
|
"ln",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ln()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.ln()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exponential / power helpers
|
||||||
|
map.insert(
|
||||||
|
"exp",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.exp()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.exp()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"pow",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||||
|
Some(Value::Number(Number::Real(x.powf(*n))))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => {
|
||||||
|
Some(Value::Number(Number::Complex(x.powf(*n))))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Complex(n))] => {
|
||||||
|
Some(Value::Number(Number::Complex(x.powc(*n))))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"root",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(n))] => {
|
||||||
|
Some(Value::Number(Number::Real(x.powf(1. / *n))))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Complex(x)), Value::Number(Number::Real(n))] => {
|
||||||
|
Some(Value::Number(Number::Complex(x.powf(1. / *n))))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"log",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log10()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.log10()))),
|
||||||
|
[Value::Number(n), Value::Number(base)] => {
|
||||||
|
// Custom base logarithm using change of base formula
|
||||||
|
let compute_log = |x: f64, b: f64| -> f64 { x.ln() / b.ln() };
|
||||||
|
match (n, base) {
|
||||||
|
(Number::Real(x), Number::Real(b)) => Some(Value::Number(Number::Real(compute_log(*x, *b)))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"log2",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.log2()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex / LN_2))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Root Functions
|
||||||
|
map.insert(
|
||||||
|
"sqrt",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"cbrt",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cbrt()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.powf(1./3.)))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Geometry Functions
|
||||||
|
map.insert(
|
||||||
|
"hypot",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||||
|
Some(Value::Number(Number::Real(a.hypot(*b))))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"atan2",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(y)), Value::Number(Number::Real(x))] => {
|
||||||
|
Some(Value::Number(Number::Real(y.atan2(*x))))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mapping Functions
|
||||||
|
map.insert(
|
||||||
|
"abs",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.abs()))),
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.abs()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"floor",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.floor()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"ceil",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.ceil()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"round",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.round()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"clamp",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(x)), Value::Number(Number::Real(min)), Value::Number(Number::Real(max))] => {
|
||||||
|
Some(Value::Number(Number::Real(x.clamp(*min, *max))))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"lerp",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b)), Value::Number(Number::Real(t))] => {
|
||||||
|
Some(Value::Number(Number::Real(a + (b - a) * t)))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"remap",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[
|
||||||
|
Value::Number(Number::Real(value)),
|
||||||
|
Value::Number(Number::Real(in_a)),
|
||||||
|
Value::Number(Number::Real(in_b)),
|
||||||
|
Value::Number(Number::Real(out_a)),
|
||||||
|
Value::Number(Number::Real(out_b)),
|
||||||
|
] => {
|
||||||
|
let t = (*value - *in_a) / (*in_b - *in_a);
|
||||||
|
Some(Value::Number(Number::Real(out_a + t * (out_b - out_a))))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"trunc",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.trunc()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"fract",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.fract()))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"sign",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => {
|
||||||
|
let s = if *real > 0. {
|
||||||
|
1.
|
||||||
|
} else if *real < 0. {
|
||||||
|
-1.
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
};
|
||||||
|
Some(Value::Number(Number::Real(s)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"gcd",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||||
|
let mut x = a.trunc() as i64;
|
||||||
|
let mut y = b.trunc() as i64;
|
||||||
|
if x == 0 && y == 0 {
|
||||||
|
return Some(Value::Number(Number::Real(0.)));
|
||||||
|
}
|
||||||
|
x = x.abs();
|
||||||
|
y = y.abs();
|
||||||
|
while y != 0 {
|
||||||
|
let r = x % y;
|
||||||
|
x = y;
|
||||||
|
y = r;
|
||||||
|
}
|
||||||
|
Some(Value::Number(Number::Real(x as f64)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"lcm",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||||
|
let mut x = a.trunc() as i64;
|
||||||
|
let mut y = b.trunc() as i64;
|
||||||
|
x = x.abs();
|
||||||
|
y = y.abs();
|
||||||
|
if x == 0 || y == 0 {
|
||||||
|
return Some(Value::Number(Number::Real(0.)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcd
|
||||||
|
let mut gx = x;
|
||||||
|
let mut gy = y;
|
||||||
|
while gy != 0 {
|
||||||
|
let r = gx % gy;
|
||||||
|
gx = gy;
|
||||||
|
gy = r;
|
||||||
|
}
|
||||||
|
let lcm = (x / gx) * y;
|
||||||
|
Some(Value::Number(Number::Real(lcm as f64)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Complex Number Functions
|
||||||
|
map.insert(
|
||||||
|
"real",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.re))),
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"imag",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Real(complex.im))),
|
||||||
|
[Value::Number(Number::Real(_))] => Some(Value::Number(Number::Real(0.))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"conj",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Complex(complex))] => {
|
||||||
|
Some(Value::Number(Number::Complex(complex.conj())))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(*real))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"arg",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Complex(complex))] => {
|
||||||
|
Some(Value::Number(Number::Real(complex.arg())))
|
||||||
|
}
|
||||||
|
[Value::Number(Number::Real(real))] => {
|
||||||
|
let angle = if *real >= 0. { 0. } else { PI };
|
||||||
|
Some(Value::Number(Number::Real(angle)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Logical Functions
|
||||||
|
map.insert(
|
||||||
|
"isnan",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(if real.is_nan() { 1. } else { 0. }))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"eq",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(a), Value::Number(b)] => Some(Value::Number(Number::Real(if a == b { 1. } else { 0. }))),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"greater",
|
||||||
|
Box::new(|values| match values {
|
||||||
|
[Value::Number(Number::Real(a)), Value::Number(Number::Real(b))] => {
|
||||||
|
Some(Value::Number(Number::Real(if a > b { 1. } else { 0. })))
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::ast::{Literal, Node};
|
|||||||
use crate::constants::DEFAULT_FUNCTIONS;
|
use crate::constants::DEFAULT_FUNCTIONS;
|
||||||
use crate::context::{EvalContext, FunctionProvider, ValueProvider};
|
use crate::context::{EvalContext, FunctionProvider, ValueProvider};
|
||||||
use crate::value::{Number, Value};
|
use crate::value::{Number, Value};
|
||||||
|
use num_complex::Complex;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -24,7 +25,7 @@ impl Node {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Node::BinOp { lhs, op, rhs } => match (lhs.eval(context)?, rhs.eval(context)?) {
|
Node::BinOp { lhs, op, rhs } => match (lhs.eval(context)?, rhs.eval(context)?) {
|
||||||
(Value::Number(lhs), Value::Number(rhs)) => Ok(Value::Number(lhs.binary_op(*op, rhs))),
|
(Value::Number(lhs), Value::Number(rhs)) => Ok(Value::Number(lhs.binary_op(*op, rhs).ok_or(EvalError::TypeError)?)),
|
||||||
},
|
},
|
||||||
Node::UnaryOp { expr, op } => match expr.eval(context)? {
|
Node::UnaryOp { expr, op } => match expr.eval(context)? {
|
||||||
Value::Number(num) => Ok(Value::Number(num.unary_op(*op))),
|
Value::Number(num) => Ok(Value::Number(num.unary_op(*op))),
|
||||||
@@ -32,6 +33,7 @@ impl Node {
|
|||||||
Node::Var(name) => context.get_value(name).ok_or_else(|| EvalError::MissingValue(name.clone())),
|
Node::Var(name) => context.get_value(name).ok_or_else(|| EvalError::MissingValue(name.clone())),
|
||||||
Node::FnCall { name, expr } => {
|
Node::FnCall { name, expr } => {
|
||||||
let values = expr.iter().map(|expr| expr.eval(context)).collect::<Result<Vec<Value>, EvalError>>()?;
|
let values = expr.iter().map(|expr| expr.eval(context)).collect::<Result<Vec<Value>, EvalError>>()?;
|
||||||
|
|
||||||
if let Some(function) = DEFAULT_FUNCTIONS.get(&name.as_str()) {
|
if let Some(function) = DEFAULT_FUNCTIONS.get(&name.as_str()) {
|
||||||
function(&values).ok_or(EvalError::TypeError)
|
function(&values).ok_or(EvalError::TypeError)
|
||||||
} else if let Some(val) = context.run_function(name, &values) {
|
} else if let Some(val) = context.run_function(name, &values) {
|
||||||
@@ -40,6 +42,14 @@ impl Node {
|
|||||||
context.get_value(name).ok_or_else(|| EvalError::MissingFunction(name.to_string()))
|
context.get_value(name).ok_or_else(|| EvalError::MissingFunction(name.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Node::Conditional { condition, if_block, else_block } => {
|
||||||
|
let condition = match condition.eval(context)? {
|
||||||
|
Value::Number(Number::Real(number)) => number != 0.,
|
||||||
|
Value::Number(Number::Complex(number)) => number != Complex::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
if condition { if_block.eval(context) } else { else_block.eval(context) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
WHITESPACE = _{ " " | "\t" }
|
|
||||||
|
|
||||||
// TODO: Proper indentation and formatting
|
|
||||||
program = _{ SOI ~ expr ~ EOI }
|
|
||||||
|
|
||||||
expr = { atom ~ (infix ~ atom)* }
|
|
||||||
atom = _{ prefix? ~ primary ~ postfix? }
|
|
||||||
infix = _{ add | sub | mul | div | pow | paren }
|
|
||||||
add = { "+" } // Addition
|
|
||||||
sub = { "-" } // Subtraction
|
|
||||||
mul = { "*" } // Multiplication
|
|
||||||
div = { "/" } // Division
|
|
||||||
mod = { "%" } // Modulo
|
|
||||||
pow = { "^" } // Exponentiation
|
|
||||||
paren = { "" } // Implicit multiplication operator
|
|
||||||
|
|
||||||
prefix = _{ neg | sqrt }
|
|
||||||
neg = { "-" } // Negation
|
|
||||||
sqrt = { "sqrt" }
|
|
||||||
|
|
||||||
postfix = _{ fac }
|
|
||||||
fac = { "!" } // Factorial
|
|
||||||
|
|
||||||
primary = _{ ("(" ~ expr ~ ")") | lit | constant | fn_call | ident }
|
|
||||||
fn_call = { ident ~ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
|
|
||||||
ident = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
|
|
||||||
lit = { unit | ((float | int) ~ unit?) }
|
|
||||||
|
|
||||||
float = @{ (int ~ "." ~ int? ~ exp? | "." ~ int ~ exp? | int ~ exp) ~ !("." | ASCII_DIGIT) }
|
|
||||||
exp = _{ ^"e" ~ ("+" | "-")? ~ int }
|
|
||||||
int = @{ ASCII_DIGIT+ }
|
|
||||||
|
|
||||||
unit = ${ (scale ~ base_unit) | base_unit ~ !ident}
|
|
||||||
base_unit = _{ meter | second | gram }
|
|
||||||
meter = { "m" }
|
|
||||||
second = { "s" }
|
|
||||||
gram = { "g" }
|
|
||||||
|
|
||||||
scale = _{ nano | micro | milli | centi | deci | deca | hecto | kilo | mega | giga | tera }
|
|
||||||
nano = { "n" }
|
|
||||||
micro = { "µ" | "u" }
|
|
||||||
milli = { "m" }
|
|
||||||
centi = { "c" }
|
|
||||||
deci = { "d" }
|
|
||||||
deca = { "da" }
|
|
||||||
hecto = { "h" }
|
|
||||||
kilo = { "k" }
|
|
||||||
mega = { "M" }
|
|
||||||
giga = { "G" }
|
|
||||||
tera = { "T" }
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
constant = { infinity | imaginary_unit | pi | tau | euler_number | golden_ratio | gravity_acceleration }
|
|
||||||
infinity = { "inf" | "INF" | "infinity" | "INFINITY" | "∞" }
|
|
||||||
imaginary_unit = { "i" | "I" }
|
|
||||||
pi = { "pi" | "PI" | "π" }
|
|
||||||
tau = { "tau" | "TAU" | "τ" }
|
|
||||||
euler_number = { "e" }
|
|
||||||
golden_ratio = { "phi" | "PHI" | "φ" }
|
|
||||||
gravity_acceleration = { "G" }
|
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
use crate::ast::Literal;
|
||||||
|
use chumsky::input::{Input, ValueInput};
|
||||||
|
use chumsky::prelude::*;
|
||||||
|
use chumsky::span::SimpleSpan;
|
||||||
|
use chumsky::text::{ident, int};
|
||||||
|
use core::f64;
|
||||||
|
use num_complex::Complex64;
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter::Peekable;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::str::Chars;
|
||||||
|
|
||||||
|
pub type Span = SimpleSpan;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Token<'src> {
|
||||||
|
Float(f64),
|
||||||
|
Const(Constant),
|
||||||
|
Ident(&'src str),
|
||||||
|
|
||||||
|
AndAnd,
|
||||||
|
OrOr,
|
||||||
|
Bang,
|
||||||
|
|
||||||
|
LParen,
|
||||||
|
RParen,
|
||||||
|
Comma,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Modulo,
|
||||||
|
Star,
|
||||||
|
Slash,
|
||||||
|
Caret,
|
||||||
|
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Ge,
|
||||||
|
Neq,
|
||||||
|
EqEq,
|
||||||
|
|
||||||
|
If,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> fmt::Display for Token<'src> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::Float(x) => write!(f, "{x}"),
|
||||||
|
Token::Const(c) => write!(f, "{c}"),
|
||||||
|
Token::Ident(name) => write!(f, "{name}"),
|
||||||
|
|
||||||
|
Token::AndAnd => f.write_str("&&"),
|
||||||
|
Token::OrOr => f.write_str("||"),
|
||||||
|
Token::Bang => f.write_str("!"),
|
||||||
|
|
||||||
|
Token::LParen => f.write_str("("),
|
||||||
|
Token::RParen => f.write_str(")"),
|
||||||
|
Token::Comma => f.write_str(","),
|
||||||
|
Token::Plus => f.write_str("+"),
|
||||||
|
Token::Minus => f.write_str("-"),
|
||||||
|
Token::Modulo => f.write_str("%"),
|
||||||
|
Token::Star => f.write_str("*"),
|
||||||
|
Token::Slash => f.write_str("/"),
|
||||||
|
Token::Caret => f.write_str("^"),
|
||||||
|
|
||||||
|
Token::Lt => f.write_str("<"),
|
||||||
|
Token::Le => f.write_str("<="),
|
||||||
|
Token::Gt => f.write_str(">"),
|
||||||
|
Token::Ge => f.write_str(">="),
|
||||||
|
Token::Neq => f.write_str("!="),
|
||||||
|
Token::EqEq => f.write_str("=="),
|
||||||
|
|
||||||
|
Token::If => f.write_str("if"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Constant {
|
||||||
|
Pi,
|
||||||
|
Tau,
|
||||||
|
E,
|
||||||
|
Phi,
|
||||||
|
Inf,
|
||||||
|
I,
|
||||||
|
G,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Constant {
|
||||||
|
pub fn value(self) -> Literal {
|
||||||
|
use Constant::*;
|
||||||
|
use std::f64::consts;
|
||||||
|
match self {
|
||||||
|
Pi => Literal::Float(consts::PI),
|
||||||
|
Tau => Literal::Float(consts::TAU),
|
||||||
|
E => Literal::Float(consts::E),
|
||||||
|
Phi => Literal::Float(1.618_033_988_75),
|
||||||
|
Inf => Literal::Float(f64::INFINITY),
|
||||||
|
I => Literal::Complex(Complex64::new(0., 1.)),
|
||||||
|
G => Literal::Float(9.80665),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_name(name: &str) -> Option<Constant> {
|
||||||
|
use Constant::*;
|
||||||
|
Some(match name {
|
||||||
|
"pi" | "π" => Pi,
|
||||||
|
"tau" | "τ" => Tau,
|
||||||
|
"e" => E,
|
||||||
|
"phi" | "φ" => Phi,
|
||||||
|
"inf" | "∞" => Inf,
|
||||||
|
"i" => I,
|
||||||
|
"G" => G,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Constant {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use Constant::*;
|
||||||
|
f.write_str(match self {
|
||||||
|
Pi => "pi",
|
||||||
|
Tau => "tau",
|
||||||
|
E => "e",
|
||||||
|
Phi => "phi",
|
||||||
|
Inf => "inf",
|
||||||
|
I => "i",
|
||||||
|
G => "G",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lexer<'a> {
|
||||||
|
input: &'a str,
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
pub fn new(input: &'a str) -> Self {
|
||||||
|
Self { input, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(&self) -> Option<char> {
|
||||||
|
self.input[self.pos..].chars().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bump(&mut self) -> Option<char> {
|
||||||
|
let c = self.peek()?;
|
||||||
|
self.pos += c.len_utf8();
|
||||||
|
Some(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_while<F>(&mut self, cond: F) -> &'a str
|
||||||
|
where
|
||||||
|
F: Fn(char) -> bool,
|
||||||
|
{
|
||||||
|
let start = self.pos;
|
||||||
|
while self.peek().is_some_and(&cond) {
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
&self.input[start..self.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex_ident(&mut self) -> &'a str {
|
||||||
|
self.consume_while(|c| c.is_alphanumeric() || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex_uint(&mut self) -> Option<(u64, usize)> {
|
||||||
|
let mut v = 0u64;
|
||||||
|
let mut digits = 0;
|
||||||
|
while let Some(d) = self.peek().and_then(|c| c.to_digit(10)) {
|
||||||
|
v = v * 10 + d as u64;
|
||||||
|
digits += 1;
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
(digits > 0).then_some((v, digits))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex_number(&mut self) -> Option<f64> {
|
||||||
|
let start_pos = self.pos;
|
||||||
|
let (int_val, int_digits) = self.lex_uint().unwrap_or((0, 0));
|
||||||
|
let mut got_digit = int_digits > 0;
|
||||||
|
let mut num = int_val as f64;
|
||||||
|
|
||||||
|
if self.peek() == Some('.') {
|
||||||
|
self.bump();
|
||||||
|
if let Some((frac_val, frac_digits)) = self.lex_uint() {
|
||||||
|
num += (frac_val as f64) / 10f64.powi(frac_digits as i32);
|
||||||
|
got_digit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(self.peek(), Some('e' | 'E')) {
|
||||||
|
self.bump();
|
||||||
|
let sign = match self.peek() {
|
||||||
|
Some('+') => {
|
||||||
|
self.bump();
|
||||||
|
1
|
||||||
|
}
|
||||||
|
Some('-') => {
|
||||||
|
self.bump();
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
if let Some((exp_val, _)) = self.lex_uint() {
|
||||||
|
num *= 10f64.powi(sign * exp_val as i32);
|
||||||
|
} else {
|
||||||
|
self.pos = start_pos;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got_digit.then_some(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_ws(&mut self) {
|
||||||
|
self.consume_while(char::is_whitespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_token(&mut self) -> Option<Token<'a>> {
|
||||||
|
self.skip_ws();
|
||||||
|
let start = self.pos;
|
||||||
|
let ch = self.bump()?;
|
||||||
|
|
||||||
|
use Token::*;
|
||||||
|
let tok = match ch {
|
||||||
|
'&' => {
|
||||||
|
if self.peek() == Some('&') {
|
||||||
|
self.bump();
|
||||||
|
AndAnd
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'|' => {
|
||||||
|
if self.peek() == Some('|') {
|
||||||
|
self.bump();
|
||||||
|
OrOr
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'(' => LParen,
|
||||||
|
')' => RParen,
|
||||||
|
',' => Comma,
|
||||||
|
'+' => Plus,
|
||||||
|
'-' => Minus,
|
||||||
|
'*' => Star,
|
||||||
|
'%' => Modulo,
|
||||||
|
'/' => Slash,
|
||||||
|
'^' => Caret,
|
||||||
|
'≠' => Neq,
|
||||||
|
|
||||||
|
'!' => {
|
||||||
|
if self.peek() == Some('=') {
|
||||||
|
self.bump();
|
||||||
|
Neq
|
||||||
|
} else {
|
||||||
|
Bang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'≤' => Le,
|
||||||
|
'<' => {
|
||||||
|
if self.peek() == Some('=') {
|
||||||
|
self.bump();
|
||||||
|
Le
|
||||||
|
} else {
|
||||||
|
Lt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'≥' => Ge,
|
||||||
|
'>' => {
|
||||||
|
if self.peek() == Some('=') {
|
||||||
|
self.bump();
|
||||||
|
Ge
|
||||||
|
} else {
|
||||||
|
Gt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'=' => {
|
||||||
|
if self.peek() == Some('=') {
|
||||||
|
self.bump();
|
||||||
|
EqEq
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c if c.is_ascii_digit() || (c == '.' && self.peek().is_some_and(|c| c.is_ascii_digit())) => {
|
||||||
|
self.pos = start;
|
||||||
|
Float(self.lex_number()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
self.consume_while(|c| c.is_alphanumeric() || c == '_');
|
||||||
|
let ident = &self.input[start..self.pos];
|
||||||
|
|
||||||
|
if ident == "if" {
|
||||||
|
If
|
||||||
|
} else if let Some(lit) = Constant::from_name(ident) {
|
||||||
|
Const(lit)
|
||||||
|
} else if ch.is_alphanumeric() {
|
||||||
|
Ident(ident)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(tok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Lexer<'a> {
|
||||||
|
type Item = Token<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.next_token()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> Input<'src> for Lexer<'src> {
|
||||||
|
type Token = Token<'src>;
|
||||||
|
type Span = Span;
|
||||||
|
type Cursor = usize; // byte offset inside `input`
|
||||||
|
type MaybeToken = Token<'src>;
|
||||||
|
type Cache = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn begin(self) -> (Self::Cursor, Self::Cache) {
|
||||||
|
(0, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cursor_location(cursor: &Self::Cursor) -> usize {
|
||||||
|
*cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn next_maybe(this: &mut Self::Cache, cursor: &mut Self::Cursor) -> Option<Self::MaybeToken> {
|
||||||
|
this.pos = *cursor;
|
||||||
|
if let Some(tok) = this.next_token() {
|
||||||
|
*cursor = this.pos;
|
||||||
|
Some(tok)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn span(_this: &mut Self::Cache, range: Range<&Self::Cursor>) -> Self::Span {
|
||||||
|
(*range.start..*range.end).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> ValueInput<'src> for Lexer<'src> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn next(this: &mut Self::Cache, cursor: &mut Self::Cursor) -> Option<Self::Token> {
|
||||||
|
this.pos = *cursor;
|
||||||
|
if let Some(tok) = this.next_token() {
|
||||||
|
*cursor = this.pos;
|
||||||
|
Some(tok)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+221
-104
@@ -4,6 +4,7 @@ pub mod ast;
|
|||||||
mod constants;
|
mod constants;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod executer;
|
pub mod executer;
|
||||||
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
@@ -13,10 +14,10 @@ use executer::EvalError;
|
|||||||
use parser::ParseError;
|
use parser::ParseError;
|
||||||
use value::Value;
|
use value::Value;
|
||||||
|
|
||||||
pub fn evaluate(expression: &str) -> Result<(Result<Value, EvalError>, Unit), ParseError> {
|
pub fn evaluate(expression: &str) -> Result<Result<Value, EvalError>, ParseError> {
|
||||||
let expr = ast::Node::try_parse_from_str(expression);
|
let expr = ast::Node::try_parse_from_str(expression);
|
||||||
let context = EvalContext::default();
|
let context = EvalContext::default();
|
||||||
expr.map(|(node, unit)| (node.eval(&context), unit))
|
expr.map(|node| node.eval(&context))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -27,136 +28,252 @@ mod tests {
|
|||||||
|
|
||||||
const EPSILON: f64 = 1e-10_f64;
|
const EPSILON: f64 = 1e-10_f64;
|
||||||
|
|
||||||
#[test]
|
fn run_end_to_end_test(input: &str, expected_value: Value) {
|
||||||
fn malformed_juxtaposed_numbers_fail_to_parse() {
|
let expr = match ast::Node::try_parse_from_str(input) {
|
||||||
// Two numbers cannot be glued together by a stray decimal point (they must not parse as implicit multiplication).
|
Ok(expr) => expr,
|
||||||
for input in ["1..5", "1.5.5", "1..", ".5.5"] {
|
Err(err) => panic!("failed to parse `{input}`: {err}"),
|
||||||
assert!(evaluate(input).is_err(), "expected `{input}` to be a parse error");
|
};
|
||||||
|
dbg!(&expr);
|
||||||
|
let context = EvalContext::default();
|
||||||
|
|
||||||
|
let actual_value = match expr.eval(&context) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => panic!("failed to evaluate {input} becuase of error {err}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// compare
|
||||||
|
match (actual_value, expected_value) {
|
||||||
|
(Value::Number(Number::Complex(a)), Value::Number(Number::Complex(e))) => {
|
||||||
|
// real part
|
||||||
|
if a.re.is_infinite() || e.re.is_infinite() {
|
||||||
|
assert!(a.re == e.re, "`{}` → real part: expected {:?}, got {:?}", input, e.re, a.re);
|
||||||
|
} else {
|
||||||
|
assert!((a.re - e.re).abs() < EPSILON, "`{}` → real part: expected {}, got {}", input, e.re, a.re);
|
||||||
|
}
|
||||||
|
|
||||||
|
// imag part
|
||||||
|
if a.im.is_infinite() || e.im.is_infinite() {
|
||||||
|
assert!(a.im == e.im, "`{}` → imag part: expected {:?}, got {:?}", input, e.im, a.im);
|
||||||
|
} else {
|
||||||
|
assert!((a.im - e.im).abs() < EPSILON, "`{}` → imag part: expected {}, got {}", input, e.im, a.im);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Value::Number(Number::Real(a)), Value::Number(Number::Real(e))) => {
|
||||||
|
if a.is_infinite() || e.is_infinite() {
|
||||||
|
// both must be infinite and equal (i.e. both +∞ or both −∞)
|
||||||
|
assert!(a == e, "`{input}` → expected infinite {e:?}, got {a:?}");
|
||||||
|
} else if a.is_nan() || e.is_nan() {
|
||||||
|
// both must be NaN
|
||||||
|
assert!(a.is_nan() && e.is_nan(), "`{input}` → expected NaN, got {a:?}");
|
||||||
|
} else {
|
||||||
|
let diff = (a - e).abs();
|
||||||
|
assert!(diff < EPSILON, "`{input}` → expected {e}, got {a}, Δ={diff}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(got, expect) => {
|
||||||
|
panic!("`{input}` → mismatched types: expected {expect:?}, got {got:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! test_end_to_end{
|
macro_rules! test_end_to_end {
|
||||||
($($name:ident: $input:expr_2021 => ($expected_value:expr_2021, $expected_unit:expr_2021)),* $(,)?) => {
|
($($name:ident: $input:expr => $expected:expr),* $(,)?) => {
|
||||||
$(
|
$(
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let expected_value = $expected_value;
|
run_end_to_end_test($input, ($expected).into());
|
||||||
let expected_unit = $expected_unit;
|
|
||||||
|
|
||||||
let expr = ast::Node::try_parse_from_str($input);
|
|
||||||
let context = EvalContext::default();
|
|
||||||
|
|
||||||
let (actual_value, actual_unit) = expr.map(|(node, unit)| (node.eval(&context), unit)).unwrap();
|
|
||||||
let actual_value = actual_value.unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
assert!(actual_unit == expected_unit, "Expected unit {:?} but found unit {:?}", expected_unit, actual_unit);
|
|
||||||
|
|
||||||
let expected_value = expected_value.into();
|
|
||||||
|
|
||||||
match (actual_value, expected_value) {
|
|
||||||
(Value::Number(Number::Complex(actual_c)), Value::Number(Number::Complex(expected_c))) => {
|
|
||||||
assert!(
|
|
||||||
(actual_c.re.is_infinite() && expected_c.re.is_infinite()) || (actual_c.re - expected_c.re).abs() < EPSILON,
|
|
||||||
"Expected real part {}, but got {}",
|
|
||||||
expected_c.re,
|
|
||||||
actual_c.re
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
(actual_c.im.is_infinite() && expected_c.im.is_infinite()) || (actual_c.im - expected_c.im).abs() < EPSILON,
|
|
||||||
"Expected imaginary part {}, but got {}",
|
|
||||||
expected_c.im,
|
|
||||||
actual_c.im
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(Value::Number(Number::Real(actual_f)), Value::Number(Number::Real(expected_f))) => {
|
|
||||||
if actual_f.is_infinite() || expected_f.is_infinite() {
|
|
||||||
assert!(
|
|
||||||
actual_f.is_infinite() && expected_f.is_infinite() && actual_f == expected_f,
|
|
||||||
"Expected infinite value {}, but got {}",
|
|
||||||
expected_f,
|
|
||||||
actual_f
|
|
||||||
);
|
|
||||||
} else if actual_f.is_nan() || expected_f.is_nan() {
|
|
||||||
assert!(actual_f.is_nan() && expected_f.is_nan(), "Expected NaN, but got {}", actual_f);
|
|
||||||
} else {
|
|
||||||
assert!((actual_f - expected_f).abs() < EPSILON, "Expected {}, but got {}", expected_f, actual_f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Handle mismatched types
|
|
||||||
_ => panic!("Mismatched types: expected {:?}, got {:?}", expected_value, actual_value),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test_end_to_end! {
|
test_end_to_end! {
|
||||||
// Basic arithmetic and units
|
// Basic arithmetic
|
||||||
infix_addition: "5 + 5" => (10., Unit::BASE_UNIT),
|
infix_addition: "5 + 5" => 10.,
|
||||||
infix_subtraction_units: "5m - 3m" => (2., Unit::LENGTH),
|
infix_subtraction: "5 - 3" => 2.,
|
||||||
infix_multiplication_units: "4s * 4s" => (16., Unit { length: 0, mass: 0, time: 2 }),
|
infix_multiplication: "4 * 4" => 16.,
|
||||||
infix_division_units: "8m/2s" => (4., Unit::VELOCITY),
|
infix_division: "8/2" => 4.,
|
||||||
|
modulo_pos_pos: "3.2 % 2" => 1.2,
|
||||||
|
modulo_pos_neg: "3.2 % -2" => 1.2,
|
||||||
|
modulo_neg_neg: "(-3.2) % -2" => -1.2,
|
||||||
|
modulo_neg_pos: "(-3.2) % 2" => -1.2,
|
||||||
|
exp_pos_pos: "3.2 ^ 2" => 256. / 25.,
|
||||||
|
exp_pos_neg: "3.2 ^ -2" => 25. / 256.,
|
||||||
|
exp_neg_neg: "-3.2 ^ -2" => -25. / 256.,
|
||||||
|
exp_neg_pos: "-3.2 ^ 2" => -256. / 25.,
|
||||||
|
|
||||||
// Order of operations
|
// Order of operations
|
||||||
order_of_operations_negative_prefix: "-10 + 5" => (-5., Unit::BASE_UNIT),
|
order_of_operations_negative_prefix: "-10 + 5" => -5.,
|
||||||
order_of_operations_add_multiply: "5+1*1+5" => (11., Unit::BASE_UNIT),
|
order_of_operations_add_multiply: "5+1*1+5" => 11.,
|
||||||
order_of_operations_add_negative_multiply: "5+(-1)*1+5" => (9., Unit::BASE_UNIT),
|
order_of_operations_add_negative_multiply: "5+(-1)*1+5" => 9.,
|
||||||
order_of_operations_sqrt: "sqrt25 + 11" => (16., Unit::BASE_UNIT),
|
order_of_operations_sqrt: "sqrt(25) + 11" => 16.,
|
||||||
order_of_operations_sqrt_expression: "sqrt(25+11)" => (6., Unit::BASE_UNIT),
|
order_of_operations_sqrt_expression: "sqrt(25+11)" => 6.,
|
||||||
|
|
||||||
// Parentheses and nested expressions
|
// Parentheses and nested expressions
|
||||||
parentheses_nested_multiply: "(5 + 3) * (2 + 6)" => (64., Unit::BASE_UNIT),
|
parentheses_nested_multiply: "(5 + 3) * (2 + 6)" => 64.,
|
||||||
parentheses_mixed_operations: "2 * (3 + 5 * (2 + 1))" => (36., Unit::BASE_UNIT),
|
parentheses_mixed_operations: "2 * (3 + 5 * (2 + 1))" => 36.,
|
||||||
parentheses_divide_add_multiply: "10 / (2 + 3) + (7 * 2)" => (16., Unit::BASE_UNIT),
|
parentheses_divide_add_multiply: "10 / (2 + 3) + (7 * 2)" => 16.,
|
||||||
|
|
||||||
// Square root and nested square root
|
// Square root and nested square root
|
||||||
sqrt_chain_operations: "sqrt(16) + sqrt(9) * sqrt(4)" => (10., Unit::BASE_UNIT),
|
sqrt_chain_operations: "sqrt(16) + sqrt(9) * sqrt(4)" => 10.,
|
||||||
sqrt_nested: "sqrt(sqrt(81))" => (3., Unit::BASE_UNIT),
|
sqrt_nested: "sqrt(sqrt(81))" => 3.,
|
||||||
sqrt_divide_expression: "sqrt((25 + 11) / 9)" => (2., Unit::BASE_UNIT),
|
sqrt_divide_expression: "sqrt((25 + 11) / 9)" => 2.,
|
||||||
|
|
||||||
// Mixed square root and units
|
// Mixed square root and units
|
||||||
sqrt_multiply_units: "sqrt(16) * 2g + 5g" => (13., Unit::MASS),
|
sqrt_add_multiply: "sqrt(49) - 1 + 2 * 3" => 12.,
|
||||||
sqrt_add_multiply: "sqrt(49) - 1 + 2 * 3" => (12., Unit::BASE_UNIT),
|
sqrt_addition_multiply: "(sqrt(36) + 2) * 2" => 16.,
|
||||||
sqrt_addition_multiply: "(sqrt(36) + 2) * 2" => (16., Unit::BASE_UNIT),
|
|
||||||
|
|
||||||
// Exponentiation
|
// Exponentiation
|
||||||
exponent_single: "2^3" => (8., Unit::BASE_UNIT),
|
exponent_single: "2^3" => 8.,
|
||||||
exponent_mixed_operations: "2^3 + 4^2" => (24., Unit::BASE_UNIT),
|
exponent_mixed_operations: "2^3 + 4^2" => 24.,
|
||||||
exponent_nested: "2^(3+1)" => (16., Unit::BASE_UNIT),
|
exponent_nested: "2^(3+1)" => 16.,
|
||||||
|
|
||||||
|
// Factorial (postfix !)
|
||||||
|
factorial_simple: "5!" => 120.,
|
||||||
|
factorial_nested: "(3 + 2)!" => 120.,
|
||||||
|
factorial_zero: "0!" => 1.,
|
||||||
|
factorial_chain: "3!!" => 720., // (3!)! = 6! = 720
|
||||||
|
|
||||||
// Operations with negative values
|
// Operations with negative values
|
||||||
negative_units_add_multiply: "-5s + (-3 * 2)s" => (-11., Unit::TIME),
|
negative_nested_parentheses: "-(5 + 3 * (2 - 1))" => -8.,
|
||||||
negative_nested_parentheses: "-(5 + 3 * (2 - 1))" => (-8., Unit::BASE_UNIT),
|
negative_sqrt_addition: "-(sqrt(16) + sqrt(9))" => -7.,
|
||||||
negative_sqrt_addition: "-(sqrt(16) + sqrt(9))" => (-7., Unit::BASE_UNIT),
|
multiply_sqrt_subtract: "5 * 2 + sqrt(16) / 2 - 3" => 9.,
|
||||||
multiply_sqrt_subtract: "5 * 2 + sqrt(16) / 2 - 3" => (9., Unit::BASE_UNIT),
|
add_multiply_subtract_sqrt: "4 + 3 * (2 + 1) - sqrt(25)" => 8.,
|
||||||
add_multiply_subtract_sqrt: "4 + 3 * (2 + 1) - sqrt(25)" => (8., Unit::BASE_UNIT),
|
add_sqrt_subtract_nested_multiply: "10 + sqrt(64) - (5 * (2 + 1))" => 3.,
|
||||||
add_sqrt_subtract_nested_multiply: "10 + sqrt(64) - (5 * (2 + 1))" => (3., Unit::BASE_UNIT),
|
|
||||||
|
|
||||||
// Mathematical constants
|
// Mathematical constants
|
||||||
constant_pi: "pi" => (std::f64::consts::PI, Unit::BASE_UNIT),
|
constant_pi: "pi" => std::f64::consts::PI,
|
||||||
constant_e: "e" => (std::f64::consts::E, Unit::BASE_UNIT),
|
constant_e: "e" => std::f64::consts::E,
|
||||||
constant_phi: "phi" => (1.61803398875, Unit::BASE_UNIT),
|
constant_phi: "phi" => 1.61803398875,
|
||||||
constant_tau: "tau" => (2. * std::f64::consts::PI, Unit::BASE_UNIT),
|
constant_tau: "tau" => 2. * std::f64::consts::PI,
|
||||||
constant_infinity: "inf" => (f64::INFINITY, Unit::BASE_UNIT),
|
constant_infinity: "if(inf == ∞, inf, 0)" => f64::INFINITY,
|
||||||
constant_infinity_symbol: "∞" => (f64::INFINITY, Unit::BASE_UNIT),
|
multiply_pi: "2 * pi" => 2. * std::f64::consts::PI,
|
||||||
multiply_pi: "2 * pi" => (2. * std::f64::consts::PI, Unit::BASE_UNIT),
|
add_e_constant: "e + 1" => std::f64::consts::E + 1.,
|
||||||
add_e_constant: "e + 1" => (std::f64::consts::E + 1., Unit::BASE_UNIT),
|
multiply_phi_constant: "phi * 2" => 1.61803398875 * 2.,
|
||||||
multiply_phi_constant: "phi * 2" => (1.61803398875 * 2., Unit::BASE_UNIT),
|
exponent_tau: "2^tau" => 2f64.powf(2. * std::f64::consts::PI),
|
||||||
exponent_tau: "2^tau" => (2f64.powf(2. * std::f64::consts::PI), Unit::BASE_UNIT),
|
infinity_subtract_large_number: "inf - 1000" => f64::INFINITY,
|
||||||
infinity_subtract_large_number: "inf - 1000" => (f64::INFINITY, Unit::BASE_UNIT),
|
|
||||||
|
|
||||||
// Decimals with no leading digit before the point
|
// Decimals with no leading digit before the point
|
||||||
leading_dot_decimal: ".5" => (0.5, Unit::BASE_UNIT),
|
leading_dot_decimal: ".5" => 0.5,
|
||||||
leading_dot_in_expression: "1+.5" => (1.5, Unit::BASE_UNIT),
|
leading_dot_in_expression: "1+.5" => 1.5,
|
||||||
leading_dot_exponent: ".5e3" => (500., Unit::BASE_UNIT),
|
leading_dot_exponent: ".5e3" => 500.,
|
||||||
|
|
||||||
// Trigonometric functions
|
// Trigonometric functions
|
||||||
trig_sin_pi: "sin(pi)" => (0., Unit::BASE_UNIT),
|
trig_sin_pi: "sin(pi)" => 0.,
|
||||||
trig_cos_zero: "cos(0)" => (1., Unit::BASE_UNIT),
|
trig_cos_zero: "cos(0)" => 1.,
|
||||||
trig_tan_pi_div_four: "tan(pi/4)" => (1., Unit::BASE_UNIT),
|
trig_tan_pi_div_four: "tan(pi/4)" => 1.,
|
||||||
trig_sin_tau: "sin(tau)" => (0., Unit::BASE_UNIT),
|
trig_sin_tau: "sin(tau)" => 0.,
|
||||||
trig_cos_tau_div_two: "cos(tau/2)" => (-1., Unit::BASE_UNIT),
|
trig_cos_tau_div_two: "cos(tau/2)" => -1.,
|
||||||
|
trig_csc: "csc(pi/2)" => 1.,
|
||||||
|
trig_sec: "sec(0)" => 1.,
|
||||||
|
trig_cot: "cot(pi/4)" => 1.,
|
||||||
|
|
||||||
|
// Inverse trig aliases
|
||||||
|
inverse_trig_asin: "asin(1)" => std::f64::consts::FRAC_PI_2,
|
||||||
|
inverse_trig_acos: "acos(1)" => 0.,
|
||||||
|
inverse_trig_atan: "atan(1)" => std::f64::consts::FRAC_PI_4,
|
||||||
|
inverse_trig_acsc: "acsc(1)" => std::f64::consts::FRAC_PI_2,
|
||||||
|
inverse_trig_asec: "asec(1)" => 0.,
|
||||||
|
inverse_trig_acot: "acot(1)" => std::f64::consts::FRAC_PI_4,
|
||||||
|
|
||||||
|
// Hyperbolic and reciprocal hyperbolic
|
||||||
|
hyperbolic_sinh: "sinh(0)" => 0.,
|
||||||
|
hyperbolic_cosh: "cosh(0)" => 1.,
|
||||||
|
hyperbolic_tanh: "tanh(0)" => 0.,
|
||||||
|
hyperbolic_csch: "csch(1)" => 1f64.sinh().recip(),
|
||||||
|
hyperbolic_sech: "sech(0)" => 1.,
|
||||||
|
hyperbolic_coth: "coth(1)" => 1f64.tanh().recip(),
|
||||||
|
|
||||||
|
// Inverse hyperbolic
|
||||||
|
inverse_hyperbolic_asinh: "asinh(0)" => 0.,
|
||||||
|
inverse_hyperbolic_acosh: "acosh(1)" => 0.,
|
||||||
|
inverse_hyperbolic_atanh: "atanh(0)" => 0.,
|
||||||
|
inverse_hyperbolic_acsch: "acsch(1)" => 1f64.asinh(),
|
||||||
|
inverse_hyperbolic_asech: "asech(1)" => 1f64.acosh(),
|
||||||
|
inverse_hyperbolic_acoth: "acoth(2)" => 0.5f64.atanh(),
|
||||||
|
|
||||||
|
// Basic if statements
|
||||||
|
if_true_condition: "if(1,5,3)" => 5.,
|
||||||
|
if_false_condition: "if(0, 5, 3)" => 3.,
|
||||||
|
|
||||||
|
// Arithmetic conditions
|
||||||
|
if_arithmetic_true: "if(2+2-4, 1 , 0)" => 0.,
|
||||||
|
if_arithmetic_false: "if(3*2-5, 1, 0)" => 1.,
|
||||||
|
|
||||||
|
// Nested arithmetic
|
||||||
|
if_complex_arithmetic: "if((5+3)*(2-1), 10, 20)" => 10.,
|
||||||
|
if_with_division: "if(8/4-2 == 0, 15, 25)" => 15.,
|
||||||
|
if_with_division_ne: "if(8/4-2 ≠ 0, 15, 25)" => 25.,
|
||||||
|
|
||||||
|
// Constants in conditions
|
||||||
|
if_with_pi: "if(pi > 3, 1, 0)" => 1.,
|
||||||
|
if_with_e: "if(e < 3, 1, 0)" => 1.,
|
||||||
|
|
||||||
|
// Functions in conditions
|
||||||
|
if_with_sqrt: "if(sqrt(16) == 4, 1, 0)" => 1.,
|
||||||
|
if_with_sin: "if(sin(pi) == 0.0, 1, 0)" => 0.,
|
||||||
|
|
||||||
|
// Logical NOT (prefix !)
|
||||||
|
logical_not_zero: "!0" => 1.,
|
||||||
|
logical_not_nonzero: "!5" => 0.,
|
||||||
|
logical_not_expression: "!(2 - 2)" => 1.,
|
||||||
|
|
||||||
|
// Logical helpers as functions
|
||||||
|
logical_isnan: "isnan(0/0)" => 1.,
|
||||||
|
logical_eq: "eq(2, 2)" => 1.,
|
||||||
|
logical_greater: "greater(3, 2)" => 1.,
|
||||||
|
|
||||||
|
// Log / exp / pow / root
|
||||||
|
log_ln: "ln(e)" => 1.,
|
||||||
|
log_log10: "log(100)" => 2.,
|
||||||
|
log_log2: "log2(8)" => 3.,
|
||||||
|
log_change_of_base: "log(8, 2)" => 3.,
|
||||||
|
exp_function: "exp(1)" => std::f64::consts::E,
|
||||||
|
pow_real: "pow(2, 3)" => 8.,
|
||||||
|
root_square: "root(9, 2)" => 3.,
|
||||||
|
root_cube: "root(8, 3)" => 2.,
|
||||||
|
|
||||||
|
// Nested if statements
|
||||||
|
nested_if: "if(1, if(0, 1, 2), 3)" => 2.,
|
||||||
|
nested_if_complex: "if(2-2 == 0, if(1, 5, 6), if(1, 7, 8))" => 5.,
|
||||||
|
|
||||||
|
// Mixed operations in conditions and blocks
|
||||||
|
if_complex_condition: "if(sqrt(16) + sin(pi) < 5, 2*pi, 3*e)" => 2. * std::f64::consts::PI,
|
||||||
|
if_complex_blocks: "if(1, 2*sqrt(16) + sin(pi/2), 3*cos(0) + 4)" => 9.,
|
||||||
|
|
||||||
|
// Mapping helpers
|
||||||
|
mapping_trunc: "trunc(3.7)" => 3.,
|
||||||
|
mapping_fract: "fract(3.25)" => 0.25,
|
||||||
|
mapping_sign_pos: "sign(5)" => 1.,
|
||||||
|
mapping_sign_neg: "sign(-5)" => -1.,
|
||||||
|
|
||||||
|
// Geometry / mapping extras
|
||||||
|
geometry_hypot: "hypot(3, 4)" => 5.,
|
||||||
|
mapping_remap: "remap(5, 0, 10, 0, 100)" => 50.,
|
||||||
|
|
||||||
|
// GCD / LCM
|
||||||
|
gcd_simple: "gcd(24, 18)" => 6.,
|
||||||
|
lcm_simple: "lcm(4, 6)" => 12.,
|
||||||
|
|
||||||
|
// atan2
|
||||||
|
trig_atan2_axis: "atan2(1, 0)" => std::f64::consts::FRAC_PI_2,
|
||||||
|
|
||||||
|
// Comparison operators combined with logical AND
|
||||||
|
comparison_operators: "if(1 <= 2 && 1 ≤ 2 && 2 >= 1 && 2 ≥ 1, 1., 0.)" => 1.,
|
||||||
|
|
||||||
|
// Logical AND / OR
|
||||||
|
logical_and_true: "if(1 <= 2 && 2 < 3, 1., 0.)" => 1.,
|
||||||
|
logical_and_false: "if(1 <= 2 && 3 < 2, 1., 0.)" => 0.,
|
||||||
|
logical_or_true_left: "if(1 > 2 || 2 < 3, 1., 0.)" => 1.,
|
||||||
|
logical_or_true_right: "if(2 < 1 || 2 < 3, 1., 0.)" => 1.,
|
||||||
|
logical_or_false: "if(1 > 2 || 3 < 2, 1., 0.)" => 0.,
|
||||||
|
logical_precedence_and_over_or: "if(0 == 1 || 1 == 1 && 0 == 0, 1., 0.)" => 1.,
|
||||||
|
|
||||||
|
// Edge cases
|
||||||
|
if_zero: "if(0.0, 1, 2)" => 2.,
|
||||||
|
|
||||||
|
// Complex nested expressions
|
||||||
|
if_nested_expr: "if((sqrt(16) + 2) * (sin(pi) + 1), 3 + 4 * 2, 5 - 2 / 1)" => 11.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+154
-290
@@ -1,316 +1,163 @@
|
|||||||
use crate::ast::{BinaryOp, Literal, Node, UnaryOp, Unit};
|
use crate::ast::{BinaryOp, Literal, Node, UnaryOp, Unit};
|
||||||
use crate::context::EvalContext;
|
use crate::context::EvalContext;
|
||||||
|
use crate::lexer::{Lexer, Span, Token};
|
||||||
use crate::value::{Complex, Number, Value};
|
use crate::value::{Complex, Number, Value};
|
||||||
|
use chumsky::container::Seq;
|
||||||
|
use chumsky::input::{BorrowInput, ValueInput};
|
||||||
|
use chumsky::{Parser, prelude::*};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use num_complex::ComplexFloat;
|
use num_complex::ComplexFloat;
|
||||||
use pest::Parser;
|
use std::fmt;
|
||||||
use pest::iterators::{Pair, Pairs};
|
|
||||||
use pest::pratt_parser::{Assoc, Op, PrattParser};
|
|
||||||
use pest_derive::Parser;
|
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Parser)]
|
/// One message per parse failure, each tagged with its byte range in the source expression.
|
||||||
#[grammar = "./grammer.pest"] // Point to the grammar file
|
#[derive(Debug)]
|
||||||
struct ExprParser;
|
pub struct ParseError(Vec<String>);
|
||||||
|
|
||||||
lazy_static! {
|
impl fmt::Display for ParseError {
|
||||||
static ref PRATT_PARSER: PrattParser<Rule> = {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
PrattParser::new()
|
for (index, error) in self.0.iter().enumerate() {
|
||||||
.op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left))
|
if index > 0 {
|
||||||
.op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left) | Op::infix(Rule::paren, Assoc::Left))
|
writeln!(f)?;
|
||||||
.op(Op::infix(Rule::pow, Assoc::Right))
|
}
|
||||||
.op(Op::postfix(Rule::fac) | Op::postfix(Rule::EOI))
|
write!(f, "{error}")?;
|
||||||
.op(Op::prefix(Rule::sqrt))
|
}
|
||||||
.op(Op::prefix(Rule::neg))
|
Ok(())
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
impl std::error::Error for ParseError {}
|
||||||
pub enum TypeError {
|
|
||||||
#[error("Invalid BinOp: {0:?} {1:?} {2:?}")]
|
|
||||||
InvalidBinaryOp(Unit, BinaryOp, Unit),
|
|
||||||
|
|
||||||
#[error("Invalid UnaryOp: {0:?}")]
|
|
||||||
InvalidUnaryOp(Unit, UnaryOp),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum ParseError {
|
|
||||||
#[error("ParseIntError: {0}")]
|
|
||||||
ParseInt(#[from] ParseIntError),
|
|
||||||
#[error("ParseFloatError: {0}")]
|
|
||||||
ParseFloat(#[from] ParseFloatError),
|
|
||||||
|
|
||||||
#[error("TypeError: {0}")]
|
|
||||||
Type(#[from] TypeError),
|
|
||||||
|
|
||||||
#[error("PestError: {0}")]
|
|
||||||
Pest(#[from] Box<pest::error::Error<Rule>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub fn try_parse_from_str(s: &str) -> Result<(Node, Unit), ParseError> {
|
pub fn try_parse_from_str(src: &str) -> Result<Node, ParseError> {
|
||||||
let pairs = ExprParser::parse(Rule::program, s).map_err(Box::new)?;
|
let tokens = Lexer::new(src);
|
||||||
let (node, metadata) = parse_expr(pairs)?;
|
|
||||||
Ok((node, metadata.unit))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NodeMetadata {
|
match parser().parse(tokens).into_result() {
|
||||||
pub unit: Unit,
|
Ok(ast) => Ok(ast),
|
||||||
}
|
Err(parse_errs) => Err(ParseError(parse_errs.into_iter().map(|e| format!("{e} at {}", e.span())).collect())),
|
||||||
|
|
||||||
impl NodeMetadata {
|
|
||||||
pub fn new(unit: Unit) -> Self {
|
|
||||||
Self { unit }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_unit(pairs: Pairs<Rule>) -> Result<(Unit, f64), ParseError> {
|
|
||||||
let mut scale = 1.;
|
|
||||||
let mut length = 0;
|
|
||||||
let mut mass = 0;
|
|
||||||
let mut time = 0;
|
|
||||||
|
|
||||||
for pair in pairs {
|
|
||||||
println!("found rule: {:?}", pair.as_rule());
|
|
||||||
match pair.as_rule() {
|
|
||||||
Rule::nano => scale *= 1e-9,
|
|
||||||
Rule::micro => scale *= 1e-6,
|
|
||||||
Rule::milli => scale *= 1e-3,
|
|
||||||
Rule::centi => scale *= 1e-2,
|
|
||||||
Rule::deci => scale *= 1e-1,
|
|
||||||
Rule::deca => scale *= 1e1,
|
|
||||||
Rule::hecto => scale *= 1e2,
|
|
||||||
Rule::kilo => scale *= 1e3,
|
|
||||||
Rule::mega => scale *= 1e6,
|
|
||||||
Rule::giga => scale *= 1e9,
|
|
||||||
Rule::tera => scale *= 1e12,
|
|
||||||
|
|
||||||
Rule::meter => length = 1,
|
|
||||||
Rule::gram => mass = 1,
|
|
||||||
Rule::second => time = 1,
|
|
||||||
|
|
||||||
_ => unreachable!(), // All possible rules should be covered
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((Unit { length, mass, time }, scale))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_const(pair: Pair<Rule>) -> Literal {
|
pub fn parser<'src, I>() -> impl Parser<'src, I, Node, extra::Err<Rich<'src, Token<'src>, Span>>>
|
||||||
match pair.as_rule() {
|
where
|
||||||
Rule::infinity => Literal::Float(f64::INFINITY),
|
I: ValueInput<'src, Token = Token<'src>, Span = Span>,
|
||||||
Rule::imaginary_unit => Literal::Complex(Complex::new(0., 1.)),
|
{
|
||||||
Rule::pi => Literal::Float(std::f64::consts::PI),
|
recursive(|expr| {
|
||||||
Rule::tau => Literal::Float(2. * std::f64::consts::PI),
|
let constant = select! {
|
||||||
Rule::euler_number => Literal::Float(std::f64::consts::E),
|
Token::Float(f) => Node::Lit(Literal::Float(f)),
|
||||||
Rule::golden_ratio => Literal::Float(1.61803398875),
|
Token::Const(c) => Node::Lit(c.value())
|
||||||
_ => unreachable!("Unexpected constant: {:?}", pair),
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_lit(mut pairs: Pairs<Rule>) -> Result<(Literal, Unit), ParseError> {
|
let args = expr.clone().separated_by(just(Token::Comma)).collect::<Vec<_>>().delimited_by(just(Token::LParen), just(Token::RParen));
|
||||||
let literal = match pairs.next() {
|
|
||||||
Some(lit) => match lit.as_rule() {
|
|
||||||
Rule::int => {
|
|
||||||
let value = lit.as_str().parse::<i32>()? as f64;
|
|
||||||
Literal::Float(value)
|
|
||||||
}
|
|
||||||
Rule::float => {
|
|
||||||
let value = lit.as_str().parse::<f64>()?;
|
|
||||||
Literal::Float(value)
|
|
||||||
}
|
|
||||||
Rule::unit => {
|
|
||||||
let (unit, scale) = parse_unit(lit.into_inner())?;
|
|
||||||
return Ok((Literal::Float(scale), unit));
|
|
||||||
}
|
|
||||||
rule => unreachable!("unexpected rule: {:?}", rule),
|
|
||||||
},
|
|
||||||
None => unreachable!("expected rule"), // No literal found
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(unit_pair) = pairs.next() {
|
let if_expr = just(Token::If)
|
||||||
let unit_pairs = unit_pair.into_inner(); // Get the inner pairs for the unit
|
.ignore_then(args.clone()) // Parses (cond, a, b)
|
||||||
let (unit, scale) = parse_unit(unit_pairs)?;
|
.try_map(|args: Vec<Node>, span| {
|
||||||
|
if args.len() != 3 {
|
||||||
println!("found unit: {unit:?}");
|
return Err(Rich::custom(span, "Expected 3 arguments in if(cond, a, b)"));
|
||||||
|
|
||||||
Ok((
|
|
||||||
match literal {
|
|
||||||
Literal::Float(num) => Literal::Float(num * scale),
|
|
||||||
Literal::Complex(num) => Literal::Complex(num * scale),
|
|
||||||
},
|
|
||||||
unit,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok((literal, Unit::BASE_UNIT))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_expr(pairs: Pairs<Rule>) -> Result<(Node, NodeMetadata), ParseError> {
|
|
||||||
PRATT_PARSER
|
|
||||||
.map_primary(|primary| {
|
|
||||||
Ok(match primary.as_rule() {
|
|
||||||
Rule::lit => {
|
|
||||||
let (lit, unit) = parse_lit(primary.into_inner())?;
|
|
||||||
|
|
||||||
(Node::Lit(lit), NodeMetadata { unit })
|
|
||||||
}
|
}
|
||||||
Rule::fn_call => {
|
let mut iter = args.into_iter();
|
||||||
let mut pairs = primary.into_inner();
|
let cond = iter.next().unwrap();
|
||||||
let name = pairs.next().expect("fn_call always has 2 children").as_str().to_string();
|
let if_b = iter.next().unwrap();
|
||||||
|
let else_b = iter.next().unwrap();
|
||||||
|
Ok(Node::Conditional {
|
||||||
|
condition: Box::new(cond),
|
||||||
|
if_block: Box::new(if_b),
|
||||||
|
else_block: Box::new(else_b),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
(
|
let ident = select! {Token::Ident(s) => s}.labelled("ident");
|
||||||
Node::FnCall {
|
|
||||||
name,
|
|
||||||
expr: pairs.map(|p| parse_expr(p.into_inner()).map(|expr| expr.0)).collect::<Result<Vec<Node>, ParseError>>()?,
|
|
||||||
},
|
|
||||||
NodeMetadata::new(Unit::BASE_UNIT),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Rule::constant => {
|
|
||||||
let lit = parse_const(primary.into_inner().next().expect("constant should have atleast 1 child"));
|
|
||||||
|
|
||||||
(Node::Lit(lit), NodeMetadata::new(Unit::BASE_UNIT))
|
let call = ident.then(args).map(|(name, args): (&str, Vec<Node>)| Node::FnCall { name: name.to_string(), expr: args });
|
||||||
}
|
|
||||||
Rule::ident => {
|
|
||||||
let name = primary.as_str().to_string();
|
|
||||||
|
|
||||||
(Node::Var(name), NodeMetadata::new(Unit::BASE_UNIT))
|
let parens = expr.clone().delimited_by(just(Token::LParen), just(Token::RParen));
|
||||||
}
|
let var = ident.map(|s| Node::Var(s.to_string()));
|
||||||
Rule::expr => parse_expr(primary.into_inner())?,
|
|
||||||
Rule::float => {
|
let atom = choice((constant, if_expr, call, parens, var)).labelled("atom").boxed();
|
||||||
let value = primary.as_str().parse::<f64>()?;
|
|
||||||
(Node::Lit(Literal::Float(value)), NodeMetadata::new(Unit::BASE_UNIT))
|
let add_op = choice((just(Token::Plus).to(BinaryOp::Add), just(Token::Minus).to(BinaryOp::Sub)));
|
||||||
}
|
let mul_op = choice((just(Token::Star).to(BinaryOp::Mul), just(Token::Slash).to(BinaryOp::Div), just(Token::Modulo).to(BinaryOp::Modulo)));
|
||||||
rule => unreachable!("unexpected rule: {:?}", rule),
|
let pow_op = just(Token::Caret).to(BinaryOp::Pow);
|
||||||
|
let unary_op = choice((just(Token::Minus).to(UnaryOp::Neg), just(Token::Bang).to(UnaryOp::Not)));
|
||||||
|
let and_op = just(Token::AndAnd).to(BinaryOp::And);
|
||||||
|
let or_op = just(Token::OrOr).to(BinaryOp::Or);
|
||||||
|
let cmp_op = choice((
|
||||||
|
just(Token::Lt).to(BinaryOp::Lt),
|
||||||
|
just(Token::Le).to(BinaryOp::Leq),
|
||||||
|
just(Token::Gt).to(BinaryOp::Gt),
|
||||||
|
just(Token::Ge).to(BinaryOp::Geq),
|
||||||
|
just(Token::Neq).to(BinaryOp::Neq),
|
||||||
|
just(Token::EqEq).to(BinaryOp::Eq),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Postfix factorial: expr! → UnaryOp::Fac
|
||||||
|
let postfix = atom
|
||||||
|
.clone()
|
||||||
|
.foldl(just(Token::Bang).repeated(), |expr, _| Node::UnaryOp {
|
||||||
|
op: UnaryOp::Fac,
|
||||||
|
expr: Box::new(expr),
|
||||||
})
|
})
|
||||||
})
|
.boxed();
|
||||||
.map_prefix(|op, rhs| {
|
|
||||||
let (rhs, rhs_metadata) = rhs?;
|
|
||||||
let op = match op.as_rule() {
|
|
||||||
Rule::neg => UnaryOp::Neg,
|
|
||||||
Rule::sqrt => UnaryOp::Sqrt,
|
|
||||||
|
|
||||||
rule => unreachable!("unexpected rule: {:?}", rule),
|
let pow = postfix.clone().foldl(
|
||||||
};
|
pow_op
|
||||||
|
.then(unary_op.clone().repeated().foldr(postfix, |op, expr| Node::UnaryOp { op, expr: Box::new(expr) }).boxed())
|
||||||
let node = Node::UnaryOp { expr: Box::new(rhs), op };
|
.repeated(),
|
||||||
let unit = rhs_metadata.unit;
|
|lhs, (op, rhs)| Node::BinOp {
|
||||||
|
|
||||||
let unit = if !unit.is_base() {
|
|
||||||
match op {
|
|
||||||
UnaryOp::Sqrt if unit.length % 2 == 0 && unit.mass % 2 == 0 && unit.time % 2 == 0 => Unit {
|
|
||||||
length: unit.length / 2,
|
|
||||||
mass: unit.mass / 2,
|
|
||||||
time: unit.time / 2,
|
|
||||||
},
|
|
||||||
UnaryOp::Neg => unit,
|
|
||||||
op => return Err(ParseError::Type(TypeError::InvalidUnaryOp(unit, op))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Unit::BASE_UNIT
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((node, NodeMetadata::new(unit)))
|
|
||||||
})
|
|
||||||
.map_postfix(|lhs, op| {
|
|
||||||
let (lhs_node, lhs_metadata) = lhs?;
|
|
||||||
|
|
||||||
let op = match op.as_rule() {
|
|
||||||
Rule::EOI => return Ok((lhs_node, lhs_metadata)),
|
|
||||||
Rule::fac => UnaryOp::Fac,
|
|
||||||
rule => unreachable!("unexpected rule: {:?}", rule),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !lhs_metadata.unit.is_base() {
|
|
||||||
return Err(ParseError::Type(TypeError::InvalidUnaryOp(lhs_metadata.unit, op)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Node::UnaryOp { expr: Box::new(lhs_node), op }, lhs_metadata))
|
|
||||||
})
|
|
||||||
.map_infix(|lhs, op, rhs| {
|
|
||||||
let (lhs, lhs_metadata) = lhs?;
|
|
||||||
let (rhs, rhs_metadata) = rhs?;
|
|
||||||
|
|
||||||
let op = match op.as_rule() {
|
|
||||||
Rule::add => BinaryOp::Add,
|
|
||||||
Rule::sub => BinaryOp::Sub,
|
|
||||||
Rule::mul => BinaryOp::Mul,
|
|
||||||
Rule::div => BinaryOp::Div,
|
|
||||||
Rule::pow => BinaryOp::Pow,
|
|
||||||
Rule::paren => BinaryOp::Mul,
|
|
||||||
rule => unreachable!("unexpected rule: {:?}", rule),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (lhs_unit, rhs_unit) = (lhs_metadata.unit, rhs_metadata.unit);
|
|
||||||
|
|
||||||
let unit = match (!lhs_unit.is_base(), !rhs_unit.is_base()) {
|
|
||||||
(true, true) => match op {
|
|
||||||
BinaryOp::Mul => Unit {
|
|
||||||
length: lhs_unit.length + rhs_unit.length,
|
|
||||||
mass: lhs_unit.mass + rhs_unit.mass,
|
|
||||||
time: lhs_unit.time + rhs_unit.time,
|
|
||||||
},
|
|
||||||
BinaryOp::Div => Unit {
|
|
||||||
length: lhs_unit.length - rhs_unit.length,
|
|
||||||
mass: lhs_unit.mass - rhs_unit.mass,
|
|
||||||
time: lhs_unit.time - rhs_unit.time,
|
|
||||||
},
|
|
||||||
BinaryOp::Add | BinaryOp::Sub => {
|
|
||||||
if lhs_unit == rhs_unit {
|
|
||||||
lhs_unit
|
|
||||||
} else {
|
|
||||||
return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, rhs_unit)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BinaryOp::Pow => {
|
|
||||||
return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, rhs_unit)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
(true, false) => match op {
|
|
||||||
BinaryOp::Add | BinaryOp::Sub => return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT))),
|
|
||||||
BinaryOp::Pow => {
|
|
||||||
//TODO: improve error type
|
|
||||||
//TODO: support 1 / int
|
|
||||||
if let Ok(Value::Number(Number::Real(val))) = rhs.eval(&EvalContext::default()) {
|
|
||||||
if (val - val as i32 as f64).abs() <= f64::EPSILON {
|
|
||||||
Unit {
|
|
||||||
length: lhs_unit.length * val as i32,
|
|
||||||
mass: lhs_unit.mass * val as i32,
|
|
||||||
time: lhs_unit.time * val as i32,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => lhs_unit,
|
|
||||||
},
|
|
||||||
(false, true) => match op {
|
|
||||||
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Pow => return Err(ParseError::Type(TypeError::InvalidBinaryOp(Unit::BASE_UNIT, op, rhs_unit))),
|
|
||||||
_ => rhs_unit,
|
|
||||||
},
|
|
||||||
(false, false) => Unit::BASE_UNIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
let node = Node::BinOp {
|
|
||||||
lhs: Box::new(lhs),
|
lhs: Box::new(lhs),
|
||||||
op,
|
op,
|
||||||
rhs: Box::new(rhs),
|
rhs: Box::new(rhs),
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok((node, NodeMetadata::new(unit)))
|
let unary = unary_op.repeated().foldr(pow, |op, expr| Node::UnaryOp { op, expr: Box::new(expr) }).boxed();
|
||||||
|
|
||||||
|
let product = unary
|
||||||
|
.clone()
|
||||||
|
.foldl(mul_op.then(unary).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
|
let add = product.clone().foldl(add_op.then(product).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
let cmp = add.clone().foldl(cmp_op.then(add).repeated(), |lhs: Node, (op, rhs)| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chain comparisons like `a < b < c` by multiplying the boolean
|
||||||
|
// (1. / 0.) results, preserving the existing semantics.
|
||||||
|
let chained_cmp = cmp.clone().foldl(cmp.repeated(), |lhs, rhs| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op: BinaryOp::Mul,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
let and = chained_cmp.clone().foldl(and_op.then(chained_cmp).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
and.clone().foldl(or_op.then(and).repeated(), |lhs, (op, rhs)| Node::BinOp {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs),
|
||||||
})
|
})
|
||||||
.parse(pairs)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: set up Unit test for Units
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -319,8 +166,12 @@ mod tests {
|
|||||||
$(
|
$(
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let result = Node::try_parse_from_str($input).unwrap();
|
|
||||||
assert_eq!(result.0, $expected);
|
let result = match Node::try_parse_from_str($input) {
|
||||||
|
Ok(expr) => expr,
|
||||||
|
Err(err) => panic!("failed to parse `{}`: {err}", $input),
|
||||||
|
};
|
||||||
|
assert_eq!(result, $expected);
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
@@ -349,16 +200,20 @@ mod tests {
|
|||||||
op: BinaryOp::Pow,
|
op: BinaryOp::Pow,
|
||||||
rhs: Box::new(Node::Lit(Literal::Float(3.))),
|
rhs: Box::new(Node::Lit(Literal::Float(3.))),
|
||||||
},
|
},
|
||||||
test_parse_unary_sqrt: "sqrt(16)" => Node::UnaryOp {
|
test_parse_unary_sqrt: "sqrt(16)" => Node::FnCall {
|
||||||
expr: Box::new(Node::Lit(Literal::Float(16.))),
|
name: "sqrt".to_string(),
|
||||||
op: UnaryOp::Sqrt,
|
expr: vec![Node::Lit(Literal::Float(16.))],
|
||||||
},
|
},
|
||||||
test_parse_sqr_ident: "sqr(16)" => Node::FnCall {
|
test_parse_ii_call: "ii(16)" => Node::FnCall {
|
||||||
name:"sqr".to_string(),
|
name: "ii".to_string(),
|
||||||
expr: vec![Node::Lit(Literal::Float(16.))]
|
expr: vec![Node::Lit(Literal::Float(16.))]
|
||||||
},
|
},
|
||||||
|
test_parse_i_mul: "i(16)" => Node::BinOp {
|
||||||
test_parse_complex_expr: "(1 + 2) 3 - 4 ^ 2" => Node::BinOp {
|
lhs: Box::new(Node::Lit(Literal::Complex(Complex::new(0., 1.)))),
|
||||||
|
op: BinaryOp::Mul,
|
||||||
|
rhs: Box::new(Node::Lit(Literal::Float(16.))),
|
||||||
|
},
|
||||||
|
test_parse_complex_expr: "(1 + 2) * 3 - 4 ^ 2" => Node::BinOp {
|
||||||
lhs: Box::new(Node::BinOp {
|
lhs: Box::new(Node::BinOp {
|
||||||
lhs: Box::new(Node::BinOp {
|
lhs: Box::new(Node::BinOp {
|
||||||
lhs: Box::new(Node::Lit(Literal::Float(1.))),
|
lhs: Box::new(Node::Lit(Literal::Float(1.))),
|
||||||
@@ -374,6 +229,15 @@ mod tests {
|
|||||||
op: BinaryOp::Pow,
|
op: BinaryOp::Pow,
|
||||||
rhs: Box::new(Node::Lit(Literal::Float(2.))),
|
rhs: Box::new(Node::Lit(Literal::Float(2.))),
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
|
test_conditional_expr: "if (x+3, 0, 1)" => Node::Conditional{
|
||||||
|
condition: Box::new(Node::BinOp{
|
||||||
|
lhs: Box::new(Node::Var("x".to_string())),
|
||||||
|
op: BinaryOp::Add,
|
||||||
|
rhs: Box::new(Node::Lit(Literal::Float(3.))),
|
||||||
|
}),
|
||||||
|
if_block: Box::new(Node::Lit(Literal::Float(0.))),
|
||||||
|
else_block: Box::new(Node::Lit(Literal::Float(1.))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,28 +52,74 @@ impl std::fmt::Display for Number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Number {
|
impl Number {
|
||||||
pub fn binary_op(self, op: BinaryOp, other: Number) -> Number {
|
pub fn binary_op(self, op: BinaryOp, other: Number) -> Option<Number> {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Number::Real(lhs), Number::Real(rhs)) => {
|
(Number::Real(lhs), Number::Real(rhs)) => {
|
||||||
let result = match op {
|
let result = match op {
|
||||||
|
BinaryOp::And => {
|
||||||
|
let l = lhs != 0.;
|
||||||
|
let r = rhs != 0.;
|
||||||
|
if l && r { 1. } else { 0. }
|
||||||
|
}
|
||||||
|
BinaryOp::Or => {
|
||||||
|
let l = lhs != 0.;
|
||||||
|
let r = rhs != 0.;
|
||||||
|
if l || r { 1. } else { 0. }
|
||||||
|
}
|
||||||
BinaryOp::Add => lhs + rhs,
|
BinaryOp::Add => lhs + rhs,
|
||||||
BinaryOp::Sub => lhs - rhs,
|
BinaryOp::Sub => lhs - rhs,
|
||||||
BinaryOp::Mul => lhs * rhs,
|
BinaryOp::Mul => lhs * rhs,
|
||||||
BinaryOp::Div => lhs / rhs,
|
BinaryOp::Div => lhs / rhs,
|
||||||
|
BinaryOp::Modulo => lhs % rhs,
|
||||||
BinaryOp::Pow => lhs.powf(rhs),
|
BinaryOp::Pow => lhs.powf(rhs),
|
||||||
|
BinaryOp::Leq => (lhs <= rhs) as u8 as f64,
|
||||||
|
BinaryOp::Lt => (lhs < rhs) as u8 as f64,
|
||||||
|
BinaryOp::Geq => (lhs >= rhs) as u8 as f64,
|
||||||
|
BinaryOp::Gt => (lhs > rhs) as u8 as f64,
|
||||||
|
BinaryOp::Neq => (lhs != rhs) as u8 as f64,
|
||||||
|
BinaryOp::Eq => (lhs == rhs) as u8 as f64,
|
||||||
};
|
};
|
||||||
Number::Real(result)
|
|
||||||
|
Some(Number::Real(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Number::Complex(lhs), Number::Complex(rhs)) => {
|
(Number::Complex(lhs), Number::Complex(rhs)) => {
|
||||||
let result = match op {
|
let result = match op {
|
||||||
|
BinaryOp::And => {
|
||||||
|
let l = lhs != Complex::new(0., 0.);
|
||||||
|
let r = rhs != Complex::new(0., 0.);
|
||||||
|
return Some(Number::Real(if l && r { 1. } else { 0. }));
|
||||||
|
}
|
||||||
|
BinaryOp::Or => {
|
||||||
|
let l = lhs != Complex::new(0., 0.);
|
||||||
|
let r = rhs != Complex::new(0., 0.);
|
||||||
|
return Some(Number::Real(if l || r { 1. } else { 0. }));
|
||||||
|
}
|
||||||
BinaryOp::Add => lhs + rhs,
|
BinaryOp::Add => lhs + rhs,
|
||||||
BinaryOp::Sub => lhs - rhs,
|
BinaryOp::Sub => lhs - rhs,
|
||||||
BinaryOp::Mul => lhs * rhs,
|
BinaryOp::Mul => lhs * rhs,
|
||||||
BinaryOp::Div => lhs / rhs,
|
BinaryOp::Div => lhs / rhs,
|
||||||
|
BinaryOp::Modulo => lhs % rhs,
|
||||||
BinaryOp::Pow => lhs.powc(rhs),
|
BinaryOp::Pow => lhs.powc(rhs),
|
||||||
|
BinaryOp::Leq | BinaryOp::Lt | BinaryOp::Geq | BinaryOp::Gt => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
BinaryOp::Neq => {
|
||||||
|
if lhs != rhs {
|
||||||
|
return Some(Number::Real(1.));
|
||||||
|
} else {
|
||||||
|
return Some(Number::Real(0.));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BinaryOp::Eq => {
|
||||||
|
if lhs == rhs {
|
||||||
|
return Some(Number::Real(1.));
|
||||||
|
} else {
|
||||||
|
return Some(Number::Real(0.));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Number::Complex(result)
|
Some(Number::Complex(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Number::Real(lhs), Number::Complex(rhs)) => {
|
(Number::Real(lhs), Number::Complex(rhs)) => {
|
||||||
@@ -84,8 +130,9 @@ impl Number {
|
|||||||
BinaryOp::Mul => lhs_complex * rhs,
|
BinaryOp::Mul => lhs_complex * rhs,
|
||||||
BinaryOp::Div => lhs_complex / rhs,
|
BinaryOp::Div => lhs_complex / rhs,
|
||||||
BinaryOp::Pow => lhs_complex.powc(rhs),
|
BinaryOp::Pow => lhs_complex.powc(rhs),
|
||||||
|
_ => return None,
|
||||||
};
|
};
|
||||||
Number::Complex(result)
|
Some(Number::Complex(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Number::Complex(lhs), Number::Real(rhs)) => {
|
(Number::Complex(lhs), Number::Real(rhs)) => {
|
||||||
@@ -96,8 +143,9 @@ impl Number {
|
|||||||
BinaryOp::Mul => lhs * rhs_complex,
|
BinaryOp::Mul => lhs * rhs_complex,
|
||||||
BinaryOp::Div => lhs / rhs_complex,
|
BinaryOp::Div => lhs / rhs_complex,
|
||||||
BinaryOp::Pow => lhs.powf(rhs),
|
BinaryOp::Pow => lhs.powf(rhs),
|
||||||
|
_ => return None,
|
||||||
};
|
};
|
||||||
Number::Complex(result)
|
Some(Number::Complex(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,15 +155,37 @@ impl Number {
|
|||||||
Number::Real(real) => match op {
|
Number::Real(real) => match op {
|
||||||
UnaryOp::Neg => Number::Real(-real),
|
UnaryOp::Neg => Number::Real(-real),
|
||||||
UnaryOp::Sqrt => Number::Real(real.sqrt()),
|
UnaryOp::Sqrt => Number::Real(real.sqrt()),
|
||||||
|
UnaryOp::Fac => {
|
||||||
UnaryOp::Fac => todo!("Implement factorial"),
|
// n! for real n: use integer semantics when n is a
|
||||||
|
// non-negative integer, otherwise return NaN.
|
||||||
|
if !real.is_finite() {
|
||||||
|
return Number::Real(f64::NAN);
|
||||||
|
}
|
||||||
|
let truncated = real.trunc();
|
||||||
|
if truncated < 0. || (real - truncated).abs() > f64::EPSILON {
|
||||||
|
return Number::Real(f64::NAN);
|
||||||
|
}
|
||||||
|
let n = truncated as u64;
|
||||||
|
let mut acc = 1_f64;
|
||||||
|
for k in 1..=n {
|
||||||
|
acc *= k as f64;
|
||||||
|
}
|
||||||
|
Number::Real(acc)
|
||||||
|
}
|
||||||
|
UnaryOp::Not => {
|
||||||
|
let is_zero = real == 0.;
|
||||||
|
Number::Real(if is_zero { 1. } else { 0. })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Number::Complex(complex) => match op {
|
Number::Complex(complex) => match op {
|
||||||
UnaryOp::Neg => Number::Complex(-complex),
|
UnaryOp::Neg => Number::Complex(-complex),
|
||||||
UnaryOp::Sqrt => Number::Complex(complex.sqrt()),
|
UnaryOp::Sqrt => Number::Complex(complex.sqrt()),
|
||||||
|
UnaryOp::Fac => Number::Complex(Complex::new(f64::NAN, f64::NAN)),
|
||||||
UnaryOp::Fac => todo!("Implement factorial"),
|
UnaryOp::Not => {
|
||||||
|
let is_zero = complex == Complex::new(0., 0.);
|
||||||
|
Number::Real(if is_zero { 1. } else { 0. })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ fn math<T: num_traits::float::Float>(
|
|||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
operand_b: T,
|
operand_b: T,
|
||||||
) -> T {
|
) -> T {
|
||||||
let (node, _unit) = match ast::Node::try_parse_from_str(&expression) {
|
let node = match ast::Node::try_parse_from_str(&expression) {
|
||||||
Ok(expr) => expr,
|
Ok(expr) => expr,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Invalid expression: `{expression}`\n{e:?}");
|
warn!("Invalid expression: `{expression}`\n{e}");
|
||||||
return T::from(0.).unwrap();
|
return T::from(0.).unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user