parsing: improve function handling
This commit is contained in:
@@ -1,17 +1,61 @@
|
||||
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> {
|
||||
// Protect function names that could be incorrectly split
|
||||
let (protected, replacements) = protect_function_names(input);
|
||||
|
||||
split_function_chars(
|
||||
&input
|
||||
&protected
|
||||
.replace("pi", "π") // replace "pi" text with pi symbol
|
||||
.replace("**", "^") // support alternate manner of expressing exponents
|
||||
.replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function
|
||||
.chars()
|
||||
.collect::<Vec<char>>(),
|
||||
split,
|
||||
)
|
||||
.iter()
|
||||
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
|
||||
.map(|x| restore_function_names(x, &replacements))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
@@ -232,4 +276,13 @@ fn split_function_test() {
|
||||
&["2", "sin(π) + 2", "cos(tau)"],
|
||||
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