Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add math-parser library #2033

Merged
merged 41 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
67a13be
start of parser
urisinger Oct 11, 2024
125294a
ops forgot
urisinger Oct 12, 2024
7c265bd
Merge branch 'GraphiteEditor:master' into master
urisinger Oct 12, 2024
b607c2f
reorder files and work on executer
urisinger Oct 12, 2024
a385a81
Merge remote-tracking branch 'refs/remotes/origin/master'
urisinger Oct 12, 2024
d573516
start of parser
urisinger Oct 11, 2024
e5d4a7b
ops forgot
urisinger Oct 12, 2024
2cbf7ca
reorder files and work on executer
urisinger Oct 12, 2024
e0a21bc
Cleanup and fix tests
0HyperCube Oct 13, 2024
6a791c0
Integrate into the editor
0HyperCube Oct 13, 2024
9d8a7a6
added unit checking at parse time
urisinger Oct 14, 2024
96fba10
cleanup
urisinger Oct 14, 2024
c0b18a7
fix tests
urisinger Oct 14, 2024
1632de8
fix issues
urisinger Oct 14, 2024
11f5799
fix editor intergration
urisinger Oct 14, 2024
f984c10
update pest grammer to support units
urisinger Oct 14, 2024
74b4796
units should be working, need to set up tests to know
urisinger Oct 14, 2024
eaa8b0b
Merge branch 'GraphiteEditor:master' into master
urisinger Oct 14, 2024
751ecfe
make unit type store exponants as i32
urisinger Oct 14, 2024
5f5277c
remove scale, insted just multiply the literal by the scale
urisinger Oct 15, 2024
99893b7
unit now contains empty unit,remove options
urisinger Oct 15, 2024
8e9f7c2
add more tests and implement almost all unary operators
urisinger Oct 15, 2024
3ec9c29
add evaluation context and variables
urisinger Oct 15, 2024
6f41897
function calling, api might be refined later
urisinger Oct 15, 2024
6cd7cd3
add constants, change function call to not be as built into the parser
urisinger Oct 15, 2024
2aec7f8
Merge branch 'master' into master
urisinger Nov 2, 2024
ab44869
Merge branch 'master' into master
urisinger Nov 6, 2024
01d08ee
add function definitions
urisinger Nov 6, 2024
b8eaf61
remove meval
urisinger Nov 6, 2024
b186cc9
remove raw-rs from workspace
urisinger Nov 6, 2024
b11c064
Merge branch 'master' into master
urisinger Nov 8, 2024
de66ed3
add support for numberless units
urisinger Nov 7, 2024
365e6d4
fix unit handleing logic, add some "unit" tests(haha)
urisinger Nov 7, 2024
e4353eb
make it so units cant do implcit mul with idents
urisinger Nov 7, 2024
5301c27
add bench and better tests
urisinger Nov 8, 2024
fd03858
fix editor api
urisinger Nov 8, 2024
2f0a0d5
Merge branch 'master' into master
Keavon Nov 21, 2024
92dcc7b
remove old test
urisinger Nov 21, 2024
8aa58f0
Merge branch 'master' into master
Keavon Nov 21, 2024
6b87696
change hashmap context to use deref
urisinger Nov 21, 2024
f684569
change constants to use hashmap instad of function
urisinger Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 65 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"libraries/dyn-any",
"libraries/path-bool",
"libraries/bezier-rs",
"libraries/math-parser",
"website/other/bezier-rs-demos/wasm",
]
exclude = ["node-graph/gpu-compiler"]
Expand All @@ -31,6 +32,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
Expand Down Expand Up @@ -77,7 +79,6 @@ glam = { version = "0.28", default-features = false, features = ["serde"] }
base64 = "0.22"
image = { version = "0.25", default-features = false, features = ["png"] }
rustybuzz = "0.17"
meval = "0.2"
spirv = "0.3"
fern = { version = "0.6", features = ["colored"] }
num_enum = "0.7"
Expand All @@ -94,9 +95,6 @@ syn = { version = "2.0", default-features = false, features = [
] }
kurbo = { version = "0.11.0", features = ["serde"] }

