packages feed

phino 0.0.153 → 0.0.154

raw patch · 103 files changed

+3984/−6688 lines, 103 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- CLI.Parsers: optDeep :: Parser Bool
- CLI.Parsers: optJobs :: Parser Int
- CLI.Types: [_deep] :: OptsMorph -> Bool
- CLI.Types: [_jobs] :: OptsMorph -> Int
- Deps: EvComputed :: Int -> Expression -> Expression -> Evaluation
- Deps: EvDeferred :: Int -> Int -> Judgment -> Expression -> Maybe Expression -> Expression -> Evaluation
- Deps: namedCarry :: Expression -> Expression -> Named a -> Named a
- Deps: renumbered :: Int -> Int -> Evaluation -> Evaluation
- Evaluate: fired :: Maybe Attribute -> Expression -> Expression -> State -> ReduceContext -> IO (Maybe Expression, State)
- Morph: Refused :: Acyclic -> Expression -> State -> Refused
- Morph: [_deep] :: ReduceContext -> Bool
- Morph: [_fire] :: ReduceContext -> FiringFunc
- Morph: [_jobs] :: ReduceContext -> Int
- Morph: admitted :: Expression -> ReduceContext -> IO (Either (Acyclic, Expression) ReduceContext)
- Morph: data Refused
- Morph: instance GHC.Exception.Type.Exception Morph.Refused
- Morph: instance GHC.Exception.Type.Exception Morph.Severed
- Morph: instance GHC.Show.Show Morph.Refused
- Morph: instance GHC.Show.Show Morph.Severed
- Morph: refused :: ReduceContext -> Refused -> IO (Maybe Expression, State)
- Morph: type FiringFunc = Maybe Attribute -> Expression -> Expression -> State -> ReduceContext -> IO (Maybe Expression, State)
- Pool: pooled :: Int -> [IO a] -> (b -> a -> IO b) -> b -> IO b
- Tau: tausOf :: Int -> IO (IO Text)
+ CLI.Parsers: optMaxLoops :: Parser Int
+ CLI.Types: [_maxLoops] :: OptsMorph -> Int
+ Lambdas: [_evaluated] :: Lambda -> [(Meta, Meta)]
+ Morph: [_maxLoops] :: ReduceContext -> Int
- CLI.Types: OptsDataize :: LogLevel -> Int -> IOFormat -> IOFormat -> SugarType -> Bool -> LineFormat -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Int -> Bool -> Bool -> Maybe Acyclic -> Bool -> Int -> Int -> Int -> Maybe Int -> Maybe Int -> Int -> Maybe Int -> Maybe Int -> [String] -> [String] -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe FilePath -> Maybe FilePath -> Maybe Int -> Bool -> Maybe FilePath -> Maybe FilePath -> OptsDataize
+ CLI.Types: OptsDataize :: LogLevel -> Int -> IOFormat -> IOFormat -> SugarType -> Bool -> LineFormat -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Int -> Bool -> Bool -> Maybe Acyclic -> Int -> Bool -> Int -> Int -> Int -> Maybe Int -> Maybe Int -> Int -> Maybe Int -> Maybe Int -> [String] -> [String] -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe FilePath -> Maybe FilePath -> Maybe Int -> Bool -> Maybe FilePath -> Maybe FilePath -> OptsDataize
- CLI.Types: OptsMorph :: LogLevel -> Int -> IOFormat -> IOFormat -> SugarType -> Bool -> LineFormat -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Int -> Bool -> Bool -> Bool -> Int -> Maybe Acyclic -> Bool -> Int -> Int -> Int -> Maybe Int -> Maybe Int -> Int -> Maybe Int -> Maybe Int -> [String] -> [String] -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe FilePath -> Maybe FilePath -> Maybe Int -> Bool -> Maybe FilePath -> Maybe FilePath -> OptsMorph
+ CLI.Types: OptsMorph :: LogLevel -> Int -> IOFormat -> IOFormat -> SugarType -> Bool -> LineFormat -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Int -> Bool -> Bool -> Maybe Acyclic -> Int -> Bool -> Int -> Int -> Int -> Maybe Int -> Maybe Int -> Int -> Maybe Int -> Maybe Int -> [String] -> [String] -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe FilePath -> Maybe FilePath -> Maybe Int -> Bool -> Maybe FilePath -> Maybe FilePath -> OptsMorph
- Deps: EvLooped :: Int -> Judgment -> Acyclic -> Expression -> Expression -> Maybe (Int, Maybe Expression) -> Evaluation
+ Deps: EvLooped :: Int -> Judgment -> Acyclic -> Expression -> Expression -> Evaluation
- Deps: EvTerm :: Int -> Text -> Expression -> Expression -> Evaluation
+ Deps: EvTerm :: Int -> Text -> Judgment -> Expression -> Expression -> Evaluation
- Lambdas: Lambda :: Text -> [(Meta, Expression)] -> [(Meta, Expression)] -> [(Meta, (Meta, [Rule]))] -> [(Meta, Expression)] -> [(Meta, (Meta, Meta))] -> Bool -> [Text] -> Expression -> Lambda
+ Lambdas: Lambda :: Text -> [(Meta, Expression)] -> [(Meta, Expression)] -> [(Meta, Meta)] -> [(Meta, (Meta, [Rule]))] -> [(Meta, Expression)] -> [(Meta, (Meta, Meta))] -> Bool -> [Text] -> Expression -> Lambda
- Morph: Memo :: IORef (Store (Int, Kept)) -> IORef (Map Firing Answer) -> IORef Int -> IORef Int -> IORef (Set (Expression, Attribute)) -> Memo
+ Morph: Memo :: IORef (Store (Int, Kept)) -> IORef (Map Firing Answer) -> IORef Int -> IORef Int -> Memo
- Morph: ReduceContext :: Expression -> Expression -> Maybe Expression -> Int -> Int -> Steps -> Maybe Tally -> IORef Int -> Maybe Deadline -> Maybe Memo -> Int -> Maybe (Int, Judgment, Expression) -> Bool -> Bool -> Bool -> Bool -> Int -> Maybe Acyclic -> Judgment -> [Text] -> Seen -> Lambdas -> BuildTermFunc -> ReductionFunc -> EvaluationFunc -> FiringFunc -> SaveStepFunc -> SaveEvalFunc -> Engine -> ReduceContext
+ Morph: ReduceContext :: Expression -> Expression -> Maybe Expression -> Int -> Int -> Steps -> Maybe Tally -> IORef Int -> Maybe Deadline -> Maybe Memo -> Int -> Maybe (Int, Judgment, Expression) -> Bool -> Bool -> Bool -> Maybe Acyclic -> Int -> Judgment -> [Text] -> Seen -> Lambdas -> BuildTermFunc -> ReductionFunc -> EvaluationFunc -> SaveStepFunc -> SaveEvalFunc -> Engine -> ReduceContext

Files

README.md view
@@ -34,2606 +34,2291 @@  ```bash cabal update-cabal install --overwrite-policy=always phino-0.0.151-phino --version-```--Or download binary from the internet using [curl](https://curl.se/) or-[wget](https://en.wikipedia.org/wiki/Wget):--```bash-sudo curl -o /usr/local/bin/phino http://phino.objectionary.com/releases/macos-15/phino-latest-sudo chmod +x /usr/local/bin/phino-phino --version-```--Download paths are:--* Ubuntu 22.04: <http://phino.objectionary.com/releases/ubuntu-22.04/phino-latest>-* Ubuntu 24.04: <http://phino.objectionary.com/releases/ubuntu-24.04/phino-latest>-* MacOS (ARM): <http://phino.objectionary.com/releases/macos-15/phino-latest>-* MacOS (Intel): <http://phino.objectionary.com/releases/macos-14-large/phino-latest>-* Windows: <http://phino.objectionary.com/releases/windows-2022/phino-latest.exe>--In a [GitHub Actions][gha] workflow, one step downloads the binary-for the runner and puts it on the `PATH`:--```yaml-- uses: objectionary/phino@0.0.151-  with:-    version-file: phino-version.txt-- run: phino --version-```--The file holds the version to install, such as `0.0.150`, and the-whitespace around it is ignored.-Give `version: 0.0.150` instead of `version-file` to write the version-in the workflow itself.-The step fails if no binary is released for the runner, such as-Linux on ARM, or if `phino --version` prints another version.--## Build--To build `phino` from source, clone this repository:--```bash-git clone git@github.com:objectionary/phino.git-cd phino-```--Then, run the following command (ensure you have [Cabal][cabal] installed):--```bash-cabal build all-```--Next, run this command to install `phino` system-wide:--```bash-sudo cp "$(cabal list-bin phino)" /usr/local/bin/phino-```--Verify that `phino` is installed correctly:--```bash-$ phino --version-0.0.0-```--You can ensure scripts are run with a specific version of `phino` using-the `--pin` global option. It exits with an error when the version supplied-doesn't match the installed one:--```bash-phino --pin=0.0.0.67 dataize hello.phi-```--The `--pin-file` global option does the same, but reads the version from-a file, ignoring the whitespace around it:--```bash-phino --pin-file=version.txt dataize hello.phi-```--## Dataize--Then, you dataize the expression:--```bash-$ phino dataize hello.phi-68-65-6C-6C-6F-```--### Symbolic λ functions--Which λ functions exist is a property of the object model being dataized, not-of the calculus, so `phino` implements none of them. They come from a YAML file-given with `--symbolic`, one entry per λ function:--```yaml-- λ: L_number_(plus|times)-  dataize:-    𝛿1: $.ρ-    𝛿2: $.x-  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-```--The `λ` of an entry is the λ names it answers for, as a regular expression, so-the one above stands for `L_number_plus` and `L_number_times`. Under `dataize`-stand the operands it brings down to data through 𝔻, each binding a bytes meta-`𝛿1`, `𝛿2` and so on; under `morph` the operands it reduces to a normal-form through 𝕄, each binding an expression meta `𝑛1`, `𝑛2`. Both blocks are-terms of the calculus, read against the formation being fired, so `ξ` is that-formation and `$.x` its `x`, while `Φ` is the universe. Every entry numbers its-own metas from `𝛿1` and `𝑛1`, and the entry is what tells two `𝛿1` apart.--The term under `𝑛` is what the firing answers with. `phino` normalizes it-exactly as it normalizes anything else, so `--protocol`, `--partial` and-`--max-steps` work on it unchanged. It may name any meta the entry bound,-those of the two blocks below among them.--### Rewriting a term into another shape--There is a third block, `rewrite`, and it reduces nothing. Each line of it-takes a term another meta of the entry is already bound to, named under `of`,-applies a list of ordinary rules to it, spelled with the very `pattern`,-`result`, `when` and meta variables a rule file uses, and binds an expression-meta of its own to the outcome:--```yaml-- λ: L_fork-  dataize:-    𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  rewrite:-    𝑛3:-      of: 𝑛1-      rules: &bool-        - name: false-literal-          pattern: >--            ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅,-            φ ↦ ξ.right ⟧ ), !B2 ⟧-          result: >--            ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅,-            φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ) ⟧-    𝑛4:-      of: 𝑛2-      rules: *bool-  𝑛: 𝑛3-```--The meta under `of` is one bound by `morph` or by a `rewrite` line above it,-and nothing else. A rule is tried at every position of the term, the outermost-first; the first rule whose pattern matches a position rewrites it, and a-rewritten position is not walked into again, so a rule whose result carries-its own pattern never loops. Nothing is normalized afterwards: a rewrite is a-substitution the entry vouches for and not a reduction, exactly as an answer-is. A rule writing a `𝜎` into its result, or reading a meta its pattern never-binds, is refused where the file is read.--This is how a program brings two branches of a fork to one shape before they-are compared. In EO a `Φ.false` is written `φ ↦ ξ.right` while a bool a firing-answered carries a symbol, and the two are one value in two spellings that-only the program knows to be one. The block runs before `symbolize` on purpose:-a result may write the datum a literal stands for, as `⟦ Δ ⤍ 00- ⟧` above, and-that datum is then stood into a known symbol the join can pair.--### Standing data into unknowns--There is a fourth block, `symbolize`, and it reduces nothing. It takes a term-another meta of the entry is already bound to and binds an expression meta of-its own to that same term with every datum in it standing for an unknown:--```yaml-- λ: L_fork-  dataize:-    𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  symbolize:-    𝑛3: 𝑛1-    𝑛4: 𝑛2-  𝑛: 𝑛3-```--The right-hand side of a line names a meta bound by `morph`, by `rewrite` or by-a `symbolize` line above it, and nothing else; a term nobody reduced has no data-to stand. Every `Δ ⤍ b` binding of that term becomes a `λ ⤍ 𝜎k` naming a-fresh symbol, one per occurrence, so `⟦ Δ ⤍ b ⟧` reads as `⟦ λ ⤍ 𝜎k ⟧` and a-literal tuple gets several. A term carrying no datum passes through as it was.--Only the `φ` chain is walked. A term carries the value it stands for where-that chain ends, so a datum standing anywhere else says nothing about the term-and is left alone, the whole subtree of it. What sits under `ρ` belongs to the-object around this one, and a normal form drags the universe it was reduced-inside along under `ρ`, so a walk reaching into it would stand the data of the-whole program into unknowns to say one thing about one term. What sits under a-method is code and not data: the `-1` of a `neg ↦ ⟦ φ ↦ ξ.ρ.times( -1 ) ⟧`-nobody has called is the body of a method, and minting a symbol for it, and for-every other literal every method of the carrier declares, would write unknowns-nobody ever reads.--This is what lets an entry compare two branches of a fork. A literal is sugar-for `Φ.number( Φ.bytes( ⟦ Δ ⤍ … ⟧ ) )`, so a branch computed from a literal-keeps a datum three levels down where a branch computed from an unknown keeps-`⟦ λ ⤍ 𝜎 ⟧`. A `Δ` against a `λ` is a difference in kind and not in value, and-after the stage both branches carry `⟦ λ ⤍ 𝜎 ⟧` where they differ.--### Joining the branches of a fork--A branching λ function answers neither of its branches. Which one the program-takes is decided by a value nobody worked out, so handing one of them through-would drop the branch point from the program altogether and a reader would see-the condition computed and thrown away. `join` is the fifth block, and it-reduces nothing either: it takes two metas the entry has bound already and-binds one of its own to the two terms joined into one.--```yaml-- λ: L_fork-  dataize:-    𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  symbolize:-    𝑛3: 𝑛1-    𝑛4: 𝑛2-  join:-    𝑛5: [𝑛3, 𝑛4]-  𝑛: 𝑛5-```--A line names two metas bound by `morph`, by `rewrite`, by `symbolize` or by a-`join` line above it, and never three: it stands for a choice between two-branches, and a walk over three terms in parallel is no such choice. The meta it-binds is one like any other, so the answer may name it alone, as above, or stand-it inside a larger term.--`phino` takes the two terms and requires them to match verbatim, with one-exception: where `⟦ λ ⤍ 𝜎A ⟧` in one meets a different `⟦ λ ⤍ 𝜎B ⟧` in the-other, it mints a fresh `𝜎C` and stands it there. The same symbol on both sides-stays as it is, and the same pair met again further down gets the same fresh-symbol, since it is one choice however often the two terms differ by it; two-different pairs get two fresh symbols. Two identical terms join into that same-term and nothing is minted at all. The join keeps the type by construction,-being the terms' own shape, so the file needs to know nothing about carriers.--Only the `φ` chain is compared, exactly as `symbolize` stands only that chain-into unknowns: a term carries the value it stands for where its `φ` chain ends,-so every other binding is taken from the first branch, the whole subtree of it,-and never compared at all. The two branches of a fork reach their normal forms-in scopes of their own, so their `ρ` differ wherever the reduction left a-trace, and comparing them would refuse the join over something saying nothing-about either branch; a method is the same, its body being code nobody has-called, so two branches differing inside one are not two values. The joined-term keeps the methods and the `ρ` of the first of the two, being of its shape,-which is what lets the program go on dispatching on what the fork answered.--A join is only ever between two expressions and a datum is never joined with-anything, which is why `symbolize` runs before it: a known symbol, one that-stage minted for a datum, is a symbol like any other here, so a literal branch-joins with a computed one and two literal branches join too. Any other-difference — a datum against a symbol, two different data, a binding one term-carries and the other does not — is no join at all, and the firing gets stuck-the way a λ function no entry answers does, so `--partial` parks it rather than-aborting the run. A fork whose branches differ in structure, such as a `Φ.true`-and a `Φ.false` written as `φ ↦ ξ.left` against `φ ↦ ξ.right`, is stuck, and-bringing two such branches to one shape is the program's job and not `phino`'s,-which its entry does in a `rewrite` block.--A bare symbol, `⟦ λ ⤍ 𝜎A ⟧` with no carrier around it, is what the deep walk-answers a copy it deferred with (see Deep morphing below), and it joins with-a formation too. It has no `φ` chain of its own, only the symbol, so the join-pairs `𝜎A` with the symbol the `φ` chain of the other term ends in, mints a-fresh `𝜎C` for that pair the usual way, and answers the bare `⟦ λ ⤍ 𝜎C ⟧`.-The methods of the formation are dropped, since the other branch may have-none of them. A formation whose `φ` chain ends in a datum, or which has no `φ`-at all, does not join with a bare symbol.--A fork whose one branch calls a method often meets a decorator. In-`if. (value.gte 0) (0.plus value) value.neg` the first branch comes to a-number `X`, while `value.neg` is a method, `times -1 > @`, and comes to-`⟦ φ ↦ X ⟧`, with the number one level down. The two differ in shape, so the-join is refused. An entry that says `lenient: true` beside its `join` block-lets it through:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-    𝑛2: $.right-  join:-    𝑛3: [𝑛1, 𝑛2]-  lenient: true-  𝑛: 𝑛3-```--Every `join` line of such an entry joins a term with a formation whose `φ` is-that term, in either order, and answers `⟦ φ ↦ X ⟧`, with symbols minted inside-`X` the usual way. Where both terms are formations, the one with the longer `φ`-chain is taken for the decorator. The answer drops the `ρ` and the other-attributes of the decorator, so it is not exactly the object either branch-made, which is why an entry is strict unless it says otherwise. Dataization-still reaches the same data through it. An entry that says `lenient: true` and-has no `join` line is refused when the file is read.--A lenient entry also joins a bare symbol with a term that holds exactly one-symbol anywhere inside. In `bool.and` one branch comes to a bare-`⟦ λ ⤍ 𝜎1 ⟧` and the other to `⟦ φ ↦ Φ.bool( if ↦ 𝑘 ) ⟧`, where `𝑘` is-`⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧`. The `φ` of the second is a call, so its-`φ` chain ends in no symbol and a strict join stops there. A lenient one-answers a copy of the second term with `𝜎2` replaced by a fresh `𝜎3`, which-stands for either `𝜎1` or `𝜎2`. The copy keeps every binding of the term, `ρ`-included. A term with no symbol, or with two, is still refused.--One term being `⊥` is the exception, since `if. cond value ⊥` is how EO spells-"raise unless `cond`": the program raises on that side of the condition and-has a perfectly good value on the other. The join then mints nothing, binds-its meta to the other term as it stands and writes on which side the program-raises, naming the condition by what the first `dataize` operand of the entry-came down to, as `terminate(𝔻(𝜎2:λ), right)  # 𝑛4` in the text format and-`<terminate symbol="𝜎2" branch="right"/>` in the markup. The deep walk fires-such a fork too, since that `⊥` is an argument the program wrote rather than-one the reduction made.--EO raises with an object too, a copy of `⟦ message ↦ ∅, λ ⤍ L_throw ⟧`, whose-λ function the file leaves out on purpose, so that an error of the program-stays an error at runtime. An entry names such functions under `terminators`:--```yaml-- λ: L_fork-  dataize:-    𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  join:-    𝑛3: [𝑛1, 𝑛2]-  terminators: [L_throw]-  𝑛: 𝑛3-```--A `join` line of such an entry takes a formation with `λ ⤍ L_throw`, or an-application of one, for a branch that raises, exactly as it takes `⊥`: it binds-its meta to the other term and writes `terminate(…)`. Under `--deep` the walk-reduces the message of that copy like any other code, records and all, and-never fires `L_throw` itself, since no entry answers it. The record names the-reduced `message` as its last argument, as-`terminate(𝔻(𝜎2:λ), right, 𝑛·4·6)  # 𝑛4` in the text format and-`<terminate symbol="𝜎2" branch="right" message="𝑛·4·6"/>` in the markup, so-the error path can be lowered as well as the other branch. A message the walk-cannot reduce ends the way any operand does: a call no entry answers stays as-it is written, and a firing that gets stuck fails the run, unless `--partial`-is given. A raising term with no `message` names none. A name under-`terminators` that a key of the file matches is refused when the file is read,-since that function fires rather than raises.--Every symbol a join mints is written into the protocol as a fact of its own,-so a reader ties it to the two it stands for without diffing the terms; the-section on `--protocol` below shows one.--### Symbols--An entry answers, it never computes. The job of these functions is symbolic-morphing: what `5.plus( 6 )` comes to is the arithmetic of the object model and-not `phino`'s, so an entry answers a term carrying a symbol standing for a-value nobody worked out, and the data its `dataize` operands came down to is-not its to read. An answer mentioning a `𝛿` is refused where the file is read.--`𝜎` is a meta of the calculus, beside `𝑛`, `𝛿` and `𝑓`, and it stands where-a λ name stands. In a term, `𝜎1` is a concrete symbol: a λ function nothing-answers, which is what makes the value the term carries unknown. Firing it is-therefore the same question as firing a λ name the `--symbolic` file does not-carry, and gets the same answer: 𝔼 stops there, the protocol records the site as-`unanswered(𝜎1)`, and `--partial` leaves the term where it stands. Dispatching-an attribute off a symbol — `⟦ λ ⤍ 𝜎1 ⟧.plus( 5 )` — therefore taints its own-binding and nothing else; what stands beside it still computes. In an answer, a-bare `𝜎` asks for a fresh one, minted as the firing happens and numbered by the-run, so no two unknowns are ever spelled alike. Minting starts after the symbols-the program already carries, so a run over the 𝜑-program an earlier run wrote-never spells a fresh symbol like one already standing there.--Dataizing a symbol never gets stuck. It answers a fixed datum, 42-(`40-45-00-00-00-00-00-00`), the same one for every symbol, so 𝔻 always-answers, a `𝛿` always holds concrete data and no firing ever declines for the-lack of it:--```bash-$ cat sum.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-  φ ↦ 5.plus( 6 )-⟧-$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho sum.phi-40-45-00-00-00-00-00-00-```--### The keys of the file--Each `λ` is a regular expression, and it must match the whole λ name, so a-plain name such as `L_number_plus` means that one function and nothing else,-while `L_number_.*` stands for every function of `number`. The keys are unique:-nothing tells two entries under one key apart, so a second entry under a key is-unreachable and the file is refused rather than merely redundant.--A λ name no key matches has no λ function at all, so 𝔼 gets stuck on it.-Without `--symbolic` there is no entry at all and every λ function gets stuck:--```bash-$ phino dataize --sweet --hide-rho sum.phi-[ERROR]: No entry of --symbolic answers the λ function 'L_number_plus'-```--The file is read before anything is parsed or reduced, so a key that is no-regular expression, an operand that is no meta of the kind its block binds, or-an answer the calculus cannot read fails the run up front rather than half-way-through a derivation.--### Recording what fired--Every λ function fired on the way to the answer may be recorded in a-machine-readable protocol, with the `--protocol` option. The protocol is a-tree: the run at the top, one block per firing under it, and inside the block-the operands the firing bound and the term it answered with. A judgment started-at a new site opens a block too, and what it does stands under it.--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --symbolic=atoms.yaml --protocol=atoms.txt --quiet \-    --sweet --hide-rho sum.phi-$ cat atoms.txt-𝔻(Φ):-  𝕄(Φ):-    𝑛·0·1 := 5-    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )-  𝔼(L_number_plus):-    𝔻(Φ.a🌵0):-      𝕄(Φ.a🌵0):-        𝑛·1·1 := 5-        𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-      𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-    𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-    𝔻(Φ.a🌵1):-      𝕄(Φ.a🌵1):-        𝑛·1·3 := 6-        𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0-    𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-    𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛-    𝕄(𝑛·1·5):-      𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-```--<!-- markdownlint-enable MD013 -->--`𝔻(…)` is the run and the term it was aimed at, `𝕄(…)` where the run is a-morphing, and `𝔼(…)` is one firing, named by the entry that answered it. A line-standing in a block needs no comment to say which judgment made it and where:-the block it stands in says that already. A comment is written only where it-says something the block does not, such as the term an operand was reduced-from, `𝔻(ξ.ρ)`, or the meta an answer was read from, `𝑛`, and every comment-naming a judgment is of that shape: a judgment applied to a term, which is the-intent the value beside it came from. The firings are numbered across the-whole run, in the order they open, so `𝛿1·2` is the value bound to `𝛿1` by-the second firing of the run, whichever λ function that was, `𝑛1·2` the same-for a `morph` meta, and `𝑛·3·2` the answer of the third firing, so-`𝑛1·2 := 𝑛·3·2` reads "the `𝑛1` of this firing is what the third firing-answered". One firing binds a meta once and no two firings-share a number, so every one of these names stands on exactly one line of the-file and a line naming another one points at it and no other.--A nested `𝔻(…):` or `𝕄(…):` line opens a block where a judgment starts at a-site other than the one of the block it stands in, and names that site. What-the judgment does stands one level deeper under it: the objects its-normalization makes, the firings it demands, and the judgments it starts in-turn. A reader therefore sees which judgment made each line and at which site,-rather than a flat list of lines each saying so. 𝔻 of a term that is no-formation asks 𝕄 for the normal form of the same term first, through its-`norm` rule, so a `𝕄(…):` block often stands right inside a `𝔻(…):` block of-the same site, and `𝑛·0·1 := 5` above is made by that 𝕄 and not by 𝔻. A-judgment started at the site of the block it stands in, and by the same-judgment, opens nothing, and what it does joins that block. A block is written-only once a line stands in it, so a judgment that made nothing worth a line-leaves no empty heading behind. The line is no firing: it binds no meta and-takes no number, so the metas of the firings under it are numbered as if it-were not there. A `𝔻(…):` block ends with the datum the `delta` rule found,-such as `𝛿·1·1 := 40-14-00-00-00-00-00-00`, named by the firing it stands in-and a counter of its own, and the operand line right after the block names it.-A datum of eight bytes is commented with the number those bytes hold as an-IEEE 754 double, `# 5.0` here, since that is how a number keeps its data.-In the run above 𝔻 gets into each number the entry brings down, and through-its `φ` into the bytes that number holds, in the same block, since the site is-the same.--An answer stands on two lines and not one. A firing answers the term its entry-wrote and `phino` morphs that term before standing it back into the program, so-`𝑛·1·5` is what the entry wrote, with the symbols this firing minted already in-it, commented with `𝑛` to name the key it was read from, and `𝑛·1·7` is the-normal form 𝕄 made of it, commented with `𝕄(𝑛·1·5)` to say where it came from.-That morphing opens a block of its own, `𝕄(𝑛·1·5):`, named after the term it-started from, since that term stands nowhere in the program a locator could-reach. It is the same morphing every other term goes through, and writing only its-outcome would have the formation of `number` appear in place of the three-tokens the entry wrote with nothing saying why. Whatever that morphing fires-or applies writes its lines between the two, exactly where a firing an operand-took opens its block, so the order the lines come in is the order the work was-done in.--`𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )` is an object an application made. The `copy`-rule of normalization fills a void of a formation with the argument it was-given, and so makes a new object, whatever judgment is running and wherever in-the term the application stands. The line binds that object to a fresh `𝑛` and-spells the application: the object applied on the left and the argument it got-on the right, so `5` is `Φ.number( φ ↦ … )` in sugar. The `ρ` a dispatch gives-the attribute it takes writes no line. The block the line stands in names the-judgment that normalized and the site it stood at. The name is counted with the-metas of the firing the line stands in, `𝑛·1·5` before it and `𝑛·1·7` after it,-and an object made outside every firing is counted under `𝑛·0`. From then on-every line spells the object by that name rather than as the formation it is,-and the application by the same name, since `copy` makes the same object of the-same application wherever it stands: `𝑛·1·1` is the object `5` made,-`𝑛·0·1.plus( x ↦ 6 )` is the `plus` of the first `5` applied to `6`, one line-for one call, and `𝑛·1·7 := 𝑛·1·6` says the firing answered with the object 𝕄-made of what the entry wrote. Such a line names its head and its argument that-way, each on its own, and never the application as a whole, so a line spells out-every application, even one making an object an earlier one already made, the-way `𝑛·1·1 := 5` does, and a later line names the latest of them. Only an object-still standing as it was made is named: once a rule rewrote a part of it, it is-another object and it is spelled out again. The walk of `--deep` is no such-rule. It stands its answers in the place of what it computed, inside the-bindings of the object it walks, and makes no new object by doing so, so the-object keeps its name once the walk is done with it.--Where an operand came down to the datum a symbol stands for, the protocol writes-`𝔻(𝜎1:λ)` in place of that 42 (`𝜎1:λ` is the formation `⟦ λ ⤍ 𝜎1 ⟧`, in the-sugar every sweet term is written with), so a reader sees that the value was-manufactured rather than read out of the program. A `𝜎` is the name of a λ-function and no term of its own, so 𝔻 is applied to the formation carrying it-and never to the name alone.--A `symbolize` line writes a line per fresh symbol it minted, ahead of the line-binding the term that carries them, and that line is a fact and no assignment:-`𝔻(𝜎44:λ) == 3F-F0-00-00-00-00-00-00` says that dataizing the formation-`𝜎44` names answers those bytes. Nothing binds bytes to a `𝜎`, since it is-neither a datum nor a term. A consumer reading the protocol back treats a-symbol with such a fact as a constant and every other symbol as an unknown.--The line binding the term of a `symbolize` one is commented with the meta it-was told to stand, `𝑛3·1 := ⟦ λ ⤔ 𝜆8 ⟧  # 𝑛1`, and with no judgment-around it: standing the data of a term into unknowns is the file's own-operation and nothing of the calculus runs there, so the line names a meta of-the entry the way a `join` line names the two it joined. A comment carries the-letter of a judgment exactly where a judgment made the value and the block-around the line does not already say so.--The site of a firing is a locator, and the block a firing stands in names it,-under the letter of the judgment that asked for the firing: 𝔼 is fired by the-`ml` rule of morphing and by the `fire` rule of dataization, so a firing in the-block `𝕄(Φ.demo.a.φ):` is a λ function fired while 𝕄 was reducing that-binding, and one standing right in `𝔻(Φ):` was fired because dataization-demanded data of `Φ`. A chain such as `5.plus( 6 ).plus( 7 )` writes both: the-inner call is fired inside the `𝕄(Φ):` block, while 𝕄 reduces the head of the-outer dispatch, and the outer one right in `𝔻(Φ):`, because 𝔻 asked for the-data. A line standing right in a firing has no block of its own around it, so-a line there that a judgment wrote at a site no block names, such as-`starved(…)` below, carries that judgment and site as its comment. The site-itself is where in the program the firing-belongs: the term the run was aimed at, so `Φ` for a run that was aimed at-nothing in particular, and, under `--deep`, the binding the walk had entered-when the λ function fired, since that walk reduces every part of the program-in turn and one entry answers the same way wherever it is fired. A locator-names a binding and reaches no further, so a firing standing deeper inside a-term than that — under a dispatch, or in the argument of an application — is-written under the last binding the walk entered, which is the smallest part of-the program a reader can aim a run of their own at. An operand of a firing is-reduced bound to a synthetic attribute of the universe (see `--inside` below),-so a λ function fired while it came down is written under that attribute and-not under the site of the firing that asked for it.--An operand line ends in the judgment that reduced it and the term it was-reduced from, written as a comment after two spaces and `#`. The value alone-says what the meta was bound to and neither what it was bound from nor what-was done to it, so `𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)` reads "the `𝛿1` of this firing-is the `ρ` of the formation brought down through 𝔻, and it came down to the-datum `𝛿·1·1` above", where a `morph` operand reads `𝑛1·5 := 𝑛·3·4  # 𝕄(ξ.then)`-and says that the `then` of the formation reached its normal form through 𝕄.-Which of the two judgments ran is the whole difference between a line ending-in data and one ending in a term. It is the very term the entry wrote under-that meta, spelled the way the calculus reads it — `$` is read as `ξ` — so a-reader never has to open the `--symbolic` file beside the protocol and match-every line by λ name and meta number.--`unanswered(…)` is a λ name no entry answers, standing where the block of its-firing would have stood. Nothing fired, so nothing opens under it. The line is-commented with the judgment that asked and the formation it was asking about,-`𝕄(L_none:λ)`, which no block names, the way an operand line is commented-with the term it was reduced from: 𝔼 is fired by the `ml` rule of morphing-and by the `fire` rule of dataization, so the letter says where in the-reduction the site stands and the term says which object the λ function that-could not fire belongs to. It is written whether or not `--partial` goes on-to park the run, since the protocol records what 𝔼 was asked for, and a-question it could not answer belongs there as much-as one it could — once per site and not once per attempt, since a site-`--partial` parks stays in the residue and `--deep` walks over it again:--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --symbolic=atoms.yaml --protocol=atoms.txt --quiet \-    --sweet --hide-rho stuck.phi-[ERROR]: No entry of --symbolic answers the λ function 'L_number_nope'-$ cat atoms.txt-𝔻(Φ):-  𝕄(Φ):-    𝑛·0·1 := 5-    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )-    𝔼(L_number_plus):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := 5-          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·3 := 6-          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0-      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛-      𝕄(𝑛·1·5):-        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )-      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-  unanswered(L_number_nope)  # 𝔻(L_number_nope:λ)-```--<!-- markdownlint-enable MD013 -->--The very same file comes back with `--partial`, where the run answers the-residue instead of failing: what `phino` could not decide is a property of the-program and not of the option that decides what to do about it.--Three more lines say why a firing gave no answer.-`stuck(L_outer)` is the last line of a firing that got stuck, naming the λ-function it got stuck on, which is seldom the one `unanswered(…)` names: that-one is written where it was asked for, and this one closes every firing the-failure passed on its way out.-`stall(L_outer)` stands under a firing that `--acyclic=plausible` answered-with the stall an earlier firing of the same formation kept, so a told stall-never reads as a fresh firing that wrote nothing.-`starved(3)` is where `--max-steps=3` ran out, whether or not `--partial` goes-on to park it. It stands in the block of the judgment and the site the-reduction stood at, and where the budget ran out before that block could open,-right in a firing, it is commented with them instead, `starved(3)  # 𝔻(Φ.a🌵2)`:--```text-𝕄(Φ.x):-  𝕄(Φ.x.arg):-    𝔼(L_outer):-      𝔻(Φ.a🌵0):-        starved(3)-      stuck(L_outer)-  𝔼(L_outer):-    𝔻(Φ.a🌵1):-      𝔼(L_outer):-        starved(3)  # 𝔻(Φ.a🌵2)-    stuck(L_outer)-```--The markup spells them `<unfinished λ="L_outer"/>`, `<stall λ="L_outer"/>`-and `<starved limit="3" by="dataize" at="Φ.a🌵2"/>`.--`timeout(5)` is where `--max-seconds=5` ran out, placed and commented the same-way and written at the first step the deadline refused.-The run ends there, with or without `--partial`, so it is always the last line-of the protocol.-The markup spells it `<timeout limit="5" by="morph" at="…"/>`.--`spent(3)` is where `--max-firings=3` ran out, placed and commented the same-way and written where the firing it refused would have started.-The markup spells it `<spent limit="3" by="dataize" at="…"/>`.--`deferred(𝜎2) := Φ.box( x ↦ 𝜎1:λ )` is a copy the deep walk-deferred instead of entering it. The fresh symbol it answered the copy with-stands in parentheses. The copy is written as a call of the object of the world-it was made of, given the arguments that fill its voids, read. The block it-stands in names the judgment and the site of the walk.-The object is found through the `ρ` of the copy: no `ρ` means `Φ`, a `ρ` that-is a name means that name with its applications erased, and a `ρ` that is a-formation means the object that formation was made of. Among the formations-declared there, the copy was made of the one whose attributes cover its own,-whose voids it fills the most, and which shares the most bindings with it.-When the world declares no such object, or two of them tie, the copy is-written as it stood, such as `⟦ x ↦ 𝜎1:λ, φ ↦ x.next ⟧`, or by the name the-line of the application that made it gave it, such as `𝑛·0·1`.-The markup spells it on one line, broken here for reading. The object stands-in `of` and the arguments in `<with>`, both written whatever `--abridged`-says, and the copy as it stood stands in `<e>`, abridged and named as usual:--```xml-<applied meta="𝑛·0·1" by="morph" at="Φ.y" of="Φ.box"><attr name="x">𝜎1</attr></applied>-<deferred symbol="𝜎2" by="morph" at="Φ.y" of="Φ.box">-  <with><attr name="x">𝜎1</attr></with>-  <e>𝑛·0·1</e>-</deferred>-```--A deferred copy is one the walk did not make: the application that made it-writes its own line, and the `deferred` line says what the walk did-not do with that object.--Every argument of the call is an `<attr>` of `<with>`. An argument that is a-bare symbol, or a carrier of one whose `φ` leads to it, such as-`Φ.number( φ ↦ 𝜎1:λ )` or `𝜎1:λ:φ`, is spelled as that symbol, and any other-argument is spelled `?`. A copy with no object of the world has neither `of`-nor `<with>`, only `<e>`.--Every term is 𝜑 on a single line, whatever `--output` and `--flat` say about-the result of the run, so a program reading the protocol back never has to know-what the run printed. The file is truncated at the beginning of every run, so-it always holds the firings of exactly one run.--The blocks come in the order the reduction walks the term, and that order is-not what the dependencies are read from — the symbols are. Take a comparison-nobody can decide, a fork branching on it, and an `atoms.yaml` of three-entries, the λ functions named briefly to keep the lines below short:--```yaml-- λ: L_plus-  dataize:-    𝛿1: $.ρ-    𝛿2: $.x-  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-- λ: L_gt-  dataize:-    𝛿1: $.ρ-    𝛿2: $.x-  𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )-- λ: L_fork-  dataize:-    𝛿1: $.φ-  morph:-    𝑛1: $.then-    𝑛2: $.else-  join:-    𝑛3: [𝑛1, 𝑛2]-  𝑛: 𝑛3-```--<!-- markdownlint-disable MD013 -->--```bash-$ cat fork.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  bool ↦ ⟦ if ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_plus ⟧, gt(ρ, x) ↦ ⟦ λ ⤍ L_gt ⟧ ⟧,-  foo(x) ↦ ⟦-    φ ↦ ξ.x.gt( 0 ).if( ξ.x.plus( ξ.x.plus( 1 ) ), ξ.x.plus( ξ.x ) ).plus( 5 )-  ⟧,-  demo ↦ ⟦ a ↦ Φ.foo( Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ⟧-⟧-$ phino morph --deep --symbolic=atoms.yaml --locator=Q.demo.a \-    --protocol=fork.txt --quiet --sweet --hide-rho fork.phi-$ cat fork.txt-𝕄(Φ.demo.a):-  𝑛·0·1 := Φ.foo( x ↦ Φ.number( φ ↦ 𝜎1:λ ) )-  𝕄(Φ.demo.a.x):-    𝑛·0·2 := Φ.number( φ ↦ 𝜎1:λ )-  𝕄(Φ.demo.a.φ):-    𝑛·0·3 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·4 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·5 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )-    𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·5 )-    𝑛·0·7 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·8 := 𝑛·0·7.gt( x ↦ 𝑛·0·6 )-    𝔼(L_gt):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·2 := Φ.number( φ ↦ 𝑛·0·5 )-          𝑛·1·3 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )-        𝛿·1·1 := 00-00-00-00-00-00-00-00  # 0.0-      𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)-      𝑛·1·4 := Φ.bool( if(then, else) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )  # 𝑛-      𝕄(𝑛·1·4):-        𝑛·1·5 := Φ.bool( if(then, else) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-      𝑛·1·6 := 𝑛·1·5  # 𝕄(𝑛·1·4)-    𝑛·0·9 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·10 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·11 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·12 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·13 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-    𝑛·0·14 := Φ.number( φ ↦ 𝑛·0·13 )-    𝑛·0·15 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·16 := 𝑛·0·15.plus( x ↦ 𝑛·0·14 )-    𝔼(L_plus):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵3):-        𝕄(Φ.a🌵3):-          𝑛·2·2 := Φ.number( φ ↦ 𝑛·0·13 )-          𝑛·2·3 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-        𝛿·2·1 := 3F-F0-00-00-00-00-00-00  # 1.0-      𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)-      𝑛·2·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛-      𝕄(𝑛·2·4):-        𝑛·2·5 := Φ.number( φ ↦ 𝜎3:λ )-      𝑛·2·6 := 𝑛·2·5  # 𝕄(𝑛·2·4)-    𝑛·0·17 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·18 := 𝑛·0·17.plus( x ↦ 𝑛·2·5 )-    𝔼(L_plus):-      𝔻(Φ.a🌵4):-        𝕄(Φ.a🌵4):-          𝑛·3·1 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝛿2·3 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-      𝑛·3·2 := Φ.number( φ ↦ 𝜎4:λ )  # 𝑛-      𝕄(𝑛·3·2):-        𝑛·3·3 := Φ.number( φ ↦ 𝜎4:λ )-      𝑛·3·4 := 𝑛·3·3  # 𝕄(𝑛·3·2)-    𝑛·0·19 := ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ 𝜎2:λ ⟧( then ↦ 𝑛·3·3 )-    𝑛·0·20 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·21 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·22 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·23 := Φ.number( φ ↦ 𝜎1:λ )-    𝑛·0·24 := 𝑛·0·23.plus( x ↦ 𝑛·0·23 )-    𝔼(L_plus):-      𝔻(Φ.a🌵6):-        𝕄(Φ.a🌵6):-          𝑛·4·1 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿1·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵7):-        𝕄(Φ.a🌵7):-          𝑛·4·2 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿2·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-      𝑛·4·3 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛-      𝕄(𝑛·4·3):-        𝑛·4·4 := Φ.number( φ ↦ 𝜎5:λ )-      𝑛·4·5 := 𝑛·4·4  # 𝕄(𝑛·4·3)-    𝑛·0·25 := ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ 𝜎2:λ ⟧( then ↦ 𝑛·3·3 )-    𝑛·0·26 := 𝑛·0·25( else ↦ 𝑛·4·4 )-    𝔼(L_fork):-      𝛿1·5 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)-      𝑛1·5 := 𝑛·3·4  # 𝕄(ξ.then)-      𝑛2·5 := 𝑛·4·5  # 𝕄(ξ.else)-      𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }-      𝑛3·5 := ⟦ φ ↦ 𝜎6:λ, plus(x) ↦ L_plus:λ, gt(x) ↦ L_gt:λ ⟧  # [𝑛1, 𝑛2]-      𝑛·5·1 := 𝑛3·5  # 𝑛-      𝑛·5·2 := 𝑛3·5  # 𝕄(𝑛·5·1)-    𝑛·0·27 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-    𝑛·0·28 := Φ.number( φ ↦ 𝑛·0·27 )-    𝑛·0·29 := Φ.number( φ ↦ 𝜎6:λ ).plus( x ↦ 𝑛·0·28 )-    𝔼(L_plus):-      𝔻(Φ.a🌵11):-        𝕄(Φ.a🌵11):-          𝑛·6·1 := Φ.number( φ ↦ 𝜎6:λ )-      𝛿1·6 := 𝔻(𝜎6:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵12):-        𝕄(Φ.a🌵12):-          𝑛·6·2 := Φ.number( φ ↦ 𝑛·0·27 )-          𝑛·6·3 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-        𝛿·6·1 := 40-14-00-00-00-00-00-00  # 5.0-      𝛿2·6 := 𝛿·6·1  # 𝔻(ξ.x)-      𝑛·6·4 := Φ.number( φ ↦ 𝜎7:λ )  # 𝑛-      𝕄(𝑛·6·4):-        𝑛·6·5 := Φ.number( φ ↦ 𝜎7:λ )-      𝑛·6·6 := 𝑛·6·5  # 𝕄(𝑛·6·4)-```--<!-- markdownlint-enable MD013 -->--`𝜎3` is minted by the second firing and consumed by the third as-`𝔻(𝜎3:λ)`, and `𝜎2` by the first and consumed by the fork. `𝜎4` and-`𝜎5` are what the two branches came to, and the fork consumes both: its `join`-line makes them one term carrying `𝜎6`, which the `plus( 5 )` standing after-the fork then reads as `𝔻(𝜎6:λ)`. The line-`𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }` is what ties the three-together: dataizing the formation `𝜎6` names answers what dataizing one of the-other two answers. A reader who knows the entry knows that `𝛿1` is what decides-between them and that the first of the two belongs to `then`. Nothing is-assigned to a `𝜎`, it being the name of a λ function, so the fact stands on a-line of its own the way what a `symbolize` line knows does, and the line under-it binds the meta, commented with the two metas it joined.--Were the fork to answer one of its branches instead, the value of the other-would be minted and never consumed, and `foo` would read as a program that-computes a condition, computes both branches and then drops the branch point.--All six firings stand in the block `𝕄(Φ.demo.a.φ):`, which is as near as a-locator gets to any of them: the walk entered the `φ` of the formation-`Φ.demo.a` morphs to, and everything under it — the dispatches of the chain,-the arguments of `if` — stands under no attribute of any formation, so the-binding the walk had entered is what the protocol writes them under.--A firing that happened while an operand of another was being reduced stands one-level deeper, under the firing that asked for it. Here it never happens,-because deep morphing reduces both branches where they sit as arguments of-`if`, long before the dispatch that fires the fork.--### The protocol as XML--The name of the file decides which of the two formats `--protocol` writes: a-name ending in `.xml` gets the same tree as markup, every other name gets the-indented text above. There is no option for it, since a caller who asks for a-file called `atoms.xml` and gets text back has been told nothing useful. Here-is the run at the top of this section again:--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --symbolic=atoms.yaml --protocol=atoms.xml --quiet \-    --sweet --hide-rho sum.phi-$ cat atoms.xml-<?xml version="1.0" encoding="UTF-8"?>-<protocol>-  <dataize at="Φ">-    <morph at="Φ">-      <applied meta="𝑛·0·1" by="morph" at="Φ" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>-      <applied meta="𝑛·0·2" by="morph" at="Φ" of="𝑛·0·1.plus"><attr name="x">6</attr></applied>-    </morph>-    <evaluate λ="L_number_plus" by="dataize" at="Φ">-      <dataize at="Φ.a🌵0">-        <morph at="Φ.a🌵0">-          <applied meta="𝑛·1·1" by="morph" at="Φ.a🌵0" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>-          <applied meta="𝑛·1·2" by="morph" at="Φ.a🌵0" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00:Δ</attr></applied>-        </morph>-        <delta meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</delta>-      </dataize>-      <bind meta="𝛿1·1">𝛿·1·1</bind>-      <dataize at="Φ.a🌵1">-        <morph at="Φ.a🌵1">-          <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵1" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )</attr></applied>-          <applied meta="𝑛·1·4" by="morph" at="Φ.a🌵1" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00:Δ</attr></applied>-        </morph>-        <delta meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</delta>-      </dataize>-      <bind meta="𝛿2·1">𝛿·1·2</bind>-      <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>-      <built meta="𝑛·1·5">Φ.number( φ ↦ 𝜎1:λ )</built>-      <morph at="𝑛·1·5">-        <applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-      </morph>-      <answer meta="𝑛·1·7">𝑛·1·6</answer>-    </evaluate>-  </dataize>-</protocol>-```--<!-- markdownlint-enable MD013 -->--The root is `<protocol>`, and the run stands inside it, named after the-judgment it ran — `<dataize>` for a 𝔻, `<morph>` for a 𝕄 — with `at` naming-the term it was aimed at, which is what the text format opens with as `𝔻(Φ)`.-After the run the root holds `<msec>`, `<firings>` and `<fps>`: how long the-run took, how many λ functions it fired, and how many it fired per second. The-text format ends with the same three as `msec(…)`, `firings(…)` and `fps(…)`.-The examples here leave them out, since the time differs from run to run.-`<evaluate>` is one firing of 𝔼, `λ` naming the entry that answered it, `by`-naming the judgment that asked for the firing — the same word the run is named-after and an `<unanswered>` carries —-and `at` naming the site it was fired at. The text format leaves those two to-the block the firing stands in, `𝔻(Φ):`, while the markup keeps them on every-element, so a program reading it never has to track the elements around one.-A nested `<dataize at="Φ.a🌵0">` or `<morph at="Φ.a🌵0">` is a judgment started-at a new site, which the text format writes as `𝔻(Φ.a🌵0):` or `𝕄(Φ.a🌵0):`,-and `at` names that site. Whatever the judgment does is written inside the-element, so it closes where the text format drops back to the indentation it-opened at, and like the text line it counts nothing and names no meta.-`<delta>` is the datum the `delta` rule found, `meta` naming it the way the-text format does, and the `<bind>` after the element holds that name in place-of the data. A datum of eight bytes also carries `number`, the double the text-format writes as the comment of its line.-`<bind>` is one meta the firing bound, `meta` naming it the same way the text-format names it, counter and all, and the element holding the value it took: a-term where the operand was reduced with 𝕄, the datum itself where a `dataize`-operand came down to data. `<dataize>` inside a firing is the other thing a-`dataize` operand may come to, the datum manufactured for an unknown, and holds-the formation that unknown names rather than the 42 standing for it: a `𝜎` is-the name of a λ function and no term of its own, so what 𝔻 was applied to is-`𝜎2:λ` and never `𝜎2` alone. It carries `meta` where the run carries-`at`, the same difference the text format draws between `𝔻(Φ)` at the top-and `𝛿1·2 := 𝔻(…)` in a block. The name of the element is what tells a-manufactured datum from data, the way `𝔻(…)` does in the text format, so-nothing has to be read off the presence of an attribute. `<answer>` holds the-term the firing answered with, named the same way by its own `meta`, and-`<built>` before it holds the term the entry wrote, the one 𝕄 made that answer-of: two elements rather than two attributes of one, for the same reason-`<dataize>` is no `<bind>`.--`<applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number">` is an object an-application made, which the text format writes as `𝑛·1·6 := …` in the block-`𝕄(𝑛·1·5):`: `meta` names the object the way the text format-does, counted with the `<built>` and the `<answer>` of the firing it stands in,-`by` and `at` name the judgment that normalized and the site it stood at, and-`of` names the object applied, the way `<deferred>` and `<looped>` name it.-The argument stands in an `<attr>`, `name` naming the attribute it fills and-the text holding the name of the object or the application it is, the symbol,-such as `𝜎1`, where it is a bare one, and the term itself otherwise. A later-element holding that object holds its name instead, in its text or in its-`term`, so `<answer meta="𝑛·1·7">𝑛·1·6</answer>` says the firing answered with-it and `<dataize at="Φ">` that 𝔻 got into it.--`<known symbol="𝜎44">3F-F0-00-00-00-00-00-00</known>` is the fact a `symbolize`-line writes about a symbol it minted, which the text format writes as-`𝔻(𝜎44:λ) == …`: the symbol stands in the attribute a reader joins-lines on and the data dataizing its formation answers are the text of the-element. It takes `symbol` and not `meta`, since the fact is about the unknown-and not about a meta the firing bound.--`<joined symbol="𝜎6">𝜎4 𝜎5</joined>` is the same kind of fact about a symbol-a `join` line minted, which the text format writes as-`𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }`: the fresh symbol stands-in `symbol` and the two it was minted for are the text, in the order the line-listed the metas it joined. A line whose two terms differ at several places-writes one element per pair of symbols, and one whose terms are alike writes-none. The meta the line binds is a `<bind>` like every other meta of the-firing.--`<minted symbol="𝜎1">40-14-… 40-18-…</minted>` is one symbol the firing-minted, one element per bare `𝜎` the entry wrote its answer with, standing-inside the block ahead of the `<built>` carrying them. The symbol stands in-`symbol`, the way `<known>` and `<joined>` put theirs, and the text is what is-known about it: the values the `dataize` lines of the entry took, in the order-the entry declares them, each spelled as its own line spells it — the bytes-for a datum, `𝜎1` for a symbol — and separated by a space the way `<joined>`-lists its pair. With the `λ` of the block the element reads as the fact-`𝔻(𝜎1:λ) == L_number_plus(40-14-…, 40-18-…)`, and a firing of an entry with-no `dataize` line writes `<minted symbol="𝜎1"/>`. The symbol is also the edge-a reader joins on: a later `<dataize meta="𝛿1·5">𝜎2:λ</dataize>` names the-symbol the firing that wrote `<minted symbol="𝜎2">` handed out. A firing-minting two symbols writes two elements and one minting none writes none,-which no attribute on the answer could say: a term may carry several symbols,-or carry one where the value it stands for is not a symbol at all. In the fork-above, `𝔼(L_gt)` writes `<minted symbol="𝜎2">` although `𝜎2` sits under `if`-and not where the value of the term is, while `𝔼(L_fork)` writes none at all,-since the symbol it answers with comes from a `join` line and stands in a-`<joined>` of its own.--A λ name no entry answers is `<unanswered λ="…">`, standing where its-`<evaluate>` would have stood with the formation 𝔼 was fired against as its text-and the judgment that asked in its `by` attribute, where the text format writes-the letter of it. A firing that happened while an operand of another was being-reduced is an `<evaluate>` inside the one that asked, which is what the deeper-indentation means in the text. Elements are written as the run goes and-the open ones are closed when it ends, so a run that fails still leaves a-well-formed document behind:--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --symbolic=atoms.yaml --protocol=atoms.xml --quiet \-    --sweet --hide-rho stuck.phi-[ERROR]: No entry of --symbolic answers the λ function 'L_number_nope'-$ cat atoms.xml-<?xml version="1.0" encoding="UTF-8"?>-<protocol>-  <dataize at="Φ">-    <morph at="Φ">-      <applied meta="𝑛·0·1" by="morph" at="Φ" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>-      <applied meta="𝑛·0·2" by="morph" at="Φ" of="𝑛·0·1.plus"><attr name="x">6</attr></applied>-      <evaluate λ="L_number_plus" by="morph" at="Φ">-        <dataize at="Φ.a🌵0">-          <morph at="Φ.a🌵0">-            <applied meta="𝑛·1·1" by="morph" at="Φ.a🌵0" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>-            <applied meta="𝑛·1·2" by="morph" at="Φ.a🌵0" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00:Δ</attr></applied>-          </morph>-          <delta meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</delta>-        </dataize>-        <bind meta="𝛿1·1">𝛿·1·1</bind>-        <dataize at="Φ.a🌵1">-          <morph at="Φ.a🌵1">-            <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵1" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )</attr></applied>-            <applied meta="𝑛·1·4" by="morph" at="Φ.a🌵1" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00:Δ</attr></applied>-          </morph>-          <delta meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</delta>-        </dataize>-        <bind meta="𝛿2·1">𝛿·1·2</bind>-        <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>-        <built meta="𝑛·1·5">Φ.number( φ ↦ 𝜎1:λ )</built>-        <morph at="𝑛·1·5">-          <applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-        </morph>-        <answer meta="𝑛·1·7">𝑛·1·6</answer>-      </evaluate>-    </morph>-    <unanswered λ="L_number_nope" by="dataize">L_number_nope:λ</unanswered>-  </dataize>-</protocol>-```--<!-- markdownlint-enable MD013 -->--### Abridging the protocol--A formation carrying a whole object is written flat on one line, so a real-run fills the protocol with lines tens of thousands of characters long. The-`--abridged` option shortens every term the protocol writes, in the text and-the XML alike: a formation longer than sixty-four characters keeps its `φ`,-`Δ` and `λ` bindings and folds the rest into a count. The width is a value of-the option, `--abridged=120`, for a run that can read longer lines. The result-the run prints stays whole, and the option is refused without `--protocol`.-Every byte string the protocol writes stays whole too, since a reader may need-the data a firing came down to:--<!-- markdownlint-disable MD013 -->--```bash-$ cat wide.phi-⟦-  t ↦ ⟦-    φ ↦ ⟦ Δ ⤍ 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64 ⟧,-    left ↦ ξ.right,-    right ↦ ξ.left,-    middle ↦ ξ.left-  ⟧-⟧-$ phino dataize --locator=Q.t --protocol=wide.txt --abridged --quiet \-    --sweet --hide-rho wide.phi-$ cat wide.txt-𝔻(Φ.t):-  𝛿·0·1 := 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64-```--<!-- markdownlint-enable MD013 -->--The `--abridged-data` option cuts the data too: a byte string longer than eight-bytes keeps its first two bytes and its last two, with the count of the bytes-cut out between them. A formation folded into a count loses its data either-way, and the option is refused without `--abridged`:--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --locator=Q.t --protocol=wide.txt --abridged \-    --abridged-data --quiet --sweet --hide-rho wide.phi-$ cat wide.txt-𝔻(Φ.t):-  𝛿·0·1 := 48-65-..(8b)..-6C-64-```--<!-- markdownlint-enable MD013 -->--### Reducing a term inside a universe--A term that is no part of the program may still be reduced against it, with the-`--inside` option: the expression it names is bound to a synthetic attribute-prepended to the input expression, which the run takes as the universe Φ,-normalized there and then reduced.--```bash-$ cat universe.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧-⟧-$ phino dataize --symbolic=atoms.yaml --inside='5.plus( 6 )' universe.phi-40-45-00-00-00-00-00-00-```--This is the very trick `phino` plays internally to reduce the operands of a-firing, made available to whoever asks it to reduce a term the program does not-hold. The option cannot be combined with `--locator`, since it aims the run at-the binding it mints itself. Both `dataize` and `morph` take `--symbolic` and-`--inside`.--### Partial evaluation--A λ function no entry of the `--symbolic` file answers fails the run. This is-what happens when an operation is deliberately left out — an input the object-model has not declared yet, or an operation whose answer is not known. With-`--partial`, dataization becomes partial evaluation instead: what the known-inputs decide is computed, the rest survives as the residual program, which is-printed in place of the bytes, and the run ends successfully:--```bash-$ cat partial.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦-    φ ↦ ∅,-    plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,-    times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧,-    as-bool ↦ ⟦ λ ⤍ L_number_as_bool ⟧-  ⟧,-  φ ↦ 2.times( 3 ).plus( 4 ).as-bool-⟧-$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho partial.phi-[ERROR]: No entry of --symbolic answers the λ function 'L_number_as_bool'-$ phino dataize --symbolic=atoms.yaml --partial --sweet --hide-rho partial.phi-L_number_as_bool:λ-```--Here `2.times( 3 ).plus( 4 )` was answered by the entries the file carries, so-it was reduced — the symbol it came to sits in the hidden `ρ` of the residual-program — while `as-bool` names a λ function no entry answers, so it stays in-place as a normal-form subterm. A stuck site opens no block in the-`--protocol` file, since nothing fired there, and stands in it as-`unanswered(…)`:--<!-- markdownlint-disable MD013 -->--```bash-$ phino dataize --symbolic=atoms.yaml --partial --protocol=atoms.txt --quiet \-    --sweet --hide-rho partial.phi-$ cat atoms.txt-𝔻(Φ):-  𝕄(Φ):-    𝑛·0·1 := 2-    𝑛·0·2 := 𝑛·0·1.times( x ↦ 3 )-    𝔼(L_number_times):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := 2-          𝑛·1·2 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-        𝛿·1·1 := 40-00-00-00-00-00-00-00  # 2.0-      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·3 := 3-          𝑛·1·4 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )-        𝛿·1·2 := 40-08-00-00-00-00-00-00  # 3.0-      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛-      𝕄(𝑛·1·5):-        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )-      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-    𝑛·0·3 := 𝑛·1·6.plus( x ↦ 4 )-    𝔼(L_number_plus):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵3):-        𝕄(Φ.a🌵3):-          𝑛·2·2 := 4-          𝑛·2·3 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )-        𝛿·2·1 := 40-10-00-00-00-00-00-00  # 4.0-      𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)-      𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-      𝕄(𝑛·2·4):-        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )-      𝑛·2·6 := 𝑛·2·5  # 𝕄(𝑛·2·4)-  unanswered(L_number_as_bool)  # 𝔻(L_number_as_bool:λ)-```--<!-- markdownlint-enable MD013 -->--Evaluation stays demand-driven, as the calculus prescribes: an argument-that nothing asked for before the run got stuck is left as it is in the-residual program, for the next iteration.--An operand of a firing that reaches the terminator `⊥`, or a term no-dataization rule matches, such as a formation whose `φ` is a void nothing-filled, never comes down to data either, and `--partial` parks that firing-the same way, writing the dead end into the protocol as `unanswered(⊥)` with the-term that could not be dataized beside it. Dataization aimed at `⊥` itself still-fails, with or without `--partial`, since there is no firing to park.--The nested morphing and dataization recursion is bounded by the-`--max-steps` option (default `1000`): when the budget is exhausted, the run-fails with `Dataization did not finish before reaching the limit of steps`.-This guards against non-terminating terms, which used to loop forever before-the bound was introduced:--```bash-$ phino dataize --max-steps=50 problem.phi-[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=50-```--That budget bounds how deep one branch goes, not how much the whole run does.-An entry that reduces two operands, each firing it again, doubles its work at-every level and still never gets deep, so no `--max-steps` stops it. The-`--max-firings` option counts every λ function the run fires and fails the-run once the count is spent; `--partial` parks it instead, the way it parks a-spent `--max-steps`. There is no limit unless the option is given:--```bash-$ cat split.yaml-- λ: L_split-  morph:-    𝑛1: Φ.s.foo-    𝑛2: Φ.s.foo-  𝑛: ⟦ l ↦ 𝑛1, r ↦ 𝑛2 ⟧-$ cat split.phi-⟦ s ↦ ⟦ λ ⤍ L_split ⟧, x ↦ Φ.s.foo ⟧-$ phino morph --symbolic=split.yaml --locator=Q.x --max-firings=64 split.phi-[ERROR]: Evaluation did not finish before reaching the limit of firings: --max-firings=64-```--Both budgets count work, so a run inside both of them may still take longer-than its caller can wait, and a caller that kills it gets a protocol nobody-closed. The `--max-seconds` option stops the run by the clock instead: once-that many seconds have passed since the command started, the next step the-run is about to take, whether it fires a λ function or not, fails it with-`Evaluation did not finish before reaching the limit of seconds`, and so does-a check of `--acyclic` that is still comparing formations by then.-`--partial` does not park it, since a run out of time has no site to park and-nothing left to go on with. The protocol is closed as usual and its last line-says where the time ran out. There is no limit unless the option is given:--```bash-$ phino morph --symbolic=split.yaml --locator=Q.x --partial --max-seconds=5 \-    --protocol=split.txt --sweet --flat split.phi-[ERROR]: Evaluation did not finish before reaching the limit of seconds: --max-seconds=5-$ grep -o 'timeout.*' split.txt-timeout(5)-```--## Morph--Dataization insists on bytes. Morphing 𝕄 asks a different question: evaluate-as far as the object model allows, without demanding data. It resolves Φ-against the universe, peels dispatches and applications through-normalization, fires whichever λ functions sit under a dispatch, and stops at-the first formation it reaches, handing that formation back untouched. The-`morph` command runs 𝕄 on its own:--```bash-$ cat two.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-  φ ↦ 5.plus( 6 ).plus( 7 )-⟧-$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho two.phi-40-45-00-00-00-00-00-00-$ phino morph --symbolic=atoms.yaml --locator=Q.φ --sweet --hide-rho two.phi-⟦ x ↦ 7, λ ⤍ L_number_plus ⟧-```--The inner `5.plus( 6 )` fires, because `.plus` is dispatched on its result,-and the symbol it answered with lands in the `ρ` hidden by `--hide-rho`. The-outer application is saturated but bare, so 𝕄 returns it and is finished;-firing it is dataization's job and takes `dataize` on to a datum.--The default locator `Q` morphs the whole top formation, which 𝕄 returns-unchanged, so `--locator` is how one aims 𝕄 at a subterm, exactly as in-`dataize`. Unlike 𝔻, 𝕄 is total: where no formation is reachable the answer-is the terminator `⊥`, printed rather than reported as a failed run:--```bash-$ phino morph --locator=Q.x <<< '⟦ x ↦ ξ ⟧'-⊥-```--𝕄 maps normal forms to formations and `morph` does not normalize what it is-given, so a term that is not a normal form, such as a dispatch off a formation-with neither `φ` nor `λ`, is reported as a failed run rather than answered:--```bash-$ phino morph --locator=Q.t <<< '⟦ t ↦ ⟦ x ↦ ⟦⟧ ⟧.x ⟧'-[ERROR]: Morphing expects a normal form, but no morphing rule matches: ⟦⟧:x.x-```--The whole `dataize` option surface applies unchanged — `--symbolic`,-`--inside`, `--sequence`, `--headers`, `--steps-dir`, `--protocol`,-`--partial`, `--max-steps`, `--shuffle`/`--seed`, `--output`, `--focus` and the-rest.--### Deep morphing--𝕄 stops at the first formation it reaches and hands its bindings back as they-were written, since firing a bare λ is dataization's job, and `dataize`-follows the one path dataization demands and ends in bytes. What a program-holds but nothing demands — the argument of a λ function no entry answers, for-one — is therefore reduced by neither. The `--deep` flag enters it:--```bash-$ cat gap.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧ ⟧,-  bar(x) ↦ ⟦ λ ⤍ L_bar ⟧,-  demo ↦ ⟦ foo ↦ ⟦ n ↦ 3, φ ↦ Φ.bar( ξ.n.times( 5 ).times( 7 ) ) ⟧ ⟧-⟧-$ phino morph --symbolic=atoms.yaml --inside='Q.demo.foo' \-    --sweet --hide-rho gap.phi-⟦ n ↦ 3, φ ↦ Φ.bar( n.times( 5 ).times( 7 ) ) ⟧-$ phino morph --deep --symbolic=atoms.yaml --inside='Q.demo.foo' \-    --sweet --hide-rho gap.phi-⟦ n ↦ 3, φ ↦ Φ.bar( ⟦ φ ↦ 𝜎2:λ, times(x) ↦ L_number_times:λ ⟧ ) ⟧-```--Every binding of the formation is entered, recursively. 𝕄 is asked about the-term standing there and, where it lands on a saturated formation whose λ an-entry answers, that λ is fired and 𝕄 is asked about the answer again. A term on-whose way a λ function fired is replaced by the answer of the last firing,-morphed: an entry answering `Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )` stands the formation of-`number` there, the very one the same term written in the program morphs to, so-a value that came out of a firing and a value that was written as a literal are-one shape and can be compared leaf by leaf. That costs the size of the object's-formation in the residual, which is the price of saying the same thing one way.-A term nothing fired on stays exactly as it was written and only its own parts-are walked, so `Φ.bar` keeps its name and what comes back is still the same-program, reduced as far as the file allows. The step joins the chain under the-name `deep`, so `--sequence` shows it, and `--max-steps` bounds the walk.--Two things are left alone. A λ no entry answers is not fired at all, so-`--deep` stays as total as 𝕄 itself and needs no `--partial`; a λ function that-gets stuck deeper on a spine still fails the run, and `--partial` parks it,-leaving that term as it was written. A firing the walk does make and cannot-finish — one whose operand never comes down to data, because a λ nothing-answers stands in it — is parked by `--partial` the same way: the binding it-stood in is left as it was written, the walk enters the next one, and the-protocol shows the firing with nothing bound under it. One entry nothing can-answer therefore taints its own binding and not the whole run. A formation-still holding a void-binding is not fired either: the void is an argument the program has not given-yet, so `times(x) ↦ L_number_times:λ` is a method waiting to be applied,-not an application waiting to be computed. Walking the whole program therefore-folds what it can and leaves the object model as it was declared:--```bash-$ phino morph --deep --symbolic=atoms.yaml --sweet --hide-rho gap.phi-⟦-  bytes(φ) ↦ ⟦⟧,-  number(φ) ↦ ⟦ times(x) ↦ L_number_times:λ ⟧,-  bar(x) ↦ L_bar:λ,-  demo ↦ ⟦ n ↦ 3, φ ↦ Φ.bar( ⟦ φ ↦ 𝜎2:λ, times(x) ↦ L_number_times:λ ⟧ ) ⟧:foo-⟧-```--One kind of formation the walk does not enter at all: a copy with a `φ`-written as code and an argument that is a bare symbol, a `⟦ λ ⤍ 𝜎1 ⟧` with-no carrier around it. The symbol stands for a value nobody knows, so the body-can only ask it questions nobody can answer, and every firing spent inside-that body is wasted. The walk defers such a copy instead: it answers the copy-with a fresh symbol and writes a `deferred` line to the protocol, tying that-symbol to the copy it stands for, which only a run of the program can work out.-The walk reads every argument written as a dispatch before it decides, so-`tup ↦ items.tail` counts when `items.tail` comes to a bare symbol, but it-fires nothing while reading. A copy with no `φ`, such as a tuple, only holds-its arguments, so reading one of them reaches the symbol and defers nothing.-Here the walk defers the copy of `box`:--```bash-$ cat box.phi-⟦-  box(x) ↦ ⟦ φ ↦ x.next ⟧,-  y ↦ Φ.box( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ )-⟧-$ phino morph --deep --locator=Q.y --protocol=p.txt --sweet --hide-rho box.phi-𝜎2:λ-$ head -3 p.txt-𝕄(Φ.y):-  𝑛·0·1 := Φ.box( x ↦ 𝜎1:λ )-  deferred(𝜎2) := Φ.box( x ↦ 𝜎1:λ )-```--### Acyclic morphing--Whether a program terminates is the object model's business, not the-calculus's, so phino prevents no recursion of its own and `--max-steps` is what-ends a run that never finishes. An entry answering with a firing of itself-therefore spends the whole budget before it fails, and what it fails on is the-limit rather than the loop:--```bash-$ cat loop.yaml-- λ: L_loop-  𝑛: ⟦ λ ⤍ L_loop ⟧-$ cat loop.phi-⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧-$ phino morph --symbolic=loop.yaml --locator='Q.x' --max-steps=40 loop.phi-[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=40-```--The `--acyclic=<mode>` option makes a reduction notice. Every frame of 𝕄 and-of 𝔻 remembers the formations the frames above it have entered, and only four-places enter one: `fire` of 𝔻 and `ml` of 𝕄, which fire the λ function of a-formation, the walk of `--deep`, which fires the λ function of every formation-𝕄 leaves bare, and `box` of 𝔻, which gets into the `φ` body of a formation-carrying no λ and no `Δ`. A frame about to enter a formation one of the frames-above it has already entered is asking a question only ever answered by asking-it again, so the option stops there and parks the site the way `--partial`-parks a λ function that cannot fire: the answer is the term the spine had-reached, left where it stood, and the command exits successfully.--```bash-$ phino morph --symbolic=loop.yaml --locator='Q.x' --acyclic=proven \-    --max-steps=40 --hide-rho loop.phi-⟦ λ ⤍ L_loop ⟧.foo-```--𝕄 and 𝔻 share that one memory. They call each other on the very term they-were asked about, and that handover is no loop, but it enters no formation-either, so it is never remembered and never mistaken for one. A body-dispatching the object it stands in is a loop 𝔻 walks round on its own — 𝕄-stops at a formation every round, and `box` gets into it again — so `dataize`-takes the option too, and so does the run of 𝔻 a λ function's `dataize` operand-is brought down with:--```bash-$ cat cyc.phi-⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧, t ↦ Φ.cyc( ⟦⟧ ) ⟧-$ phino dataize --locator='Q.t' --acyclic=proven --partial \-    --sweet --hide-rho --flat cyc.phi-⟦ cyc(x) ↦ Φ.cyc( x ):φ, t ↦ Φ.cyc( ⟦⟧ ) ⟧-```--𝔻 insists on bytes and a parked term carries none, so under `dataize` the option-wants `--partial` to have something to print: the residual program, exactly the-one it prints for a λ function that cannot fire. Without it the run stops on-the loop all the same, naming the formation it entered again instead of running-the budget down. Under `morph` nothing is asked for: 𝕄 always has a term to-answer with, a loop 𝔻 meets under a firing parks the site the firing stands at,-and the walk of `--deep` goes on to the next binding.--The mode says what "the same formation" means, and there is no default, since-no answer is right for every run. Under `proven` it means the same up to a-renaming of symbols: two-formations are one where some one-to-one pairing of the symbols of the first-with those of the second makes them equal, so `𝜎5` may stand where `𝜎3` stood-as long as it does so everywhere and no other symbol stands there too. Data,-attribute names and λ names must still match exactly. That is what catches a-recursion over an unknown, which never repeats a term: every round mints fresh-symbols, so the formation it enters on the second round is the one it entered-on the first with new names for the unknowns. The renaming is sound because a-symbol is an opaque value nobody worked out — every one of them dataizes to the-same manufactured datum and no entry of `--symbolic` answers one — so a-formation entered again with nothing but its symbols renamed replays the round-it is inside forever. Data still tells rounds apart: a formation entered with-`n ↦ 3` and then with `n ↦ 2` is two formations, so a recursion over data is-not cut while it goes on computing. Take a factorial over a symbolic argument,-with `fact.yaml` answering `L_zero`, `L_dec` and `L_mul` with a fresh symbol-each and `L_if` a fork joining its two branches. The argument, and what `L_dec`-answers, is an object whose `φ` is a symbol rather than a bare symbol, since-the walk of `--deep` defers a copy over a bare symbol instead of entering it:--<!-- markdownlint-disable MD013 -->--```bash-$ cat fact.phi-⟦-  if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,-  zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,-  dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,-  mul ↦ ⟦ a ↦ ∅, b ↦ ∅, λ ⤍ L_mul ⟧,-  fact ↦ ⟦ n ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ⟦ Δ ⤍ 01- ⟧, right ↦ Φ.mul( a ↦ ξ.n, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ) ) ) ) ⟧,-  x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ )-⟧-$ cat fact.yaml-- λ: L_zero-  dataize:-    𝛿1: $.x-  𝑛: ⟦ λ ⤍ 𝜎 ⟧-- λ: L_dec-  dataize:-    𝛿1: $.x-  𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-- λ: L_mul-  dataize:-    𝛿1: $.a-    𝛿2: $.b-  𝑛: ⟦ λ ⤍ 𝜎 ⟧-- λ: L_if-  dataize:-    𝛿1: $.c-  morph:-    𝑛1: $.left-    𝑛2: $.right-  symbolize:-    𝑛3: 𝑛1-    𝑛4: 𝑛2-  join:-    𝑛5: [𝑛3, 𝑛4]-  𝑛: 𝑛5-$ phino morph --deep --acyclic=proven --partial --sweet --hide-rho --flat \-    --symbolic=fact.yaml --locator='Q.x' --protocol=fact.txt fact.phi-⟦ n ↦ 𝜎1:λ:φ, φ ↦ Φ.if( c ↦ 𝜎2:λ, left ↦ 01-:Δ, right ↦ Φ.mul( a ↦ n, b ↦ Φ.fact( n ↦ 𝜎3:λ:φ ) ) ) ⟧-$ cat fact.txt-𝕄(Φ.x):-  𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ:φ )-  𝕄(Φ.x.φ):-    𝑛·0·2 := Φ.zero( x ↦ 𝜎1:λ:φ )-    𝔼(L_zero):-      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-      𝑛·1·1 := 𝜎2:λ  # 𝑛-      𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)-    𝑛·0·3 := Φ.if( c ↦ 𝜎2:λ )-    𝑛·0·4 := Φ.if( c ↦ 𝜎2:λ )-    𝑛·0·5 := 𝑛·0·4( left ↦ 01-:Δ )-    𝑛·0·6 := Φ.mul( a ↦ 𝜎1:λ:φ )-    𝑛·0·7 := Φ.dec( x ↦ 𝜎1:λ:φ )-    𝔼(L_dec):-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-      𝑛·2·1 := 𝜎3:λ:φ  # 𝑛-      𝑛·2·2 := 𝜎3:λ:φ  # 𝕄(𝑛·2·1)-    𝑛·0·8 := Φ.fact( n ↦ 𝜎3:λ:φ )-    𝑛·0·9 := Φ.mul( a ↦ 𝜎1:λ:φ )-    𝑛·0·10 := 𝑛·0·9( b ↦ 𝑛·0·8 )-    𝔼(L_mul):-      𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)-      𝔻(Φ.a🌵3):-        𝕄(Φ.a🌵3):-          𝑛·3·1 := Φ.fact( n ↦ 𝜎3:λ:φ )-          𝑛·3·2 := Φ.if( c ↦ Φ.zero( x ↦ 𝜎3:λ:φ ) )-          𝑛·3·3 := 𝑛·3·2( left ↦ 01-:Δ )-          𝑛·3·4 := 𝑛·3·3( right ↦ Φ.mul( a ↦ 𝜎3:λ:φ, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) ) )-        𝔼(L_if):-          𝔻(Φ.a🌵4):-            𝕄(Φ.a🌵4):-              𝑛·4·1 := Φ.zero( x ↦ 𝜎3:λ:φ )-            𝔼(L_zero):-              𝛿1·5 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-              𝑛·5·1 := 𝜎4:λ  # 𝑛-              𝑛·5·2 := 𝜎4:λ  # 𝕄(𝑛·5·1)-          𝛿1·4 := 𝔻(𝜎4:λ)  # 𝔻(ξ.c)-          𝑛1·4 := 01-:Δ  # 𝕄(ξ.left)-          𝕄(Φ.a🌵7):-            𝑛·4·2 := Φ.mul( a ↦ 𝜎3:λ:φ )-            𝑛·4·3 := 𝑛·4·2( b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) )-            𝕄(Φ.a🌵7.b):-              𝑛·4·4 := Φ.dec( x ↦ 𝜎3:λ:φ )-              𝔼(L_dec):-                𝛿1·6 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                𝑛·6·1 := 𝜎5:λ:φ  # 𝑛-                𝑛·6·2 := 𝜎5:λ:φ  # 𝕄(𝑛·6·1)-              𝑛·4·5 := Φ.fact( n ↦ 𝜎5:λ:φ )-            looped(𝑛·0·10)  # proven-          𝑛2·4 := 𝑛·4·3  # 𝕄(ξ.right)-          𝔻(𝜎6:λ) == 01--          𝑛3·4 := 𝜎6:λ  # 𝑛1-          𝑛4·4 := 𝑛·4·3  # 𝑛2-          stuck(L_if)-      stuck(L_if)-    𝑛·0·11 := Φ.if( c ↦ 𝜎2:λ )-    𝑛·0·12 := 𝑛·0·11( left ↦ 01-:Δ )-    𝑛·0·13 := 𝑛·0·12( right ↦ 𝑛·0·10 )-    𝔼(L_if):-      𝛿1·7 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)-      𝑛1·7 := 01-:Δ  # 𝕄(ξ.left)-      𝕄(Φ.a🌵11):-        𝑛·7·1 := Φ.mul( a ↦ 𝜎1:λ:φ )-        𝑛·7·2 := 𝑛·7·1( b ↦ 𝑛·3·1 )-        𝕄(Φ.a🌵11.b):-          𝑛·7·3 := Φ.fact( n ↦ 𝜎3:λ:φ )-        𝔼(L_mul):-          𝛿1·8 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)-          𝔻(Φ.a🌵13):-            𝕄(Φ.a🌵13):-              𝑛·8·1 := Φ.fact( n ↦ 𝜎3:λ:φ )-              𝑛·8·2 := Φ.if( c ↦ 𝑛·4·1 )-              𝑛·8·3 := 𝑛·8·2( left ↦ 01-:Δ )-              𝑛·8·4 := 𝑛·8·3( right ↦ 𝑛·4·3 )-            𝔼(L_if):-              𝔻(Φ.a🌵14):-                𝕄(Φ.a🌵14):-                  𝑛·9·1 := Φ.zero( x ↦ 𝜎3:λ:φ )-                𝔼(L_zero):-                  𝛿1·10 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                  𝑛·10·1 := 𝜎7:λ  # 𝑛-                  𝑛·10·2 := 𝜎7:λ  # 𝕄(𝑛·10·1)-              𝛿1·9 := 𝔻(𝜎7:λ)  # 𝔻(ξ.c)-              𝑛1·9 := 01-:Δ  # 𝕄(ξ.left)-              𝕄(Φ.a🌵17):-                𝑛·9·2 := Φ.mul( a ↦ 𝜎3:λ:φ )-                𝑛·9·3 := 𝑛·9·2( b ↦ Φ.fact( n ↦ 𝑛·4·4 ) )-                𝕄(Φ.a🌵17.b):-                  𝑛·9·4 := Φ.dec( x ↦ 𝜎3:λ:φ )-                  𝔼(L_dec):-                    𝛿1·11 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                    𝑛·11·1 := 𝜎8:λ:φ  # 𝑛-                    𝑛·11·2 := 𝜎8:λ:φ  # 𝕄(𝑛·11·1)-                  𝑛·9·5 := Φ.fact( n ↦ 𝜎8:λ:φ )-                looped(𝑛·7·2)  # proven-              𝑛2·9 := 𝑛·9·3  # 𝕄(ξ.right)-              𝔻(𝜎9:λ) == 01--              𝑛3·9 := 𝜎9:λ  # 𝑛1-              𝑛4·9 := 𝑛·9·3  # 𝑛2-              stuck(L_if)-          stuck(L_if)-      𝑛2·7 := 𝑛·7·2  # 𝕄(ξ.right)-      𝔻(𝜎10:λ) == 01--      𝑛3·7 := 𝜎10:λ  # 𝑛1-      𝑛4·7 := 𝑛·7·2  # 𝑛2-      stuck(L_if)-```--<!-- markdownlint-enable MD013 -->--The first `L_mul` brings its `b` down, and that gets 𝔻 into `fact` with-`n ↦ 𝜎3`, the `𝔻(…):` line under it. Inside, the fork reduces its right-branch, and the walk of `--deep` over it would fire `L_mul` with `a ↦ 𝜎3` and-`b ↦ Φ.fact( n ↦ 𝜎5 )`: the formation the first `L_mul` was fired with, `𝜎3`-standing where `𝜎1` stood and `𝜎5` where `𝜎3` stood, so the firing is cut-before it opens. The cut is the `looped(…)` line in the block `𝕄(Φ.a🌵7):`,-standing where the block of the cut firing would have stood: the block names-the judgment the frame belonged to and the site it was cut at, and the comment-names the mode that cut it. What it-carries is the formation the frame above entered, named by the line that made-it, `𝑛·0·10`, so the two are paired by their terms and no reader has to rename-symbols by eye or find the cut in the residue. Nothing runs under a cut, so no-block opens under the line. In the XML protocol it is-`<looped by="morph" match="proven" at="Φ.a🌵7"><e>𝑛·0·10</e></looped>`, with-the site and the mode as attributes and the formation in `<e>`. Without the option-the same run nests one round inside another until `--max-steps` runs out.--A cut at the `φ` of a copy the walk of `--deep` has placed, such as-`Φ.a🌵4.φ`, answers that copy with a fresh symbol, the way a deferred copy is-answered. A fork above it then joins that symbol with its other branch,-instead of getting stuck on a copy nobody can read. The line names the-symbol, as in `looped(…) := 𝜎6  # plausible` in the block `𝕄(Φ.a🌵4.φ):`,-and the markup-writes the object the copy was made of and its arguments the way it writes-them for a deferred copy, on one line, broken here for reading:--<!-- markdownlint-disable MD013 -->--```xml-<looped symbol="𝜎6" by="morph" match="plausible" at="Φ.a🌵4.φ" of="Φ.fact">-  <with><attr name="n">𝜎3</attr><attr name="acc">?</attr></with>-  <e>𝑛·0·16</e>-</looped>-```--<!-- markdownlint-enable MD013 -->--A cut anywhere else, or at the `φ` of a copy nested inside a term, answers-nothing and leaves the copy as it stood.--What a frame remembers is the branch from the run down to it, never everything-the run has touched, so two siblings entering one formation enter it twice and-only a formation entered from inside itself is a loop: the fork at `Φ.x.φ`-above gets into `fact` with `n ↦ 𝜎3` once more, on a branch of its own, and is-not cut there. The cut costs one lookup and fires on the turn the repeat-appears, so raising `--max-steps` from 40 to a million changes neither the-answer nor the time.--What `proven` cannot see is a recursion that never comes back to the same-formation: one whose accumulator grows by a wrapper every round. Give the-factorial an `acc` it builds a `pair` onto, drop `L_mul` from `fact.yaml`, and-under `proven` the run nests deeper until `--max-steps` runs out, since no-renaming of symbols turns a longer chain of pairs into a shorter one:--<!-- markdownlint-disable MD013 -->--```bash-$ cat facta.phi-⟦-  if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,-  zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,-  dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,-  pair ↦ ⟦ head ↦ ∅, tail ↦ ∅ ⟧,-  fact ↦ ⟦ n ↦ ∅, acc ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ξ.acc, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ), acc ↦ Φ.pair( head ↦ ξ.n, tail ↦ ξ.acc ) ) ) ⟧,-  x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, acc ↦ ⟦ Δ ⤍ 00- ⟧ )-⟧-$ phino morph --deep --acyclic=plausible --partial --sweet --hide-rho --flat \-    --symbolic=facta.yaml --locator='Q.x' --protocol=facta.txt facta.phi-⟦ n ↦ 𝜎1:λ:φ, acc ↦ 00-:Δ, φ ↦ 𝜎8:λ ⟧-$ grep looped facta.txt-          looped(𝑛·0·16) := 𝜎6  # plausible-```--<!-- markdownlint-enable MD013 -->--Under `plausible` the same formation means one the formation entered earlier-is embedded in: the two have the same attributes, data and λ function at the-top, and every term the earlier one bound there is found again in the later-one, as it stands or somewhere below a wrapper it gained. Any symbol stands for-any other, and nothing is looked for under a `ρ`, which holds the object a term-came from rather than a term it grew into. The second `if` above holds the-first under one more `pair`, so it is cut, and its `looped(…)` line says-`plausible`. A call nested in its own operand, such as a sum of sums, is never-cut, since the inner call is smaller than the outer one and cannot hold it.-The mode is not sound, though: a recursion whose argument grows on its way to-stopping is cut too, which is why the line names the mode that made the cut.--The mode also fires every formation once. Every use of a binding copies the-term bound to it, so a program reading `truncated ↦ ρ.abs.floor` in five-places fires `abs` and `floor` five times over and mints five symbols for one-value, and a guard written that way spends its whole budget saying the same-thing again. 𝔼 is a function of the formation it fires: the entry that-answers is found by the λ name the formation carries, every operand is-reduced from its bindings inside the one universe of the run, and the answer-is built from what they came down to. So a run under `plausible` keeps what-every firing answered, by the formation it fired, and a later firing of the-same formation takes that answer, with the very symbols the first one minted,-instead of making it again. Two bindings spelling one term then come to one-symbol:--```bash-$ cat twins.phi-⟦-  bytes ↦ ⟦ φ ↦ ∅ ⟧,-  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-  a ↦ 7.plus( 5.plus( 6 ) ),-  b ↦ 7.plus( 5.plus( 6 ) )-⟧-$ phino morph --symbolic=atoms.yaml --acyclic=plausible --deep --sweet \-    --hide-rho twins.phi-⟦-  bytes(φ) ↦ ⟦⟧,-  number(φ) ↦ ⟦ plus(x) ↦ L_number_plus:λ ⟧,-  a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧,-  b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧-⟧-```--Under `proven` `b` lands on `𝜎4`, since the walk over it fires the inner sum-and the outer one once more. The protocol of the run above still holds four-firings, two under `a` and two under `b`, since it records where 𝔼 was asked-and what it answered there; the two under `b` carry the answer lines of the-two under `a` and no operand line, since nothing was reduced for them, and-they are not charged to `--max-firings`, which counts the firings the run-made. The formation is compared with everything it carries, `ρ` included, so-a firing on another object is another firing, yet it may take the same answer-all the same: the run keeps every answer by the λ name and what the operands-came down to as well, and a firing whose operands come down to the data, the-symbols and the normal forms of one already answered takes that answer once-they are down, minting nothing. An attribute read from the `φ` of an object-and then reached as a binding of it is read through two `ξ`, each the object-minus the attribute being read, so it is two formations, but one firing, and-so one symbol. A symbol counts as the symbol it is, not as the datum every-symbol manufactures, so two firings over two symbols stay two. The protocol-writes such a firing with its operand lines and the answer lines of the one-it took the answer from, and `--max-firings` charges it like a firing made,-since a firing is charged as it starts bringing its operands down, which is-the only place a recursion widening inside its operands can be cut. A firing-cut by the mode on its way to an answer keeps the cut, and the next firing of-the same formation is cut at its own site without reducing anything first. A-firing that got stuck keeps the λ function it got stuck on, and the next-firing of the same formation gets stuck at its own site the same way, with-nothing reduced under it, as long as nothing new was answered in between.-Once something was, the formation is fired again, since an operand that could-not be brought down the first time may come down now.--The mode also walks a binding of the world once. Every dispatch on an object-of the world copies it, and `--deep` walks every copy, so the tests of an-object are reduced once per copy of it the program holds. A copy goes by a-name in the world, such as `Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ )`, the name `dot` writes-into its `ρ`, and that name without its application, `Φ.num`, stands for every-copy. So a run under `plausible` enters `test` of `Φ.num` in the first copy it-meets and leaves it as written in every later one:--```bash-$ cat atoms.yaml-- λ: L_id-  morph:-    𝑛1: $.x-  𝑛: 𝑛1-- λ: L_twice-  dataize:-    𝛿1: $.ρ-  𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-$ cat world.phi-⟦-  num ↦ ⟦-    φ ↦ ∅,-    twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧,-    test ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ).twice-  ⟧,-  a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ⟧,-  b ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2B- ⟧ ) ⟧-⟧-$ phino morph --symbolic=atoms.yaml --acyclic=plausible --deep --partial \-    --sweet --hide-rho world.phi-⟦-  num(φ) ↦ ⟦ twice ↦ L_twice:λ, test ↦ Φ.num( φ ↦ 01-:Δ ).twice ⟧,-  a ↦ ⟦-    φ ↦ 2A-:Δ,-    twice ↦ L_twice:λ,-    test ↦ ⟦ φ ↦ 𝜎1:λ, twice ↦ L_twice:λ, test ↦ Φ.num( φ ↦ 01-:Δ ).twice ⟧-  ⟧,-  b ↦ ⟦ φ ↦ 2B-:Δ, twice ↦ L_twice:λ, test ↦ Φ.num( φ ↦ 01-:Δ ).twice ⟧-⟧-```--A binding the copy filled, such as `φ` above, belongs to that copy alone and-is entered every time. A binding left out is never replaced by what an earlier-copy came to, since a method may read the `ρ` or the `φ` of the copy it-stands in, and so a recursion over copies of one object stops after its first-round, with the second one as written.--The walk of `--deep` takes the bindings of the formation it starts at one-after another. When they are independent entries, such as the objects of a-whole runtime listed in one formation, the `--jobs` option walks them side by-side on that many workers. Each binding gets its own memo, its own-`--max-firings` tally and its own fresh names, so what it comes to does not-depend on which worker got there first. The answers and the protocol come out-in the order of the bindings, with the symbols numbered as one walk would-number them:--```bash-$ cat plus.yaml-- λ: L_plus-  dataize:-    𝛿1: $.ρ-    𝛿2: $.x-  𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-$ cat sums.phi-⟦-  num ↦ ⟦ φ ↦ ∅, plus ↦ ⟦ ρ ↦ ∅, x ↦ ∅, λ ⤍ L_plus ⟧ ⟧,-  l🌵 ↦ ⟦-    a ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ).plus( x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 02- ⟧ ) ),-    b ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 03- ⟧ ).plus( x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 04- ⟧ ) )-  ⟧-⟧-$ phino morph --symbolic=plus.yaml --deep --locator=Q.l🌵 --jobs=2 \-    --protocol=sums.txt --sweet --hide-rho sums.phi-⟦ a ↦ ⟦ φ ↦ 𝜎1:λ, plus(x) ↦ L_plus:λ ⟧, b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_plus:λ ⟧ ⟧-$ cat sums.txt-𝕄(Φ.l🌵):-  𝕄(Φ.l🌵.a):-    𝑛·0·1 := Φ.num( φ ↦ 01-:Δ )-    𝑛·0·2 := Φ.num( φ ↦ 01-:Δ )-    𝑛·0·3 := Φ.num( φ ↦ 02-:Δ )-    𝑛·0·4 := Φ.num( φ ↦ 01-:Δ )-    𝑛·0·5 := 𝑛·0·4.plus( x ↦ 𝑛·0·3 )-    𝔼(L_plus):-      𝔻(Φ.a🌵1-0):-        𝕄(Φ.a🌵1-0):-          𝑛·1·1 := Φ.num( φ ↦ 01-:Δ )-        𝛿·1·1 := 01--      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1-1):-        𝕄(Φ.a🌵1-1):-          𝑛·1·2 := Φ.num( φ ↦ 02-:Δ )-        𝛿·1·2 := 02--      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-      𝑛·1·3 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛-      𝕄(𝑛·1·3):-        𝑛·1·4 := Φ.num( φ ↦ 𝜎1:λ )-      𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)-  𝕄(Φ.l🌵.b):-    𝑛·0·6 := Φ.num( φ ↦ 03-:Δ )-    𝑛·0·7 := Φ.num( φ ↦ 03-:Δ )-    𝑛·0·8 := Φ.num( φ ↦ 04-:Δ )-    𝑛·0·9 := Φ.num( φ ↦ 03-:Δ )-    𝑛·0·10 := 𝑛·0·9.plus( x ↦ 𝑛·0·8 )-    𝔼(L_plus):-      𝔻(Φ.a🌵2-0):-        𝕄(Φ.a🌵2-0):-          𝑛·2·1 := Φ.num( φ ↦ 03-:Δ )-        𝛿·2·1 := 03--      𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵2-1):-        𝕄(Φ.a🌵2-1):-          𝑛·2·2 := Φ.num( φ ↦ 04-:Δ )-        𝛿·2·2 := 04--      𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)-      𝑛·2·3 := Φ.num( φ ↦ 𝜎2:λ )  # 𝑛-      𝕄(𝑛·2·3):-        𝑛·2·4 := Φ.num( φ ↦ 𝜎2:λ )-      𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)-```--A fresh name a binding mints carries its place in the formation, `a🌵2-0` for-the second one, so no two bindings ever mint the same name. The workers split-the time, not the work: the slowest binding still takes as long as it did,-and the others no longer wait behind it. Without `--jobs`, or with-`--jobs=1`, the walk is the one described above, sharing one memo and one-tally across all bindings.--## Rewrite--You can rewrite this expression with the help of [rules](#rule-structure)-defined in the `my-rule.yml` YAML file (here, the `!d1` and `!B1` are capturing-groups, similar to regular expressions):--```yaml-name: My custom rule-pattern: ⟦ Δ ⤍ !d1, !B1 ⟧-result: ⟦ Δ ⤍ 62-79-65, !B1 ⟧-```--Then, rewrite:--```bash-$ phino rewrite --rule=my-rule.yml hello.phi-⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧, t ↦ ξ.k, k ↦ ⟦⟧ ⟧-```--If you want to use many rules, just use `--rule` as many times as you need:--```bash-phino rewrite --rule=rule1.yaml --rule=rule2.yaml ...-```--You can also use [built-in rules](resources), which are designed-to normalize expressions:--```bash-phino rewrite --normalize hello.phi-```--Both flags may be combined, so that your own rules are applied-alongside the built-in ones, in a single rewriting session:--```bash-phino rewrite --normalize --rule=my-rule.yaml hello.phi-```--Some rules mint fresh synthetic names via the `random-string` built-in. To-keep the output reproducible across runs, `phino` seeds the random generator-deterministically with `0` by default. Use `--seed` to pick a different seed:--```bash-phino rewrite --seed=42 --rule=my-rule.yml hello.phi-```--If no input file is provided, the 𝜑-expression is taken from `stdin`:--```bash-$ echo '⟦ φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ⟧' | phino rewrite --rule=my-rule.yml-⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧ ⟧-```--You're able to pass [`XMIR`][xmir] as input. Use `--input=xmir` and `phino`-will parse given `XMIR` from file or `stdin` and convert it to `phi` AST.--```bash-phino rewrite --rule=my-rule.yaml --input=xmir file.xmir-```--Also `phino` supports 𝜑-expressions in-[ASCII](https://en.wikipedia.org/wiki/ASCII) format and with-syntax sugar. The `rewrite` command also allows you to desugar the expression-and print it in canonical syntax:--```bash-$ echo '[[ @ -> Q.io.stdout("hello") ]]' | phino rewrite-⟦-  φ ↦ Φ.io.stdout( α0 ↦ Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ) ) )-⟧-```--A formation of a single binding may be written as its asset, a colon,-and the attribute the asset is bound to:--```text-⟦ Δ ⤍ FF-AA ⟧    = FF-AA:Δ    = FF-AA:D-⟦ λ ⤍ 𝜎1 ⟧       = 𝜎1:λ       = !S1:L-⟦ a ↦ ∅ ⟧        = ∅:a        = ?:a-⟦ φ ↦ ξ.a ⟧      = ξ.a:φ      = $.a:@-```--The colon binds as tightly as a dot, so `ξ.a:φ.b` is `⟦ φ ↦ ξ.a ⟧.b`.-With `--sweet`, `phino` prints every such formation this way, so-`⟦ x ↦ ⟦ φ ↦ ξ.a ⟧ ⟧` comes out as `a:φ:x` and `⟦ x(a) ↦ ⟦ φ ↦ a ⟧ ⟧`-as `⟦ x(a) ↦ a:φ ⟧`: the formation inline voids open takes the sugar,-while the one holding the voids keeps its brackets, and so does-every formation in the salty syntax and in [LaTeX][latex].--A formation has a receiver `ρ` only when it declares one among its voids, the-way EO declares `^`: `⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧`, or `a(ρ) ↦ ⟦ t ↦ ξ.ρ.k ⟧` with-the void inline. `phino` adds none of its own, and a dispatch into a formation-that declares none hands it no `ρ` (the `skip` rule), so `ξ.ρ` there is `⊥`:--<!-- markdownlint-disable MD013 -->--```bash-$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧ ⟧.a.t ⟧' | phino rewrite --normalize --sweet-01-:Δ:x-$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ t ↦ ξ.ρ.k ⟧ ⟧.b.t ⟧' | phino rewrite --normalize --sweet-⊥:x-```--<!-- markdownlint-enable MD013 -->--## Merge--You can merge several 𝜑-expressions into a single one by merging their-top level formations:--```bash-$ cat bytes.phi-⟦ bytes ↦ ⟦ φ ↦ ∅ ⟧ ⟧-$ cat number.phi-⟦-  number ↦ ⟦-    φ ↦ ∅,-    plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧-  ⟧-⟧-$ cat minus.phi-⟦ number ↦ ⟦ minus(x) ↦ ⟦ λ ⤍ L_number_minus ⟧ ⟧ ⟧-$ phino merge bytes.phi number.phi minus.phi --sweet-⟦-  bytes(φ) ↦ ⟦⟧,-  number(φ) ↦ ⟦ plus(x) ↦ L_number_plus:λ, minus(x) ↦ L_number_minus:λ ⟧-⟧-```--## Match--You can test the 𝜑-expression matches against the [rule](#rule-structure)-pattern. The result output contains matched substitutions:--```bash-$ phino match --pattern='⟦ Δ ⤍ !d, !B ⟧' hello.phi-B >> ⟦⟧-d >> 68-65-6C-6C-6F-```--## Compile--By default, `phino` reads its rules from YAML and interprets them at every-step. The `compile` command turns the rules into Haskell instead. Then a-second build of `phino` runs them as plain functions:--```bash-phino compile-cabal build all-```--The command writes the module `compiled/generated/Compiled.hs`, which git-ignores. It compiles the built-in rules of normalization, the-contextualization function 𝒞, and every file you pass with `--rule`. The-`--target` option writes the module somewhere else.--A build links the module in only when the Cabal flag `compiled` is on. If-there is no `cabal.project.local`, `compile` creates one that turns the flag-on. If the file already exists, `compile` leaves it alone and prints the two-lines to add to it:--```text-package phino-  flags: +compiled-```--The compiled rules take exactly the same steps as the YAML ones, so the-output and every `--sequence` stay the same. A few things are still read from-YAML at runtime:--* the rules of morphing (𝕄) and dataization (𝔻);-* a `--rule` file that changed after `compile`;-* the pattern of `match` and the `rewrite:` blocks of the `--symbolic` file.--The `explain` command also reads the rules from YAML.--`compile` refuses a rule it cannot turn into Haskell and names the reason.-For example, it refuses a rule with `having`, a `where` function other than-`contextualize` or `named`, or the conditions `matches` and `part-of`.--A binary built this way refuses to run if the built-in rules changed after-the last `compile`, since it would run rules nobody wrote. Run `compile`-again and rebuild. To test the whole suite against the compiled rules, run:--```bash-make compiled-```--## Explain--You can _explain_ the built-in rules by printing them in [LaTeX][latex]-format. Pass exactly one of `--normalize`, `--morph`, `--dataize` or-`--contextualize` for the rewriting, morphing (𝕄), dataization (𝔻) or-contextualization (𝒞) rules (or `--rule` for a custom rule file):--```bash-$ phino explain --normalize-\begin{tabular}{rl}-\phinoNormalizationRule{alpha}-  { [[ B_1, \tau -> ?, B_2 ]] ( \phiTerminal{\alpha_{i}} -> e ) }-  { [[ B_1, \tau -> ?, B_2 ]] ( \tau -> e ) }-  { $ i = \vert \overline{ B_1 } \vert $ }-  { }-\phinoNormalizationRule{dc}-  { T ( \tau -> e ) }-  { T }-  { }-  { }-...-\phinoNormalizationRule{stop}-  { [[ B ]] . \tau }-  { T }-  { $ [ \tau \char44{} @ \char44{} L ] \cap B = \emptyset $ }-  { }-\end{tabular}-```--The morphing and dataization rules are printed the same way:--```bash-$ phino explain --morph-\begin{tabular}{rl}-\phinoMorphingRule{mf}-  { \mathbb{M}( [[ B ]], e ) }-  { [[ B ]] }-  { }-  { }-...-\phinoMorphingRule{universe}-  { \mathbb{M}( Q, e ) }-  { \mathbb{M}( \phinoNormalize{ e }, e ) }-  { $ e \not= Q $ }-  { }-\end{tabular}-```--```bash-$ phino explain --dataize-\begin{tabular}{rl}-\phinoDataizationRule{delta}-  { \phinoDataize{ [[ B_1, D> \delta_0, B_2 ]] } }-  { \delta_0 }-  { }-  { }-...-\phinoDataizationRule{norm}-  { \phinoDataize{ n } }-  { \phinoDataize{ \mathbb{M}( n, e ) } }-  { }-  { }-\end{tabular}-```--```bash-$ phino explain --contextualize-\begin{phinoContextualizationInference}-  \phinoName{cxi}-  \phinoConclusion{ \phinoContextualize{ \phiTerminal{\xi} }{ k }{ k } }-\end{phinoContextualizationInference}-...-\begin{phinoContextualizationInference}-  \phinoName{cd}-  \phinoPremise{ \phinoContextualize{ n }{ k }{ n_1 } }-  \phinoConclusion{ \phinoContextualize{ n . \tau }{ k }{ n_1 . \tau } }-\end{phinoContextualizationInference}-```--For more details, use `phino [COMMAND] --help` option.--## Rule structure--This is BNF-like yaml rule structure. Here types ended with-apostrophe, like `Attribute'` are built types from 𝜑-expression [AST](src/AST.hs)--```bnfc-Rule:-  name: String-  pattern: String-  result: String-  when: Condition?       # predicate, works with substitutions before extension-  where: [Extension]?    # substitution extensions-  having: Condition?     # predicate, works with substitutions after extension--Condition:-  = and: [Condition]     # logical AND-  | or:  [Condition]     # logical OR-  | not: Condition       # logical NOT-  | eq:                  # compare two comparable objects-      - Comparable-      - Comparable-  | in:                  # returns True if every given attribute exists in-      - Attribute' | [Attribute'] # the union of the given bindings-      - Binding' | [Binding']-  | nf: Expression'      # returns True if given expression in normal form-                         # which means that no more other normalization rules-                         # can be applied-  | absolute: Expression' # returns True if given expression is xi-free, i.e.-                         # there is no ξ outside of a formation: it is Φ, a-                         # formation, a dispatch with a xi-free subject, or an-                         # application with a xi-free subject and argument.-                         # Combined with a normal-form check by the '𝑘'/'!k'-                         # meta variable, which ranges over the absolute-                         # expressions 𝒦 ⊆ 𝒩, used by the Rcopy rule.-  | matches:             # returns True if given expression after dataization-      - String           # matches to given regex-      - Expression-  | part-of:             # returns True if given expression is attached to any-      - Expression'      # attribute in ginve bindings-      - BiMeta'-  | formation:           # returns True if given expression is a formation-      Expression'        # (an abstraction ⟦…⟧); used by morphing 'md'-                         # as 'not (formation 𝑛)', so a non-formation head is-                         # morphed and a formation head is left to 'ml'-  | gt:                  # returns True if the first comparable object is-      - Comparable       # greater than the second one-      - Comparable-  | disjoint:            # returns True if none of the given attributes exists-      - [Attribute']     # in the given bindings-      - Binding'--Comparable:              # comparable object that may be used in 'eq' condition-  = Attribute'-  | Number-  | Expression'--Number:                  # comparable number-  = Integer              # just regular integer-  | IndexMeta'           # 𝑖 (or !i), the index captured by an α𝑖 argument-  | length: BiMeta'      # calculate length of bindings by given meta binding-  | domain: BiMeta'      # calculate number of unique attributes in given-                         # meta binding (excluding 'assets')--Extension:               # substitutions extension used to introduce new meta variables-  meta: [ExtArgument]    # new introduced meta variable-  function: String       # name of the function-  args: [ExtArgument]    # arguments of the function--ExtArgument-  = Bytes'               # !d-  | Binding'             # !B-  | Expression'          # !e-  | Attribute'           # !t-```--Here's list of functions that are supported for extensions:--* `contextualize` - function of two arguments, that rewrites given expression-  depending on provided context according to the contextualization-  [rules](assets/contextualize.jpg)-* `random-tau` - creates an attribute with a random unique name. It accepts no-  arguments; uniqueness is guaranteed across all names already taken in the-  document.-* `dataize` - dataizes given expression and returns bytes.-* `concat` - accepts bytes or dataizable expressions as arguments,-  concatenates them into single sequence and convert it to expression-  that can be pretty printed as human readable string:-  `Φ.string(Φ.bytes⟦ Δ ⤍ !d ⟧)`.-* `sed` - pattern replacer, works like unix `sed` function.-  Accepts two arguments: target expression and pattern.-  Pattern must start with `s/`, consists of three parts-  separated by `/`, for example, this pattern `s/\\s+//g`-  replaces all the spaces with empty string. To escape braces and slashes-  in pattern and replacement parts - use them with `\\`,-  e.g. `s/\\(.+\\)//g`.-* `random-string` - accepts dataizable expression or bytes as pattern.-  Replaces `%x` and `%d` formatters with random hex numbers and-  decimals accordingly. Uniqueness is guaranteed during one-  execution of `phino`.-* `size` - accepts exactly one meta binding and returns size of it and-  `Φ.number`.-* `tau` - accepts `Φ.string`, dataizes it and converts it to attribute.-  If dataized string can't be converted to attribute - an error is thrown.-* `string` - accepts `Φ.string` or `Φ.number` or attribute and converts it-  to `Φ.string`.-* `number` - accepts `Φ.string` and converts it `Φ.number`-* `sum` - accepts list of `Φ.number` or `Φ.bytes` and returns sum of them as `Φ.number`-* `join` - accepts list of bindings and returns list of joined bindings. Duplicated-  `ρ`, `Δ` and `λ` attributes are ignored, all other duplicated attributes are replaced-  with unique attributes using `random-tau` function.--## Meta variables--The `phino` supports meta variables to write 𝜑-expression patterns for-capturing attributes, bindings, etc.--This is the list of supported meta variables:--* `!t` || `𝜏` - attribute-* `!i` || `𝑖` - the index of a positional (α) application argument,-                captured by writing `α𝑖` (or `~!i`)-* `!e` || `𝑒` - any expression-* `!n` || `𝑛` - any expression that is already in normal form (behaves like-                `!e`/`𝑒`, but only binds a sub-expression in NF, so no explicit-                `nf:` guard is needed)-* `!k` || `𝑘` - any expression that is absolute, i.e. xi-free and in normal-                form (ranges over `𝒦 ⊆ 𝒩`); behaves like `!e`/`𝑒` but only-                binds an absolute sub-expression, so no explicit `absolute:`-                or `nf:` guard is needed-* `!B` || `𝐵` - list of bindings-* `!d` || `𝛿` - bytes in meta delta binding-* `!F` || `𝑓` - function name in meta lambda binding-* `!S` || `𝜎` - a symbol standing where a λ name stands (see-                [Symbols](#symbols)). It is spelled the way a meta variable is-                spelled but is a name and no capture: `𝜎1` is one concrete-                symbol, which no substitution ever binds, and a bare `𝜎` in the-                answer of a `--symbolic` entry asks for a fresh one--A meta variable carries a suffix, like `!B1` or `𝜏2`, to name what it-captured, so that the `result`, `when`, `where` and `having` of a rule can-read it back. An index starts with one: a suffix of `0`, as in `!B0` or-`𝜏0`, is refused where it is written, because it is a first index spelled-wrong and no name. A positional argument keeps counting from zero, though,-since `α0` is an index of the calculus and no meta variable.--Written bare, with no suffix at all, a meta variable is anonymous: it matches-whatever term stands in its place, every occurrence on its own, and binds no-name. Two anonymous metas of one kind are therefore two different captures,-which is what lets a pattern ask for any two attributes without inventing-names for them:--```yaml-name: two-attributes-pattern: '⟦ 𝜏 ↦ 𝑒, 𝜏 ↦ 𝑒 ⟧'-result: '⟦ x ↦ ⟦ Δ ⤍ 2A- ⟧ ⟧'-```--Spelled with suffixes, that pattern would read `⟦ 𝜏1 ↦ 𝑒1, 𝜏2 ↦ 𝑒2 ⟧` and-name four captures the result never mentions, while `⟦ 𝜏1 ↦ 𝑒1, 𝜏1 ↦ 𝑒1 ⟧`-would be rejected as a duplicated attribute.--Nothing can refer to an anonymous meta, since it has no name to be referred to-by. Writing one outside a `pattern` (or the `match`, `universe` and `c-match` of-an inference rule) is a mistake in the rule and is reported as the rule loads.--A positional (α) application argument is written as `α0`, `~0` (ASCII), or-`α𝑖`/`~!i` when its index is captured by an `!i`/`𝑖` meta variable.--Incorrect usage of meta variables in 𝜑-expression patterns leads to-parsing errors.--## Benchmark--To run performance benchmarks, you need [Java 8+][java] and [curl][curl].-Maven is downloaded automatically on first run via `benchmark/mvnw`.--The benchmark uses the compiled [`Native`][jna-native] class from-[JNA][jna] — a large real-world Java class — as its test input.-On first run, `make bench` downloads the class, disassembles it to-[XMIR][xmir] via [jeo-maven-plugin][jeo], converts it to 𝜑 using-`phino rewrite`, and caches the results in `benchmark/tmp/`.-Subsequent runs skip straight to the benchmarks.--Besides parsing, printing and rewriting that class, the suite morphs-symbolically. `benchmark/demo.phi` is a small world whose entries name the λ-functions of `benchmark/atoms.yaml`, and each entry is a case of its own, so-that a slowdown of one of them is a line of the report rather than a share of-a single total. The smallest entry — one λ function fired against one unknown-— is timed twice, over the demo world alone and over the same world merged-into the class, and the two numbers say between them what the world around an-entry costs (see [#1291][issue-1291]).--A case whose single run is measured in seconds gets fewer warmups and fewer-batches than a case measured in microseconds, since the whole suite runs-inside one job; the report says how many of each a case was given.--```bash-make bench-```--<!-- benchmark_begin -->--```text-=== parse/phi ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      2094387.514 μs-  avg:        209438.751 μs-  min:        192641.231 μs-  max:        232222.007 μs-  std dev:    15432.075 μs-=== parse/xmir ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      9539874.986 μs-  avg:        953987.499 μs-  min:        891243.312 μs-  max:        1017605.607 μs-  std dev:    44263.498 μs-=== rewrite/normalize ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      20.489 μs-  avg:        2.049 μs-  min:        1.713 μs-  max:        2.604 μs-  std dev:    0.281 μs-=== print/sweet/multiline ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      4017888.697 μs-  avg:        401788.870 μs-  min:        367355.222 μs-  max:        451665.532 μs-  std dev:    27750.906 μs-=== print/sweet/flat ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      4129328.874 μs-  avg:        412932.887 μs-  min:        381222.773 μs-  max:        440759.690 μs-  std dev:    15935.378 μs-=== print/salty/multiline ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      12486021.615 μs-  avg:        1248602.162 μs-  min:        1210030.931 μs-  max:        1315219.913 μs-  std dev:    29724.975 μs-=== morph/symbolic/demo/e1 ===-  warmup:     3 iterations-  batches:    10 x 2-  total:      169315.306 μs-  avg:        8465.765 μs-  min:        8399.346 μs-  max:        8543.902 μs-  std dev:    48.118 μs-=== morph/symbolic/demo/e2 ===-  warmup:     3 iterations-  batches:    10 x 2-  total:      161881.224 μs-  avg:        8094.061 μs-  min:        7995.987 μs-  max:        8224.079 μs-  std dev:    55.390 μs-=== morph/symbolic/demo/e3 ===-  warmup:     3 iterations-  batches:    10 x 2-  total:      234257.502 μs-  avg:        11712.875 μs-  min:        11591.756 μs-  max:        11897.267 μs-  std dev:    100.821 μs-=== morph/symbolic/demo/e4 ===-  warmup:     3 iterations-  batches:    10 x 3-  total:      211951.865 μs-  avg:        7065.062 μs-  min:        6976.291 μs-  max:        7108.785 μs-  std dev:    43.882 μs-=== morph/symbolic/demo/e5 ===-  warmup:     3 iterations-  batches:    10 x 9-  total:      201954.992 μs-  avg:        2243.944 μs-  min:        2209.663 μs-  max:        2273.110 μs-  std dev:    20.511 μs-=== morph/symbolic/native/e5 ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      252909.974 μs-  avg:        25290.997 μs-  min:        24128.018 μs-  max:        26030.789 μs-  std dev:    686.354 μs-=== morph/symbolic/accum/0 ===-  warmup:     3 iterations-  batches:    10 x 2-  total:      203382.050 μs-  avg:        10169.102 μs-  min:        10090.947 μs-  max:        10299.047 μs-  std dev:    60.479 μs-=== morph/symbolic/accum/400 ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      336426.864 μs-  avg:        33642.686 μs-  min:        33345.536 μs-  max:        33999.930 μs-  std dev:    201.060 μs-```--The results were calculated in [this GHA job][benchmark-gha]-on 2026-10-09 at 14:39,-on Linux with 4 CPUs.--<!-- benchmark_end -->--## How to Contribute--Fork repository, make changes, then send us a [pull request][guidelines].-We will review your changes and apply them to the `master` branch shortly,-provided they don't violate our quality standards. To avoid frustration,-before sending us your pull request please make sure all your tests pass:--```bash-make all-```--To generate a local coverage report for development, run:--```bash-make coverage-```--To build a `phino` executable into the root of the repository, run:--```bash-make phino-```--This produces an executable `phino` (or `phino.exe` on Windows) in the-project root, which you can run directly for quick local testing:--```bash-./phino --version-```--You will need [GHC ≥ 9.6.7][GHC] and [Cabal ≥ 3.0 (recommended)][cabal]-or [Stack ≥ 3.0][stack] installed.--[cabal]: https://www.haskell.org/cabal/-[stack]: https://docs.haskellstack.org/en/stable/install_and_upgrade/-[GHC]: https://www.haskell.org/ghc/-[guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html-[xmir]: https://news.eolang.org/2022-11-25-xmir-guide.html-[latex]: https://en.wikipedia.org/wiki/LaTeX-[java]: https://www.java.com/en/download/-[curl]: https://curl.se/-[gha]: https://docs.github.com/en/actions-[jna]: https://github.com/java-native-access/jna-[jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java-[jeo]: https://github.com/objectionary/jeo-maven-plugin-[issue-1291]: https://github.com/objectionary/phino/issues/1291-[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/37945133395+cabal install --overwrite-policy=always phino-0.0.153+phino --version+```++Or download binary from the internet using [curl](https://curl.se/) or+[wget](https://en.wikipedia.org/wiki/Wget):++```bash+sudo curl -o /usr/local/bin/phino http://phino.objectionary.com/releases/macos-15/phino-latest+sudo chmod +x /usr/local/bin/phino+phino --version+```++Download paths are:++* Ubuntu 22.04: <http://phino.objectionary.com/releases/ubuntu-22.04/phino-latest>+* Ubuntu 24.04: <http://phino.objectionary.com/releases/ubuntu-24.04/phino-latest>+* MacOS (ARM): <http://phino.objectionary.com/releases/macos-15/phino-latest>+* MacOS (Intel): <http://phino.objectionary.com/releases/macos-14-large/phino-latest>+* Windows: <http://phino.objectionary.com/releases/windows-2022/phino-latest.exe>++In a [GitHub Actions][gha] workflow, one step downloads the binary+for the runner and puts it on the `PATH`:++```yaml+- uses: objectionary/phino@0.0.153+  with:+    version-file: phino-version.txt+- run: phino --version+```++The file holds the version to install, such as `0.0.150`, and the+whitespace around it is ignored.+Give `version: 0.0.150` instead of `version-file` to write the version+in the workflow itself.+The step fails if no binary is released for the runner, such as+Linux on ARM, or if `phino --version` prints another version.++## Build++To build `phino` from source, clone this repository:++```bash+git clone git@github.com:objectionary/phino.git+cd phino+```++Then, run the following command (ensure you have [Cabal][cabal] installed):++```bash+cabal build all+```++Next, run this command to install `phino` system-wide:++```bash+sudo cp "$(cabal list-bin phino)" /usr/local/bin/phino+```++Verify that `phino` is installed correctly:++```bash+$ phino --version+0.0.0+```++You can ensure scripts are run with a specific version of `phino` using+the `--pin` global option. It exits with an error when the version supplied+doesn't match the installed one:++```bash+phino --pin=0.0.0.67 dataize hello.phi+```++The `--pin-file` global option does the same, but reads the version from+a file, ignoring the whitespace around it:++```bash+phino --pin-file=version.txt dataize hello.phi+```++## Dataize++Then, you dataize the expression:++```bash+$ phino dataize hello.phi+68-65-6C-6C-6F+```++### Symbolic λ functions++Which λ functions exist is a property of the object model being dataized, not+of the calculus, so `phino` implements none of them. They come from a YAML file+given with `--symbolic`, one entry per λ function:++```yaml+- λ: L_number_(plus|times)+  dataize:+    𝛿1: $.ρ+    𝛿2: $.x+  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+```++The `λ` of an entry is the λ names it answers for, as a regular expression, so+the one above stands for `L_number_plus` and `L_number_times`. Under `dataize`+stand the operands it brings down to data through 𝔻, each binding a bytes meta+`𝛿1`, `𝛿2` and so on; under `morph` the operands it reduces to a normal+form through 𝕄, each binding an expression meta `𝑛1`, `𝑛2`. Both blocks are+terms of the calculus, read against the formation being fired, so `ξ` is that+formation and `$.x` its `x`, while `Φ` is the universe. Every entry numbers its+own metas from `𝛿1` and `𝑛1`, and the entry is what tells two `𝛿1` apart.++The term under `𝑛` is what the firing answers with. `phino` normalizes it+exactly as it normalizes anything else, so `--protocol`, `--partial` and+`--max-steps` work on it unchanged. It may name any meta the entry bound,+those of the two blocks below among them.++### Firing what an operand came to++𝕄 fires a λ function only where a dispatch reads the formation carrying it, so+an operand reduced under `morph` may come to a formation whose λ function+nobody fired, such as a call the program passed as an argument and never read.+There is a third block, `evaluate`, and each line of it fires such a formation+through 𝔼 and binds an expression meta of its own to the answer:++```yaml+- λ: L_fork+  morph:+    𝑛1: $.left+    𝑛2: $.right+  evaluate:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    𝑛5: [𝑛3, 𝑛4]+  𝑛: 𝑛5+```++The right-hand side of a line names a meta bound by `morph` or by an `evaluate`+line above it, and nothing else. A formation with a λ function is fired by the+entry its λ name matches, the way 𝕄 fires it, and the line binds its meta to+the normal form of the answer. A formation with no λ function is no call and+stays as it is, and so does `⊥`. A λ name no entry answers gets stuck, as it+does anywhere else, and `--partial` parks it. A λ function the entry names+under `terminators` is not fired at all, since it raises rather than answers.+An answer is not fired again, even when it is one more call: firing it takes+one more line, so how many calls an entry makes is written in its file and not+guessed by `phino`. Each firing of a line is one step deeper, the way a frame+of 𝕄 or 𝔻 is, so a recursion running through `evaluate` lines alone spends+`--max-steps` and is cut by `--acyclic` like any other.++The protocol writes the firing of a line as a block of its own, commented with+the meta it fired, `𝔼(L_second):  # 𝔼(𝑛2)`, and the line binding the answer+the same way, `𝑛3·1 := 𝑛·3·2  # 𝔼(𝑛2)`.++### Rewriting a term into another shape++There is a fourth block, `rewrite`, and it reduces nothing. Each line of it+takes a term another meta of the entry is already bound to, named under `of`,+applies a list of ordinary rules to it, spelled with the very `pattern`,+`result`, `when` and meta variables a rule file uses, and binds an expression+meta of its own to the outcome:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.left+    𝑛2: $.right+  rewrite:+    𝑛3:+      of: 𝑛1+      rules: &bool+        - name: false-literal+          pattern: >-+            ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅,+            φ ↦ ξ.right ⟧ ), !B2 ⟧+          result: >-+            ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅,+            φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ) ⟧+    𝑛4:+      of: 𝑛2+      rules: *bool+  𝑛: 𝑛3+```++The meta under `of` is one bound by `morph`, by `evaluate` or by a `rewrite`+line above it, and nothing else. A rule is tried at every position of the term,+the outermost first; the first rule whose pattern matches a position rewrites+it, and a rewritten position is not walked into again, so a rule whose result+carries its own pattern never loops. Nothing is normalized afterwards: a+rewrite is a substitution the entry vouches for and not a reduction, exactly as+an answer is. A rule writing a `𝜎` into its result, or reading a meta its+pattern never+binds, is refused where the file is read.++This is how a program brings two branches of a fork to one shape before they+are compared. In EO a `Φ.false` is written `φ ↦ ξ.right` while a bool a firing+answered carries a symbol, and the two are one value in two spellings that+only the program knows to be one. The block runs before `symbolize` on purpose:+a result may write the datum a literal stands for, as `⟦ Δ ⤍ 00- ⟧` above, and+that datum is then stood into a known symbol the join can pair.++### Standing data into unknowns++There is a fifth block, `symbolize`, and it reduces nothing. It takes a term+another meta of the entry is already bound to and binds an expression meta of+its own to that same term with every datum in it standing for an unknown:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.left+    𝑛2: $.right+  symbolize:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  𝑛: 𝑛3+```++The right-hand side of a line names a meta bound by `morph`, by `evaluate`, by+`rewrite` or by a `symbolize` line above it, and nothing else; a term nobody+reduced has no data to stand. Every `Δ ⤍ b` binding of that term becomes a+`λ ⤍ 𝜎k` naming a fresh symbol, one per occurrence, so `⟦ Δ ⤍ b ⟧` reads as+`⟦ λ ⤍ 𝜎k ⟧` and a+literal tuple gets several. A term carrying no datum passes through as it was.++Only the `φ` chain is walked. A term carries the value it stands for where+that chain ends, so a datum standing anywhere else says nothing about the term+and is left alone, the whole subtree of it. What sits under `ρ` belongs to the+object around this one, and a normal form drags the universe it was reduced+inside along under `ρ`, so a walk reaching into it would stand the data of the+whole program into unknowns to say one thing about one term. What sits under a+method is code and not data: the `-1` of a `neg ↦ ⟦ φ ↦ ξ.ρ.times( -1 ) ⟧`+nobody has called is the body of a method, and minting a symbol for it, and for+every other literal every method of the carrier declares, would write unknowns+nobody ever reads.++This is what lets an entry compare two branches of a fork. A literal is sugar+for `Φ.number( Φ.bytes( ⟦ Δ ⤍ … ⟧ ) )`, so a branch computed from a literal+keeps a datum three levels down where a branch computed from an unknown keeps+`⟦ λ ⤍ 𝜎 ⟧`. A `Δ` against a `λ` is a difference in kind and not in value, and+after the stage both branches carry `⟦ λ ⤍ 𝜎 ⟧` where they differ.++### Joining the branches of a fork++A branching λ function answers neither of its branches. Which one the program+takes is decided by a value nobody worked out, so handing one of them through+would drop the branch point from the program altogether and a reader would see+the condition computed and thrown away. `join` is the sixth block, and it+reduces nothing either: it takes two metas the entry has bound already and+binds one of its own to the two terms joined into one.++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.left+    𝑛2: $.right+  symbolize:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    𝑛5: [𝑛3, 𝑛4]+  𝑛: 𝑛5+```++A line names two metas bound by `morph`, by `evaluate`, by `rewrite`, by+`symbolize` or by a `join` line above it, and never three: it stands for a+choice between two branches, and a walk over three terms in parallel is no such+choice. The meta it binds is one like any other, so the answer may name it+alone, as above, or stand+it inside a larger term.++`phino` takes the two terms and requires them to match verbatim, with one+exception: where `⟦ λ ⤍ 𝜎A ⟧` in one meets a different `⟦ λ ⤍ 𝜎B ⟧` in the+other, it mints a fresh `𝜎C` and stands it there. The same symbol on both sides+stays as it is, and the same pair met again further down gets the same fresh+symbol, since it is one choice however often the two terms differ by it; two+different pairs get two fresh symbols. Two identical terms join into that same+term and nothing is minted at all. The join keeps the type by construction,+being the terms' own shape, so the file needs to know nothing about carriers.++Only the `φ` chain is compared, exactly as `symbolize` stands only that chain+into unknowns: a term carries the value it stands for where its `φ` chain ends,+so every other binding is taken from the first branch, the whole subtree of it,+and never compared at all. The two branches of a fork reach their normal forms+in scopes of their own, so their `ρ` differ wherever the reduction left a+trace, and comparing them would refuse the join over something saying nothing+about either branch; a method is the same, its body being code nobody has+called, so two branches differing inside one are not two values. The joined+term keeps the methods and the `ρ` of the first of the two, being of its shape,+which is what lets the program go on dispatching on what the fork answered.++A join is only ever between two expressions and a datum is never joined with+anything, which is why `symbolize` runs before it: a known symbol, one that+stage minted for a datum, is a symbol like any other here, so a literal branch+joins with a computed one and two literal branches join too. Any other+difference — a datum against a symbol, two different data, a binding one term+carries and the other does not — is no join at all, and the firing gets stuck+the way a λ function no entry answers does, so `--partial` parks it rather than+aborting the run. A fork whose branches differ in structure, such as a `Φ.true`+and a `Φ.false` written as `φ ↦ ξ.left` against `φ ↦ ξ.right`, is stuck, and+bringing two such branches to one shape is the program's job and not `phino`'s,+which its entry does in a `rewrite` block.++A bare symbol, `⟦ λ ⤍ 𝜎A ⟧` with no carrier around it, is what an entry+answering `⟦ λ ⤍ 𝜎 ⟧` makes, and it joins with a formation too. It has no `φ`+chain of its own, only the symbol, so the join pairs `𝜎A` with the symbol the+`φ` chain of the other term ends in, mints a+fresh `𝜎C` for that pair the usual way, and answers the bare `⟦ λ ⤍ 𝜎C ⟧`.+The methods of the formation are dropped, since the other branch may have+none of them. A formation whose `φ` chain ends in a datum, or which has no `φ`+at all, does not join with a bare symbol.++A fork whose one branch calls a method often meets a decorator. In+`if. (value.gte 0) (0.plus value) value.neg` the first branch comes to a+number `X`, while `value.neg` is a method, `times -1 > @`, and comes to+`⟦ φ ↦ X ⟧`, with the number one level down. The two differ in shape, so the+join is refused. An entry that says `lenient: true` beside its `join` block+lets it through:++```yaml+- λ: L_fork+  morph:+    𝑛1: $.left+    𝑛2: $.right+  join:+    𝑛3: [𝑛1, 𝑛2]+  lenient: true+  𝑛: 𝑛3+```++Every `join` line of such an entry joins a term with a formation whose `φ` is+that term, in either order, and answers `⟦ φ ↦ X ⟧`, with symbols minted inside+`X` the usual way. Where both terms are formations, the one with the longer `φ`+chain is taken for the decorator. The answer drops the `ρ` and the other+attributes of the decorator, so it is not exactly the object either branch+made, which is why an entry is strict unless it says otherwise. Dataization+still reaches the same data through it. An entry that says `lenient: true` and+has no `join` line is refused when the file is read.++A lenient entry also joins a bare symbol with a term that holds exactly one+symbol anywhere inside. In `bool.and` one branch comes to a bare+`⟦ λ ⤍ 𝜎1 ⟧` and the other to `⟦ φ ↦ Φ.bool( if ↦ 𝑘 ) ⟧`, where `𝑘` is+`⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧`. The `φ` of the second is a call, so its+`φ` chain ends in no symbol and a strict join stops there. A lenient one+answers a copy of the second term with `𝜎2` replaced by a fresh `𝜎3`, which+stands for either `𝜎1` or `𝜎2`. The copy keeps every binding of the term, `ρ`+included. A term with no symbol, or with two, is still refused.++A lenient entry takes one more step before it gives up. A branch may be an+ordinary object, such as `⟦ φ ↦ ξ.eq, eq ↦ ⟦ λ ⤍ L_eq ⟧ ⟧`: a formation with+no `λ`, whose `φ` reads an attribute of the object itself. Morphing stops at a+formation, so the value of such a branch stays one call away and it joins with+nothing. When a join fails, a lenient entry morphs the `φ` of every such+branch, fires what it comes to, the way an `evaluate` line does, and joins+once more. The step is taken once and is written to the protocol like any+operand. Its firing counts against `--max-steps`, as one of an `evaluate`+line does, and `--acyclic` cuts it when it fires again what it fired before.+A branch whose `φ` reads nothing of its own object, such as+`⟦ φ ↦ Φ.bool( … ) ⟧`, is left as it is, since morphing it only expands the+call.++One term being `⊥` is the exception, since `if. cond value ⊥` is how EO spells+"raise unless `cond`": the program raises on that side of the condition and+has a perfectly good value on the other. The join then mints nothing, binds+its meta to the other term as it stands and writes on which side the program+raises, naming the condition by what the first `dataize` operand of the entry+came down to, as `terminate(𝔻(𝜎2:λ), right)  # 𝑛4` in the text format and+`<terminate symbol="𝜎2" branch="right"/>` in the markup.++EO raises with an object too, a copy of `⟦ message ↦ ∅, λ ⤍ L_throw ⟧`, whose+λ function the file leaves out on purpose, so that an error of the program+stays an error at runtime. An entry names such functions under `terminators`:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.left+    𝑛2: $.right+  join:+    𝑛3: [𝑛1, 𝑛2]+  terminators: [L_throw]+  𝑛: 𝑛3+```++A `join` line of such an entry takes a formation with `λ ⤍ L_throw`, or an+application of one, for a branch that raises, exactly as it takes `⊥`: it binds+its meta to the other term and writes `terminate(…)`. An `evaluate` line never+fires `L_throw`, since the entry names it under `terminators`, so the branch+reaches the join as `morph` left it. The record names the `message` of that+term as its last argument, as written, as+`terminate(𝔻(𝜎2:λ), right, 7.plus( 8 ))  # 𝑛4` in the text format and+`<terminate symbol="𝜎2" branch="right" message="7.plus( 8 )"/>` in the+markup, so the error path can be lowered as well as the other branch. A raising+term with no `message` names none. A name under+`terminators` that a key of the file matches is refused when the file is read,+since that function fires rather than raises.++Every symbol a join mints is written into the protocol as a fact of its own,+so a reader ties it to the two it stands for without diffing the terms; the+section on `--protocol` below shows one.++### Symbols++An entry answers, it never computes. The job of these functions is symbolic+morphing: what `5.plus( 6 )` comes to is the arithmetic of the object model and+not `phino`'s, so an entry answers a term carrying a symbol standing for a+value nobody worked out, and the data its `dataize` operands came down to is+not its to read. An answer mentioning a `𝛿` is refused where the file is read.++`𝜎` is a meta of the calculus, beside `𝑛`, `𝛿` and `𝑓`, and it stands where+a λ name stands. In a term, `𝜎1` is a concrete symbol: a λ function nothing+answers, which is what makes the value the term carries unknown. Firing it is+therefore the same question as firing a λ name the `--symbolic` file does not+carry, and gets the same answer: 𝔼 stops there, the protocol records the site as+`unanswered(𝜎1)`, and `--partial` leaves the term where it stands. Dispatching+an attribute off a symbol — `⟦ λ ⤍ 𝜎1 ⟧.plus( 5 )` — therefore taints its own+binding and nothing else; what stands beside it still computes. In an answer, a+bare `𝜎` asks for a fresh one, minted as the firing happens and numbered by the+run, so no two unknowns are ever spelled alike. Minting starts after the symbols+the program already carries, so a run over the 𝜑-program an earlier run wrote+never spells a fresh symbol like one already standing there.++Dataizing a symbol never gets stuck. It answers a fixed datum, 42+(`40-45-00-00-00-00-00-00`), the same one for every symbol, so 𝔻 always+answers, a `𝛿` always holds concrete data and no firing ever declines for the+lack of it:++```bash+$ cat sum.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,+  φ ↦ 5.plus( 6 )+⟧+$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho sum.phi+40-45-00-00-00-00-00-00+```++### The keys of the file++Each `λ` is a regular expression, and it must match the whole λ name, so a+plain name such as `L_number_plus` means that one function and nothing else,+while `L_number_.*` stands for every function of `number`. The keys are unique:+nothing tells two entries under one key apart, so a second entry under a key is+unreachable and the file is refused rather than merely redundant.++A λ name no key matches has no λ function at all, so 𝔼 gets stuck on it.+Without `--symbolic` there is no entry at all and every λ function gets stuck:++```bash+$ phino dataize --sweet --hide-rho sum.phi+[ERROR]: No entry of --symbolic answers the λ function 'L_number_plus'+```++The file is read before anything is parsed or reduced, so a key that is no+regular expression, an operand that is no meta of the kind its block binds, or+an answer the calculus cannot read fails the run up front rather than half-way+through a derivation.++### Recording what fired++Every λ function fired on the way to the answer may be recorded in a+machine-readable protocol, with the `--protocol` option. The protocol is a+tree: the run at the top, one block per firing under it, and inside the block+the operands the firing bound and the term it answered with. A judgment started+at a new site opens a block too, and what it does stands under it.++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --symbolic=atoms.yaml --protocol=atoms.txt --quiet \+    --sweet --hide-rho sum.phi+$ cat atoms.txt+𝔻(Φ):+  𝕄(Φ):+    𝑛·0·1 := 5+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )+  𝔼(L_number_plus):+    𝔻(Φ.a🌵0):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := 5+        𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )+      𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0+    𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)+    𝔻(Φ.a🌵1):+      𝕄(Φ.a🌵1):+        𝑛·1·3 := 6+        𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )+      𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0+    𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)+    𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛+    𝕄(𝑛·1·5):+      𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )+    𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)+```++<!-- markdownlint-enable MD013 -->++`𝔻(…)` is the run and the term it was aimed at, `𝕄(…)` where the run is a+morphing, and `𝔼(…)` is one firing, named by the entry that answered it. A line+standing in a block needs no comment to say which judgment made it and where:+the block it stands in says that already. A comment is written only where it+says something the block does not, such as the term an operand was reduced+from, `𝔻(ξ.ρ)`, or the meta an answer was read from, `𝑛`, and every comment+naming a judgment is of that shape: a judgment applied to a term, which is the+intent the value beside it came from. The firings are numbered across the+whole run, in the order they open, so `𝛿1·2` is the value bound to `𝛿1` by+the second firing of the run, whichever λ function that was, `𝑛1·2` the same+for a `morph` meta, and `𝑛·3·2` the answer of the third firing, so+`𝑛1·2 := 𝑛·3·2` reads "the `𝑛1` of this firing is what the third firing+answered". One firing binds a meta once and no two firings+share a number, so every one of these names stands on exactly one line of the+file and a line naming another one points at it and no other.++A nested `𝔻(…):` or `𝕄(…):` line opens a block where a judgment starts at a+site other than the one of the block it stands in, and names that site. What+the judgment does stands one level deeper under it: the objects its+normalization makes, the firings it demands, and the judgments it starts in+turn. A reader therefore sees which judgment made each line and at which site,+rather than a flat list of lines each saying so. 𝔻 of a term that is no+formation asks 𝕄 for the normal form of the same term first, through its+`norm` rule, so a `𝕄(…):` block often stands right inside a `𝔻(…):` block of+the same site, and `𝑛·0·1 := 5` above is made by that 𝕄 and not by 𝔻. A+judgment started at the site of the block it stands in, and by the same+judgment, opens nothing, and what it does joins that block. A block is written+only once a line stands in it, so a judgment that made nothing worth a line+leaves no empty heading behind. The line is no firing: it binds no meta and+takes no number, so the metas of the firings under it are numbered as if it+were not there. A `𝔻(…):` block ends with the datum the `delta` rule found,+such as `𝛿·1·1 := 40-14-00-00-00-00-00-00`, named by the firing it stands in+and a counter of its own, and the operand line right after the block names it.+A datum of eight bytes is commented with the number those bytes hold as an+IEEE 754 double, `# 5.0` here, since that is how a number keeps its data.+In the run above 𝔻 gets into each number the entry brings down, and through+its `φ` into the bytes that number holds, in the same block, since the site is+the same.++An answer stands on two lines and not one. A firing answers the term its entry+wrote and `phino` morphs that term before standing it back into the program, so+`𝑛·1·5` is what the entry wrote, with the symbols this firing minted already in+it, commented with `𝑛` to name the key it was read from, and `𝑛·1·7` is the+normal form 𝕄 made of it, commented with `𝕄(𝑛·1·5)` to say where it came from.+That morphing opens a block of its own, `𝕄(𝑛·1·5):`, named after the term it+started from, since that term stands nowhere in the program a locator could+reach. It is the same morphing every other term goes through, and writing only its+outcome would have the formation of `number` appear in place of the three+tokens the entry wrote with nothing saying why. Whatever that morphing fires+or applies writes its lines between the two, exactly where a firing an operand+took opens its block, so the order the lines come in is the order the work was+done in.++`𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )` is an object an application made. The `copy`+rule of normalization fills a void of a formation with the argument it was+given, and so makes a new object, whatever judgment is running and wherever in+the term the application stands. The line binds that object to a fresh `𝑛` and+spells the application: the object applied on the left and the argument it got+on the right, so `5` is `Φ.number( φ ↦ … )` in sugar. The `ρ` a dispatch gives+the attribute it takes writes no line. The block the line stands in names the+judgment that normalized and the site it stood at. The name is counted with the+metas of the firing the line stands in, `𝑛·1·5` before it and `𝑛·1·7` after it,+and an object made outside every firing is counted under `𝑛·0`. From then on+every line spells the object by that name rather than as the formation it is,+and the application by the same name, since `copy` makes the same object of the+same application wherever it stands: `𝑛·1·1` is the object `5` made,+`𝑛·0·1.plus( x ↦ 6 )` is the `plus` of the first `5` applied to `6`, one line+for one call, and `𝑛·1·7 := 𝑛·1·6` says the firing answered with the object 𝕄+made of what the entry wrote. Such a line names its head and its argument that+way, each on its own, and never the application as a whole, so a line spells out+every application, even one making an object an earlier one already made, the+way `𝑛·1·1 := 5` does, and a later line names the latest of them. Only an object+still standing as it was made is named: once a rule rewrote a part of it, it is+another object and it is spelled out again.++Where an operand came down to the datum a symbol stands for, the protocol writes+`𝔻(𝜎1:λ)` in place of that 42 (`𝜎1:λ` is the formation `⟦ λ ⤍ 𝜎1 ⟧`, in the+sugar every sweet term is written with), so a reader sees that the value was+manufactured rather than read out of the program. A `𝜎` is the name of a λ+function and no term of its own, so 𝔻 is applied to the formation carrying it+and never to the name alone.++A `symbolize` line writes a line per fresh symbol it minted, ahead of the line+binding the term that carries them, and that line is a fact and no assignment:+`𝔻(𝜎44:λ) == 3F-F0-00-00-00-00-00-00` says that dataizing the formation+`𝜎44` names answers those bytes. Nothing binds bytes to a `𝜎`, since it is+neither a datum nor a term. A consumer reading the protocol back treats a+symbol with such a fact as a constant and every other symbol as an unknown.++The line binding the term of a `symbolize` one is commented with the meta it+was told to stand, `𝑛3·1 := ⟦ λ ⤔ 𝜆8 ⟧  # 𝑛1`, and with no judgment+around it: standing the data of a term into unknowns is the file's own+operation and nothing of the calculus runs there, so the line names a meta of+the entry the way a `join` line names the two it joined. A comment carries the+letter of a judgment exactly where a judgment made the value and the block+around the line does not already say so.++The site of a firing is a locator, and the block a firing stands in names it,+under the letter of the judgment that asked for the firing: 𝔼 is fired by the+`ml` rule of morphing and by the `fire` rule of dataization, so a firing in the+block `𝕄(Φ.demo.a.φ):` is a λ function fired while 𝕄 was reducing that+binding, and one standing right in `𝔻(Φ):` was fired because dataization+demanded data of `Φ`. A chain such as `5.plus( 6 ).plus( 7 )` writes both: the+inner call is fired inside the `𝕄(Φ):` block, while 𝕄 reduces the head of the+outer dispatch, and the outer one right in `𝔻(Φ):`, because 𝔻 asked for the+data. A line standing right in a firing has no block of its own around it, so+a line there that a judgment wrote at a site no block names, such as+`starved(…)` below, carries that judgment and site as its comment. The site+itself is where in the program the firing+belongs: the term the run was aimed at, so `Φ` for a run that was aimed at+nothing in particular. An operand of a firing is reduced bound to a synthetic+attribute of the universe (see `--inside` below), so a λ function fired while+it came down is written under that attribute and not under the site of the+firing that asked for it, and a formation an `evaluate` line fires is written+under the meta it fired, `𝔼(𝑛2)`.++An operand line ends in the judgment that reduced it and the term it was+reduced from, written as a comment after two spaces and `#`. The value alone+says what the meta was bound to and neither what it was bound from nor what+was done to it, so `𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)` reads "the `𝛿1` of this firing+is the `ρ` of the formation brought down through 𝔻, and it came down to the+datum `𝛿·1·1` above", where a `morph` operand reads `𝑛1·5 := 𝑛·3·4  # 𝕄(ξ.then)`+and says that the `then` of the formation reached its normal form through 𝕄.+Which of the two judgments ran is the whole difference between a line ending+in data and one ending in a term. It is the very term the entry wrote under+that meta, spelled the way the calculus reads it — `$` is read as `ξ` — so a+reader never has to open the `--symbolic` file beside the protocol and match+every line by λ name and meta number.++`unanswered(…)` is a λ name no entry answers, standing where the block of its+firing would have stood. Nothing fired, so nothing opens under it. The line is+commented with the judgment that asked and the formation it was asking about,+`𝕄(L_none:λ)`, which no block names, the way an operand line is commented+with the term it was reduced from: 𝔼 is fired by the `ml` rule of morphing+and by the `fire` rule of dataization, so the letter says where in the+reduction the site stands and the term says which object the λ function that+could not fire belongs to. It is written whether or not `--partial` goes on+to park the run, since the protocol records what 𝔼 was asked for, and a+question it could not answer belongs there as much+as one it could — once per site and not once per attempt, since a site+`--partial` parks stays in the residue, where a later step may meet it again:++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --symbolic=atoms.yaml --protocol=atoms.txt --quiet \+    --sweet --hide-rho stuck.phi+[ERROR]: No entry of --symbolic answers the λ function 'L_number_nope'+$ cat atoms.txt+𝔻(Φ):+  𝕄(Φ):+    𝑛·0·1 := 5+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )+    𝔼(L_number_plus):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 5+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵1):+        𝕄(Φ.a🌵1):+          𝑛·1·3 := 6+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛+      𝕄(𝑛·1·5):+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)+  unanswered(L_number_nope)  # 𝔻(L_number_nope:λ)+```++<!-- markdownlint-enable MD013 -->++The very same file comes back with `--partial`, where the run answers the+residue instead of failing: what `phino` could not decide is a property of the+program and not of the option that decides what to do about it.++Three more lines say why a firing gave no answer.+`stuck(L_outer)` is the last line of a firing that got stuck, naming the λ+function it got stuck on, which is seldom the one `unanswered(…)` names: that+one is written where it was asked for, and this one closes every firing the+failure passed on its way out.+`stall(L_outer)` stands under a firing that `--acyclic=plausible` answered+with the stall an earlier firing of the same formation kept, so a told stall+never reads as a fresh firing that wrote nothing.+`starved(3)` is where `--max-steps=3` ran out, whether or not `--partial` goes+on to park it. It stands in the block of the judgment and the site the+reduction stood at, and where the budget ran out before that block could open,+right in a firing, it is commented with them instead, `starved(3)  # 𝔻(Φ.a🌵2)`:++```text+𝕄(Φ.x):+  𝕄(Φ.x.arg):+    𝔼(L_outer):+      𝔻(Φ.a🌵0):+        starved(3)+      stuck(L_outer)+  𝔼(L_outer):+    𝔻(Φ.a🌵1):+      𝔼(L_outer):+        starved(3)  # 𝔻(Φ.a🌵2)+    stuck(L_outer)+```++The markup spells them `<unfinished λ="L_outer"/>`, `<stall λ="L_outer"/>`+and `<starved limit="3" by="dataize" at="Φ.a🌵2"/>`.++`timeout(5)` is where `--max-seconds=5` ran out, placed and commented the same+way and written at the first step the deadline refused.+The run ends there, with or without `--partial`, so it is always the last line+of the protocol.+The markup spells it `<timeout limit="5" by="morph" at="…"/>`.++`spent(3)` is where `--max-firings=3` ran out, placed and commented the same+way and written where the firing it refused would have started.+The markup spells it `<spent limit="3" by="dataize" at="…"/>`.++Every term is 𝜑 on a single line, whatever `--output` and `--flat` say about+the result of the run, so a program reading the protocol back never has to know+what the run printed. The file is truncated at the beginning of every run, so+it always holds the firings of exactly one run.++The blocks come in the order the reduction walks the term, and that order is+not what the dependencies are read from — the symbols are. Take a comparison+nobody can decide, a fork branching on it, and an `atoms.yaml` of three+entries, the λ functions named briefly to keep the lines below short:++```yaml+- λ: L_plus+  dataize:+    𝛿1: $.ρ+    𝛿2: $.x+  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+- λ: L_gt+  dataize:+    𝛿1: $.ρ+    𝛿2: $.x+  𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.then+    𝑛2: $.else+  evaluate:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    𝑛5: [𝑛3, 𝑛4]+  𝑛: 𝑛5+```++<!-- markdownlint-disable MD013 -->++```bash+$ cat fork.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  bool ↦ ⟦ if ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_plus ⟧, gt(ρ, x) ↦ ⟦ λ ⤍ L_gt ⟧ ⟧,+  foo(x) ↦ ⟦+    φ ↦ ξ.x.gt( 0 ).if( ξ.x.plus( ξ.x.plus( 1 ) ), ξ.x.plus( ξ.x ) ).plus( 5 )+  ⟧,+  demo ↦ ⟦ a ↦ Φ.foo( Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ⟧+⟧+$ phino dataize --symbolic=atoms.yaml --locator=Q.demo.a \+    --protocol=fork.txt --quiet --sweet --hide-rho fork.phi+$ cat fork.txt+𝔻(Φ.demo.a):+  𝕄(Φ.demo.a):+    𝑛·0·1 := Φ.foo( x ↦ Φ.number( φ ↦ 𝜎1:λ ) )+    𝑛·0·2 := Φ.number( φ ↦ 𝜎1:λ )+    𝑛·0·3 := 𝑛·0·2.gt( x ↦ 0 )+    𝔼(L_gt):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := Φ.number( φ ↦ 𝜎1:λ )+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵1):+        𝕄(Φ.a🌵1):+          𝑛·1·2 := 0+          𝑛·1·3 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 00-00-00-00-00-00-00-00  # 0.0+      𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)+      𝑛·1·4 := Φ.bool( if(then, else) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )  # 𝑛+      𝕄(𝑛·1·4):+        𝑛·1·5 := Φ.bool( if(then, else) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )+      𝑛·1·6 := 𝑛·1·5  # 𝕄(𝑛·1·4)+    𝑛·0·4 := ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ 𝜎2:λ ⟧( then ↦ 𝑛·1·1.plus( 𝑛·1·1.plus( 1 ) ) )+    𝑛·0·5 := 𝑛·0·4( else ↦ 𝑛·1·1.plus( 𝑛·1·1 ) )+    𝔼(L_fork):+      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)+      𝕄(Φ.a🌵3):+        𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )+        𝑛·2·2 := 𝑛·2·1.plus( x ↦ 𝑛·2·1.plus( 1 ) )+      𝑛1·2 := 𝑛·2·2  # 𝕄(ξ.then)+      𝕄(Φ.a🌵4):+        𝑛·2·3 := Φ.number( φ ↦ 𝜎1:λ )+        𝑛·2·4 := 𝑛·2·3.plus( x ↦ 𝑛·2·3 )+      𝑛2·2 := 𝑛·2·4  # 𝕄(ξ.else)+      𝔼(L_plus):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·3·1 := Φ.number( φ ↦ 𝜎1:λ )+        𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·2 := Φ.number( φ ↦ 𝜎1:λ )+            𝑛·3·3 := 𝑛·3·2.plus( x ↦ 1 )+          𝔼(L_plus):+            𝔻(Φ.a🌵7):+              𝕄(Φ.a🌵7):+                𝑛·4·1 := Φ.number( φ ↦ 𝜎1:λ )+            𝛿1·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+            𝔻(Φ.a🌵8):+              𝕄(Φ.a🌵8):+                𝑛·4·2 := 1+                𝑛·4·3 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+              𝛿·4·1 := 3F-F0-00-00-00-00-00-00  # 1.0+            𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)+            𝑛·4·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+            𝕄(𝑛·4·4):+              𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )+            𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)+        𝛿2·3 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)+        𝑛·3·4 := Φ.number( φ ↦ 𝜎4:λ )  # 𝑛+        𝕄(𝑛·3·4):+          𝑛·3·5 := Φ.number( φ ↦ 𝜎4:λ )+        𝑛·3·6 := 𝑛·3·5  # 𝕄(𝑛·3·4)+      𝑛3·2 := 𝑛·3·6  # 𝔼(𝑛1)+      𝔼(L_plus):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵9):+          𝕄(Φ.a🌵9):+            𝑛·5·1 := Φ.number( φ ↦ 𝜎1:λ )+        𝛿1·5 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵10):+          𝕄(Φ.a🌵10):+            𝑛·5·2 := Φ.number( φ ↦ 𝜎1:λ )+        𝛿2·5 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)+        𝑛·5·3 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛+        𝕄(𝑛·5·3):+          𝑛·5·4 := Φ.number( φ ↦ 𝜎5:λ )+        𝑛·5·5 := 𝑛·5·4  # 𝕄(𝑛·5·3)+      𝑛4·2 := 𝑛·5·5  # 𝔼(𝑛2)+      𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }+      𝑛5·2 := ⟦ φ ↦ 𝜎6:λ, plus(x) ↦ L_plus:λ, gt(x) ↦ L_gt:λ ⟧  # [𝑛3, 𝑛4]+      𝑛·2·5 := 𝑛5·2  # 𝑛+      𝑛·2·6 := 𝑛5·2  # 𝕄(𝑛·2·5)+    𝑛·0·6 := Φ.number( φ ↦ 𝜎6:λ ).plus( x ↦ 5 )+  𝔼(L_plus):+    𝔻(Φ.a🌵11):+      𝕄(Φ.a🌵11):+        𝑛·6·1 := Φ.number( φ ↦ 𝜎6:λ )+    𝛿1·6 := 𝔻(𝜎6:λ)  # 𝔻(ξ.ρ)+    𝔻(Φ.a🌵12):+      𝕄(Φ.a🌵12):+        𝑛·6·2 := 5+        𝑛·6·3 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )+      𝛿·6·1 := 40-14-00-00-00-00-00-00  # 5.0+    𝛿2·6 := 𝛿·6·1  # 𝔻(ξ.x)+    𝑛·6·4 := Φ.number( φ ↦ 𝜎7:λ )  # 𝑛+    𝕄(𝑛·6·4):+      𝑛·6·5 := Φ.number( φ ↦ 𝜎7:λ )+    𝑛·6·6 := 𝑛·6·5  # 𝕄(𝑛·6·4)+```++<!-- markdownlint-enable MD013 -->++`𝜎3` is minted by the fourth firing and consumed by the third as+`𝔻(𝜎3:λ)`, and `𝜎2` by the first and consumed by the fork. `𝜎4` and+`𝜎5` are what the two branches came to, and the fork consumes both: its `join`+line makes them one term carrying `𝜎6`, which the `plus( 5 )` standing after+the fork then reads as `𝔻(𝜎6:λ)`. The line+`𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }` is what ties the three+together: dataizing the formation `𝜎6` names answers what dataizing one of the+other two answers. A reader who knows the entry knows that `𝛿1` is what decides+between them and that the first of the two belongs to `then`. Nothing is+assigned to a `𝜎`, it being the name of a λ function, so the fact stands on a+line of its own the way what a `symbolize` line knows does, and the line under+it binds the meta, commented with the two metas it joined.++Were the fork to answer one of its branches instead, the value of the other+would be minted and never consumed, and `foo` would read as a program that+computes a condition, computes both branches and then drops the branch point.++A firing that happened while an operand of another was being reduced stands one+level deeper, under the firing that asked for it. The `plus( 1 )` of `then` is+fired while the outer `plus` brings its `x` down, so it stands inside that+firing. The two branches reach the fork as calls 𝕄 left unfired, and its+`evaluate` lines fire them, so both stand inside the fork, each commented with+the meta it fired, `𝔼(𝑛1)` and `𝔼(𝑛2)`.++### The protocol as XML++The name of the file decides which of the two formats `--protocol` writes: a+name ending in `.xml` gets the same tree as markup, every other name gets the+indented text above. There is no option for it, since a caller who asks for a+file called `atoms.xml` and gets text back has been told nothing useful. Here+is the run at the top of this section again:++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --symbolic=atoms.yaml --protocol=atoms.xml --quiet \+    --sweet --hide-rho sum.phi+$ cat atoms.xml+<?xml version="1.0" encoding="UTF-8"?>+<protocol>+  <dataize at="Φ">+    <morph at="Φ">+      <applied meta="𝑛·0·1" by="morph" at="Φ" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>+      <applied meta="𝑛·0·2" by="morph" at="Φ" of="𝑛·0·1.plus"><attr name="x">6</attr></applied>+    </morph>+    <evaluate λ="L_number_plus" by="dataize" at="Φ">+      <dataize at="Φ.a🌵0">+        <morph at="Φ.a🌵0">+          <applied meta="𝑛·1·1" by="morph" at="Φ.a🌵0" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>+          <applied meta="𝑛·1·2" by="morph" at="Φ.a🌵0" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00:Δ</attr></applied>+        </morph>+        <delta meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</delta>+      </dataize>+      <bind meta="𝛿1·1">𝛿·1·1</bind>+      <dataize at="Φ.a🌵1">+        <morph at="Φ.a🌵1">+          <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵1" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )</attr></applied>+          <applied meta="𝑛·1·4" by="morph" at="Φ.a🌵1" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00:Δ</attr></applied>+        </morph>+        <delta meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</delta>+      </dataize>+      <bind meta="𝛿2·1">𝛿·1·2</bind>+      <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>+      <built meta="𝑛·1·5">Φ.number( φ ↦ 𝜎1:λ )</built>+      <morph at="𝑛·1·5">+        <applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>+      </morph>+      <answer meta="𝑛·1·7">𝑛·1·6</answer>+    </evaluate>+  </dataize>+</protocol>+```++<!-- markdownlint-enable MD013 -->++The root is `<protocol>`, and the run stands inside it, named after the+judgment it ran — `<dataize>` for a 𝔻, `<morph>` for a 𝕄 — with `at` naming+the term it was aimed at, which is what the text format opens with as `𝔻(Φ)`.+After the run the root holds `<msec>`, `<firings>` and `<fps>`: how long the+run took, how many λ functions it fired, and how many it fired per second. The+text format ends with the same three as `msec(…)`, `firings(…)` and `fps(…)`.+The examples here leave them out, since the time differs from run to run.+`<evaluate>` is one firing of 𝔼, `λ` naming the entry that answered it, `by`+naming the judgment that asked for the firing — the same word the run is named+after and an `<unanswered>` carries —+and `at` naming the site it was fired at. The text format leaves those two to+the block the firing stands in, `𝔻(Φ):`, while the markup keeps them on every+element, so a program reading it never has to track the elements around one.+A nested `<dataize at="Φ.a🌵0">` or `<morph at="Φ.a🌵0">` is a judgment started+at a new site, which the text format writes as `𝔻(Φ.a🌵0):` or `𝕄(Φ.a🌵0):`,+and `at` names that site. Whatever the judgment does is written inside the+element, so it closes where the text format drops back to the indentation it+opened at, and like the text line it counts nothing and names no meta.+`<delta>` is the datum the `delta` rule found, `meta` naming it the way the+text format does, and the `<bind>` after the element holds that name in place+of the data. A datum of eight bytes also carries `number`, the double the text+format writes as the comment of its line.+`<bind>` is one meta the firing bound, `meta` naming it the same way the text+format names it, counter and all, and the element holding the value it took: a+term where the operand was reduced with 𝕄, the datum itself where a `dataize`+operand came down to data. `<dataize>` inside a firing is the other thing a+`dataize` operand may come to, the datum manufactured for an unknown, and holds+the formation that unknown names rather than the 42 standing for it: a `𝜎` is+the name of a λ function and no term of its own, so what 𝔻 was applied to is+`𝜎2:λ` and never `𝜎2` alone. It carries `meta` where the run carries+`at`, the same difference the text format draws between `𝔻(Φ)` at the top+and `𝛿1·2 := 𝔻(…)` in a block. The name of the element is what tells a+manufactured datum from data, the way `𝔻(…)` does in the text format, so+nothing has to be read off the presence of an attribute. `<answer>` holds the+term the firing answered with, named the same way by its own `meta`, and+`<built>` before it holds the term the entry wrote, the one 𝕄 made that answer+of: two elements rather than two attributes of one, for the same reason+`<dataize>` is no `<bind>`.++`<applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number">` is an object an+application made, which the text format writes as `𝑛·1·6 := …` in the block+`𝕄(𝑛·1·5):`: `meta` names the object the way the text format+does, counted with the `<built>` and the `<answer>` of the firing it stands in,+`by` and `at` name the judgment that normalized and the site it stood at, and+`of` names the object applied.+The argument stands in an `<attr>`, `name` naming the attribute it fills and+the text holding the name of the object or the application it is, the symbol,+such as `𝜎1`, where it is a bare one, and the term itself otherwise. A later+element holding that object holds its name instead, in its text or in its+`term`, so `<answer meta="𝑛·1·7">𝑛·1·6</answer>` says the firing answered with+it and `<dataize at="Φ">` that 𝔻 got into it.++`<known symbol="𝜎44">3F-F0-00-00-00-00-00-00</known>` is the fact a `symbolize`+line writes about a symbol it minted, which the text format writes as+`𝔻(𝜎44:λ) == …`: the symbol stands in the attribute a reader joins+lines on and the data dataizing its formation answers are the text of the+element. It takes `symbol` and not `meta`, since the fact is about the unknown+and not about a meta the firing bound.++`<joined symbol="𝜎6">𝜎4 𝜎5</joined>` is the same kind of fact about a symbol+a `join` line minted, which the text format writes as+`𝔻(𝜎6:λ) ∈ { 𝔻(𝜎4:λ), 𝔻(𝜎5:λ) }`: the fresh symbol stands+in `symbol` and the two it was minted for are the text, in the order the line+listed the metas it joined. A line whose two terms differ at several places+writes one element per pair of symbols, and one whose terms are alike writes+none. The meta the line binds is a `<bind>` like every other meta of the+firing.++`<minted symbol="𝜎1">40-14-… 40-18-…</minted>` is one symbol the firing+minted, one element per bare `𝜎` the entry wrote its answer with, standing+inside the block ahead of the `<built>` carrying them. The symbol stands in+`symbol`, the way `<known>` and `<joined>` put theirs, and the text is what is+known about it: the values the `dataize` lines of the entry took, in the order+the entry declares them, each spelled as its own line spells it — the bytes+for a datum, `𝜎1` for a symbol — and separated by a space the way `<joined>`+lists its pair. With the `λ` of the block the element reads as the fact+`𝔻(𝜎1:λ) == L_number_plus(40-14-…, 40-18-…)`, and a firing of an entry with+no `dataize` line writes `<minted symbol="𝜎1"/>`. The symbol is also the edge+a reader joins on: a later `<dataize meta="𝛿1·5">𝜎2:λ</dataize>` names the+symbol the firing that wrote `<minted symbol="𝜎2">` handed out. A firing+minting two symbols writes two elements and one minting none writes none,+which no attribute on the answer could say: a term may carry several symbols,+or carry one where the value it stands for is not a symbol at all. In the fork+above, `𝔼(L_gt)` writes `<minted symbol="𝜎2">` although `𝜎2` sits under `if`+and not where the value of the term is, while `𝔼(L_fork)` writes none at all,+since the symbol it answers with comes from a `join` line and stands in a+`<joined>` of its own.++A λ name no entry answers is `<unanswered λ="…">`, standing where its+`<evaluate>` would have stood with the formation 𝔼 was fired against as its text+and the judgment that asked in its `by` attribute, where the text format writes+the letter of it. A firing that happened while an operand of another was being+reduced is an `<evaluate>` inside the one that asked, which is what the deeper+indentation means in the text. Elements are written as the run goes and+the open ones are closed when it ends, so a run that fails still leaves a+well-formed document behind:++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --symbolic=atoms.yaml --protocol=atoms.xml --quiet \+    --sweet --hide-rho stuck.phi+[ERROR]: No entry of --symbolic answers the λ function 'L_number_nope'+$ cat atoms.xml+<?xml version="1.0" encoding="UTF-8"?>+<protocol>+  <dataize at="Φ">+    <morph at="Φ">+      <applied meta="𝑛·0·1" by="morph" at="Φ" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>+      <applied meta="𝑛·0·2" by="morph" at="Φ" of="𝑛·0·1.plus"><attr name="x">6</attr></applied>+      <evaluate λ="L_number_plus" by="morph" at="Φ">+        <dataize at="Φ.a🌵0">+          <morph at="Φ.a🌵0">+            <applied meta="𝑛·1·1" by="morph" at="Φ.a🌵0" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )</attr></applied>+            <applied meta="𝑛·1·2" by="morph" at="Φ.a🌵0" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00:Δ</attr></applied>+          </morph>+          <delta meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</delta>+        </dataize>+        <bind meta="𝛿1·1">𝛿·1·1</bind>+        <dataize at="Φ.a🌵1">+          <morph at="Φ.a🌵1">+            <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵1" of="Φ.number"><attr name="φ">Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )</attr></applied>+            <applied meta="𝑛·1·4" by="morph" at="Φ.a🌵1" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00:Δ</attr></applied>+          </morph>+          <delta meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</delta>+        </dataize>+        <bind meta="𝛿2·1">𝛿·1·2</bind>+        <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>+        <built meta="𝑛·1·5">Φ.number( φ ↦ 𝜎1:λ )</built>+        <morph at="𝑛·1·5">+          <applied meta="𝑛·1·6" by="morph" at="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>+        </morph>+        <answer meta="𝑛·1·7">𝑛·1·6</answer>+      </evaluate>+    </morph>+    <unanswered λ="L_number_nope" by="dataize">L_number_nope:λ</unanswered>+  </dataize>+</protocol>+```++<!-- markdownlint-enable MD013 -->++### Abridging the protocol++A formation carrying a whole object is written flat on one line, so a real+run fills the protocol with lines tens of thousands of characters long. The+`--abridged` option shortens every term the protocol writes, in the text and+the XML alike: a formation longer than sixty-four characters keeps its `φ`,+`Δ` and `λ` bindings and folds the rest into a count. The width is a value of+the option, `--abridged=120`, for a run that can read longer lines. The result+the run prints stays whole, and the option is refused without `--protocol`.+Every byte string the protocol writes stays whole too, since a reader may need+the data a firing came down to:++<!-- markdownlint-disable MD013 -->++```bash+$ cat wide.phi+⟦+  t ↦ ⟦+    φ ↦ ⟦ Δ ⤍ 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64 ⟧,+    left ↦ ξ.right,+    right ↦ ξ.left,+    middle ↦ ξ.left+  ⟧+⟧+$ phino dataize --locator=Q.t --protocol=wide.txt --abridged --quiet \+    --sweet --hide-rho wide.phi+$ cat wide.txt+𝔻(Φ.t):+  𝛿·0·1 := 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64+```++<!-- markdownlint-enable MD013 -->++The `--abridged-data` option cuts the data too: a byte string longer than eight+bytes keeps its first two bytes and its last two, with the count of the bytes+cut out between them. A formation folded into a count loses its data either+way, and the option is refused without `--abridged`:++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --locator=Q.t --protocol=wide.txt --abridged \+    --abridged-data --quiet --sweet --hide-rho wide.phi+$ cat wide.txt+𝔻(Φ.t):+  𝛿·0·1 := 48-65-..(8b)..-6C-64+```++<!-- markdownlint-enable MD013 -->++### Reducing a term inside a universe++A term that is no part of the program may still be reduced against it, with the+`--inside` option: the expression it names is bound to a synthetic attribute+prepended to the input expression, which the run takes as the universe Φ,+normalized there and then reduced.++```bash+$ cat universe.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧+⟧+$ phino dataize --symbolic=atoms.yaml --inside='5.plus( 6 )' universe.phi+40-45-00-00-00-00-00-00+```++This is the very trick `phino` plays internally to reduce the operands of a+firing, made available to whoever asks it to reduce a term the program does not+hold. The option cannot be combined with `--locator`, since it aims the run at+the binding it mints itself. Both `dataize` and `morph` take `--symbolic` and+`--inside`.++### Partial evaluation++A λ function no entry of the `--symbolic` file answers fails the run. This is+what happens when an operation is deliberately left out — an input the object+model has not declared yet, or an operation whose answer is not known. With+`--partial`, dataization becomes partial evaluation instead: what the known+inputs decide is computed, the rest survives as the residual program, which is+printed in place of the bytes, and the run ends successfully:++```bash+$ cat partial.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦+    φ ↦ ∅,+    plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,+    times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧,+    as-bool ↦ ⟦ λ ⤍ L_number_as_bool ⟧+  ⟧,+  φ ↦ 2.times( 3 ).plus( 4 ).as-bool+⟧+$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho partial.phi+[ERROR]: No entry of --symbolic answers the λ function 'L_number_as_bool'+$ phino dataize --symbolic=atoms.yaml --partial --sweet --hide-rho partial.phi+L_number_as_bool:λ+```++Here `2.times( 3 ).plus( 4 )` was answered by the entries the file carries, so+it was reduced — the symbol it came to sits in the hidden `ρ` of the residual+program — while `as-bool` names a λ function no entry answers, so it stays in+place as a normal-form subterm. A stuck site opens no block in the+`--protocol` file, since nothing fired there, and stands in it as+`unanswered(…)`:++<!-- markdownlint-disable MD013 -->++```bash+$ phino dataize --symbolic=atoms.yaml --partial --protocol=atoms.txt --quiet \+    --sweet --hide-rho partial.phi+$ cat atoms.txt+𝔻(Φ):+  𝕄(Φ):+    𝑛·0·1 := 2+    𝑛·0·2 := 𝑛·0·1.times( x ↦ 3 )+    𝔼(L_number_times):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 2+          𝑛·1·2 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 40-00-00-00-00-00-00-00  # 2.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵1):+        𝕄(Φ.a🌵1):+          𝑛·1·3 := 3+          𝑛·1·4 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )+        𝛿·1·2 := 40-08-00-00-00-00-00-00  # 3.0+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛+      𝕄(𝑛·1·5):+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)+    𝑛·0·3 := 𝑛·1·6.plus( x ↦ 4 )+    𝔼(L_number_plus):+      𝔻(Φ.a🌵2):+        𝕄(Φ.a🌵2):+          𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵3):+        𝕄(Φ.a🌵3):+          𝑛·2·2 := 4+          𝑛·2·3 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )+        𝛿·2·1 := 40-10-00-00-00-00-00-00  # 4.0+      𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)+      𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+      𝕄(𝑛·2·4):+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )+      𝑛·2·6 := 𝑛·2·5  # 𝕄(𝑛·2·4)+  unanswered(L_number_as_bool)  # 𝔻(L_number_as_bool:λ)+```++<!-- markdownlint-enable MD013 -->++Evaluation stays demand-driven, as the calculus prescribes: an argument+that nothing asked for before the run got stuck is left as it is in the+residual program, for the next iteration.++An operand of a firing that reaches the terminator `⊥`, or a term no+dataization rule matches, such as a formation whose `φ` is a void nothing+filled, never comes down to data either, and `--partial` parks that firing+the same way, writing the dead end into the protocol as `unanswered(⊥)` with the+term that could not be dataized beside it. Dataization aimed at `⊥` itself still+fails, with or without `--partial`, since there is no firing to park.++The nested morphing and dataization recursion is bounded by the+`--max-steps` option (default `1000`): when the budget is exhausted, the run+fails with `Dataization did not finish before reaching the limit of steps`.+This guards against non-terminating terms, which used to loop forever before+the bound was introduced:++```bash+$ phino dataize --max-steps=50 problem.phi+[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=50+```++That budget bounds how deep one branch goes, not how much the whole run does.+An entry that reduces two operands, each firing it again, doubles its work at+every level and still never gets deep, so no `--max-steps` stops it. The+`--max-firings` option counts every λ function the run fires and fails the+run once the count is spent; `--partial` parks it instead, the way it parks a+spent `--max-steps`. There is no limit unless the option is given:++```bash+$ cat split.yaml+- λ: L_split+  morph:+    𝑛1: Φ.s.foo+    𝑛2: Φ.s.foo+  𝑛: ⟦ l ↦ 𝑛1, r ↦ 𝑛2 ⟧+$ cat split.phi+⟦ s ↦ ⟦ λ ⤍ L_split ⟧, x ↦ Φ.s.foo ⟧+$ phino morph --symbolic=split.yaml --locator=Q.x --max-firings=64 split.phi+[ERROR]: Evaluation did not finish before reaching the limit of firings: --max-firings=64+```++Both budgets count work, so a run inside both of them may still take longer+than its caller can wait, and a caller that kills it gets a protocol nobody+closed. The `--max-seconds` option stops the run by the clock instead: once+that many seconds have passed since the command started, the next step the+run is about to take, whether it fires a λ function or not, fails it with+`Evaluation did not finish before reaching the limit of seconds`, and so does+a check of `--acyclic` that is still comparing formations by then.+`--partial` does not park it, since a run out of time has no site to park and+nothing left to go on with. The protocol is closed as usual and its last line+says where the time ran out. There is no limit unless the option is given:++```bash+$ phino morph --symbolic=split.yaml --locator=Q.x --partial --max-seconds=5 \+    --protocol=split.txt --sweet --flat split.phi+[ERROR]: Evaluation did not finish before reaching the limit of seconds: --max-seconds=5+$ grep -o 'timeout.*' split.txt+timeout(5)+```++## Morph++Dataization insists on bytes. Morphing 𝕄 asks a different question: evaluate+as far as the object model allows, without demanding data. It resolves Φ+against the universe, peels dispatches and applications through+normalization, fires whichever λ functions sit under a dispatch, and stops at+the first formation it reaches, handing that formation back untouched. The+`morph` command runs 𝕄 on its own:++```bash+$ cat two.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,+  φ ↦ 5.plus( 6 ).plus( 7 )+⟧+$ phino dataize --symbolic=atoms.yaml --sweet --hide-rho two.phi+40-45-00-00-00-00-00-00+$ phino morph --symbolic=atoms.yaml --locator=Q.φ --sweet --hide-rho two.phi+⟦ x ↦ 7, λ ⤍ L_number_plus ⟧+```++The inner `5.plus( 6 )` fires, because `.plus` is dispatched on its result,+and the symbol it answered with lands in the `ρ` hidden by `--hide-rho`. The+outer application is saturated but bare, so 𝕄 returns it and is finished;+firing it is dataization's job and takes `dataize` on to a datum.++The default locator `Q` morphs the whole top formation, which 𝕄 returns+unchanged, so `--locator` is how one aims 𝕄 at a subterm, exactly as in+`dataize`. Unlike 𝔻, 𝕄 is total: where no formation is reachable the answer+is the terminator `⊥`, printed rather than reported as a failed run:++```bash+$ phino morph --locator=Q.x <<< '⟦ x ↦ ξ ⟧'+⊥+```++𝕄 maps normal forms to formations and `morph` does not normalize what it is+given, so a term that is not a normal form, such as a dispatch off a formation+with neither `φ` nor `λ`, is reported as a failed run rather than answered:++```bash+$ phino morph --locator=Q.t <<< '⟦ t ↦ ⟦ x ↦ ⟦⟧ ⟧.x ⟧'+[ERROR]: Morphing expects a normal form, but no morphing rule matches: ⟦⟧:x.x+```++The whole `dataize` option surface applies unchanged — `--symbolic`,+`--inside`, `--sequence`, `--headers`, `--steps-dir`, `--protocol`,+`--partial`, `--max-steps`, `--shuffle`/`--seed`, `--output`, `--focus` and the+rest.++### Acyclic morphing++Whether a program terminates is the object model's business, not the+calculus's, so phino prevents no recursion of its own and `--max-steps` is what+ends a run that never finishes. An entry answering with a firing of itself+therefore spends the whole budget before it fails, and what it fails on is the+limit rather than the loop:++```bash+$ cat loop.yaml+- λ: L_loop+  𝑛: ⟦ λ ⤍ L_loop ⟧+$ cat loop.phi+⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧+$ phino morph --symbolic=loop.yaml --locator='Q.x' --max-steps=40 loop.phi+[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=40+```++The `--acyclic=<mode>` option makes a reduction notice. Every frame of 𝕄 and+of 𝔻 remembers the formations the frames above it have entered, and only four+places enter one: `fire` of 𝔻, `ml` of 𝕄 and an `evaluate` line of an entry,+which fire the λ function of a formation, and `box` of 𝔻, which gets into the+`φ` body of a formation carrying no λ and no `Δ`. A frame about to enter a+formation one of the frames above it has already entered is asking a question+only ever answered by asking it again, so the option stops there and parks the+site the way `--partial` parks a λ function that cannot fire: the answer is the+term the spine had reached, left where it stood, and the command exits+successfully.++```bash+$ phino morph --symbolic=loop.yaml --locator='Q.x' --acyclic=proven \+    --max-steps=40 --hide-rho loop.phi+⟦ λ ⤍ L_loop ⟧.foo+```++𝕄 and 𝔻 share that one memory. They call each other on the very term they+were asked about, and that handover is no loop, but it enters no formation+either, so it is never remembered and never mistaken for one. A body+dispatching the object it stands in is a loop 𝔻 walks round on its own — 𝕄+stops at a formation every round, and `box` gets into it again — so `dataize`+takes the option too, and so does the run of 𝔻 a λ function's `dataize` operand+is brought down with:++```bash+$ cat cyc.phi+⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧, t ↦ Φ.cyc( ⟦⟧ ) ⟧+$ phino dataize --locator='Q.t' --acyclic=proven --partial \+    --sweet --hide-rho --flat cyc.phi+⟦ cyc(x) ↦ Φ.cyc( x ):φ, t ↦ Φ.cyc( ⟦⟧ ) ⟧+```++𝔻 insists on bytes and a parked term carries none, so under `dataize` the option+wants `--partial` to have something to print: the residual program, exactly the+one it prints for a λ function that cannot fire. Without it the run stops on+the loop all the same, naming the formation it entered again instead of running+the budget down. Under `morph` nothing is asked for: 𝕄 always has a term to+answer with, and a loop 𝔻 meets under a firing parks the site the firing+stands at.++The mode says what "the same formation" means, and there is no default, since+no answer is right for every run. Under `proven` it means the same up to a+renaming of symbols: two+formations are one where some one-to-one pairing of the symbols of the first+with those of the second makes them equal, so `𝜎5` may stand where `𝜎3` stood+as long as it does so everywhere and no other symbol stands there too. Data,+attribute names and λ names must still match exactly. That is what catches a+recursion over an unknown, which never repeats a term: every round mints fresh+symbols, so the formation it enters on the second round is the one it entered+on the first with new names for the unknowns. The renaming is sound because a+symbol is an opaque value nobody worked out — every one of them dataizes to the+same manufactured datum and no entry of `--symbolic` answers one — so a+formation entered again with nothing but its symbols renamed replays the round+it is inside forever. Data still tells rounds apart: a formation entered with+`n ↦ 3` and then with `n ↦ 2` is two formations, so a recursion over data is+not cut while it goes on computing. Take an entry that answers a call of its+own object over a fresh symbol, so every round fires the same λ function over+an unknown it has never seen:++```bash+$ cat down.yaml+- λ: L_down+  dataize:+    𝛿1: $.x+  𝑛: Φ.down( x ↦ ⟦ λ ⤍ 𝜎 ⟧ )+$ cat down.phi+⟦+  down ↦ ⟦ x ↦ ∅, λ ⤍ L_down ⟧,+  y ↦ Φ.down( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).φ+⟧+$ phino morph --symbolic=down.yaml --locator=Q.y --acyclic=proven \+    --protocol=down.txt --sweet --hide-rho down.phi+⟦ x ↦ 𝜎1:λ, λ ⤍ L_down ⟧.φ+$ cat down.txt+𝕄(Φ.y):+  𝑛·0·1 := Φ.down( x ↦ 𝜎1:λ )+  𝔼(L_down):+    𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)+    𝑛·1·1 := Φ.down( x ↦ 𝜎2:λ )  # 𝑛+    𝕄(𝑛·1·1):+      𝑛·1·2 := Φ.down( x ↦ 𝜎2:λ )+    𝑛·1·3 := 𝑛·1·2  # 𝕄(𝑛·1·1)+  looped(𝑛·0·1)  # proven+```++The first round fires `down` with `x ↦ 𝜎1` and answers it with `x ↦ 𝜎2`, and+the `φ` dispatched off that answer would fire it again: the formation the first+round entered, `𝜎2` standing where `𝜎1` stood, so the firing is cut before it+opens. The cut is the `looped(…)` line, standing where the block of the cut+firing would have stood: the block it stands in names the judgment the frame+belonged to and the site it was cut at, and the comment names the mode that+cut it. What it carries is the formation the frame above entered, named by the+line that made it, `𝑛·0·1`, so the two are paired by their terms and no reader+has to rename symbols by eye or find the cut in the residue. Nothing runs under+a cut, so no block opens under the line. In the XML protocol it is+`<looped by="morph" match="proven" at="Φ.y"><e>𝑛·0·1</e></looped>`, with the+site and the mode as attributes and the formation in `<e>`. Without the option+the same run nests one round inside another until `--max-steps` runs out.++What a frame remembers is the branch from the run down to it, never everything+the run has touched, so two siblings entering one formation enter it twice and+only a formation entered from inside itself is a loop. The cut costs one lookup+and fires on the turn the repeat appears, so raising `--max-steps` from 40 to a+million changes neither the answer nor the time.++What `proven` cannot see is a recursion that never comes back to the same+formation: one whose arguments grow by a wrapper every round. Take a factorial+with an accumulator it builds a `pair` onto, with `facta.yaml` answering+`L_zero` and `L_dec` with a fresh symbol each and `L_if` a fork joining its two+branches. Under `proven` the run nests deeper until `--max-steps` runs out,+since no renaming of symbols turns a longer chain of pairs into a shorter one:++<!-- markdownlint-disable MD013 -->++```bash+$ cat facta.phi+⟦+  if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,+  zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,+  dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,+  pair ↦ ⟦ head ↦ ∅, tail ↦ ∅ ⟧,+  fact ↦ ⟦ n ↦ ∅, acc ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ξ.acc, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ), acc ↦ Φ.pair( head ↦ ξ.n, tail ↦ ξ.acc ) ).φ ).φ ⟧,+  x ↦ Φ.fact( n ↦ ⟦ λ ⤍ 𝜎1 ⟧, acc ↦ ⟦ Δ ⤍ 00- ⟧ ).φ+⟧+$ cat facta.yaml+- λ: L_zero+  dataize:+    𝛿1: $.x+  𝑛: ⟦ λ ⤍ 𝜎 ⟧+- λ: L_dec+  dataize:+    𝛿1: $.x+  𝑛: ⟦ λ ⤍ 𝜎 ⟧+- λ: L_if+  dataize:+    𝛿1: $.c+  morph:+    𝑛1: $.left+    𝑛2: $.right+  symbolize:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    𝑛5: [𝑛3, 𝑛4]+  𝑛: 𝑛5+$ phino morph --acyclic=plausible --partial --sweet --hide-rho --flat \+    --symbolic=facta.yaml --locator='Q.x' --protocol=facta.txt facta.phi+Φ.if( c ↦ Φ.zero( x ↦ 𝜎1:λ ), left ↦ 00-:Δ, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎1:λ ), acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) ).φ ).φ+$ grep looped facta.txt+      looped(𝑛·0·5)  # plausible+```++<!-- markdownlint-enable MD013 -->++Under `plausible` the same formation means one the formation entered earlier+is embedded in: the two have the same attributes, data and λ function at the+top, and every term the earlier one bound there is found again in the later+one, as it stands or somewhere below a wrapper it gained. Any symbol stands for+any other, and nothing is looked for under a `ρ`, which holds the object a term+came from rather than a term it grew into. The second `if` above holds the+first under one more `pair` and one more `dec`, so it is cut, and its+`looped(…)` line says `plausible`. The `right` operand of the first `if` then+comes back as the term the cut left, which no `join` pairs with the datum of+the other branch, so that firing gets stuck and `--partial` parks it. A call+nested in its own operand, such as a sum of sums, is never+cut, since the inner call is smaller than the outer one and cannot hold it.+The mode is not sound, though: a recursion whose argument grows on its way to+stopping is cut too, which is why the line names the mode that made the cut.++The mode also fires every formation once. Every use of a binding copies the+term bound to it, so a program reading `truncated ↦ ρ.abs.floor` in five+places fires `abs` and `floor` five times over and mints five symbols for one+value, and a guard written that way spends its whole budget saying the same+thing again. 𝔼 is a function of the formation it fires: the entry that+answers is found by the λ name the formation carries, every operand is+reduced from its bindings inside the one universe of the run, and the answer+is built from what they came down to. So a run under `plausible` keeps what+every firing answered, by the formation it fired, and a later firing of the+same formation takes that answer, with the very symbols the first one minted,+instead of making it again. Two arguments spelling one term then come to one+symbol, here through an entry `L_pair` that fires both with `evaluate` lines:++```bash+$ cat twins.yaml+- λ: L_number_plus+  dataize:+    𝛿1: $.ρ+    𝛿2: $.x+  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+- λ: L_pair+  morph:+    𝑛1: $.a+    𝑛2: $.b+  evaluate:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  𝑛: ⟦ φ ↦ ⟦ a ↦ 𝑛3, b ↦ 𝑛4 ⟧ ⟧+$ cat twins.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,+  pair ↦ ⟦ a ↦ ∅, b ↦ ∅, λ ⤍ L_pair ⟧,+  t ↦ Φ.pair( 7.plus( 5.plus( 6 ) ), 7.plus( 5.plus( 6 ) ) ).φ+⟧+$ phino morph --symbolic=twins.yaml --acyclic=plausible --locator=Q.t \+    --sweet --hide-rho twins.phi+⟦+  a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧,+  b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧+⟧+```++Under `proven` `b` lands on `𝜎4`, since its `evaluate` line fires the outer sum+and the inner one once more. The protocol of the run above holds three firings+of `L_number_plus`, two for `a` and one for `b`, since it records where 𝔼 was+asked and what it answered there; the one for `b` carries the answer lines of+the outer one for `a` and no operand line, since nothing was reduced for it,+and it is not charged to `--max-firings`, which counts the firings the run+made. The formation is compared with everything it carries, `ρ` included, so+a firing on another object is another firing, yet it may take the same answer+all the same: the run keeps every answer by the λ name and what the operands+came down to as well, and a firing whose operands come down to the data, the+symbols and the normal forms of one already answered takes that answer once+they are down, minting nothing. An attribute read from the `φ` of an object+and then reached as a binding of it is read through two `ξ`, each the object+minus the attribute being read, so it is two formations, but one firing, and+so one symbol. A symbol counts as the symbol it is, not as the datum every+symbol manufactures, so two firings over two symbols stay two. The protocol+writes such a firing with its operand lines and the answer lines of the one+it took the answer from, and `--max-firings` charges it like a firing made,+since a firing is charged as it starts bringing its operands down, which is+the only place a recursion widening inside its operands can be cut. A firing+cut by the mode on its way to an answer keeps the cut, and the next firing of+the same formation is cut at its own site without reducing anything first. A+firing that got stuck keeps the λ function it got stuck on, and the next+firing of the same formation gets stuck at its own site the same way, with+nothing reduced under it, as long as nothing new was answered in between.+Once something was, the formation is fired again, since an operand that could+not be brought down the first time may come down now.++The `--max-loops=<n>` option makes either mode more patient. A frame is cut+only once more than `n` of the formations the frames above it entered are the+same as the one it is about to enter, so a recursion that stops within `n`+rounds is never cut, and one that goes on is cut `n` rounds later. The default+is `0`, which cuts on the first repeat. The option needs `--acyclic`, since+nothing else cuts a loop. Here the second `f` holds `Φ.start`, which the first+`f` is bound to, under a `Φ.stop`, so `plausible` cuts it, even though `stop`+answers data at once:++<!-- markdownlint-disable MD013 -->++```bash+$ cat once.phi+⟦+  f ↦ ⟦ x ↦ ∅, φ ↦ ξ.x.again ⟧,+  start ↦ ⟦ again ↦ Φ.f( x ↦ Φ.stop( v ↦ Φ.start ) ) ⟧,+  stop ↦ ⟦ v ↦ ∅, again ↦ ⟦ Δ ⤍ 01- ⟧ ⟧,+  t ↦ Φ.f( x ↦ Φ.start )+⟧+$ phino dataize --locator=Q.t --acyclic=plausible once.phi+[ERROR]: Reduction entered a formation it is already inside: ⟦ x ↦ Φ.stop( v ↦ Φ.start ), φ ↦ x.again ⟧+$ phino dataize --locator=Q.t --acyclic=plausible --max-loops=1 once.phi+01-+```++<!-- markdownlint-enable MD013 -->++## Rewrite++You can rewrite this expression with the help of [rules](#rule-structure)+defined in the `my-rule.yml` YAML file (here, the `!d1` and `!B1` are capturing+groups, similar to regular expressions):++```yaml+name: My custom rule+pattern: ⟦ Δ ⤍ !d1, !B1 ⟧+result: ⟦ Δ ⤍ 62-79-65, !B1 ⟧+```++Then, rewrite:++```bash+$ phino rewrite --rule=my-rule.yml hello.phi+⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧, t ↦ ξ.k, k ↦ ⟦⟧ ⟧+```++If you want to use many rules, just use `--rule` as many times as you need:++```bash+phino rewrite --rule=rule1.yaml --rule=rule2.yaml ...+```++You can also use [built-in rules](resources), which are designed+to normalize expressions:++```bash+phino rewrite --normalize hello.phi+```++Both flags may be combined, so that your own rules are applied+alongside the built-in ones, in a single rewriting session:++```bash+phino rewrite --normalize --rule=my-rule.yaml hello.phi+```++Some rules mint fresh synthetic names via the `random-string` built-in. To+keep the output reproducible across runs, `phino` seeds the random generator+deterministically with `0` by default. Use `--seed` to pick a different seed:++```bash+phino rewrite --seed=42 --rule=my-rule.yml hello.phi+```++If no input file is provided, the 𝜑-expression is taken from `stdin`:++```bash+$ echo '⟦ φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ⟧' | phino rewrite --rule=my-rule.yml+⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧ ⟧+```++You're able to pass [`XMIR`][xmir] as input. Use `--input=xmir` and `phino`+will parse given `XMIR` from file or `stdin` and convert it to `phi` AST.++```bash+phino rewrite --rule=my-rule.yaml --input=xmir file.xmir+```++Also `phino` supports 𝜑-expressions in+[ASCII](https://en.wikipedia.org/wiki/ASCII) format and with+syntax sugar. The `rewrite` command also allows you to desugar the expression+and print it in canonical syntax:++```bash+$ echo '[[ @ -> Q.io.stdout("hello") ]]' | phino rewrite+⟦+  φ ↦ Φ.io.stdout( α0 ↦ Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ) ) )+⟧+```++A formation of a single binding may be written as its asset, a colon,+and the attribute the asset is bound to:++```text+⟦ Δ ⤍ FF-AA ⟧    = FF-AA:Δ    = FF-AA:D+⟦ λ ⤍ 𝜎1 ⟧       = 𝜎1:λ       = !S1:L+⟦ a ↦ ∅ ⟧        = ∅:a        = ?:a+⟦ φ ↦ ξ.a ⟧      = ξ.a:φ      = $.a:@+```++The colon binds as tightly as a dot, so `ξ.a:φ.b` is `⟦ φ ↦ ξ.a ⟧.b`.+With `--sweet`, `phino` prints every such formation this way, so+`⟦ x ↦ ⟦ φ ↦ ξ.a ⟧ ⟧` comes out as `a:φ:x` and `⟦ x(a) ↦ ⟦ φ ↦ a ⟧ ⟧`+as `⟦ x(a) ↦ a:φ ⟧`: the formation inline voids open takes the sugar,+while the one holding the voids keeps its brackets, and so does+every formation in the salty syntax and in [LaTeX][latex].++A formation has a receiver `ρ` only when it declares one among its voids, the+way EO declares `^`: `⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧`, or `a(ρ) ↦ ⟦ t ↦ ξ.ρ.k ⟧` with+the void inline. `phino` adds none of its own, and a dispatch into a formation+that declares none hands it no `ρ` (the `skip` rule), so `ξ.ρ` there is `⊥`:++<!-- markdownlint-disable MD013 -->++```bash+$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧ ⟧.a.t ⟧' | phino rewrite --normalize --sweet+01-:Δ:x+$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ t ↦ ξ.ρ.k ⟧ ⟧.b.t ⟧' | phino rewrite --normalize --sweet+⊥:x+```++<!-- markdownlint-enable MD013 -->++## Merge++You can merge several 𝜑-expressions into a single one by merging their+top level formations:++```bash+$ cat bytes.phi+⟦ bytes ↦ ⟦ φ ↦ ∅ ⟧ ⟧+$ cat number.phi+⟦+  number ↦ ⟦+    φ ↦ ∅,+    plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧+  ⟧+⟧+$ cat minus.phi+⟦ number ↦ ⟦ minus(x) ↦ ⟦ λ ⤍ L_number_minus ⟧ ⟧ ⟧+$ phino merge bytes.phi number.phi minus.phi --sweet+⟦+  bytes(φ) ↦ ⟦⟧,+  number(φ) ↦ ⟦ plus(x) ↦ L_number_plus:λ, minus(x) ↦ L_number_minus:λ ⟧+⟧+```++## Match++You can test the 𝜑-expression matches against the [rule](#rule-structure)+pattern. The result output contains matched substitutions:++```bash+$ phino match --pattern='⟦ Δ ⤍ !d, !B ⟧' hello.phi+B >> ⟦⟧+d >> 68-65-6C-6C-6F+```++## Compile++By default, `phino` reads its rules from YAML and interprets them at every+step. The `compile` command turns the rules into Haskell instead. Then a+second build of `phino` runs them as plain functions:++```bash+phino compile+cabal build all+```++The command writes the module `compiled/generated/Compiled.hs`, which git+ignores. It compiles the built-in rules of normalization, the+contextualization function 𝒞, and every file you pass with `--rule`. The+`--target` option writes the module somewhere else.++A build links the module in only when the Cabal flag `compiled` is on. If+there is no `cabal.project.local`, `compile` creates one that turns the flag+on. If the file already exists, `compile` leaves it alone and prints the two+lines to add to it:++```text+package phino+  flags: +compiled+```++The compiled rules take exactly the same steps as the YAML ones, so the+output and every `--sequence` stay the same. A few things are still read from+YAML at runtime:++* the rules of morphing (𝕄) and dataization (𝔻);+* a `--rule` file that changed after `compile`;+* the pattern of `match` and the `rewrite:` blocks of the `--symbolic` file.++The `explain` command also reads the rules from YAML.++`compile` refuses a rule it cannot turn into Haskell and names the reason.+For example, it refuses a rule with `having`, a `where` function other than+`contextualize` or `named`, or the conditions `matches` and `part-of`.++A binary built this way refuses to run if the built-in rules changed after+the last `compile`, since it would run rules nobody wrote. Run `compile`+again and rebuild. To test the whole suite against the compiled rules, run:++```bash+make compiled+```++## Explain++You can _explain_ the built-in rules by printing them in [LaTeX][latex]+format. Pass exactly one of `--normalize`, `--morph`, `--dataize` or+`--contextualize` for the rewriting, morphing (𝕄), dataization (𝔻) or+contextualization (𝒞) rules (or `--rule` for a custom rule file):++```bash+$ phino explain --normalize+\begin{tabular}{rl}+\phinoNormalizationRule{alpha}+  { [[ B_1, \tau -> ?, B_2 ]] ( \phiTerminal{\alpha_{i}} -> e ) }+  { [[ B_1, \tau -> ?, B_2 ]] ( \tau -> e ) }+  { $ i = \vert \overline{ B_1 } \vert $ }+  { }+\phinoNormalizationRule{dc}+  { T ( \tau -> e ) }+  { T }+  { }+  { }+...+\phinoNormalizationRule{stop}+  { [[ B ]] . \tau }+  { T }+  { $ [ \tau \char44{} @ \char44{} L ] \cap B = \emptyset $ }+  { }+\end{tabular}+```++The morphing and dataization rules are printed the same way:++```bash+$ phino explain --morph+\begin{tabular}{rl}+\phinoMorphingRule{mf}+  { \mathbb{M}( [[ B ]], e ) }+  { [[ B ]] }+  { }+  { }+...+\phinoMorphingRule{universe}+  { \mathbb{M}( Q, e ) }+  { \mathbb{M}( \phinoNormalize{ e }, e ) }+  { $ e \not= Q $ }+  { }+\end{tabular}+```++```bash+$ phino explain --dataize+\begin{tabular}{rl}+\phinoDataizationRule{delta}+  { \phinoDataize{ [[ B_1, D> \delta_0, B_2 ]] } }+  { \delta_0 }+  { }+  { }+...+\phinoDataizationRule{norm}+  { \phinoDataize{ n } }+  { \phinoDataize{ \mathbb{M}( n, e ) } }+  { }+  { }+\end{tabular}+```++```bash+$ phino explain --contextualize+\begin{phinoContextualizationInference}+  \phinoName{cxi}+  \phinoConclusion{ \phinoContextualize{ \phiTerminal{\xi} }{ k }{ k } }+\end{phinoContextualizationInference}+...+\begin{phinoContextualizationInference}+  \phinoName{cd}+  \phinoPremise{ \phinoContextualize{ n }{ k }{ n_1 } }+  \phinoConclusion{ \phinoContextualize{ n . \tau }{ k }{ n_1 . \tau } }+\end{phinoContextualizationInference}+```++For more details, use `phino [COMMAND] --help` option.++## Rule structure++This is BNF-like yaml rule structure. Here types ended with+apostrophe, like `Attribute'` are built types from 𝜑-expression [AST](src/AST.hs)++```bnfc+Rule:+  name: String+  pattern: String+  result: String+  when: Condition?       # predicate, works with substitutions before extension+  where: [Extension]?    # substitution extensions+  having: Condition?     # predicate, works with substitutions after extension++Condition:+  = and: [Condition]     # logical AND+  | or:  [Condition]     # logical OR+  | not: Condition       # logical NOT+  | eq:                  # compare two comparable objects+      - Comparable+      - Comparable+  | in:                  # returns True if every given attribute exists in+      - Attribute' | [Attribute'] # the union of the given bindings+      - Binding' | [Binding']+  | nf: Expression'      # returns True if given expression in normal form+                         # which means that no more other normalization rules+                         # can be applied+  | absolute: Expression' # returns True if given expression is xi-free, i.e.+                         # there is no ξ outside of a formation: it is Φ, a+                         # formation, a dispatch with a xi-free subject, or an+                         # application with a xi-free subject and argument.+                         # Combined with a normal-form check by the '𝑘'/'!k'+                         # meta variable, which ranges over the absolute+                         # expressions 𝒦 ⊆ 𝒩, used by the Rcopy rule.+  | matches:             # returns True if given expression after dataization+      - String           # matches to given regex+      - Expression+  | part-of:             # returns True if given expression is attached to any+      - Expression'      # attribute in ginve bindings+      - BiMeta'+  | formation:           # returns True if given expression is a formation+      Expression'        # (an abstraction ⟦…⟧); used by morphing 'md'+                         # as 'not (formation 𝑛)', so a non-formation head is+                         # morphed and a formation head is left to 'ml'+  | gt:                  # returns True if the first comparable object is+      - Comparable       # greater than the second one+      - Comparable+  | disjoint:            # returns True if none of the given attributes exists+      - [Attribute']     # in the given bindings+      - Binding'++Comparable:              # comparable object that may be used in 'eq' condition+  = Attribute'+  | Number+  | Expression'++Number:                  # comparable number+  = Integer              # just regular integer+  | IndexMeta'           # 𝑖 (or !i), the index captured by an α𝑖 argument+  | length: BiMeta'      # calculate length of bindings by given meta binding+  | domain: BiMeta'      # calculate number of unique attributes in given+                         # meta binding (excluding 'assets')++Extension:               # substitutions extension used to introduce new meta variables+  meta: [ExtArgument]    # new introduced meta variable+  function: String       # name of the function+  args: [ExtArgument]    # arguments of the function++ExtArgument+  = Bytes'               # !d+  | Binding'             # !B+  | Expression'          # !e+  | Attribute'           # !t+```++Here's list of functions that are supported for extensions:++* `contextualize` - function of two arguments, that rewrites given expression+  depending on provided context according to the contextualization+  [rules](assets/contextualize.jpg)+* `random-tau` - creates an attribute with a random unique name. It accepts no+  arguments; uniqueness is guaranteed across all names already taken in the+  document.+* `dataize` - dataizes given expression and returns bytes.+* `concat` - accepts bytes or dataizable expressions as arguments,+  concatenates them into single sequence and convert it to expression+  that can be pretty printed as human readable string:+  `Φ.string(Φ.bytes⟦ Δ ⤍ !d ⟧)`.+* `sed` - pattern replacer, works like unix `sed` function.+  Accepts two arguments: target expression and pattern.+  Pattern must start with `s/`, consists of three parts+  separated by `/`, for example, this pattern `s/\\s+//g`+  replaces all the spaces with empty string. To escape braces and slashes+  in pattern and replacement parts - use them with `\\`,+  e.g. `s/\\(.+\\)//g`.+* `random-string` - accepts dataizable expression or bytes as pattern.+  Replaces `%x` and `%d` formatters with random hex numbers and+  decimals accordingly. Uniqueness is guaranteed during one+  execution of `phino`.+* `size` - accepts exactly one meta binding and returns size of it and+  `Φ.number`.+* `tau` - accepts `Φ.string`, dataizes it and converts it to attribute.+  If dataized string can't be converted to attribute - an error is thrown.+* `string` - accepts `Φ.string` or `Φ.number` or attribute and converts it+  to `Φ.string`.+* `number` - accepts `Φ.string` and converts it `Φ.number`+* `sum` - accepts list of `Φ.number` or `Φ.bytes` and returns sum of them as `Φ.number`+* `join` - accepts list of bindings and returns list of joined bindings. Duplicated+  `ρ`, `Δ` and `λ` attributes are ignored, all other duplicated attributes are replaced+  with unique attributes using `random-tau` function.++## Meta variables++The `phino` supports meta variables to write 𝜑-expression patterns for+capturing attributes, bindings, etc.++This is the list of supported meta variables:++* `!t` || `𝜏` - attribute+* `!i` || `𝑖` - the index of a positional (α) application argument,+                captured by writing `α𝑖` (or `~!i`)+* `!e` || `𝑒` - any expression+* `!n` || `𝑛` - any expression that is already in normal form (behaves like+                `!e`/`𝑒`, but only binds a sub-expression in NF, so no explicit+                `nf:` guard is needed)+* `!k` || `𝑘` - any expression that is absolute, i.e. xi-free and in normal+                form (ranges over `𝒦 ⊆ 𝒩`); behaves like `!e`/`𝑒` but only+                binds an absolute sub-expression, so no explicit `absolute:`+                or `nf:` guard is needed+* `!B` || `𝐵` - list of bindings+* `!d` || `𝛿` - bytes in meta delta binding+* `!F` || `𝑓` - function name in meta lambda binding+* `!S` || `𝜎` - a symbol standing where a λ name stands (see+                [Symbols](#symbols)). It is spelled the way a meta variable is+                spelled but is a name and no capture: `𝜎1` is one concrete+                symbol, which no substitution ever binds, and a bare `𝜎` in the+                answer of a `--symbolic` entry asks for a fresh one++A meta variable carries a suffix, like `!B1` or `𝜏2`, to name what it+captured, so that the `result`, `when`, `where` and `having` of a rule can+read it back. An index starts with one: a suffix of `0`, as in `!B0` or+`𝜏0`, is refused where it is written, because it is a first index spelled+wrong and no name. A positional argument keeps counting from zero, though,+since `α0` is an index of the calculus and no meta variable.++Written bare, with no suffix at all, a meta variable is anonymous: it matches+whatever term stands in its place, every occurrence on its own, and binds no+name. Two anonymous metas of one kind are therefore two different captures,+which is what lets a pattern ask for any two attributes without inventing+names for them:++```yaml+name: two-attributes+pattern: '⟦ 𝜏 ↦ 𝑒, 𝜏 ↦ 𝑒 ⟧'+result: '⟦ x ↦ ⟦ Δ ⤍ 2A- ⟧ ⟧'+```++Spelled with suffixes, that pattern would read `⟦ 𝜏1 ↦ 𝑒1, 𝜏2 ↦ 𝑒2 ⟧` and+name four captures the result never mentions, while `⟦ 𝜏1 ↦ 𝑒1, 𝜏1 ↦ 𝑒1 ⟧`+would be rejected as a duplicated attribute.++Nothing can refer to an anonymous meta, since it has no name to be referred to+by. Writing one outside a `pattern` (or the `match`, `universe` and `c-match` of+an inference rule) is a mistake in the rule and is reported as the rule loads.++A positional (α) application argument is written as `α0`, `~0` (ASCII), or+`α𝑖`/`~!i` when its index is captured by an `!i`/`𝑖` meta variable.++Incorrect usage of meta variables in 𝜑-expression patterns leads to+parsing errors.++## Benchmark++To run performance benchmarks, you need [Java 8+][java] and [curl][curl].+Maven is downloaded automatically on first run via `benchmark/mvnw`.++The benchmark uses the compiled [`Native`][jna-native] class from+[JNA][jna] — a large real-world Java class — as its test input.+On first run, `make bench` downloads the class, disassembles it to+[XMIR][xmir] via [jeo-maven-plugin][jeo], converts it to 𝜑 using+`phino rewrite`, and caches the results in `benchmark/tmp/`.+Subsequent runs skip straight to the benchmarks.++Besides parsing, printing and rewriting that class, the suite morphs+symbolically. `benchmark/demo.phi` is a small world whose entries name the λ+functions of `benchmark/atoms.yaml`, and each entry is a case of its own, so+that a slowdown of one of them is a line of the report rather than a share of+a single total. The smallest entry — one λ function fired against one unknown+— is timed twice, over the demo world alone and over the same world merged+into the class, and the two numbers say between them what the world around an+entry costs (see [#1291][issue-1291]).++A case whose single run is measured in seconds gets fewer warmups and fewer+batches than a case measured in microseconds, since the whole suite runs+inside one job; the report says how many of each a case was given.++```bash+make bench+```++<!-- benchmark_begin -->++```text+=== parse/phi ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      2101646.837 μs+  avg:        210164.684 μs+  min:        191034.014 μs+  max:        247000.010 μs+  std dev:    21885.360 μs+=== parse/xmir ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      9327965.748 μs+  avg:        932796.575 μs+  min:        864569.297 μs+  max:        1038777.636 μs+  std dev:    67090.644 μs+=== rewrite/normalize ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      21.780 μs+  avg:        2.178 μs+  min:        1.663 μs+  max:        2.535 μs+  std dev:    0.286 μs+=== print/sweet/multiline ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      4374361.243 μs+  avg:        437436.124 μs+  min:        398758.490 μs+  max:        473283.855 μs+  std dev:    26607.005 μs+=== print/sweet/flat ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      663496.668 μs+  avg:        66349.667 μs+  min:        58505.945 μs+  max:        81586.458 μs+  std dev:    8909.299 μs+=== print/salty/multiline ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      14094681.991 μs+  avg:        1409468.199 μs+  min:        1353470.986 μs+  max:        1470382.428 μs+  std dev:    30647.729 μs+=== morph/symbolic/demo/e1 ===+  warmup:     3 iterations+  batches:    10 x 2+  total:      179986.717 μs+  avg:        8999.336 μs+  min:        8393.411 μs+  max:        11474.177 μs+  std dev:    1088.979 μs+=== morph/symbolic/demo/e2 ===+  warmup:     3 iterations+  batches:    10 x 2+  total:      162405.617 μs+  avg:        8120.281 μs+  min:        8045.820 μs+  max:        8235.901 μs+  std dev:    55.769 μs+=== morph/symbolic/demo/e3 ===+  warmup:     3 iterations+  batches:    10 x 2+  total:      231633.663 μs+  avg:        11581.683 μs+  min:        11461.187 μs+  max:        11878.593 μs+  std dev:    123.097 μs+=== morph/symbolic/demo/e4 ===+  warmup:     3 iterations+  batches:    10 x 3+  total:      208729.614 μs+  avg:        6957.654 μs+  min:        6899.022 μs+  max:        6999.483 μs+  std dev:    29.412 μs+=== morph/symbolic/demo/e5 ===+  warmup:     3 iterations+  batches:    10 x 9+  total:      200950.553 μs+  avg:        2232.784 μs+  min:        2216.430 μs+  max:        2252.836 μs+  std dev:    10.899 μs+=== morph/symbolic/native/e5 ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      173698.880 μs+  avg:        17369.888 μs+  min:        16596.425 μs+  max:        19463.645 μs+  std dev:    879.974 μs+=== morph/symbolic/accum/0 ===+  warmup:     3 iterations+  batches:    10 x 2+  total:      202491.620 μs+  avg:        10124.581 μs+  min:        10011.657 μs+  max:        10336.836 μs+  std dev:    78.856 μs+=== morph/symbolic/accum/400 ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      330874.174 μs+  avg:        33087.417 μs+  min:        32906.916 μs+  max:        33429.534 μs+  std dev:    166.375 μs+```++The results were calculated in [this GHA job][benchmark-gha]+on 2026-10-12 at 16:04,+on Linux with 4 CPUs.++<!-- benchmark_end -->++## How to Contribute++Fork repository, make changes, then send us a [pull request][guidelines].+We will review your changes and apply them to the `master` branch shortly,+provided they don't violate our quality standards. To avoid frustration,+before sending us your pull request please make sure all your tests pass:++```bash+make all+```++To generate a local coverage report for development, run:++```bash+make coverage+```++To build a `phino` executable into the root of the repository, run:++```bash+make phino+```++This produces an executable `phino` (or `phino.exe` on Windows) in the+project root, which you can run directly for quick local testing:++```bash+./phino --version+```++You will need [GHC ≥ 9.6.7][GHC] and [Cabal ≥ 3.0 (recommended)][cabal]+or [Stack ≥ 3.0][stack] installed.++[cabal]: https://www.haskell.org/cabal/+[stack]: https://docs.haskellstack.org/en/stable/install_and_upgrade/+[GHC]: https://www.haskell.org/ghc/+[guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html+[xmir]: https://news.eolang.org/2022-11-25-xmir-guide.html+[latex]: https://en.wikipedia.org/wiki/LaTeX+[java]: https://www.java.com/en/download/+[curl]: https://curl.se/+[gha]: https://docs.github.com/en/actions+[jna]: https://github.com/java-native-access/jna+[jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java+[jeo]: https://github.com/objectionary/jeo-maven-plugin+[issue-1291]: https://github.com/objectionary/phino/issues/1291+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/38274371324
benchmark/Main.hs view
@@ -18,7 +18,7 @@ import Deps (Acyclic (Plausible, Proven), Judgment (Morphing), dontSaveEval, dontSaveMade, dontSaveStep) import Encoding (Encoding (UNICODE)) import Engine (Engine (_matching, _normal), building, stepOf, yaml)-import Evaluate (evaluation, fired)+import Evaluate (evaluation) import Lambdas (Lambdas, readLambdas) import Lining (LineFormat (MULTILINE, SINGLELINE)) import Margin (defaultMargin)@@ -80,9 +80,8 @@     False     False     True-    True-    1     (Just acyclic)+    0     Morphing     []     Map.empty@@ -90,7 +89,6 @@     (building linked)     reduction     evaluation-    fired     dontSaveStep     dontSaveEval     linked@@ -179,7 +177,7 @@     paddings = [0, 400]     looped :: Int -> Expression -> Lambdas -> IO ()     looped count universe counters = do-      locator <- parseExpressionThrows "Φ.l🌵"+      locator <- parseExpressionThrows "Φ.l🌵.e1"       runBench (printf "morph/symbolic/accum/%d" count) (symbolic Plausible universe counters locator)     padded :: Expression -> Int -> Expression -> Expression     padded method count (ExFormation bds) = ExFormation (map grown bds)
compiled/generated/Compiled.hs view
@@ -50,7 +50,7 @@  -- The texts of the built-in rules of all four judgments this module is made of. sources :: [String]-sources = ["Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", "Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", "Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", "Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"norho\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) \961, result = ExTermination, when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", "Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", "ContextualizeRule {name = \"ca\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"caa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cd\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, cmatch = ExMeta \"k1\", cresult = ExDispatch (ExMeta \"n2\") !t1, premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], cmatch = ExMeta \"k1\", cresult = ExFormation [BiMeta \"B1\"], premises = []}", "ContextualizeRule {name = \"cg\", label = Nothing, match = ExRoot, cmatch = ExMeta \"k1\", cresult = ExRoot, premises = []}", "ContextualizeRule {name = \"ct\", label = Nothing, match = ExTermination, cmatch = ExMeta \"k1\", cresult = ExTermination, premises = []}", "ContextualizeRule {name = \"cxi\", label = Nothing, match = ExXi, cmatch = ExMeta \"k1\", cresult = ExMeta \"k1\", premises = []}", "MorphRule {name = \"dead\", label = Nothing, match = ExTermination, ematch = ExMeta \"e1\", nresult = ExTermination, when = Nothing, premises = []}", "MorphRule {name = \"ma\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArAlpha \945!i (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"mad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArTau !t (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"md\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Just (Not (IsFormation (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExDispatch (ExMeta \"n2\") !t1)},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", nresult = ExFormation [BiMeta \"B1\"], when = Nothing, premises = []}", "MorphRule {name = \"mg\", label = Nothing, match = ExRoot, ematch = ExRoot, nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination ExRoot}]}", "MorphRule {name = \"ml\", label = Just \"\\\\lambda\", match = ExDispatch (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n3\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpNormalize (ExDispatch (ExMeta \"n1\") !t1)},Premise {result = \"n3\", operation = OpMorph (ExMeta \"n2\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mphi\", label = Just \"\\\\varphi\", match = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (And [In [\966] [BiMeta \"B1\"],Disjoint [!t1,\955] [BiMeta \"B1\"]]), premises = [Premise {result = \"n1\", operation = OpNormalize (ExDispatch (ExDispatch (ExFormation [BiMeta \"B1\"]) \966) !t1)},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"universe\", label = Just \"\\\\Phi\", match = ExRoot, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Eq (CmpExpr (ExMeta \"e1\")) (CmpExpr ExRoot))), premises = [Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"xi\", label = Nothing, match = ExXi, ematch = ExMeta \"e1\", nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"box\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"e3\", operation = OpContextualize (ExMeta \"e2\") (ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"])},Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e3\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"delta\", label = Just \"\\\\Delta\", match = ExFormation [BiMeta \"B1\",BiDelta (BtMeta \"d1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = []}", "DataizeRule {name = \"fire\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"none\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955,\966] [BiMeta \"B1\"]), premises = [Premise {result = \"d1\", operation = OpDataize ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"norm\", label = Nothing, match = ExMeta \"n1\", ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (And [Not (IsFormation (ExMeta \"n1\")),Not (Eq (CmpExpr (ExMeta \"n1\")) (CmpExpr ExTermination))]), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n2\") (ExMeta \"e1\")}]}"]+sources = ["Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", "Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", "Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", "Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"norho\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) \961, result = ExTermination, when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", "Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", "ContextualizeRule {name = \"ca\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"caa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cd\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, cmatch = ExMeta \"k1\", cresult = ExDispatch (ExMeta \"n2\") !t1, premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], cmatch = ExMeta \"k1\", cresult = ExFormation [BiMeta \"B1\"], premises = []}", "ContextualizeRule {name = \"cg\", label = Nothing, match = ExRoot, cmatch = ExMeta \"k1\", cresult = ExRoot, premises = []}", "ContextualizeRule {name = \"ct\", label = Nothing, match = ExTermination, cmatch = ExMeta \"k1\", cresult = ExTermination, premises = []}", "ContextualizeRule {name = \"cxi\", label = Nothing, match = ExXi, cmatch = ExMeta \"k1\", cresult = ExMeta \"k1\", premises = []}", "MorphRule {name = \"dead\", label = Nothing, match = ExTermination, ematch = ExMeta \"e1\", nresult = ExTermination, when = Nothing, premises = []}", "MorphRule {name = \"ma\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArAlpha \945!i (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"mad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArTau !t (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"md\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Just (Not (IsFormation (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExDispatch (ExMeta \"n2\") !t1)},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", nresult = ExFormation [BiMeta \"B1\"], when = Nothing, premises = []}", "MorphRule {name = \"mg\", label = Nothing, match = ExRoot, ematch = ExRoot, nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination ExRoot}]}", "MorphRule {name = \"ml\", label = Just \"\\\\uplambda\", match = ExDispatch (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n3\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpNormalize (ExDispatch (ExMeta \"n1\") !t1)},Premise {result = \"n3\", operation = OpMorph (ExMeta \"n2\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mphi\", label = Just \"\\\\upvarphi\", match = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (And [In [\966] [BiMeta \"B1\"],Disjoint [!t1,\955] [BiMeta \"B1\"]]), premises = [Premise {result = \"n1\", operation = OpNormalize (ExDispatch (ExDispatch (ExFormation [BiMeta \"B1\"]) \966) !t1)},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"universe\", label = Just \"\\\\Phi\", match = ExRoot, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Eq (CmpExpr (ExMeta \"e1\")) (CmpExpr ExRoot))), premises = [Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"xi\", label = Just \"\\\\upxi\", match = ExXi, ematch = ExMeta \"e1\", nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"box\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"e3\", operation = OpContextualize (ExMeta \"e2\") (ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"])},Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e3\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"delta\", label = Just \"\\\\Delta\", match = ExFormation [BiMeta \"B1\",BiDelta (BtMeta \"d1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = []}", "DataizeRule {name = \"fire\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"none\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955,\966] [BiMeta \"B1\"]), premises = [Premise {result = \"d1\", operation = OpDataize ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"norm\", label = Nothing, match = ExMeta \"n1\", ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (And [Not (IsFormation (ExMeta \"n1\")),Not (Eq (CmpExpr (ExMeta \"n1\")) (CmpExpr ExTermination))]), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n2\") (ExMeta \"e1\")}]}"]  -- Whether the term is a normal form: no built-in rule of normalization -- matches anywhere inside it.
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.153+version: 0.0.154 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>@@ -80,7 +80,6 @@     Morph     Must     Parser-    Pool     Printer     Random     Regexp@@ -189,7 +188,6 @@     MustSpec     ParserSpec     Paths_phino-    PoolSpec     PrinterSpec     RandomSpec     RegexpSpec
resources/morphing/ml.yaml view
@@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT --- name: ml-label: \lambda+label: \uplambda match: '⟦𝐵1, λ ⤍ 𝑓1, 𝐵2⟧.𝜏1' universe: 𝑒1 conclusion: 𝑛3
resources/morphing/mphi.yaml view
@@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT --- name: mphi-label: \varphi+label: \upvarphi match: ⟦𝐵1⟧.𝜏1 universe: 𝑒1 conclusion: 𝑛2
resources/morphing/xi.yaml view
@@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT --- name: xi+label: \upxi match: ξ universe: 𝑒1 conclusion: 𝑛1
src/CLI/Parsers.hs view
@@ -230,15 +230,6 @@ optPartial :: Parser Bool optPartial = switch (long "partial" <> help "Partial evaluation: compute what the known inputs decide and, instead of failing on a λ function that cannot fire (no entry of the --symbolic file answers it), leave it in place and print the residual 𝜑-program") -optDeep :: Parser Bool-optDeep = switch (long "deep" <> help "Don't stop at the first formation: enter its bindings too, recursively, firing every λ function the --symbolic file answers and standing its answer in the place of what it computed, while everything else stays as it was written")--optJobs :: Parser Int-optJobs =-  option-    (boundedIntOption >>= validateIntOption (> 0) "--jobs must be positive")-    (long "jobs" <> metavar "JOBS" <> help "Number of workers the --deep walk morphs the bindings of the formation it starts at on, side by side, each with a memo, a tally and fresh names of its own" <> value 1 <> showDefault)- optAcyclic :: Parser (Maybe Acyclic) optAcyclic = optional (option parseAcyclic (long "acyclic" <> metavar "MODE" <> help "Stop reducing as soon as the reduction enters a formation it is already inside (fires its λ function or dataizes its φ body again) instead of going round until --max-steps runs out, and leave the term in place the way --partial leaves a λ function that cannot fire; 'proven' takes it for the same one up to a renaming of symbols, 'plausible' also when it holds the earlier one under wrappers it gained, such as a growing accumulator"))   where@@ -247,6 +238,12 @@       found : _ -> Right found       [] -> Left (printf "The value '%s' can't be used for '--acyclic' option, use --help to check possible values" mode) +optMaxLoops :: Parser Int+optMaxLoops =+  option+    (boundedIntOption >>= validateIntOption (>= 0) "--max-loops must be >= 0")+    (long "max-loops" <> metavar "LOOPS" <> help "Number of times the reduction may enter again a formation like one it is already inside before --acyclic cuts it, so that a recursion stopping after that many rounds is not cut" <> value 0 <> showDefault)+ optSymbolic :: Parser (Maybe FilePath) optSymbolic =   optional@@ -405,6 +402,7 @@             <*> switch (long "quiet" <> help "Don't print the result of dataization")             <*> optPartial             <*> optAcyclic+            <*> optMaxLoops             <*> optCompress             <*> optMaxDepth             <*> optMaxCycles@@ -452,9 +450,8 @@             <*> optSeed             <*> switch (long "quiet" <> help "Don't print the result of morphing")             <*> optPartial-            <*> optDeep-            <*> optJobs             <*> optAcyclic+            <*> optMaxLoops             <*> optCompress             <*> optMaxDepth             <*> optMaxCycles
src/CLI/Runners.hs view
@@ -12,7 +12,6 @@ import CLI.Types import CLI.Validators import Condition (parseConditionThrows)-import Control.Concurrent (rtsSupportsBoundThreads, setNumCapabilities) import Control.Exception import Control.Monad (unless, when) import Data.Foldable (traverse_)@@ -27,7 +26,7 @@ import Emit (emitted) import Encoding import Engine (Engine (..), building, current, stepOf)-import Evaluate (evaluation, fired)+import Evaluate (evaluation) import Files (overwrite) import qualified Filter as F import LaTeX (explainContextualizeRules, explainDataizeRules, explainMorphRules, explainRules)@@ -188,7 +187,7 @@       _protocol       printCtx       ( \record -> do-          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Dataization, loc)) _depthSensitive _shuffle _partial False 1 _acyclic Dataization [] Map.empty lambdas (building linked) reduction evaluation fired save record linked+          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Dataization, loc)) _depthSensitive _shuffle _partial _acyclic _maxLoops Dataization [] Map.empty lambdas (building linked) reduction evaluation save record linked           (universe, aiming) <- aimed printCtx Dataization _inside expr ctx           started universe aiming           dataize universe emptyState aiming@@ -214,6 +213,7 @@       when (length _show > 1) (invalidCLIArguments "The option --show can be used only once")       when (isJust _abridged && isNothing _protocol) (invalidCLIArguments "The option --abridged requires --protocol, since only the protocol is abridged")       when (_abridgedData && isNothing _abridged) (invalidCLIArguments "The option --abridged-data requires --abridged, since only an abridged protocol cuts its data")+      when (_maxLoops > 0 && isNothing _acyclic) (invalidCLIArguments "The option --max-loops requires --acyclic, since only --acyclic cuts a loop")       when         (isJust _inside && _locator /= "Q")         (invalidCLIArguments "The options --inside and --locator cannot be used together, since --inside aims the run at the binding it mints")@@ -246,7 +246,6 @@ runMorph OptsMorph{..} = do   validateOpts   deadline <- timed _maxSeconds-  when rtsSupportsBoundThreads (setNumCapabilities _jobs)   lambdas <- lambdasOf _symbolic   excluded <- validatedDispatches "hide" _hide   included <- validatedDispatches "show" _show@@ -270,7 +269,7 @@       _protocol       printCtx       ( \record -> do-          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Morphing, loc)) _depthSensitive _shuffle _partial _deep _jobs _acyclic Morphing [] Map.empty lambdas (building linked) reduction evaluation fired save record linked+          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Morphing, loc)) _depthSensitive _shuffle _partial _acyclic _maxLoops Morphing [] Map.empty lambdas (building linked) reduction evaluation save record linked           (universe, aiming) <- aimed printCtx Morphing _inside expr ctx           started universe aiming           morph universe emptyState aiming@@ -299,7 +298,7 @@       when (length _show > 1) (invalidCLIArguments "The option --show can be used only once")       when (isJust _abridged && isNothing _protocol) (invalidCLIArguments "The option --abridged requires --protocol, since only the protocol is abridged")       when (_abridgedData && isNothing _abridged) (invalidCLIArguments "The option --abridged-data requires --abridged, since only an abridged protocol cuts its data")-      when (_jobs > 1 && not _deep) (invalidCLIArguments "The option --jobs requires --deep, since only the deep walk runs on several workers")+      when (_maxLoops > 0 && isNothing _acyclic) (invalidCLIArguments "The option --max-loops requires --acyclic, since only --acyclic cuts a loop")       when         (isJust _inside && _locator /= "Q")         (invalidCLIArguments "The options --inside and --locator cannot be used together, since --inside aims the run at the binding it mints")
src/CLI/Types.hs view
@@ -110,6 +110,7 @@   , _quiet :: Bool   , _partial :: Bool   , _acyclic :: Maybe Acyclic+  , _maxLoops :: Int   , _compress :: Bool   , _maxDepth :: Int   , _maxCycles :: Int@@ -154,9 +155,8 @@   , _seed :: Int   , _quiet :: Bool   , _partial :: Bool-  , _deep :: Bool-  , _jobs :: Int   , _acyclic :: Maybe Acyclic+  , _maxLoops :: Int   , _compress :: Bool   , _maxDepth :: Int   , _maxCycles :: Int
src/Deps.hs view
@@ -101,7 +101,7 @@   | EvFiring Int T.Text Judgment Expression   | EvStarted Int Judgment Expression   | EvDelta Int Bytes-  | EvLooped Int Judgment Acyclic Expression Expression (Maybe (Int, Maybe Expression))+  | EvLooped Int Judgment Acyclic Expression Expression   | EvStuck Int T.Text Judgment Expression   | EvStall Int T.Text   | EvStuckOn Int T.Text@@ -109,66 +109,29 @@   | EvTimeout Int Int Judgment Expression   | EvSpent Int Int Judgment Expression   | EvData Int T.Text Expression (Either Int Bytes)-  | EvTerm Int T.Text Expression Expression+  | EvTerm Int T.Text Judgment Expression Expression   | EvSymbolize Int T.Text Expression Expression   | EvKnown Int Int Bytes   | EvJoin Int T.Text (T.Text, T.Text) Expression   | EvJoined Int Int (Int, Int)   | EvTerminate Int (Maybe (Either Int Bytes)) T.Text T.Text (Maybe Expression)   | EvMinted Int Int [Either Int Bytes]-  | EvDeferred Int Int Judgment Expression (Maybe Expression) Expression   | EvApplied Int Judgment Expression Expression Expression-  | EvComputed Int Expression Expression   | EvBuilt Int Expression   | EvAnswer Int Expression  type SaveEvalFunc = Evaluation -> IO () -renumbered :: Int -> Int -> Evaluation -> Evaluation-renumbered floor' offset = record-  where-    record :: Evaluation -> Evaluation-    record (EvFiring depth key judgment site) = EvFiring depth key judgment (term site)-    record (EvStarted depth judgment site) = EvStarted depth judgment (term site)-    record (EvLooped depth judgment mode self site answered) = EvLooped depth judgment mode (term self) (term site) (fmap (bimap symbol (fmap term)) answered)-    record (EvStuck depth key judgment self) = EvStuck depth key judgment (term self)-    record (EvStarved depth limit judgment site) = EvStarved depth limit judgment (term site)-    record (EvTimeout depth limit judgment site) = EvTimeout depth limit judgment (term site)-    record (EvSpent depth limit judgment site) = EvSpent depth limit judgment (term site)-    record (EvData depth spelling operand value) = EvData depth spelling (term operand) (datum value)-    record (EvTerm depth spelling operand value) = EvTerm depth spelling (term operand) (term value)-    record (EvSymbolize depth spelling source value) = EvSymbolize depth spelling (term source) (term value)-    record (EvKnown depth sym bytes) = EvKnown depth (symbol sym) bytes-    record (EvJoin depth spelling pair value) = EvJoin depth spelling pair (term value)-    record (EvJoined depth fresh (one, two)) = EvJoined depth (symbol fresh) (symbol one, symbol two)-    record (EvTerminate depth condition side raising message) = EvTerminate depth (fmap datum condition) side raising (fmap term message)-    record (EvMinted depth sym operands) = EvMinted depth (symbol sym) (map datum operands)-    record (EvDeferred depth sym judgment copy call site) = EvDeferred depth (symbol sym) judgment (term copy) (fmap term call) (term site)-    record (EvApplied depth judgment call object site) = EvApplied depth judgment (term call) (term object) (term site)-    record (EvComputed depth before after) = EvComputed depth (term before) (term after)-    record (EvBuilt depth value) = EvBuilt depth (term value)-    record (EvAnswer depth value) = EvAnswer depth (term value)-    record other = other-    term :: Expression -> Expression-    term = lifted floor' offset-    symbol :: Int -> Int-    symbol idx-      | idx > floor' = idx + offset-      | otherwise = idx-    datum :: Either Int Bytes -> Either Int Bytes-    datum = either (Left . symbol) Right- resited :: Expression -> Expression -> Evaluation -> Evaluation resited from to = record   where     record :: Evaluation -> Evaluation     record (EvFiring depth key judgment site) = EvFiring depth key judgment (moved site)     record (EvStarted depth judgment site) = EvStarted depth judgment (moved site)-    record (EvLooped depth judgment mode self site answered) = EvLooped depth judgment mode self (moved site) answered+    record (EvLooped depth judgment mode self site) = EvLooped depth judgment mode self (moved site)     record (EvStarved depth limit judgment site) = EvStarved depth limit judgment (moved site)     record (EvTimeout depth limit judgment site) = EvTimeout depth limit judgment (moved site)     record (EvSpent depth limit judgment site) = EvSpent depth limit judgment (moved site)-    record (EvDeferred depth sym judgment copy call site) = EvDeferred depth sym judgment copy call (moved site)     record (EvApplied depth judgment call object site) = EvApplied depth judgment call object (moved site)     record other = other     moved :: Expression -> Expression@@ -186,7 +149,7 @@ tier (EvFiring depth _ _ _) = depth tier (EvStarted depth _ _) = depth tier (EvDelta depth _) = depth-tier (EvLooped depth _ _ _ _ _) = depth+tier (EvLooped depth _ _ _ _) = depth tier (EvStuck depth _ _ _) = depth tier (EvStall depth _) = depth tier (EvStuckOn depth _) = depth@@ -194,16 +157,14 @@ tier (EvTimeout depth _ _ _) = depth tier (EvSpent depth _ _ _) = depth tier (EvData depth _ _ _) = depth-tier (EvTerm depth _ _ _) = depth+tier (EvTerm depth _ _ _ _) = depth tier (EvSymbolize depth _ _ _) = depth tier (EvKnown depth _ _) = depth tier (EvJoin depth _ _ _) = depth tier (EvJoined depth _ _) = depth tier (EvTerminate depth _ _ _ _) = depth tier (EvMinted depth _ _) = depth-tier (EvDeferred depth _ _ _ _ _) = depth tier (EvApplied depth _ _ _ _) = depth-tier (EvComputed depth _ _) = depth tier (EvBuilt depth _) = depth tier (EvAnswer depth _) = depth @@ -218,9 +179,6 @@     renamed :: Maybe [(Expression, a)] -> Maybe [(Expression, a)]     renamed entries = Just ((term, naming) : filter ((/= term) . fst) (fromMaybe [] entries)) -namedCarry :: Expression -> Expression -> Named a -> Named a-namedCarry before after names = maybe names (\naming -> namedInsert after naming names) (namedLookup before names)- abbreviated :: Named Expression -> Expression -> Expression abbreviated names term   | Map.null names = term@@ -280,7 +238,6 @@     forgotten _ protocol = protocol{_found = Nothing}     beside :: Evaluation -> Protocol -> Protocol     beside EvStarted{} protocol = protocol-    beside EvComputed{} protocol = protocol     beside EvMinted{} protocol = protocol     beside report' protocol = protocol{_blocks = dropWhile (\(level, _, _) -> level >= tier report') protocol._blocks}     headed :: (Protocol, Maybe String) -> (Protocol, [String])@@ -349,13 +306,10 @@           naming :: String           naming = printf "%s·%d" (labelled protocol sigil) index       pure (protocol{_deltas = Map.insert (opener protocol) index protocol._deltas, _found = Just (bytes, naming)}, Just (indented depth (remarked (printf "%s := %s" naming datum) (maybe [] pure (ieee bytes)))))-    written (EvLooped depth judgment mode self site answered) protocol = do+    written (EvLooped depth judgment mode self site) protocol = do       form <- render self       remarks <- context protocol judgment site-      pure (protocol, Just (indented depth (remarked (printf "looped(%s)%s" form (maybe "" symbolized answered)) (remarks ++ [certainty mode]))))-      where-        symbolized :: (Int, Maybe Expression) -> String-        symbolized (symbol, _) = printf " := %s" (printFunction (FnSymbol symbol))+      pure (protocol, Just (indented depth (remarked (printf "looped(%s)" form) (remarks ++ [certainty mode]))))     written (EvStuck depth key judgment self) protocol = do       form <- render self       pure (protocol, Just (indented depth (printf "unanswered(%s)  # %s(%s)" (T.unpack key) (letter judgment) form)))@@ -380,10 +334,10 @@         spelled :: Either Int Bytes -> IO String         spelled (Left symbol) = printf "𝔻(%s)" <$> render (standing symbol)         spelled (Right bytes) = render (ExBytes bytes)-    written (EvTerm depth spelling operand term) protocol = do+    written (EvTerm depth spelling judgment operand term) protocol = do       let naming = labelled protocol spelling       (protocol', value) <- valued protocol naming term-      line <- commented (printf "%s := %s" naming value) Morphing operand+      line <- commented (printf "%s := %s" naming value) judgment operand       pure (protocol', Just (indented depth line))     written (EvSymbolize depth spelling source term) protocol = do       let naming = labelled protocol spelling@@ -412,10 +366,6 @@         spelled (Left symbol) = printf "𝔻(%s)" <$> render (standing symbol)         spelled (Right bytes) = render (ExBytes bytes)     written EvMinted{} protocol = pure (protocol, Nothing)-    written (EvDeferred depth symbol judgment copy call site) protocol = do-      form <- maybe (render copy) (printed . abbreviatedInside protocol._made) call-      remarks <- context protocol judgment site-      pure (protocol, Just (indented depth (remarked (printf "deferred(%s) := %s" (printFunction (FnSymbol symbol)) form) remarks)))     written (EvApplied depth judgment call object site) protocol = do       let (index, counted) = numbered protocol           aliased :: Expression@@ -423,7 +373,6 @@       form <- printed (abbreviatedInside protocol._made call)       remarks <- context protocol judgment site       pure (counted{_made = namedInsert call aliased (namedInsert object aliased counted._made)}, Just (indented depth (remarked (printf "%s·%d := %s" (labelled protocol answer) index form) remarks)))-    written (EvComputed _ before after) protocol = pure (protocol{_made = namedCarry before after protocol._made}, Nothing)     written (EvBuilt depth term) protocol = do       let (index, counted) = numbered protocol       value <- borrowed protocol term@@ -497,7 +446,6 @@     opened' (nesting, openings) = bimap (forgotten report) (openings ++) <$> elements report nesting     beside :: Evaluation -> Nesting -> Nesting     beside EvStarted{} nesting = nesting-    beside EvComputed{} nesting = nesting     beside report' nesting = nesting{_heads = dropWhile ((>= tier report') . fst) nesting._heads, _pending = dropWhile (\(level, _, _) -> level >= tier report') nesting._pending}     flushed :: Nesting -> (Nesting, [String])     flushed nesting@@ -505,7 +453,6 @@       | otherwise = (nesting{_pending = [], _closing = [(level, element) | (level, element, _) <- nesting._pending] ++ nesting._closing}, [opening | (_, _, opening) <- reverse nesting._pending])     silent :: Evaluation -> Bool     silent EvStarted{} = True-    silent EvComputed{} = True     silent _ = False     forgotten :: Evaluation -> Nesting -> Nesting     forgotten EvDelta{} nesting = nesting@@ -543,15 +490,11 @@       pure $ case inner of         (level, opening') : _ | level == depth && opening' == opening -> (nesting{_heads = inner, _pending = pending}, [])         _ -> (nesting{_heads = (depth, opening) : dropWhile ((>= depth) . fst) inner, _pending = (depth, opened judgment, opening) : dropWhile (\(level, _, _) -> level >= depth) pending, _closing = kept}, closers)-    elements (EvLooped depth judgment mode self site answered) nesting = do+    elements (EvLooped depth judgment mode self site) nesting = do       form <- render self       locator <- located nesting site-      (origin, given) <- maybe (pure ("", "")) called (answered >>= snd)       let (kept, closers) = closed depth nesting._closing-      pure (nesting{_closing = kept}, closers ++ [indentedXml depth (printf "<looped%s by=\"%s\" match=\"%s\" at=\"%s\"%s>%s<e>%s</e></looped>" (maybe "" symbolized answered) (opened judgment) (certainty mode) (escapeXML locator) origin given (escapeXMLText form))])-      where-        symbolized :: (Int, Maybe Expression) -> String-        symbolized (symbol, _) = printf " symbol=\"%s\"" (sigma symbol)+      pure (nesting{_closing = kept}, closers ++ [indentedXml depth (printf "<looped by=\"%s\" match=\"%s\" at=\"%s\"><e>%s</e></looped>" (opened judgment) (certainty mode) (escapeXML locator) (escapeXMLText form))])     elements (EvStuck depth key judgment self) nesting = do       form <- render self       let (kept, closers) = closed depth nesting._closing@@ -593,7 +536,7 @@         stood (Right bytes) = do           form <- render (ExBytes bytes)           pure (printf "<bind meta=\"%s\">%s</bind>" (escapeXML (labelled nesting spelling)) (escapeXMLText form))-    elements (EvTerm depth spelling _ term) nesting = do+    elements (EvTerm depth spelling _ _ term) nesting = do       body <- render term       let (kept, closers) = closed depth nesting._closing       pure (nesting{_closing = kept}, closers ++ [indentedXml depth (printf "<bind meta=\"%s\">%s</bind>" (escapeXML (labelled nesting spelling)) (escapeXMLText body))])@@ -638,12 +581,6 @@         spelled :: Either Int Bytes -> IO String         spelled (Left fresh) = pure (sigma fresh)         spelled (Right bytes) = render (ExBytes bytes)-    elements (EvDeferred depth symbol judgment copy call site) nesting = do-      form <- render copy-      locator <- located nesting site-      (origin, given) <- maybe (pure ("", "")) called call-      let (kept, closers) = closed depth nesting._closing-      pure (nesting{_closing = kept}, closers ++ [indentedXml depth (printf "<deferred symbol=\"%s\" by=\"%s\" at=\"%s\"%s>%s<e>%s</e></deferred>" (sigma symbol) (opened judgment) (escapeXML locator) origin given (escapeXMLText form))])     elements (EvApplied depth judgment call object site) nesting = do       (origin, given) <- parted (abbreviatedInside nesting._objects call)       locator <- located nesting site@@ -654,7 +591,6 @@           aliased :: Expression           aliased = alias (opener nesting) index       pure (counted{_closing = kept, _objects = namedInsert call aliased (namedInsert object aliased counted._objects)}, closers ++ [indentedXml depth (printf "<applied meta=\"%s\" by=\"%s\" at=\"%s\" of=\"%s\">%s</applied>" (escapeXML naming) (opened judgment) (escapeXML locator) (escapeXML origin) given)])-    elements (EvComputed _ before after) nesting = pure (nesting{_objects = namedCarry before after nesting._objects}, [])     elements (EvBuilt depth term) nesting = do       body <- render term       let (index, counted) = numbered nesting@@ -680,21 +616,6 @@       where         index :: Int         index = maybe 1 (+ 1) (Map.lookup (opener nesting) nesting._numbered)-    called :: Expression -> IO (String, String)-    called term = do-      let (object, arguments) = invoked term-      path <- render object-      pure (printf " of=\"%s\"" (escapeXML path), printf "<with>%s</with>" (concat arguments))-    invoked :: Expression -> (Expression, [String])-    invoked (ExApplication term (ArTau attr value)) =-      let (object, arguments) = invoked term-       in (object, arguments ++ [printf "<attr name=\"%s\">%s</attr>" (escapeXML (printAttribute attr)) (escapeXMLText (valued value))])-    invoked term = (term, [])-    valued :: Expression -> String-    valued (ExFormation [BiLambda (FnSymbol idx)]) = sigma idx-    valued (ExFormation bds) = maybe "?" valued (listToMaybe [body | BiTau AtPhi body <- bds])-    valued (ExApplication _ (ArTau AtPhi body)) = valued body-    valued _ = "?"     parted :: Expression -> IO (String, String)     parted (ExApplication term (ArTau attr value)) = do       origin <- printed term
src/Evaluate.hs view
@@ -5,31 +5,31 @@ -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com -- SPDX-License-Identifier: MIT -module Evaluate (evaluation, fired) where+module Evaluate (evaluation) where  import AST import Builder (buildExpressionThrows)-import Control.Exception (catch, throwIO, try)+import Control.Exception (throwIO, try) import Control.Monad (foldM, unless) import Data.IORef (readIORef, writeIORef) import Data.List (partition) import Data.List.NonEmpty (NonEmpty (..))-import Data.Maybe (fromMaybe, isNothing, listToMaybe)+import Data.Maybe (fromMaybe, listToMaybe) import qualified Data.Text as T-import Deps (Evaluation (..), Judgment (Morphing), State (..), resited)+import Deps (Evaluation (..), Judgment (Evaluation, Morphing), State (..), resited) import Engine (Engine (..)) import Lambdas (Lambda (..), Meta (..), joined, matched, message, minted, symbolized, thrown) import Matcher (MetaValue (..), Subst, combine, substEmpty, substSingle, substSlot)-import Morph (Answer, Firing (..), Kept (..), ReduceContext (..), ReduceException (..), Refused (..), Steps (..), admitted, charged, counted, deeper, isLambda, lambda, morphing, normalized, opening, recalled, refused, remember, remembered, retained, settled, starved, unparked)+import Morph (Answer, Firing (..), Kept (..), ReduceContext (..), ReduceException (..), Steps (..), charged, counted, deeper, entering, isLambda, lambda, morphing, normalized, opening, recalled, remember, remembered, retained, settled, starved, unparked) import Printer (printFunction)-import Rule (RuleContext (RuleContext), matchExpressionWithRule')+import Rule (RuleContext (RuleContext), matchExpressionWithRule', xiFree) import Text.Printf (printf) import qualified Yaml as Y  evaluation :: ReduceContext -> State -> Expression -> Expression -> IO (Expression, State) evaluation ctx state form univ = case form of   ExFormation bds-    | not (any isLambda bds) -> pure (ExTermination, state)+    | not (any isLambda bds) -> pure (form, state)     | otherwise -> case lambda bds of         Just (func, args) -> do           (raw, state') <- symbol func form args univ state ctx@@ -85,12 +85,13 @@           throwIO failure     worked :: ReduceContext -> Lambda -> Firing -> Subst -> State -> IO (Answer, State)     worked ctx entry firing@(Firing _ operands _) bound state' = do-      rewrote <- foldM (reshaped ctx) bound entry._rewritten+      (fired, state'') <- foldM (evaluated ctx entry) (bound, state') entry._evaluated+      rewrote <- foldM (reshaped ctx) fired entry._rewritten       stood <- foldM (masked ctx) rewrote entry._symbolized-      forked <- foldM (paired ctx entry (listToMaybe operands)) stood entry._paired-      (answer, state'') <- answered ctx entry operands forked state'+      (forked, joining) <- foldM (paired ctx entry (listToMaybe operands)) (stood, state'') entry._paired+      (answer, state''') <- answered ctx entry operands forked joining       remember caller._memo firing answer-      pure (answer, state'')+      pure (answer, state''')     shown :: Int -> Answer -> IO ()     shown depth (built, normal) = do       caller._saveEval (EvBuilt depth built)@@ -115,7 +116,7 @@       pure (snd answer, state)     told (Looped term) = do       caller._saveEval (EvFiring caller._nesting func caller._judgment caller._site)-      mapM_ (\mode -> caller._saveEval (EvLooped (caller._nesting + 1) caller._judgment mode term caller._site Nothing)) caller._acyclic+      mapM_ (\mode -> caller._saveEval (EvLooped (caller._nesting + 1) caller._judgment mode term caller._site)) caller._acyclic       throwIO (Looping term)     told (Stalled name _) = do       caller._saveEval (EvFiring caller._nesting func caller._judgment caller._site)@@ -136,9 +137,23 @@     through ctx (bound, state', normals) (meta, term) = do       placed <- operand term       (normal, state'') <- morphing univ ctx placed state'-      ctx._saveEval (EvTerm ctx._nesting meta._spelling term normal)+      ctx._saveEval (EvTerm ctx._nesting meta._spelling Morphing term normal)       bound' <- bind meta (MvExpression normal) bound       pure (bound', state'', normal : normals)+    evaluated :: ReduceContext -> Lambda -> (Subst, State) -> (Meta, Meta) -> IO (Subst, State)+    evaluated ctx entry (bound, state') (meta, source) = do+      term <- buildExpressionThrows (ExMeta source._name) bound+      (answer, state'') <- fire ctx entry source state' term+      ctx._saveEval (EvTerm ctx._nesting meta._spelling Evaluation (ExMeta source._name) answer)+      bound' <- bind meta (MvExpression answer) bound+      pure (bound', state'')+    fire :: ReduceContext -> Lambda -> Meta -> State -> Expression -> IO (Expression, State)+    fire _ entry _ state' term+      | raising entry term = pure (term, state')+    fire ctx _ source state' term@(ExFormation _) = do+      entered <- deeper =<< entering term ctx{_judgment = Evaluation, _site = ExMeta source._name}+      evaluation entered state' term univ+    fire _ _ _ _ _ = throwIO (Stuck func)     reshaped :: ReduceContext -> Subst -> (Meta, (Meta, [Y.Rule])) -> IO Subst     reshaped ctx bound (meta, (source, rules)) = do       term <- buildExpressionThrows (ExMeta source._name) bound@@ -156,37 +171,59 @@       where         fact :: (Int, Bytes) -> Evaluation         fact (fresh, bytes) = EvKnown ctx._nesting fresh bytes-    paired :: ReduceContext -> Lambda -> Maybe (Either Int Bytes) -> Subst -> (Meta, (Meta, Meta)) -> IO Subst-    paired ctx entry condition bound (meta, (left, right)) = do+    paired :: ReduceContext -> Lambda -> Maybe (Either Int Bytes) -> (Subst, State) -> (Meta, (Meta, Meta)) -> IO (Subst, State)+    paired ctx entry condition (bound, state') (meta, (left, right)) = do       one <- branch left       two <- branch right-      case (raising one, raising two) of+      case (raising entry one, raising entry two) of         (True, False) -> terminating "left" left one two         (False, True) -> terminating "right" right two one         _ -> both one two       where-        raising :: Expression -> Bool-        raising ExTermination = True-        raising term = thrown entry._terminators term-        both :: Expression -> Expression -> IO Subst+        both :: Expression -> Expression -> IO (Subst, State)         both one two = do           outcome <- joined entry._lenient one two <$> readIORef ctx._minted           case outcome of-            Nothing -> throwIO (Stuck func)-            Just (term, made, spent) -> do-              writeIORef ctx._minted spent-              mapM_ (ctx._saveEval . fact) made-              ctx._saveEval (EvJoin ctx._nesting meta._spelling (left._spelling, right._spelling) term)-              bind meta (MvExpression term) bound-        terminating :: T.Text -> Meta -> Expression -> Expression -> IO Subst+            Just made -> written state' made+            Nothing+              | entry._lenient && (hanging one || hanging two) -> do+                  (one', reducing) <- reduced left one state'+                  (two', state'') <- reduced right two reducing+                  outcome' <- joined entry._lenient one' two' <$> readIORef ctx._minted+                  maybe (throwIO (Stuck func)) (written state'') outcome'+              | otherwise -> throwIO (Stuck func)+        written :: State -> (Expression, [(Int, (Int, Int))], Int) -> IO (Subst, State)+        written state'' (term, made, spent) = do+          writeIORef ctx._minted spent+          mapM_ (ctx._saveEval . fact) made+          ctx._saveEval (EvJoin ctx._nesting meta._spelling (left._spelling, right._spelling) term)+          bound' <- bind meta (MvExpression term) bound+          pure (bound', state'')+        reduced :: Meta -> Expression -> State -> IO (Expression, State)+        reduced side term state''+          | hanging term = do+              (normal, morphed) <- morphing univ ctx (ExDispatch term AtPhi) state''+              ctx._saveEval (EvTerm ctx._nesting side._spelling Morphing (ExDispatch (ExMeta side._name) AtPhi) normal)+              (answer, state''') <- fire ctx entry side morphed normal+              ctx._saveEval (EvTerm ctx._nesting side._spelling Evaluation (ExMeta side._name) answer)+              pure (answer, state''')+          | otherwise = pure (term, state'')+        hanging :: Expression -> Bool+        hanging (ExFormation bds) = not (any isLambda bds) && or [not (xiFree body) | BiTau AtPhi body <- bds]+        hanging _ = False+        terminating :: T.Text -> Meta -> Expression -> Expression -> IO (Subst, State)         terminating side raised fault term = do           ctx._saveEval (EvTerminate ctx._nesting condition side raised._spelling (message fault))           ctx._saveEval (EvJoin ctx._nesting meta._spelling (left._spelling, right._spelling) term)-          bind meta (MvExpression term) bound+          bound' <- bind meta (MvExpression term) bound+          pure (bound', state')         branch :: Meta -> IO Expression         branch named = buildExpressionThrows (ExMeta named._name) bound         fact :: (Int, (Int, Int)) -> Evaluation         fact (fresh, pair) = EvJoined ctx._nesting fresh pair+    raising :: Lambda -> Expression -> Bool+    raising _ ExTermination = True+    raising entry term = thrown entry._terminators term     answered :: ReduceContext -> Lambda -> [Either Int Bytes] -> Subst -> State -> IO (Answer, State)     answered ctx entry operands bound state' = do       (fresh, spent) <- minted entry._answer <$> readIORef ctx._minted@@ -237,73 +274,3 @@     goArgument :: Argument -> IO Argument     goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr     goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr--fired :: Maybe Attribute -> Expression -> Expression -> State -> ReduceContext -> IO (Maybe Expression, State)-fired dispatched term univ state caller = do-  ctx <- deeper caller-  morphed <- try (reduced ctx)-  case morphed of-    Right (ExFormation bds, state')-      | demanded bds -> maybe (pure (Nothing, state')) (evaluated ctx state' (ExFormation bds)) (saturated term bds)-    Right (_, state') -> pure (Nothing, state')-    Left failure -> parked state failure-  where-    demanded :: [Binding] -> Bool-    demanded bds = not (any bound bds)-      where-        bound :: Binding -> Bool-        bound (BiTau attr _) = Just attr == dispatched-        bound _ = False-    reduced :: ReduceContext -> IO (Expression, State)-    reduced = settled term univ state-    evaluated :: ReduceContext -> State -> Expression -> (T.Text, Expression) -> IO (Maybe Expression, State)-    evaluated ctx state' form (func, self)-      | isNothing (matched ctx._symbolic func) = pure (Nothing, state')-      | otherwise = do-          made <- try (admitted form ctx >>= either (\(mode, before) -> throwIO (Refused mode before state')) (symbol func form self univ state'))-          case made of-            Right (answer, answered) -> do-              (again, reached) <- fired dispatched answer univ answered ctx `catch` refused ctx-              pure (Just (fromMaybe answer again), reached)-            Left failure -> parked state' failure-    parked :: State -> ReduceException -> IO (Maybe Expression, State)-    parked _ (StuckAt _ _ reached) | caller._partial = pure (Nothing, reached)-    parked _ (OutOfStepsAt _ _ reached) | caller._partial = pure (Nothing, reached)-    parked reached (Stuck _) | caller._partial = pure (Nothing, reached)-    parked reached (OutOfSteps _) | caller._partial = pure (Nothing, reached)-    parked _ (LoopingAt _ _ reached) = pure (Nothing, reached)-    parked reached (Looping _) = pure (Nothing, reached)-    parked _ (StuckAt func _ _) = throwIO (Stuck func)-    parked _ (OutOfStepsAt budget _ _) = throwIO (OutOfSteps budget)-    parked _ failure = throwIO failure--saturated :: Expression -> [Binding] -> Maybe (T.Text, Expression)-saturated term bds = case lambda bds of-  Just (func, ExFormation rest)-    | all filled rest && (not (any raising rest) || given rest) -> Just (func, ExFormation rest)-  _ -> Nothing-  where-    named :: [Attribute]-    positional :: Int-    valued :: Bool-    (named, positional, valued) = written term-    given :: [Binding] -> Bool-    given rest = valued && length (filter unwritten rest) <= positional-    raising :: Binding -> Bool-    raising (BiTau _ ExTermination) = True-    raising _ = False-    unwritten :: Binding -> Bool-    unwritten (BiTau attr ExTermination) = attr `notElem` named-    unwritten _ = False-    written :: Expression -> ([Attribute], Int, Bool)-    written (ExApplication expr (ArTau attr ExTermination)) =-      let (attrs, count, other) = written expr in (attr : attrs, count, other)-    written (ExApplication expr (ArAlpha _ ExTermination)) =-      let (attrs, count, other) = written expr in (attrs, count + 1, other)-    written (ExApplication expr _) =-      let (attrs, count, _) = written expr in (attrs, count, True)-    written _ = ([], 0, False)--filled :: Binding -> Bool-filled (BiVoid _) = False-filled _ = True
src/Lambdas.hs view
@@ -57,6 +57,7 @@   { _key :: Text   , _dataized :: [(Meta, Expression)]   , _morphed :: [(Meta, Expression)]+  , _evaluated :: [(Meta, Meta)]   , _rewritten :: [(Meta, (Meta, [Y.Rule]))]   , _symbolized :: [(Meta, Expression)]   , _paired :: [(Meta, (Meta, Meta))]@@ -83,6 +84,7 @@       Lambda key         <$> operands key bytesMeta entry "dataize"         <*> operands key expressionMeta entry "morph"+        <*> evaluations entry         <*> rewrites (T.unpack key) entry         <*> operands key expressionMeta entry "symbolize"         <*> pairs (T.unpack key) entry@@ -116,6 +118,10 @@       bytesMeta meta = case parseBytes (T.unpack meta) of         Right (BtMeta name) -> pure (Meta meta name)         _ -> fail (printf "The operand '%s' is not a bytes meta, such as '𝛿1'" (T.unpack meta))+      evaluations :: Object -> Yaml.Parser [(Meta, Meta)]+      evaluations entry = do+        mapping <- entry .:? "evaluate" .!= (Map.empty :: Map Text Text)+        mapM (\(meta, source) -> (,) <$> expressionMeta meta <*> expressionMeta source) (numbered mapping)       pairs :: String -> Object -> Yaml.Parser [(Meta, (Meta, Meta))]       pairs key entry = do         mapping <- entry .:? "join" .!= (Map.empty :: Map Text [Text])@@ -172,14 +178,15 @@               known = metas parsed.pattern ++ concatMap (metas . (.meta)) (concat parsed.where_)       earlier :: String -> Lambda -> Yaml.Parser ()       earlier key lambda = do-        rewrote <- goRewrites (map (_name . fst) lambda._morphed) lambda._rewritten+        fired <- goSources (map (_name . fst) lambda._morphed) lambda._evaluated+        rewrote <- goSources fired [(meta, source) | (meta, (source, _)) <- lambda._rewritten]         stood <- go rewrote lambda._symbolized         void (goJoins stood lambda._paired)         where-          goRewrites :: [Text] -> [(Meta, (Meta, [Y.Rule]))] -> Yaml.Parser [Text]-          goRewrites reduced [] = pure reduced-          goRewrites reduced ((meta, (source, _)) : rest)-            | source._name `elem` reduced = goRewrites (meta._name : reduced) rest+          goSources :: [Text] -> [(Meta, Meta)] -> Yaml.Parser [Text]+          goSources reduced [] = pure reduced+          goSources reduced ((meta, source) : rest)+            | source._name `elem` reduced = goSources (meta._name : reduced) rest             | otherwise = unbound source           go :: [Text] -> [(Meta, Expression)] -> Yaml.Parser [Text]           go reduced [] = pure reduced@@ -212,6 +219,7 @@           bound :: [Text]           bound =             map (_spelling . fst) lambda._morphed+              ++ map (_spelling . fst) lambda._evaluated               ++ map (_spelling . fst) lambda._rewritten               ++ map (_spelling . fst) lambda._symbolized               ++ map (_spelling . fst) lambda._paired@@ -241,6 +249,7 @@           known =             map (_name . fst) lambda._dataized               ++ map (_name . fst) lambda._morphed+              ++ map (_name . fst) lambda._evaluated               ++ map (_name . fst) lambda._rewritten               ++ map (_name . fst) lambda._symbolized               ++ map (_name . fst) lambda._paired
src/Margin.hs view
@@ -8,6 +8,7 @@ module Margin (defaultMargin, withMargin, withLines, WithMargin) where  import CST+import Data.Char (GeneralCategory (..), generalCategory) import qualified Data.Text as T import Lining (LineFormat (..), ToSingleLine (..)) import Render (Render (..))@@ -44,7 +45,7 @@     let single = toSingleLine ex         main = withMargin' cfg expr         singleMain = toSingleLine main-        extra' = T.length (last (T.lines (render main))) + 4+        extra' = displayWidth (last (T.lines (render main))) + 4         arg' = withMargin' (2 * indt, margin) argument         singleArg = toSingleLine arg'      in if@@ -116,9 +117,14 @@  charWidth :: Char -> Int charWidth character+  | fromEnum character < 0x300 = 1+  | blank = 0   | isWideCodePoint (fromEnum character) = 2   | otherwise = 1   where+    blank :: Bool+    blank = generalCategory character `elem` [NonSpacingMark, EnclosingMark, Format]+     isWideCodePoint :: Int -> Bool     isWideCodePoint codePoint = any (inRange codePoint) wideRanges @@ -128,16 +134,73 @@     wideRanges :: [(Int, Int)]     wideRanges =       [ (0x1100, 0x115F)+      , (0x231A, 0x231B)+      , (0x2329, 0x232A)+      , (0x23E9, 0x23EC)+      , (0x23F0, 0x23F0)+      , (0x23F3, 0x23F3)+      , (0x25FD, 0x25FE)+      , (0x2614, 0x2615)+      , (0x2648, 0x2653)+      , (0x267F, 0x267F)+      , (0x2693, 0x2693)+      , (0x26A1, 0x26A1)+      , (0x26AA, 0x26AB)+      , (0x26BD, 0x26BE)+      , (0x26C4, 0x26C5)+      , (0x26CE, 0x26CE)+      , (0x26D4, 0x26D4)+      , (0x26EA, 0x26EA)+      , (0x26F2, 0x26F3)+      , (0x26F5, 0x26F5)+      , (0x26FA, 0x26FA)+      , (0x26FD, 0x26FD)+      , (0x2705, 0x2705)+      , (0x270A, 0x270B)+      , (0x2728, 0x2728)+      , (0x274C, 0x274C)+      , (0x274E, 0x274E)+      , (0x2753, 0x2755)+      , (0x2757, 0x2757)+      , (0x2795, 0x2797)+      , (0x27B0, 0x27B0)+      , (0x27BF, 0x27BF)+      , (0x2B1B, 0x2B1C)+      , (0x2B50, 0x2B50)+      , (0x2B55, 0x2B55)       , (0x2E80, 0x303E)       , (0x3041, 0x33FF)       , (0x3400, 0x4DBF)       , (0x4E00, 0x9FFF)       , (0xA000, 0xA4CF)+      , (0xA960, 0xA97C)       , (0xAC00, 0xD7A3)       , (0xF900, 0xFAFF)+      , (0xFE10, 0xFE19)       , (0xFE30, 0xFE4F)+      , (0xFE50, 0xFE52)+      , (0xFE54, 0xFE66)+      , (0xFE68, 0xFE6B)       , (0xFF00, 0xFF60)       , (0xFFE0, 0xFFE6)+      , (0x16FE0, 0x16FE4)+      , (0x16FF0, 0x16FF1)+      , (0x17000, 0x187F7)+      , (0x18800, 0x18CD5)+      , (0x18D00, 0x18D08)+      , (0x1B000, 0x1B11E)+      , (0x1B150, 0x1B152)+      , (0x1B164, 0x1B167)+      , (0x1B170, 0x1B2FB)+      , (0x1F004, 0x1F004)+      , (0x1F0CF, 0x1F0CF)+      , (0x1F18E, 0x1F18E)+      , (0x1F191, 0x1F19A)+      , (0x1F200, 0x1F202)+      , (0x1F210, 0x1F23B)+      , (0x1F240, 0x1F248)+      , (0x1F250, 0x1F251)+      , (0x1F260, 0x1F265)       , (0x1F300, 0x1FAFF)       , (0x20000, 0x3FFFD)       ]
src/Morph.hs view
@@ -1,47 +1,43 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} {-# OPTIONS_GHC -Wno-unused-record-wildcards #-}  -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com -- SPDX-License-Identifier: MIT -module Morph (Answer, Deadline (..), Firing (..), Kept (..), ReduceContext (..), ReduceException (..), EvaluationFunc, FiringFunc, Memo (..), ReductionFunc, Morphed, Refused (..), Steps (..), Tally (..), admitted, boxed, charged, counted, deeper, emptyState, enter, entering, execBuildTerm, inferred, insideUniverse, isLambda, lambda, leadsTo, memoized, morph, morph', morphing, normalized, onward, opening, parking, recalled, refused, remember, remembered, retained, settled, starved, tallied, timed, universed, unparked) where+module Morph (Answer, Deadline (..), Firing (..), Kept (..), ReduceContext (..), ReduceException (..), EvaluationFunc, Memo (..), ReductionFunc, Morphed, Steps (..), Tally (..), boxed, charged, counted, deeper, emptyState, enter, entering, execBuildTerm, inferred, insideUniverse, isLambda, lambda, leadsTo, memoized, morph, morph', morphing, normalized, onward, opening, parking, recalled, remember, remembered, retained, settled, starved, tallied, timed, universed, unparked) where  import AST-import Builder (buildExpressionThrows, nameIn, pathOf)+import Builder (buildExpressionThrows, nameIn) import Control.Applicative ((<|>))-import Control.Exception (Exception, SomeException, catch, evaluate, throwIO, try)-import Control.Monad (unless, when)-import Data.Bifunctor (first)-import Data.IORef (IORef, atomicModifyIORef', modifyIORef', newIORef, readIORef, writeIORef)+import Control.Exception (Exception, catch, evaluate, throwIO, try)+import Control.Monad (when)+import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef) import Data.List (find, partition) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe, isJust, listToMaybe)-import qualified Data.Set as Set+import Data.Maybe (isJust, listToMaybe) import qualified Data.Text as T-import Deps (Acyclic (..), BuildTermFunc, BuildTermMethod, Evaluation (..), Judgment (..), SaveEvalFunc, SaveStepFunc, State (..), Term (..), dontSaveEval, dontSaveStep, renumbered)+import Deps (Acyclic (..), BuildTermFunc, BuildTermMethod, Evaluation (..), Judgment (..), SaveEvalFunc, SaveStepFunc, State (..), Term (..), dontSaveStep) import Engine (Engine (..)) import GHC.Clock (getMonotonicTime) import qualified Inference as In-import Lambdas (Lambdas, emptyLambdas)+import Lambdas (Lambdas) import Locator (locatedExpression, withLocatedExpression) import Matcher (substEmpty) import Must (Must (..))-import Pool (pooled) import Printer (printExpression) import Random (shuffle) import Rewriter (RewriteContext (RewriteContext), Rewritten, Seen, rewrite, seenInsert) import Rule (RuleContext (RuleContext)) import System.Timeout (timeout)-import Tau (tausOf) import Text.Printf (printf) import Yaml (ExtraArgument (..)) @@ -51,8 +47,6 @@  type EvaluationFunc = ReduceContext -> State -> Expression -> Expression -> IO (Expression, State) -type FiringFunc = Maybe Attribute -> Expression -> Expression -> State -> ReduceContext -> IO (Maybe Expression, State)- emptyState :: State emptyState = State Nothing Nothing @@ -71,7 +65,7 @@   , _until :: Double   } -data Memo = Memo (IORef (Store (Int, Kept))) (IORef (Map.Map Firing Answer)) (IORef Int) (IORef Int) (IORef (Set.Set (Expression, Attribute)))+data Memo = Memo (IORef (Store (Int, Kept))) (IORef (Map.Map Firing Answer)) (IORef Int) (IORef Int)  type Answer = (Expression, Expression) @@ -85,8 +79,6 @@  type Store answer = Map.Map Int [(Expression, answer)] -data Frame = Frame (IORef Expression) (IORef Expression) Expression (Maybe Attribute)- data ReduceContext = ReduceContext   { _locator :: Expression   , _site :: Expression@@ -103,9 +95,8 @@   , _depthSensitive :: Bool   , _shuffle :: Bool   , _partial :: Bool-  , _deep :: Bool-  , _jobs :: Int   , _acyclic :: Maybe Acyclic+  , _maxLoops :: Int   , _judgment :: Judgment   , _parked :: [T.Text]   , _entered :: Seen@@ -113,7 +104,6 @@   , _buildTerm :: BuildTermFunc   , _reduce :: ReductionFunc   , _evaluate :: EvaluationFunc-  , _fire :: FiringFunc   , _saveStep :: SaveStepFunc   , _saveEval :: SaveEvalFunc   , _engine :: Engine@@ -154,18 +144,6 @@   show (Undataizable term _) = printf "no dataization rule matched, while dataization expects a normal form: %s" (printExpression term)   show (Unmorphable term) = printf "Morphing expects a normal form, but no morphing rule matches: %s" (printExpression term) -data Refused = Refused Acyclic Expression State-  deriving anyclass (Exception)--instance Show Refused where-  show (Refused _ before _) = show (Looping before)--data Severed = Severed Expression State-  deriving anyclass (Exception)--instance Show Severed where-  show (Severed answer _) = printf "The deep walk answered a copy it cut with %s" (printExpression answer)- deeper :: ReduceContext -> IO ReduceContext deeper ctx@ReduceContext{_steps = Steps limit spent} = do   clocked ctx@@ -177,7 +155,7 @@   where     starve :: Maybe Memo -> IO ()     starve Nothing = pure ()-    starve (Just (Memo _ _ _ exhausted _)) = modifyIORef' exhausted (+ 1)+    starve (Just (Memo _ _ _ exhausted)) = modifyIORef' exhausted (+ 1)  tallied :: Maybe Int -> IO (Maybe Tally) tallied = traverse (\cap -> Tally cap <$> newIORef 0)@@ -212,12 +190,12 @@   throwIO (OutOfTime cap)  memoized :: Maybe Acyclic -> IO (Maybe Memo)-memoized (Just Plausible) = Just <$> (Memo <$> newIORef Map.empty <*> newIORef Map.empty <*> newIORef 0 <*> newIORef 0 <*> newIORef Set.empty)+memoized (Just Plausible) = Just <$> (Memo <$> newIORef Map.empty <*> newIORef Map.empty <*> newIORef 0 <*> newIORef 0) memoized _ = pure Nothing  recalled :: Maybe Memo -> Expression -> Int -> IO (Maybe Kept) recalled Nothing _ _ = pure Nothing-recalled (Just (Memo store _ answers _ _)) form spent = do+recalled (Just (Memo store _ answers _)) form spent = do   kept <- readIORef store   count <- readIORef answers   let live = [known | (term, (stamp, known)) <- Map.findWithDefault [] (hashExpression form) kept, term == form, current count stamp known]@@ -232,35 +210,27 @@  counted :: Maybe Memo -> IO Int counted Nothing = pure 0-counted (Just (Memo _ _ answers _ _)) = readIORef answers+counted (Just (Memo _ _ answers _)) = readIORef answers  starved :: Maybe Memo -> IO Int starved Nothing = pure 0-starved (Just (Memo _ _ _ exhausted _)) = readIORef exhausted+starved (Just (Memo _ _ _ exhausted)) = readIORef exhausted  retained :: Maybe Memo -> Expression -> Int -> Kept -> IO () retained Nothing _ _ _ = pure ()-retained (Just (Memo store _ _ _ _)) form stamp kept =+retained (Just (Memo store _ _ _)) form stamp kept =   modifyIORef' store (Map.insertWith (++) (hashExpression form) [(form, (stamp, kept))])  remembered :: Maybe Memo -> Firing -> IO (Maybe Answer) remembered Nothing _ = pure Nothing-remembered (Just (Memo _ firings _ _ _)) firing = Map.lookup firing <$> readIORef firings+remembered (Just (Memo _ firings _ _)) firing = Map.lookup firing <$> readIORef firings  remember :: Maybe Memo -> Firing -> Answer -> IO () remember Nothing _ _ = pure ()-remember (Just (Memo _ firings answers _ _)) firing answer = do+remember (Just (Memo _ firings answers _)) firing answer = do   modifyIORef' firings (Map.insert firing answer)   modifyIORef' answers (+ 1) -visited :: Maybe Memo -> Expression -> Attribute -> IO Bool-visited Nothing _ _ = pure False-visited (Just (Memo _ _ _ _ walked)) object attr = Set.member (object, attr) <$> readIORef walked--visit :: Maybe Memo -> Expression -> Attribute -> IO ()-visit Nothing _ _ = pure ()-visit (Just (Memo _ _ _ _ walked)) object attr = modifyIORef' walked (Set.insert (object, attr))- parking :: NonEmpty Rewritten -> State -> IO a -> IO a parking seq state action = action `catch` rethrow   where@@ -283,20 +253,15 @@ entering term ctx = maybe (pure ctx) (`enter` ctx) (entrance ctx._judgment term)  enter :: Expression -> ReduceContext -> IO ReduceContext-enter form ctx = admitted form ctx >>= either refuse pure-  where-    refuse :: (Acyclic, Expression) -> IO ReduceContext-    refuse (mode, before) = do-      looped ctx mode before Nothing-      throwIO (Looping form)--admitted :: Expression -> ReduceContext -> IO (Either (Acyclic, Expression) ReduceContext)-admitted form ctx = maybe (pure (Right ctx)) remembered ctx._acyclic+enter form ctx = maybe (pure ctx) remembered ctx._acyclic   where-    remembered :: Acyclic -> IO (Either (Acyclic, Expression) ReduceContext)+    remembered :: Acyclic -> IO ReduceContext     remembered mode =-      maybe (Right ctx{_entered = seenInsert (digest mode form) form ctx._entered}) (Left . (mode,))-        <$> awaited (find (repeated mode form) (Map.findWithDefault [] (digest mode form) ctx._entered))+      awaited (listToMaybe (drop ctx._maxLoops (filter (repeated mode form) (Map.findWithDefault [] (digest mode form) ctx._entered)))) >>= \case+        Just before -> do+          ctx._saveEval (EvLooped ctx._nesting ctx._judgment mode before ctx._site)+          throwIO (Looping form)+        Nothing -> pure ctx{_entered = seenInsert (digest mode form) form ctx._entered}     awaited :: Maybe Expression -> IO (Maybe Expression)     awaited found = case ctx._deadline of       Nothing -> pure found@@ -310,17 +275,13 @@     repeated Proven form before = alike form before     repeated Plausible form before = within before form -refused :: ReduceContext -> Refused -> IO (Maybe Expression, State)-refused ctx (Refused mode before reached) = (Nothing, reached) <$ looped ctx mode before Nothing--looped :: ReduceContext -> Acyclic -> Expression -> Maybe (Int, Maybe Expression) -> IO ()-looped ctx mode before answer = ctx._saveEval (EvLooped ctx._nesting ctx._judgment mode before ctx._site answer)- entrance :: Judgment -> Expression -> Maybe Expression entrance Dataization term@(ExFormation bds)   | boxed bds || isJust (lambda bds) = Just term entrance Morphing (ExDispatch form@(ExFormation bds) _)   | isJust (lambda bds) = Just form+entrance Evaluation term@(ExFormation bds)+  | isJust (lambda bds) = Just term entrance _ _ = Nothing  boxed :: [Binding] -> Bool@@ -355,341 +316,26 @@             seq' <- leadsTo seq step built ctx             pure ((built, seq'), state')           Just (In.Onward way built world, state') -> do-            (walked, state'') <- prewalked way built univ state' ctx{_steps = entry._steps}-            (morphed, state''') <- onward seq state'' way walked ctx-            go morphed world state''' ctx+            (morphed, state'') <- onward seq state' way built ctx+            go morphed world state'' ctx           Nothing -> throwIO (Unmorphable expr) -prewalked :: In.Way -> Expression -> Expression -> State -> ReduceContext -> IO (Expression, State)-prewalked (In.Normalized _) expr univ state ctx-  | ctx._deep = go expr-  where-    go :: Expression -> IO (Expression, State)-    go (ExDispatch target@(ExDispatch _ _) attr) = first (`ExDispatch` attr) <$> go target-    go (ExDispatch form@(ExFormation bds) attr)-      | attr /= AtRho && reading attr bds && all tau bds = first (`ExDispatch` attr) <$> deepened (Just attr) form univ state ctx-    go term = pure (term, state)-    tau :: Binding -> Bool-    tau (BiTau _ _) = True-    tau _ = False-    reading :: Attribute -> [Binding] -> Bool-    reading attr bds = maybe False (not . closed) (listToMaybe [body | BiTau attr' body <- bds, attr' == attr])-prewalked _ expr _ state _ = pure (expr, state)- morph :: Expression -> State -> ReduceContext -> IO (Expression, [Rewritten], State) morph universe state caller@ReduceContext{..} = do   ctx <- opening Morphing =<< universed universe caller   expr <- locatedExpression _locator universe   result <- try (morph' (expr, (universe, Nothing) :| []) universe state ctx)   case result of-    Right ((morphed, seq), state') -> walked (walking ctx) morphed seq state'-    Left (StuckAt func seq parked) | _partial -> do-      residue <- locatedExpression _locator (fst (NE.head seq))-      walked (marked ctx func) residue seq parked{_stuck = Just func}-    Left (OutOfStepsAt _ seq parked) | _partial -> do-      residue <- locatedExpression _locator (fst (NE.head seq))-      walked (walking ctx) residue seq parked-    Left (LoopingAt _ seq parked) -> do-      residue <- locatedExpression _locator (fst (NE.head seq))-      walked (walking ctx) residue seq parked+    Right ((morphed, seq), state') -> pure (morphed, reverse (NE.toList seq), state')+    Left (StuckAt func seq parked) | _partial -> residue seq parked{_stuck = Just func}+    Left (OutOfStepsAt _ seq parked) | _partial -> residue seq parked+    Left (LoopingAt _ seq parked) -> residue seq parked     Left failure -> throwIO (failure :: ReduceException)   where-    walking :: ReduceContext -> ReduceContext-    walking ctx = ctx{_judgment = Morphing}-    marked :: ReduceContext -> T.Text -> ReduceContext-    marked ctx func = (walking ctx){_parked = func : _parked}-    walked :: ReduceContext -> Expression -> NonEmpty Rewritten -> State -> IO (Expression, [Rewritten], State)-    walked walker morphed seq state'-      | not _deep = pure (morphed, reverse (NE.toList seq), state')-      | otherwise = do-          (deep, state'') <- deepened Nothing morphed universe state' walker-          seq' <- leadsTo seq (Morphing, "deep") deep walker-          pure (deep, reverse (NE.toList seq'), state'')--deepened :: Maybe Attribute -> Expression -> Expression -> State -> ReduceContext -> IO (Expression, State)-deepened focus expr univ state ctx = do-  world <- newIORef (fromMaybe univ ctx._universe)-  case focus of-    Just attr -> do-      (store, path) <- home Nothing world expr-      body <- held (ExDispatch path attr) store-      (entered, state') <- sibling Nothing (Frame world store path (Just attr)) body state ctx-      when (entered /= body) (stored store (ExDispatch path attr) entered)-      (,state') <$> held path store-    Nothing -> step (if ctx._jobs > 1 then spread else parts) (Just ctx._site) Nothing (Frame world world ctx._site Nothing) expr state ctx-  where-    go :: Maybe Expression -> Maybe Attribute -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)-    go = step parts-    step :: (Maybe Expression -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)) -> Maybe Expression -> Maybe Attribute -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)-    step walk standing dispatched frame@(Frame world _ _ _) term state' caller = do-      here <- maybe (pure caller) (\loc -> opening Morphing caller{_site = loc}) standing-      ctx' <- deeper here-      copy <- deferrable dispatched world term state' here-      case copy of-        Just form -> deferred form state' here-        Nothing -> do-          outcome <- try (walk standing frame term state' here)-          case outcome of-            Left (Severed answer reached) -> pure (answer, reached)-            Right (walked, walkedState) -> do-              when (walked /= term) (here._saveEval (EvComputed here._nesting term walked))-              placed <- ctx._engine._contextualize walked =<< context frame-              current <- readIORef world-              (answer, answered) <- ctx'._fire dispatched placed current walkedState ctx'{_universe = Just current} `catch` cut standing frame ctx'-              mapM_ (noted here._site frame walked) answer-              pure (fromMaybe walked answer, answered)-    cut :: Maybe Expression -> Frame -> ReduceContext -> Refused -> IO (Maybe Expression, State)-    cut (Just _) (Frame _ store path (Just AtPhi)) caller refusal@(Refused mode before reached) = do-      form <- held path store-      if copied form-        then do-          fresh <- coined caller-          looped caller mode before (Just (fresh, called form))-          throwIO (Severed (ExFormation [BiLambda (FnSymbol fresh)]) reached)-        else refused caller refusal-    cut _ _ caller refusal = refused caller refusal-    copied :: Expression -> Bool-    copied (ExFormation bds) = boxed bds && not (any abstract bds) && any code bds-    copied _ = False-    deferrable :: Maybe Attribute -> IORef Expression -> Expression -> State -> ReduceContext -> IO (Maybe Expression)-    deferrable dispatched world form@(ExFormation bds) state' caller-      | copied form && maybe True (\attr -> not (any (named attr) bds)) dispatched = do-          current <- readIORef world-          known <- mapM (resolved current form state' caller) bds-          pure (if any bare known then Just (ExFormation known) else Nothing)-    deferrable _ _ _ _ _ = pure Nothing-    code :: Binding -> Bool-    code (BiTau AtPhi (ExFormation _)) = False-    code (BiTau AtPhi _) = True-    code _ = False-    bare :: Binding -> Bool-    bare (BiTau attr (ExFormation [BiLambda (FnSymbol _)])) = attr /= AtPhi && attr /= AtRho-    bare _ = False-    resolved :: Expression -> Expression -> State -> ReduceContext -> Binding -> IO Binding-    resolved current form state' caller bd@(BiTau attr body@(ExDispatch _ _))-      | attr /= AtPhi && attr /= AtRho = do-          placed <- ctx._engine._contextualize body (scope attr form)-          outcome <- try (settled placed current state' (reading current caller))-          case outcome of-            Right (made@(ExFormation [BiLambda (FnSymbol _)]), _) -> pure (BiTau attr made)-            Left (OutOfTime cap) -> expired caller cap-            _ -> pure bd-    resolved _ _ _ _ bd = pure bd-    reading :: Expression -> ReduceContext -> ReduceContext-    reading current caller =-      caller-        { _universe = Just current-        , _symbolic = emptyLambdas-        , _memo = Nothing-        , _tally = Nothing-        , _acyclic = Nothing-        , _deep = False-        , _saveStep = dontSaveStep-        , _saveEval = dontSaveEval-        }-    deferred :: Expression -> State -> ReduceContext -> IO (Expression, State)-    deferred copy state' caller = do-      fresh <- coined caller-      caller._saveEval (EvDeferred caller._nesting fresh caller._judgment copy (called copy) caller._site)-      pure (ExFormation [BiLambda (FnSymbol fresh)], state')-    coined :: ReduceContext -> IO Int-    coined caller = atomicModifyIORef' caller._minted (\count -> (count + 1, count + 1))-    called :: Expression -> Maybe Expression-    called copy@(ExFormation bds) = do-      (path, declared) <- origin copy-      pure (foldl ExApplication path [ArTau attr value | BiTau attr value <- bds, attr /= AtRho, BiVoid attr `elem` declared])-    called _ = Nothing-    origin :: Expression -> Maybe (Expression, [Binding])-    origin (ExFormation bds) = do-      parent <- case filter ((== Just AtRho) . attributeFromBinding) bds of-        [] -> Just ExRoot-        [BiTau AtRho form@(ExFormation _)] -> fst <$> origin form-        [BiTau AtRho path] -> Just (erased path)-        _ -> Nothing-      ExFormation siblings <- located parent (fromMaybe univ ctx._universe)-      chosen bds [(ExDispatch parent attr, declared) | BiTau attr (ExFormation declared) <- siblings, attr /= AtRho, fits declared]-      where-        fits :: [Binding] -> Bool-        fits declared = all (`elem` map attributeFromBinding declared) [attributeFromBinding bd | bd <- bds, attributeFromBinding bd /= Just AtRho]-    origin _ = Nothing-    chosen :: [Binding] -> [(Expression, [Binding])] -> Maybe (Expression, [Binding])-    chosen _ [] = Nothing-    chosen bds candidates = case [candidate | candidate <- candidates, agreed candidate == maximum (map agreed candidates)] of-      [one] -> Just one-      _ -> Nothing-      where-        agreed :: (Expression, [Binding]) -> (Int, Int)-        agreed (_, declared) = (length [attr | BiTau attr _ <- bds, BiVoid attr `elem` declared], length (filter (`elem` declared) bds))-    parts :: Maybe Expression -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)-    parts _ _ term@(ExFormation bds) state' _-      | any abstract bds = pure (term, state')-    parts standing (Frame world _ _ _) form@(ExFormation bds) state' caller = do-      (store, path) <- home standing world form-      state'' <- bindings standing (synonym caller._universe form) (Frame world store path Nothing) [attr | BiTau attr _ <- bds, attr /= AtRho] state' caller-      entered <- held path store-      pure (entered, state'')-    parts _ frame (ExDispatch target attr) state' caller = do-      (entered, state'') <- go Nothing (Just attr) frame target state' caller-      pure (ExDispatch entered attr, state'')-    parts _ frame (ExApplication target arg) state' caller = do-      (entered, state'') <- go Nothing Nothing frame target state' caller-      (applied, state''') <- argument (go Nothing Nothing) frame arg state'' caller-      pure (ExApplication entered applied, state''')-    parts _ _ term state' _ = pure (term, state')-    sibling :: Maybe Attribute -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)-    sibling dispatched frame term@(ExDispatch ExXi attr) state' caller-      | attr /= AtRho = go Nothing dispatched frame term state' caller-    sibling _ frame (ExDispatch target attr) state' caller = first (`ExDispatch` attr) <$> sibling (Just attr) frame target state' caller-    sibling _ frame (ExApplication target arg) state' caller = do-      (entered, state'') <- sibling Nothing frame target state' caller-      first (ExApplication entered) <$> argument (sibling Nothing) frame arg state'' caller-    sibling _ _ term state' _ = pure (term, state')-    abstract :: Binding -> Bool-    abstract (BiVoid _) = True-    abstract _ = False-    spread :: Maybe Expression -> Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)-    spread standing (Frame world _ _ _) form@(ExFormation bds) state' caller-      | not (any abstract bds) = do-          floor' <- readIORef caller._minted-          jobs <- mapM (planned floor' (synonym caller._universe form)) (zip [1 ..] bds)-          (entered, state'') <- pooled caller._jobs jobs (gathered floor') ([], state')-          pure (ExFormation (reverse entered), state'')-      where-        planned :: Int -> Maybe (Expression, [Attribute]) -> (Int, Binding) -> IO (IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State))))-        planned floor' alias (idx, BiTau attr body)-          | attr /= AtRho = do-              new <- if closed body then fresh alias attr caller else pure True-              pure (if new then worker floor' idx attr body else kept (BiTau attr body))-        planned _ _ (_, bd) = pure (kept bd)-        kept :: Binding -> IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State)))-        kept bd = pure ([], [], 0, Right (const (pure (bd, Nothing))))-        worker :: Int -> Int -> Attribute -> Expression -> IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State)))-        worker floor' idx attr body = do-          buffer <- newIORef []-          steps <- newIORef []-          tau <- tausOf idx-          tally <- tallied (fmap (\(Tally cap _) -> cap) caller._tally)-          minted <- newIORef floor'-          memo <- memoized caller._acyclic-          copy <- newIORef =<< readIORef world-          (store, path) <- home standing copy form-          let own = caller{_jobs = 1, _tally = tally, _minted = minted, _memo = memo, _saveEval = modifyIORef' buffer . (:), _saveStep = modifyIORef' steps . (:), _buildTerm = minting tau caller._buildTerm}-          outcome <- try (try (go (fmap (`ExDispatch` attr) standing) Nothing (Frame copy store path (Just attr)) body state' own))-          records <- reverse <$> readIORef buffer-          taken' <- reverse <$> readIORef steps-          spent <- subtract floor' <$> readIORef minted-          pure (records, taken', spent, fmap (either (severed floor') (\(term, walked) offset -> pure (BiTau attr (lifted floor' offset term), Just (moved floor' offset walked)))) outcome)-        severed :: Int -> Severed -> Int -> IO (Binding, Maybe State)-        severed floor' (Severed answer reached) offset = throwIO (Severed (lifted floor' offset answer) (moved floor' offset reached))-        moved :: Int -> Int -> State -> State-        moved floor' offset walked = walked{_manufactured = fmap (\sym -> if sym > floor' then sym + offset else sym) walked._manufactured}-        gathered :: Int -> ([Binding], State) -> ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State))) -> IO ([Binding], State)-        gathered floor' (done, current) (records, taken', spent, outcome) = do-          offset <- subtract floor' <$> readIORef caller._minted-          mapM_ (caller._saveEval . renumbered floor' offset) records-          mapM_ caller._saveStep taken'-          modifyIORef' caller._minted (+ spent)-          (bd, walked) <- either throwIO ($ offset) outcome-          pure (bd : done, fromMaybe current walked)-    spread standing frame term state' caller = parts standing frame term state' caller-    minting :: IO T.Text -> BuildTermFunc -> BuildTermFunc-    minting tau build func-      | func == "random-tau" = \args subst -> if null args then TeAttribute . AtLabel <$> tau else build func args subst-      | otherwise = build func-    bindings :: Maybe Expression -> Maybe (Expression, [Attribute]) -> Frame -> [Attribute] -> State -> ReduceContext -> IO State-    bindings _ _ _ [] state' _ = pure state'-    bindings standing alias frame@(Frame world store path _) (attr : rest) state' caller = do-      body <- held (ExDispatch path attr) store-      new <- if closed body then fresh alias attr caller else pure True-      state'' <--        if new-          then do-            (entered, walked) <- go (fmap (`ExDispatch` attr) standing) Nothing (Frame world store path (Just attr)) body state' caller-            walked <$ when (entered /= body) (stored store (ExDispatch path attr) entered)-          else pure state'-      bindings standing alias frame rest state'' caller-    home :: Maybe Expression -> IORef Expression -> Expression -> IO (IORef Expression, Expression)-    home (Just path) world form = do-      placed <- put path form <$> readIORef world-      case placed of-        Just whole -> (world, path) <$ writeIORef world whole-        Nothing -> home Nothing world form-    home Nothing _ form = (,ExRoot) <$> newIORef form-    context :: Frame -> IO Expression-    context (Frame _ _ _ Nothing) = pure ExXi-    context (Frame _ store path (Just attr)) = scope attr <$> held path store-    noted :: Expression -> Frame -> Expression -> Expression -> IO ()-    noted site frame@(Frame world _ _ _) walked answer = case address frame walked of-      Just (store, path@(ExDispatch _ _))-        | store /= world || not (above path site) -> stored store path answer-      _ -> pure ()-    address :: Frame -> Expression -> Maybe (IORef Expression, Expression)-    address (Frame world _ _ _) ExRoot = Just (world, ExRoot)-    address (Frame _ store path (Just _)) ExXi = Just (store, path)-    address frame (ExDispatch target attr) = fmap (`ExDispatch` attr) <$> address frame target-    address _ _ = Nothing-    above :: Expression -> Expression -> Bool-    above path (ExDispatch target _) = path == target || above path target-    above _ _ = False-    held :: Expression -> IORef Expression -> IO Expression-    held path store = readIORef store >>= maybe (throwIO (userError (printf "The deep walk lost the object at %s" (printExpression path)))) pure . located path-    stored :: IORef Expression -> Expression -> Expression -> IO ()-    stored store path value = modifyIORef' store (\whole -> fromMaybe whole (put path value whole))-    put :: Expression -> Expression -> Expression -> Maybe Expression-    put ExRoot value _ = Just value-    put (ExDispatch path attr) value whole = case located path whole of-      Just (ExFormation bds)-        | attr /= AtRho && not (any abstract bds) && any (named attr) bds ->-            put path (ExFormation (map (\bd -> if named attr bd then BiTau attr value else bd) bds)) whole-      _ -> Nothing-    put _ _ _ = Nothing-    located :: Expression -> Expression -> Maybe Expression-    located ExRoot whole = Just whole-    located (ExDispatch path attr) whole = case located path whole of-      Just (ExFormation bds) -> listToMaybe [body | BiTau attr' body <- bds, attr' == attr]-      _ -> Nothing-    located _ _ = Nothing-    synonym :: Maybe Expression -> Expression -> Maybe (Expression, [Attribute])-    synonym Nothing _ = Nothing-    synonym (Just world) form = case pathOf world form of-      ExRoot -> Nothing-      ExFormation _ -> Nothing-      name -> Just (erased name, supplied name)-      where-        supplied :: Expression -> [Attribute]-        supplied (ExApplication target (ArTau attr _)) = attr : supplied target-        supplied _ = []-    erased :: Expression -> Expression-    erased (ExApplication target _) = erased target-    erased (ExDispatch target attr) = ExDispatch (erased target) attr-    erased other = other-    fresh :: Maybe (Expression, [Attribute]) -> Attribute -> ReduceContext -> IO Bool-    fresh (Just (object, filled)) attr caller-      | attr `notElem` filled = do-          seen <- visited caller._memo object attr-          unless seen (visit caller._memo object attr)-          pure (not seen)-    fresh _ _ _ = pure True-    scope :: Attribute -> Expression -> Expression-    scope attr (ExFormation bds) = ExFormation (filter (not . named attr) bds)-    scope _ other = other-    named :: Attribute -> Binding -> Bool-    named attr (BiTau attr' _) = attr' == attr-    named _ _ = False-    argument :: (Frame -> Expression -> State -> ReduceContext -> IO (Expression, State)) -> Frame -> Argument -> State -> ReduceContext -> IO (Argument, State)-    argument walk frame (ArTau attr arg) state' caller = do-      (entered, state'') <- walk frame arg state' caller-      pure (ArTau attr entered, state'')-    argument walk frame (ArAlpha alpha arg) state' caller = do-      (entered, state'') <- walk frame arg state' caller-      pure (ArAlpha alpha entered, state'')--closed :: Expression -> Bool-closed ExXi = False-closed (ExDispatch target _) = closed target-closed (ExApplication target (ArTau _ arg)) = closed target && closed arg-closed (ExApplication target (ArAlpha _ arg)) = closed target && closed arg-closed _ = True+    residue :: NonEmpty Rewritten -> State -> IO (Expression, [Rewritten], State)+    residue seq parked = do+      expr <- locatedExpression _locator (fst (NE.head seq))+      pure (expr, reverse (NE.toList seq), parked)  inferred :: Expression -> Expression -> State -> ReduceContext -> [In.Inference value] -> IO (Maybe (In.Conclusion value, State)) inferred expr univ state ctx rules = do
− src/Pool.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}---- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com--- SPDX-License-Identifier: MIT--module Pool (pooled) where--import Control.Concurrent (QSem, ThreadId, forkIO, killThread, newQSem, signalQSem, waitQSem)-import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)-import Control.Exception (SomeException, bracket_, finally, mask, throwIO, try)-import Control.Monad (foldM)--pooled :: forall a b. Int -> [IO a] -> (b -> a -> IO b) -> b -> IO b-pooled width actions fold start = do-  gate <- newQSem (max 1 width)-  launched <- mapM (launch gate) actions-  foldM collected start (map snd launched) `finally` mapM_ (killThread . fst) launched-  where-    launch :: QSem -> IO a -> IO (ThreadId, MVar (Either SomeException a))-    launch gate action = do-      box <- newEmptyMVar-      thread <- mask $ \restore -> forkIO (try (restore (bracket_ (waitQSem gate) (signalQSem gate) action)) >>= putMVar box)-      pure (thread, box)-    collected :: b -> MVar (Either SomeException a) -> IO b-    collected acc box = takeMVar box >>= either throwIO (fold acc)
src/Tau.hs view
@@ -3,10 +3,10 @@ -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com -- SPDX-License-Identifier: MIT -module Tau (seedTaus, freshTau, tausOf) where+module Tau (seedTaus, freshTau) where  import AST-import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef)+import Data.IORef (IORef, atomicModifyIORef', newIORef, writeIORef) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text)@@ -27,27 +27,13 @@       let (minted, idx) = mint taken cursor        in ((Set.insert minted taken, idx + 1), minted) -tausOf :: Int -> IO (IO Text)-tausOf entry = do-  (taken, _) <- readIORef taus-  own <- newIORef (taken, 0)-  pure (atomicModifyIORef' own advance)-  where-    advance :: (Set Text, Int) -> ((Set Text, Int), Text)-    advance (taken, cursor) =-      let (minted, idx) = mint' (T.pack ("a🌵" <> show entry <> "-")) taken cursor-       in ((Set.insert minted taken, idx + 1), minted)- mint :: Set Text -> Int -> (Text, Int)-mint = mint' "a🌵"--mint' :: Text -> Set Text -> Int -> (Text, Int)-mint' stem taken idx-  | name `Set.member` taken = mint' stem taken (idx + 1)+mint taken idx+  | name `Set.member` taken = mint taken (idx + 1)   | otherwise = (name, idx)   where     name :: Text-    name = stem <> T.pack (show idx)+    name = "a🌵" <> T.pack (show idx)  exprLabels :: Expression -> Set Text exprLabels (ExFormation bds) = Set.unions (map bindingLabels bds)
test-resources/evaluate-packs/answers-a-symbol-nobody-has-worked-out.yaml view
@@ -2,9 +2,10 @@ # SPDX-License-Identifier: MIT --- # yamllint disable rule:line-length-# One firing, and nothing is computed: the operands come down to the numbers-# they are, and what the entry answers with is a number standing for a sum-# nobody has worked out.+# One firing, and nothing is computed: the dispatch of φ off the call fires+# it, the operands come down to the numbers they are, and the entry answers+# with a number standing for a sum nobody has worked out, whose φ is the+# symbol 𝜎1. symbolic: |   - λ: L_number_plus     dataize:@@ -12,19 +13,15 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.plus( 6 ) ⟧-result: '⟦ φ ↦ 𝜎1:λ, plus(x) ↦ L_number_plus:λ ⟧'+    @ ↦ 5.plus( 6 ).φ ⟧+result: '𝜎1:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5     𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )     𝔼(L_number_plus):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):@@ -34,7 +31,7 @@       𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵1):         𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )+          𝑛·1·3 := 6           𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
test-resources/evaluate-packs/answers-a-whole-family-of-names-with-one-entry.yaml view
@@ -11,15 +11,13 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,       times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧ ⟧,-    @ ↦ 5.plus( 6 ).times( 7 ) ⟧-result: |--  ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ, times(x) ↦ L_number_times:λ ⟧+    @ ↦ 5.plus( 6 ).times( 7 ).φ ⟧+result: '𝜎2:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -42,9 +40,6 @@         𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )       𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)     𝑛·0·3 := 𝑛·1·6.times( x ↦ 7 )-    𝕄(Φ.φ.x):-      𝑛·0·4 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )     𝔼(L_number_times):       𝔻(Φ.a🌵2):         𝕄(Φ.a🌵2):@@ -52,7 +47,7 @@       𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵3):         𝕄(Φ.a🌵3):-          𝑛·2·2 := Φ.number( φ ↦ 𝑛·0·4 )+          𝑛·2·2 := 7           𝑛·2·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )         𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0       𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)
test-resources/evaluate-packs/answers-the-same-under-acyclic.yaml view
@@ -13,20 +13,16 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true acyclic: proven input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.plus( 6 ) ⟧-result: '⟦ φ ↦ 𝜎1:λ, plus(x) ↦ L_number_plus:λ ⟧'+    @ ↦ 5.plus( 6 ).φ ⟧+result: '𝜎1:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5     𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )     𝔼(L_number_plus):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):@@ -36,7 +32,7 @@       𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵1):         𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )+          𝑛·1·3 := 6           𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
test-resources/evaluate-packs/blames-the-lambda-the-operand-was-parked-on.yaml view
@@ -4,16 +4,13 @@ # A firing whose 'dataize' operand was parked gets stuck on the λ function # that parked it and never on its own: an entry answers 'L_entry', so blaming # it would name a λ function the file carries where the one nothing answers,-# 'L_none', stands one reduction deeper. The spine parks that name, so the-# deep walk over the residue writes no second 'unanswered(L_none)' for the one-# site (#1288, #1300).+# 'L_none', stands one reduction deeper (#1288). symbolic: |   - λ: L_entry     dataize:       𝛿1: $.v     𝑛: ⟦ ⟧ location: 'Q.x'-deep: true partial: true input: '⟦ x ↦ ⟦ v ↦ ⟦ λ ⤍ L_none ⟧.foo, λ ⤍ L_entry, ρ ↦ ∅ ⟧.z ⟧' protocol: |@@ -22,6 +19,4 @@       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):           unanswered(L_none)  # 𝕄(L_none:λ)-      stuck(L_none)-    𝔼(L_entry):       stuck(L_none)
+ test-resources/evaluate-packs/cuts-a-branch-whose-phi-leads-back-to-it.yaml view
@@ -0,0 +1,36 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The step a lenient join takes into a branch of #1885, under+# '--acyclic=proven', over a branch whose φ fires the same fork again: 'z'+# decorates a call of 'L_fork' whose branch 'a' is 'z' itself. The join morphs+# the φ of 'z' and fires the fork it comes to, which meets 'z' again, morphs+# its φ again and comes to the same fork. The step fires the way an 'evaluate'+# line does, entering the formation it fires (#1882), so the second firing is+# cut and writes a 'looped' line where it would have opened, and the run parks+# the first fork.+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      𝑛3: [𝑛1, 𝑛2]+    lenient: true+    𝑛: 𝑛3+location: 'Q.y'+acyclic: proven+input: '⟦ z ↦ ⟦ φ ↦ ξ.f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ⟧ ⟧, y ↦ ⟦ a ↦ Φ.z, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ a ↦ Φ.z, b ↦ 𝜎2:λ:φ, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := ⟦ φ ↦ f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧ ⟧  # 𝕄(ξ.a)+      𝑛2·1 := 𝜎2:λ:φ  # 𝕄(ξ.b)+      𝑛1·1 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)+      𝔼(L_fork):  # 𝔼(𝑛1)+        𝑛1·2 := ⟦ φ ↦ f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧ ⟧  # 𝕄(ξ.a)+        𝑛2·2 := 𝜎1:λ:φ  # 𝕄(ξ.b)+        𝑛1·2 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)+        looped(⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧)  # 𝔼(𝑛1), proven
− test-resources/evaluate-packs/cuts-a-loop-over-unknown-data-on-its-second-round.yaml
@@ -1,184 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A loop walking an index from zero up to a size that is a symbol, under-# '--acyclic=plausible'. The first round holds the number '0', the second holds-# the symbol 'plus' answered in its place, and a symbol stands for a term that-# holds no symbol of its own, so the fork of the second round is cut against-# the fork of the first one rather than a round later (#1491). The cut fork is-# the φ of a copy of 'rec', so the cut answers that copy with a fresh symbol,-# which the 'join' of the first fork pairs with the other branch (#1735).-symbolic: |-  - λ: L_id-    morph:-      𝑛1: $.x.φ-    𝑛: 𝑛1-  - λ: L_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_eq-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ) ⟧-  - λ: L_fork-    dataize:-      𝛿1: $.φ-    morph:-      𝑛1: $.left-      𝑛2: $.right-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.a'-deep: true-acyclic: plausible-partial: true-steps: 32-input: >-  ⟦ num ↦ ⟦ φ ↦ ∅, plus ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_plus ⟧, eq ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_eq ⟧ ⟧,-    bool ↦ ⟦ φ ↦ ∅, if ↦ ∅ ⟧,-    count ↦ ⟦ size ↦ ∅, φ ↦ ξ.rec( index ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 00- ⟧ ), accum ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 00- ⟧ ) ), rec ↦ ⟦ ρ ↦ ∅, index ↦ ∅, accum ↦ ∅, φ ↦ ξ.index.eq( ξ.ρ.size ).if( ξ.accum, ξ.ρ.rec( index ↦ ξ.index.plus( Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ) ), accum ↦ ξ.accum.plus( Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ) ) ) ) ⟧ ⟧,-    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.count( size ↦ Φ.num( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ⟧ ⟧-protocol: |-  𝕄(Φ.a):-    𝕄(Φ.a.x):-      𝑛·0·1 := Φ.num( φ ↦ 𝜎1:λ )-      𝑛·0·2 := Φ.count( size ↦ 𝑛·0·1 )-    𝔼(L_id):-      𝕄(Φ.a🌵0):-        𝑛·1·1 := Φ.count( size ↦ 𝑛·0·1 )-        𝑛·1·2 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( Φ.num( φ ↦ 01-:Δ ) ), accum ↦ accum.plus( Φ.num( φ ↦ 01-:Δ ) ) ) ) ⟧( index ↦ Φ.num( φ ↦ 00-:Δ ) )-        𝑛·1·3 := 𝑛·1·2( accum ↦ Φ.num( φ ↦ 00-:Δ ) )-        𝕄(Φ.a🌵0.index):-          𝑛·1·4 := Φ.num( φ ↦ 00-:Δ )-        𝕄(Φ.a🌵0.accum):-          𝑛·1·5 := Φ.num( φ ↦ 00-:Δ )-        𝕄(Φ.a🌵0.φ):-          𝑛·1·6 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·7 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·8 := Φ.num( φ ↦ 𝜎1:λ )-          𝑛·1·9 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·10 := 𝑛·1·9.eq( b ↦ 𝑛·1·8 )-          𝔼(L_eq):-            𝔻(Φ.a🌵1):-              𝕄(Φ.a🌵1):-                𝑛·2·1 := Φ.num( φ ↦ 00-:Δ )-              𝛿·2·1 := 00--            𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-            𝔻(Φ.a🌵2):-              𝕄(Φ.a🌵2):-                𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )-            𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.b)-            𝑛·2·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝑛-            𝑛·2·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(𝑛·2·3)-          𝑛·1·11 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-          𝑛·1·12 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·13 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-          𝑛·1·14 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ 𝑛·1·12 )-          𝑛·1·15 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·16 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·17 := Φ.num( φ ↦ 01-:Δ )-          𝑛·1·18 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·19 := 𝑛·1·18.plus( b ↦ 𝑛·1·17 )-          𝔼(L_plus):-            𝔻(Φ.a🌵3):-              𝕄(Φ.a🌵3):-                𝑛·3·1 := Φ.num( φ ↦ 00-:Δ )-              𝛿·3·1 := 00--            𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-            𝔻(Φ.a🌵4):-              𝕄(Φ.a🌵4):-                𝑛·3·2 := Φ.num( φ ↦ 01-:Δ )-              𝛿·3·2 := 01--            𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.b)-            𝑛·3·3 := Φ.num( φ ↦ 𝜎3:λ )  # 𝑛-            𝕄(𝑛·3·3):-              𝑛·3·4 := Φ.num( φ ↦ 𝜎3:λ )-            𝑛·3·5 := 𝑛·3·4  # 𝕄(𝑛·3·3)-          𝑛·1·20 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·3·2 ), accum ↦ accum.plus( 𝑛·3·2 ) ) ) ⟧( index ↦ 𝑛·3·4 )-          𝑛·1·21 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·22 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·23 := Φ.num( φ ↦ 01-:Δ )-          𝑛·1·24 := Φ.num( φ ↦ 00-:Δ )-          𝑛·1·25 := 𝑛·1·24.plus( b ↦ 𝑛·1·23 )-          𝔼(L_plus):-            𝑛·4·1 := 𝑛·3·4  # 𝑛-            𝑛·4·2 := 𝑛·3·5  # 𝕄(𝑛·4·1)-          𝑛·1·26 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·1·23 ), accum ↦ accum.plus( 𝑛·1·23 ) ) ) ⟧( index ↦ 𝑛·3·4 )-          𝑛·1·27 := 𝑛·1·26( accum ↦ 𝑛·3·4 )-          𝑛·1·28 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·1·23 ), accum ↦ accum.plus( 𝑛·1·23 ) ) ) ⟧( index ↦ 𝑛·3·4 )-          𝑛·1·29 := 𝑛·1·28( accum ↦ 𝑛·3·4 )-          𝑛·1·30 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-          𝑛·1·31 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ 𝑛·1·24 )-          𝑛·1·32 := 𝑛·1·31( right ↦ 𝑛·1·29 )-          𝔼(L_fork):-            𝛿1·5 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)-            𝕄(Φ.a🌵6):-              𝑛·5·1 := Φ.num( φ ↦ 00-:Δ )-            𝑛1·5 := 𝑛·5·1  # 𝕄(ξ.left)-            𝕄(Φ.a🌵7):-              𝕄(Φ.a🌵7.φ):-                𝑛·5·2 := Φ.num( φ ↦ 𝜎1:λ )-                𝑛·5·3 := 𝑛·3·4.eq( b ↦ 𝑛·5·2 )-                𝔼(L_eq):-                  𝔻(Φ.a🌵8):-                    𝕄(Φ.a🌵8):-                      𝑛·6·1 := Φ.num( φ ↦ 𝜎3:λ )-                  𝛿1·6 := 𝔻(𝜎3:λ)  # 𝔻(ξ.ρ)-                  𝔻(Φ.a🌵9):-                    𝕄(Φ.a🌵9):-                      𝑛·6·2 := Φ.num( φ ↦ 𝜎1:λ )-                  𝛿2·6 := 𝔻(𝜎1:λ)  # 𝔻(ξ.b)-                  𝑛·6·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝑛-                  𝑛·6·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝕄(𝑛·6·3)-                𝑛·5·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )-                𝑛·5·5 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )-                𝑛·5·6 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎4:λ ⟧( left ↦ 𝑛·6·1 )-                𝑛·5·7 := Φ.num( φ ↦ 01-:Δ )-                𝑛·5·8 := 𝑛·6·1.plus( b ↦ 𝑛·5·7 )-                𝔼(L_plus):-                  𝔻(Φ.a🌵10):-                    𝕄(Φ.a🌵10):-                      𝑛·7·1 := Φ.num( φ ↦ 𝜎3:λ )-                  𝛿1·7 := 𝔻(𝜎3:λ)  # 𝔻(ξ.ρ)-                  𝔻(Φ.a🌵11):-                    𝕄(Φ.a🌵11):-                      𝑛·7·2 := Φ.num( φ ↦ 01-:Δ )-                    𝛿·7·1 := 01--                  𝛿2·7 := 𝛿·7·1  # 𝔻(ξ.b)-                  𝑛·7·3 := Φ.num( φ ↦ 𝜎5:λ )  # 𝑛-                  𝕄(𝑛·7·3):-                    𝑛·7·4 := Φ.num( φ ↦ 𝜎5:λ )-                  𝑛·7·5 := 𝑛·7·4  # 𝕄(𝑛·7·3)-                𝑛·5·9 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·7·2 ), accum ↦ accum.plus( 𝑛·7·2 ) ) ) ⟧( index ↦ 𝑛·7·4 )-                𝑛·5·10 := Φ.num( φ ↦ 01-:Δ )-                𝑛·5·11 := 𝑛·7·1.plus( b ↦ 𝑛·5·10 )-                𝔼(L_plus):-                  𝑛·8·1 := 𝑛·7·4  # 𝑛-                  𝑛·8·2 := 𝑛·7·5  # 𝕄(𝑛·8·1)-                𝑛·5·12 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·5·10 ), accum ↦ accum.plus( 𝑛·5·10 ) ) ) ⟧( index ↦ 𝑛·7·4 )-                𝑛·5·13 := 𝑛·5·12( accum ↦ 𝑛·7·4 )-                𝑛·5·14 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·5·10 ), accum ↦ accum.plus( 𝑛·5·10 ) ) ) ⟧( index ↦ 𝑛·7·4 )-                𝑛·5·15 := 𝑛·5·14( accum ↦ 𝑛·7·4 )-                𝑛·5·16 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )-                𝑛·5·17 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎4:λ ⟧( left ↦ 𝑛·7·1 )-                𝑛·5·18 := 𝑛·5·17( right ↦ 𝑛·5·15 )-                looped(𝑛·1·32) := 𝜎6  # plausible-            𝑛2·5 := 𝜎6:λ  # 𝕄(ξ.right)-            𝔻(𝜎7:λ) == 00--            𝑛3·5 := ⟦ φ ↦ 𝜎7:λ, plus(b) ↦ L_plus:λ, eq(b) ↦ L_eq:λ ⟧  # 𝑛1-            𝑛4·5 := 𝑛2·5  # 𝑛2-            𝔻(𝜎8:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎6:λ) }-            𝑛5·5 := 𝜎8:λ  # [𝑛3, 𝑛4]-            𝑛·5·19 := 𝑛5·5  # 𝑛-            𝑛·5·20 := 𝑛5·5  # 𝕄(𝑛·5·19)-      𝑛1·1 := 𝑛·1·3  # 𝕄(ξ.x.φ)-      𝑛·1·33 := 𝑛1·1  # 𝑛-      𝑛·1·34 := 𝑛1·1  # 𝕄(𝑛·1·33)
+ test-resources/evaluate-packs/cuts-a-recursion-running-through-evaluate-lines.yaml view
@@ -0,0 +1,28 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A recursion that runs through 'evaluate' lines alone, under+# '--acyclic=proven'. 'L_loop' morphs 'next' to the very formation it was+# fired from, and its 'evaluate' line fires that formation again. The line+# enters the formation the way 'ml' does, so the second firing is cut before+# it opens and writes a 'looped' line where its block would have stood+# (#1882). The budget of firings is there only to end the run if the cut+# is ever lost again.+symbolic: |+  - λ: L_loop+    morph:+      𝑛1: $.next+    evaluate:+      𝑛2: 𝑛1+    𝑛: 𝑛2+location: 'Q.app'+acyclic: proven+firings: 5+input: '⟦ loop ↦ ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧, app ↦ Φ.loop.φ ⟧'+result: '⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧.φ'+protocol: |+  𝕄(Φ.app):+    𝔼(L_loop):+      𝑛1·1 := ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧  # 𝕄(ξ.next)+      looped(⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧)  # 𝔼(𝑛1), proven
test-resources/evaluate-packs/cuts-a-recursion-that-differs-by-symbols-only.yaml view
@@ -2,170 +2,32 @@ # SPDX-License-Identifier: MIT --- # yamllint disable rule:line-length-# A factorial over a symbolic argument, under '--acyclic=proven'. Every round-# of the recursion mints fresh symbols, so the deep walk fires 'mul' with-# a ↦ 𝜎1 and b ↦ Φ.fact( n ↦ 𝜎3 ) on the first round and with a ↦ 𝜎3 and-# b ↦ Φ.fact( n ↦ 𝜎5 ) on the second, and no two rounds are ever the same term.-# They are the same formation up to a renaming of symbols, which is what the-# guard compares by (#1420), and the walk enters the formation it fires (#1451),-# so the second firing is cut before it opens and writes a 'looped' line where-# its block would have stood, carrying the term of the round that was kept-# (#1434). The budget is generous here, so what ends the run is the cut and not-# the limit. The argument 'n' and every symbol 'dec' answers stand in a-# carrier '⟦ φ ↦ 𝜎 ⟧', since the walk defers a copy one of whose arguments is-# a bare symbol instead of entering it (#1729). The first 'L_if' gets stuck,-# yet the symbols it minted stay taken, so the second one mints from 𝜎7 (#1738).+# A recursion over a symbolic argument, under '--acyclic=proven'. 'L_down'+# answers a call of 'down' over a fresh symbol, and 'ml' dispatches 'φ' off+# that answer, which fires 'down' again: the first round fires it with x ↦ 𝜎1,+# the second with x ↦ 𝜎2, and no two rounds are ever the same term. They are+# the same formation up to a renaming of symbols, which is what the guard+# compares by (#1420), and 'ml' enters the formation it fires, so the second+# firing is cut before it opens and writes a 'looped' line where its block+# would have stood, carrying the term of the round that was kept (#1434). The+# budget is generous here, so what ends the run is the cut and not the limit. symbolic: |-  - λ: L_zero-    dataize:-      𝛿1: $.x-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_dec+  - λ: L_down     dataize:       𝛿1: $.x-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-  - λ: L_mul-    dataize:-      𝛿1: $.a-      𝛿2: $.b-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_if-    dataize:-      𝛿1: $.c-    morph:-      𝑛1: $.left-      𝑛2: $.right-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.x'-deep: true+    𝑛: Φ.down( x ↦ ⟦ λ ⤍ 𝜎 ⟧ )+location: 'Q.y' acyclic: proven-partial: true steps: 250-input: >-  ⟦ if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,-    zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,-    dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,-    mul ↦ ⟦ a ↦ ∅, b ↦ ∅, λ ⤍ L_mul ⟧,-    fact ↦ ⟦ n ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ⟦ Δ ⤍ 01- ⟧, right ↦ Φ.mul( a ↦ ξ.n, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ) ) ) ) ⟧,-    x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ) ⟧-result: |--  ⟦-    n ↦ 𝜎1:λ:φ,-    φ ↦ Φ.if(-      c ↦ 𝜎2:λ,-      left ↦ 01-:Δ,-      right ↦ Φ.mul( a ↦ n, b ↦ Φ.fact( n ↦ 𝜎3:λ:φ ) )-    )-  ⟧+input: '⟦ down ↦ ⟦ x ↦ ∅, λ ⤍ L_down ⟧, y ↦ Φ.down( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).φ ⟧'+result: '⟦ x ↦ 𝜎1:λ, λ ⤍ L_down ⟧.φ' protocol: |-  𝕄(Φ.x):-    𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ:φ )-    𝕄(Φ.x.φ):-      𝑛·0·2 := Φ.zero( x ↦ 𝜎1:λ:φ )-      𝔼(L_zero):-        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-        𝑛·1·1 := 𝜎2:λ  # 𝑛-        𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)-      𝑛·0·3 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·4 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·5 := 𝑛·0·4( left ↦ 01-:Δ )-      𝑛·0·6 := Φ.mul( a ↦ 𝜎1:λ:φ )-      𝑛·0·7 := Φ.dec( x ↦ 𝜎1:λ:φ )-      𝔼(L_dec):-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-        𝑛·2·1 := 𝜎3:λ:φ  # 𝑛-        𝑛·2·2 := 𝜎3:λ:φ  # 𝕄(𝑛·2·1)-      𝑛·0·8 := Φ.fact( n ↦ 𝜎3:λ:φ )-      𝑛·0·9 := Φ.mul( a ↦ 𝜎1:λ:φ )-      𝑛·0·10 := 𝑛·0·9( b ↦ 𝑛·0·8 )-      𝔼(L_mul):-        𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)-        𝔻(Φ.a🌵3):-          𝕄(Φ.a🌵3):-            𝑛·3·1 := Φ.fact( n ↦ 𝜎3:λ:φ )-            𝑛·3·2 := Φ.if( c ↦ Φ.zero( x ↦ 𝜎3:λ:φ ) )-            𝑛·3·3 := 𝑛·3·2( left ↦ 01-:Δ )-            𝑛·3·4 := 𝑛·3·3( right ↦ Φ.mul( a ↦ 𝜎3:λ:φ, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) ) )-          𝔼(L_if):-            𝔻(Φ.a🌵4):-              𝕄(Φ.a🌵4):-                𝑛·4·1 := Φ.zero( x ↦ 𝜎3:λ:φ )-              𝔼(L_zero):-                𝛿1·5 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                𝑛·5·1 := 𝜎4:λ  # 𝑛-                𝑛·5·2 := 𝜎4:λ  # 𝕄(𝑛·5·1)-            𝛿1·4 := 𝔻(𝜎4:λ)  # 𝔻(ξ.c)-            𝑛1·4 := 01-:Δ  # 𝕄(ξ.left)-            𝕄(Φ.a🌵7):-              𝑛·4·2 := Φ.mul( a ↦ 𝜎3:λ:φ )-              𝑛·4·3 := 𝑛·4·2( b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) )-              𝕄(Φ.a🌵7.b):-                𝑛·4·4 := Φ.dec( x ↦ 𝜎3:λ:φ )-                𝔼(L_dec):-                  𝛿1·6 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                  𝑛·6·1 := 𝜎5:λ:φ  # 𝑛-                  𝑛·6·2 := 𝜎5:λ:φ  # 𝕄(𝑛·6·1)-                𝑛·4·5 := Φ.fact( n ↦ 𝜎5:λ:φ )-              looped(𝑛·0·10)  # proven-            𝑛2·4 := 𝑛·4·3  # 𝕄(ξ.right)-            𝔻(𝜎6:λ) == 01--            𝑛3·4 := 𝜎6:λ  # 𝑛1-            𝑛4·4 := 𝑛·4·3  # 𝑛2-            stuck(L_if)-        stuck(L_if)-      𝑛·0·11 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·12 := 𝑛·0·11( left ↦ 01-:Δ )-      𝑛·0·13 := 𝑛·0·12( right ↦ 𝑛·0·10 )-      𝔼(L_if):-        𝛿1·7 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)-        𝑛1·7 := 01-:Δ  # 𝕄(ξ.left)-        𝕄(Φ.a🌵11):-          𝑛·7·1 := Φ.mul( a ↦ 𝜎1:λ:φ )-          𝑛·7·2 := 𝑛·7·1( b ↦ 𝑛·3·1 )-          𝕄(Φ.a🌵11.b):-            𝑛·7·3 := Φ.fact( n ↦ 𝜎3:λ:φ )-          𝔼(L_mul):-            𝛿1·8 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)-            𝔻(Φ.a🌵13):-              𝕄(Φ.a🌵13):-                𝑛·8·1 := Φ.fact( n ↦ 𝜎3:λ:φ )-                𝑛·8·2 := Φ.if( c ↦ 𝑛·4·1 )-                𝑛·8·3 := 𝑛·8·2( left ↦ 01-:Δ )-                𝑛·8·4 := 𝑛·8·3( right ↦ 𝑛·4·3 )-              𝔼(L_if):-                𝔻(Φ.a🌵14):-                  𝕄(Φ.a🌵14):-                    𝑛·9·1 := Φ.zero( x ↦ 𝜎3:λ:φ )-                  𝔼(L_zero):-                    𝛿1·10 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                    𝑛·10·1 := 𝜎7:λ  # 𝑛-                    𝑛·10·2 := 𝜎7:λ  # 𝕄(𝑛·10·1)-                𝛿1·9 := 𝔻(𝜎7:λ)  # 𝔻(ξ.c)-                𝑛1·9 := 01-:Δ  # 𝕄(ξ.left)-                𝕄(Φ.a🌵17):-                  𝑛·9·2 := Φ.mul( a ↦ 𝜎3:λ:φ )-                  𝑛·9·3 := 𝑛·9·2( b ↦ Φ.fact( n ↦ 𝑛·4·4 ) )-                  𝕄(Φ.a🌵17.b):-                    𝑛·9·4 := Φ.dec( x ↦ 𝜎3:λ:φ )-                    𝔼(L_dec):-                      𝛿1·11 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-                      𝑛·11·1 := 𝜎8:λ:φ  # 𝑛-                      𝑛·11·2 := 𝜎8:λ:φ  # 𝕄(𝑛·11·1)-                    𝑛·9·5 := Φ.fact( n ↦ 𝜎8:λ:φ )-                  looped(𝑛·7·2)  # proven-                𝑛2·9 := 𝑛·9·3  # 𝕄(ξ.right)-                𝔻(𝜎9:λ) == 01--                𝑛3·9 := 𝜎9:λ  # 𝑛1-                𝑛4·9 := 𝑛·9·3  # 𝑛2-                stuck(L_if)-            stuck(L_if)-        𝑛2·7 := 𝑛·7·2  # 𝕄(ξ.right)-        𝔻(𝜎10:λ) == 01--        𝑛3·7 := 𝜎10:λ  # 𝑛1-        𝑛4·7 := 𝑛·7·2  # 𝑛2-        stuck(L_if)+  𝕄(Φ.y):+    𝑛·0·1 := Φ.down( x ↦ 𝜎1:λ )+    𝔼(L_down):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)+      𝑛·1·1 := Φ.down( x ↦ 𝜎2:λ )  # 𝑛+      𝕄(𝑛·1·1):+        𝑛·1·2 := Φ.down( x ↦ 𝜎2:λ )+      𝑛·1·3 := 𝑛·1·2  # 𝕄(𝑛·1·1)+    looped(𝑛·0·1)  # proven
− test-resources/evaluate-packs/defers-a-copy-whose-argument-is-a-bare-symbol.yaml
@@ -1,167 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The loop of #1729: 'joined' folds a tuple whose tail is the bare symbol '𝜎3',-# an object nobody knows. The right branch of the first fork is the copy-# 'step( acc ↦ …, tup ↦ 𝜎3 )', and nothing inside its body can succeed, since-# every round asks '𝜎3' for its 'length'. Before #1729 the walk entered that-# body, got stuck six times, and left it as written, so the join had a symbol-# on the left and a body on the right, and 'e' came to nothing. Now the walk-# does not enter a copy one of whose arguments reads to a bare symbol: it-# answers the copy with a fresh symbol and writes a 'deferred' line naming the-# copy it stands for, the join pairs that symbol with the one the left branch-# ends in, and 'e' comes to a symbol. Since #1732 that line spells the copy as-# a call of 'Φ.joined.step', the object of the world it was made of, rather-# than of 'Φ.e.step', the copy of 'joined' the walk wrote into 'e', since only-# the first one has voids the copy fills. Reading 'items.length' through the-# tuple, itself a copy over bare symbols, still reaches '𝜎1', since a copy with-# no φ has no body to defer.-symbolic: |-  - λ: L_bytes_eq-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )-  - λ: L_bytes_concat-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.φ-    morph:-      𝑛1: $.left-      𝑛2: $.right-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.e'-deep: true-partial: true-acyclic: plausible-result: '⟦ items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ ), φ ↦ 𝜎9:λ, step(acc, tup) ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ):φ ⟧'-input: |-  ⟦-    bool(if) ↦ ⟦ φ ↦ if( Φ.bytes( φ ↦ FF-:Δ ), Φ.bytes( φ ↦ 00-:Δ ) ) ⟧,-    bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ, concat(ρ, b) ↦ L_bytes_concat:λ ⟧,-    dataized(target) ↦ L_dataized:λ,-    tuple(length, head, tail) ↦ ⟦⟧,-    joined(items) ↦ ⟦-      φ ↦ Φ.dataized( target ↦ items.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if(-        Φ.bytes( φ ↦ --:Δ ),-        step( acc ↦ Φ.dataized( target ↦ items.head ), tup ↦ items.tail )-      ),-      step(ρ, acc, tup) ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if(-        acc,-        ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail )-      ):φ-    ⟧,-    e ↦ Φ.joined(-      items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ )-    )-  ⟧-protocol: |-  𝕄(Φ.e):-    𝑛·0·1 := Φ.joined( items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ ) )-    𝕄(Φ.e.items):-      𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·3 := Φ.tuple( length ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.tuple( length ↦ 𝑛·0·2 )-      𝑛·0·5 := 𝑛·0·4( head ↦ 𝜎2:λ )-      𝑛·0·6 := Φ.tuple( length ↦ 𝑛·0·2 )-      𝑛·0·7 := 𝑛·0·6( head ↦ 𝜎2:λ )-      𝑛·0·8 := 𝑛·0·7( tail ↦ 𝜎3:λ )-    𝕄(Φ.e.φ):-      𝑛·0·9 := Φ.tuple( length ↦ 𝑛·0·2 )-      𝑛·0·10 := 𝑛·0·9( head ↦ 𝜎2:λ )-      𝑛·0·11 := 𝑛·0·10( tail ↦ 𝜎3:λ )-      𝑛·0·12 := Φ.tuple( length ↦ 𝑛·0·2 )-      𝑛·0·13 := 𝑛·0·12( head ↦ 𝜎2:λ )-      𝑛·0·14 := 𝑛·0·13( tail ↦ 𝜎3:λ )-      𝑛·0·15 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·16 := Φ.dataized( target ↦ 𝑛·0·14.length )-      𝔼(L_dataized):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.tuple( length ↦ 𝑛·0·15 )-            𝑛·1·2 := 𝑛·1·1( head ↦ 𝜎2:λ )-            𝑛·1·3 := 𝑛·1·2( tail ↦ 𝜎3:λ )-            𝑛·1·4 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)-        𝑛·1·5 := Φ.bytes( φ ↦ 𝜎4:λ )  # 𝑛-        𝕄(𝑛·1·5):-          𝑛·1·6 := Φ.bytes( φ ↦ 𝜎4:λ )-        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-      𝑛·0·17 := Φ.bytes( φ ↦ 00-:Δ )-      𝑛·0·18 := 𝑛·1·6.eq( b ↦ 𝑛·0·17 )-      𝔼(L_bytes_eq):-        𝔻(Φ.a🌵1):-          𝕄(Φ.a🌵1):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎4:λ )-        𝛿1·2 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·2 := Φ.bytes( φ ↦ 00-:Δ )-          𝛿·2·1 := 00--        𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.b)-        𝑛·2·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )  # 𝑛-        𝕄(𝑛·2·3):-          𝑛·2·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )-        𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)-      𝑛·0·19 := Φ.bytes( φ ↦ --:Δ )-      𝑛·0·20 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ 𝑛·0·19 )-      𝑛·0·21 := Φ.tuple( length ↦ 𝑛·1·4 )-      𝑛·0·22 := 𝑛·0·21( head ↦ 𝜎2:λ )-      𝑛·0·23 := 𝑛·0·22( tail ↦ 𝜎3:λ )-      𝑛·0·24 := Φ.tuple( length ↦ 𝑛·1·4 )-      𝑛·0·25 := 𝑛·0·24( head ↦ 𝜎2:λ )-      𝑛·0·26 := 𝑛·0·25( tail ↦ 𝜎3:λ )-      𝑛·0·27 := Φ.dataized( target ↦ 𝑛·0·26.head )-      𝔼(L_dataized):-        𝔻(Φ.a🌵3):-          𝕄(Φ.a🌵3):-            𝑛·3·1 := Φ.tuple( length ↦ 𝑛·1·4 )-            𝑛·3·2 := 𝑛·3·1( head ↦ 𝜎2:λ )-            𝑛·3·3 := 𝑛·3·2( tail ↦ 𝜎3:λ )-        𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.target)-        𝑛·3·4 := Φ.bytes( φ ↦ 𝜎6:λ )  # 𝑛-        𝕄(𝑛·3·4):-          𝑛·3·5 := Φ.bytes( φ ↦ 𝜎6:λ )-        𝑛·3·6 := 𝑛·3·5  # 𝕄(𝑛·3·4)-      𝑛·0·28 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )-      𝑛·0·29 := Φ.tuple( length ↦ 𝑛·1·4 )-      𝑛·0·30 := 𝑛·0·29( head ↦ 𝜎2:λ )-      𝑛·0·31 := 𝑛·0·30( tail ↦ 𝜎3:λ )-      𝑛·0·32 := Φ.tuple( length ↦ 𝑛·1·4 )-      𝑛·0·33 := 𝑛·0·32( head ↦ 𝜎2:λ )-      𝑛·0·34 := 𝑛·0·33( tail ↦ 𝜎3:λ )-      𝑛·0·35 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )-      𝑛·0·36 := 𝑛·0·35( tup ↦ 𝑛·0·34.tail )-      𝑛·0·37 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ 𝑛·0·19 )-      𝑛·0·38 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )-      𝑛·0·39 := 𝑛·0·38( tup ↦ 𝑛·0·34.tail )-      𝑛·0·40 := 𝑛·0·37( right ↦ 𝑛·0·39 )-      𝔼(L_fork):-        𝛿1·4 := 𝔻(𝜎5:λ)  # 𝔻(ξ.φ)-        𝕄(Φ.a🌵5):-          𝑛·4·1 := Φ.bytes( φ ↦ --:Δ )-        𝑛1·4 := 𝑛·4·1  # 𝕄(ξ.left)-        𝕄(Φ.a🌵6):-          deferred(𝜎7) := Φ.joined.step( acc ↦ 𝑛·3·5, tup ↦ 𝜎3:λ )-        𝑛2·4 := 𝜎7:λ  # 𝕄(ξ.right)-        𝔻(𝜎8:λ) == ---        𝑛3·4 := ⟦ φ ↦ 𝜎8:λ, eq(b) ↦ L_bytes_eq:λ, concat(b) ↦ L_bytes_concat:λ ⟧  # 𝑛1-        𝑛4·4 := 𝑛2·4  # 𝑛2-        𝔻(𝜎9:λ) ∈ { 𝔻(𝜎8:λ), 𝔻(𝜎7:λ) }-        𝑛5·4 := 𝜎9:λ  # [𝑛3, 𝑛4]-        𝑛·4·2 := 𝑛5·4  # 𝑛-        𝑛·4·3 := 𝑛5·4  # 𝕄(𝑛·4·2)
test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-one-symbol-when-strict.yaml view
@@ -15,29 +15,19 @@       𝑛3: [𝑛1, 𝑛2]     𝑛: 𝑛3 location: 'Q.y'-deep: true input: |   ⟦     bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,     y ↦ ⟦-      r ↦ ⟦-        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,-        λ ⤍ L_fork-      ⟧-    ⟧+      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,+      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,+      λ ⤍ L_fork+    ⟧.φ   ⟧ fails: "No entry of --symbolic answers the λ function 'L_fork'" protocol: |   𝕄(Φ.y):-    𝕄(Φ.y.r):-      𝕄(Φ.y.r.b):-        𝕄(Φ.y.r.b.φ):-          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-      𝔼(L_fork):-        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-        𝕄(Φ.a🌵1):-          𝕄(Φ.a🌵1.φ):-            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)-        stuck(L_fork)+    𝔼(L_fork):+      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(ξ.b)+      stuck(L_fork)
test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-two-symbols-when-lenient.yaml view
@@ -16,29 +16,19 @@     lenient: true     𝑛: 𝑛3 location: 'Q.y'-deep: true input: |   ⟦     bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,     y ↦ ⟦-      r ↦ ⟦-        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, was ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ) ⟧,-        λ ⤍ L_fork-      ⟧-    ⟧+      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,+      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, was ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ) ⟧,+      λ ⤍ L_fork+    ⟧.φ   ⟧ fails: "No entry of --symbolic answers the λ function 'L_fork'" protocol: |   𝕄(Φ.y):-    𝕄(Φ.y.r):-      𝕄(Φ.y.r.b):-        𝕄(Φ.y.r.b.φ):-          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ )-      𝔼(L_fork):-        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-        𝕄(Φ.a🌵1):-          𝕄(Φ.a🌵1.φ):-            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ )-        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)-        stuck(L_fork)+    𝔼(L_fork):+      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ ):φ  # 𝕄(ξ.b)+      stuck(L_fork)
+ test-resources/evaluate-packs/fails-on-a-branch-whose-phi-reads-its-own-object-when-strict.yaml view
@@ -0,0 +1,27 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The fork the lenient join of #1885 gets through, under an entry that does+# not say 'lenient: true'. The branch that is an ordinary object stays as 𝕄+# left it, a formation whose φ reads its own attribute, so the strict join+# refuses it beside a bare symbol and the firing gets stuck.+symbolic: |+  - λ: L_eq+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      𝑛3: [𝑛1, 𝑛2]+    𝑛: 𝑛3+location: 'Q.y'+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ξ.eq, eq ↦ ⟦ λ ⤍ L_eq ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+fails: "No entry of --symbolic answers the λ function 'L_fork'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := ⟦ φ ↦ eq, eq ↦ L_eq:λ ⟧  # 𝕄(ξ.a)+      𝑛2·1 := 𝜎1:λ:φ  # 𝕄(ξ.b)+      stuck(L_fork)
− test-resources/evaluate-packs/fails-on-a-message-whose-firing-gets-stuck.yaml
@@ -1,106 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The walk reduces the message of the copy a branch raises with like any other-# code, so the firing of 'L_number_plus' it asks for gets stuck on the-# 'L_number_nope' no entry answers, the way the firing of any operand does, and-# the run fails (#1867).-symbolic: |-  - λ: L_number_gt-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    terminators: [L_throw]-    𝑛: 𝑛5-location: 'Q.@'-deep: true-input: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    error(message) ↦ ⟦ λ ⤍ L_throw ⟧,-    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,-    number(φ) ↦ ⟦-      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,-      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,-      nope(ρ, x) ↦ ⟦ λ ⤍ L_number_nope ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( then ↦ 1, else ↦ Φ.error( message ↦ 7.plus( 8.nope( 9 ) ) ) ).plus( 6 ) ⟧-fails: "No entry of --symbolic answers the λ function 'L_number_nope'"-protocol: |-  𝕄(Φ.φ):-    𝑛·0·1 := 5-    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )-    𝔼(L_number_gt):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := 5-          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·3 := 0-          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )-        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0-      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛-      𝕄(𝑛·1·5):-        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )-      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1 )-    𝑛·0·4 := 𝑛·0·3( else ↦ Φ.error( message ↦ 7.plus( 8.nope( 9 ) ) ) )-    𝔼(L_fork):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝕄(Φ.a🌵3):-        𝑛·2·2 := 1-        𝕄(Φ.a🌵3.φ):-          𝑛·2·3 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛1·2 := 𝑛·2·2  # 𝕄(ξ.then)-      𝕄(Φ.a🌵4):-        𝑛·2·4 := Φ.error( message ↦ 7.plus( 8.nope( 9 ) ) )-        𝕄(Φ.a🌵4.message):-          𝑛·2·5 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-          𝑛·2·6 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·7 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·8 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-          𝑛·2·9 := Φ.number( φ ↦ 𝑛·2·8 )-          𝑛·2·10 := Φ.number( φ ↦ 𝑛·2·8 )-          𝑛·2·11 := Φ.bytes( φ ↦ 40-22-00-00-00-00-00-00:Δ )-          𝑛·2·12 := Φ.number( φ ↦ 𝑛·2·11 )-          𝑛·2·13 := Φ.number( φ ↦ 𝑛·2·8 )-          𝑛·2·14 := 𝑛·2·13.nope( x ↦ 𝑛·2·12 )-          𝑛·2·15 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·16 := 𝑛·2·15.plus( x ↦ 𝑛·2·13.nope( 𝑛·2·12 ) )-          𝔼(L_number_plus):-            𝔻(Φ.a🌵5):-              𝕄(Φ.a🌵5):-                𝑛·3·1 := Φ.number( φ ↦ 𝑛·2·5 )-                𝑛·3·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-              𝛿·3·1 := 40-1C-00-00-00-00-00-00  # 7.0-            𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-            𝔻(Φ.a🌵6):-              𝕄(Φ.a🌵6):-                𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·8 )-                𝑛·3·4 := 𝑛·3·3.nope( x ↦ 𝑛·2·12 )-              unanswered(L_number_nope)  # 𝔻(𝑛·3·4)-            stuck(L_number_nope)-      stuck(L_number_nope)
test-resources/evaluate-packs/fails-on-an-operand-that-reaches-the-terminator.yaml view
@@ -20,10 +20,7 @@     dataize:       𝛿1: $.v     𝑛: ⟦ ⟧-location: 'Q.l🌵'-deep: true-acyclic: proven-steps: 32+location: 'Q.l🌵.e1' input: >   ⟦     number ↦ ⟦ φ ↦ ∅ ⟧,@@ -33,29 +30,22 @@     l🌵 ↦ ⟦       mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,       root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read )+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read ).φ     ⟧   ⟧ fails: 'terminator ⊥' protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.block( buffer ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.block( buffer ↦ 𝑛·0·4 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6.read )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.block( buffer ↦ 𝑛·0·4 )-          𝑛·1·4 := Φ.dataized( target ↦ ⊥ )-          𝔼(L_dataized):+  𝕄(Φ.l🌵.e1):+    𝑛·0·1 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 1 )+    𝑛·0·2 := 𝑛·0·1( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ).read )+    𝔼(L_entry):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 1+          𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)+      𝕄(Φ.a🌵1):+        𝑛·1·3 := Φ.block( buffer ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )+        𝑛·1·4 := Φ.dataized( target ↦ ⊥ )+        𝔼(L_dataized):
test-resources/evaluate-packs/fires-a-formation-again-where-the-budget-stalled-it.yaml view
@@ -2,14 +2,15 @@ # SPDX-License-Identifier: MIT --- # yamllint disable rule:line-length-# The memo of '--acyclic=plausible' keeps no stall the step budget made. The-# formation 'f' is first fired deep inside the operand of 'L_wrap', where the-# budget runs out before 'arg' comes down, so the firing gets stuck. Before-# #1514 the stall was kept, and the firings of the same formation under-# 'Φ.q.φ' and 'Φ.f', where the budget is enough, were told it bare, with-# nothing answered in between. Now the firing under 'Φ.q.φ' brings 'arg'-# down and answers, and the walk leaves that answer in 'f', so it fires-# nothing more when it reaches 'Φ.f' (#1720).+# The memo of '--acyclic=plausible' tells no stall the step budget made to a+# firing that has spent fewer steps than the stalled one. 'L_pair' first+# reduces 'x', whose firing brings 'p' down, which reaches the formation 'f'+# through 'q' and fires it so deep that the budget runs out before 'arg'+# comes down, so the firing gets stuck. Before #1514 the stall was kept bare,+# and a later firing of the same formation, where the budget is enough, was+# told it with nothing answered in between. Now the 'evaluate' line of+# 'L_pair' fires 'f' again at a shallower site, which brings 'arg' down and+# answers (#1521, #1874). symbolic: |   - λ: L_outer     dataize:@@ -19,34 +20,49 @@     dataize:       𝛿1: ξ.w     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.f+    evaluate:+      𝑛3: 𝑛2+    𝑛: ⟦ φ ↦ 𝑛3 ⟧+location: 'Q.r' partial: true acyclic: plausible-steps: 10+steps: 12 input: >-  ⟦ x ↦ ⟦ w ↦ Φ.p, λ ⤍ L_wrap ⟧,+  ⟦ r ↦ ⟦+      x ↦ ⟦ w ↦ Φ.p, λ ⤍ L_wrap ⟧.φ,+      f ↦ Φ.f,+      λ ⤍ L_pair ⟧.φ,     p ↦ ⟦ φ ↦ Φ.q ⟧,     q ↦ ⟦ φ ↦ Φ.f ⟧,     f ↦ ⟦ arg ↦ Φ.c, λ ⤍ L_outer ⟧,     c ↦ ⟦ φ ↦ Φ.d ⟧,     d ↦ ⟦ φ ↦ Φ.e ⟧,     e ↦ ⟦ Δ ⤍ 01- ⟧ ⟧+result: '𝜎1:λ' protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝔼(L_wrap):-        𝔻(Φ.a🌵0):-          𝔼(L_outer):-            𝔻(Φ.a🌵1):-              𝕄(Φ.a🌵1):-                starved(10)-            stuck(L_outer)-        stuck(L_outer)-    𝕄(Φ.q):-      𝕄(Φ.q.φ):-        𝔼(L_outer):-          𝔻(Φ.a🌵2):-            𝛿·3·1 := 01--          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.arg)-          𝑛·3·1 := 𝜎1:λ  # 𝑛-          𝑛·3·2 := 𝜎1:λ  # 𝕄(𝑛·3·1)+  𝕄(Φ.r):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝔼(L_wrap):+          𝔻(Φ.a🌵1):+            𝔼(L_outer):+              𝔻(Φ.a🌵2):+                𝕄(Φ.a🌵2):+                  starved(12)+              stuck(L_outer)+          stuck(L_outer)+      𝑛1·1 := ⟦ w ↦ Φ.p, λ ⤍ L_wrap ⟧.φ  # 𝕄(ξ.x)+      𝑛2·1 := ⟦ arg ↦ Φ.c, λ ⤍ L_outer ⟧  # 𝕄(ξ.f)+      𝔼(L_outer):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵4):+          𝛿·4·1 := 01-+        𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.arg)+        𝑛·4·1 := 𝜎1:λ  # 𝑛+        𝑛·4·2 := 𝜎1:λ  # 𝕄(𝑛·4·1)+      𝑛3·1 := 𝑛·4·2  # 𝔼(𝑛2)+      𝑛·1·1 := 𝜎1:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎1:λ:φ  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/fires-a-formation-once-however-often-it-is-met.yaml view
@@ -3,94 +3,89 @@ --- # yamllint disable rule:line-length # The memo of '--acyclic=plausible': 𝔼 is a function of the formation it-# fires, so two bindings spelling one term are two firings of one formation,-# and a run firing each of them afresh mints two symbols for one value and-# fires everything under the second once more (#1476). Under the mode the-# second takes what the first answered: the inner sum of 'b' is the inner sum-# of 'a' fired again, and so is the outer one once the inner answers the-# same, so the walk over 'b' reduces nothing, mints nothing and lands 'b' on-# the very symbol 'a' came to. The protocol still writes the two firings-# under 'b', at their own site, each with the answer the firing under 'a'-# made, named after the line that made it, and with no operand line under-# it, since none was reduced.+# fires, so two bindings spelling one term fire one formation twice, and a+# run firing it afresh each time mints two symbols for one value and fires+# everything under the second once more (#1476). The 'evaluate' lines of+# 'L_pair' fire 'a' and then 'b', which spell the very same sum. Under the+# mode the second firing takes what the first answered: it reduces nothing,+# mints nothing and lands 'b' on the very symbol 'a' came to. The protocol+# still writes the firing of 'b' at its own site, with the answer the firing+# of 'a' made, named after the line that made it, and with no operand line+# under it, since none was reduced (#1874). symbolic: |   - λ: L_number_plus     dataize:       𝛿1: $.ρ       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    𝑛: ⟦ φ ↦ ⟦ a ↦ 𝑛3, b ↦ 𝑛4 ⟧ ⟧+location: 'Q.p' acyclic: plausible input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    a ↦ 7.plus( 5.plus( 6 ) ),-    b ↦ 7.plus( 5.plus( 6 ) ) ⟧+    p ↦ ⟦+      a ↦ 7.plus( 5.plus( 6 ) ),+      b ↦ 7.plus( 5.plus( 6 ) ),+      λ ⤍ L_pair ⟧.φ ⟧ result: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    number(φ) ↦ ⟦ plus(x) ↦ L_number_plus:λ ⟧,-    a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧,+  ⟦ a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧,     b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧ ⟧ protocol: |-  𝕄(Φ):-    𝕄(Φ.a):-      𝑛·0·1 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·4 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·4 )-      𝑛·0·7 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·8 := Φ.number( φ ↦ 𝑛·0·7 )-      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·4 )-      𝑛·0·10 := 𝑛·0·9.plus( x ↦ 𝑛·0·8 )-      𝔼(L_number_plus):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·4 )-            𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵1):-          𝕄(Φ.a🌵1):-            𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·7 )-            𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-          𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0-        𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-        𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛-        𝕄(𝑛·1·5):-          𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )-        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-      𝑛·0·11 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·12 := 𝑛·0·11.plus( x ↦ 𝑛·1·6 )-      𝔼(L_number_plus):+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := 7+        𝑛·1·2 := 𝑛·1·1.plus( x ↦ 5.plus( 6 ) )+      𝑛1·1 := 𝑛·1·2  # 𝕄(ξ.a)+      𝕄(Φ.a🌵1):+        𝑛·1·3 := 7+        𝑛·1·4 := 𝑛·1·3.plus( x ↦ 5.plus( 6 ) )+      𝑛2·1 := 𝑛·1·4  # 𝕄(ξ.b)+      𝔼(L_number_plus):  # 𝔼(𝑛1)         𝔻(Φ.a🌵2):           𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·1 )+            𝑛·2·1 := 7             𝑛·2·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )           𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0         𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵3):+          𝕄(Φ.a🌵3):+            𝑛·2·3 := 5+            𝑛·2·4 := 𝑛·2·3.plus( x ↦ 6 )+          𝔼(L_number_plus):+            𝔻(Φ.a🌵4):+              𝕄(Φ.a🌵4):+                𝑛·3·1 := 5+                𝑛·3·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )+              𝛿·3·1 := 40-14-00-00-00-00-00-00  # 5.0+            𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+            𝔻(Φ.a🌵5):+              𝕄(Φ.a🌵5):+                𝑛·3·3 := 6+                𝑛·3·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )+              𝛿·3·2 := 40-18-00-00-00-00-00-00  # 6.0+            𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)+            𝑛·3·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛+            𝕄(𝑛·3·5):+              𝑛·3·6 := Φ.number( φ ↦ 𝜎1:λ )+            𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)         𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-        𝑛·2·3 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-        𝕄(𝑛·2·3):-          𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )-        𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)-    𝕄(Φ.b):-      𝑛·0·13 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·14 := Φ.number( φ ↦ 𝑛·0·13 )-      𝑛·0·15 := Φ.number( φ ↦ 𝑛·0·13 )-      𝑛·0·16 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-      𝑛·0·17 := Φ.number( φ ↦ 𝑛·0·16 )-      𝑛·0·18 := Φ.number( φ ↦ 𝑛·0·16 )-      𝑛·0·19 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·20 := Φ.number( φ ↦ 𝑛·0·19 )-      𝑛·0·21 := Φ.number( φ ↦ 𝑛·0·16 )-      𝑛·0·22 := 𝑛·0·21.plus( x ↦ 𝑛·0·20 )-      𝔼(L_number_plus):-        𝑛·3·1 := 𝑛·1·6  # 𝑛-        𝑛·3·2 := 𝑛·1·7  # 𝕄(𝑛·3·1)-      𝑛·0·23 := Φ.number( φ ↦ 𝑛·0·13 )-      𝑛·0·24 := 𝑛·0·23.plus( x ↦ 𝑛·1·6 )-      𝔼(L_number_plus):-        𝑛·4·1 := 𝑛·2·4  # 𝑛-        𝑛·4·2 := 𝑛·2·5  # 𝕄(𝑛·4·1)+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+        𝕄(𝑛·2·5):+          𝑛·2·6 := Φ.number( φ ↦ 𝜎2:λ )+        𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)+      𝑛3·1 := 𝑛·2·7  # 𝔼(𝑛1)+      𝔼(L_number_plus):  # 𝔼(𝑛2)+        𝑛·4·1 := 𝑛·2·6  # 𝑛+        𝑛·4·2 := 𝑛3·1  # 𝕄(𝑛·4·1)+      𝑛4·1 := 𝑛·4·2  # 𝔼(𝑛2)+      𝑛·1·5 := ⟦ a ↦ 𝑛·2·6, b ↦ 𝑛·2·6 ⟧:φ  # 𝑛+      𝑛·1·6 := ⟦ a ↦ 𝑛·2·6, b ↦ 𝑛·2·6 ⟧:φ  # 𝕄(𝑛·1·5)
− test-resources/evaluate-packs/fires-a-lambda-once-however-many-bindings-read-it.yaml
@@ -1,25 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The deep walk writes what it morphed back into the world (#1720). Before,-# the world stayed as written, so 'b' read 'Φ.a' as 'Φ.foo.x' and fired 'Fn'-# twice more, and the walk fired it once again when it reached 'foo'. Now the-# read of 'Φ.foo' under 'a' leaves the answer in 'foo', the walk leaves what-# it made of 'a' in 'a', and 'Fn' fires once, as it does in EO.-symbolic: |-  - λ: Fn-    𝑛: ⟦ x ↦ ⟦ y ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ⟧-deep: true-partial: true-acyclic: plausible-input: >-  ⟦ a ↦ Φ.foo.x, b ↦ Φ.a.y, foo ↦ ⟦ λ ⤍ Fn ⟧ ⟧-result: >-  ⟦ a ↦ ⟦ x ↦ ⟦ y ↦ 𝜎1:λ ⟧ ⟧.x, b ↦ Φ.a.y, foo ↦ ⟦ x ↦ ⟦ y ↦ 𝜎1:λ ⟧ ⟧ ⟧-protocol: |-  𝕄(Φ):-    𝕄(Φ.a):-      𝔼(Fn):-        𝑛·1·1 := 𝜎1:λ:y:x  # 𝑛-        𝑛·1·2 := 𝜎1:λ:y:x  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/fires-a-read-once-in-the-copy-it-stands-in.yaml
@@ -1,62 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The deep walk writes what it morphed back into the copy it stands in, not-# only into the world as written (#1720). Here 'm' lives in the copy-# 'Φ.same( left ↦ … )', and 'φ' reads it twice before the walk reaches 'm'.-# Before, every read fired 'L_bytes_eq' again, and so did the walk at 'm'. Now-# the first read leaves the answer in 'm', so the second read and the walk at-# 'm' fire nothing, and 'L_bytes_eq' fires twice: once for 'm' and once for-# 'm.eq( b ↦ m )'.-symbolic: |-  - λ: L_bytes_eq-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.e'-deep: true-partial: true-acyclic: plausible-input: >-  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,-    same(left) ↦ ⟦ φ ↦ m.eq( b ↦ m ), m ↦ left.eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ) ⟧,-    e ↦ Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) ⟧-result: >-  ⟦ left ↦ Φ.bytes( φ ↦ 𝜎1:λ ),-    φ ↦ ⟦ φ ↦ 𝜎3:λ, eq(b) ↦ L_bytes_eq:λ ⟧,-    m ↦ ⟦ φ ↦ 𝜎2:λ, eq(b) ↦ L_bytes_eq:λ ⟧ ⟧-protocol: |-  𝕄(Φ.e):-    𝑛·0·1 := Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )-    𝕄(Φ.e.left):-      𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝕄(Φ.e.φ):-      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·4 := 𝑛·0·3.eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) )-      𝔼(L_bytes_eq):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵1):-          𝕄(Φ.a🌵1):-            𝑛·1·2 := Φ.bytes( φ ↦ 00-:Δ )-          𝛿·1·1 := 00--        𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.b)-        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛-        𝕄(𝑛·1·3):-          𝑛·1·4 := Φ.bytes( φ ↦ 𝜎2:λ )-        𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)-      𝑛·0·5 := 𝑛·1·4.eq( b ↦ 𝑛·1·4 )-      𝔼(L_bytes_eq):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )-        𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-        𝛿2·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.b)-        𝑛·2·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛-        𝕄(𝑛·2·2):-          𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )-        𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
− test-resources/evaluate-packs/fires-a-sibling-at-the-depth-the-head-pass-began-at.yaml
@@ -1,38 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The head pass of 𝕄 takes four steps through the four φ layers of 'Φ.e'-# before it reads 'eq' of the inner formation, whose 'φ' reads its sibling-# 'm'. Under '--deep' the head pass fires 'm' first and writes its answer back-# (#1727). Before #1731 it fired 'm' at the depth of its fourth step, so the-# firing ran out of ten steps, and 'L_dataized' fired three times without-# reaching the answer. Now the head pass fires 'm' at the depth it began at,-# the way the deep walk at the end of 𝕄 fires it, and 'm' fires once.-symbolic: |-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.e'-deep: true-partial: true-acyclic: plausible-steps: 10-input: >-  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,-    dataized(target) ↦ L_dataized:λ,-    e ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ φ ↦ m, m ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) ⟧ ⟧ ⟧ ⟧.eq ⟧-result: '⟦ b ↦ ∅, λ ⤍ L_bytes_eq ⟧'-protocol: |-  𝕄(Φ.e):-    𝑛·0·1 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )-    𝔼(L_dataized):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)-      𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛-      𝕄(𝑛·1·2):-        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)
− test-resources/evaluate-packs/fires-a-sibling-once-through-a-dispatch-into-its-object.yaml
@@ -1,56 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The term at 'Φ.e' reads 'φ' of the object 'Φ.same' makes, and that 'φ'-# reads its sibling 'm' twice. Before #1727 the head pass of 𝕄 copied 'm'-# into both reads before anything fired, so the deep walk later fired-# 'L_dataized' again for each copy, four firings in all. Now, under '--deep',-# the head pass first fires the reads of siblings in the binding it-# dispatches into, the way the deep walk fires them, and writes their answers-# back, so 'm' fires once and both reads take its answer: two firings, as-# many as the same object costs without the '.φ'.-symbolic: |-  - λ: L_bytes_eq-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.e'-deep: true-partial: true-acyclic: plausible-input: >-  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,-    dataized(target) ↦ L_dataized:λ,-    same(left) ↦ ⟦ φ ↦ m.eq( b ↦ m ), m ↦ Φ.dataized( target ↦ left ) ⟧,-    e ↦ Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ).φ ⟧-result: '⟦ φ ↦ 𝜎3:λ, eq(b) ↦ L_bytes_eq:λ ⟧'-protocol: |-  𝕄(Φ.e):-    𝑛·0·1 := Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )-    𝑛·0·2 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )-    𝔼(L_dataized):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)-      𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛-      𝕄(𝑛·1·2):-        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)-    𝑛·0·3 := 𝑛·1·3.eq( b ↦ 𝑛·1·3 )-    𝔼(L_bytes_eq):-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-      𝛿2·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.b)-      𝑛·2·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛-      𝕄(𝑛·2·2):-        𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )-      𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
test-resources/evaluate-packs/fires-a-stalled-formation-again-after-what-its-own-firing-answered.yaml view
@@ -3,13 +3,13 @@ --- # yamllint disable rule:line-length # The memo of '--acyclic=plausible' stamps a stall with the count of answers-# the memo held when the firing began, not when it failed. The firing under-# 'x' brings 'first' down through 'L_inner', which answers, and then gets-# stuck on 'L_none'. Before #1507 the stall was stamped after that answer, so-# the firing under 'y', the very same formation, was told the stall bare, as-# if nothing was answered since. Now the answer made inside the first firing-# counts as one made after it began, so the firing under 'y' reduces its-# operands again.+# the memo held when the firing began, not when it failed. 'L_pair' reduces+# 'x' and then 'y', and both spell the very same formation of 'L_outer'. The+# firing under 'x' brings 'first' down through 'L_inner', which answers, and+# then gets stuck on 'L_none'. Before #1507 the stall was stamped after that+# answer, so the firing under 'y' was told the stall bare, as if nothing was+# answered since. Now the answer made inside the first firing counts as one+# made after it began, so the firing under 'y' reduces its operands again. symbolic: |   - λ: L_outer     dataize:@@ -18,41 +18,47 @@     𝑛: ⟦ λ ⤍ 𝜎 ⟧   - λ: L_inner     𝑛: ⟦ Δ ⤍ 01- ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.y+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+location: 'Q.p' partial: true acyclic: plausible-steps: 64 input: >-  ⟦ x ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,-    y ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,+  ⟦ p ↦ ⟦+      x ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,+      y ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,+      λ ⤍ L_pair ⟧.φ,     s ↦ ⟦ φ ↦ Φ.g ⟧,     g ↦ ⟦ λ ⤍ L_inner ⟧ ⟧ protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝔼(L_outer):-        𝔻(Φ.a🌵0):-          𝔼(L_inner):-            𝑛·2·1 := 01-:Δ  # 𝑛-            𝑛·2·2 := 01-:Δ  # 𝕄(𝑛·2·1)-          𝛿·1·1 := 01--        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.first)-        𝔻(Φ.a🌵1):-          unanswered(L_none)  # 𝔻(L_none:λ)-        stuck(L_none)-    𝕄(Φ.y):-      𝔼(L_outer):-        𝔻(Φ.a🌵2):-          𝔼(L_inner):-            𝑛·4·1 := 01-:Δ  # 𝑛-            𝑛·4·2 := 01-:Δ  # 𝕄(𝑛·4·1)-          𝛿·3·1 := 01--        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.first)-        𝔻(Φ.a🌵3):-          unanswered(L_none)  # 𝔻(L_none:λ)-        stuck(L_none)-    𝕄(Φ.s):-      𝕄(Φ.s.φ):-        𝔼(L_inner):-          𝑛·5·1 := 01-:Δ  # 𝑛-          𝑛·5·2 := 01-:Δ  # 𝕄(𝑛·5·1)+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝔼(L_outer):+          𝔻(Φ.a🌵1):+            𝔼(L_inner):+              𝑛·3·1 := 01-:Δ  # 𝑛+              𝑛·3·2 := 01-:Δ  # 𝕄(𝑛·3·1)+            𝛿·2·1 := 01-+          𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.first)+          𝔻(Φ.a🌵2):+            unanswered(L_none)  # 𝔻(L_none:λ)+          stuck(L_none)+      𝑛1·1 := ⟦ first ↦ Φ.s, second ↦ L_none:λ, λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.x)+      𝕄(Φ.a🌵3):+        𝔼(L_outer):+          𝔻(Φ.a🌵4):+            𝔼(L_inner):+              𝑛·5·1 := 01-:Δ  # 𝑛+              𝑛·5·2 := 01-:Δ  # 𝕄(𝑛·5·1)+            𝛿·4·1 := 01-+          𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.first)+          𝔻(Φ.a🌵5):+            unanswered(L_none)  # 𝔻(L_none:λ)+          stuck(L_none)+      𝑛2·1 := ⟦ first ↦ Φ.s, second ↦ L_none:λ, λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.y)+      𝑛·1·1 := 𝜎1:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎1:λ:φ  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/fires-a-stalled-formation-again-once-something-was-answered.yaml view
@@ -3,11 +3,13 @@ --- # yamllint disable rule:line-length # The memo of '--acyclic=plausible' keeps a stall only while nothing new was-# answered. The firing of 'f' under 'x' brings 'big' down inside the box of-# 's', and 's' is within 'big', so the mode cuts the operand and the firing-# gets stuck. Then 'g' answers, and 'f' is fired again at its own site, where-# nothing cuts it. Before #1495 that firing took the stall the first one met-# and reduced nothing; now it brings 'big' down and answers.+# answered. 'L_pair' first reduces 'x', whose firing brings 's' down, which+# fires 'f', whose operand 'big' is brought down inside the box of 's'. Since+# 's' is within 'big', the mode cuts the operand and the firing of 'f' gets+# stuck. Then the 'evaluate' lines of 'L_pair' fire 'g', which answers, and+# 'f' again, where nothing cuts it. Before #1495 that firing took the stall+# the first one met and reduced nothing; now it brings 'big' down and+# answers (#1874). symbolic: |   - λ: L_f     dataize:@@ -15,40 +17,56 @@     𝑛: ⟦ λ ⤍ 𝜎 ⟧   - λ: L_g     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.g+      𝑛3: $.f+    evaluate:+      𝑛4: 𝑛2+      𝑛5: 𝑛3+    𝑛: ⟦ φ ↦ 𝑛5 ⟧+location: 'Q.p' partial: true acyclic: plausible-steps: 32 input: >-  ⟦ x ↦ ⟦ λ ⤍ L_f, arg ↦ Φ.s ⟧,+  ⟦ p ↦ ⟦+      x ↦ ⟦ λ ⤍ L_f, arg ↦ Φ.s ⟧.φ,+      g ↦ Φ.g,+      f ↦ Φ.f,+      λ ⤍ L_pair ⟧.φ,     g ↦ ⟦ λ ⤍ L_g ⟧,     f ↦ ⟦ λ ⤍ L_f, arg ↦ Φ.big ⟧,     s ↦ ⟦ φ ↦ Φ.f ⟧,     big ↦ ⟦ φ ↦ Φ.k( x ↦ Φ.f ) ⟧,     k ↦ ⟦ x ↦ ∅, φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧ ⟧+result: '𝜎2:λ' protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝔼(L_f):-        𝔻(Φ.a🌵0):-          𝔼(L_f):-            𝔻(Φ.a🌵1):-              looped(Φ.f:φ)  # plausible-            stuck(L_f)-        stuck(L_f)-    𝕄(Φ.g):-      𝔼(L_g):-        𝑛·3·1 := 𝜎1:λ  # 𝑛-        𝑛·3·2 := 𝜎1:λ  # 𝕄(𝑛·3·1)-    𝕄(Φ.f):-      𝔼(L_f):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·4·1 := Φ.k( x ↦ Φ.f )-          𝛿·4·1 := 01--        𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.arg)-        𝑛·4·2 := 𝜎2:λ  # 𝑛-        𝑛·4·3 := 𝜎2:λ  # 𝕄(𝑛·4·2)-    𝕄(Φ.big):-      𝕄(Φ.big.φ):-        𝑛·0·1 := Φ.k( x ↦ Φ.f )+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝔼(L_f):+          𝔻(Φ.a🌵1):+            𝔼(L_f):+              𝔻(Φ.a🌵2):+                looped(Φ.f:φ)  # plausible+              stuck(L_f)+          stuck(L_f)+      𝑛1·1 := ⟦ λ ⤍ L_f, arg ↦ Φ.s ⟧.φ  # 𝕄(ξ.x)+      𝑛2·1 := L_g:λ  # 𝕄(ξ.g)+      𝑛3·1 := ⟦ λ ⤍ L_f, arg ↦ Φ.big ⟧  # 𝕄(ξ.f)+      𝔼(L_g):  # 𝔼(𝑛2)+        𝑛·4·1 := 𝜎1:λ  # 𝑛+        𝑛·4·2 := 𝜎1:λ  # 𝕄(𝑛·4·1)+      𝑛4·1 := 𝑛·4·2  # 𝔼(𝑛2)+      𝔼(L_f):  # 𝔼(𝑛3)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·5·1 := Φ.k( x ↦ Φ.f )+          𝛿·5·1 := 01-+        𝛿1·5 := 𝛿·5·1  # 𝔻(ξ.arg)+        𝑛·5·2 := 𝜎2:λ  # 𝑛+        𝑛·5·3 := 𝜎2:λ  # 𝕄(𝑛·5·2)+      𝑛5·1 := 𝑛·5·3  # 𝔼(𝑛3)+      𝑛·1·1 := 𝜎2:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎2:λ:φ  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/fires-nothing-twice-in-a-binding-a-dispatch-reads.yaml
@@ -1,65 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Under '--deep' the head pass of 𝕄 fires the reads of siblings in the-# binding it dispatches into before it reads that binding (#1727), and only-# them. Here every sibling 'φ' reads is 'a', which is no λ, so nothing fires-# before the head pass reads 'φ', and each of the two 'L_plus' fires once.-# Walking the whole binding instead would settle 'a.twice.plus' on the way,-# which fires the inner 'L_plus', and keep nothing of it, since what it-# settles to is a 'plus' still waiting for 'x' and no answer of a λ. The head-# pass would then fire the inner 'L_plus' again, three firings in all.-symbolic: |-  - λ: L_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.e'-deep: true-partial: true-input: >-  ⟦ bytes(φ) ↦ ⟦ plus(ρ, x) ↦ L_plus:λ, twice(ρ) ↦ ⟦ φ ↦ ξ.ρ.plus( x ↦ ξ.ρ ) ⟧ ⟧,-    pair(a) ↦ ⟦ φ ↦ a.twice.plus( x ↦ a ) ⟧,-    e ↦ Φ.pair( Φ.bytes( φ ↦ 𝜎1:λ ) ).φ ⟧-result: '⟦ φ ↦ 𝜎3:λ, plus(x) ↦ L_plus:λ, twice ↦ ρ.plus( x ↦ ρ ):φ ⟧'-protocol: |-  𝕄(Φ.e):-    𝑛·0·1 := Φ.pair( a ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )-    𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝑛·0·3 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝑛·0·5 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝑛·0·6 := 𝑛·0·5.plus( x ↦ 𝑛·0·5 )-    𝔼(L_plus):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·2 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝛿2·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-      𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛-      𝕄(𝑛·1·3):-        𝑛·1·4 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)-    𝑛·0·7 := 𝑛·1·4.plus( x ↦ 𝑛·1·2 )-    𝕄(Φ.e.x):-      𝑛·0·8 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·9 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·10 := Φ.bytes( φ ↦ 𝜎1:λ )-    𝔼(L_plus):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵3):-        𝕄(Φ.a🌵3):-          𝑛·2·2 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-      𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛-      𝕄(𝑛·2·3):-        𝑛·2·4 := Φ.bytes( φ ↦ 𝜎3:λ )-      𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)
+ test-resources/evaluate-packs/fires-the-answer-of-an-evaluate-line-on-the-next-line.yaml view
@@ -0,0 +1,36 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# An 'evaluate' line fires once and no more: 'L_first' answers a ready call+# of 'L_second', which 𝔼 leaves as 𝕄 left it, and only the next line of the+# block fires that call (#1874).+symbolic: |+  - λ: L_first+    𝑛: ⟦ λ ⤍ L_second ⟧+  - λ: L_second+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+  - λ: L_twice+    morph:+      𝑛1: $.a+    evaluate:+      𝑛2: 𝑛1+      𝑛3: 𝑛2+    𝑛: 𝑛3+location: 'Q.y'+input: '⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ L_first ⟧, λ ⤍ L_twice ⟧.φ ⟧'+result: '𝜎1:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_twice):+      𝑛1·1 := L_first:λ  # 𝕄(ξ.a)+      𝔼(L_first):  # 𝔼(𝑛1)+        𝑛·2·1 := L_second:λ  # 𝑛+        𝑛·2·2 := L_second:λ  # 𝕄(𝑛·2·1)+      𝑛2·1 := L_second:λ  # 𝔼(𝑛1)+      𝔼(L_second):  # 𝔼(𝑛2)+        𝑛·3·1 := 𝜎1:λ:φ  # 𝑛+        𝑛·3·2 := 𝜎1:λ:φ  # 𝕄(𝑛·3·1)+      𝑛3·1 := 𝑛·3·2  # 𝔼(𝑛2)+      𝑛·1·1 := 𝑛3·1  # 𝑛+      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/joins-a-bare-symbol-with-a-branch-holding-one-symbol-when-lenient.yaml view
@@ -17,32 +17,23 @@     lenient: true     𝑛: 𝑛3 location: 'Q.y'-deep: true input: |   ⟦     bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,     y ↦ ⟦-      r ↦ ⟦-        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,-        λ ⤍ L_fork-      ⟧-    ⟧+      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,+      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,+      λ ⤍ L_fork+    ⟧.φ   ⟧-result: 'Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ:r'+result: '⟦ if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧, φ ↦ if( FF-:Δ, 00-:Δ ) ⟧' protocol: |   𝕄(Φ.y):-    𝕄(Φ.y.r):-      𝕄(Φ.y.r.b):-        𝕄(Φ.y.r.b.φ):-          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-      𝔼(L_fork):-        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-        𝕄(Φ.a🌵1):-          𝕄(Φ.a🌵1.φ):-            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)-        𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-        𝑛3·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # [𝑛1, 𝑛2]-        𝑛·1·2 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛-        𝑛·1·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝕄(𝑛·1·2)+    𝔼(L_fork):+      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(ξ.b)+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }+      𝑛3·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # [𝑛1, 𝑛2]+      𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛+      𝑛·1·2 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝕄(𝑛·1·1)+    𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ )
+ test-resources/evaluate-packs/joins-a-branch-whose-phi-reads-its-own-object-when-lenient.yaml view
@@ -0,0 +1,39 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A fork whose one branch is an ordinary object: a formation with no λ, whose+# value sits in a φ that reads an attribute of the object itself. 𝕄 stops at+# that formation, so the branch and a bare symbol differ in shape and depth,+# and the join alone cannot pair them. An entry that says 'lenient: true'+# takes one more step before it gets stuck: it morphs the φ of that branch,+# fires what it comes to through 𝔼, the way 'morph' and 'evaluate' lines do,+# and joins once more, now two bare symbols (#1885).+symbolic: |+  - λ: L_eq+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      𝑛3: [𝑛1, 𝑛2]+    lenient: true+    𝑛: 𝑛3+location: 'Q.y'+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ξ.eq, eq ↦ ⟦ λ ⤍ L_eq ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎3:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := ⟦ φ ↦ eq, eq ↦ L_eq:λ ⟧  # 𝕄(ξ.a)+      𝑛2·1 := 𝜎1:λ:φ  # 𝕄(ξ.b)+      𝑛1·1 := L_eq:λ  # 𝕄(𝑛1.φ)+      𝔼(L_eq):  # 𝔼(𝑛1)+        𝑛·2·1 := 𝜎2:λ:φ  # 𝑛+        𝑛·2·2 := 𝜎2:λ:φ  # 𝕄(𝑛·2·1)+      𝑛1·1 := 𝑛·2·2  # 𝔼(𝑛1)+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎1:λ) }+      𝑛3·1 := 𝜎3:λ:φ  # [𝑛1, 𝑛2]+      𝑛·1·1 := 𝑛3·1  # 𝑛+      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-raises.yaml view
@@ -4,9 +4,9 @@ # yamllint disable rule:line-length # A fork one branch of which is a literal ⊥ is how a program spells "raise # unless the condition holds", and the other branch is a perfectly good value.-# The deep walk fires it, since that ⊥ is an argument the program wrote and no-# ξ.ρ the walk reduced to nothing, and the join says on which side of the-# condition the program raises and answers the other side (#1405).+# An 'evaluate' line fires the good branch and leaves ⊥ as it is (#1874), and+# the join says on which side of the condition the program raises and answers+# the other side (#1405). symbolic: |   - λ: L_number_gt     dataize:@@ -24,23 +24,21 @@     morph:       𝑛1: $.then       𝑛2: $.else-    symbolize:+    evaluate:       𝑛3: 𝑛1       𝑛4: 𝑛2     join:       𝑛5: [𝑛3, 𝑛4]     𝑛: 𝑛5 location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,     number(φ) ↦ ⟦       gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ ⊥ ).plus( 6 ) ⟧-result: |--  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧+    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ ⊥ ).plus( 6 ).φ ⟧+result: '𝜎3:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -72,38 +70,32 @@       𝕄(Φ.a🌵3):         𝑛·2·2 := 1         𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )-        𝕄(Φ.a🌵3.x):-          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵4):-            𝕄(Φ.a🌵4):-              𝑛·3·1 := 1-              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0-          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵5):-            𝕄(Φ.a🌵5):-              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )-              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0-          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)-          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-          𝕄(𝑛·3·5):-            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )-          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)-      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)+      𝑛1·2 := 𝑛·2·3  # 𝕄(ξ.then)       𝑛2·2 := ⊥  # 𝕄(ξ.else)-      𝑛3·2 := 𝑛1·2  # 𝑛1-      𝑛4·2 := ⊥  # 𝑛2+      𝔼(L_number_plus):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·3·1 := 1+            𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+          𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·3 := 2+            𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+          𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0+        𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)+        𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+        𝕄(𝑛·3·5):+          𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )+        𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)+      𝑛3·2 := 𝑛·3·7  # 𝔼(𝑛1)+      𝑛4·2 := ⊥  # 𝔼(𝑛2)       terminate(𝔻(𝜎1:λ), right)  # 𝑛4       𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]-      𝑛·2·6 := 𝑛5·2  # 𝑛-      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)+      𝑛·2·4 := 𝑛5·2  # 𝑛+      𝑛·2·5 := 𝑛5·2  # 𝕄(𝑛·2·4)     𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )     𝔼(L_number_plus):       𝔻(Φ.a🌵7):         𝕄(Φ.a🌵7):@@ -111,7 +103,7 @@       𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵8):         𝕄(Φ.a🌵8):-          𝑛·4·2 := Φ.number( φ ↦ 𝑛·0·6 )+          𝑛·4·2 := 6           𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)
test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-throws.yaml view
@@ -4,11 +4,11 @@ # yamllint disable rule:line-length # EO raises with a copy of an object whose λ function the file leaves out on # purpose, here 'L_throw', rather than with ⊥. The entry of the fork names it-# among its terminators, so the join takes the branch that comes to such a-# copy for one that raises, the way it takes ⊥, and answers the other branch.-# The walk reduces the message of that copy like any other code, firing-# 'L_number_plus' for it, and never fires 'L_throw', and the 'terminate' line-# names the meta of the reduced message (#1867).+# among its terminators, so its 'evaluate' line leaves that copy unfired+# (#1874), and the join takes the branch that comes to such a copy for one+# that raises, the way it takes ⊥, and answers the other branch (#1865). The+# 'terminate' line names the message of the copy as the program wrote it,+# since nothing fires 'L_number_plus' for it (#1867). symbolic: |   - λ: L_number_gt     dataize:@@ -26,7 +26,7 @@     morph:       𝑛1: $.then       𝑛2: $.else-    symbolize:+    evaluate:       𝑛3: 𝑛1       𝑛4: 𝑛2     join:@@ -34,7 +34,6 @@     terminators: [L_throw]     𝑛: 𝑛5 location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     error(message) ↦ ⟦ λ ⤍ L_throw ⟧,@@ -42,9 +41,8 @@     number(φ) ↦ ⟦       gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ Φ.error( message ↦ 7.plus( 8 ) ) ).plus( 6 ) ⟧-result: |--  ⟦ φ ↦ 𝜎4:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧+    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ Φ.error( message ↦ 7.plus( 8 ) ) ).plus( 6 ).φ ⟧+result: '𝜎3:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -76,77 +74,46 @@       𝕄(Φ.a🌵3):         𝑛·2·2 := 1         𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )-        𝕄(Φ.a🌵3.x):-          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵4):-            𝕄(Φ.a🌵4):-              𝑛·3·1 := 1-              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0-          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵5):-            𝕄(Φ.a🌵5):-              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )-              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0-          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)-          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-          𝕄(𝑛·3·5):-            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )-          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)-      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)-      𝕄(Φ.a🌵6):-        𝑛·2·6 := Φ.error( message ↦ 7.plus( 8 ) )-        𝕄(Φ.a🌵6.message):-          𝑛·2·7 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-          𝑛·2·8 := Φ.number( φ ↦ 𝑛·2·7 )-          𝑛·2·9 := Φ.number( φ ↦ 𝑛·2·7 )-          𝑛·2·10 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-          𝑛·2·11 := Φ.number( φ ↦ 𝑛·2·10 )-          𝑛·2·12 := Φ.number( φ ↦ 𝑛·2·7 )-          𝑛·2·13 := 𝑛·2·12.plus( x ↦ 𝑛·2·11 )-          𝔼(L_number_plus):-            𝔻(Φ.a🌵7):-              𝕄(Φ.a🌵7):-                𝑛·4·1 := Φ.number( φ ↦ 𝑛·2·7 )-                𝑛·4·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-              𝛿·4·1 := 40-1C-00-00-00-00-00-00  # 7.0-            𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)-            𝔻(Φ.a🌵8):-              𝕄(Φ.a🌵8):-                𝑛·4·3 := Φ.number( φ ↦ 𝑛·2·10 )-                𝑛·4·4 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-              𝛿·4·2 := 40-20-00-00-00-00-00-00  # 8.0-            𝛿2·4 := 𝛿·4·2  # 𝔻(ξ.x)-            𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛-            𝕄(𝑛·4·5):-              𝑛·4·6 := Φ.number( φ ↦ 𝜎3:λ )-            𝑛·4·7 := 𝑛·4·6  # 𝕄(𝑛·4·5)-      𝑛2·2 := 𝑛·2·6  # 𝕄(ξ.else)-      𝑛3·2 := 𝑛1·2  # 𝑛1-      𝑛4·2 := 𝑛·2·6  # 𝑛2-      terminate(𝔻(𝜎1:λ), right, 𝑛·4·6)  # 𝑛4+      𝑛1·2 := 𝑛·2·3  # 𝕄(ξ.then)+      𝕄(Φ.a🌵4):+        𝑛·2·4 := Φ.error( message ↦ 7.plus( 8 ) )+      𝑛2·2 := 𝑛·2·4  # 𝕄(ξ.else)+      𝔼(L_number_plus):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·3·1 := 1+            𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+          𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·3 := 2+            𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+          𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0+        𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)+        𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+        𝕄(𝑛·3·5):+          𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )+        𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)+      𝑛3·2 := 𝑛·3·7  # 𝔼(𝑛1)+      𝑛4·2 := 𝑛·2·4  # 𝔼(𝑛2)+      terminate(𝔻(𝜎1:λ), right, 7.plus( 8 ))  # 𝑛4       𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]-      𝑛·2·14 := 𝑛5·2  # 𝑛-      𝑛·2·15 := 𝑛5·2  # 𝕄(𝑛·2·14)+      𝑛·2·5 := 𝑛5·2  # 𝑛+      𝑛·2·6 := 𝑛5·2  # 𝕄(𝑛·2·5)     𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )     𝔼(L_number_plus):-      𝔻(Φ.a🌵9):-        𝕄(Φ.a🌵9):-          𝑛·5·1 := Φ.number( φ ↦ 𝜎2:λ )-      𝛿1·5 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵10):-        𝕄(Φ.a🌵10):-          𝑛·5·2 := Φ.number( φ ↦ 𝑛·0·6 )-          𝑛·5·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-        𝛿·5·1 := 40-18-00-00-00-00-00-00  # 6.0-      𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)-      𝑛·5·4 := Φ.number( φ ↦ 𝜎4:λ )  # 𝑛-      𝕄(𝑛·5·4):-        𝑛·5·5 := Φ.number( φ ↦ 𝜎4:λ )-      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)+      𝔻(Φ.a🌵7):+        𝕄(Φ.a🌵7):+          𝑛·4·1 := Φ.number( φ ↦ 𝜎2:λ )+      𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵8):+        𝕄(Φ.a🌵8):+          𝑛·4·2 := 6+          𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )+        𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0+      𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)+      𝑛·4·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+      𝕄(𝑛·4·4):+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )+      𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)
test-resources/evaluate-packs/joins-a-fork-that-raises-on-the-left.yaml view
@@ -4,8 +4,8 @@ # yamllint disable rule:line-length # The same fork with its branches given by position and the ⊥ on the other # side: a positional ⊥ is as much an argument the program wrote as a named one,-# so the walk fires the fork all the same, and the join says the program raises-# on the left and answers the right (#1405).+# so the fork fires all the same, and the join says the program raises on the+# left and answers the right (#1405). symbolic: |   - λ: L_number_gt     dataize:@@ -23,23 +23,21 @@     morph:       𝑛1: $.then       𝑛2: $.else-    symbolize:+    evaluate:       𝑛3: 𝑛1       𝑛4: 𝑛2     join:       𝑛5: [𝑛3, 𝑛4]     𝑛: 𝑛5 location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,     number(φ) ↦ ⟦       gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( ⊥, 1.plus( 2 ) ).plus( 6 ) ⟧-result: |--  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧+    @ ↦ 5.gt( 0 ).if( ⊥, 1.plus( 2 ) ).plus( 6 ).φ ⟧+result: '𝜎3:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -72,37 +70,31 @@       𝕄(Φ.a🌵4):         𝑛·2·2 := 1         𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )-        𝕄(Φ.a🌵4.x):-          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵5):-            𝕄(Φ.a🌵5):-              𝑛·3·1 := 1-              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0-          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵6):-            𝕄(Φ.a🌵6):-              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )-              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0-          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)-          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-          𝕄(𝑛·3·5):-            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )-          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)-      𝑛2·2 := 𝑛·3·7  # 𝕄(ξ.else)-      𝑛3·2 := ⊥  # 𝑛1-      𝑛4·2 := 𝑛2·2  # 𝑛2+      𝑛2·2 := 𝑛·2·3  # 𝕄(ξ.else)+      𝑛3·2 := ⊥  # 𝔼(𝑛1)+      𝔼(L_number_plus):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·3·1 := 1+            𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+          𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·3 := 2+            𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+          𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0+        𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)+        𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+        𝕄(𝑛·3·5):+          𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )+        𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)+      𝑛4·2 := 𝑛·3·7  # 𝔼(𝑛2)       terminate(𝔻(𝜎1:λ), left)  # 𝑛3       𝑛5·2 := 𝑛4·2  # [𝑛3, 𝑛4]-      𝑛·2·6 := 𝑛5·2  # 𝑛-      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)+      𝑛·2·4 := 𝑛5·2  # 𝑛+      𝑛·2·5 := 𝑛5·2  # 𝕄(𝑛·2·4)     𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )     𝔼(L_number_plus):       𝔻(Φ.a🌵7):         𝕄(Φ.a🌵7):@@ -110,7 +102,7 @@       𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵8):         𝕄(Φ.a🌵8):-          𝑛·4·2 := Φ.number( φ ↦ 𝑛·0·6 )+          𝑛·4·2 := 6           𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)
+ test-resources/evaluate-packs/joins-a-fork-whose-branch-an-evaluate-line-fired.yaml view
@@ -0,0 +1,39 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# 𝕄 leaves a formation carrying a λ as it is, so a branch of a fork that+# morphs to a ready call of 'L_eq' has a shape the other branch, a plain+# decorator, does not, and the two cannot be joined. An 'evaluate' line asks+# 𝔼 of each branch once: the decorator carries no λ and stays as it is, the+# call fires and answers a decorator too, and the two join (#1874).+symbolic: |+  - λ: L_eq+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      𝑛5: [𝑛3, 𝑛4]+    𝑛: 𝑛5+location: 'Q.y'+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ λ ⤍ L_eq ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎3:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)+      𝑛2·1 := L_eq:λ  # 𝕄(ξ.b)+      𝑛3·1 := 𝑛1·1  # 𝔼(𝑛1)+      𝔼(L_eq):  # 𝔼(𝑛2)+        𝑛·2·1 := 𝜎2:λ:φ  # 𝑛+        𝑛·2·2 := 𝜎2:λ:φ  # 𝕄(𝑛·2·1)+      𝑛4·1 := 𝑛·2·2  # 𝔼(𝑛2)+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }+      𝑛5·1 := 𝜎3:λ:φ  # [𝑛3, 𝑛4]+      𝑛·1·1 := 𝑛5·1  # 𝑛+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/joins-a-negated-bool-whose-literal-a-rewrite-brought-to-one-shape.yaml view
@@ -7,8 +7,11 @@ # while what the fork answers is a bool carrying a symbol. The two spellings # are one value, and the 'rewrite' block of the fork says so, turning either # literal into the shape an answer has with the datum it stands for, which-# 'symbolize' then stands into a known symbol, so the join pairs them and the-# root dataizes the fork of 'not' over the fork of '>' (#1409).+# 'symbolize' then stands into a known symbol, so the join pairs them (#1409).+# Morphing leaves the fork of 'not' as a call, so the entry fires it with an+# 'evaluate' line (#1874). The dispatch of φ that ends 'e1' fires the entry+# and then the root it answers, and the root dataizes the fork of 'not' over+# the fork of '>'. symbolic: |   - λ: L_number_gt     dataize:@@ -20,7 +23,9 @@       𝛿1: $.n     morph:       𝑛1: $.v.φ-    𝑛: Φ.l🌵.root( 𝑛1 )+    evaluate:+      𝑛2: 𝑛1+    𝑛: Φ.l🌵.root( 𝑛2 )   - λ: L_root     dataize:       𝛿1: $.v@@ -53,11 +58,7 @@     join:       𝑛7: [𝑛5, 𝑛6]     𝑛: 𝑛7-location: 'Q.l🌵'-deep: true-partial: true-acyclic: proven-steps: 32+location: 'Q.l🌵.e1' input: |   ⟦     number ↦ ⟦ φ ↦ ∅, gt ↦ ⟦ b ↦ ∅, λ ⤍ L_number_gt, ρ ↦ ∅ ⟧ ⟧,@@ -67,97 +68,84 @@     false ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.right ⟧ ) ⟧,     guard ↦ ⟦ x ↦ ∅, φ ↦ ξ.x.gt( b ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00-00-00-00-00-00-00-00 ⟧ ) ) ).not ⟧,     l🌵 ↦ ⟦ mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧, root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.guard( x ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) ) ⟧+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.guard( x ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) ).φ ⟧   ⟧ protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.guard( x ↦ 𝑛·0·5 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.guard( x ↦ 𝑛·0·5 )-          𝑛·1·4 := Φ.number( φ ↦ 𝑛·0·4 )-          𝑛·1·5 := Φ.number( φ ↦ 𝑛·0·4 )-          𝑛·1·6 := 𝑛·1·5.gt( b ↦ 0 )-          𝔼(L_number_gt):-            𝔻(Φ.a🌵2):-              𝕄(Φ.a🌵2):-                𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·4 )-                𝑛·2·2 := Φ.bytes( φ ↦ 𝜎1:λ )-            𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-            𝔻(Φ.a🌵3):-              𝕄(Φ.a🌵3):-                𝑛·2·3 := 0-                𝑛·2·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )-              𝛿·2·1 := 00-00-00-00-00-00-00-00  # 0.0-            𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.b)-            𝑛·2·5 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝑛-            𝑛·2·6 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(𝑛·2·5)-          𝑛·1·7 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )-          𝑛·1·8 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ Φ.false )-          𝑛·1·9 := 𝑛·1·8( right ↦ Φ.true )-          𝔼(L_fork):-            𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)-            𝕄(Φ.a🌵5):-              𝕄(Φ.a🌵5.φ):-                𝑛·3·1 := Φ.bool( if(left, right) ↦ right:φ )-            𝑛1·3 := 𝑛·3·1:φ  # 𝕄(ξ.left)-            𝕄(Φ.a🌵6):-              𝕄(Φ.a🌵6.φ):-                𝑛·3·2 := Φ.bool( if(left, right) ↦ left:φ )-            𝑛2·3 := 𝑛·3·2:φ  # 𝕄(ξ.right)-            𝑛3·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 00-:Δ ⟧ ):φ  # 𝑛1-            𝑛4·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ FF-:Δ ⟧ ):φ  # 𝑛2-            𝔻(𝜎3:λ) == 00--            𝑛5·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛3-            𝔻(𝜎4:λ) == FF--            𝑛6·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝑛4-            𝔻(𝜎5:λ) ∈ { 𝔻(𝜎3:λ), 𝔻(𝜎4:λ) }-            𝑛7·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # [𝑛5, 𝑛6]-            𝑛·3·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝑛-            𝑛·3·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝕄(𝑛·3·3)-        𝑛1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝕄(ξ.v.φ)-        𝑛·1·10 := Φ.l🌵.root( Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )  # 𝑛-        𝕄(𝑛·1·10):-          𝑛·1·11 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )-        𝑛·1·12 := 𝑛·1·11  # 𝕄(𝑛·1·10)-      𝔼(L_root):-        𝔻(Φ.a🌵7):-          𝕄(Φ.a🌵7):-            𝑛·4·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )-          𝑛·4·2 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ Φ.bytes( φ ↦ FF-:Δ ) )-          𝑛·4·3 := 𝑛·4·2( right ↦ Φ.bytes( φ ↦ 00-:Δ ) )-          𝔼(L_fork):-            𝛿1·5 := 𝔻(𝜎5:λ)  # 𝔻(ξ.φ)-            𝕄(Φ.a🌵9):-              𝑛·5·1 := Φ.bytes( φ ↦ FF-:Δ )-            𝑛1·5 := 𝑛·5·1  # 𝕄(ξ.left)-            𝕄(Φ.a🌵10):-              𝑛·5·2 := Φ.bytes( φ ↦ 00-:Δ )-            𝑛2·5 := 𝑛·5·2  # 𝕄(ξ.right)-            𝑛3·5 := 𝑛·5·1  # 𝑛1-            𝑛4·5 := 𝑛·5·2  # 𝑛2-            𝔻(𝜎6:λ) == FF--            𝑛5·5 := 𝜎6:λ:φ  # 𝑛3-            𝔻(𝜎7:λ) == 00--            𝑛6·5 := 𝜎7:λ:φ  # 𝑛4-            𝔻(𝜎8:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎7:λ) }-            𝑛7·5 := 𝜎8:λ:φ  # [𝑛5, 𝑛6]-            𝑛·5·3 := 𝑛7·5  # 𝑛-            𝑛·5·4 := 𝑛7·5  # 𝕄(𝑛·5·3)-        𝛿1·4 := 𝔻(𝜎8:λ)  # 𝔻(ξ.v)-        𝑛·4·4 := ⟦⟧  # 𝑛-        𝑛·4·5 := ⟦⟧  # 𝕄(𝑛·4·4)+  𝕄(Φ.l🌵.e1):+    𝑛·0·1 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 1 )+    𝑛·0·2 := 𝑛·0·1( v ↦ Φ.guard( x ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) ) )+    𝔼(L_entry):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 1+          𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)+      𝕄(Φ.a🌵1):+        𝑛·1·3 := Φ.guard( x ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) )+        𝑛·1·4 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )+        𝑛·1·5 := 𝑛·1·4.gt( b ↦ 0 )+        𝔼(L_number_gt):+          𝔻(Φ.a🌵2):+            𝕄(Φ.a🌵2):+              𝑛·2·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )+              𝑛·2·2 := Φ.bytes( φ ↦ 𝜎1:λ )+          𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+          𝔻(Φ.a🌵3):+            𝕄(Φ.a🌵3):+              𝑛·2·3 := 0+              𝑛·2·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )+            𝛿·2·1 := 00-00-00-00-00-00-00-00  # 0.0+          𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.b)+          𝑛·2·5 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝑛+          𝑛·2·6 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(𝑛·2·5)+        𝑛·1·6 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )+        𝑛·1·7 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ Φ.false )+        𝑛·1·8 := 𝑛·1·7( right ↦ Φ.true )+      𝑛1·1 := 𝑛·1·8  # 𝕄(ξ.v.φ)+      𝔼(L_fork):  # 𝔼(𝑛1)+        𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)+        𝑛1·3 := Φ.bool( if(left, right) ↦ right:φ ):φ  # 𝕄(ξ.left)+        𝑛2·3 := Φ.bool( if(left, right) ↦ left:φ ):φ  # 𝕄(ξ.right)+        𝑛3·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 00-:Δ ⟧ ):φ  # 𝑛1+        𝑛4·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ FF-:Δ ⟧ ):φ  # 𝑛2+        𝔻(𝜎3:λ) == 00-+        𝑛5·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛3+        𝔻(𝜎4:λ) == FF-+        𝑛6·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝑛4+        𝔻(𝜎5:λ) ∈ { 𝔻(𝜎3:λ), 𝔻(𝜎4:λ) }+        𝑛7·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # [𝑛5, 𝑛6]+        𝑛·3·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝑛+        𝑛·3·2 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝕄(𝑛·3·1)+      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝔼(𝑛1)+      𝑛·1·9 := Φ.l🌵.root( Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )  # 𝑛+      𝕄(𝑛·1·9):+        𝑛·1·10 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )+      𝑛·1·11 := 𝑛·1·10  # 𝕄(𝑛·1·9)+    𝔼(L_root):+      𝔻(Φ.a🌵7):+        𝕄(Φ.a🌵7):+          𝑛·4·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )+        𝑛·4·2 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ Φ.bytes( φ ↦ FF-:Δ ) )+        𝑛·4·3 := 𝑛·4·2( right ↦ Φ.bytes( φ ↦ 00-:Δ ) )+        𝔼(L_fork):+          𝛿1·5 := 𝔻(𝜎5:λ)  # 𝔻(ξ.φ)+          𝕄(Φ.a🌵9):+            𝑛·5·1 := Φ.bytes( φ ↦ FF-:Δ )+          𝑛1·5 := 𝑛·5·1  # 𝕄(ξ.left)+          𝕄(Φ.a🌵10):+            𝑛·5·2 := Φ.bytes( φ ↦ 00-:Δ )+          𝑛2·5 := 𝑛·5·2  # 𝕄(ξ.right)+          𝑛3·5 := 𝑛·5·1  # 𝑛1+          𝑛4·5 := 𝑛·5·2  # 𝑛2+          𝔻(𝜎6:λ) == FF-+          𝑛5·5 := 𝜎6:λ:φ  # 𝑛3+          𝔻(𝜎7:λ) == 00-+          𝑛6·5 := 𝜎7:λ:φ  # 𝑛4+          𝔻(𝜎8:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎7:λ) }+          𝑛7·5 := 𝜎8:λ:φ  # [𝑛5, 𝑛6]+          𝑛·5·3 := 𝑛7·5  # 𝑛+          𝑛·5·4 := 𝑛7·5  # 𝕄(𝑛·5·3)+      𝛿1·4 := 𝔻(𝜎8:λ)  # 𝔻(ξ.v)+      𝑛·4·4 := ⟦⟧  # 𝑛+      𝑛·4·5 := ⟦⟧  # 𝕄(𝑛·4·4)
test-resources/evaluate-packs/joins-the-branches-a-program-forks-into.yaml view
@@ -7,8 +7,10 @@ # it: the branch point stays in the program, since the 'plus( 6 )' below the # fork reads the symbol the two branches were joined into and not the value of # either. The line saying what that symbol stands for is what ties it to the-# two, and the firing of a branch stands one level deeper, under the fork that-# asked for it.+# two. Morphing leaves each branch as a call of 'plus', and an 'evaluate' line+# of the fork fires it (#1874), so the firing of a branch stands one level+# deeper, under the fork that asked for it. The dispatch of φ that ends the+# program is what fires the 'plus( 6 )'. symbolic: |   - λ: L_number_gt     dataize:@@ -26,23 +28,21 @@     morph:       𝑛1: $.then       𝑛2: $.else-    symbolize:+    evaluate:       𝑛3: 𝑛1       𝑛4: 𝑛2     join:       𝑛5: [𝑛3, 𝑛4]     𝑛: 𝑛5 location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,     number(φ) ↦ ⟦       gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ).plus( 6 ) ⟧-result: |--  ⟦ φ ↦ 𝜎5:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧+    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ).plus( 6 ).φ ⟧+result: '𝜎5:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -74,61 +74,52 @@       𝕄(Φ.a🌵3):         𝑛·2·2 := 1         𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )-        𝕄(Φ.a🌵3.x):-          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵4):-            𝕄(Φ.a🌵4):-              𝑛·3·1 := 1-              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0-          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵5):-            𝕄(Φ.a🌵5):-              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )-              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0-          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)-          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-          𝕄(𝑛·3·5):-            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )-          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)-      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)-      𝕄(Φ.a🌵6):-        𝑛·2·6 := 3-        𝑛·2·7 := 𝑛·2·6.plus( x ↦ 4 )-        𝕄(Φ.a🌵6.x):-          𝑛·2·8 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )-          𝑛·2·9 := Φ.number( φ ↦ 𝑛·2·8 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵7):-            𝕄(Φ.a🌵7):-              𝑛·4·1 := 3-              𝑛·4·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )-            𝛿·4·1 := 40-08-00-00-00-00-00-00  # 3.0-          𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵8):-            𝕄(Φ.a🌵8):-              𝑛·4·3 := Φ.number( φ ↦ 𝑛·2·8 )-              𝑛·4·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )-            𝛿·4·2 := 40-10-00-00-00-00-00-00  # 4.0-          𝛿2·4 := 𝛿·4·2  # 𝔻(ξ.x)-          𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛-          𝕄(𝑛·4·5):-            𝑛·4·6 := Φ.number( φ ↦ 𝜎3:λ )-          𝑛·4·7 := 𝑛·4·6  # 𝕄(𝑛·4·5)-      𝑛2·2 := 𝑛·4·7  # 𝕄(ξ.else)-      𝑛3·2 := 𝑛1·2  # 𝑛1-      𝑛4·2 := 𝑛2·2  # 𝑛2+      𝑛1·2 := 𝑛·2·3  # 𝕄(ξ.then)+      𝕄(Φ.a🌵4):+        𝑛·2·4 := 3+        𝑛·2·5 := 𝑛·2·4.plus( x ↦ 4 )+      𝑛2·2 := 𝑛·2·5  # 𝕄(ξ.else)+      𝔼(L_number_plus):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵5):+          𝕄(Φ.a🌵5):+            𝑛·3·1 := 1+            𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+          𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·3 := 2+            𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+          𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0+        𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)+        𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛+        𝕄(𝑛·3·5):+          𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )+        𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)+      𝑛3·2 := 𝑛·3·7  # 𝔼(𝑛1)+      𝔼(L_number_plus):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵7):+          𝕄(Φ.a🌵7):+            𝑛·4·1 := 3+            𝑛·4·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )+          𝛿·4·1 := 40-08-00-00-00-00-00-00  # 3.0+        𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵8):+          𝕄(Φ.a🌵8):+            𝑛·4·3 := 4+            𝑛·4·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )+          𝛿·4·2 := 40-10-00-00-00-00-00-00  # 4.0+        𝛿2·4 := 𝛿·4·2  # 𝔻(ξ.x)+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+        𝕄(𝑛·4·5):+          𝑛·4·6 := Φ.number( φ ↦ 𝜎3:λ )+        𝑛·4·7 := 𝑛·4·6  # 𝕄(𝑛·4·5)+      𝑛4·2 := 𝑛·4·7  # 𝔼(𝑛2)       𝔻(𝜎4:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎3:λ) }       𝑛5·2 := ⟦ φ ↦ 𝜎4:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧  # [𝑛3, 𝑛4]-      𝑛·2·10 := 𝑛5·2  # 𝑛-      𝑛·2·11 := 𝑛5·2  # 𝕄(𝑛·2·10)+      𝑛·2·6 := 𝑛5·2  # 𝑛+      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)     𝑛·0·5 := Φ.number( φ ↦ 𝜎4:λ ).plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )     𝔼(L_number_plus):       𝔻(Φ.a🌵9):         𝕄(Φ.a🌵9):@@ -136,7 +127,7 @@       𝛿1·5 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵10):         𝕄(Φ.a🌵10):-          𝑛·5·2 := Φ.number( φ ↦ 𝑛·0·6 )+          𝑛·5·2 := 6           𝑛·5·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·5·1 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)
test-resources/evaluate-packs/keeps-a-cut-met-inside-a-firing-for-the-next-firing.yaml view
@@ -3,35 +3,45 @@ --- # yamllint disable rule:line-length # The memo of '--acyclic=plausible' keeps a cut the way it keeps an answer.-# The firing under 'x' brings its operand down through 𝔻, which goes round-# 'cyc' until the mode cuts it, so the firing never answers; 'y' spells the-# very same formation, and before #1480 its firing walked all the way down to-# the same cut again, since only an answer was kept. Now the second firing-# takes the cut the first one met: it is written at its own site with the+# 'L_pair' reduces 'x' and then 'y', and both spell the very same formation+# of 'L_outer'. The firing under 'x' brings its operand down through 𝔻,+# which goes round 'cyc' until the mode cuts it, so the firing never+# answers. Before #1480 the firing under 'y' went all the way down to the+# same cut again, since only an answer was kept. Now the second firing takes+# the cut the first one met: it is written at its own site with the # formation the cut carried, and nothing under it is reduced. symbolic: |   - λ: L_outer     dataize:       𝛿1: ξ.arg     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.y+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+location: 'Q.p' acyclic: plausible-steps: 64 input: >   ⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧,-    x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧,-    y ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧ ⟧+    p ↦ ⟦+      x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ,+      y ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ,+      λ ⤍ L_pair ⟧.φ ⟧ protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝕄(Φ.x.arg):-        𝑛·0·1 := Φ.cyc( x ↦ ⟦⟧ )-      𝔼(L_outer):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )-            𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )-          looped(𝑛·1·2)  # plausible-    𝕄(Φ.y):-      𝔼(L_outer):-        looped(𝑛·1·2)  # 𝕄(Φ.y), plausible+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝔼(L_outer):+          𝔻(Φ.a🌵1):+            𝕄(Φ.a🌵1):+              𝑛·2·1 := Φ.cyc( x ↦ ⟦⟧ )+              𝑛·2·2 := Φ.cyc( x ↦ ⟦⟧ )+            looped(𝑛·2·2)  # plausible+      𝑛1·1 := ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.x)+      𝕄(Φ.a🌵2):+        𝔼(L_outer):+          looped(𝑛·2·2)  # 𝕄(Φ.a🌵2), plausible+      𝑛2·1 := ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.y)+      𝑛·1·1 := 𝜎1:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎1:λ:φ  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/keeps-a-stuck-firing-for-the-next-firing.yaml view
@@ -3,31 +3,43 @@ --- # yamllint disable rule:line-length # The memo of '--acyclic=plausible' keeps a stuck firing the way it keeps a-# cut. The firing under 'x' brings its operand down through 𝔻, which gets-# stuck on 'L_none', so the firing never answers; 'y' spells the very same-# formation, and before #1493 its firing reduced its operand and got stuck on-# 'L_none' again, since a stuck firing kept nothing. Now the second firing-# takes the failure the first one met: it is written at its own site and-# nothing under it is reduced.+# cut. 'L_pair' reduces 'x' and then 'y', and both spell the very same+# formation of 'L_outer'. The firing under 'x' brings its operand down+# through 𝔻, which gets stuck on 'L_none', so the firing never answers and+# '--partial' parks it. Before #1493 the firing under 'y' reduced its operand+# and got stuck on 'L_none' again, since a stuck firing kept nothing. Now the+# second firing takes the failure the first one met: it is written at its own+# site and nothing under it is reduced. symbolic: |   - λ: L_outer     dataize:       𝛿1: ξ.arg     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.y+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+location: 'Q.p' partial: true acyclic: plausible-steps: 64 input: >-  ⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,-    y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧ ⟧+  ⟦ p ↦ ⟦+      x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,+      y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,+      λ ⤍ L_pair ⟧.φ ⟧ protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝔼(L_outer):-        𝔻(Φ.a🌵0):-          unanswered(L_none)  # 𝔻(L_none:λ)-        stuck(L_none)-    𝕄(Φ.y):-      𝔼(L_outer):-        stall(L_none)+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝔼(L_outer):+          𝔻(Φ.a🌵1):+            unanswered(L_none)  # 𝔻(L_none:λ)+          stuck(L_none)+      𝑛1·1 := ⟦ arg ↦ L_none:λ, λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.x)+      𝕄(Φ.a🌵2):+        𝔼(L_outer):+          stall(L_none)+      𝑛2·1 := ⟦ arg ↦ L_none:λ, λ ⤍ L_outer ⟧.φ  # 𝕄(ξ.y)+      𝑛·1·1 := 𝜎1:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎1:λ:φ  # 𝕄(𝑛·1·1)
+ test-resources/evaluate-packs/keeps-a-terminated-branch-an-evaluate-line-meets.yaml view
@@ -0,0 +1,31 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A branch that morphs to ⊥ has nothing to fire, so an 'evaluate' line binds+# ⊥ as it is, and the join takes it as the branch that raises (#1874).+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      𝑛5: [𝑛3, 𝑛4]+    𝑛: 𝑛5+location: 'Q.y'+input: '⟦ y ↦ ⟦ a ↦ ⊥, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎1:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := ⊥  # 𝕄(ξ.a)+      𝑛2·1 := 𝜎1:λ:φ  # 𝕄(ξ.b)+      𝑛3·1 := ⊥  # 𝔼(𝑛1)+      𝑛4·1 := 𝑛2·1  # 𝔼(𝑛2)+      terminate(left)  # 𝑛3+      𝑛5·1 := 𝑛4·1  # [𝑛3, 𝑛4]+      𝑛·1·1 := 𝑛5·1  # 𝑛+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/keeps-the-name-of-an-object-the-walk-computed-inside.yaml
@@ -1,64 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The '--deep' walk stands its answers in the place of what it computed, inside-# the bindings of the object it walks, and makes no new object by doing so-# (#1757). The operand of 'L_entry' is the object 'Φ.dbl' made, named 𝑛·1·1 by-# its 'applied' line, and the walk computes its 'φ' and its 'v' before 𝕄 hands-# it back. It is still 𝑛·1·1 then: in the line binding the operand, in the term-# the entry wrote, in the argument 'root' was applied to, and in the formation-# 𝔻 gets into when 'L_root' brings that argument down.-symbolic: |-  - λ: L_entry-    morph:-      𝑛1: $.x-    𝑛: Φ.root( v ↦ 𝑛1 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_twice-    dataize:-      𝛿1: $.ρ-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.y'-deep: true-input: >-  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧ ⟧,-    dbl ↦ ⟦ v ↦ ∅, φ ↦ ξ.v.twice ⟧,-    root(v) ↦ ⟦ λ ⤍ L_root ⟧,-    y ↦ ⟦ λ ⤍ L_entry, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ) ⟧ ⟧-result: '𝜎2:λ'-protocol: |-  𝕄(Φ.y):-    𝕄(Φ.y.x):-      𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )-      𝑛·0·2 := Φ.dbl( v ↦ 𝑛·0·1 )-    𝔼(L_entry):-      𝕄(Φ.a🌵0):-        𝑛·1·1 := Φ.dbl( v ↦ 𝑛·0·1 )-        𝕄(Φ.a🌵0.v):-          𝑛·1·2 := Φ.num( φ ↦ 2A-:Δ )-        𝕄(Φ.a🌵0.φ):-          𝑛·1·3 := Φ.num( φ ↦ 2A-:Δ )-          𝑛·1·4 := Φ.num( φ ↦ 2A-:Δ )-          𝔼(L_twice):-            𝔻(Φ.a🌵1):-              𝕄(Φ.a🌵1):-                𝑛·2·1 := Φ.num( φ ↦ 2A-:Δ )-              𝛿·2·1 := 2A--            𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-            𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛-            𝕄(𝑛·2·2):-              𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )-            𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)-      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)-      𝑛·1·5 := Φ.root( v ↦ 𝑛·1·1 )  # 𝑛-      𝕄(𝑛·1·5):-        𝑛·1·6 := Φ.root( v ↦ 𝑛·1·1 )-      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-    𝔼(L_root):-      𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)-      𝑛·3·1 := 𝜎2:λ  # 𝑛-      𝑛·3·2 := 𝜎2:λ  # 𝕄(𝑛·3·1)
− test-resources/evaluate-packs/leaves-a-method-of-a-copy-without-rho-unwalked.yaml
@@ -1,78 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The same method nobody dispatched, in a copy of an object that declares no-# ρ, the way eo-runtime spells 'text': 'skip' drops the ρ the dispatch hands-# the copy, so the copy has no ρ of its own, yet 'length' still declares one-# and still waits for its receiver. The deep walk hands it back as it was-# written instead of firing 'L_dataized' on a ⊥ it stands on (#1414).-symbolic: |-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_entry-    dataize:-      𝛿1: $.n-    morph:-      𝑛1: $.v.φ-    𝑛: Φ.l🌵.root( 𝑛1 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧-location: 'Q.l🌵'-deep: true-partial: true-acyclic: proven-steps: 32-input: |-  ⟦-    number ↦ ⟦ φ ↦ ∅ ⟧,-    bytes ↦ ⟦ φ ↦ ∅, as-bytes ↦ ⟦ φ ↦ ξ.ρ, ρ ↦ ∅ ⟧ ⟧,-    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,-    text ↦ ⟦ φ ↦ ∅, length ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.φ ).as-bytes, ρ ↦ ∅ ⟧ ⟧,-    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,-    l🌵 ↦ ⟦-      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,-      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )-    ⟧-  ⟧-protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )-          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )-          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )-          𝕄(Φ.a🌵1.φ):-            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)-        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛-        𝕄(𝑛·1·7):-          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )-        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)-      𝔼(L_root):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)-        𝑛·2·2 := ⟦⟧  # 𝑛-        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
− test-resources/evaluate-packs/leaves-a-rho-void-formation-unwalked.yaml
@@ -1,78 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A method nobody dispatched is a formation whose ρ is still void: 'length' of-# the copy of 'text' the entry of 'L_entry' morphs waits for a dispatch to bind-# its ρ. The deep walk hands it back as it was written instead of entering it,-# since the entry of 'L_dataized' inside it would dataize 'ξ.ρ.φ', which stands-# for ⊥, and that ⊥ would end the whole run even under '--partial' (#1397).-symbolic: |-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_entry-    dataize:-      𝛿1: $.n-    morph:-      𝑛1: $.v.φ-    𝑛: Φ.l🌵.root( 𝑛1 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧-location: 'Q.l🌵'-deep: true-partial: true-acyclic: proven-steps: 32-input: |-  ⟦-    number ↦ ⟦ φ ↦ ∅ ⟧,-    bytes ↦ ⟦ φ ↦ ∅, as-bytes ↦ ⟦ φ ↦ ξ.ρ, ρ ↦ ∅ ⟧ ⟧,-    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,-    text ↦ ⟦ φ ↦ ∅, length ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.φ ).as-bytes, ρ ↦ ∅ ⟧, ρ ↦ ∅ ⟧,-    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,-    l🌵 ↦ ⟦-      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,-      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )-    ⟧-  ⟧-protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )-          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )-          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )-          𝕄(Φ.a🌵1.φ):-            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)-        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛-        𝕄(𝑛·1·7):-          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )-        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)-      𝔼(L_root):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)-        𝑛·2·2 := ⟦⟧  # 𝑛-        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
+ test-resources/evaluate-packs/leaves-a-terminator-an-evaluate-line-meets-unfired.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A branch that raises through a terminator of the entry is not fired by an+# 'evaluate' line, since no entry answers 'L_throw': the line leaves it as it+# is, and the join takes it as the branch that raises (#1874).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      𝑛5: [𝑛3, 𝑛4]+    terminators: [L_throw]+    𝑛: 𝑛5+location: 'Q.y'+input: '⟦ y ↦ ⟦ ρ ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ λ ⤍ L_throw ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎1:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝔻(Φ.a🌵0):+        𝛿·1·1 := 01-+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)+      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)+      𝑛2·1 := L_throw:λ  # 𝕄(ξ.b)+      𝑛3·1 := 𝑛1·1  # 𝔼(𝑛1)+      𝑛4·1 := L_throw:λ  # 𝔼(𝑛2)+      terminate(01-, right)  # 𝑛4+      𝑛5·1 := 𝑛3·1  # [𝑛3, 𝑛4]+      𝑛·1·1 := 𝑛5·1  # 𝑛+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/leaves-an-abstract-formation-unwalked.yaml
@@ -1,93 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A method nobody applied is an abstract formation, one holding a void of its-# own: 'at' of the copy of 'text' the entry of 'L_entry' morphs waits for 'i'.-# The deep walk hands it back as it was written instead of entering it, since-# its body is parametric: the entry of 'L_dataized' inside it would dataize-# 'ξ.i', which stands for nothing, and the ⊥ it reaches would end the whole run-# even under '--partial' (#1393).-symbolic: |-  - λ: L_number_gt-    dataize:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )-  - λ: L_dataized-    dataize:-      𝛿1: $.target-    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_entry-    dataize:-      𝛿1: $.n-    morph:-      𝑛1: $.v.φ-    𝑛: Φ.l🌵.root( 𝑛1 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧-location: 'Q.l🌵'-deep: true-partial: true-acyclic: proven-steps: 32-input: |-  ⟦-    number ↦ ⟦ φ ↦ ∅, gt ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_number_gt ⟧ ⟧,-    bytes ↦ ⟦ φ ↦ ∅ ⟧,-    bool ↦ ⟦ if ↦ ∅ ⟧,-    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,-    text ↦ ⟦-      φ ↦ ∅,-      at ↦ ⟦-        i ↦ ∅,-        φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00-00-00-00-00-00-00-00 ⟧ ) ).gt( b ↦ Φ.dataized( target ↦ ξ.i ) )-      ⟧-    ⟧,-    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,-    l🌵 ↦ ⟦-      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,-      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark(-        n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) )-      )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )-    ⟧-  ⟧-protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )-          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )-          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )-          𝕄(Φ.a🌵1.φ):-            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)-        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛-        𝕄(𝑛·1·7):-          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )-        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)-      𝔼(L_root):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)-        𝑛·2·2 := ⟦⟧  # 𝑛-        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
− test-resources/evaluate-packs/leaves-unfinished-a-message-no-entry-answers.yaml
@@ -1,117 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The walk reduces the message of the copy a branch raises with like any other-# code, and leaves the call of 'L_number_nope', which no entry answers, as it-# is written, the way it leaves any binding it cannot fire. The 'terminate'-# line spells that message out, so the reader sees the error path is-# unfinished (#1867).-symbolic: |-  - λ: L_number_gt-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    terminators: [L_throw]-    𝑛: 𝑛5-location: 'Q.@'-deep: true-input: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    error(message) ↦ ⟦ λ ⤍ L_throw ⟧,-    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,-    number(φ) ↦ ⟦-      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,-      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,-      nope(ρ, x) ↦ ⟦ λ ⤍ L_number_nope ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( then ↦ 1, else ↦ Φ.error( message ↦ 7.nope( 8 ) ) ).plus( 6 ) ⟧-result: |--  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ, nope(x) ↦ L_number_nope:λ ⟧-protocol: |-  𝕄(Φ.φ):-    𝑛·0·1 := 5-    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )-    𝔼(L_number_gt):-      𝔻(Φ.a🌵0):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := 5-          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵1):-        𝕄(Φ.a🌵1):-          𝑛·1·3 := 0-          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )-        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0-      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛-      𝕄(𝑛·1·5):-        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )-      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1 )-    𝑛·0·4 := 𝑛·0·3( else ↦ Φ.error( message ↦ 7.nope( 8 ) ) )-    𝔼(L_fork):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝕄(Φ.a🌵3):-        𝑛·2·2 := 1-        𝕄(Φ.a🌵3.φ):-          𝑛·2·3 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛1·2 := 𝑛·2·2  # 𝕄(ξ.then)-      𝕄(Φ.a🌵4):-        𝑛·2·4 := Φ.error( message ↦ 7.nope( 8 ) )-        𝕄(Φ.a🌵4.message):-          𝑛·2·5 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-          𝑛·2·6 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·7 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·8 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-          𝑛·2·9 := Φ.number( φ ↦ 𝑛·2·8 )-          𝑛·2·10 := Φ.number( φ ↦ 𝑛·2·5 )-          𝑛·2·11 := 𝑛·2·10.nope( x ↦ 𝑛·2·9 )-      𝑛2·2 := 𝑛·2·4  # 𝕄(ξ.else)-      𝔻(𝜎2:λ) == 3F-F0-00-00-00-00-00-00-      𝑛3·2 := ⟦ φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ), gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ, nope(x) ↦ L_number_nope:λ ⟧  # 𝑛1-      𝑛4·2 := 𝑛·2·4  # 𝑛2-      terminate(𝔻(𝜎1:λ), right, 𝑛·2·10.nope( 𝑛·2·9 ))  # 𝑛4-      𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]-      𝑛·2·12 := 𝑛5·2  # 𝑛-      𝑛·2·13 := 𝑛5·2  # 𝕄(𝑛·2·12)-    𝑛·0·5 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ).plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )-    𝔼(L_number_plus):-      𝔻(Φ.a🌵5):-        𝕄(Φ.a🌵5):-          𝑛·3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ) )-          𝑛·3·2 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵6):-        𝕄(Φ.a🌵6):-          𝑛·3·3 := Φ.number( φ ↦ 𝑛·0·6 )-          𝑛·3·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-        𝛿·3·1 := 40-18-00-00-00-00-00-00  # 6.0-      𝛿2·3 := 𝛿·3·1  # 𝔻(ξ.x)-      𝑛·3·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛-      𝕄(𝑛·3·5):-        𝑛·3·6 := Φ.number( φ ↦ 𝜎3:λ )-      𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
test-resources/evaluate-packs/mints-a-fresh-symbol-for-every-firing.yaml view
@@ -2,9 +2,10 @@ # SPDX-License-Identifier: MIT --- # yamllint disable rule:line-length-# Two firings down a chain of dispatches: each mints a symbol of its own, and-# the operand of the second is the answer of the first, which the protocol-# names rather than brings down again — every symbol answers the same datum.+# Two firings down a chain of dispatches, the last of which is φ: each mints+# a symbol of its own, and the operand of the second is the answer of the+# first, which the protocol names rather than brings down again — every+# symbol answers the same datum. symbolic: |   - λ: L_number_plus     dataize:@@ -12,12 +13,11 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.plus( 6 ).plus( 7 ) ⟧-result: '⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧'+    @ ↦ 5.plus( 6 ).plus( 7 ).φ ⟧+result: '𝜎2:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -40,9 +40,6 @@         𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )       𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)     𝑛·0·3 := 𝑛·1·6.plus( x ↦ 7 )-    𝕄(Φ.φ.x):-      𝑛·0·4 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )     𝔼(L_number_plus):       𝔻(Φ.a🌵2):         𝕄(Φ.a🌵2):@@ -50,7 +47,7 @@       𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵3):         𝕄(Φ.a🌵3):-          𝑛·2·2 := Φ.number( φ ↦ 𝑛·0·4 )+          𝑛·2·2 := 7           𝑛·2·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )         𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0       𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)
test-resources/evaluate-packs/morphs-a-program-that-already-carries-a-symbol.yaml view
@@ -13,19 +13,15 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).plus( 7 ) ⟧-result: '⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧'+    @ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).plus( 7 ).φ ⟧+result: '𝜎2:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := Φ.number( φ ↦ 𝜎1:λ )     𝑛·0·2 := 𝑛·0·1.plus( x ↦ 7 )-    𝕄(Φ.φ.x):-      𝑛·0·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )     𝔼(L_number_plus):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):@@ -33,7 +29,7 @@       𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵1):         𝕄(Φ.a🌵1):-          𝑛·1·2 := Φ.number( φ ↦ 𝑛·0·3 )+          𝑛·1·2 := 7           𝑛·1·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )         𝛿·1·1 := 40-1C-00-00-00-00-00-00  # 7.0       𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)
test-resources/evaluate-packs/parks-a-dispatch-off-a-symbol.yaml view
@@ -6,65 +6,16 @@ # worked out looks like: ⟦ λ ⤍ 𝜎1 ⟧. Dispatching an attribute off it asks 𝕄 to # reduce it, 'ml' fires 𝔼 on the target, and before #1287 𝔼 refused the # formation outright — a 'userError' nothing catches, so the whole run died on-# the first such term and everything standing after it never ran. 𝔼 now gets-# stuck on the symbol the way it gets stuck on a λ name no entry of the-# '--symbolic' file answers, so '--partial' parks the site: 'a' is left exactly-# as it was written, a taint with no root, and the walk goes on to 'b', which-# fires and computes. The site is written to the protocol as 'unanswered(𝜎1)',-# spelled the way every term carrying that symbol spells it.-#-# The site is recorded twice because the walk passes it twice — once for the-# dispatch 'x.plus' and once for the application 'x.plus( 5 )' around it — which-# is what a λ name nothing answers does on this shape too; nothing about the-# symbol makes it write more than a name would.-symbolic: |-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) )-location: 'Q.demo'-deep: true+# the first such term. 𝔼 now gets stuck on the symbol the way it gets stuck on+# a λ name no entry of the '--symbolic' file answers, so '--partial' parks the+# site: 'a' is left exactly as it was written, a taint with no root. The site+# is written to the protocol as 'unanswered(𝜎1)', spelled the way every term+# carrying that symbol spells it.+symbolic: '[]'+location: 'Q.a' partial: true-input: >-  ⟦ bytes ↦ ⟦ φ ↦ ∅ ⟧,-    number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    demo ↦ ⟦-      a ↦ ⟦ λ ⤍ 𝜎1 ⟧.plus( 5 ),-      b ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ).plus( 5 ) ⟧ ⟧-result: |--  ⟦-    a ↦ 𝜎1:λ.plus( 5 ),-    b ↦ ⟦ φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ), plus(x) ↦ L_number_plus:λ ⟧-  ⟧+input: '⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧.plus( 5 ) ⟧'+result: '𝜎1:λ.plus( 5 )' protocol: |-  𝕄(Φ.demo):-    𝕄(Φ.demo.a):-      unanswered(𝜎1)  # 𝕄(𝜎1:λ)-      𝑛·0·1 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      unanswered(𝜎1)  # 𝕄(𝜎1:λ)-    𝕄(Φ.demo.b):-      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )-      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·3 )-      𝑛·0·6 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )-      𝑛·0·8 := Φ.number( φ ↦ 𝑛·0·3 )-      𝑛·0·9 := 𝑛·0·8.plus( x ↦ 𝑛·0·7 )-      𝔼(L_number_plus):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·3 )-            𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )-        𝛿1·1 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵1):-          𝕄(Φ.a🌵1):-            𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·6 )-            𝑛·1·4 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-        𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)-        𝑛·1·5 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) )  # 𝑛-        𝕄(𝑛·1·5):-          𝑛·1·6 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) )-        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)+  𝕄(Φ.a):+    unanswered(𝜎1)  # 𝕄(𝜎1:λ)
test-resources/evaluate-packs/parks-a-firing-whose-operand-reaches-the-terminator.yaml view
@@ -6,8 +6,10 @@ # the 'dataize' operand 'ρ.data.size' of 'L_dataized' reaches the terminator ⊥. # Under '_partial' that parks the firing the way an operand parked on a λ # function no entry answers does, rather than ending the run: the protocol-# writes 'unanswered(⊥)' with the term 𝔻 could not dataize, and the walk goes on-# to 'L_root', whose own operand dies the same way (#1401).+# writes 'unanswered(⊥)' with the term 𝔻 could not dataize, the 'morph' operand+# of 'L_entry' hands back the residue it parked on, and the dispatch of 'φ' off+# the answer fires 'L_root', whose own operand dies the same way, so the run+# parks there (#1401). symbolic: |   - λ: L_dataized     dataize:@@ -23,11 +25,8 @@     dataize:       𝛿1: $.v     𝑛: ⟦ ⟧-location: 'Q.l🌵'-deep: true-acyclic: proven+location: 'Q.l🌵.e1' partial: true-steps: 32 input: >   ⟦     number ↦ ⟦ φ ↦ ∅ ⟧,@@ -37,60 +36,37 @@     l🌵 ↦ ⟦       mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,       root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,-      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read )+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read ).φ     ⟧   ⟧-result: |--  ⟦-    mark(n, v) ↦ L_entry:λ,-    root(v) ↦ L_root:λ,-    e1 ↦ ⟦ v ↦ Φ.dataized( target ↦ ⊥ ).as-bytes, λ ⤍ L_root ⟧-  ⟧+result: '⟦ v ↦ Φ.dataized( target ↦ ⊥ ).as-bytes, λ ⤍ L_root ⟧.φ' protocol: |-  𝕄(Φ.l🌵):-    𝕄(Φ.l🌵.e1):-      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·5 := Φ.block( buffer ↦ 𝑛·0·4 )-      𝑛·0·6 := Φ.block( buffer ↦ 𝑛·0·4 )-      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )-      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6.read )-      𝔼(L_entry):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)-        𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.block( buffer ↦ 𝑛·0·4 )-          𝑛·1·4 := Φ.dataized( target ↦ ⊥ )+  𝕄(Φ.l🌵.e1):+    𝑛·0·1 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 1 )+    𝑛·0·2 := 𝑛·0·1( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ).read )+    𝔼(L_entry):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 1+          𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)+      𝕄(Φ.a🌵1):+        𝑛·1·3 := Φ.block( buffer ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )+        𝑛·1·4 := Φ.dataized( target ↦ ⊥ )+        𝔼(L_dataized):+          unanswered(⊥)  # 𝔻(⊥)+          stuck(⊥)+      𝑛1·1 := 𝑛·1·4.as-bytes  # 𝕄(ξ.v.φ)+      𝑛·1·5 := Φ.l🌵.root( 𝑛·1·4.as-bytes )  # 𝑛+      𝕄(𝑛·1·5):+        𝑛·1·6 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·4.as-bytes )+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)+    𝔼(L_root):+      𝔻(Φ.a🌵3):+        𝕄(Φ.a🌵3):+          𝑛·3·1 := Φ.dataized( target ↦ ⊥ )           𝔼(L_dataized):             unanswered(⊥)  # 𝔻(⊥)             stuck(⊥)-          𝑛·1·5 := Φ.dataized( target ↦ ⊥ )-          𝑛·1·6 := Φ.dataized( target ↦ ⊥ )-          𝔼(L_dataized):-            stuck(⊥)-          𝑛·1·7 := Φ.dataized( target ↦ ⊥ )-          𝑛·1·8 := Φ.dataized( target ↦ ⊥ )-          𝔼(L_dataized):-            stuck(⊥)-          𝑛·1·9 := Φ.dataized( target ↦ ⊥ )-          𝔼(L_dataized):-            stuck(⊥)-        𝑛1·1 := 𝑛·1·9.as-bytes  # 𝕄(ξ.v.φ)-        𝑛·1·10 := Φ.l🌵.root( 𝑛·1·9.as-bytes )  # 𝑛-        𝕄(𝑛·1·10):-          𝑛·1·11 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·9.as-bytes )-        𝑛·1·12 := 𝑛·1·11  # 𝕄(𝑛·1·10)-      𝔼(L_root):-        𝔻(Φ.a🌵6):-          𝕄(Φ.a🌵6):-            𝑛·6·1 := Φ.dataized( target ↦ ⊥ )-            𝔼(L_dataized):-              unanswered(⊥)  # 𝔻(⊥)-              stuck(⊥)-        stuck(⊥)+      stuck(⊥)
+ test-resources/evaluate-packs/parks-an-evaluate-line-firing-a-lambda-no-entry-answers.yaml view
@@ -0,0 +1,24 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# An 'evaluate' line fires a λ function no entry answers, so the firing of+# the fork gets stuck the way any firing does, and '--partial' parks the+# site the fork stands at (#1874).+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+    evaluate:+      𝑛2: 𝑛1+    𝑛: 𝑛2+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ a ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := L_none:λ  # 𝕄(ξ.a)+      unanswered(L_none)  # 𝔼(L_none:λ)+      stuck(L_none)
+ test-resources/evaluate-packs/parks-an-evaluate-line-over-a-branch-left-unreduced.yaml view
@@ -0,0 +1,26 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# Under '--partial' a branch that got stuck comes back as the term it was+# parked at, which is no formation and so nothing 𝔼 can be asked of: the+# 'evaluate' line gets stuck on the fork itself, and the site the fork+# stands at is parked (#1874).+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+    evaluate:+      𝑛2: 𝑛1+    𝑛: 𝑛2+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ L_none ⟧.x, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ a ↦ ⟦ λ ⤍ L_none ⟧.x, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝕄(Φ.a🌵0):+        unanswered(L_none)  # 𝕄(L_none:λ)+      𝑛1·1 := L_none:λ.x  # 𝕄(ξ.a)+      stuck(L_fork)
− test-resources/evaluate-packs/records-a-parked-site-once.yaml
@@ -1,22 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# The same λ function no entry answers, under '_partial' and the deep walk: the-# spine parks the site and the walk goes over the residue it parked on, so 𝔼 is-# attempted on that one site twice. The protocol records it once, since what a-# 'unanswered(…)' line counts is the sites 𝔼 got stuck on and not the passes the-# walk made over them (#1300).-symbolic: |-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.x'-deep: true-partial: true-input: '⟦ x ↦ ⟦ λ ⤍ L_none ⟧.foo ⟧'-result: 'L_none:λ.foo'-protocol: |-  𝕄(Φ.x):-    unanswered(L_none)  # 𝕄(L_none:λ)
test-resources/evaluate-packs/reduces-the-branches-of-a-fork-through-morphing.yaml view
@@ -4,9 +4,11 @@ # yamllint disable rule:line-length # A branch is where a 'morph' operand earns its place: the condition comes # down through 𝔻 and answers a symbol nobody has decided, both branches reach-# a normal form through 𝕄, and the answer is one of them. Every line of the-# firing points back at the line that minted the symbol it carries, which is-# how the dependencies between firings are read.+# a normal form through 𝕄, a call of 'plus' nobody has fired, an 'evaluate'+# line fires each of them (#1874), and the answer is the first. Every line of+# the firing points back at the line that minted the symbol it carries, which+# is how the dependencies between firings are read. The dispatch of φ that+# ends the program is what fires the fork. symbolic: |   - λ: L_number_gt     dataize:@@ -24,16 +26,19 @@     morph:       𝑛1: $.then       𝑛2: $.else-    𝑛: 𝑛1+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    𝑛: 𝑛3 location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,     number(φ) ↦ ⟦       gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,       plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ) ⟧+    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ).φ ⟧+result: '𝜎2:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5@@ -57,62 +62,54 @@       𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)     𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ) )     𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )-    𝕄(Φ.φ.then):-      𝑛·0·5 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-      𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·5 )-      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·5 )-      𝑛·0·8 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·8 )-      𝑛·0·10 := Φ.number( φ ↦ 𝑛·0·5 )-      𝑛·0·11 := 𝑛·0·10.plus( x ↦ 𝑛·0·9 )-      𝔼(L_number_plus):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·5 )-            𝑛·2·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )-          𝛿·2·1 := 3F-F0-00-00-00-00-00-00  # 1.0-        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵3):-          𝕄(Φ.a🌵3):-            𝑛·2·3 := Φ.number( φ ↦ 𝑛·0·8 )-            𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )-          𝛿·2·2 := 40-00-00-00-00-00-00-00  # 2.0-        𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)-        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-        𝕄(𝑛·2·5):-          𝑛·2·6 := Φ.number( φ ↦ 𝜎2:λ )-        𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)-    𝕄(Φ.φ.else):-      𝑛·0·12 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )-      𝑛·0·13 := Φ.number( φ ↦ 𝑛·0·12 )-      𝑛·0·14 := Φ.number( φ ↦ 𝑛·0·12 )-      𝑛·0·15 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )-      𝑛·0·16 := Φ.number( φ ↦ 𝑛·0·15 )-      𝑛·0·17 := Φ.number( φ ↦ 𝑛·0·12 )-      𝑛·0·18 := 𝑛·0·17.plus( x ↦ 𝑛·0·16 )-      𝔼(L_number_plus):-        𝔻(Φ.a🌵4):-          𝕄(Φ.a🌵4):-            𝑛·3·1 := Φ.number( φ ↦ 𝑛·0·12 )-            𝑛·3·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )-          𝛿·3·1 := 40-08-00-00-00-00-00-00  # 3.0-        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+    𝔼(L_fork):+      𝔻(Φ.a🌵2):+        𝕄(Φ.a🌵2):+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+      𝕄(Φ.a🌵3):+        𝑛·2·2 := 1+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )+      𝑛1·2 := 𝑛·2·3  # 𝕄(ξ.then)+      𝕄(Φ.a🌵4):+        𝑛·2·4 := 3+        𝑛·2·5 := 𝑛·2·4.plus( x ↦ 4 )+      𝑛2·2 := 𝑛·2·5  # 𝕄(ξ.else)+      𝔼(L_number_plus):  # 𝔼(𝑛1)         𝔻(Φ.a🌵5):           𝕄(Φ.a🌵5):-            𝑛·3·3 := Φ.number( φ ↦ 𝑛·0·15 )-            𝑛·3·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )-          𝛿·3·2 := 40-10-00-00-00-00-00-00  # 4.0+            𝑛·3·1 := 1+            𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+          𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵6):+          𝕄(Φ.a🌵6):+            𝑛·3·3 := 2+            𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+          𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0         𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)-        𝑛·3·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+        𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛         𝕄(𝑛·3·5):-          𝑛·3·6 := Φ.number( φ ↦ 𝜎3:λ )+          𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )         𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)-    𝔼(L_fork):-      𝔻(Φ.a🌵6):-        𝕄(Φ.a🌵6):-          𝑛·4·1 := Φ.bool( φ ↦ 𝜎1:λ )-      𝛿1·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝑛1·4 := 𝑛·2·7  # 𝕄(ξ.then)-      𝑛2·4 := 𝑛·3·7  # 𝕄(ξ.else)-      𝑛·4·2 := 𝑛1·4  # 𝑛-      𝑛·4·3 := 𝑛1·4  # 𝕄(𝑛·4·2)+      𝑛3·2 := 𝑛·3·7  # 𝔼(𝑛1)+      𝔼(L_number_plus):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵7):+          𝕄(Φ.a🌵7):+            𝑛·4·1 := 3+            𝑛·4·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )+          𝛿·4·1 := 40-08-00-00-00-00-00-00  # 3.0+        𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)+        𝔻(Φ.a🌵8):+          𝕄(Φ.a🌵8):+            𝑛·4·3 := 4+            𝑛·4·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )+          𝛿·4·2 := 40-10-00-00-00-00-00-00  # 4.0+        𝛿2·4 := 𝛿·4·2  # 𝔻(ξ.x)+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+        𝕄(𝑛·4·5):+          𝑛·4·6 := Φ.number( φ ↦ 𝜎3:λ )+        𝑛·4·7 := 𝑛·4·6  # 𝕄(𝑛·4·5)+      𝑛4·2 := 𝑛·4·7  # 𝔼(𝑛2)+      𝑛·2·6 := 𝑛3·2  # 𝑛+      𝑛·2·7 := 𝑛3·2  # 𝕄(𝑛·2·6)
+ test-resources/evaluate-packs/spends-a-step-on-every-firing-of-an-evaluate-line.yaml view
@@ -0,0 +1,29 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The same recursion through 'evaluate' lines, with no '--acyclic' to cut+# it. Every firing an 'evaluate' line asks for is one step deeper, the way+# a frame of 𝕄 or 𝔻 is, so the step budget alone ends the run (#1882): the+# second firing has one step less to morph 'next' with, and starves there.+# The budget of firings is there only to end the run if the step is ever+# lost again.+symbolic: |+  - λ: L_loop+    morph:+      𝑛1: $.next+    evaluate:+      𝑛2: 𝑛1+    𝑛: 𝑛2+location: 'Q.app'+steps: 6+firings: 20+input: '⟦ loop ↦ ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧, app ↦ Φ.loop.φ ⟧'+fails: 'reaching the limit of steps'+protocol: |+  𝕄(Φ.app):+    𝔼(L_loop):+      𝑛1·1 := ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧  # 𝕄(ξ.next)+      𝔼(L_loop):  # 𝔼(𝑛1)+        𝕄(Φ.a🌵1):+          starved(6)
+ test-resources/evaluate-packs/spends-the-step-budget-on-a-branch-whose-phi-leads-back-to-it.yaml view
@@ -0,0 +1,35 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The branch whose φ fires the same fork again, with no '--acyclic' to cut it.+# The step a lenient join takes into a branch of #1885 fires the way an+# 'evaluate' line does, spending a step on every firing (#1882), so the step+# limit ends the run instead of letting the join fire the fork forever. This+# is the control the pack asking for the guard stands against.+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      𝑛3: [𝑛1, 𝑛2]+    lenient: true+    𝑛: 𝑛3+location: 'Q.y'+steps: 6+input: '⟦ z ↦ ⟦ φ ↦ ξ.f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ⟧ ⟧, y ↦ ⟦ a ↦ Φ.z, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+fails: 'reaching the limit of steps'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := ⟦ φ ↦ f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧ ⟧  # 𝕄(ξ.a)+      𝑛2·1 := 𝜎2:λ:φ  # 𝕄(ξ.b)+      𝑛1·1 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)+      𝔼(L_fork):  # 𝔼(𝑛1)+        𝑛1·2 := ⟦ φ ↦ f, f ↦ ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧ ⟧  # 𝕄(ξ.a)+        𝑛2·2 := 𝜎1:λ:φ  # 𝕄(ξ.b)+        𝑛1·2 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)+        𝔼(L_fork):  # 𝔼(𝑛1)+          𝕄(Φ.a🌵6):+            starved(6)
test-resources/evaluate-packs/starts-minting-after-the-symbols-the-program-carries.yaml view
@@ -12,20 +12,16 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,     old ↦ ⟦ λ ⤍ 𝜎3 ⟧,-    @ ↦ 5.plus( 6 ) ⟧-result: '⟦ φ ↦ 𝜎4:λ, plus(x) ↦ L_number_plus:λ ⟧'+    @ ↦ 5.plus( 6 ).φ ⟧+result: '𝜎4:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := 5     𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )-    𝕄(Φ.φ.x):-      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )     𝔼(L_number_plus):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):@@ -35,7 +31,7 @@       𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)       𝔻(Φ.a🌵1):         𝕄(Φ.a🌵1):-          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )+          𝑛·1·3 := 6           𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )         𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0       𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
test-resources/evaluate-packs/stops-an-accumulator-recursion-under-plausible.yaml view
@@ -3,21 +3,16 @@ --- # yamllint disable rule:line-length # A factorial with an accumulator, under '--acyclic=plausible'. Every round-# wraps the accumulator in one more 'pair', which no entry answers, so no two-# rounds are ever the same formation up to a renaming of symbols and-# '--acyclic=proven' goes round forever. The formation 'if' the deep walk fires-# on the second round holds the one it fired on the first, under the wrapper it-# gained, which is what the plausible guard compares by, so the second firing-# is cut before it opens and its 'looped' line names the mode that cut it-# (#1451). Every round is a copy of 'Φ.fact', but its φ reads the 'n' and the-# 'acc' of the copy it stands in, so the walk enters it in every round rather-# than once for the whole object (#1485). The argument 'n' and every symbol-# 'dec' answers stand in a carrier '⟦ φ ↦ 𝜎 ⟧', since the walk defers a copy-# one of whose arguments is a bare symbol instead of entering it (#1729).-# The cut 'if' is the φ of a copy of 'Φ.fact', so the cut answers that copy-# with a fresh symbol, which the 'join' of the first 'if' pairs with the datum-# of the other branch, and the run answers a symbol instead of getting stuck-# (#1735).+# wraps the accumulator in one more 'pair' and the counter in one more 'dec',+# so no two rounds are ever the same formation up to a renaming of symbols and+# '--acyclic=proven' never cuts it. The body of 'fact' dispatches 'φ' off its+# 'if', so 'ml' fires it, and the 'morph' operand of 'L_if' reduces the+# recursive call the same way. The 'if' it reaches on the second round holds+# the one fired on the first, under the wrappers it gained, which is what the+# plausible guard compares by, so the second firing is cut before it opens and+# its 'looped' line names the mode that cut it (#1451). The operand hands back+# the term the cut left, which no 'join' can pair with the datum of the other+# branch, so the first 'L_if' gets stuck and '--partial' parks it. symbolic: |   - λ: L_zero     dataize:@@ -26,7 +21,7 @@   - λ: L_dec     dataize:       𝛿1: $.x-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+    𝑛: ⟦ λ ⤍ 𝜎 ⟧   - λ: L_if     dataize:       𝛿1: $.c@@ -40,89 +35,43 @@       𝑛5: [𝑛3, 𝑛4]     𝑛: 𝑛5 location: 'Q.x'-deep: true acyclic: plausible partial: true-steps: 250 input: >   ⟦ if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,     zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,     dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,     pair ↦ ⟦ head ↦ ∅, tail ↦ ∅ ⟧,-    fact ↦ ⟦ n ↦ ∅, acc ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ξ.acc, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ), acc ↦ Φ.pair( head ↦ ξ.n, tail ↦ ξ.acc ) ) ) ⟧,-    x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, acc ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧-result: |--  ⟦ n ↦ 𝜎1:λ:φ, acc ↦ 00-:Δ, φ ↦ 𝜎8:λ ⟧+    fact ↦ ⟦ n ↦ ∅, acc ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ξ.acc, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ), acc ↦ Φ.pair( head ↦ ξ.n, tail ↦ ξ.acc ) ).φ ).φ ⟧,+    x ↦ Φ.fact( n ↦ ⟦ λ ⤍ 𝜎1 ⟧, acc ↦ ⟦ Δ ⤍ 00- ⟧ ).φ ⟧+result: 'Φ.if( c ↦ Φ.zero( x ↦ 𝜎1:λ ), left ↦ 00-:Δ, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎1:λ ), acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) ).φ ).φ' protocol: |   𝕄(Φ.x):-    𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ:φ )+    𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ )     𝑛·0·2 := 𝑛·0·1( acc ↦ 00-:Δ )-    𝕄(Φ.x.φ):-      𝑛·0·3 := Φ.zero( x ↦ 𝜎1:λ:φ )-      𝔼(L_zero):-        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-        𝑛·1·1 := 𝜎2:λ  # 𝑛-        𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)-      𝑛·0·4 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·5 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·6 := 𝑛·0·5( left ↦ 00-:Δ )-      𝑛·0·7 := Φ.dec( x ↦ 𝜎1:λ:φ )-      𝔼(L_dec):-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)-        𝑛·2·1 := 𝜎3:λ:φ  # 𝑛-        𝑛·2·2 := 𝜎3:λ:φ  # 𝕄(𝑛·2·1)-      𝑛·0·8 := Φ.fact( n ↦ 𝜎3:λ:φ )-      𝑛·0·9 := Φ.pair( head ↦ 𝜎1:λ:φ )-      𝑛·0·10 := Φ.pair( head ↦ 𝜎1:λ:φ )-      𝑛·0·11 := 𝑛·0·10( tail ↦ 00-:Δ )-      𝑛·0·12 := Φ.fact( n ↦ 𝜎3:λ:φ )-      𝑛·0·13 := 𝑛·0·12( acc ↦ 𝑛·0·11 )-      𝑛·0·14 := Φ.if( c ↦ 𝜎2:λ )-      𝑛·0·15 := 𝑛·0·14( left ↦ 00-:Δ )-      𝑛·0·16 := 𝑛·0·15( right ↦ 𝑛·0·13 )-      𝔼(L_if):-        𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)-        𝑛1·3 := 00-:Δ  # 𝕄(ξ.left)-        𝕄(Φ.a🌵4):-          𝑛·3·1 := Φ.fact( n ↦ 𝜎3:λ:φ )-          𝑛·3·2 := 𝑛·3·1( acc ↦ 𝑛·0·11 )-          𝕄(Φ.a🌵4.acc):-            𝑛·3·3 := Φ.pair( head ↦ 𝜎1:λ:φ )-            𝑛·3·4 := Φ.pair( head ↦ 𝜎1:λ:φ )-            𝑛·3·5 := 𝑛·3·4( tail ↦ 00-:Δ )-          𝕄(Φ.a🌵4.φ):-            𝑛·3·6 := Φ.zero( x ↦ 𝜎3:λ:φ )-            𝔼(L_zero):-              𝛿1·4 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-              𝑛·4·1 := 𝜎4:λ  # 𝑛-              𝑛·4·2 := 𝜎4:λ  # 𝕄(𝑛·4·1)-            𝑛·3·7 := Φ.if( c ↦ 𝜎4:λ )-            𝑛·3·8 := Φ.pair( head ↦ 𝜎1:λ:φ )-            𝑛·3·9 := 𝑛·3·8( tail ↦ 00-:Δ )-            𝑛·3·10 := Φ.if( c ↦ 𝜎4:λ )-            𝑛·3·11 := 𝑛·3·10( left ↦ 𝑛·3·9 )-            𝑛·3·12 := Φ.dec( x ↦ 𝜎3:λ:φ )-            𝔼(L_dec):-              𝛿1·5 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)-              𝑛·5·1 := 𝜎5:λ:φ  # 𝑛-              𝑛·5·2 := 𝜎5:λ:φ  # 𝕄(𝑛·5·1)-            𝑛·3·13 := Φ.fact( n ↦ 𝜎5:λ:φ )-            𝑛·3·14 := Φ.pair( head ↦ 𝜎3:λ:φ )-            𝑛·3·15 := Φ.pair( head ↦ 𝜎1:λ:φ )-            𝑛·3·16 := 𝑛·3·15( tail ↦ 00-:Δ )-            𝑛·3·17 := Φ.pair( head ↦ 𝜎3:λ:φ )-            𝑛·3·18 := 𝑛·3·17( tail ↦ 𝑛·3·16 )-            𝑛·3·19 := Φ.fact( n ↦ 𝜎5:λ:φ )-            𝑛·3·20 := 𝑛·3·19( acc ↦ 𝑛·3·18 )-            𝑛·3·21 := Φ.if( c ↦ 𝜎4:λ )-            𝑛·3·22 := 𝑛·3·21( left ↦ 𝑛·3·16 )-            𝑛·3·23 := 𝑛·3·22( right ↦ 𝑛·3·20 )-            looped(𝑛·0·16) := 𝜎6  # plausible-        𝑛2·3 := 𝜎6:λ  # 𝕄(ξ.right)-        𝔻(𝜎7:λ) == 00--        𝑛3·3 := 𝜎7:λ  # 𝑛1-        𝑛4·3 := 𝑛2·3  # 𝑛2-        𝔻(𝜎8:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎6:λ) }-        𝑛5·3 := 𝜎8:λ  # [𝑛3, 𝑛4]-        𝑛·3·24 := 𝑛5·3  # 𝑛-        𝑛·3·25 := 𝑛5·3  # 𝕄(𝑛·3·24)+    𝑛·0·3 := Φ.if( c ↦ Φ.zero( x ↦ 𝜎1:λ ) )+    𝑛·0·4 := 𝑛·0·3( left ↦ 00-:Δ )+    𝑛·0·5 := 𝑛·0·4( right ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎1:λ ), acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) ).φ )+    𝔼(L_if):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := Φ.zero( x ↦ 𝜎1:λ )+        𝔼(L_zero):+          𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)+          𝑛·2·1 := 𝜎2:λ  # 𝑛+          𝑛·2·2 := 𝜎2:λ  # 𝕄(𝑛·2·1)+      𝛿1·1 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)+      𝑛1·1 := 00-:Δ  # 𝕄(ξ.left)+      𝕄(Φ.a🌵3):+        𝑛·1·2 := Φ.fact( n ↦ Φ.dec( x ↦ 𝜎1:λ ) )+        𝑛·1·3 := 𝑛·1·2( acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) )+        𝑛·1·4 := Φ.if( c ↦ Φ.zero( x ↦ Φ.dec( x ↦ 𝜎1:λ ) ) )+        𝑛·1·5 := 𝑛·1·4( left ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) )+        𝑛·1·6 := 𝑛·1·5( right ↦ Φ.fact( n ↦ Φ.dec( x ↦ Φ.dec( x ↦ 𝜎1:λ ) ), acc ↦ Φ.pair( head ↦ Φ.dec( x ↦ 𝜎1:λ ), tail ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 00-:Δ ) ) ).φ )+        looped(𝑛·0·5)  # plausible+      𝑛2·1 := 𝑛·1·3.φ  # 𝕄(ξ.right)+      𝔻(𝜎3:λ) == 00-+      𝑛3·1 := 𝜎3:λ  # 𝑛1+      𝔻(𝜎4:λ) == 00-+      𝑛4·1 := 𝑛·1·2( acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 𝜎4:λ ) ).φ  # 𝑛2+      stuck(L_if)
test-resources/evaluate-packs/takes-the-answer-of-a-firing-with-the-same-operands.yaml view
@@ -4,47 +4,59 @@ # yamllint disable rule:line-length # The memo of '--acyclic=plausible' keeps what a firing answered by the λ # function and the operands it brought down, beside the formation it fired.-# Here 'a' and 'b' fire two formations, since one reads 'x' and the other-# reads 'y', so the memo of formations misses the second. Yet both bring the-# same datum down, and an entry answers from its operands alone. Before #1661-# the firing under 'b' minted '𝜎2' for the very value '𝜎1' already named. Now-# it brings its operand down, finds the firing already answered and takes-# '𝜎1', minting nothing.+# The 'evaluate' lines of 'L_pair' fire 'a' and then 'b', two formations,+# since one reads 'x' and the other reads 'y', so the memo of formations+# misses the second. Yet both bring the same datum down, and an entry+# answers from its operands alone. Before #1661 the firing of 'b' minted+# '𝜎2' for the very value '𝜎1' already named. Now it brings its operand+# down, finds the firing already answered and takes '𝜎1', minting nothing+# (#1874). symbolic: |   - λ: L_dataized     dataize:       𝛿1: $.target     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true-partial: true+  - λ: L_pair+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    𝑛: ⟦ φ ↦ ⟦ a ↦ 𝑛3, b ↦ 𝑛4 ⟧ ⟧+location: 'Q.p' acyclic: plausible input: >   ⟦ dataized(target) ↦ L_dataized:λ,-    a ↦ Φ.dataized( target ↦ Φ.x ),-    b ↦ Φ.dataized( target ↦ Φ.y ),+    p ↦ ⟦+      a ↦ Φ.dataized( target ↦ Φ.x ),+      b ↦ Φ.dataized( target ↦ Φ.y ),+      λ ⤍ L_pair ⟧.φ,     x ↦ ⟦ Δ ⤍ 01- ⟧,     y ↦ ⟦ Δ ⤍ 01- ⟧ ⟧-result: >-  ⟦ dataized(target) ↦ L_dataized:λ,-    a ↦ 𝜎1:λ,-    b ↦ 𝜎1:λ,-    x ↦ 01-:Δ,-    y ↦ 01-:Δ ⟧+result: '⟦ a ↦ 𝜎1:λ, b ↦ 𝜎1:λ ⟧' protocol: |-  𝕄(Φ):-    𝕄(Φ.a):-      𝑛·0·1 := Φ.dataized( target ↦ Φ.x )-      𝔼(L_dataized):-        𝔻(Φ.a🌵0):-          𝛿·1·1 := 01--        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.target)-        𝑛·1·1 := 𝜎1:λ  # 𝑛-        𝑛·1·2 := 𝜎1:λ  # 𝕄(𝑛·1·1)-    𝕄(Φ.b):-      𝑛·0·2 := Φ.dataized( target ↦ Φ.y )-      𝔼(L_dataized):-        𝔻(Φ.a🌵1):+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := Φ.dataized( target ↦ Φ.x )+      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.a)+      𝕄(Φ.a🌵1):+        𝑛·1·2 := Φ.dataized( target ↦ Φ.y )+      𝑛2·1 := 𝑛·1·2  # 𝕄(ξ.b)+      𝔼(L_dataized):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵2):           𝛿·2·1 := 01-         𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.target)-        𝑛·2·1 := 𝑛·1·2  # 𝑛-        𝑛·2·2 := 𝑛·1·2  # 𝕄(𝑛·2·1)+        𝑛·2·1 := 𝜎1:λ  # 𝑛+        𝑛·2·2 := 𝜎1:λ  # 𝕄(𝑛·2·1)+      𝑛3·1 := 𝑛·2·2  # 𝔼(𝑛1)+      𝔼(L_dataized):  # 𝔼(𝑛2)+        𝔻(Φ.a🌵3):+          𝛿·3·1 := 01-+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.target)+        𝑛·3·1 := 𝑛3·1  # 𝑛+        𝑛·3·2 := 𝑛3·1  # 𝕄(𝑛·3·1)+      𝑛4·1 := 𝑛·3·2  # 𝔼(𝑛2)+      𝑛·1·3 := ⟦ a ↦ 𝜎1:λ, b ↦ 𝜎1:λ ⟧:φ  # 𝑛+      𝑛·1·4 := ⟦ a ↦ 𝜎1:λ, b ↦ 𝜎1:λ ⟧:φ  # 𝕄(𝑛·1·3)
test-resources/evaluate-packs/tells-a-budget-stall-to-a-firing-no-shallower.yaml view
@@ -4,249 +4,44 @@ # yamllint disable rule:line-length # The memo of '--acyclic=plausible' keeps a stall the step budget made beside # the steps its firing had spent, and tells it to a firing of the same-# formation that has spent at least as many. The operand of 'x.arg' runs out-# of steps two firings down, so the stall is kept. Before #1521 it was dropped,-# and 'x' walked the nest again inside its own firing, while 'y.arg' and 'y'-# fired it all over. Now the firing inside 'x' is told the stall, and so are-# 'y.arg' and 'y', which stand as deep as 'x.arg' and 'x' did.+# formation that has spent at least as many. 'L_pair' reduces 'x' and then+# 'y', and both spell the very same nest of 'L_outer'. The firing under 'x'+# runs out of steps two firings down, so the stall is kept. Before #1521 it+# was dropped, and the firing under 'y' went down the nest all over again.+# Now that firing stands as deep as the one under 'x' did, so it is told the+# stall and reduces nothing. symbolic: |   - λ: L_outer     dataize:       𝛿1: ξ.arg     𝑛: ⟦ λ ⤍ 𝜎 ⟧-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.y+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧+location: 'Q.p' partial: true acyclic: plausible steps: 4 input: >-  ⟦ x ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧,-    y ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧ ⟧+  ⟦ p ↦ ⟦+      x ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧ ⟧ ⟧.φ,+      y ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧ ⟧ ⟧.φ,+      λ ⤍ L_pair ⟧.φ ⟧ protocol: |-  𝕄(Φ):-    𝑛·0·1 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )-    𝑛·0·2 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )-    𝑛·0·3 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )-    𝑛·0·4 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )-    𝑛·0·5 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 )-    𝑛·0·6 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·7 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·2 ⟧( v ↦ 1 )-    𝑛·0·8 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·3 ⟧( v ↦ 1 )-    𝑛·0·9 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·4 ⟧( v ↦ 1 )-    𝑛·0·10 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·5 ⟧( v ↦ 1 )-    𝑛·0·11 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·6 ⟧( v ↦ 1 )-    𝑛·0·12 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·13 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·9 ⟧( v ↦ 1 )-    𝑛·0·14 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·10 ⟧( v ↦ 1 )-    𝑛·0·15 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·11 ⟧( v ↦ 1 )-    𝑛·0·16 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·12 ⟧( v ↦ 1 )-    𝑛·0·17 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·18 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·14 ⟧( v ↦ 1 )-    𝑛·0·19 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·15 ⟧( v ↦ 1 )-    𝑛·0·20 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·16 ⟧( v ↦ 1 )-    𝑛·0·21 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·17 ⟧( v ↦ 1 )-    𝑛·0·22 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·23 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·20 ⟧( v ↦ 1 )-    𝑛·0·24 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·21 ⟧( v ↦ 1 )-    𝑛·0·25 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·22 ⟧( v ↦ 1 )-    𝑛·0·26 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·27 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·24 ⟧( v ↦ 1 )-    𝑛·0·28 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·25 ⟧( v ↦ 1 )-    𝑛·0·29 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·26 ⟧( v ↦ 1 )-    𝑛·0·30 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·31 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·29 ⟧( v ↦ 1 )-    𝑛·0·32 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·30 ⟧( v ↦ 1 )-    𝑛·0·33 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·34 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·32 ⟧( v ↦ 1 )-    𝑛·0·35 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·33 ⟧( v ↦ 1 )-    𝑛·0·36 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·37 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·36 ⟧( v ↦ 1 )-    𝑛·0·38 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·39 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·38 ⟧( v ↦ 1 )-    𝑛·0·40 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·41 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·42 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝕄(Φ.x):-      𝕄(Φ.x.arg):-        𝑛·0·43 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·27 ⟧( v ↦ 1 )-        𝑛·0·44 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·34 ⟧( v ↦ 1 )-        𝑛·0·45 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·39 ⟧( v ↦ 1 )-        𝑛·0·46 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·42 ⟧( v ↦ 1 )-        𝑛·0·47 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·48 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·45 ⟧( v ↦ 1 )-        𝑛·0·49 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·46 ⟧( v ↦ 1 )-        𝑛·0·50 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·47 ⟧( v ↦ 1 )-        𝑛·0·51 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·52 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·50 ⟧( v ↦ 1 )-        𝑛·0·53 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·51 ⟧( v ↦ 1 )-        𝑛·0·54 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·55 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·54 ⟧( v ↦ 1 )-        𝑛·0·56 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·57 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        starved(4)-        starved(4)-        starved(4)-        starved(4)-        𝑛·0·58 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·43 ⟧( v ↦ 1 )-        𝑛·0·59 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·48 ⟧( v ↦ 1 )-        𝑛·0·60 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·52 ⟧( v ↦ 1 )-        𝑛·0·61 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·55 ⟧( v ↦ 1 )-        𝑛·0·62 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·57 ⟧( v ↦ 1 )-        𝑛·0·63 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·64 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·60 ⟧( v ↦ 1 )-        𝑛·0·65 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·61 ⟧( v ↦ 1 )-        𝑛·0·66 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·62 ⟧( v ↦ 1 )-        𝑛·0·67 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·63 ⟧( v ↦ 1 )-        𝑛·0·68 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·69 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·66 ⟧( v ↦ 1 )-        𝑛·0·70 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·67 ⟧( v ↦ 1 )-        𝑛·0·71 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·68 ⟧( v ↦ 1 )-        𝑛·0·72 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·73 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·71 ⟧( v ↦ 1 )-        𝑛·0·74 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·72 ⟧( v ↦ 1 )-        𝑛·0·75 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·76 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·75 ⟧( v ↦ 1 )-        𝑛·0·77 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·78 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):         𝔼(L_outer):-          𝔻(Φ.a🌵0):+          𝔻(Φ.a🌵1):             𝔼(L_outer):-              𝔻(Φ.a🌵1):-                𝔼(L_outer):-                  starved(4)  # 𝔻(Φ.a🌵2)-              stuck(L_outer)+              starved(4)  # 𝔻(Φ.a🌵2)           stuck(L_outer)-      𝑛·0·79 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·64 ⟧( v ↦ 1 )-      𝑛·0·80 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·69 ⟧( v ↦ 1 )-      𝑛·0·81 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·73 ⟧( v ↦ 1 )-      𝑛·0·82 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·76 ⟧( v ↦ 1 )-      𝑛·0·83 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·78 ⟧( v ↦ 1 )-      𝑛·0·84 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·85 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·81 ⟧( v ↦ 1 )-      𝑛·0·86 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·82 ⟧( v ↦ 1 )-      𝑛·0·87 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·83 ⟧( v ↦ 1 )-      𝑛·0·88 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·84 ⟧( v ↦ 1 )-      𝑛·0·89 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·90 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·87 ⟧( v ↦ 1 )-      𝑛·0·91 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·88 ⟧( v ↦ 1 )-      𝑛·0·92 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·89 ⟧( v ↦ 1 )-      𝑛·0·93 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·94 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·92 ⟧( v ↦ 1 )-      𝑛·0·95 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·93 ⟧( v ↦ 1 )-      𝑛·0·96 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·97 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·96 ⟧( v ↦ 1 )-      𝑛·0·98 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·99 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝔼(L_outer):-        𝔻(Φ.a🌵3):-          𝔼(L_outer):-            stall(L_outer)-        stuck(L_outer)-    𝕄(Φ.y):-      𝕄(Φ.y.arg):-        𝑛·0·100 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·90 ⟧( v ↦ 1 )-        𝑛·0·101 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·94 ⟧( v ↦ 1 )-        𝑛·0·102 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·97 ⟧( v ↦ 1 )-        𝑛·0·103 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·99 ⟧( v ↦ 1 )-        𝑛·0·104 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·105 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·102 ⟧( v ↦ 1 )-        𝑛·0·106 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·103 ⟧( v ↦ 1 )-        𝑛·0·107 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·104 ⟧( v ↦ 1 )-        𝑛·0·108 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·109 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·107 ⟧( v ↦ 1 )-        𝑛·0·110 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·108 ⟧( v ↦ 1 )-        𝑛·0·111 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·112 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·111 ⟧( v ↦ 1 )-        𝑛·0·113 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·114 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        starved(4)-        starved(4)-        starved(4)-        starved(4)-        𝑛·0·115 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·100 ⟧( v ↦ 1 )-        𝑛·0·116 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·105 ⟧( v ↦ 1 )-        𝑛·0·117 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·109 ⟧( v ↦ 1 )-        𝑛·0·118 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·112 ⟧( v ↦ 1 )-        𝑛·0·119 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·114 ⟧( v ↦ 1 )-        𝑛·0·120 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·121 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·117 ⟧( v ↦ 1 )-        𝑛·0·122 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·118 ⟧( v ↦ 1 )-        𝑛·0·123 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·119 ⟧( v ↦ 1 )-        𝑛·0·124 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·120 ⟧( v ↦ 1 )-        𝑛·0·125 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·126 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·123 ⟧( v ↦ 1 )-        𝑛·0·127 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·124 ⟧( v ↦ 1 )-        𝑛·0·128 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·125 ⟧( v ↦ 1 )-        𝑛·0·129 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·130 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·128 ⟧( v ↦ 1 )-        𝑛·0·131 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·129 ⟧( v ↦ 1 )-        𝑛·0·132 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·133 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·132 ⟧( v ↦ 1 )-        𝑛·0·134 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-        𝑛·0·135 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )+      𝑛1·1 := ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ L_none:λ ⟧ ⟧ ⟧.φ  # 𝕄(ξ.x)+      𝕄(Φ.a🌵3):         𝔼(L_outer):           stall(L_outer)-      𝑛·0·136 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·121 ⟧( v ↦ 1 )-      𝑛·0·137 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·126 ⟧( v ↦ 1 )-      𝑛·0·138 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·130 ⟧( v ↦ 1 )-      𝑛·0·139 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·133 ⟧( v ↦ 1 )-      𝑛·0·140 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·135 ⟧( v ↦ 1 )-      𝑛·0·141 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·142 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·138 ⟧( v ↦ 1 )-      𝑛·0·143 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·139 ⟧( v ↦ 1 )-      𝑛·0·144 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·140 ⟧( v ↦ 1 )-      𝑛·0·145 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·141 ⟧( v ↦ 1 )-      𝑛·0·146 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·147 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·144 ⟧( v ↦ 1 )-      𝑛·0·148 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·145 ⟧( v ↦ 1 )-      𝑛·0·149 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·146 ⟧( v ↦ 1 )-      𝑛·0·150 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·151 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·149 ⟧( v ↦ 1 )-      𝑛·0·152 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·150 ⟧( v ↦ 1 )-      𝑛·0·153 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·154 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·153 ⟧( v ↦ 1 )-      𝑛·0·155 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝑛·0·156 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-      𝔼(L_outer):-        stall(L_outer)-    𝑛·0·157 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·142 ⟧( v ↦ 1 )-    𝑛·0·158 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·147 ⟧( v ↦ 1 )-    𝑛·0·159 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·151 ⟧( v ↦ 1 )-    𝑛·0·160 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·154 ⟧( v ↦ 1 )-    𝑛·0·161 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·156 ⟧( v ↦ 1 )-    𝑛·0·162 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·163 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·158 ⟧( v ↦ 1 )-    𝑛·0·164 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·159 ⟧( v ↦ 1 )-    𝑛·0·165 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·160 ⟧( v ↦ 1 )-    𝑛·0·166 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·161 ⟧( v ↦ 1 )-    𝑛·0·167 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·162 ⟧( v ↦ 1 )-    𝑛·0·168 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·169 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·165 ⟧( v ↦ 1 )-    𝑛·0·170 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·166 ⟧( v ↦ 1 )-    𝑛·0·171 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·167 ⟧( v ↦ 1 )-    𝑛·0·172 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·168 ⟧( v ↦ 1 )-    𝑛·0·173 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·174 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·170 ⟧( v ↦ 1 )-    𝑛·0·175 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·171 ⟧( v ↦ 1 )-    𝑛·0·176 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·172 ⟧( v ↦ 1 )-    𝑛·0·177 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·173 ⟧( v ↦ 1 )-    𝑛·0·178 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·179 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·176 ⟧( v ↦ 1 )-    𝑛·0·180 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·177 ⟧( v ↦ 1 )-    𝑛·0·181 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·178 ⟧( v ↦ 1 )-    𝑛·0·182 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·183 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·180 ⟧( v ↦ 1 )-    𝑛·0·184 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·181 ⟧( v ↦ 1 )-    𝑛·0·185 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·182 ⟧( v ↦ 1 )-    𝑛·0·186 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·187 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·185 ⟧( v ↦ 1 )-    𝑛·0·188 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·186 ⟧( v ↦ 1 )-    𝑛·0·189 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·190 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·188 ⟧( v ↦ 1 )-    𝑛·0·191 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·189 ⟧( v ↦ 1 )-    𝑛·0·192 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·193 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·192 ⟧( v ↦ 1 )-    𝑛·0·194 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·195 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·194 ⟧( v ↦ 1 )-    𝑛·0·196 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·197 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )-    𝑛·0·198 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )+      𝑛2·1 := ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ L_none:λ ⟧ ⟧ ⟧.φ  # 𝕄(ξ.y)+      𝑛·1·1 := 𝜎1:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎1:λ:φ  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/tells-two-carried-symbols-apart-in-one-firing.yaml view
@@ -12,19 +12,16 @@       𝛿2: $.x     𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) location: 'Q.@'-deep: true input: >   ⟦ bytes(φ) ↦ ⟦⟧,     number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,     @ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ).plus(-          Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ) ⟧-result: '⟦ φ ↦ 𝜎6:λ, plus(x) ↦ L_number_plus:λ ⟧'+          Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ).φ ⟧+result: '𝜎6:λ' protocol: |   𝕄(Φ.φ):     𝑛·0·1 := Φ.number( φ ↦ 𝜎2:λ )     𝑛·0·2 := 𝑛·0·1.plus( x ↦ Φ.number( φ ↦ 𝜎5:λ ) )-    𝕄(Φ.φ.x):-      𝑛·0·3 := Φ.number( φ ↦ 𝜎5:λ )     𝔼(L_number_plus):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):
test-resources/evaluate-packs/tells-two-symbols-of-one-datum-apart-between-firings.yaml view
@@ -8,47 +8,56 @@ # the same bytes, yet they stand for two values nobody worked out. A memo # keyed on the bytes would answer the second firing with the symbol the first # one minted and make the two values one; this one keys on the symbol, so each-# firing mints a symbol of its own.+# firing mints a symbol of its own. The two firings are the two 'morph'+# operands of 'L_pair', each fired by the dispatch of φ off its call. symbolic: |   - λ: L_dataized     dataize:       𝛿1: $.target     𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-deep: true+  - λ: L_pair+    morph:+      𝑛1: $.x+      𝑛2: $.y+    𝑛: ⟦ φ ↦ ⟦ x ↦ 𝑛1, y ↦ 𝑛2 ⟧ ⟧+location: 'Q.@' acyclic: plausible input: >   ⟦ bytes(φ) ↦ ⟦⟧,     dataized(target) ↦ L_dataized:λ,-    x ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ),-    y ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ) ⟧-result: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    dataized(target) ↦ L_dataized:λ,-    x ↦ ⟦ φ ↦ 𝜎3:λ ⟧,-    y ↦ ⟦ φ ↦ 𝜎4:λ ⟧ ⟧+    pair(x, y) ↦ L_pair:λ,+    @ ↦ Φ.pair(+      Φ.dataized( Φ.bytes( φ ↦ 𝜎1:λ ) ).φ,+      Φ.dataized( Φ.bytes( φ ↦ 𝜎2:λ ) ).φ ).φ ⟧+result: '⟦ x ↦ 𝜎3:λ, y ↦ 𝜎4:λ ⟧' protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      𝑛·0·1 := Φ.bytes( φ ↦ 𝜎1:λ )-      𝑛·0·2 := Φ.dataized( target ↦ 𝑛·0·1 )-      𝔼(L_dataized):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )-        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)-        𝑛·1·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛-        𝕄(𝑛·1·2):-          𝑛·1·3 := Φ.bytes( φ ↦ 𝜎3:λ )-        𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)-    𝕄(Φ.y):-      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛·0·4 := Φ.dataized( target ↦ 𝑛·0·3 )-      𝔼(L_dataized):-        𝔻(Φ.a🌵1):-          𝕄(Φ.a🌵1):-            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )-        𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.target)-        𝑛·2·2 := Φ.bytes( φ ↦ 𝜎4:λ )  # 𝑛-        𝕄(𝑛·2·2):-          𝑛·2·3 := Φ.bytes( φ ↦ 𝜎4:λ )-        𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)+  𝕄(Φ.φ):+    𝑛·0·1 := Φ.pair( x ↦ Φ.dataized( Φ.bytes( φ ↦ 𝜎1:λ ) ).φ )+    𝑛·0·2 := 𝑛·0·1( y ↦ Φ.dataized( Φ.bytes( φ ↦ 𝜎2:λ ) ).φ )+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )+        𝔼(L_dataized):+          𝔻(Φ.a🌵1):+            𝕄(Φ.a🌵1):+              𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )+          𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)+          𝑛·2·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛+          𝕄(𝑛·2·2):+            𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )+          𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)+      𝑛1·1 := 𝜎3:λ  # 𝕄(ξ.x)+      𝕄(Φ.a🌵2):+        𝑛·1·2 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎2:λ ) )+        𝔼(L_dataized):+          𝔻(Φ.a🌵3):+            𝕄(Φ.a🌵3):+              𝑛·3·1 := Φ.bytes( φ ↦ 𝜎2:λ )+          𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.target)+          𝑛·3·2 := Φ.bytes( φ ↦ 𝜎4:λ )  # 𝑛+          𝕄(𝑛·3·2):+            𝑛·3·3 := Φ.bytes( φ ↦ 𝜎4:λ )+          𝑛·3·4 := 𝑛·3·3  # 𝕄(𝑛·3·2)+      𝑛2·1 := 𝜎4:λ  # 𝕄(ξ.y)+      𝑛·1·3 := ⟦ x ↦ 𝜎3:λ, y ↦ 𝜎4:λ ⟧:φ  # 𝑛+      𝑛·1·4 := ⟦ x ↦ 𝜎3:λ, y ↦ 𝜎4:λ ⟧:φ  # 𝕄(𝑛·1·3)
− test-resources/evaluate-packs/walks-a-binding-of-an-object-once-however-many-copies-hold-it.yaml
@@ -1,73 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The synonym of '--acyclic=plausible': every dispatch on 'Φ.num' copies the-# object, and the deep walk entered the bindings of every copy as if they were-# new, so the test of 'num' was brought to its normal form and fired once-# under 'a', once under 'b' and once under 'c' (#1480). The copy goes by a-# name in the world, 'Φ.num( φ ↦ … )', and the object part of that name is a-# synonym of it, so the walk enters 'test' of 'Φ.num' in the first copy it-# meets and leaves it as written in every later one. The φ each copy fills is-# an argument of that copy and not a binding of 'Φ.num', so it is entered-# every time.-symbolic: |-  - λ: L_id-    morph:-      𝑛1: $.x-    𝑛: 𝑛1-  - λ: L_twice-    dataize:-      𝛿1: $.ρ-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-deep: true-partial: true-acyclic: plausible-steps: 32-input: >-  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧, test ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ).twice ⟧,-    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ⟧,-    b ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2B- ⟧ ) ⟧,-    c ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2C- ⟧ ) ⟧ ⟧-protocol: |-  𝕄(Φ):-    𝕄(Φ.a):-      𝕄(Φ.a.x):-        𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )-      𝔼(L_id):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.num( φ ↦ 2A-:Δ )-          𝕄(Φ.a🌵0.test):-            𝑛·1·2 := Φ.num( φ ↦ 01-:Δ )-            𝑛·1·3 := Φ.num( φ ↦ 01-:Δ )-            𝔼(L_twice):-              𝔻(Φ.a🌵1):-                𝕄(Φ.a🌵1):-                  𝑛·2·1 := Φ.num( φ ↦ 01-:Δ )-                𝛿·2·1 := 01--              𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-              𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛-              𝕄(𝑛·2·2):-                𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )-              𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)-        𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)-        𝑛·1·4 := 𝑛·1·1  # 𝑛-        𝑛·1·5 := 𝑛·1·1  # 𝕄(𝑛·1·4)-    𝕄(Φ.b):-      𝕄(Φ.b.x):-        𝑛·0·2 := Φ.num( φ ↦ 2B-:Δ )-      𝔼(L_id):-        𝕄(Φ.a🌵2):-          𝑛·3·1 := Φ.num( φ ↦ 2B-:Δ )-        𝑛1·3 := 𝑛·3·1  # 𝕄(ξ.x)-        𝑛·3·2 := 𝑛·3·1  # 𝑛-        𝑛·3·3 := 𝑛·3·1  # 𝕄(𝑛·3·2)-    𝕄(Φ.c):-      𝕄(Φ.c.x):-        𝑛·0·3 := Φ.num( φ ↦ 2C-:Δ )-      𝔼(L_id):-        𝕄(Φ.a🌵3):-          𝑛·4·1 := Φ.num( φ ↦ 2C-:Δ )-        𝑛1·4 := 𝑛·4·1  # 𝕄(ξ.x)-        𝑛·4·2 := 𝑛·4·1  # 𝑛-        𝑛·4·3 := 𝑛·4·1  # 𝕄(𝑛·4·2)
− test-resources/evaluate-packs/walks-a-binding-reading-its-copy-in-every-copy.yaml
@@ -1,80 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The synonym of '--acyclic=plausible' lets the walk enter a binding of an-# object of the world in the first copy it meets and leave it as written in-# every later one (#1480). A body reading ξ reads the copy it stands in, and-# two copies filling 'v' with different numbers make two different programs of-# the φ of 'dbl', so that φ is walked in every copy and 'L_twice' fires under-# 'a' and under 'b', each with a symbol of its own (#1485).-symbolic: |-  - λ: L_id-    morph:-      𝑛1: $.x-    𝑛: 𝑛1-  - λ: L_twice-    dataize:-      𝛿1: $.ρ-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-deep: true-partial: true-acyclic: plausible-steps: 32-input: >-  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧ ⟧,-    dbl ↦ ⟦ v ↦ ∅, φ ↦ ξ.v.twice ⟧,-    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ) ⟧,-    b ↦ ⟦ λ ⤍ L_id, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2B- ⟧ ) ) ⟧ ⟧-protocol: |-  𝕄(Φ):-    𝕄(Φ.a):-      𝕄(Φ.a.x):-        𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )-        𝑛·0·2 := Φ.dbl( v ↦ 𝑛·0·1 )-      𝔼(L_id):-        𝕄(Φ.a🌵0):-          𝑛·1·1 := Φ.dbl( v ↦ 𝑛·0·1 )-          𝕄(Φ.a🌵0.v):-            𝑛·1·2 := Φ.num( φ ↦ 2A-:Δ )-          𝕄(Φ.a🌵0.φ):-            𝑛·1·3 := Φ.num( φ ↦ 2A-:Δ )-            𝑛·1·4 := Φ.num( φ ↦ 2A-:Δ )-            𝔼(L_twice):-              𝔻(Φ.a🌵1):-                𝕄(Φ.a🌵1):-                  𝑛·2·1 := Φ.num( φ ↦ 2A-:Δ )-                𝛿·2·1 := 2A--              𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-              𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛-              𝕄(𝑛·2·2):-                𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )-              𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)-        𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)-        𝑛·1·5 := 𝑛1·1  # 𝑛-        𝑛·1·6 := 𝑛1·1  # 𝕄(𝑛·1·5)-    𝕄(Φ.b):-      𝕄(Φ.b.x):-        𝑛·0·3 := Φ.num( φ ↦ 2B-:Δ )-        𝑛·0·4 := Φ.dbl( v ↦ 𝑛·0·3 )-      𝔼(L_id):-        𝕄(Φ.a🌵2):-          𝑛·3·1 := Φ.dbl( v ↦ 𝑛·0·3 )-          𝕄(Φ.a🌵2.v):-            𝑛·3·2 := Φ.num( φ ↦ 2B-:Δ )-          𝕄(Φ.a🌵2.φ):-            𝑛·3·3 := Φ.num( φ ↦ 2B-:Δ )-            𝑛·3·4 := Φ.num( φ ↦ 2B-:Δ )-            𝔼(L_twice):-              𝔻(Φ.a🌵3):-                𝕄(Φ.a🌵3):-                  𝑛·4·1 := Φ.num( φ ↦ 2B-:Δ )-                𝛿·4·1 := 2B--              𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)-              𝑛·4·2 := Φ.num( φ ↦ 𝜎2:λ )  # 𝑛-              𝕄(𝑛·4·2):-                𝑛·4·3 := Φ.num( φ ↦ 𝜎2:λ )-              𝑛·4·4 := 𝑛·4·3  # 𝕄(𝑛·4·2)-        𝑛1·3 := 𝑛·3·1  # 𝕄(ξ.x)-        𝑛·3·5 := 𝑛1·3  # 𝑛-        𝑛·3·6 := 𝑛1·3  # 𝕄(𝑛·3·5)
− test-resources/evaluate-packs/walks-on-past-a-binding-it-parked-on.yaml
@@ -1,24 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A deep walk under '_partial': the binding stuck on a λ function no entry-# answers is left as it is, and the walk goes on to the binding after it-# instead of giving the whole run up.-symbolic: |-  - λ: L_answers-    𝑛: ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎 ⟧-location: 'Q'-deep: true-partial: true-input: >-  ⟦ x ↦ ⟦ λ ⤍ L_none, ρ ↦ ∅ ⟧.foo,-    y ↦ ⟦ λ ⤍ L_answers, ρ ↦ ∅ ⟧.foo ⟧-result: '⟦ x ↦ L_none:λ.foo, y ↦ L_answers:λ.foo ⟧'-protocol: |-  𝕄(Φ):-    𝕄(Φ.x):-      unanswered(L_none)  # 𝕄(L_none:λ)-    𝕄(Φ.y):-      𝔼(L_answers):-        𝑛·1·1 := ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎1 ⟧  # 𝑛-        𝑛·1·2 := ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎1 ⟧  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/walks-on-past-a-firing-it-could-not-make.yaml
@@ -1,36 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A firing the deep walk opened and could not make: the entry of 'L_root'-# dataizes what it was applied to, that operand never comes down to data, and-# the firing is parked the way a term 𝕄 could not reduce is. The walk goes on-# to the binding after it instead of ending the whole run, so one argument-# nothing can dataize taints its own binding and nothing else (#1288).-symbolic: |-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧-location: 'Q'-deep: true-partial: true-input: >-  ⟦ root(v) ↦ ⟦ λ ⤍ L_root ⟧,-    e1 ↦ Φ.root( ⟦ λ ⤍ L_none ⟧.foo ),-    e2 ↦ Φ.root( ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧-protocol: |-  𝕄(Φ):-    𝕄(Φ.e1):-      unanswered(L_none)  # 𝕄(L_none:λ)-      𝑛·0·1 := Φ.root( v ↦ L_none:λ.foo )-      𝔼(L_root):-        𝔻(Φ.a🌵0):-          𝕄(Φ.a🌵0):-            unanswered(L_none)  # 𝕄(L_none:λ)-        stuck(L_none)-    𝕄(Φ.e2):-      𝑛·0·2 := Φ.root( v ↦ 𝜎1:λ )-      𝔼(L_root):-        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)-        𝑛·2·1 := ⟦⟧  # 𝑛-        𝑛·2·2 := ⟦⟧  # 𝕄(𝑛·2·1)
− test-resources/evaluate-packs/writes-the-binding-a-deep-firing-stands-in.yaml
@@ -1,77 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The site a firing is written under, which the '--deep' walk refines as it-# enters a binding: both firings are of the same entry and answer the same way,-# so the locator standing beside them is the one thing telling a reader which-# part of the program each of them belongs to. A locator reaches a binding and-# no further: the λ function of the first one fires on a dispatch standing-# inside 'mark', which no locator names, so the firing is written under-# 'Φ.foo.mark' — the binding the walk had entered.-symbolic: |-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q'-deep: true-input: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,-    foo ↦ ⟦ mark ↦ 5.plus( 6 ) ⟧,-    bar ↦ 7.plus( 8 ) ⟧-protocol: |-  𝕄(Φ):-    𝕄(Φ.foo):-      𝕄(Φ.foo.mark):-        𝑛·0·1 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-        𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )-        𝑛·0·3 := Φ.number( φ ↦ 𝑛·0·1 )-        𝑛·0·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-        𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )-        𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·1 )-        𝑛·0·7 := 𝑛·0·6.plus( x ↦ 𝑛·0·5 )-        𝔼(L_number_plus):-          𝔻(Φ.a🌵0):-            𝕄(Φ.a🌵0):-              𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )-              𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )-            𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0-          𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)-          𝔻(Φ.a🌵1):-            𝕄(Φ.a🌵1):-              𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·4 )-              𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )-            𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0-          𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)-          𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛-          𝕄(𝑛·1·5):-            𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )-          𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)-    𝕄(Φ.bar):-      𝑛·0·8 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·8 )-      𝑛·0·10 := Φ.number( φ ↦ 𝑛·0·8 )-      𝑛·0·11 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-      𝑛·0·12 := Φ.number( φ ↦ 𝑛·0·11 )-      𝑛·0·13 := Φ.number( φ ↦ 𝑛·0·8 )-      𝑛·0·14 := 𝑛·0·13.plus( x ↦ 𝑛·0·12 )-      𝔼(L_number_plus):-        𝔻(Φ.a🌵2):-          𝕄(Φ.a🌵2):-            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·8 )-            𝑛·2·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )-          𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0-        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)-        𝔻(Φ.a🌵3):-          𝕄(Φ.a🌵3):-            𝑛·2·3 := Φ.number( φ ↦ 𝑛·0·11 )-            𝑛·2·4 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )-          𝛿·2·2 := 40-20-00-00-00-00-00-00  # 8.0-        𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)-        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛-        𝕄(𝑛·2·5):-          𝑛·2·6 := Φ.number( φ ↦ 𝜎2:λ )-        𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)
test-resources/explain-packs/morphing/ml.yaml view
@@ -5,7 +5,7 @@ latex: |-   \begin{phinoMorphingInference}     \phinoName{ml}-    \phinoLabel{\lambda}+    \phinoLabel{\uplambda}     \phinoPremise{ \phinoEvaluate{ [[ B_1, L> f, B_2 ]] }{ e }{ s_1 }{ n_1 }{ s_2 } }     \phinoPremise{ \phinoNormalize{ n_1 . \tau }{ n_2 } }     \phinoPremise{ \phinoMorph{ n_2 }{ e }{ s_2 }{ n_3 }{ s_3 } }
test-resources/explain-packs/morphing/mphi.yaml view
@@ -5,7 +5,7 @@ latex: |-   \begin{phinoMorphingInference}     \phinoName{mphi}-    \phinoLabel{\varphi}+    \phinoLabel{\upvarphi}     \phinoCondition{ @ \in B \phiText{and} [ \tau \char44{} L ] \cap B = \emptyset }     \phinoPremise{ \phinoNormalize{ [[ B ]] . @ . \tau }{ n_1 } }     \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
test-resources/explain-packs/morphing/xi.yaml view
@@ -5,6 +5,7 @@ latex: |-   \begin{phinoMorphingInference}     \phinoName{xi}+    \phinoLabel{\upxi}     \phinoPremise{ \phinoMorph{ T }{ e }{ s_1 }{ n }{ s_2 } }     \phinoConclusion{ \phinoMorph{ \phiTerminal{\xi} }{ e }{ s_1 }{ n }{ s_2 } }   \end{phinoMorphingInference}
− test-resources/morph-deep-packs/dont-fire-a-lambda-an-unapplied-rho-filled-with-bottom.yaml
@@ -1,10 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# The same shape with nothing around the unapplied formation: the ⊥ still-# fills the only void of 'plus', so 'saturated' would let it fire (#1196).-location: 'Q.@'-model: true-symbolic: true-input: '[[ a -> [[ @ -> 5.plus( $.^ ) ]] ]]'-result: '5.plus( ρ ):φ:a'
− test-resources/morph-deep-packs/fails-on-a-stuck-atom-deep-on-a-spine.yaml
@@ -1,7 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# An atom deeper on a binding's spine gets stuck exactly as it does under 𝕄-# alone: the run fails, unless '_partial' parks it.-input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'-fails: "No entry of --symbolic answers the λ function 'Sym_arg_0'"
− test-resources/morph-deep-packs/fires-the-atom-nested-in-an-argument.yaml
@@ -1,20 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# The λ function nested in the argument fires before the one around it, so-# the symbol standing for the whole sum is the second the run minted.-location: 'Q.@'-model: true-symbolic: true-input: '[[ x -> 5.plus( 6.plus( 7 ) ) ]]'-result: |--  ⟦-    φ ↦ 𝜎2:λ,-    as-bytes ↦ φ,-    plus(ρ, x) ↦ L_number_plus:λ,-    times(ρ, x) ↦ L_number_times:λ,-    div(ρ, x) ↦ L_number_div:λ,-    gt(ρ, x) ↦ L_number_gt:λ,-    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,-    nope(ρ) ↦ L_number_nope:λ-  ⟧:x
− test-resources/morph-deep-packs/keeps-the-answer-of-the-last-atom-in-a-chain.yaml
@@ -1,20 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# Two firings down a chain of dispatches, and what stands is the symbol the-# last of them minted: the first is what the second brought down.-location: 'Q.@'-model: true-symbolic: true-input: '[[ x -> 5.plus( 6 ).plus( 7 ) ]]'-result: |--  ⟦-    φ ↦ 𝜎2:λ,-    as-bytes ↦ φ,-    plus(ρ, x) ↦ L_number_plus:λ,-    times(ρ, x) ↦ L_number_times:λ,-    div(ρ, x) ↦ L_number_div:λ,-    gt(ρ, x) ↦ L_number_gt:λ,-    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,-    nope(ρ) ↦ L_number_nope:λ-  ⟧:x
− test-resources/morph-deep-packs/leaves-a-lambda-no-entry-answers-alone.yaml
@@ -1,10 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# No entry of the file answers 'L_number_nope', so there is nothing to fire-# and the binding keeps the name it was written under.-location: 'Q.@'-model: true-symbolic: true-input: '[[ x -> 5.nope ]]'-result: '5.nope:x'
− test-resources/morph-deep-packs/leaves-a-lambda-still-waiting-for-arguments-alone.yaml
@@ -1,13 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A λ-formation whose bindings are still void is a method waiting to be-# applied, not an application waiting to be computed: nothing demands one,-# so 𝔻 never meets one, while the walk meets every one the object model-# declares. Both the void ρ of 'not' and the void 'b' of 'eq' keep their-# atoms unfired here.-location: 'Q.bytes'-model: true-symbolic: true-input: '[[ ]]'-result: '⟦ φ ↦ ∅, not(ρ) ↦ L_bytes_not:λ, eq(ρ, b) ↦ L_bytes_eq:λ ⟧'
− test-resources/morph-deep-packs/leaves-an-unapplied-formation-reaching-its-rho-alone.yaml
@@ -1,12 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A formation standing unapplied inside its parent still holds ρ ↦ ∅, so the-# ξ.ρ its body reaches is an input the next dispatch supplies, not ⊥. The-# walk reduces that body in the formation's own scope, where ρ is void, and-# must not hand the ⊥ that comes back to the λ of 'plus' (#1196).-location: 'Q.@'-model: true-symbolic: true-input: '[[ foo -> [[ n -> ?, a -> [[ @ -> 5.plus( $.^ ) ]] ]] ]]'-result: '⟦ foo(n) ↦ 5.plus( ρ ):φ:a ⟧'
− test-resources/morph-deep-packs/parks-a-stuck-atom-deep-on-a-spine.yaml
@@ -1,8 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# The same stuck atom under '_partial': the binding stays as it was written-# and the walk goes on.-partial: true-input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'-result: 'Sym_arg_0:λ.foo:x'
− test-resources/morph-deep-packs/resolves-a-xi-against-the-formation-holding-it.yaml
@@ -1,23 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# The ξ of a binding reaches the formation the binding sits in, so 'n' is-# what the firing brings down, and a symbol stands where its answer belongs.-location: 'Q.@'-model: true-symbolic: true-input: '[[ n -> 5, x -> $.n.plus( 6 ) ]]'-result: |--  ⟦-    n ↦ 5,-    x ↦ ⟦-      φ ↦ 𝜎1:λ,-      as-bytes ↦ φ,-      plus(ρ, x) ↦ L_number_plus:λ,-      times(ρ, x) ↦ L_number_times:λ,-      div(ρ, x) ↦ L_number_div:λ,-      gt(ρ, x) ↦ L_number_gt:λ,-      eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,-      nope(ρ) ↦ L_number_nope:λ-    ⟧-  ⟧
− test-resources/morph-deep-packs/stands-the-answer-of-a-bare-lambda-in-its-place.yaml
@@ -1,20 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 'mf' hands a bare saturated λ-formation back and leaves firing it to 𝔻;-# the walk fires it and stands the symbol it answered with in its place.-location: 'Q.@'-model: true-symbolic: true-input: '[[ x -> 5.plus( 6 ) ]]'-result: |--  ⟦-    φ ↦ 𝜎1:λ,-    as-bytes ↦ φ,-    plus(ρ, x) ↦ L_number_plus:λ,-    times(ρ, x) ↦ L_number_times:λ,-    div(ρ, x) ↦ L_number_div:λ,-    gt(ρ, x) ↦ L_number_gt:λ,-    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,-    nope(ρ) ↦ L_number_nope:λ-  ⟧:x
− test-resources/morph-deep-packs/walks-into-the-argument-of-an-atom-it-cannot-fire.yaml
@@ -1,22 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# Nothing demands the argument of a λ function that cannot fire, so 𝔻 never-# reaches it; the walk does, and the λ function around it stays in place.-location: 'Q.@'-model: true-symbolic: true-input: '[[ x -> [[ y -> ?, L> L_bar ]]( y -> 6.plus( 7 ) ) ]]'-result: |--  ⟦ y ↦ ∅, λ ⤍ L_bar ⟧(-    y ↦ ⟦-      φ ↦ 𝜎1:λ,-      as-bytes ↦ φ,-      plus(ρ, x) ↦ L_number_plus:λ,-      times(ρ, x) ↦ L_number_times:λ,-      div(ρ, x) ↦ L_number_div:λ,-      gt(ρ, x) ↦ L_number_gt:λ,-      eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,-      nope(ρ) ↦ L_number_nope:λ-    ⟧-  ):x
test/CLISpec.hs view
@@ -12,7 +12,7 @@ import Control.Exception import Control.Monad (forM_, unless) import Data.Char (isDigit)-import Data.List (find, intercalate, isInfixOf, isPrefixOf, sort)+import Data.List (intercalate, isInfixOf, isPrefixOf, sort) import Data.Text qualified as T import Data.Time.Clock (addUTCTime, getCurrentTime) import Data.Time.Clock.POSIX (getPOSIXTime)@@ -1444,6 +1444,32 @@         withStdin "⟦ t ↦ ⟦ Δ ⤍ 01-02 ⟧ ⟧" $           testCLISucceeded ["dataize", "--locator=Q.t", "--acyclic=proven"] ["01-02"] +    describe "--max-loops" $ do+      let once = "⟦ f ↦ ⟦ x ↦ ∅, φ ↦ ξ.x.again ⟧, start ↦ ⟦ again ↦ Φ.f( x ↦ Φ.stop( v ↦ Φ.start ) ) ⟧, stop ↦ ⟦ v ↦ ∅, again ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, t ↦ Φ.f( x ↦ Φ.start ) ⟧"+      it "cuts a recursion that stops after one round without the flag" $+        withStdin once $+          testCLIFailed+            ["dataize", "--locator=Q.t", "--acyclic=plausible"]+            ["[ERROR]: Reduction entered a formation it is already inside:"]++      it "dataizes a recursion that stops after one round with --max-loops=1" $+        withStdin once $+          testCLISucceeded ["dataize", "--locator=Q.t", "--acyclic=plausible", "--max-loops=1"] ["01-"]++      it "cuts a recursion that never stops with --max-loops=1" $+        withStdin "⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧, t ↦ Φ.cyc( ⟦⟧ ) ⟧" $+          testCLIFailed+            ["dataize", "--locator=Q.t", "--acyclic=proven", "--max-loops=1", "--max-steps=4000"]+            ["[ERROR]: Reduction entered a formation it is already inside:"]++      it "refuses a negative count" $+        withStdin once $+          testCLIFailed ["dataize", "--locator=Q.t", "--acyclic=plausible", "--max-loops=-1"] ["--max-loops must be >= 0"]++      it "refuses the flag without --acyclic" $+        withStdin once $+          testCLIFailed ["dataize", "--locator=Q.t", "--max-loops=1"] ["The option --max-loops requires --acyclic, since only --acyclic cuts a loop"]+     it "dataizes with --sequence" $       withStdin "[[ @ -> [[ x -> [[ D> 01-, y -> ? ]](y -> [[ ]]) ]].x ]]" $         testCLISucceeded@@ -1811,48 +1837,23 @@                        , "    𝑛·1·2 := 𝜎1:λ:z  # 𝕄(𝑛·1·1)"                        ] -      it "writes a deferred copy as a call of the object of the world it was made of" $-        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-          hClose stream-          withStdin "⟦ box(x) ↦ ⟦ φ ↦ x.next ⟧, y ↦ Φ.box( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" $-            testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records `shouldContain` ["  deferred(𝜎2) := Φ.box( x ↦ 𝜎1:λ )"]--      it "writes a deferred copy as it stands when the world declares no object it was made of" $-        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-          hClose stream-          withStdin "⟦ wrap(v) ↦ ⟦⟧, y ↦ Φ.wrap( v ↦ ⟦ b(x) ↦ ⟦ φ ↦ x.next ⟧ ⟧ ).v.b( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" $-            testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records `shouldContain` ["  𝑛·0·2 := ⟦ x ↦ ∅, φ ↦ x.next ⟧( x ↦ 𝜎1:λ )", "  deferred(𝜎2) := 𝑛·0·2"]--      it "writes the symbol a cut answers a copy with" $-        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-          hClose stream-          withLambdasOf (T.pack "- λ: L_loop\n  morph:\n    𝑛1: $.x\n  𝑛: 𝑛1\n") $ \loops ->-            withStdin "⟦ box(n) ↦ ⟦ φ ↦ Φ.loop( x ↦ Φ.box( n ↦ ξ.n ) ) ⟧, loop(x) ↦ L_loop:λ, y ↦ Φ.loop( x ↦ Φ.box( n ↦ ⟦ Δ ⤍ 01- ⟧ ) ) ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ loops, "--deep", "--acyclic=proven", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records `shouldContain` ["        𝑛·1·3 := Φ.loop( x ↦ 𝑛·1·2 )", "        looped(𝑛·1·3) := 𝜎1  # proven"]-       it "writes a told stall to the XML protocol" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do           hClose stream-          withLambdasOf (T.pack "- λ: L_outer\n  dataize:\n    𝛿1: ξ.arg\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n") $ \outer ->-            withStdin "⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧, y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧ ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ outer, "--deep", "--partial", "--acyclic=plausible", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []+          withLambdasOf (T.pack "- λ: L_outer\n  dataize:\n    𝛿1: ξ.arg\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n- λ: L_pair\n  morph:\n    𝑛1: $.x.φ\n    𝑛2: $.y.φ\n  𝑛: ⟦⟧\n") $ \outer ->+            withStdin "⟦ p ↦ ⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧, y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧, λ ⤍ L_pair ⟧.φ ⟧" $+              testCLISucceeded ["morph", "--symbolic=" ++ outer, "--locator=Q.p", "--partial", "--acyclic=plausible", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []           records <- readProtocol path-          lines records `shouldContain` ["      <stall λ=\"L_none\"/>"]+          lines records `shouldContain` ["        <stall λ=\"L_none\"/>"]        it "writes a stuck firing to the XML protocol" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do           hClose stream           withLambdasOf (T.pack "- λ: L_outer\n  dataize:\n    𝛿1: ξ.arg\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n") $ \outer ->-            withStdin "⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_absent ⟧, λ ⤍ L_outer ⟧ ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ outer, "--deep", "--partial", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []+            withStdin "⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_absent ⟧, λ ⤍ L_outer ⟧.φ ⟧" $+              testCLISucceeded ["morph", "--symbolic=" ++ outer, "--locator=Q.x", "--partial", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []           records <- readProtocol path-          lines records `shouldContain` ["      <unfinished λ=\"L_absent\"/>"]+          lines records `shouldContain` ["    <unfinished λ=\"L_absent\"/>"]        it "writes a starved step budget to the XML protocol" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do@@ -2173,70 +2174,6 @@                          , "</morph>"                          ] -        it "writes the copy a deferred symbol stands for as an element of its own" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withStdin "⟦ box(x) ↦ ⟦ φ ↦ x.next ⟧, y ↦ Φ.box( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" $-              testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records-              `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"-                         , "<morph at=\"Φ.y\">"-                         , "  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ.y\" of=\"Φ.box\"><attr name=\"x\">𝜎1</attr></applied>"-                         , "  <deferred symbol=\"𝜎2\" by=\"morph\" at=\"Φ.y\" of=\"Φ.box\"><with><attr name=\"x\">𝜎1</attr></with><e>𝑛·0·1</e></deferred>"-                         , "</morph>"-                         ]--        it "names the object a deferred copy was made of through the formation its ρ holds" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withStdin "⟦ joined(items) ↦ ⟦ φ ↦ step( tup ↦ items ), step(ρ, tup) ↦ ⟦ φ ↦ tup.next ⟧ ⟧, y ↦ Φ.joined( items ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).φ ⟧" $-              testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["  <deferred symbol=\"𝜎2\" by=\"morph\" at=\"Φ.y\" of=\"Φ.joined.step\"><with><attr name=\"tup\">𝜎1</attr></with><e>𝑛·0·2</e></deferred>"]--        it "names the object a deferred copy was made of after the walk wrote an answer into its ρ" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_dataized\n  dataize:\n    𝛿1: $.target\n  𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )\n") $ \dataized ->-              withStdin "⟦ bytes(φ) ↦ ⟦⟧, dataized(target) ↦ L_dataized:λ, joined(items) ↦ ⟦ φ ↦ step( tup ↦ items, s ↦ sep ), sep ↦ Φ.dataized( target ↦ items ), step(ρ, tup, s) ↦ ⟦ φ ↦ tup.next ⟧ ⟧, y ↦ Φ.joined( items ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).φ ⟧" $-                testCLISucceeded ["morph", "--deep", "--symbolic=" ++ dataized, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["  <deferred symbol=\"𝜎3\" by=\"morph\" at=\"Φ.y\" of=\"Φ.joined.step\"><with><attr name=\"tup\">𝜎1</attr><attr name=\"s\">𝜎2</attr></with><e>𝑛·0·4</e></deferred>"]--        it "writes a question mark for an argument of a deferred copy that is no bare symbol" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withStdin "⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, y ↦ Φ.box( x ↦ ⟦ λ ⤍ 𝜎1 ⟧, w ↦ ⟦ z ↦ Φ ⟧ ) ⟧" $-              testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["  <deferred symbol=\"𝜎2\" by=\"morph\" at=\"Φ.y\" of=\"Φ.box\"><with><attr name=\"x\">𝜎1</attr><attr name=\"w\">?</attr></with><e>𝑛·0·2</e></deferred>"]--        it "writes the object a deferred copy was made of whatever --abridged says" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withStdin "⟦ joined(items) ↦ ⟦ φ ↦ step( tup ↦ items ), step(ρ, tup) ↦ ⟦ φ ↦ tup.next ⟧ ⟧, y ↦ Φ.joined( items ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).φ ⟧" $-              testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--abridged=20", "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["  <deferred symbol=\"𝜎2\" by=\"morph\" at=\"Φ.y\" of=\"Φ.joined.step\"><with><attr name=\"tup\">𝜎1</attr></with><e>𝑛·0·2</e></deferred>"]--        it "writes no object for a deferred copy of a formation the world does not declare" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withStdin "⟦ wrap(v) ↦ ⟦⟧, y ↦ Φ.wrap( v ↦ ⟦ b(x) ↦ ⟦ φ ↦ x.next ⟧ ⟧ ).v.b( x ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" $-              testCLISucceeded ["morph", "--deep", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["  <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ.y\" of=\"⟦ x ↦ ∅, φ ↦ x.next ⟧\"><attr name=\"x\">𝜎1</attr></applied>", "  <deferred symbol=\"𝜎2\" by=\"morph\" at=\"Φ.y\"><e>𝑛·0·2</e></deferred>"]--        it "writes the copy a cut answers as a call of the object it was made of" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_loop\n  morph:\n    𝑛1: $.x\n  𝑛: 𝑛1\n") $ \loops ->-              withStdin "⟦ num(φ) ↦ ⟦⟧, box(n) ↦ ⟦ φ ↦ Φ.loop( x ↦ Φ.box( n ↦ ξ.n ) ) ⟧, loop(x) ↦ L_loop:λ, y ↦ Φ.loop( x ↦ Φ.box( n ↦ Φ.num( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ loops, "--deep", "--acyclic=plausible", "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["        <looped symbol=\"𝜎2\" by=\"morph\" match=\"plausible\" at=\"Φ.a🌵0.φ\" of=\"Φ.box\"><with><attr name=\"n\">𝜎1</attr></with><e>𝑛·0·1</e></looped>"]-         it "writes no 'minted' element for a firing minting nothing" $           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream@@ -2821,20 +2758,13 @@               ["morph", "--symbolic=" ++ table, "--locator=Q.x", "--max-firings=64", "--partial", "--flat", "--hide-rho", "--sweet"]               ["⊥"] -      it "parks the spent --max-firings budget with --deep and --partial" $-        splitting $ \table ->-          withStdin split $-            testCLISucceeded-              ["morph", "--symbolic=" ++ table, "--deep", "--max-firings=64", "--partial", "--flat", "--hide-rho", "--sweet"]-              ["x ↦ Φ.s.foo"]-       it "fires no more λ functions than --max-firings allows" $         splitting $ \table ->           withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do             hClose stream             withStdin split $               testCLISucceeded-                ["morph", "--symbolic=" ++ table, "--deep", "--max-firings=64", "--partial", "--protocol=" ++ path, "--quiet"]+                ["morph", "--symbolic=" ++ table, "--locator=Q.x", "--max-firings=64", "--partial", "--protocol=" ++ path, "--quiet"]                 []             records <- readProtocol path             length (filter (isInfixOf "𝔼(L_split)") (lines records)) `shouldBe` 64@@ -2870,16 +2800,15 @@                 ["dataize", "--symbolic=" ++ table, "--locator=Q.x", "--max-seconds=1"]                 ["[ERROR]: Evaluation did not finish before reaching the limit of seconds: --max-seconds=1"] -      forM_ [["--locator=Q.x", "--partial"], ["--deep", "--partial"]] $ \opts ->-        it ("fails once the --max-seconds budget is spent with " ++ unwords opts) $-          ladder $ \table ->-            bounded $-              withStdin rungs $-                testCLIFailed-                  (["morph", "--symbolic=" ++ table, "--max-seconds=1"] ++ opts)-                  ["[ERROR]: Evaluation did not finish before reaching the limit of seconds: --max-seconds=1"]+      it "fails once the --max-seconds budget is spent with --partial" $+        ladder $ \table ->+          bounded $+            withStdin rungs $+              testCLIFailed+                ["morph", "--symbolic=" ++ table, "--max-seconds=1", "--locator=Q.x", "--partial"]+                ["[ERROR]: Evaluation did not finish before reaching the limit of seconds: --max-seconds=1"] -      forM_ [["--locator=Q.x"], ["--locator=Q.x", "--partial"], ["--deep", "--partial"], ["--deep", "--partial", "--jobs=4"]] $ \opts ->+      forM_ [["--locator=Q.x"], ["--locator=Q.x", "--partial"]] $ \opts ->         it ("writes the timeout as the last line of the protocol with " ++ unwords opts) $           ladder $ \table ->             withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do@@ -2892,14 +2821,14 @@               records <- readProtocol path               dropWhile (== ' ') (last (lines records)) `shouldBe` "timeout(1)" -      it "writes the timeout once to the XML protocol of a deep run" $+      it "writes the timeout once to the XML protocol" $         ladder $ \table ->           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream             bounded $               withStdin rungs $                 testCLIFailed-                  ["morph", "--symbolic=" ++ table, "--deep", "--partial", "--max-seconds=1", "--protocol=" ++ path, "--quiet"]+                  ["morph", "--symbolic=" ++ table, "--locator=Q.x", "--partial", "--max-seconds=1", "--protocol=" ++ path, "--quiet"]                   ["--max-seconds=1"]             records <- readProtocol path             length (filter (isInfixOf "<timeout limit=\"1\" by=\"morph\" at=\"") (lines records)) `shouldBe` 1@@ -2916,73 +2845,6 @@             document <- X.readFile X.def path             X.nameLocalName (X.elementName (X.documentRoot document)) `shouldBe` T.pack "protocol" -    describe "--jobs" $ do-      let twins = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], a -> 7.plus( 5.plus( 6 ) ), b -> 7.plus( 5.plus( 6 ) ) ]]"-          recorded :: [String] -> IO [String]-          recorded extra =-            withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-              hClose stream-              withStdin twins $-                testCLISucceeded (["morph", symbolic, "--deep", "--protocol=" ++ path, "--quiet"] ++ extra) []-              lines <$> readProtocol path-          untaued :: String -> String-          untaued [] = []-          untaued text-            | "a🌵" `isPrefixOf` text = "a🌵" ++ untaued (dropWhile (\ch -> isDigit ch || ch == '-') (drop 2 text))-          untaued (ch : rest) = ch : untaued rest-      it "prints the answer one walk over the bindings prints" $-        withStdin twins $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--acyclic=proven", "--jobs=3", "--flat", "--hide-rho", "--sweet"]-            ["a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧, b ↦ ⟦ φ ↦ 𝜎4:λ, plus(x) ↦ L_number_plus:λ ⟧"]--      it "writes the firings of a binding after those of the bindings before it" $-        recorded ["--jobs=2"]-          >>= (`shouldBe` ["  𝕄(Φ.a):", "    𝔼(L_number_plus):", "    𝔼(L_number_plus):", "  𝕄(Φ.b):", "    𝔼(L_number_plus):", "    𝔼(L_number_plus):"]) . filter (\line -> any (`isPrefixOf` line) ["  𝕄(", "    𝔼("])--      it "numbers the symbols of the protocol the way the answer numbers them" $-        recorded ["--jobs=2"] >>= (`shouldSatisfy` elem "      𝑛·4·3 := Φ.number( φ ↦ ⟦ λ ⤍ 𝜎4 ⟧ )  # 𝑛")--      it "names what a binding mints after the binding" $-        recorded ["--jobs=2"] >>= (`shouldSatisfy` any (isInfixOf "𝔻(Φ.a🌵4-0)"))--      it "writes the protocol one walk writes, the names a binding mints apart" $ do-        one <- recorded ["--jobs=1"]-        many <- recorded ["--jobs=4"]-        map untaued many `shouldBe` map untaued one--      it "writes the same protocol however many workers it is given" $ do-        few <- recorded ["--jobs=2"]-        many <- recorded ["--jobs=5"]-        many `shouldBe` few--      it "writes the step files of a binding after those of the bindings before it" $-        withTempDirectory "phino-steps-jobs" $ \dir ->-          withStdin "[[ a -> [[ x -> 1, y -> $.x ]], b -> [[ x -> 2, y -> $.x ]] ]]" $ do-            testCLISucceeded-              ["morph", "--deep", "--steps-dir=" ++ dir, "--quiet", "--jobs=5", "--flat", "--hide-rho", "--sweet"]-              []-            files <- sort <$> listDirectory dir-            contents <- mapM (\file -> readFile (dir ++ "/" ++ file)) files-            contents `shouldSatisfy` elem "1"-            contents `shouldSatisfy` elem "2"-            let named value = fst <$> find ((== value) . snd) (zip files contents)-            named "1" `shouldSatisfy` (< named "2")--      it "keeps a memo of its own for every binding under plausible" $-        withStdin twins $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--acyclic=plausible", "--jobs=2", "--flat", "--hide-rho", "--sweet"]-            ["b ↦ ⟦ φ ↦ 𝜎4:λ, plus(x) ↦ L_number_plus:λ ⟧"]--      it "fails with non-positive --jobs" $-        withStdin twins $-          testCLIFailed ["morph", "--deep", "--jobs=0"] ["--jobs must be positive"]--      it "fails with --jobs above one and no --deep" $-        withStdin twins $-          testCLIFailed ["morph", "--jobs=2"] ["The option --jobs requires --deep, since only the deep walk runs on several workers"]-     it "parks the spent budget as a residual with --partial" $       withStdin "⟦ φ ↦ 5.gt(Φ.nan) ⟧" $         testCLISucceeded@@ -3001,48 +2863,6 @@             ["morph", "--locator=Q.@", "--partial", "--flat", "--hide-rho"]             ["⟦ λ ⤍ Sym_arg_0 ⟧.foo"] -    describe "--deep" $ do-      let program =-            "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, \-            \number(φ) -> [[ times(^, x) -> [[ L> L_number_times ]] ]], \-            \bar(x) -> [[ L> L_bar ]], \-            \demo -> [[ foo -> [[ n -> 3, @ -> Q.bar( $.n.times( 5 ).times( 7 ) ) ]] ]] ]]"-      it "answers the formation as it was written without the flag" $-        withStdin program $-          testCLISucceeded-            ["morph", symbolic, "--inside=Q.demo.foo", "--sweet", "--hide-rho", "--flat"]-            ["⟦ n ↦ 3, φ ↦ Φ.bar( n.times( 5 ).times( 7 ) ) ⟧"]--      it "reduces every binding it can and leaves the rest in place" $-        withStdin program $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--inside=Q.demo.foo", "--sweet", "--hide-rho", "--flat"]-            ["⟦ n ↦ 3, φ ↦ Φ.bar( ⟦ φ ↦ 𝜎2:λ, times(x) ↦ L_number_times:λ ⟧ ) ⟧"]--      it "fires the bare saturated λ-formation mf hands back" $-        withStdin chained $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--locator=Q.@", "--sweet", "--hide-rho", "--flat"]-            ["⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧"]--      it "keeps the object model intact while it folds the program" $-        withStdin program $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--sweet", "--hide-rho", "--flat"]-            [ "number(φ) ↦ ⟦ times(x) ↦ L_number_times:λ ⟧"-            , "demo ↦ ⟦ n ↦ 3, φ ↦ Φ.bar( ⟦ φ ↦ 𝜎2:λ, times(x) ↦ L_number_times:λ ⟧ ) ⟧:foo"-            ]--      it "keeps a binding whose spine got stuck with --partial" $-        withStdin "[[ x -> [[ L> Sym_arg_0 ]].foo ]]" $-          testCLISucceeded-            ["morph", "--deep", "--partial", "--sweet", "--hide-rho", "--flat"]-            ["Sym_arg_0:λ.foo:x"]--      it "fails on that same spine without --partial" $-        withStdin "[[ x -> [[ L> Sym_arg_0 ]].foo ]]" $-          testCLIFailed ["morph", "--deep"] ["No entry of --symbolic answers the λ function 'Sym_arg_0'"]-     describe "a lenient join" $ do       let strict = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"           lenient = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: true\n  𝑛: 𝑛3\n"@@ -3076,50 +2896,54 @@        it "gets stuck on a fork pairing a bare symbol with a call holding one symbol when the entry is strict" $         withLambdasOf strict $ \forks ->-          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ r ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧ ⟧ ⟧" $-            testCLIFailed ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--deep"] ["No entry of --symbolic answers the λ function 'L_fork'"]+          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧.φ ⟧" $+            testCLIFailed ["morph", "--symbolic=" ++ forks, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"] -      it "joins a bare symbol with a call holding one symbol into a copy of the call under --deep" $+      it "joins a bare symbol with a call holding one symbol into a copy of the call" $         withLambdasOf lenient $ \forks ->-          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ r ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧ ⟧ ⟧" $+          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧.φ ⟧" $             testCLISucceeded-              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--deep", "--sweet", "--hide-rho", "--flat"]-              ["Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ:r"]+              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--sweet", "--hide-rho", "--flat"]+              ["⟦ if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧, φ ↦ if( FF-:Δ, 00-:Δ ) ⟧"]      describe "--acyclic=plausible" $ do-      let twins = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], a -> 7.plus( 5.plus( 6 ) ), b -> 7.plus( 5.plus( 6 ) ) ]]"+      let twins = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], pair(a, b) -> [[ L> L_pair ]], t -> Φ.pair( 7.plus( 5.plus( 6 ) ), 7.plus( 5.plus( 6 ) ) ).φ ]]"+          pairs = T.pack "- λ: L_number_plus\n  dataize:\n    𝛿1: $.ρ\n    𝛿2: $.x\n  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )\n- λ: L_pair\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  evaluate:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: ⟦ φ ↦ ⟦ a ↦ 𝑛3, b ↦ 𝑛4 ⟧ ⟧\n"           recorded :: String -> IO [String]           recorded mode =             withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do               hClose stream-              withStdin twins $-                testCLISucceeded-                  ["morph", symbolic, "--deep", "--acyclic=" ++ mode, "--protocol=" ++ path, "--quiet"]-                  []+              withLambdasOf pairs $ \file ->+                withStdin twins $+                  testCLISucceeded+                    ["morph", "--symbolic=" ++ file, "--locator=Q.t", "--acyclic=" ++ mode, "--protocol=" ++ path, "--quiet"]+                    []               lines <$> readProtocol path-      it "charges a formation once per binding spelling it under proven" $-        withStdin twins $-          testCLIFailed-            ["morph", symbolic, "--deep", "--acyclic=proven", "--max-firings=3"]-            ["[ERROR]: Evaluation did not finish before reaching the limit of firings: --max-firings=3"]+      it "charges a formation once per argument spelling it under proven" $+        withLambdasOf pairs $ \file ->+          withStdin twins $+            testCLIFailed+              ["morph", "--symbolic=" ++ file, "--locator=Q.t", "--acyclic=proven", "--max-firings=4"]+              ["[ERROR]: Evaluation did not finish before reaching the limit of firings: --max-firings=4"]        it "charges a formation once for the run under plausible" $-        withStdin twins $-          testCLISucceeded-            ["morph", symbolic, "--deep", "--acyclic=plausible", "--max-firings=2", "--flat", "--hide-rho", "--sweet"]-            ["b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧"]+        withLambdasOf pairs $ \file ->+          withStdin twins $+            testCLISucceeded+              ["morph", "--symbolic=" ++ file, "--locator=Q.t", "--acyclic=plausible", "--max-firings=3", "--flat", "--hide-rho", "--sweet"]+              ["b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧"] -      it "reduces the operands of a formation once per binding spelling it under proven" $+      it "reduces the operands of a formation once per argument spelling it under proven" $         recorded "proven" >>= (`shouldSatisfy` ((== 4) . length . filter (isInfixOf "𝛿1·")))        it "reduces the operands of a formation once for the run under plausible" $         recorded "plausible" >>= (`shouldSatisfy` ((== 2) . length . filter (isInfixOf "𝛿1·")))        it "writes a recalled firing at its own site under plausible" $-        recorded "plausible" >>= (`shouldSatisfy` ((== 4) . length . filter (isInfixOf "𝔼(L_number_plus)")))+        recorded "plausible" >>= (`shouldSatisfy` ((== 3) . length . filter (isInfixOf "𝔼(L_number_plus)")))        it "answers a recalled firing with the line of the first under plausible" $-        recorded "plausible" >>= (`shouldContain` ["      𝑛·4·2 := 𝑛·2·5  # 𝕄(𝑛·4·1)"])+        recorded "plausible" >>= (`shouldContain` ["      𝑛·4·1 := 𝑛·2·6  # 𝑛"])        it "dataizes to the same datum under plausible" $         withStdin chained $@@ -3149,26 +2973,21 @@             ["morph", symbolic, "--acyclic=proven", "--locator=Q.@", "--sweet", "--hide-rho", "--flat"]             ["⟦ x ↦ 7, λ ⤍ L_number_plus ⟧"] -      it "parks the looping binding and keeps walking with --deep" $+    describe "--max-loops" $ do+      it "fires a looping λ function once more with --max-loops=1" $         loopingLambdas $ \endless ->-          withStdin "⟦ x ↦ ⟦ λ ⤍ L_loop, ρ ↦ ∅ ⟧.foo, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧" $-            testCLISucceeded-              ["morph", "--symbolic=" ++ endless, "--deep", "--acyclic=proven", "--max-steps=4000", "--flat", "--hide-rho"]-              ["⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧"]--      it "answers a copy with a fresh symbol once it cuts the φ of the copy" $-        withLambdasOf (T.pack "- λ: L_loop\n  morph:\n    𝑛1: $.x\n  𝑛: 𝑛1\n") $ \loops ->-          withStdin "⟦ box(n) ↦ ⟦ φ ↦ Φ.loop( x ↦ Φ.box( n ↦ ξ.n ) ) ⟧, loop(x) ↦ L_loop:λ, y ↦ Φ.loop( x ↦ Φ.box( n ↦ ⟦ Δ ⤍ 01- ⟧ ) ) ⟧" $-            testCLISucceeded-              ["morph", "--symbolic=" ++ loops, "--deep", "--acyclic=proven", "--locator=Q.y", "--flat", "--hide-rho", "--sweet"]-              ["𝜎1:λ"]+          withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do+            hClose stream+            withStdin "⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧" $+              testCLISucceeded+                ["morph", "--symbolic=" ++ endless, "--locator=Q.x", "--acyclic=proven", "--max-loops=1", "--protocol=" ++ path, "--quiet"]+                []+            records <- readProtocol path+            length (filter (isInfixOf "𝔼(L_loop)") (lines records)) `shouldBe` 2 -      it "answers a cut copy with the symbol one walk gives it whatever --jobs says" $-        withLambdasOf (T.pack "- λ: L_loop\n  morph:\n    𝑛1: $.x\n  𝑛: 𝑛1\n- λ: L_mint\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n") $ \loops ->-          withStdin "⟦ mint ↦ L_mint:λ, box(n) ↦ ⟦ k ↦ Φ.mint, φ ↦ Φ.loop( x ↦ Φ.box( n ↦ ξ.n ) ) ⟧, loop(x) ↦ L_loop:λ, y ↦ Φ.loop( x ↦ Φ.box( n ↦ ⟦ Δ ⤍ 01- ⟧ ) ) ⟧" $-            testCLISucceeded-              ["morph", "--symbolic=" ++ loops, "--deep", "--acyclic=proven", "--jobs=2", "--locator=Q.y", "--flat", "--hide-rho", "--sweet"]-              ["𝜎2:λ"]+      it "refuses the flag without --acyclic" $+        withStdin "⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧" $+          testCLIFailed ["morph", "--locator=Q.x", "--max-loops=3"] ["The option --max-loops requires --acyclic, since only --acyclic cuts a loop"]      describe "fails" $ do       it "with --output=xmir on a top formation of several bindings" $
test/DataizeSpec.hs view
@@ -22,7 +22,7 @@ import Dataize (Outcome (..), dataize, dataize', reduction) import Deps (Judgment (..), State, dontSaveEval, dontSaveStep) import Engine (Engine (_normal), building)-import Evaluate (evaluation, fired)+import Evaluate (evaluation) import Files (allPathsIn) import Fixtures (defaultReduceContext, fixtureLambdas, linked, loopingLambdas, overdue, primitives, recorded, withLambdas) import GHC.Generics (Generic)@@ -173,14 +173,14 @@       looping $ \endless -> do         expr <- parseExpressionThrows "⟦ @ ↦ ⟦ λ ⤍ L_loop ⟧ ⟧"         minted <- newIORef 0-        dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty endless (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)+        dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True False Nothing 0 Dataization [] Map.empty endless (building linked) reduction evaluation dontSaveStep dontSaveEval linked)           `shouldThrow` (\e -> "--max-steps=40" `isInfixOf` show (e :: SomeException))      it "parks the step limit as a residual with --partial" $       looping $ \endless -> do         expr <- parseExpressionThrows "⟦ @ ↦ ⟦ λ ⤍ L_loop ⟧ ⟧"         minted <- newIORef 0-        (outcome, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True True False 1 Nothing Dataization [] Map.empty endless (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)+        (outcome, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True True Nothing 0 Dataization [] Map.empty endless (building linked) reduction evaluation dontSaveStep dontSaveEval linked)         case outcome of           Residual _ -> pure ()           Dataized bts -> expectationFailure ("expected a residual, dataized to " ++ show bts)@@ -265,12 +265,12 @@     forM_       [         ( "--max-cycles"-        , \minted -> ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked+        , \minted -> ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False Nothing 0 Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked         , "--max-cycles=0"         )       ,         ( "--max-depth"-        , \minted -> ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked+        , \minted -> ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False Nothing 0 Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked         , "--max-depth=0"         )       ]@@ -283,12 +283,12 @@     it "does not throw without --depth-sensitive even once --max-depth is exhausted" $ do       expr <- parseExpressionThrows boxed       minted <- newIORef 0-      (value, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)+      (value, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False Nothing 0 Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked)       value `shouldBe` Dataized (BtOne "00")     it "throws once --max-cycles is exhausted even without --depth-sensitive" $ do       expr <- parseExpressionThrows boxed       minted <- newIORef 0-      dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)+      dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False Nothing 0 Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked)         `shouldThrow` (\e -> "--max-cycles=0" `isInfixOf` show (e :: SomeException))    describe "labels every step with a defined rule or operation" $ do
test/DepsSpec.hs view
@@ -5,13 +5,13 @@  module DepsSpec where -import AST (Argument (ArTau), Attribute (AtLabel, AtPhi), Binding (BiLambda, BiTau), Bytes (BtMany, BtOne), Expression (ExApplication, ExDispatch, ExFormation, ExRoot, ExXi), Function (FnSymbol), symbols)+import AST (Argument (ArTau), Attribute (AtLabel, AtPhi), Binding (BiLambda, BiTau), Bytes (BtMany, BtOne), Expression (ExApplication, ExDispatch, ExFormation, ExRoot, ExXi), Function (FnSymbol)) import Control.Exception (bracket) import Control.Monad (replicateM_, when) import Data.IORef (modifyIORef', newIORef, readIORef) import Data.List (isInfixOf, isPrefixOf) import Data.Time.Clock.POSIX (getPOSIXTime)-import Deps (Acyclic (Proven), Evaluation (EvAnswer, EvApplied, EvBuilt, EvComputed, EvData, EvDeferred, EvDelta, EvFiring, EvJoined, EvLooped, EvMinted, EvRun, EvStarted, EvTerm, EvTerminate), Judgment (Dataization, Morphing), Nesting (..), Protocol (..), dontSaveEval, dontSaveStep, emptyNesting, emptyProgress, emptyProtocol, endEval, endEvalXml, perSecond, progressed, renumbered, resited, saveStep)+import Deps (Acyclic (Proven), Evaluation (EvAnswer, EvApplied, EvBuilt, EvData, EvDelta, EvFiring, EvLooped, EvRun, EvStarted, EvTerm, EvTerminate), Judgment (Dataization, Morphing), Nesting (..), Protocol (..), dontSaveEval, dontSaveStep, emptyNesting, emptyProgress, emptyProtocol, endEval, endEvalXml, perSecond, progressed, resited, saveStep) import Fixtures (readUtf8, recorded, recordedXml) import GHC.Clock (getMonotonicTime) import Logger (LogLevel (DEBUG, ERROR, INFO), setLogConfig)@@ -108,48 +108,6 @@       captured <- hCapture_ [stderr] (progressed cursor 0 (const (pure "Φ.q")) dontSaveEval (EvRun Morphing "Φ.k"))       captured `shouldBe` "" -  describe "renumbered" $ do-    it "raises the symbols a record names above the floor" $-      case renumbered 3 10 (EvMinted 2 5 [Left 4, Left 1, Right (BtOne "7C")]) of-        EvMinted _ minted operands -> (minted, operands) `shouldBe` (15, [Left 14, Left 1, Right (BtOne "7C")])-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols the terms of a record carry above the floor" $-      case renumbered 1 6 (EvTerm 4 "𝑛1" (ExFormation [BiLambda (FnSymbol 1)]) (ExFormation [BiLambda (FnSymbol 2)])) of-        EvTerm _ _ operand term -> (symbols operand, symbols term) `shouldBe` ([1], [8])-        _ -> expectationFailure "The record did not stay the record it was"-    it "does not change the depth a record stands at" $-      case renumbered 0 9 (EvJoined 7 1 (2, 3)) of-        EvJoined depth fresh pair -> (depth, fresh, pair) `shouldBe` (7, 10, (11, 12))-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbol a deferred copy stands for and the symbols it carries above the floor" $-      case renumbered 2 5 (EvDeferred 3 4 Morphing (ExFormation [BiTau (AtLabel "x") (ExFormation [BiLambda (FnSymbol 1)]), BiTau (AtLabel "y") (ExFormation [BiLambda (FnSymbol 3)])]) Nothing ExXi) of-        EvDeferred _ fresh _ copy _ _ -> (fresh, symbols copy) `shouldBe` (9, [1, 8])-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols the call a deferred copy stands for carries above the floor" $-      case renumbered 2 5 (EvDeferred 3 4 Morphing (ExFormation []) (Just (ExApplication (ExDispatch ExRoot (AtLabel "box")) (ArTau (AtLabel "x") (ExFormation [BiLambda (FnSymbol 7)])))) ExXi) of-        EvDeferred _ _ _ _ call _ -> fmap symbols call `shouldBe` Just [12]-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbol a cut answers a copy with and the symbols of its call above the floor" $-      case renumbered 2 5 (EvLooped 3 Morphing Proven (ExFormation []) ExXi (Just (4, Just (ExApplication (ExDispatch ExRoot (AtLabel "box")) (ArTau (AtLabel "n") (ExFormation [BiLambda (FnSymbol 7)])))))) of-        EvLooped _ _ _ _ _ answer -> fmap (fmap (fmap symbols)) answer `shouldBe` Just (9, Just [12])-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols an application and the object it made carry above the floor" $-      case renumbered 2 5 (EvApplied 3 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "box")) (ArTau (AtLabel "x") (ExFormation [BiLambda (FnSymbol 4)]))) (ExFormation [BiTau (AtLabel "x") (ExFormation [BiLambda (FnSymbol 1)])]) ExXi) of-        EvApplied _ _ call object _ -> (symbols call, symbols object) `shouldBe` ([9], [1])-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols an object carries before and after the walk computed inside it above the floor" $-      case renumbered 3 4 (EvComputed 2 (ExFormation [BiTau (AtLabel "ш") (ExFormation [BiLambda (FnSymbol 2)])]) (ExFormation [BiTau (AtLabel "ш") (ExFormation [BiLambda (FnSymbol 6)])])) of-        EvComputed _ before after -> (symbols before, symbols after) `shouldBe` ([2], [10])-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols the site of a started judgment carries above the floor" $-      case renumbered 1 3 (EvStarted 2 Morphing (ExFormation [BiLambda (FnSymbol 5)])) of-        EvStarted _ _ site -> symbols site `shouldBe` [8]-        _ -> expectationFailure "The record did not stay the record it was"-    it "raises the symbols the message of a raising fork carries above the floor" $-      case renumbered 2 5 (EvTerminate 3 (Just (Left 1)) "right" "𝑛4" (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёрш")) (ArTau AtPhi (ExFormation [BiLambda (FnSymbol 6)]))))) of-        EvTerminate _ _ _ _ message -> fmap symbols message `shouldBe` Just [11]-        _ -> expectationFailure "The record did not stay the record it was"-   describe "resited" $ do     it "moves an application made at one site to another" $       case resited (ExDispatch ExRoot (AtLabel "ёж")) ExXi (EvApplied 2 Morphing (ExFormation []) (ExFormation []) (ExDispatch ExRoot (AtLabel "ёж"))) of@@ -173,7 +131,7 @@       (_, written) <- recorded (\record -> record (EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExRoot (AtLabel "w"))))       written `shouldBe` "  𝑛·0·1 := Φ.ёж( q ↦ ⟦⟧ )  # 𝕄(Φ.w)\n"     it "spells an object an application made by its name on a later line" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_щ" Morphing ExRoot, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot, EvTerm 2 "𝑛1" (ExDispatch ExXi (AtLabel "z")) (ExFormation [BiTau (AtLabel "z") (ExFormation [BiTau (AtLabel "q") (ExFormation [])]), BiTau (AtLabel "у") (ExFormation [])])])+      (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_щ" Morphing ExRoot, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot, EvTerm 2 "𝑛1" Morphing (ExDispatch ExXi (AtLabel "z")) (ExFormation [BiTau (AtLabel "z") (ExFormation [BiTau (AtLabel "q") (ExFormation [])]), BiTau (AtLabel "у") (ExFormation [])])])       last (lines written) `shouldBe` "    𝑛1·1 := ⟦ z ↦ 𝑛·1·1, у ↦ ⟦⟧ ⟧  # 𝕄(ξ.z)"     it "numbers the answer of a firing past the objects applications made inside it" $ do       (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ю" Morphing ExRoot, EvBuilt 2 (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))), EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot, EvAnswer 2 (ExFormation [BiTau (AtLabel "q") (ExFormation [])])])@@ -184,15 +142,6 @@     it "spells an application made again by its head and argument rather than by the name of the first one" $ do       (_, written) <- recorded (\record -> replicateM_ 2 (record (EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot)))       last (lines written) `shouldBe` "  𝑛·0·2 := Φ.ёж( q ↦ ⟦⟧ )  # 𝕄(Φ)"-    it "writes a deferred copy as the call it was made of even when an earlier application spelled that call" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)]))) (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)])]) ExRoot, EvDeferred 1 4 Morphing (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)])]) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)])))) ExRoot])-      last (lines written) `shouldBe` "  deferred(𝜎4) := Φ.ёж( q ↦ 𝜎3:λ )  # 𝕄(Φ)"-    it "spells an object the walk computed inside by the name its application gave it" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ъ" Morphing ExRoot, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф")))) (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф"))]) ExRoot, EvComputed 2 (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф"))]) (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 8)])]), EvTerm 2 "𝑛1" (ExDispatch ExXi (AtLabel "z")) (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 8)])])])-      last (lines written) `shouldBe` "    𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.z)"-    it "spells out an object the walk computed inside when no application named it" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvComputed 1 (ExFormation [BiTau (AtLabel "ю") ExRoot]) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (FnSymbol 3)])]), EvTerm 1 "𝑛1" (ExDispatch ExXi (AtLabel "z")) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (FnSymbol 3)])])])-      last (lines written) `shouldBe` "  𝑛1·0 := 𝜎3:λ:ю  # 𝕄(ξ.z)"     it "writes the datum the delta rule found under a name of its own" $ do       (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ж" Dataization ExRoot, EvDelta 2 (BtMany ["1F", "E0"]), EvDelta 2 (BtOne "33")])       last (lines written) `shouldBe` "    𝛿·1·2 := 33-"@@ -233,7 +182,7 @@       (_, written) <- recorded (\record -> mapM_ record [EvRun Dataization "Φ", EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "w")), EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExRoot (AtLabel "w")), EvDelta 1 (BtOne "0B"), EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "w")), EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExRoot (AtLabel "w"))])       length (filter (== "  𝕄(Φ.w):") (lines written)) `shouldBe` 2     it "keeps the mode of a cut made at the site of its block" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation []) ExRoot Nothing])+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation []) ExRoot])       last (lines written) `shouldBe` "  looped(⟦⟧)  # proven"     it "names the block over an answer by the line that built it" $ do       (_, written) <- recorded (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ф" Dataization ExRoot, EvBuilt 2 (ExDispatch ExRoot (AtLabel "ю")), EvStarted 2 Morphing (ExDispatch ExRoot (AtLabel "ю")), EvApplied 3 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExRoot (AtLabel "ю"))])@@ -244,9 +193,6 @@     it "writes no message for a fork that raises with none" $ do       (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ф" Morphing ExRoot, EvTerminate 2 (Just (Right (BtOne "0D"))) "left" "𝑛3" Nothing])       last (lines written) `shouldBe` "    terminate(0D-, left)  # 𝑛3"-    it "writes no line for an object the walk computed inside" $ do-      (_, written) <- recorded (\record -> record (EvComputed 1 (ExFormation [BiTau (AtLabel "ю") ExRoot]) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (FnSymbol 3)])])))-      written `shouldBe` ""    describe "saveEvalXml" $ do     it "writes an application as an element naming its head and holding its argument" $ do@@ -270,9 +216,6 @@     it "spells the datum of an operand by the name the delta rule just gave it in the markup" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvDelta 3 (BtOne "7F"), EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Right (BtOne "7F"))])       lines written `shouldContain` ["    <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"]-    it "spells an object the walk computed inside by its name in a later element" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф")))) (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф"))]) ExRoot, EvComputed 1 (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ф"))]) (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 8)])]), EvTerm 1 "𝑛1" (ExDispatch ExXi (AtLabel "z")) (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 8)])])])-      lines written `shouldContain` ["  <bind meta=\"𝑛1·0\">𝑛·0·1</bind>"]     it "writes a block as an element once an element stands in it" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "w")), EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExRoot (AtLabel "w"))])       lines written `shouldContain` ["  <morph at=\"Φ.w\">"]
test/EvaluateSpec.hs view
@@ -26,7 +26,7 @@ import Lining (LineFormat (SINGLELINE)) import Margin (defaultMargin) import Matcher (substEmpty)-import Morph (ReduceContext (..), Steps (..), emptyState, execBuildTerm, memoized, morph)+import Morph (ReduceContext (..), Steps (..), emptyState, execBuildTerm, memoized, morph, tallied) import Parser (parseExpressionThrows) import Printer (printExpression, printExpression', printExpressionHidingRho') import Sugar (SugarType (SWEET))@@ -39,10 +39,10 @@   { symbolic :: String   , location :: Maybe String   , input :: String-  , deep :: Maybe Bool   , partial :: Maybe Bool   , acyclic :: Maybe String   , steps :: Maybe Int+  , firings :: Maybe Int   , protocol :: String   , result :: Maybe String   , fails :: Maybe String@@ -65,14 +65,15 @@     (_, written) <- recorded' hidden $ \record -> do       let mode = named <$> acyclic       cells <- memoized mode+      tally <- tallied firings       base <- defaultReduceContext loc       let ctx =             base-              { _deep = deep == Just True-              , _partial = partial == Just True+              { _partial = partial == Just True               , _acyclic = mode               , _memo = cells               , _steps = Steps (fromMaybe 250 steps) 0+              , _tally = tally               , _symbolic = known               , _saveEval = record               , _opened = Just (1, Morphing, loc)@@ -141,12 +142,13 @@     forM_       [ ("carries no binding at all", ExFormation [])       , ("carries bindings but none of them a λ", ExFormation [BiVoid AtRho])+      , ("decorates another object instead of firing a λ", ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "bool"))])       ]       ( \(desc, form) ->-          it ("answers ⊥ for a formation that " ++ desc) $ do+          it ("answers unchanged a formation that " ++ desc) $ do             answered <- runEvaluate [ArgExpression form, ArgExpression univ]             case answered of-              TeExpression expr -> expr `shouldBe` ExTermination+              TeExpression expr -> expr `shouldBe` form               _ -> expectationFailure "expected TeExpression"       )     it "evaluates a λ-bearing formation to the answer of its entry, normalized" $ do
test/Fixtures.hs view
@@ -41,7 +41,7 @@ import Dataize (reduction) import Deps (Judgment (..), SaveEvalFunc, dontSaveEval, dontSaveStep) import Engine (Engine, building, yaml)-import Evaluate (evaluation, fired)+import Evaluate (evaluation) import GHC.Clock (getMonotonicTime) import Lambdas (Lambdas, emptyLambdas, readLambdas) import Lining (LineFormat (MULTILINE))@@ -55,7 +55,7 @@ defaultReduceContext :: Expression -> IO ReduceContext defaultReduceContext loc = do   minted <- newIORef 0-  pure (ReduceContext loc loc Nothing 25 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Morphing [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)+  pure (ReduceContext loc loc Nothing 25 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False Nothing 0 Morphing [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked)  linked :: Engine linked = fromMaybe yaml compiled
test/LambdasSpec.hs view
@@ -31,6 +31,12 @@     spelled :: (Meta, (Meta, Meta)) -> (T.Text, (T.Text, T.Text))     spelled (meta, (left, right)) = (_spelling meta, (_spelling left, _spelling right)) +evaluates :: Lambdas -> T.Text -> [(T.Text, T.Text)]+evaluates known func = map spelled (maybe [] _evaluated (matched known func))+  where+    spelled :: (Meta, Meta) -> (T.Text, T.Text)+    spelled (meta, source) = (_spelling meta, _spelling source)+ rewriting :: T.Text -> T.Text rewriting lines' = "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  rewrite:\n" <> lines' @@ -149,6 +155,22 @@       known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n")       map (_spelling . fst) (maybe [] _symbolized (matched known "L_fork")) `shouldBe` ["𝑛3"] +    it "reads the metas the lines of 'evaluate' fire" $ do+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  evaluate:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n"+      evaluates known "L_fork" `shouldBe` [("𝑛3", "𝑛1"), ("𝑛4", "𝑛2")]++    it "reads an 'evaluate' line firing what the line above it fired" $ do+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"+      evaluates known "L_fork" `shouldBe` [("𝑛2", "𝑛1"), ("𝑛3", "𝑛2")]++    it "reads a 'rewrite' line rewriting what an 'evaluate' line fired" $ do+      known <- lambdasOf ("- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛1\n  rewrite:\n    𝑛3:\n      of: 𝑛2\n" <> rules <> "  𝑛: 𝑛3\n")+      map rewrote (maybe [] _rewritten (matched known "L_fork")) `shouldBe` [("𝑛3", "𝑛2", 1)]++    it "reads an entry naming no 'evaluate' block at all" $ do+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      evaluates known "L_fork" `shouldBe` []+     it "reads an entry naming no 'join' block at all" $ do       known <- lambdasOf "- λ: L_pair\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"       joins known "L_pair" `shouldBe` []@@ -202,6 +224,13 @@       , ("a rule of 'rewrite' writing a symbol nobody minted", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: naming\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ λ ⤍ 𝜎1 ⟧\n  𝑛: 𝑛2\n", "writes a symbol 𝜎 into its result")       , ("a rule of 'rewrite' reading a meta it never binds", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: unbound\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ φ ↦ 𝑒2 ⟧\n  𝑛: 𝑛2\n", "reads the meta 'e2' it never binds")       , ("a rule of 'rewrite' with no result", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: empty\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n  𝑛: 𝑛2\n", "cannot be read")+      , ("an 'evaluate' line firing a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by a line above it")+      , ("an 'evaluate' line firing what a line below it fired", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛3\n    𝑛3: 𝑛1\n  𝑛: 𝑛2\n", "names no meta bound by 'morph' or by a line above it")+      , ("an 'evaluate' line firing what a 'rewrite' line made", rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  evaluate:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by a line above it")+      , ("an 'evaluate' line bound to what is no expression meta", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝛿1: 𝑛1\n  𝑛: 𝑛1\n", "is not an expression meta")+      , ("an 'evaluate' line firing a meta that came down to data", "- λ: L_fork\n  dataize:\n    𝛿1: $.ρ\n  evaluate:\n    𝑛1: 𝛿1\n  𝑛: 𝑛1\n", "is not an expression meta")+      , ("an 'evaluate' line firing a term instead of a meta", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: $.a\n  𝑛: 𝑛2\n", "is not an expression meta")+      , ("a meta bound by 'morph' and again by 'evaluate'", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_fork' is bound by more than one line")       , ("an answer naming a meta no block binds", "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  𝑛: 𝑛7\n", "reads the meta 'n7' that no block binds")       , ("an operand of 'dataize' reading a meta", "- λ: L_pair\n  dataize:\n    𝛿1: 𝑛5\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "of 'dataize' of λ function 'L_pair' reads the meta 'n5'")       , ("an operand of 'morph' reading a meta", "- λ: L_pair\n  morph:\n    𝑛1: $.x.𝜏1\n  𝑛: 𝑛1\n", "of 'morph' of λ function 'L_pair' reads the meta 't1'")
test/MarginSpec.hs view
@@ -9,6 +9,7 @@ import Bytes (numToBts, strToBts) import CST import Control.Monad (forM_)+import Data.Text qualified as T import Lining (LineFormat (..), toSingleLine) import Margin (withLines, withMargin) import Render (render)@@ -47,6 +48,50 @@     , BiTau (AtLabel "c") (ExFormation [])     ] +wide :: Expression+wide =+  ExFormation+    [ BiTau+        (AtLabel "r")+        (ExApplication (ExDispatch callee (AtLabel "h大家大家大家大家")) (ArTau (AtLabel "cc") ExRoot))+    ]++narrow :: Expression+narrow =+  ExFormation+    [ BiTau+        (AtLabel "r")+        (ExApplication (ExDispatch callee (AtLabel "haaaaaaaaaaaaaaaa")) (ArTau (AtLabel "cc") ExRoot))+    ]++callee :: Expression+callee =+  ExFormation+    [ BiTau (AtLabel "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") (ExFormation [])+    , BiTau (AtLabel "bb") (ExFormation [])+    ]++blocks :: Expression+blocks =+  ExFormation+    [ BiTau (AtLabel "b") (DataString (strToBts "\11035\11035\11035\11035\11035\11035\11035\11035"))+    , BiTau (AtLabel "c") (ExFormation [])+    ]++marks :: Expression+marks =+  ExFormation+    [ BiTau (AtLabel "b") (DataString (strToBts "e\769e\769e\769e\769e\769e\769e\769e\769"))+    , BiTau (AtLabel "c") (ExFormation [])+    ]++precomposed :: Expression+precomposed =+  ExFormation+    [ BiTau (AtLabel "b") (DataString (strToBts "\233\233\233\233\233\233\233\233"))+    , BiTau (AtLabel "c") (ExFormation [])+    ]+ spec :: Spec spec = do   describe "withMargin on EX_FORMATION" $ do@@ -215,6 +260,19 @@         it ("wraps " ++ desc ++ " at margin " ++ show margin ++ " as the margin pass does") $           withLines MULTILINE margin (expressionToCST expression)             `shouldBe` withMargin margin (expressionToCST expression)+  describe "withMargin measures the last line of a call by display width" $+    it "breaks the argument of a call whose callee ends in wide characters" $+      rows wide `shouldBe` rows narrow++  describe "withMargin measures every wide character as two columns" $+    it "wraps a string of wide characters the way it wraps the same count of CJK ones" $+      rows blocks `shouldBe` rows wideStringFormation++  describe "withMargin gives a zero-width character no column" $ do+    it "keeps a string of combining marks on one line, as its precomposed twin stays" $+      rows marks `shouldBe` rows precomposed+    it "keeps that string on one line within the margin" $+      rows marks `shouldBe` 1   where     terms :: [(String, Expression)]     terms =@@ -230,3 +288,6 @@       ]     margins :: [Int]     margins = [0, 1, 40, 80, 1000]++    rows :: Expression -> Int+    rows expression = length (T.lines (render (withMargin 30 (expressionToCST expression))))
test/MorphSpec.hs view
@@ -23,11 +23,11 @@ import Deps (Acyclic (..), Judgment (..), State, Term (TeExpression)) import Engine (Engine (_normal)) import Files (allPathsIn)-import Fixtures (defaultReduceContext, fixtureLambdas, linked, overdue, primitives, withLambdas, withLambdasOf)+import Fixtures (defaultReduceContext, fixtureLambdas, linked, overdue, primitives) import GHC.Clock (getMonotonicTime) import GHC.Generics (Generic) import Inference (Conclusion (Answered), Premises (Concludes, Morphs), direct)-import Lambdas (Lambdas, emptyLambdas, readLambdas)+import Lambdas (Lambdas, emptyLambdas) import Matcher (substEmpty) import Morph (Deadline (..), ReduceContext (..), emptyState, enter, execBuildTerm, inferred, insideUniverse, morph, morph') import Parser (parseExpressionThrows)@@ -58,8 +58,8 @@   }   deriving (Generic, Show, FromJSON) -testMorph :: Lambdas -> Bool -> FilePath -> Expectation-testMorph known deep pth = do+testMorph :: Lambdas -> FilePath -> Expectation+testMorph known pth = do   MorphPack{..} <- Decode.decodeFileThrow pth   expr <- parseExpressionThrows (if model == Just True then primitives input else input)   seedTaus expr@@ -67,8 +67,7 @@   base <- defaultReduceContext loc   let ctx =         base-          { _deep = deep-          , _partial = partial == Just True+          { _partial = partial == Just True           , _symbolic = if symbolic == Just True then known else emptyLambdas           }   case (result, fails) of@@ -87,7 +86,7 @@   describe "morph" $ do     let resources = "test-resources/morph-packs"     packs <- runIO (allPathsIn resources)-    forM_ packs (\pth -> it (makeRelative resources pth) (testMorph known False pth))+    forM_ packs (\pth -> it (makeRelative resources pth) (testMorph known pth))      it "reports the chain of steps oldest first" $ do       expr <- parseExpressionThrows "[[ D> 00- ]]"@@ -104,20 +103,6 @@       _ <- morph expr emptyState ctx{_saveStep = const (modifyIORef' saved (+ 1))}       readIORef saved `shouldReturn` 2 -  describe "morph with '_deep'" $ do-    let resources = "test-resources/morph-deep-packs"-    packs <- runIO (allPathsIn resources)-    forM_ packs (\pth -> it (makeRelative resources pth) (testMorph known True pth))--    describe "a dispatch naming an attribute of the formation it stands on" $-      it "cannot fire the λ the dispatch does not demand" $-        withLambdasOf "- λ: L_answer\n  𝑛: ⟦ Δ ⤍ FF- ⟧\n" $ \file -> do-          box <- readLambdas file-          world <- parseExpressionThrows "[[ foo -> [[ f -> [[ a -> ?, @ -> $.a, L> L_answer ]] ]], x -> Q.foo.f( a -> [[ D> 01- ]] ).@ ]]"-          ctx <- withLambdas box <$> defaultReduceContext ExRoot-          (morphed, _, _) <- morph world emptyState ctx{_deep = True}-          morphed `shouldBe` world-   describe "morph'" $     test'       morph'@@ -268,3 +253,17 @@       ctx <- enter (formation ExRoot 1300) base{_acyclic = Just Plausible, _deadline = Just (Deadline 31 due)}       timeout 10000000 (enter (formation ExXi 1700) ctx)         `shouldThrow` (\e -> "--max-seconds=31" `isInfixOf` show (e :: SomeException))++  describe "enters a formation again as many times as --max-loops allows" $ do+    it "enters a formation holding the one entered above it under --max-loops=1" $ do+      let formation :: Int -> Expression+          formation depth = ExFormation [BiTau (AtLabel "kv") (iterate (`ExDispatch` AtLabel "zu") ExRoot !! depth), BiLambda (Function "L_hop")]+      base <- defaultReduceContext ExRoot+      ctx <- enter (formation 4) =<< enter (formation 3) base{_acyclic = Just Plausible, _maxLoops = 1}+      length (concat ctx._entered) `shouldBe` 2+    it "cuts a formation holding the two entered above it under --max-loops=1" $ do+      let formation :: Int -> Expression+          formation depth = ExFormation [BiTau (AtLabel "qe") (iterate (`ExDispatch` AtLabel "yo") ExRoot !! depth), BiLambda (Function "L_kit")]+      base <- defaultReduceContext ExRoot+      (enter (formation 7) =<< enter (formation 6) =<< enter (formation 5) base{_acyclic = Just Plausible, _maxLoops = 1})+        `shouldThrow` (\e -> "already inside" `isInfixOf` show (e :: SomeException))
− test/PoolSpec.hs
@@ -1,35 +0,0 @@--- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com--- SPDX-License-Identifier: MIT--module PoolSpec where--import Control.Concurrent (threadDelay)-import Control.Exception (ErrorCall (..), throwIO, try)-import Data.IORef (atomicModifyIORef', newIORef, readIORef)-import Pool (pooled)-import Test.Hspec (Spec, describe, it, shouldReturn)--spec :: Spec-spec = describe "Pool" $ do-  it "folds what the actions gave in the order they were listed" $-    pooled 3 [threadDelay (pause * 1000) >> pure pause | pause <- [9, 1, 7, 2, 5]] (\acc pause -> pure (acc ++ [pause])) []-      `shouldReturn` [9, 1, 7, 2, 5 :: Int]-  it "runs no more actions at once than it was given" $ do-    running <- newIORef (0 :: Int)-    most <- newIORef (0 :: Int)-    let action :: IO ()-        action = do-          now <- atomicModifyIORef' running (\count -> (count + 1, count + 1))-          atomicModifyIORef' most (\peak -> (max peak now, ()))-          threadDelay 2000-          atomicModifyIORef' running (\count -> (count - 1, ()))-    pooled 2 (replicate 7 action) (\_ _ -> pure ()) ()-    readIORef most `shouldReturn` 2-  it "throws what an action threw once the actions before it are folded" $ do-    folded <- newIORef []-    outcome <- try (pooled 2 [pure 'x', throwIO (ErrorCall "broken"), pure 'z'] (\_ ch -> atomicModifyIORef' folded (\seen -> (seen ++ [ch], ()))) ())-    (,) outcome <$> readIORef folded `shouldReturn` (Left (ErrorCall "broken"), "x")-  it "folds nothing where it is given no action" $-    pooled 4 ([] :: [IO Int]) (\acc val -> pure (acc + val)) 42 `shouldReturn` 42-  it "folds every action where it may run one at a time" $-    pooled 1 (map pure [3, 8, 1]) (\acc val -> pure (acc * 10 + val)) 0 `shouldReturn` (381 :: Int)
test/TauSpec.hs view
@@ -6,8 +6,8 @@ module TauSpec where  import AST-import Control.Monad (forM_, join, replicateM)-import Tau (freshTau, seedTaus, tausOf)+import Control.Monad (forM_, replicateM)+import Tau (freshTau, seedTaus) import Test.Hspec (Spec, describe, it, shouldBe)  spec :: Spec@@ -40,17 +40,3 @@         name <- freshTau         name `shouldBe` "a🌵1"     )-  it "mints names carrying the entry they were minted for" $ do-    seedTaus (ExFormation [])-    mint <- tausOf 7-    names <- replicateM 3 mint-    names `shouldBe` ["a🌵7-0", "a🌵7-1", "a🌵7-2"]-  it "skips the names of an entry the document already took" $ do-    seedTaus (ExFormation [BiTau (AtLabel "a🌵4-0") ExRoot, BiTau (AtLabel "a🌵4-1") ExXi])-    name <- join (tausOf 4)-    name `shouldBe` "a🌵4-2"-  it "does not move the cursor the run mints its own names with" $ do-    seedTaus (ExFormation [])-    _ <- tausOf 2 >>= \mint -> replicateM 5 mint-    name <- freshTau-    name `shouldBe` "a🌵0"