diff --git a/parsing/src/splitting.rs b/parsing/src/splitting.rs index c802d14..579feae 100644 --- a/parsing/src/splitting.rs +++ b/parsing/src/splitting.rs @@ -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 { + // 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::>(), split, ) .iter() - .map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text + .map(|x| restore_function_names(x, &replacements)) .collect::>() } @@ -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, + ); }