* For a simplify-target rule whose canonical match head is in a hot bucket, * return a pre-screened functional rule with an `operators` dispatch hint; * for all other rules return the plain pattern rule unchanged. * * The functional body reproduces `applyRule()`'s pattern-rule sequence for * th
(parts: BoxedRuleParts)
| 599 | * builds, so the `onGuardUndecided` hook payload is unchanged) → `subs()`. |
| 600 | * Returning a `RuleStep` keeps the rule's own `id` in `because` and its |
| 601 | * `purpose` on the step. |
| 602 | */ |
| 603 | function wrapHotHeadRule(parts: BoxedRuleParts): Rule { |
| 604 | const { match, replace, condition, id, purpose } = parts; |
| 605 | |
| 606 | const head = (match as { ops?: ReadonlyArray<Expression> }).ops |
| 607 | ? match.operator |
| 608 | : undefined; |
| 609 | if ( |
| 610 | head === undefined || |
| 611 | head.startsWith('_') || |
| 612 | !HOT_DISPATCH_HEADS.has(head) |
| 613 | ) |
| 614 | return parts as Rule; |
| 615 | |
| 616 | const required = rankRequiredFeatures(requiredFeatures(match), head); |
| 617 | |
| 618 | const replaceFn = (expr: Expression): RuleStep | undefined => { |
| 619 | // 1. Cheap conservative pre-screen |
| 620 | const features = featuresOf(expr); |
| 621 | for (let i = 0; i < required.length; i++) |
| 622 | if (!features.has(required[i])) return undefined; |
| 623 | |
| 624 | // 2. Full pattern match (same options applyRule uses on this channel) |
| 625 | const sub = expr.match(match, { |
| 626 | useVariations: false, |
| 627 | recursive: false, |
| 628 | matchPermutations: true, |
| 629 | }); |
| 630 | if (sub === null) return undefined; |
| 631 | |
| 632 | // 3. Guard conditions (same substitution shape applyRule builds: |
| 633 | // wildcard keys plus their de-prefixed aliases) |
| 634 | if (condition !== undefined) { |
| 635 | const conditionSub = { |
| 636 | ...Object.fromEntries( |
| 637 | Object.entries(sub).map(([k, v]) => [k.slice(1), v]) |
| 638 | ), |
| 639 | ...sub, |
| 640 | }; |
| 641 | try { |
| 642 | if (!condition(conditionSub)) return undefined; |
| 643 | } catch { |
| 644 | return undefined; |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | // 4. Replacement (canonical, as on the engine's simplification channels) |
| 649 | return { |
| 650 | value: replace.subs(sub, { canonical: true }), |
| 651 | because: id, |
| 652 | purpose, |
| 653 | }; |
| 654 | }; |
| 655 | |
| 656 | return { replace: replaceFn, operators: [head], id, purpose }; |
| 657 | } |
| 658 |
no test coverage detected