| 451 | |
| 452 | /* Return an expression for a match/replace part of a rule if a LaTeX string |
| 453 | or MathJSON expression. |
| 454 | |
| 455 | When `autoWildcard` is true (default for string rule parsing), single-character |
| 456 | symbols are automatically converted to wildcards (e.g., 'a' -> '_a'). This is |
| 457 | appropriate when parsing rule strings like "a*x -> 2*x" where pattern matching |
| 458 | is expected. |
| 459 | |
| 460 | When `autoWildcard` is false (default for object rules), symbols are kept as |
| 461 | literals. This allows `.replace({match: 'a', replace: 2})` to match the literal |
| 462 | symbol 'a' rather than acting as a wildcard. |
| 463 | */ |
| 464 | function parseRulePart( |
| 465 | ce: ComputeEngine, |
| 466 | rule?: string | ExpressionInput | RuleReplaceFunction | RuleFunction, |
| 467 | options?: { canonical?: boolean; autoWildcard?: boolean } |
| 468 | ): Expression | undefined { |
| 469 | if (rule === undefined || typeof rule === 'function') return undefined; |
| 470 | if (typeof rule === 'string') { |
| 471 | let expr = |
| 472 | ce.parse(rule, { |
| 473 | form: options?.canonical ? 'canonical' : 'raw', |
| 474 | }) ?? ce.expr('Nothing'); |
| 475 | // Resolve literal `e` / `i` to their canonical constant (see |
| 476 | // `resolveRuleConstant`). A no-op when `options.canonical` is true: in |
| 477 | // that case `ce.parse` already canonicalized the constant at parse time. |
| 478 | expr = normalizeRuleConstants(expr); |
| 479 | // Recover explicit wildcards (`_a`, `__a`) that the lenient LaTeX parser |
| 480 | // fragments into InvisibleOperator/Subscript shapes (see |
| 481 | // `resolveWildcardShorthand`). |
| 482 | expr = expr.map(resolveWildcardShorthand, { canonical: false }); |
| 483 | // Only auto-wildcard when explicitly requested (e.g., when parsing |
| 484 | // rule strings like "a*x -> 2*x"). For object rules, keep symbols literal. |
| 485 | if (options?.autoWildcard) { |
| 486 | expr = expr.map( |
| 487 | (x) => { |
| 488 | // Only transform single character symbols. Avoid \pi, \imaginaryUnit, etc.. |
| 489 | if (isSymbol(x) && x.symbol.length === 1) |
| 490 | return ce.symbol('_' + x.symbol); |
| 491 | return x; |
| 492 | }, |
| 493 | { canonical: false } |
| 494 | ); |