[patch.crates-io]
meval = { git = "https://github.com/Titaniumtown/meval-rs" }

[profile.dev]
opt-level = 1

Expand Down
2 changes: 1 addition & 1 deletion frontend/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ js-sys = { workspace = true }
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
Expand Down
90 changes: 11 additions & 79 deletions frontend/wasm/src/editor_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,72 +910,17 @@ impl EditorHandle {

#[wasm_bindgen(js_name = evaluateMathExpression)]
pub fn evaluate_math_expression(expression: &str) -> Option<f64> {
// 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()
}

// Modified from this public domain snippet: <https://gist.github.com/Titaniumtown/c181be5d06505e003d8c4d1e372684ff>
// Discussion: <https://github.com/rekka/meval-rs/issues/28#issuecomment-1826381922>
pub fn implicit_multiplication_preprocess(expression: &str) -> String {
let function = expression.to_lowercase().replace("log10(", "log(").replace("log2(", "logtwo(").replace("pi", "π").replace("tau", "τ");
let valid_variables: Vec<char> = "eπτ".chars().collect();
let letters: Vec<char> = ('a'..='z').chain('A'..='Z').collect();
let numbers: Vec<char> = ('0'..='9').collect();
let function_chars: Vec<char> = function.chars().collect();
let mut output_string: String = String::new();
let mut prev_chars: Vec<char> = Vec::new();

for c in function_chars {
let mut add_asterisk: bool = false;
let prev_chars_len = prev_chars.len();

let prev_prev_char = if prev_chars_len >= 2 { *prev_chars.get(prev_chars_len - 2).unwrap() } else { ' ' };

let prev_char = if prev_chars_len >= 1 { *prev_chars.get(prev_chars_len - 1).unwrap() } else { ' ' };

let c_letters_var = letters.contains(&c) | valid_variables.contains(&c);
let prev_letters_var = valid_variables.contains(&prev_char) | letters.contains(&prev_char);

if prev_char == ')' {
if (c == '(') | numbers.contains(&c) | c_letters_var {
add_asterisk = true;
}
} else if c == '(' {
if (valid_variables.contains(&prev_char) | (')' == prev_char) | numbers.contains(&prev_char)) && !letters.contains(&prev_prev_char) {
add_asterisk = true;
}
} else if numbers.contains(&prev_char) {
if (c == '(') | c_letters_var {
add_asterisk = true;
}
} else if letters.contains(&c) {
if numbers.contains(&prev_char) | (valid_variables.contains(&prev_char) && valid_variables.contains(&c)) {
add_asterisk = true;
}
} else if (numbers.contains(&c) | c_letters_var) && prev_letters_var {
add_asterisk = true;
}

if add_asterisk {
output_string += "*";
}

prev_chars.push(c);
output_string += &c.to_string();
}

// We have to convert the Greek symbols back to ASCII because meval doesn't support unicode symbols as context constants
output_string.replace("logtwo(", "log2(").replace('π', "pi").replace('τ', "tau")
let value = math_parser::evaluate(expression)
.inspect_err(|err| error!("Math parser error on \"{expression}\": {err}"))
.ok()?
.0
.inspect_err(|err| error!("Math evaluate error on \"{expression}\": {err} "))
.ok()?;
let Some(real) = value.as_real() else {
error!("{value} was not a real; skipping.");
return None;
};
Some(real)
}

/// Helper function for calling JS's `requestAnimationFrame` with the given closure
Expand Down Expand Up @@ -1066,16 +1011,3 @@ fn auto_save_all_documents() {
}
});
}

