diff --git a/lib/src/equation_parser.dart b/lib/src/equation_parser.dart index 7a35a7d..889f681 100644 --- a/lib/src/equation_parser.dart +++ b/lib/src/equation_parser.dart @@ -306,6 +306,37 @@ class EquationParser { ), }; + // ═════════════════════════════════════════════════════════════════════════ + // PRE-COMPILED REGULAR EXPRESSIONS (Optimization) + // ═════════════════════════════════════════════════════════════════════════ + + static final _textRegExp = RegExp(r'\\text\{([^}]*)\}'); + static final _styleRegExp = RegExp( + r'\\(mathrm|mathbf|mathit|mathsf|mathtt|bold|invisible)\{([^}]*)\}', + ); + static final _bracketRegExp = RegExp(r'\\(left|right|big|Big|bigg|Bigg)'); + static final _decorationRegExp = RegExp( + r'\\(vec|hat|bar|dot|ddot|tilde)\{([^}]*)\}', + ); + static final _decorationSpacedRegExp = RegExp( + r'\\(vec|hat|bar|dot|ddot|tilde)\s+([a-zA-Z])', + ); + static final _limRegExp = RegExp(r'\\lim_\{([^}]*)\}'); + static final _exponentRegExp = RegExp(r'\^{([^}]*)}'); + static final _subscriptRegExp = RegExp(r'_\{([^}]*)\}'); + static final _fracRegExp = RegExp(r'\\frac\{([^}]*)\}\{([^}]*)\}'); + static final _sqrtNRegExp = RegExp(r'\\sqrt\[([^\]]*)\]\{([^}]*)\}'); + static final _sqrtRegExp = RegExp(r'\\sqrt\{([^}]*)\}'); + static final _deltaRegExp = RegExp(r'(\b[Dd]elta)\s+([a-zA-Z])'); + static final _iRegExp = RegExp(r'(? ')*${m.group(1)}', ); substitutedExpr = substitutedExpr.replaceAllMapped( - RegExp(r'(\d)\('), + _implicitDigitParenRegExp, (m) => '${m.group(1)}*(', ); @@ -630,29 +661,18 @@ class EquationParser { // 1. Remove common LaTeX style commands // \text{...} -> remove entirely (content often units/labels) - expr = expr.replaceAllMapped(RegExp(r'\\text\{([^}]*)\}'), (m) => ''); + expr = expr.replaceAllMapped(_textRegExp, (m) => ''); // Others -> keep content - expr = expr.replaceAllMapped( - RegExp( - r'\\(mathrm|mathbf|mathit|mathsf|mathtt|bold|invisible)\{([^}]*)\}', - ), - (m) => m[2]!, - ); - expr = expr.replaceAll(RegExp(r'\\(left|right|big|Big|bigg|Bigg)'), ''); + expr = expr.replaceAllMapped(_styleRegExp, (m) => m[2]!); + expr = expr.replaceAll(_bracketRegExp, ''); expr = expr.replaceAll(r'\,', ' '); expr = expr.replaceAll(r'\:', ' '); expr = expr.replaceAll(r'\;', ' '); expr = expr.replaceAll(r'\!', ''); // 1.5 Remove decorations: \vec{x}, \hat{x}, \bar{x}, \dot{x}, \ddot{x} - expr = expr.replaceAllMapped( - RegExp(r'\\(vec|hat|bar|dot|ddot|tilde)\{([^}]*)\}'), - (m) => '${m[2]}', - ); - expr = expr.replaceAllMapped( - RegExp(r'\\(vec|hat|bar|dot|ddot|tilde)\s+([a-zA-Z])'), - (m) => '${m[2]}', - ); + expr = expr.replaceAllMapped(_decorationRegExp, (m) => '${m[2]}'); + expr = expr.replaceAllMapped(_decorationSpacedRegExp, (m) => '${m[2]}'); // 2. Constants/Operators expr = expr.replaceAll(r'\cdot', '*'); @@ -665,64 +685,33 @@ class EquationParser { expr = expr.replaceAll(r'\rightarrow', ' '); // 3. Limits - expr = expr.replaceAllMapped(RegExp(r'\\lim_\{([^}]*)\}'), (m) => 'lim '); + expr = expr.replaceAllMapped(_limRegExp, (m) => 'lim '); expr = expr.replaceAll(r' \lim ', ' lim '); expr = expr.replaceAll(r'\lim ', 'lim '); // 4. Exponents and Subscripts (removes {} - do this BEFORE fractions to handle nested braces in exp) - expr = expr.replaceAllMapped(RegExp(r'\^{([^}]*)}'), (m) => '^(${m[1]})'); - expr = expr.replaceAllMapped(RegExp(r'_\{([^}]*)\}'), (m) => '_${m[1]}'); + expr = expr.replaceAllMapped(_exponentRegExp, (m) => '^(${m[1]})'); + expr = expr.replaceAllMapped(_subscriptRegExp, (m) => '_${m[1]}'); // 5. Fractions (Recursive, removes {}) - final fracPattern = RegExp(r'\\frac\{([^}]*)\}\{([^}]*)\}'); while (expr.contains(r'\frac{')) { final oldExpr = expr; - expr = expr.replaceAllMapped(fracPattern, (m) => '(${m[1]})/(${m[2]})'); + expr = expr.replaceAllMapped(_fracRegExp, (m) => '(${m[1]})/(${m[2]})'); if (oldExpr == expr) break; } // 6. Roots (removes {}) // \sqrt[n]{x} expr = expr.replaceAllMapped( - RegExp(r'\\sqrt\[([^\]]*)\]\{([^}]*)\}'), + _sqrtNRegExp, (m) => '(${m[2]})^(1/${m[1]})', ); // \sqrt{x} - expr = expr.replaceAllMapped( - RegExp(r'\\sqrt\{([^}]*)\}'), - (m) => 'sqrt(${m[1]})', - ); + expr = expr.replaceAllMapped(_sqrtRegExp, (m) => 'sqrt(${m[1]})'); // 7. Replace literal functions (e.g. \sin -> sin) - // Note: \sin is often space separated, but might be attached if braces were removed? - // Usually \sin x or \sin(x). - for (final f in [ - 'asin', - 'acos', - 'atan', - 'sinh', - 'cosh', - 'tanh', - 'sin', - 'cos', - 'tan', - 'sqrt', - 'exp', - 'log', - 'abs', - 'sec', - 'csc', - 'cot', - 'lim', - 'pow', - ]) { - expr = expr.replaceAll('\\$f ', '$f '); - expr = expr.replaceAll('\\$f(', '$f('); - // Also catch cases where space might have been removed or not present - // e.g. \sinx -> sinx (then split). - // Use regex to be safe - expr = expr.replaceAllMapped(RegExp('\\\\$f(?![a-zA-Z])'), (m) => f); - } + // Use optimized combined regex for all common math functions. + expr = expr.replaceAllMapped(_functionRegExp, (m) => m.group(1)!); // Greek letters final greekMap = { @@ -764,10 +753,7 @@ class EquationParser { greekMap.forEach((k, v) => expr = expr.replaceAll(k, v)); // Special case for Delta/delta as prefix for change (Delta t) - expr = expr.replaceAllMapped( - RegExp(r'(\b[Dd]elta)\s+([a-zA-Z])'), - (m) => '${m[1]}${m[2]}', - ); + expr = expr.replaceAllMapped(_deltaRegExp, (m) => '${m[1]}${m[2]}'); // Implicit Multiplication @@ -777,10 +763,7 @@ class EquationParser { // Normalize standalone 'i' to 'IN' (imaginary unit internal token) // This ensures consistency between extractVariables and substitution - expr = expr.replaceAllMapped( - RegExp(r'(? 'IN', - ); + expr = expr.replaceAllMapped(_iRegExp, (m) => 'IN'); return expr; }