( def: unknown )
| 321 | * `budget` bounds the TOTAL number of beta-reductions so a self-recursive |
| 322 | * definition (`fact(n) = … fact(n - 1) …`) cannot loop forever, while a finite |
| 323 | * self-composition (`g(g(x))` for a non-recursive `g`) still fully expands — an |
| 324 | * on-path name guard would wrongly stop the inner `g(x)`, leaving `Solve` an |
| 325 | * opaque `g(x)` it reads as "no solutions". `budget` is a single object shared |
| 326 | * by reference across every branch of the traversal, so it is one global cap on |
| 327 | * the TOTAL number of beta-reductions in the whole tree — sibling calls |
| 328 | * (`g(a) + g(b)`) draw down the same counter rather than each getting a fresh |
| 329 | * budget. That shared cap is what bounds a self-recursive definition. |
| 330 | */ |
| 331 | // Generous enough that no realistic expression (a wide system of many function |
| 332 | // calls) is capped, low enough that a self-recursive definition terminates |
| 333 | // quickly. Only genuine runaway recursion reaches it. |
| 334 | const MAX_LAMBDA_INLINE = 1000; |
| 335 | |
| 336 | function inlineLambdaApplications( |
| 337 | expr: Expression, |
| 338 | budget: { n: number } = { n: MAX_LAMBDA_INLINE } |
| 339 | ): Expression { |
| 340 | if (!isFunction(expr)) return expr; |
| 341 | |
| 342 | if (budget.n > 0) { |
| 343 | const reduced = betaReduceLambda(expr); |
| 344 | if (reduced !== undefined) { |
| 345 | budget.n -= 1; |
| 346 | return inlineLambdaApplications(reduced, budget); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | const ops = expr.ops; |
| 351 | const inlined = ops.map((op) => inlineLambdaApplications(op, budget)); |
| 352 | if (inlined.every((op, i) => op === ops[i])) return expr; |
| 353 | return expr.engine.function(expr.operator, inlined); |
| 354 | } |
| 355 | |
| 356 | /** |
| 357 | * Replace symbols bound to a value by that value, except for the names in |
| 358 | * `protect`. |
| 359 | * |
| 360 | * A symbol whose value *contains* the unknown hides it from the solver: |
| 361 | * `Solve(s = 2, w)` with `s := (9 - w²)/4` saw an equation with no `w` in it |
| 362 | * and returned `[]` — which by contract means "proven no solutions". A |
no test coverage detected