diff --git a/Cargo.lock b/Cargo.lock index c7f1a5a239..1560f7096d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2550,6 +2550,7 @@ dependencies = [ "graphite-editor", "js-sys", "log", + "math-parser", "meval", "ron", "serde", @@ -3569,6 +3570,17 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "math-parser" +version = "0.0.0" +dependencies = [ + "lazy_static", + "log", + "pest", + "pest_derive", + "thiserror", +] + [[package]] name = "matrixmultiply" version = "0.3.9" @@ -4376,6 +4388,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pest_meta" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -6816,6 +6873,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uds_windows" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index d59baab314..6ee1bf2bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ graph-craft = { path = "node-graph/graph-craft", features = ["serde"] } wgpu-executor = { path = "node-graph/wgpu-executor" } bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any"] } path-bool = { path = "libraries/path-bool", default-features = false } +math-parser = { path = "libraries/math-parser" } node-macro = { path = "node-graph/node-macro" } # Workspace dependencies diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index f30d5ed357..0960963c55 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -38,6 +38,7 @@ wasm-bindgen-futures = { workspace = true } bezier-rs = { workspace = true } glam = { workspace = true } meval = { workspace = true } +math-parser = { workspace = true } wgpu = { workspace = true, features = [ "fragile-send-sync-non-atomic-wasm", ] } # We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 4345c9092b..16104cec6a 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -916,18 +916,12 @@ impl EditorHandle { #[wasm_bindgen(js_name = evaluateMathExpression)] pub fn evaluate_math_expression(expression: &str) -> Option { - // TODO: Rewrite our own purpose-built math expression parser that supports unit conversions. - - let mut context = meval::Context::new(); - context.var("tau", std::f64::consts::TAU); - context.func("log", f64::log10); - context.func("log10", f64::log10); - context.func("log2", f64::log2); - - // Insert asterisks where implicit multiplication is used in the expression string - let expression = implicit_multiplication_preprocess(expression); - - meval::eval_str_with_context(expression, &context).ok() + let value = math_parser::evaluate(expression).inspect_err(|err| error!("Math parser error on \"{expression}\": {err}")).ok()?; + let Some(real) = value.as_real() else { + error!("{value} was not a real; skipping."); + return None; + }; + Some(real) } // Modified from this public domain snippet: diff --git a/libraries/math-parser/Cargo.toml b/libraries/math-parser/Cargo.toml index 45d5f773b2..9011b99941 100644 --- a/libraries/math-parser/Cargo.toml +++ b/libraries/math-parser/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "math-parser" -version = "0.1.0" +version = "0.0.0" +rust-version = "1.79" edition = "2021" +authors = ["Graphite Authors "] +description = "Parser for Graphite style mathematics expressions" +license = "MIT OR Apache-2.0" [dependencies] pest = "2.7" diff --git a/libraries/math-parser/src/value.rs b/libraries/math-parser/src/value.rs index 15bfb93f80..7fdb790579 100644 --- a/libraries/math-parser/src/value.rs +++ b/libraries/math-parser/src/value.rs @@ -7,6 +7,13 @@ impl Value { pub fn from_f64(x: f64) -> Self { Self::Complex(x, 0.0) } + /// Attempt to convert to a real number + pub fn as_real(&self) -> Option { + match self { + Self::Complex(real, imaginary) if imaginary.abs() < f64::EPSILON => Some(*real), + _ => None, + } + } } impl From for Value { @@ -14,3 +21,14 @@ impl From for Value { Self::from_f64(x) } } + +impl core::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(real) = self.as_real() { + return real.fmt(f); + } + match self { + Value::Complex(real, imaginary) => write!(f, "{real}{imaginary:+}i"), + } + } +}