( rule: Readonly<BoxedRule>, expr: Expression, substitution: BoxedSubstitution, options?: Readonly<Partial<ReplaceOptions>> )
| 979 | if (options.canonical === true) return 'canonical'; |
| 980 | if (options.canonical === false) return 'raw'; |
| 981 | return options.canonical; |
| 982 | } |
| 983 | |
| 984 | return options?.form; |
| 985 | } |
| 986 | |
| 987 | /** |
| 988 | * Apply a rule to an expression, assuming an incoming substitution |
| 989 | * @param rule the rule to apply |
| 990 | * @param expr the expression to apply the rule to |
| 991 | * @param substitution an incoming substitution |
| 992 | * @param options |
| 993 | * @returns A transformed expression, if the rule matched. `null` otherwise. |
| 994 | */ |
| 995 | export function applyRule( |
| 996 | rule: Readonly<BoxedRule>, |
| 997 | expr: Expression, |
| 998 | substitution: BoxedSubstitution, |
| 999 | options?: Readonly<Partial<ReplaceOptions>> |
| 1000 | ): RuleStep | null { |
| 1001 | if (!rule) return null; |
| 1002 | const requestedForm = normalizeReplaceForm(options); |
| 1003 | |
| 1004 | // eslint-disable-next-line prefer-const |
| 1005 | let { match, replace, condition, id, onMatch, onBeforeMatch, purpose } = rule; |
| 1006 | const because = id ?? ''; |
| 1007 | |
| 1008 | const ce = expr.engine; |
| 1009 | |
| 1010 | const canonicalRequested = |
| 1011 | requestedForm !== undefined && |
| 1012 | requestedForm !== 'raw' && |
| 1013 | requestedForm !== 'structural'; |
| 1014 | |
| 1015 | // If the canonical form of the match loses wildcards, this rule cannot match |
| 1016 | // canonical expressions (they would already be simplified). Skip this rule. |
| 1017 | if ((canonicalRequested || expr.isCanonical) && match) { |
| 1018 | if (canonicalMatchLosesWildcards(match)) return null; |
| 1019 | } |
| 1020 | |
| 1021 | let operandsMatched = false; |
| 1022 | |
| 1023 | if (isFunction(expr) && options?.recursive) { |
| 1024 | const direction = options?.direction ?? 'left-right'; |
| 1025 | let newOps = |
| 1026 | direction === 'left-right' ? expr.ops : [...expr.ops].reverse(); |
| 1027 | |
| 1028 | // Apply the rule to the operands of the expression |
| 1029 | newOps = newOps.map((op) => { |
| 1030 | const subExpr = applyRule(rule, op, {}, options); |
| 1031 | if (!subExpr) return op; |
| 1032 | operandsMatched = true; |
| 1033 | return subExpr.value; |
| 1034 | }); |
| 1035 | |
| 1036 | if (direction === 'right-left') (newOps as Expression[]).reverse(); |
| 1037 | |
| 1038 | // At least one operand (directly or recursively) matched: but continue onwards to match against |
no test coverage detected