( ce: ComputeEngine, e: Entry, matchW: MathJSON, replaceW: MathJSON, guards: ReadonlyArray<GuardSpec>, seedTypes: Record<string, string> )
| 990 | } catch { |
| 991 | /* tolerate */ |
| 992 | } |
| 993 | } |
| 994 | return selfTestScoped(ce, e, matchW, replaceW, guards, seedTypes); |
| 995 | } finally { |
| 996 | ce.popScope(); |
| 997 | } |
| 998 | } |
| 999 | |
| 1000 | function selfTestScoped( |
| 1001 | ce: ComputeEngine, |
| 1002 | e: Entry, |
| 1003 | matchW: MathJSON, |
| 1004 | replaceW: MathJSON, |
| 1005 | guards: ReadonlyArray<GuardSpec>, |
| 1006 | seedTypes: Record<string, string> |
| 1007 | ): SelfTestResult { |
| 1008 | const closures = buildGuardClosures(ce, guards); |
| 1009 | const condition = |
| 1010 | guards.length === 0 ? undefined : (sub: Sub) => closures.every((f) => f(sub)); |
| 1011 | |
| 1012 | // 1. Box the rule (reject: box-error). The match/replace MathJSON is |
| 1013 | // pre-boxed: a bare-string side ('ComplexInfinity', '_x') passed |
| 1014 | // directly to ce.rules would be parsed as a LaTeX rule string. The |
| 1015 | // replace is pre-boxed *canonically* with the typed wildcards in scope |
| 1016 | // — the M2 loader replicates this using the artifact's guard specs. |
| 1017 | let ruleSet: ReturnType<ComputeEngine['rules']>; |
| 1018 | try { |
| 1019 | const replaceExpr = ce.expr(replaceW as never); |
| 1020 | if (!replaceExpr.isValid) |
| 1021 | return { |
| 1022 | ok: false, |
| 1023 | reason: 'box-error', |
| 1024 | detail: `invalid replace: ${replaceExpr.toString()}`.slice(0, 160), |
| 1025 | }; |
| 1026 | // The match is pre-boxed CANONICALLY (not raw): raw boxing leaves |
| 1027 | // literals as structural function expressions (['Rational',1,2] stays a |
| 1028 | // Rational function, not a number) and loses ~100 rules to literal |
| 1029 | // mismatches. Canonicalization is safe here: the match MathJSON is |
| 1030 | // already in canonical shape and step 6 of the pipeline verified that |
| 1031 | // canonicalizing it preserves every wildcard. The M2 runtime loader must |
| 1032 | // do the same (box artifact matches canonically before ce.rules). |
| 1033 | ruleSet = ce.rules( |
| 1034 | [ |
| 1035 | { |
| 1036 | match: ce.expr(matchW as never) as never, |
| 1037 | replace: replaceExpr as never, |
| 1038 | condition, |
| 1039 | id: 'fungrim:' + e.id, |
| 1040 | }, |
| 1041 | ], |
| 1042 | { canonical: true } |
| 1043 | ); |
| 1044 | } catch (err) { |
| 1045 | return { |
| 1046 | ok: false, |
| 1047 | reason: 'box-error', |
| 1048 | detail: String((err as Error)?.message) |
| 1049 | .replace(/\s+/g, ' ') |
no test coverage detected