#[test]
fn implicit_multiplication_preprocess_tests() {
assert_eq!(implicit_multiplication_preprocess("2pi"), "2*pi");
assert_eq!(implicit_multiplication_preprocess("sin(2pi)"), "sin(2*pi)");
assert_eq!(implicit_multiplication_preprocess("2sin(pi)"), "2*sin(pi)");
assert_eq!(implicit_multiplication_preprocess("2sin(3(4 + 5))"), "2*sin(3*(4 + 5))");
assert_eq!(implicit_multiplication_preprocess("3abs(-4)"), "3*abs(-4)");
assert_eq!(implicit_multiplication_preprocess("-1(4)"), "-1*(4)");
assert_eq!(implicit_multiplication_preprocess("(-1)4"), "(-1)*4");
assert_eq!(implicit_multiplication_preprocess("(((-1)))(4)"), "(((-1)))*(4)");
assert_eq!(implicit_multiplication_preprocess("2sin(pi) + 2cos(tau)"), "2*sin(pi) + 2*cos(tau)");
}
23 changes: 23 additions & 0 deletions libraries/math-parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "math-parser"
version = "0.0.0"
rust-version = "1.79"
edition = "2021"
authors = ["Graphite Authors <[email protected]>"]
description = "Parser for Graphite style mathematics expressions"
license = "MIT OR Apache-2.0"

[dependencies]
pest = "2.7"
pest_derive = "2.7.11"
thiserror = "1"
lazy_static = "1.5"
num-complex = "0.4"
log = { workspace = true }

[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "bench"
harness = false
50 changes: 50 additions & 0 deletions libraries/math-parser/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use math_parser::ast;
use math_parser::context::EvalContext;

macro_rules! generate_benchmarks {
($( $input:expr ),* $(,)?) => {
fn parsing_bench(c: &mut Criterion) {
$(
c.bench_function(concat!("parse ", $input), |b| {
b.iter(|| {
let _ = black_box(ast::Node::from_str($input)).unwrap();
});
});
)*
}

fn evaluation_bench(c: &mut Criterion) {
$(
let expr = ast::Node::from_str($input).unwrap().0;
let context = EvalContext::default();

c.bench_function(concat!("eval ", $input), |b| {
b.iter(|| {
let _ = black_box(expr.eval(&context));
});
});
)*
}

criterion_group!(benches, parsing_bench, evaluation_bench);
criterion_main!(benches);
};
}

generate_benchmarks! {
"(3 * (4 + sqrt(25)) - cos(pi/3) * (2^3)) + 5 * e", // Mixed nested functions, constants, and operations
"((5 + 2 * (3 - sqrt(49)))^2) / (1 + sqrt(16)) + tau / 2", // Complex nested expression with constants
"log(100, 10) + (5 * sin(pi/4) + sqrt(81)) / (2 * phi)", // Logarithmic and trigonometric functions
"(sqrt(144) * 2 + 5) / (3 * (4 - sin(pi / 6))) + e^2", // Combined square root, trigonometric, and exponential operations
"cos(2 * pi) + tan(pi / 3) * log(32, 2) - sqrt(256)", // Multiple trigonometric and logarithmic functions
"(10 * (3 + 2) - 8 / 2)^2 + 7 * (2^4) - sqrt(225) + phi", // Mixed arithmetic with constants
"(5^2 + 3^3) * (sqrt(81) + sqrt(64)) - tau * log(1000, 10)", // Power and square root with constants
"((8 * sqrt(49) - 2 * e) + log(256, 2) / (2 + cos(pi))) * 1.5", // Nested functions and constants
"(tan(pi / 4) + 5) * (3 + sqrt(36)) / (log(1024, 2) - 4)", // Nested functions with trigonometry and logarithm
"((3 * e + 2 * sqrt(100)) - cos(tau / 4)) * log(27, 3) + phi", // Mixed constant usage and functions
"(sqrt(100) + 5 * sin(pi / 6) - 8 / log(64, 2)) + e^(1.5)", // Complex mix of square root, division, and exponentiation
"((sin(pi/2) + cos(0)) * (e^2 - 2 * sqrt(16))) / (log(100, 10) + pi)", // Nested trigonometric, exponential, and logarithmic functions
"(5 * (7 + sqrt(121)) - (log(243, 3) * phi)) + 3^5 / tau", //
}
Loading
Loading