parsing: improve function handling
This commit is contained in:
@@ -1,17 +1,61 @@
|
|||||||
use crate::parsing::is_variable;
|
use crate::parsing::is_variable;
|
||||||
|
use crate::SUPPORTED_FUNCTIONS;
|
||||||
|
|
||||||
|
/// Protect function names that start with variable characters (like 'e' in 'exp')
|
||||||
|
/// by replacing them with a placeholder during parsing, then restoring them after.
|
||||||
|
/// This prevents incorrect splitting like "exp" -> "e" * "xp".
|
||||||
|
fn protect_function_names(input: &str) -> (String, Vec<(&'static str, String)>) {
|
||||||
|
let mut result = input.to_string();
|
||||||
|
let mut replacements = Vec::new();
|
||||||
|
|
||||||
|
// Only protect functions that start with a variable character
|
||||||
|
// Sort by length descending to replace longer matches first (e.g., "exp" before "e")
|
||||||
|
let mut funcs: Vec<&'static str> = SUPPORTED_FUNCTIONS
|
||||||
|
.iter()
|
||||||
|
.filter(|&&func| {
|
||||||
|
func.chars()
|
||||||
|
.next()
|
||||||
|
.map(|c| is_variable(&c))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
funcs.sort_by(|a, b| b.len().cmp(&a.len()));
|
||||||
|
|
||||||
|
for func in funcs {
|
||||||
|
// Use a placeholder made of letters that will be treated as a function name
|
||||||
|
// The placeholder won't be split because it's all letters
|
||||||
|
let placeholder = format!("zzzfn{}", replacements.len());
|
||||||
|
result = result.replace(func, &placeholder);
|
||||||
|
replacements.push((func, placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
(result, replacements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore protected function names from their placeholders
|
||||||
|
fn restore_function_names(input: &str, replacements: &[(&'static str, String)]) -> String {
|
||||||
|
let mut result = input.to_string();
|
||||||
|
for (func, placeholder) in replacements {
|
||||||
|
result = result.replace(placeholder, func);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
|
pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
|
||||||
|
// Protect function names that could be incorrectly split
|
||||||
|
let (protected, replacements) = protect_function_names(input);
|
||||||
|
|
||||||
split_function_chars(
|
split_function_chars(
|
||||||
&input
|
&protected
|
||||||
.replace("pi", "π") // replace "pi" text with pi symbol
|
.replace("pi", "π") // replace "pi" text with pi symbol
|
||||||
.replace("**", "^") // support alternate manner of expressing exponents
|
.replace("**", "^") // support alternate manner of expressing exponents
|
||||||
.replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function
|
|
||||||
.chars()
|
.chars()
|
||||||
.collect::<Vec<char>>(),
|
.collect::<Vec<char>>(),
|
||||||
split,
|
split,
|
||||||
)
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
|
.map(|x| restore_function_names(x, &replacements))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,4 +276,13 @@ fn split_function_test() {
|
|||||||
&["2", "sin(π) + 2", "cos(tau)"],
|
&["2", "sin(π) + 2", "cos(tau)"],
|
||||||
SplitType::Multiplication,
|
SplitType::Multiplication,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test that exp() function is properly handled (not split into e*xp)
|
||||||
|
assert_test("exp(x)", &["exp(x)"], SplitType::Multiplication);
|
||||||
|
assert_test("2exp(x)", &["2", "exp(x)"], SplitType::Multiplication);
|
||||||
|
assert_test(
|
||||||
|
"exp(x)sin(x)",
|
||||||
|
&["exp(x)", "sin(x)"],
|
||||||
|
SplitType::Multiplication,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user